7

次のものと、 a を表すvariables対応するものがあるとします。valuesrecord

name = 'abc'
age = 23
weight = 60
height = 174

valueが異なる可能性があることに注意してくださいtypes( stringintegerfloat、他のオブジェクトへの参照など)。

多くありますrecords(少なくとも >100,000)。これらの 4 つ(実際にはその) がすべて組み合わさrecordれると、それぞれがすべてになります。つまり、 4 つすべてが同じものは存在しません。uniquevariablesvaluesrecordvalues

私は、時間の複雑さでこれらのいずれかに基づいPythonて(保存および)取得できる効率的なデータ構造を見つけようとしています。recordsvariableslog(n)

例えば:

def retrieve(name=None,age=None,weight=None,height=None) 
    if name is not None and age is None and weight is None and height is None:
        /* get all records with the given name */
    if name is None and age is not None and weight is None and height is None:
        /* get all records with the given age */
    ....
    return records

を呼び出す方法retrieveは次のとおりです。

retrieve(name='abc') 

上記は返されるはずです[{name:'abc', age:23, wight:50, height=175}, {name:'abc', age:28, wight:55, height=170}, etc]

retrieve(age=23) 

上記は返されるはずです[{name:'abc', age:23, wight:50, height=175}, {name:'def', age:23, wight:65, height=180}, etc]

variablesそして、将来、このレコードにさらに 1 つまたは 2 つ追加する必要があるかもしれません。たとえば、と言いsex = 'm'ます。したがって、retrieve関数はスケーラブルでなければなりません。

要するに: (名前、年齢、性別、体重、身長など)の数をPython許可storing a recordし、複雑さ(または理想的にはルックアップ時間) のいずれか (1 つ) に基づくデータ構造はありますか?ncolumnsretrieving recordscolumnlogarithmicconstant - O(1)

4

4 に答える 4

9

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
于 2013-03-14T21:39:40.207 に答える
4

n列の数(名前、年齢、性別、体重、身長など)を含むレコードを保存し、列のいずれか(1つ)に基づいて対数(または理想的には定数-O)に基づいてレコードを取得できるPythonのデータ構造はありますか(1) ルックアップ時間) 複雑さ?

いいえ、ありません。ただし、値ディメンションごとに 1 つのディクショナリに基づいて実装を試みることもできます。もちろん、値がハッシュ可能である限り。レコードにカスタム クラスを実装すると、各ディクショナリには同じオブジェクトへの参照が含まれます。これにより、メモリが節約されます。

于 2013-03-14T19:54:04.917 に答える
4

O(log(n)**k)インデックス (単一列インデックス)を使用して、リレーショナル データベースで対数時間の複雑さを実現できます。次に、適切な SQL を構築するだけでデータを取得できます。

names = {'name', 'age', 'weight', 'height'}

def retrieve(c, **params):
    if not (params and names.issuperset(params)):
        raise ValueError(params)
    where = ' and '.join(map('{0}=:{0}'.format, params))
    return c.execute('select * from records where ' + where, params)

例:

import sqlite3

c = sqlite3.connect(':memory:')
c.row_factory = sqlite3.Row # to provide key access

# create table
c.execute("""create table records
             (name text, age integer, weight real, height real)""")

# insert data
records = (('abc', 23, 60, 174+i) for i in range(2))
c.executemany('insert into records VALUES (?,?,?,?)', records)

# create indexes
for name in names:
    c.execute("create index idx_{0} on records ({0})".format(name))

try:
    retrieve(c, naame='abc')
except ValueError:
    pass
else:
    assert 0

for record in retrieve(c, name='abc', weight=60):
    print(record['height'])

出力:

174.0
175.0
于 2013-03-14T20:40:16.590 に答える
3

http://wiki.python.org/moin/TimeComplexityを考えると、これはどうですか:

  • 、、などAGE、関心のあるすべての列の辞書を用意してください。NAME
  • その辞書のキー ( AGENAME) を、指定された列 (35 または "m") の可能な値にします。
  • 1 つの「コレクション」の値を表すリストのリストがあります。VALUES = [ [35, "m"], ...]
  • 列辞書 ( AGENAME) の値をリストからのインデックスのVALUESリストにします。
  • 列名をリスト内のインデックスにマップする辞書をVALUES用意して、最初の列が年齢で 2 番目が性別であることを確認します (それを避けて辞書を使用することもできますが、大きなメモリ フットプリントが発生し、100K を超えるオブジェクトでは、そうでない場合もあります。問題)。

次に、retrieve関数は次のようになります。

def retrieve(column_name, column_value):
    if column_name == "age":
        return [VALUES[index] for index in AGE[column_value]]      
    elif ...: # repeat for other "columns"

すると、これがあなたが得るものです

VALUES = [[35, "m"], [20, "f"]]
AGE = {35:[0], 20:[1]}
SEX = {"m":[0], "f":[1]}
KEYS = ["age", "sex"]

retrieve("age", 35)
# [[35, 'm']]

辞書が必要な場合は、次の操作を実行できます。

[dict(zip(KEYS, values)) for values in retrieve("age", 35)]
# [{'age': 35, 'sex': 'm'}]

ただし、辞書はメモリ側で少し重いので、値のリストを使用できる場合は、より良いかもしれません。

ディクショナリとリストの取得はどちらも平均で O(1) です。ディクショナリの最悪の場合は O(n) です。したがって、これはかなり高速になるはずです。それを維持するのは少し面倒ですが、それほどではありません。「書き込む」には、リストに追加してから、各辞書にVALUESインデックスを追加するだけです。VALUES

もちろん、実際の実装をベンチマークし、潜在的な改善点を探すのが最善ですが、これが理にかなっており、うまくいくことを願っています:)

編集:

@moooeeeepが言ったように、これは値がハッシュ可能であり、したがって辞書キーとして使用できる場合にのみ機能することに注意してください。

于 2013-03-14T19:49:16.960 に答える