Учитывая этот вложенный список:
foo = [["apple", "cherry"], ["banana"], ["pear", "raspberry", "pineapple"]]
Я хочу сохранить структуру и заменить все элементы последовательными номерами. Мой желаемый результат:
[[0, 1], [2], [3, 4, 5]]
Я надеялся на простой однострочник, но самое короткое рабочее решение, которое я придумал, было:
foo_numbers = []
count = 0
for i, sublist in enumerate(foo):
foo_numbers.append([])
for item in sublist:
foo_numbers[i].append(count)
count += 1
Обычно эти ручные итераторы указывают на то, что есть более питонический способ добиться того же. Если это должно быть сделано с пониманием списка, я не мог понять, как создать «общий счетчик» для обоих циклов, чтобы он не начинался с нуля для каждого sublist.






Вы можете использовать itertools.count с пониманием вложенного списка:
from itertools import count
foo = [["apple", "cherry"], ["banana"], ["pear", "raspberry", "pineapple"]]
c = count() # 0 start is default, e.g. count(1) will start from 1
res = [[next(c) for _ in lst] for lst in foo]
print(res)
# [[0, 1], [2], [3, 4, 5]]
Просто обратите внимание: использование timeit, кажется, указывает на то, что это решение медленнее, чем ваш исходный код.
Сравнивая эти решения:
import itertools
def f1(foo):
"""original version in question"""
foo_numbers = []
count = 0
for i, sublist in enumerate(foo):
foo_numbers.append([])
for item in sublist:
foo_numbers[i].append(count)
count += 1
return foo_numbers
def f2(foo):
"""@jpp answer"""
c = itertools.count()
return [[next(c) for _ in range(len(lst))] for lst in foo]
def f3(foo):
"""@DanielMesejo answer"""
foo_numbers = []
count = 0
for i, sublist in enumerate(foo):
foo_numbers.append([j for j in range(count, len(sublist) + count)])
count += len(sublist)
return foo_numbers
Дает нам это:
>>> import timeit
>>>
>>> timeit.timeit('f(foo)', 'from __main__ import f1 as f, foo')
1.2990377170499414
>>> timeit.timeit('f(foo)', 'from __main__ import f2 as f, foo')
2.260929798008874
>>> timeit.timeit('f(foo)', 'from __main__ import f3 as f, foo')
2.1552230638917536
Кажется, оригинальная версия быстрее (в 2 раза).
Спасибо за указание на это! На самом деле время не имеет значения. Мой список на самом деле не намного больше, чем код примера, и он вызывается только один раз. Но эти циклы for оставляют меня в недоумении, когда я возвращаюсь к коду год спустя и задаюсь вопросом, что они делают. Поэтому я искал что-то тонкое
Почему два вложенных for loop быстрее, чем list comprehension?
@offeltoffel Я тоже не знаю, почему в этом случае циклы for работают быстрее, чем понимание списка. Я только что сделал временные тесты и сам был удивлен.
Вы можете использовать понимание списка вместо внутреннего цикла:
foo = [["apple", "cherry"], ["banana"], ["pear", "raspberry", "pineapple"]]
foo_numbers = []
count = 0
for i, sublist in enumerate(foo):
foo_numbers.append([j for j in range(count, len(sublist) + count)])
count += len(sublist)
print(foo_numbers)
Вывод
[[0, 1], [2], [3, 4, 5]]
В общем, понимание списков быстрее, чем циклы для создания списков. В качестве альтернативы вы можете преобразовать объект диапазона в список, например:
foo_numbers.append(list(range(count, len(sublist) + count)))
range(count, len(sublist) + count) был своего рода отсутствующим кусочком головоломки, которого не хватало, чтобы составить это решение самостоятельно. Спасибо за прояснение моего зрения там
Просто примечание: см. мой ответ; кажется, что эта версия также медленнее исходного кода
Используйте приведенное ниже решение, если вы действительно не надо хотите что-либо импортировать:
foo = [["apple", "cherry"], ["banana"], ["pear", "raspberry", "pineapple"]]
count = -1
def indexer(fruits):
global count
count += 1
return count
foo_new = list(map(lambda fruits: list(map(indexer, fruits)), foo))
print(foo_new)
[[0, 1], [2], [3, 4, 5]]
В этом ответе есть несколько хороших идей. Я на самом деле не ограничен, это скорее личные предпочтения. Спасибо!
Это аккуратно! По умолчанию я не импортирую itertools и обычно предпочитаю простой python, но это определенно лучше моего громоздкого решения.