Подтвердить что ты не робот

Структура данных для хранения табличных данных в памяти?

Мой сценарий выглядит следующим образом: у меня есть таблица данных (несколько полей, не более ста строк), которые я широко использую в своей программе. Мне также нужны эти данные, чтобы они были постоянными, поэтому я сохраняю его как 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

Примечания:

  • Строка может быть "реальным" значением, записанным в файл, или просто автоматически сгенерированным значением, которое представляет номер строки. В любом случае он существует в памяти.
  • Имена уникальны.

Что я делаю с данными:

  • Поиск строки на основе идентификатора (итерации) или имени (прямого доступа).
  • Отобразить таблицу в разных заказах на основе нескольких полей: мне нужно отсортировать ее, например. по приоритету, а затем по году или году, а затем по приоритету и т.д.
  • Мне нужно подсчитывать экземпляры на основе наборов параметров, например. сколько строк имеет свой год между 1997 и 2002 годами или сколько строк в 1998 году, приоритет > 2 и т.д.

Я знаю это "крики" для SQL...

Я пытаюсь выяснить, какой лучший выбор для структуры данных. Ниже приведены несколько вариантов:

Список списков строк:

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

Список списков столбцов (очевидно, будет API для add_row и т.д.):

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
...

И я уверен, что есть другие способы... Однако каждый способ имеет свои недостатки, когда речь идет о моих требованиях (сложный порядок и подсчет).

Какой рекомендуемый подход?

EDIT:

Чтобы прояснить, производительность для меня не является серьезной проблемой. Поскольку таблица настолько мала, я считаю, что почти каждая операция будет находиться в диапазоне миллисекунд, что не является проблемой для моего приложения.

4b9b3361

Ответ 1

Наличие "таблицы" в памяти, которая требует поиска, сортировки и произвольной агрегации, действительно вызывает SQL. Вы сказали, что вы пробовали SQLite, но понимаете ли вы, что SQLite может использовать базу данных только в памяти?

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

Затем вы можете создавать таблицы /drop/query/update в памяти со всеми функциональными возможностями 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) и легко растет для удовлетворения будущих потребностей. Но опять же, мне вполне комфортно с SQL, поэтому YMMV.

Ответ 2

Очень старый вопрос, который я знаю, но...

A pandas DataFrame представляется идеальным вариантом здесь.

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

Из рекламного ролика

Двумерные изменяемые по размеру, потенциально разнородные табличные данные структура с маркированными осями (строки и столбцы). Арифметические операции выровнять по ярлыкам строк и столбцов. Можно думать как о диктоподобном контейнер для объектов серии. Первичная структура данных pandas

http://pandas.pydata.org/

Ответ 3

Я лично использовал бы список списков строк. Поскольку данные для каждой строки всегда находятся в одном порядке, вы можете легко отсортировать их по любому из столбцов, просто обратившись к этому элементу в каждом из списков. Вы также можете легко рассчитывать на основе определенного столбца в каждом списке и выполнять поиск. Это в основном так же близко, как и до 2-мерного массива.

Действительно, единственным недостатком здесь является то, что вы должны знать, в каком порядке находятся данные, и если вы измените это упорядочение, вам придется изменить процедуры поиска/сортировки, чтобы они соответствовали.

Еще одна вещь, которую вы можете сделать, - это список словарей.

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

Это позволит избежать необходимости знать порядок параметров, поэтому вы можете просмотреть каждое поле "год" в списке.

Ответ 4

У вас есть класс таблицы, строки которого представляют собой список объектов dict или better row

В таблице нет прямого добавления строк, но есть метод, который обновляет несколько карт поиска, например. для имени если вы не добавляете строки по порядку или 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'})

Ответ 5

Во-первых, учитывая, что у вас сложный сценарий поиска данных, вы уверены, что даже SQLite перебор?

В итоге у вас будет специальная, неформальная, исправленная ошибка, медленная реализация половины SQLite, перефразируя Greenspun Tenth Rule.

Тем не менее, вы совершенно правы в том, что выбор единой структуры данных повлияет на один или несколько операций поиска, сортировки или подсчета, поэтому, если производительность имеет первостепенное значение, а ваши данные постоянны, вы можете рассмотреть возможность создания более чем одной структуры для разные цели.

Прежде всего, измерить, какие операции будут более распространены и решить, какая структура будет стоить меньше.

Ответ 6

Я лично написал lib для довольно много, что совсем недавно он называется BD_XML

поскольку его основной причиной существования является способ отправки данных между XML файлами и базами данных SQL.

Он написан на испанском языке (если это имеет значение на языке программирования), но это очень просто.

from BD_XML import Tabla

Он определяет объект, называемый Tabla (Table), его можно создать с именем для идентификации предварительно созданного объекта соединения интерфейса базы данных, совместимого с pep-246.

Table = Tabla('Animals') 

Затем вам нужно добавить столбцы с помощью метода agregar_columna (add_column), который может принимать различные аргументы ключевых слов:

  • campo (поле): имя поля

  • tipo (type): тип хранимых данных может быть такими, как "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

Дополнительно

Это способы использования объекта 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