Pull to refresh

Comments 74

Вариант 3 не только самый быстрый, но и самый понятный и простой.

Лямбда-выражения это, конечно, типа круто, но смысл использования в данном конкретном случае под вопросом. Экономия строк?
Разминка для мозгов.

К тому же я предлагаю вам решить задачку в конце, способом аналогичным Варинту 3. И мы сравним с моим, аналогичным Варианту 6.
Разминка для мозгов людям, которым понадобится разбираться в вашем коде?
Да, вы же тоже разобрались ..)
Судя по всему попытка ностальгия за перл-прошлым у автора :)
Скорее это ностальгия за Хаскель-будущим автора)
как люди ограничены…
Писать на perl можно на любом языке программирования
Можно, но тут это не в тему. Чистые функции повышают читабельность и понятность кода. Но у них естественно есть некий порог вхождения, до которого ничего не понятно.

Если вас смущает, что в программировании есть вещи сложнее мануала к PHP, то кто виноват?
меня не смущают лямбды, просто при отладке чудого кода, допустим при поиске какойнибудь трудноуловимой ошибки мне было бы приятнее встретить 3тий вариант, а не 5тый.
Для вложенных списков, самый очевидный вариант с рекурсией:
def listmerge(lst):
    res = []
    for el in lst:
        res += listmerge(el) if isinstance(el, list) else [el]
    return res
Вариант 7:
import itertools
listmerge = lambda lst: list(itertools.chain(*(el for el in lst))) 
Как-то у вас сложно всё вышло.

import itertools
listmerge = lambda lst: list(itertools.chain(*lst))
Да, действительно, что-то я перемудрил :)
А если добавить волшебную звёздочку — можно будет передавать сколько угодно параметров.

from itertools import chain
listmerge = lambda *lst: list(chain(*lst))

Я вот только не знаю хорошо это или плохо.
Это никак. Функция перестаёт делать то, что требуется в задаче.
>>> l1 = [1,2,3]
>>> l2 = [4,5,6]
>>> l3 = [7,8,9]
>>> listmerge(l1, l2, l3)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Ничего функция не перестаёт. Прикол в том, что можно передавать сколько угодно списков, а на выходе получить один. Список списков можно передать так: listmerge(*lstlst)

Но вот, судя по этому ответу, код перестаёт быть понятным всем.
Я прекрасно понимаю как работает звёздочка.

Функция не выполняет то, что задано в задаче. В задаче сказано: склеить список списков. Ваша функция этого не делает.
>>> listmerge(*[[1,2], [3,4], [5,6,7], [8]])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

почему не делает?
Вот конкретно это можно написать проще:

import itertools
listmerge = lambda lst: list(itertools.chain(*lst))

listmerge([[1,2], [3,4]])

а не вводить лишнюю звёздочку, чтобы потом от неё избавляться.
Подозреваю, что по скорости это будет на уровне с List comprehensions вариантом (№5)
По-простому

def merge(lst, res=[]):
  for el in lst:
    merge(el) if isinstance(el, list) else res.append(el)
  return res

С извратом ;)

merge = lambda lst: reduce(lambda a, b: a.extend(merge(b)) or a if isinstance(b, list) else a.append(b) or a, lst, [])
Только вот хотел узнать — не нашел — переменных а-ля static в php в чистом виде нельзя создавать в питоньих функциях? Ну кроме «моего» способа через значение по умолчанию.
Нельзя. Способ через связывание с анонимным изменяемым объектов вполне себе нормальный и питонячий.
Ага, хотя я бы не назвал второй вариант большим извратом, тк рекурсивные алгоритмы в форме лямбд записываются более элегантно. Вот чисто итеративный вариант будет извратом.

единственный комментарий: можно убрать «or» из цикла:
mergeto = lambda dst,lst: reduce(lambda a,b: a.extend(b) if type(b) is list else a.append(b), lst, dst) or dst
merge = lambda lst: mergeto([], lst)
опечатался
mergeto = lambda dst,lst: reduce(lambda none,b: dst.extend(b) if type(b) is list else dst.append(b), lst, dst) or dst
merge = lambda lst: mergeto([], lst)
но это уже 2 строчки :)
Вторую можно выкинуть) Или обернуть первую еще одной лямбдой. Все из-за того, что питоновские in-place методы возвращают None вместо объекта…
mutables в определении функции — зло
Еслм быть точнее, то они зло в определении умолчальных аргументов.
А вот подобную штуку вряд ли удастся раскрыть:
a = [1]
a.append(a)
Чисто из любопытства: в питоне нет рубишного .flatten?
Вы расскажите что это.
В ruby:

a = [1, [2,3], [4,5]]
a.flatten
p a

В результате: [1, 2, 3, 4, 5]

В Python ближайший эквивалент — chain из пакета itertools:

from itertools import chain
a = [1, [2,3], [4,5]]

a = list(chain(*a))

print a

Поскольку chain вернёт итератор, мы из него делаем список, передав итератор в качестве аргумента конструктора list.
Fandorin забыл восклицательный знак после flatten, иначе вызывается метод, который результат не сохраняет в массив, и пропадает он по концу вычисления.

bolk, кстати, если интересно, стандартный метод flatten в руби 1.9 имеет еще и параметр глубины, на какую «уплощать»:

irb(main):001:0> [1,[2,3],[4,[5,6]]]
=> [1, [2, 3], [4, [5, 6]]]
irb(main):002:0> [1,[2,3],[4,[5,6]]].flatten
=> [1, 2, 3, 4, 5, 6]
irb(main):003:0> [1,[2,3],[4,[5,6]]].flatten 0
=> [1, [2, 3], [4, [5, 6]]]
irb(main):004:0> [1,[2,3],[4,[5,6]]].flatten 1
=> [1, 2, 3, 4, [5, 6]]
irb(main):005:0> [1,[2,3],[4,[5,6]]].flatten 2
=> [1, 2, 3, 4, 5, 6]
Забавно :) В Python это придётся делать рекурсией.
Забавно :) В Python это придётся делать рекурсией.
ruby: a = [1,2,3]; b = [3,4,5]; c = a+b # => [1,2,3,3,4,5]
python: a=[1,2,3]; b = [3,4,5]; c=a+b # => [1,2,3,4,5]

задача из a=[[1,2,3], [3,4,5], [6]] сделать [1,2,3,4,5,6]
Фигня, ща разрулим.
a=[[1,2,3], [3,4,5], [6]]; a.flatten #=> [1,2,3,4,5,6]
В Python только немного сложнее получается (chain импортируется из itertools):

a = list(chain(*a))
python: a = [1,2,3]; b = [3,4,5]; c = a+b # => [1,2,3,3,4,5]

ваш код делает явно не то что подразумевалось автором топика.
>>> a = [[1,2,3], [4,5,6], [7,8,9]]
>>> l = [x for lst in a for x in lst]
>>> l
[1, 2, 3, 4, 5, 6, 7, 8, 9]

или я неправильно понял что надо сделать?
Нет, всё правильно.
а, пардон. это и есть способ 5 =))
На лиспе это делать надо, на лиспе =)
Вот конкретно это нафиг делать не надо ни на чем: ) То есть если это и есть собственно цель.
Ну и хотя бы по примеру любителя ruby написали бы мега шустрый пример что ли.
(defun listmerge (list)
(if (null list)
nil
(append (car list) (listmerge (cdr list)))
)
)

(listmerge '((1 2) (3) (5) (4 6)))
(1 2 3 5 4 6)
Вот ещё на хаскелле, там точно проще.
merge = foldr [] (++)

main = print ( merge [ [1, 2], [3, 4], [5, 6] ] )
>[1,2,3,4,5,6]
в чем разница? Кроме того Хаскелль статически типизированный язык и для списков произвольной вложенности надо будет изобретать тип наподобие деревьев, и это будет уже далеко не 1 строчка.
Решение задачки:

def iter_flatten(iterable):
  it = iter(iterable)
  for e in it:
    if isinstance(e, (list, tuple)):
      for f in iter_flatten(e):
        yield f
    else:
      yield e


Вернется конечно генератор, а не список, но так даже практичнее.

А вот в руби насколько я помню есть нативный метод для этой задачи.
Я бы поостерегся возвращать генератор, зависящий от мутируемых объектов. Можно получить ерунду если программа продолжит изменять данные.
Для чего вводится переменная it?
На питоне быстрее всего будет с lambda, map или filter
Оффтоп. Когда я начал изучать Erlang, я был поражен, как просто и быстро там делаются подобные вещи. Вот сейчас, мне стало интересно, как быстро эрланг смержит большие списки. Накидал небольшой бенч

L1 = [random:uniform(1000) || N <- lists:seq(1, 1000000)].

Это список из миллиона чисел < 1000
L2, L3, L4 генерируются подобным образом(да, ощутимо по времени)

timer:tc(lists, flatten, [L1, [L2, L3], L4]).
>{250000,
 [93,444,724,946,502,312,598,916,667,478,597,143,210,698,160,
  559,215,458,422,6,563,476,401,310,59,579,990|...]}

4 списка по 1000000 элементов сливались четверть секунды на среднем ноуте. Я бы побоялся запустить такое на питоне или руби, хотя сам питоновод. Жду unladen swallow.
0.15 сек. (Отсутствие рандомных чисел сути не меняет)

Код list.py:

def listmerge(lstlst):
    all=[]
    for lst in lstlst:
      all.extend(lst)
    return all	

list_over_9000 = listmerge([list(xrange(1000))*1000, list(xrange(1000))*1000, list(xrange(1000))*1000, list(xrange(1000))*1000])

print "len = %s" % len(list_over_9000)


Что сказал профайлер:

C:\Users\user\Desktop\pff>python -m cProfile list.py
len = 4000000
         10 function calls in 0.155 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.155    0.155 <string>:1(<module>)
        1    0.081    0.081    0.155    0.155 list.py:1(<module>)
        1    0.000    0.000    0.073    0.073 list.py:1(listmerge)
        1    0.001    0.001    0.155    0.155 {execfile}
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        4    0.073    0.018    0.073    0.018 {method 'extend' of 'list' objects}


Я бы еще написал пару слов про disco (реализация MapReduce на языке Erlang для параллельных вычислений на больших наборах данных с интерфейсом для Питона), но похоже уже в другой раз, ибо очень хочется спать.
Извините, но Ваш пример некорректен. Во-первых, ваш listmerge не мержит вложенные списки; во-вторых, Вы сгенерировали кучу мусора: 4 списка, в каждом по 1000 нормальных элементов из 1000000. Остальные 999000 элементов — 999 копий первой тысячи

>>>a = list(xrange(1000))*1000
>>>a[999] is a[1999]
True

или
>>>a = list(xrange(1000))*1000
>>>id(a[999]) == id(a[1999) == id(2999) == ...
True
упс, опечатка последнем примере, но смысл, я думаю, понятен
Да это правда — объекты идентичны. Но для списков с числами до 1 млн ситуация меняется не критично: 0.291 s

def test(l1, l2, l3, l4):	
	list_over_9000 = listmerge([l1, l2, l3, l4])

	print "len = %s" % len(list_over_9000)
	
l1, l2, l3, l4 = list(xrange(1000000)), list(xrange(1000000)), list(xrange(1000000)), list(xrange(1000000))
test(l1, l2, l3, l4)
«В два раза» это «не критично»?
Во-первых они сравнивают на разных машинах, так что это ничего не значит. Во-вторых зависит от задачи, если список из 10 элементов, то не критично)
Я говорил про разницу в двух вышеуказанных примерах хабраюзера habrcut.
Что касается не вложенности списков тут пример с функцией варианта №7, время чуть больше чем показал Erlang — 0.342 сек.
Вы забыли помериться своими компьютерами. :-)
ну это ж не перл чтобы однострочки тут выписывать, третий самый быстрый и самый простой, в чем проблема.
всё равно, руки за «ll» на до отрывать!
Если оно встречается два раза в короткой строке, которую можно охватить взглядом, почему нет?)
Тем более, что с нормальными шрифтами (какие должны использоваться в программировании) все выглядит не так ужасно.
вы идиот?

потому что так делать нельзя! есть правила хорошего тона в обзывании переменных. нарушать их нельзя, потому что их нельзя нарушать никогда!
Фанатичное следование Code Conventions полезный скилл для рядового кодера, с чем вас и поздравляю.
а отступление от них в публичных публикациях — явный признак неуважения к аудитории и тяги к самолюбованию, с чем поздравляю вас

достаточно было использовать общепринятые foo, bar, etc, чтобы избежать обвинений в ламерстве и отвращения со стороны людей, которые видят каждый день тысячи строк хорошего кода.
Задачи «избежать обвинений в ламерстве» от аудитории, которой не понятна строка «lambda ll: sum(ll, [])» у меня не было и никогда не будет. Рекомендую впредь мои посты пропускать, заменяя просмотром «тысяч строк хорошего кода». Тема закрыта.
вообще я ваши посты и не собирался читать, тема была интересна, а внутри обычное говно оказалось
Sign up to leave a comment.

Articles