Pythonに組み込まれている、必要なすべてを実行する単一のデータ構造はありませんが、目標を達成するために必要なデータ構造を組み合わせて使用するのは非常に簡単で、かなり効率的に実行できます。
たとえば、入力がemployees.csv
、最初の行に示されているように定義されたフィールド名で呼び出されたコンマ区切り値ファイルの次のデータであったとします。
name,age,weight,height
Bob Barker,25,175,6ft 2in
Ted Kingston,28,163,5ft 10in
Mary Manson,27,140,5ft 6in
Sue Sommers,27,132,5ft 8in
Alice Toklas,24,124,5ft 6in
以下は、このデータを読み取ってレコードのリストに格納し、これらの各レコードのフィールドに含まれる値に関連付けられたレコードを検索するための個別のルックアップテーブルを自動的に作成する方法を示す作業コードです。
レコードは作成されたクラスのインスタンスであり、各レコードにはクラスインスタンスに通常含まれる属性がnamedtuple
ないため、メモリ効率が非常に高くなります。__dict__
それらを使用すると、のようなドット構文を使用して名前でそれぞれのフィールドにアクセスできるようになりますrecord.fieldname
。
ルックアップテーブルはdefaultdict(list)
インスタンスであり、辞書のようなO(1)ルックアップ時間を平均して提供し、複数の値をそれぞれに関連付けることもできます。したがって、ルックアップキーは、求められているフィールド値の値であり、それに関連付けられたデータは、その値とともにリストにPerson
格納されているレコードの整数インデックスのemployees
リストになります。したがって、それらはすべて比較的小さくなります。
クラスのコードは完全にデータ駆動型であり、ハードコードされたフィールド名は含まれていません。代わりに、読み込まれたときにcsvデータ入力ファイルの最初の行から取得されます。もちろん、インスタンスを使用する場合は、すべてretrieve()
メソッド呼び出しは、有効なフィールド名を提供する必要があります。
アップデート
データファイルが最初に読み取られるときに、すべてのフィールドのすべての一意の値に対してルックアップテーブルを作成しないように変更されました。現在、retrieve()
メソッドは「怠惰な」方法で、必要な場合にのみそれらを作成します(そして、将来の使用のために結果を保存/キャッシュします)。また、3.xを含むPython2.7以降で動作するように変更されました。
from collections import defaultdict, namedtuple
import csv
class DataBase(object):
def __init__(self, csv_filename, recordname):
# Read data from csv format file into a list of namedtuples.
with open(csv_filename, 'r') as inputfile:
csv_reader = csv.reader(inputfile, delimiter=',')
self.fields = next(csv_reader) # Read header row.
self.Record = namedtuple(recordname, self.fields)
self.records = [self.Record(*row) for row in csv_reader]
self.valid_fieldnames = set(self.fields)
# Create an empty table of lookup tables for each field name that maps
# each unique field value to a list of record-list indices of the ones
# that contain it.
self.lookup_tables = {}
def retrieve(self, **kwargs):
""" Fetch a list of records with a field name with the value supplied
as a keyword arg (or return None if there aren't any).
"""
if len(kwargs) != 1: raise ValueError(
'Exactly one fieldname keyword argument required for retrieve function '
'(%s specified)' % ', '.join([repr(k) for k in kwargs.keys()]))
field, value = kwargs.popitem() # Keyword arg's name and value.
if field not in self.valid_fieldnames:
raise ValueError('keyword arg "%s" isn\'t a valid field name' % field)
if field not in self.lookup_tables: # Need to create a lookup table?
lookup_table = self.lookup_tables[field] = defaultdict(list)
for index, record in enumerate(self.records):
field_value = getattr(record, field)
lookup_table[field_value].append(index)
# Return (possibly empty) sequence of matching records.
return tuple(self.records[index]
for index in self.lookup_tables[field].get(value, []))
if __name__ == '__main__':
empdb = DataBase('employees.csv', 'Person')
print("retrieve(name='Ted Kingston'): {}".format(empdb.retrieve(name='Ted Kingston')))
print("retrieve(age='27'): {}".format(empdb.retrieve(age='27')))
print("retrieve(weight='150'): {}".format(empdb.retrieve(weight='150')))
try:
print("retrieve(hight='5ft 6in'):".format(empdb.retrieve(hight='5ft 6in')))
except ValueError as e:
print("ValueError('{}') raised as expected".format(e))
else:
raise type('NoExceptionError', (Exception,), {})(
'No exception raised from "retrieve(hight=\'5ft\')" call.')
出力:
retrieve(name='Ted Kingston'): [Person(name='Ted Kingston', age='28', weight='163', height='5ft 10in')]
retrieve(age='27'): [Person(name='Mary Manson', age='27', weight='140', height='5ft 6in'),
Person(name='Sue Sommers', age='27', weight='132', height='5ft 8in')]
retrieve(weight='150'): None
retrieve(hight='5ft 6in'): ValueError('keyword arg "hight" is an invalid fieldname')
raised as expected