Сортировка списка: числа по возрастанию, буквы по убыванию
этот вопрос фактически адаптирован из ранее спросил мат.S (изображения). Хотя он был удален, я подумал, что это хороший вопрос, поэтому я перепечатываю его с более четкими требованиями и моим собственным решением.
приведенный список букв и цифр, скажем
['a', 2, 'b', 1, 'c', 3]
требование состоит в сортировке чисел по возрастанию и букв по убыванию, без изменения относительного положения букв и цифр. Этим я означает, если несортированный список:
[L, D, L, L, D] # L -> letter; # D -> digit
затем отсортированный список также должен быть
[L, D, L, L, D]
буквы и цифры делать не обязательно чередуются в регулярном порядке - они могут появляться в любом произвольном порядке
после сортировки чисел по возрастанию, буквы-по убыванию.
таким образом, для примера выше выход
['c', 1, 'b', 2, 'a', 3]
другой пример:
In[]: [5, 'a', 'x', 3, 6, 'b']
Out[]: [3, 'x', 'b', 5, 6, 'a']
что было бы хорошим способом сделать это?
6 ответов
вот оптимизированный подход с использованием defaultdict()
и bisect()
:
In [14]: lst = [5, 'a', 'x', 3, 6, 'b']
In [15]: from collections import defaultdict
In [16]: import bisect
In [17]: def use_dict_with_bisect(lst):
d = defaultdict(list)
for i in lst:
bisect.insort(d[type(i)], i)
# since bisect doesn't accept key we need to reverse the sorted integers
d[int].sort(reverse=True)
return [d[type(i)].pop() for i in lst]
.....:
демо :
In [18]: lst
Out[18]: [5, 'a', 'x', 3, 6, 'b']
In [19]: use_dict_with_bisect(lst)
Out[19]: [3, 'x', 'b', 5, 6, 'a']
в случае, если вы имеете дело с большими списками, он более оптимизирован для удаления с помощью bisect
который имеет сложность около O (n2) и просто используйте встроенный python sort()
функция со сложностью Nlog(n).
In [26]: def use_dict(lst):
d = defaultdict(list)
for i in lst:
d[type(i)].append(i)
d[int].sort(reverse=True); d[str].sort()
return [d[type(i)].pop() for i in lst]
Benchmark с другими ответами, которые показывают последний подход с использованием dict
и встроенный sort
почти 1мс быстрее!--22--> чем другие подходы:
In [29]: def use_sorted1(lst):
letters = sorted(let for let in lst if isinstance(let,str))
numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
return [letters.pop() if isinstance(elt,str) else numbers.pop() for elt in lst]
.....:
In [31]: def use_sorted2(lst):
f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))
f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
return [next(f1) if isinstance(x, str) else next(f2) for x in lst]
.....:
In [32]: %timeit use_sorted1(lst * 1000)
100 loops, best of 3: 3.05 ms per loop
In [33]: %timeit use_sorted2(lst * 1000)
100 loops, best of 3: 3.63 ms per loop
In [34]: %timeit use_dict(lst * 1000) # <-- WINNER
100 loops, best of 3: 2.15 ms per loop
вот тест, который показывает, как использовать bisect
может замедлить процесс для длинных списков:
In [37]: %timeit use_dict_with_bisect(lst * 1000)
100 loops, best of 3: 4.46 ms per loop
посмотреть Ма нет iter
:
lst = ['a', 2, 'b', 1, 'c', 3]
letters = sorted(let for let in lst if isinstance(let,str))
numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
lst = [(letters if isinstance(elt,str) else numbers).pop()for elt in lst]
Я ищу способ превратить это в (ужасный) однострочный, но пока не повезло-предложения приветствуются!
я взялся за это, создав два генератора, а затем взяв из них условно:
In [116]: f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))
In [117]: f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
In [118]: [next(f1) if isinstance(x, str) else next(f2) for x in lst]
Out[118]: ['c', 1, 'b', 2, 'a', 3]
в одну строку:
list(map(list, sorted(zip(lst[::2], lst[1::2]), key=lambda x: x[1] if hasattr(x[0], '__iter__') else x[0])))
полностью не рекомендуется, но мне было весело кодировать его.
from collections import deque
from operator import itemgetter
lst = ['a', 2, 'b', 1, 'c', 3]
is_str = [isinstance(e, str) for e in lst]
two_heads = deque(map(itemgetter(1), sorted(zip(is_str, lst))))
[two_heads.pop() if a_str else two_heads.popleft() for a_str in is_str]
Почему бы нам просто не сортировать список в порядке возрастания, но убедитесь, что цифры приходят перед буквами:
[D, D, L, L, L] # L -> letter; # D -> digit
мы можем достичь этого таким образом:
>>> lst = [5, 'a', 'x', 3, 6, 'b']
>>> sorted(lst, key=lambda el: (isinstance(el, str), el))
[3, 5, 6, 'a', 'b', 'x']
затем мы просматриваем исходный массив слева направо, и если мы сталкиваемся с номером, мы выбираем элемент из начала отсортированного массива, в противном случае из конца. Полное подробное решение будет тогда:
def one_sort(lst):
s = sorted(lst, key=lambda el: (isinstance(el, str), el))
res = []
i, j = 0, len(s)
for el in lst:
if isinstance(el, str):
j -= 1
res.append(s[j])
else:
res.append(s[i])
i += 1
return res
lst = [5, 'a', 'x', 3, 6, 'b']
print(one_sort(lst)) # [3, 'x', 'b', 5, 6, 'a']
гораздо короче, но загадочное решение будет:
def one_sort_cryptic(lst):
s = sorted(lst, key=lambda el: (isinstance(el, str), el))
return [s.pop(-isinstance(el, str)) for el in lst]
lst = [5, 'a', 'x', 3, 6, 'b']
print(one_sort_cryptic(lst)) # [3, 'x', 'b', 5, 6, 'a']