[解決済み] 表形式データをメモリ上に保持するためのデータ構造?
質問
私のシナリオは次のとおりです。私のプログラムで広範囲に使用するデータのテーブル (少数のフィールド、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
注意事項
- Row は、ファイルに書き込まれた "real" 値であるか、行番号を表す単なる自動生成値である可能性があります。いずれにせよ、それはメモリ内に存在します。
- 名前は一意です。
データで行うこと
- ID (反復処理) または名前 (直接アクセス) のいずれかに基づいて行を検索します。
- 複数のフィールドに基づき、テーブルを異なる順序で表示する。例えば、優先度、年の順、または年、優先度の順でソートする必要があります。
- パラメータのセットに基づいてインスタンスをカウントする必要があります。たとえば、年を 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]
(Row, Field)のタプルをキーとする辞書。
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
...
他にも方法はあると思います。しかし、それぞれの方法は、私の要件(複雑な順序付けとカウント)に関しては欠点があります。
お勧めの方法は何ですか?
EDITです。
明確にするために、パフォーマンスは私にとっては大きな問題ではありません。テーブルが非常に小さいため、ほとんどすべての操作がミリ秒の範囲になると思いますが、これは私のアプリケーションにとって心配することではありません。
どのように解決するのですか?
ルックアップ、ソート、および任意の集計を必要とするメモリ内のテーブルを持つことは、本当に SQL を必要とします。 SQLite を試したということですが、SQLite がメモリ内のみのデータベースを使用できることをご存知でしたか?
connection = sqlite3.connect(':memory:')
そうすると、SQLiteのすべての機能を備えたメモリ上でテーブルを作成/削除/問い合わせ/更新することができ、終了時にファイルを残すことはありません。 そして、Python 2.5 の時点で
sqlite3
は標準ライブラリに含まれているので、IMOでは実際に"overkill"ではないのです。
以下は、データベースを作成し、入力する方法のサンプルです。
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の余分な変換コードなしで)、将来の要件に対応するために簡単に拡張できるので、より気に入っています。 しかし、私はSQLにとても慣れているので、YMMVです。
関連
-
[解決済み] for'ループでインデックスにアクセスする?
-
[解決済み] __init__.py は何のためにあるのですか?
-
[解決済み] パラメータに**(ダブルスター/アスタリスク)、*(スター/アスタリスク)がありますが、これはどういう意味ですか?
-
[解決済み] Pythonのswitch文の代用品?
-
[解決済み] リストを表形式データとして印刷する
-
[解決済み】forループを使った辞書の反復処理
-
[解決済み】なぜPythonはこのJSONデータをパースできないのですか?[終了] PythonがこのJSONデータをパースできないのはなぜですか?
-
[解決済み] Django のテストデータベースをメモリ上だけで動作させるには?
-
[解決済み] Pythonのargparseを使った隠し引数の作成
-
[解決済み] Python 言語を決定するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Jupyterノートブックでenv変数を設定する方法
-
[解決済み] PythonでSVGからPNGに変換する
-
[解決済み] Pythonのインスタンス変数とクラス変数
-
[解決済み] Pandasの'Freq'タグにはどのような値が有効ですか?
-
[解決済み] データフレームをソートした後にインデックスを更新する
-
[解決済み] スペースがないテキストを単語のリストに分割する方法
-
[解決済み] サブフォルダからのインポートモジュール
-
[解決済み] Flaskで非同期タスクを作る
-
[解決済み] pycharmがタブをスペースに自動変換する
-
[解決済み] pipの依存性/必要条件をリストアップする方法はありますか?