Словари велики, когда вы хотите посмотреть ключ по точному соответствию. В частности, хэш ключа поиска должен быть таким же, как хэш сохраненного ключа.
Если ваши диапазоны согласованы, вы можете подделать это, написав хеш-функцию, которая возвращает то же значение для диапазона, и для каждого значения в этом диапазоне. Но если это не так, эта хеш-функция должна отслеживать все известные диапазоны, что возвращает вас к той же самой проблеме, с которой вы начинаете.
В этом случае правильная структура данных здесь, вероятно, представляет собой некоторую сортированную коллекцию. Если вам нужно только собрать коллекцию, а затем использовать ее много раз, не изменяя ее, просто введите список и используйте модуль bisect
, сделав это для вас. Если вам нужно изменить коллекцию после создания, вам понадобится что-то построенное вокруг двоичного дерева или варианта B-дерева, например blist
или bintrees
.
Это сократит время поиска диапазона от N/2 до log2 (N). Итак, если у вас есть 10000 диапазонов, вместо 5000 сравнений, вы сделаете 14.
В то время как мы на нем, это поможет преобразовать значения начала и остановки диапазона в ints один раз, вместо того, чтобы делать это каждый раз. Кроме того, если вы хотите использовать stdlib bisect
, вы, к сожалению, не можете передать key
для большинства функций, поэтому давайте реорганизовать диапазоны в сопоставимый заказ. Итак:
for Line in FileB:
LineB = Line.strip('\n').split('\t')
Ranges[Chr].append(int(LineB[1]), int(LineB[2]), [LineB[0])
for r in Ranges:
r.sort()
Теперь, вместо этого цикла:
for i, r in enumerate(Ranges[Chr]):
# ...
ли это:
i = bisect.bisect(Ranges[Chr], (Location, Location, None))
if i:
r = Ranges[Chr][i-1]
if r[0] <= Location < r[1]:
# do whatever you wanted with r
else:
# there is no range that includes Location
else:
# Location is before all ranges
Вы должны быть осторожными, думая о bisect
, и возможно, я получил это неправильно с первой попытки, поэтому ... прочитайте документы о том, что он делает, и поэкспериментируйте с вашими данными (распечатайте результаты функции bisect
), прежде чем доверять этому.
Если диапазоны могут перекрываться, и вы хотите, чтобы быть в состоянии найти все диапазоны, которые содержат значения, а не только один, вам нужно немного больше, чем это, чтобы держать вещи эффективно. Невозможно полностью упорядочить перекрывающиеся диапазоны, поэтому bisect
не отрежет.
Если вы ожидаете больше совпадений в логах N на средний поиск, вы можете сделать это с помощью двух отсортированных списков и bisect
.
Но в остальном вам нужна более сложная структура данных и более сложный код. Например, если вы можете зарезервировать пространство N^2, вы можете сохранить время в журнале N, указав для каждого диапазона в первом списке второй список, отсортированный по концу, всех значений с соответствующим началом.
И в этот момент я думаю, что он становится достаточно сложным, и вы хотите найти библиотеку, чтобы сделать это за вас.
Однако, возможно, вы захотите рассмотреть другое решение.
Если вы используете numpy
или базу данных вместо чистого Python, это не может сократить алгоритмическую сложность от N до log N ... но она может сократить постоянные накладные расходы в 10 раз или около того, что может быть достаточно хорошим , Фактически, если вы делаете тонны поисков в небольшом небольшом списке, это может быть даже лучше.
Плюс, это выглядит намного проще, и как только вы привыкнете к операциям массива или SQL, это может быть даже более читаемым. Итак:
RangeArrays = [np.array(a[:2] for a in value) for value in Ranges]
... или, если Ranges
отображение ДИКТ строки на значения, вместо списка:
RangeArrays = {key: np.array(a[:2] for a in value) for key, value in Ranges.items()}
Затем, вместо этого:
for i, r in enumerate(Ranges[Chr]):
# ...
Do:
comparisons = Location < RangeArrays[Chr]
matches = comparisons[:,0] < comparisons[:,1]
indices = matches.nonzero()[0]
for index in indices:
r = Ranges[indices[0]]
# Do stuff with r
(Вы может, конечно, сделать вещи более кратким, но это стоит делать это таким образом и распечатывать все промежуточные шаги, чтобы понять, почему это работает)
Или, используя базу данных:.
cur = db.execute('''SELECT Start, Stop, Chr FROM Ranges
WHERE Start <= ? AND Stop > ?''', (Location, Location))
for (Start, Stop, Chr) in cur:
# do stuff
Спасибо! Это работало только с одним редактированием - при указании «r = Ranges [Chr] [i]» мне пришлось изменить его на «r = Диапазоны [Chr] [i-1]» – user2165857
@ user2165857: ОК, спасибо; Я отредактирую его. Но убедитесь, что это правильно в случае пустых диапазонов (например, у вас есть '3-10', затем' 10-10', затем '10-21', и вы ищете' 10' - вы должен найти '10-21', а не' 10-10' там). Конечно, я предполагаю, что вы используете типичные Python полуоткрытые диапазоны, и пустые диапазоны разрешены ... – abarnert
@ user2165857: Кроме того, я уверен, что вам нужно иметь дело с случаем, когда значение меньше всех диапазонов , потому что 'bisect' вернет' 0', и вы проверите 'Диапазоны [Chr] [- 1]'. И, хотя это вернет правильный результат, это только потому, что значение не может находиться в последнем диапазоне, и я думаю, что логика слишком непрозрачна, чтобы опираться без комментариев. – abarnert