web-dev-qa-db-ja.com

メモリ内の表形式データを維持するためのデータ構造?

私のシナリオは次のとおりです。プログラムで広範囲に使用するデータのテーブル(フィールドの握り、100行未満)があります。また、このデータは永続的である必要があるため、CSVとして保存し、起動時にロードします。すべてのオプション(SQLiteでさえも)が私の謙虚な要件には過剰すぎるので、データベースを使用しないことを選択します(また、値を簡単な方法でオフラインで編集できるようにしたいと思います。メモ帳ほど簡単ではありません)。

私のデータは次のようになっていると仮定します(ファイル内では、タイトルなしでコンマで区切られています。これは単なる例です)。

 Row  | Name     | Year   | Priority
------------------------------------
 1    | Cat      | 1998   | 1
 2    | Fish     | 1998   | 2
 3    | Dog      | 1999   | 1 
 4    | Aardvark | 2000   | 1
 5    | Wallaby  | 2000   | 1
 6    | Zebra    | 2001   | 3

ノート:

  1. 行は、ファイルに書き込まれた「実際の」値でも、行番号を表す自動生成値でもかまいません。どちらにしてもメモリに存在します。
  2. 名前は一意です。

データを使用して行うこと:

  1. ID(反復)または名前(直接アクセス)に基づいて行を検索します。
  2. 複数のフィールドに基づいて異なる順序でテーブルを表示します。たとえば、並べ替える必要があります。優先度で次に年、または年で優先度などで.
  3. パラメータのセットに基づいてインスタンスをカウントする必要があります。 1997年から2002年までの年の行数、または1998年で優先順位が2を超える行数など。

私はSQLのこの「叫び」を知っています...

データ構造に最適な選択肢を見つけようとしています。以下にいくつかの選択肢を示します。

行リストのリスト:

a = []
a.append( [1, "Cat", 1998, 1] )
a.append( [2, "Fish", 1998, 2] )
a.append( [3, "Dog", 1999, 1] )
...

列リストのリスト(明らかにadd_rowなどのAPIがあります):

a = []
a.append( [1, 2, 3, 4, 5, 6] )
a.append( ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] )
a.append( [1998, 1998, 1999, 2000, 2000, 2001] )
a.append( [1, 2, 1, 1, 1, 3] )

列リストの辞書(文字列キーを置き換える定数を作成できます):

a = {}
a['ID'] = [1, 2, 3, 4, 5, 6]
a['Name'] = ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] 
a['Year'] = [1998, 1998, 1999, 2000, 2000, 2001] 
a['Priority'] = [1, 2, 1, 1, 1, 3] 

キーが(行、フィールド)のタプルである辞書:

Create constants to avoid string searching
NAME=1
YEAR=2
PRIORITY=3

a={}
a[(1, NAME)] = "Cat"
a[(1, YEAR)] = 1998
a[(1, PRIORITY)] = 1
a[(2, NAME)] = "Fish"
a[(2, YEAR)] = 1998
a[(2, PRIORITY)] = 2
...

そして、他の方法もあると確信しています...しかし、それぞれの方法には、私の要件(複雑な順序付けとカウント)に関して不利な点があります。

推奨されるアプローチは何ですか?

編集:

明確にするために、パフォーマンスは私にとって大きな問題ではありません。テーブルは非常に小さいため、ほとんどすべての操作はミリ秒の範囲内に収まると思いますが、これはアプリケーションにとっては問題ではありません。

71
Roee Adler

ルックアップ、ソート、および任意の集計を必要とするメモリ内に「テーブル」があると、実際にはSQLが呼び出されます。 SQLiteを試したと言いましたが、SQLiteがメモリ内のみのデータベースを使用できることに気付きましたか?

connection = sqlite3.connect(':memory:')

その後、SQLiteのすべての機能を使用してメモリ内にテーブルを作成/ドロップ/クエリ/更新することができ、完了時にファイルが残されません。そして、Python 2.5、sqlite3は標準ライブラリにあるため、実際には「過剰」なIMOではありません。

以下は、データベースを作成および設定する方法のサンプルです。

import csv
import sqlite3

db = sqlite3.connect(':memory:')

def init_db(cur):
    cur.execute('''CREATE TABLE foo (
        Row INTEGER,
        Name TEXT,
        Year INTEGER,
        Priority INTEGER)''')

def populate_db(cur, csv_fp):
    rdr = csv.reader(csv_fp)
    cur.executemany('''
        INSERT INTO foo (Row, Name, Year, Priority)
        VALUES (?,?,?,?)''', rdr)

cur = db.cursor()
init_db(cur)
populate_db(cur, open('my_csv_input_file.csv'))
db.commit()

SQLを使用したくない場合は、おそらく辞書のリストを使用する必要があります。

lod = [ ] # "list of dicts"

def populate_lod(lod, csv_fp):
    rdr = csv.DictReader(csv_fp, ['Row', 'Name', 'Year', 'Priority'])
    lod.extend(rdr)

def query_lod(lod, filter=None, sort_keys=None):
    if filter is not None:
        lod = (r for r in lod if filter(r))
    if sort_keys is not None:
        lod = sorted(lod, key=lambda r:[r[k] for k in sort_keys])
    else:
        lod = list(lod)
    return lod

def lookup_lod(lod, **kw):
    for row in lod:
        for k,v in kw.iteritems():
            if row[k] != str(v): break
        else:
            return row
    return None

テストでは次の結果が得られます。

>>> lod = []
>>> populate_lod(lod, csv_fp)
>>> 
>>> pprint(lookup_lod(lod, Row=1))
{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}
>>> pprint(lookup_lod(lod, Name='Aardvark'))
{'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}
>>> pprint(query_lod(lod, sort_keys=('Priority', 'Year')))
[{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'},
 {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'},
 {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'},
 {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'},
 {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'},
 {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}]
>>> pprint(query_lod(lod, sort_keys=('Year', 'Priority')))
[{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'},
 {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'},
 {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'},
 {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'},
 {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'},
 {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}]
>>> print len(query_lod(lod, lambda r:1997 <= int(r['Year']) <= 2002))
6
>>> print len(query_lod(lod, lambda r:int(r['Year'])==1998 and int(r['Priority']) > 2))
0

個人的には、SQLiteバージョンの方が(Pythonで余分な変換コードを使用せずに)型を保持し、将来の要件に合わせて簡単に拡張できるため、SQLiteバージョンの方が好きです。しかし、もう一度言いますが、私はSQLに非常に満足しているので、YMMVです。

76
Rick Copeland

私が知っている非常に古い質問ですが...

A pandasここでは、DataFrameが理想的なオプションのようです。

http://pandas.pydata.org/pandas-docs/version/0.13.1/generated/pandas.DataFrame.html

宣伝文句から

ラベル付けされた軸(行と列)を持つ2次元のサイズ変更可能な、潜在的に異種の表形式データ構造。算術演算は、行ラベルと列ラベルの両方で整列します。 Seriesオブジェクトのdictのようなコンテナと考えることができます。プライマリpandas=データ構造

http://pandas.pydata.org/

30
user5061

私は個人的に行リストのリストを使用します。各行のデータは常に同じ順序であるため、各リストのその要素にアクセスするだけで、任意の列で簡単にソートできます。また、各リストの特定の列に基づいて簡単に数えたり、検索を行うこともできます。基本的には、2次元配列に近いものです。

ここでの唯一の欠点は、データの順序を知る必要があることです。その順序を変更する場合、一致するように検索/ソートルーチンを変更する必要があります。

もう1つできることは、辞書のリストです。

rows = []
rows.append({"ID":"1", "name":"Cat", "year":"1998", "priority":"1"})

これにより、パラメーターの順序を知る必要がなくなるため、リスト内の各「年」フィールドを調べることができます。

17
AlbertoPL

行がdictまたはより良い行オブジェクトのリストであるTableクラスを持っている

テーブルに直接行を追加するのではなく、いくつかのルックアップマップを更新するメソッドがあります。あなたが順番に行を追加していない場合、またはIDが連続していない場合、名前にはidMapも持つことができます.

class Table(object):
    def __init__(self):
        self.rows =  []# list of row objects, we assume if order of id
        self.nameMap = {} # for faster direct lookup for row by name

    def addRow(self, row):
        self.rows.append(row)
        self.nameMap[row['name']] = row

    def getRow(self, name):
        return self.nameMap[name]


table = Table()
table.addRow({'ID':1,'name':'a'})
6
Anurag Uniyal

まず、複雑なデータ取得シナリオがあることを考えると、SQLiteでさえ過剰であると確信していますか?

最終的には、SQLiteの半分の、アドホックで、非公式に指定された、バグに乗った、遅い実装になります。言い換えると、 Greenspunの第10規則 です。

とはいえ、単一のデータ構造を選択すると、検索、並べ替え、またはカウントの1つ以上に影響を与えると言うのは非常に正しいので、パフォーマンスが最重要でデータが一定であれば、異なる目的で複数の構造を使用することを検討できます。

とりわけ、どの操作がより一般的になるかを測定し、どの構造が最終的に低コストになるかを決定します。

5
Vinko Vrsalovic

私は個人的に、ごく最近、BD_XMLと呼ばれるライブラリを作成しました。

その最も基本的な存在理由は、XMLファイルとSQLデータベース間でデータをやり取りする方法として機能することです。

これはスペイン語で書かれています(プログラミング言語で重要な場合)が、非常に簡単です。

from BD_XML import Tabla

Tabla(テーブル)と呼ばれるオブジェクトを定義し、pep-246互換のデータベースインターフェースの事前に作成された接続オブジェクトを識別するための名前で作成できます。

Table = Tabla('Animals') 

次に、agregar_columna(add_column)メソッドを使用して列を追加する必要があります。さまざまな主要なWord引数を取ることができます。

  • campo(フィールド):フィールドの名前

  • tipo(タイプ):保存されるデータのタイプ。「varchar」や「double」などの名前、またはpythonオブジェクトのエクスポートに興味がない場合)後者のデータベースに。

  • defecto(デフォルト):行の追加時に列がない場合、列のデフォルト値を設定します

  • 他の3つがありますが、データベースティングのためだけにあり、実際には機能しません

好む:

Table.agregar_columna(campo='Name', tipo='str')
Table.agregar_columna(campo='Year', tipo='date')
#declaring it date, time, datetime or timestamp is important for being able to store it as a time object and not only as a number, But you can always put it as a int if you don't care for dates
Table.agregar_columna(campo='Priority', tipo='int')

次に、+ =演算子を使用して行を追加します(余分な行を含むコピーを作成する場合は+)

Table += ('Cat', date(1998,1,1), 1)
Table += {'Year':date(1998,1,1), 'Priority':2, Name:'Fish'}
#…
#The condition for adding is that is a container accessible with either the column name or the position of the column in the table

次に、XMLを生成し、exportar_XML(export_XML)およびescribir_XML(write_XML)を使用してファイルに書き込むことができます。

file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Animals.xml'))
Table.exportar_xml()
Table.escribir_xml(file)

そして、ファイル名と文字列リテラルではなくファイルを使用していることを示すimportar_XML(import_XML)でインポートして戻します。

Table.importar_xml(file, tipo='archivo')
#archivo means file

高度な

これは、SQLの方法でTablaオブジェクトを使用できる方法です。

#UPDATE <Table> SET Name = CONCAT(Name,' ',Priority), Priority = NULL WHERE id = 2
for row in Table:
    if row['id'] == 2:
        row['Name'] += ' ' + row['Priority']
        row['Priority'] = None
print(Table)

#DELETE FROM <Table> WHERE MOD(id,2) = 0 LIMIT 1
n = 0
nmax = 1
for row in Table:
    if row['id'] % 2 == 0:
        del Table[row]
        n += 1
        if n >= nmax: break
print(Table)

この例では、「id」という名前の列を想定していますが、この例ではwidth row.posに置き換えることができます。

if row.pos == 2:

ファイルは次からダウンロードできます。

https://bitbucket.org/WolfangT/librerias

2
Wolfang Torres