Вставить элемент в сортированный список без учета регистра в Python
у меня есть список строк, которые уже отсортированы в регистр порядка. Я хотел бы вставить новую строку в список. Один из способов сделать это-добавить элемент, а затем отсортировать список, вот так:
myList.append('Something')
myList.sort(key=lambda s: s.lower())
но мне было интересно, есть ли способ просто вставить элемент в правильное положение, не сортируя все это снова.
Я нашел этот вопрос: вставьте элемент в отсортированном списке в Python. Он указывает на Python bisect модуль. Но этот модуль не выглядит так, как будто он может поддерживать нечувствительность к регистру.
редактировать: я проверил несколько ответов, перечисленных здесь.
- добавление элемента в конец и сортировка всего списка (как предложено в исходном вопросе) была самой медленной.
- ответ Моинуддина Квадри был быстрее, чем сортировка всего списка, но он все еще был довольно медленным, из-за запуска
lower()
по каждому пункту список.
Стефан Pochmann был на порядок быстрее, чем сортировка всего списка.
- ответ Джареда Гогена был самым быстрым для повторных вставок. В первый раз, однако, он работает
lower()
на каждый элемент.
Я был близок к тому, чтобы принять ответ. В конце концов, я пошел с ответом Стефана Почманна, потому что он был лучшим для одноразовой вставки, и доступ к результирующему списку не требует доступа к члену переменная. Однако варианты использования различаются, поэтому обязательно изучите все ответы.
6 ответов
это хорошая возможность снова практиковать двоичный поиск (или просто копировать и вставлять и изменять bisect.insort
, что я и сделала):
def insort_case_insensitive(a, x):
key = x.lower()
lo, hi = 0, len(myList)
while lo < hi:
mid = (lo + hi) // 2
if key < a[mid].lower():
hi = mid
else:
lo = mid + 1
a.insert(lo, x)
демо:
myList = ['a', 'b', 'c', 'd', 'e']
for x in 'A', 'B', 'C', 'D', 'E':
insort_case_insensitive(myList, x)
print myList
принты:
['a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E']
Это O (n), как и append+sort, но только из-за a.insert(lo, x)
в конце. Что мертво-просто и сделано в C, так что это супер быстро. Двоичный поиск, конечно, принимает только шаги O(log n), так что это очень быстро. Добавление + сортировка способ вызовет .lower()
на все элементы и сравнить их, оба из которых гораздо медленнее. Первое решение @MoinuddinQuadri также намного медленнее из-за вызова .lower()
по всем элементам.
см. мой другой ответ Для сравнения бенчмаркинга.
вы можете использовать bisect.bisect
в нижнем регистре отсортированного списка как:
from bisect import bisect
my_list = ["aa", "bb", "Dd", "ee"]
insert_string = "CC"
# convert all the items in list to lower case for
# v finding the correct location via. bisect
index = bisect([i.lower() for i in my_list], insert_string.lower())
# bisect based on lower-cased string for ^
# case-insensitive behavior
my_list.insert(index, insert_string)
где обновленное содержание my_list
будет:
['aa', 'bb', 'CC', 'Dd', 'ee']
вы можете создать свой собственный тип для инкапсуляции этого поведения (в сочетании с bisect
как предложено в другом ответе).
from bisect import bisect
class CaseInsensitiveSortedList:
def __init__(self, iterable):
self.with_case = list(sorted(iterable, key=lambda s: s.lower()))
self.without_case = [s.lower() for s in self.with_case]
def insert_in_order(self, s):
s_lower = s.lower()
index = bisect(self.without_case, s_lower)
self.without_case.insert(index, s_lower)
self.with_case.insert(index, s)
test_list = CaseInsensitiveSortedList(['a', 'B', 'cd', 'E', 'fff'])
test_list.insert_in_order('D')
print(test_list.with_case) # ['a', 'B', 'cd', 'D', 'E', 'fff']
test_list.insert_in_order('ee')
print(test_list.with_case) # ['a', 'B', 'cd', 'D', 'E', 'ee', 'fff']
вы могли бы расширить list
непосредственно и сделать это немного "более естественным" или сделать все, что вы хотите с ним. Это просто идея, чтобы избежать вызова str.lower
на каждом элементе для каждой вставки.
Я никогда не работал с bisect, но вот мой удар по нему. Первая функция, которую я беру непосредственно из bisect
страница, которую вы перешли по ссылке:
def index(a, x):
'Locate the leftmost value exactly equal to x'
i = bisect_left(a, x)
if i != len(a) and a[i] == x:
return i
raise ValueError
def insert_into_sorted(char, my_list):
marker = chr(ord(char) + 1)
spot = index(my_list, marker)
my_list[spot:spot] = char
return my_list
x = ['a', 'b', 'd', 'e']
insert_into_sorted('c', x)
>>['a', 'b', 'c', 'd', 'e']
на основе комментария от OP, так как это нормально, чтобы поддерживать промежуточный список для удержания строк в нижнем регистре (один раз); это может быть достигнуто как:
from bisect import bisect
my_list = ["aa", "bb", "Dd", "ee"]
lower_list = [i.lower() for i in my_list] # list of lower-cased strings.
# one time operation
insert_string = "CC" # word to insert
# find index based on lower-cased list
index = bisect(lower_list, insert_string.lower())
my_list.insert(index, insert_string) # insert word in original list
lower_list.insert(index, insert_string.lower()) # insert lower-cased word
# in lower-cased list
где конечное значение my_list
будет lower_list
будет:
>>> my_list # original list
['aa', 'bb', 'CC', 'Dd', 'ee']
>>> lower_list
['aa', 'bb', 'cc', 'dd', 'ee']
здесь мы разделяем список слов с более низким регистром, чтобы найти индекс и на основе индекса, вставляя строку в исходный список.
Я вижу, что вы добавили результаты теста к вам вопрос. Я сделал несколько тестов, а теперь и получить аналогичную картину:
Insorting 20000 words:
80.224 seconds with insort_sorting
0.166 seconds with insort_own_binary_search
70.294 seconds with insort_lower_all
0.094 seconds with insort_keep_lower
вы несколько ошибаетесь насчет быстрых двух, хотя. При большем количестве вставок моя становится быстрее. Примерно в два раза быстрее:
Insorting 1000000 words:
92.712 seconds with insort_own_binary_search
173.577 seconds with insort_keep_lower
это потому, что время o(log n) для поиска индекса становится незначительным, и время доминирует время O (n) для insert
звонки. И мое решение имеет только один из них, а другой решение имеет два.
другое отличие-сложность пространства, сохранение дополнительного списка пониженных версий всех строк не так хорошо.
вот мой код бенчмаркинга:
import random, string, time
#--------------------------------------------------------------
def insort_sorting(a, x):
a.append(x)
a.sort(key=str.lower)
#--------------------------------------------------------------
def insort_own_binary_search(a, x):
key = x.lower()
lo, hi = 0, len(myList)
while lo < hi:
mid = (lo + hi) // 2
if key < a[mid].lower():
hi = mid
else:
lo = mid + 1
a.insert(lo, x)
#--------------------------------------------------------------
from bisect import bisect
def insort_lower_all(a, x):
index = bisect([i.lower() for i in a], x.lower())
a.insert(index, x)
#--------------------------------------------------------------
from bisect import bisect
def insort_keep_lower(a, x, lower=[]):
x_lower = x.lower()
index = bisect(lower, x_lower)
a.insert(index, x)
lower.insert(index, x_lower)
#--------------------------------------------------------------
# Generate random words
words = [''.join(random.choice(string.ascii_letters) for _ in range(10))
for _ in range(20000)]
# for _ in range(1000000)]
# Compare the solutions
print 'Insorting', len(words), 'words:'
reference = None
for insort in insort_sorting, insort_own_binary_search, insort_lower_all, insort_keep_lower:
#for insort in insort_own_binary_search, insort_keep_lower:
t0 = time.time()
myList = []
for word in words:
insort(myList, word)
print '%7.3f seconds with %s' % (time.time() - t0, insort.__name__)
if reference is None:
reference = myList
else:
assert myList == reference