Как лучше всего хранить установленные данные в Python?

У меня есть список данных в следующем виде:

[(id\__1_, description, id\_type), (id\__2_, description, id\_type), ... , (id\__n_, description, id\_type))

Данные загружаются из файлов, принадлежащих к той же группе. В каждой группе может быть несколько одинаковых идентификаторов, каждый из которых поступает из разных файлов. Меня не волнуют дубликаты, поэтому я подумал, что хороший способ сохранить все это - это поместить его в тип Set. Но есть проблема.

Иногда для одного и того же идентификатора описания могут незначительно отличаться, например:

IPI00110753

  • Цепь тубулина альфа-1А
  • Цепь тубулина альфа-1
  • Альфа-тубулин 1
  • Альфа-тубулин изотипа М-альфа-1

(Обратите внимание, что этот пример взят из база данных белков uniprot.)

Меня не волнует, различаются ли описания. Я не могу их выбросить, потому что есть вероятность, что используемая мной база данных белков не будет содержать список для определенного идентификатора. Если это произойдет, я захочу показать биологам понятное для человека описание, чтобы они примерно знали, на какой белок они смотрят.

В настоящее время я решаю эту проблему, используя тип словаря. Однако мне не очень нравится это решение, потому что оно использует много памяти (у меня много таких идентификаторов). Это лишь их промежуточный перечень. Идентификаторы проходят некоторую дополнительную обработку, прежде чем они будут помещены в базу данных, поэтому я хотел бы, чтобы моя структура данных была меньше.

У меня действительно два вопроса. Во-первых, получу ли я меньший объем памяти, используя для этого тип Set (над типом словаря), или мне следует использовать отсортированный список, где я проверяю каждый раз, когда я вставляю в список, чтобы увидеть, существует ли идентификатор, или есть ли Третье решение, о котором я не подумал? Во-вторых, если тип Set является лучшим ответом, как мне включить его, чтобы посмотреть только на первый элемент кортежа, а не на все?

Спасибо, что прочитали мой вопрос,
Тим

Обновлять

Основываясь на некоторых полученных мной комментариях, позвольте мне немного уточнить. Большая часть того, что я делаю со структурой данных, я вставляю в нее. Я прочитал его только дважды, один раз, чтобы добавить в него дополнительную информацию *, и один раз, чтобы вставить его в базу данных. Однако в дальнейшем может быть дополнительная аннотация, которая делается до того, как я вставлю в базу данных. К сожалению, я не знаю, произойдет ли это сейчас.

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

* Информация представляет собой список идентификаторов белков Swiss-Prot, который я получаю, запрашивая uniprot.

Что вы делаете в 90% случаев с этими данными, выполняете поиск или вставку?

Florian Bösch 24.09.2008 20:54
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
1
5 624
6

Ответы 6

В наборах нет ключей. Элемент является ключ.

Если вы думаете, что вам нужны ключи, у вас есть сопоставление. Более или менее по определению.

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

Вы говорите о таком словаре?

{ 'id1': [ ('description1a', 'type1'), ('description1b','type1') ], 
  'id2': [ ('description2', 'type2') ],
...
}

Конечно, это кажется минимальным. ID представлены только один раз.

Возможно, у вас есть что-то подобное?

{ 'id1': ( ('description1a', 'description1b' ), 'type1' ),
  'id2': ( ('description2',), 'type2' ),
...
}

Не уверен, что можно найти что-то более компактное, если не прибегать к использованию модуля struct.

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

tim.tadh 24.09.2008 21:07

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

Florian Bösch 24.09.2008 21:13

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

S.Lott 24.09.2008 21:39

Это не преждевременная оптимизация. Люди, использующие альфа-версию, достигли унаследованных пределов моего плохого первоначального дизайна. Это лишь один из аспектов многих дизайнерских решений, которые я не продумала.

tim.tadh 25.09.2008 00:48

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

Claudiu 24.11.2008 07:41

Как насчет использования словаря {id: (description, id_type)}? Или словарь {(id, id_type): description}, если (id, id_type) является ключом.

Наборы в Python реализованы с помощью хеш-таблиц. В более ранних версиях они фактически были реализованы с помощью наборов, но это изменило AFAIK. Единственное, что вы сохраняете, используя набор, - это размер указателя для каждой записи (указателя на значение).

Чтобы использовать только часть кортежа для хэш-кода, вам нужно создать подкласс кортежа и переопределить метод хэш-кода:

class ProteinTuple(tuple):
     def __new__(cls, m1, m2, m3):
         return tuple.__new__(cls, (m1, m2, m3))

     def __hash__(self):
         return hash(self[0])

Имейте в виду, что в этом случае вы платите за дополнительный вызов функции __hash__, потому что в противном случае это был бы метод C.

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

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

Используйте меньше структур, кроме строк (str)

Вы задаетесь вопросом, как структурировать данные в одном процессе, чтобы использовать меньше памяти. Единственный канонический ответ на этот вопрос (если вам все еще нужен ассоциативный поиск), используйте как можно меньше других структур, кроме строк Python (str, а не unicode). Хеш (словарь) python довольно эффективно хранит ссылки на ваши строки (это не реализация b-дерева).

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

Альтернативное решение

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

  • Разделите информацию на несколько процессов, каждый из которых будет содержать любую удобную для этого структуру данных.
  • Реализуйте межпроцессное взаимодействие с сокетами, чтобы процессы могли полностью находиться на других машинах.
  • Попробуйте разделить свои данные, чтобы минимизировать взаимодействие между процессами (ввод-вывод выполняется очень медленно по сравнению с циклами процессора).

Преимущество описываемого мною подхода состоит в том, что

  • Вы можете полностью использовать два или больше ядер на машине для повышения производительности.
  • Вы не ограничены адресным пространством одного процесса или даже физической памятью одной машины.

Существует множество пакетов и подходов к распределенной обработке, некоторые из которых

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

tim.tadh 25.09.2008 00:43

Это все еще неясно, но похоже, что у вас есть несколько списков [(id, description, type) ...]

Идентификаторы уникальны в пределах списка и согласованы между списками.

Вы хотите создать UNION: единый список, в котором каждый идентификатор встречается один раз, возможно, с несколькими описаниями.

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

Это может быть (если я правильно угадаю) стандартная операция «слияния» из нескольких источников.

source1.sort()
source2.sort()
result= []
while len(source1) > 0 or len(source2) > 0:
    if len(source1) == 0:
        result.append( source2.pop(0) )
    elif len(source2) == 0:
        result.append( source1.pop(0) )
    elif source1[0][0] < source2[0][0]:
        result.append( source1.pop(0) )
    elif source2[0][0] < source1[0][0]:
        result.append( source2.pop(0) )
    else:
        # keys are equal
        result.append( source1.pop(0) )
        # check for source2, to see if the description is different.

Это объединяет два списка путем сортировки и слияния. Нет сопоставления, нет хеша.

Это интересное решение, но мне придется расширить его, чтобы можно было использовать n источников (n потенциально может быть 15 или 20), и допустить, чтобы идентификатор дважды появлялся в одном из источников. Источник может быть собран человеком, поэтому мне нужно учесть эту возможность. Короче говоря, мне придется провести некоторое время для анализа.

tim.tadh 24.09.2008 21:42

Если вы выполняете n-way слияние с удалением дубликатов, возможно, вы ищете следующее.

Этот генератор объединит любое количество источников. Каждый источник должен быть последовательностью. Ключ должен находиться в позиции 0. Это дает объединенную последовательность по одному элементу за раз.

def merge( *sources ):
    keyPos= 0
    for s in sources:
        s.sort()
    while any( [len(s)>0 for s in sources] ):
        topEnum= enumerate([ s[0][keyPos] if len(s) > 0 else None for s in sources ])
        top= [ t for t in topEnum if t[1] is not None ]
        top.sort( key=lambda a:a[1] )
        src, key = top[0]
        #print src, key
        yield sources[ src ].pop(0)

Этот генератор удаляет дубликаты из последовательности.

def unique( sequence ):
    keyPos= 0
    seqIter= iter(sequence)
    curr= seqIter.next()
    for next in seqIter:
        if next[keyPos] == curr[keyPos]:
            # might want to create a sub-list of matches
            continue
        yield curr
        curr= next
    yield curr

Вот сценарий, который использует эти функции для создания результирующей последовательности, которая представляет собой объединение всех источников с удаленными дубликатами.

for u in unique( merge( source1, source2, source3, ... ) ):
    print u

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

посмотрев на ваш код, я пришел к выводу, что ваш код обрабатывает весь набор данных примерно так, как nаlg (a), где n - общее количество элементов во всех источниках, а a - количество источников. Эй, это все еще линейное время, что очень хорошо. Еще не решил, что делать, но приятный.

tim.tadh 25.09.2008 00:40

Вы можете ускорить процесс, отсортировав источники извне. Тогда это действительно линейно. Используйте subprocess.Popen ('sort source', stdout = PIPE) и прочтите стандартный вывод из sort.

S.Lott 25.09.2008 00:58

Другие вопросы по теме