Нужен ли диапазон (len (a))?

85

Выражения этого типа часто встречаются в вопросах Python по SO. Либо для доступа ко всем элементам итерируемого

for i in range(len(a)):
    print(a[i])

Это просто громоздкий способ написания:

for e in a:
    print(e)

Или для присвоения элементам итерируемого:

for i in range(len(a)):
    a[i] = a[i] * 2

Что должно быть таким же, как:

for i, e in enumerate(a):
     a[i] = e * 2
# Or if it isn't too expensive to create a new iterable
a = [e * 2 for e in a]

Или для фильтрации по индексам:

for i in range(len(a)):
    if i % 2 == 1: continue
    print(a[i])

Что можно выразить так:

for e in a [::2]:
    print(e)

Или когда вам просто нужна длина списка, а не его содержимое:

for _ in range(len(a)):
    doSomethingUnrelatedToA()

Что могло быть:

for _ in a:
    doSomethingUnrelatedToA()

В Python мы имеем enumerate, нарезка, filter, sortedи т.д. ... Как питон forконструкция предназначена перебрать итерируемые и не только в диапазон целых чисел, есть ли реальные потребительные случаи , когда вам нужны in range(len(a))?

Гиперборей
источник
5
Я думаю, что range(len(a))обычно это люди, которые довольно неопытны в Python (хотя и не обязательно в программировании в целом).
rlms 04
Я использовал только range(len(a))тогда, когда изучал Python. В настоящее время я этого не делаю, потому что, как вы сказали, его довольно легко заменить.
на самом деле, нет. Я использую range(len(a))часто, потому что мне нужно не содержимое списка a, а только его длина.
aIKid 04
8
Что, если в цикле мне нужно получить доступ к элементу до и после текущего? У меня обычно есть for i in range(len(a)): doSomethingAbout(a[i+1] - a[i])Как это обойти?
Zhang18
1
@ JaakkoSeppälä согласился. Я просто привел пример, чтобы проиллюстрировать основную проблему необходимости перебирать индексы, а не только значения, понимая, что в конце есть угловой случай, который является помимо основного.
Zhang18

Ответы:

17

Если вам нужно работать с индексами последовательности, тогда да - вы используете его ... например, для эквивалента numpy.argsort ...:

>>> a = [6, 3, 1, 2, 5, 4]
>>> sorted(range(len(a)), key=a.__getitem__)
[2, 3, 1, 5, 4, 0]
Джон Клементс
источник
Хорошо, это выглядит разумным. Большое спасибо. Но вопрос в том, что вы будете делать со своим недавно отсортированным списком индексов. Если через этот список вы снова получите доступ к некоторой итерации, собака кусает себя за хвост.
Hyperboreus
1
Эквивалентно: [ix for ix, _ in sorted(enumerate(a), key=lambda i: i[1])]хотя, возможно, ваш, возможно, лучше и интереснее.
Эрик Каплун
10

Что делать, если вам нужно получить доступ к двум элементам списка одновременно?

for i in range(len(a[0:-1])):
    something_new[i] = a[i] * a[i+1]

Вы можете использовать это, но, вероятно, это менее понятно:

for i, _ in enumerate(a[0:-1]):
     something_new[i] = a[i] * a[i+1]

Лично я тоже не на 100% доволен!

Giswok
источник
1
for ix, i in enumerate(a)кажется эквивалентным, нет?
Эрик Каплун
2
Вместо этого следует использовать попарно .
летучая овца
В таких ситуациях я делаю:for a1,a2 in zip(a[:-1],a[1:])
Лука Америо
7

Краткий ответ : с математической точки зрения - нет, с практической - да, например, для преднамеренного программирования.

Технически ответ был бы «нет, это не нужно», потому что это можно выразить с помощью других конструкций. Но на практике я использую for i in range(len(a)(или for _ in range(len(a))если мне не нужен индекс), чтобы явно указать, что я хочу повторять столько раз, сколько есть элементов в последовательности, без необходимости использовать элементы в последовательности для чего-либо.

Итак: "А есть ли необходимость?" ? - да, мне это нужно, чтобы выразить смысл / намерение кода для удобства чтения.

См. Также: https://en.wikipedia.org/wiki/Intentional_programming

И очевидно, что если нет коллекции, которая вообще связана с итерацией, for ... in range(len(N))это единственный вариант, чтобы не прибегать кi = 0; while i < N; i += 1 ...

Эрик Каплун
источник
Какие преимущества имеет for _ in range(len(a))перед for _ in a?
Hyperboreus
@Hyperboreus: да, я только что изменил свой ответ за несколько секунд до вашего комментария ... так что, я думаю, разница в том, хотите ли вы действительно четко говорить о том, что "повторять КАК МНОГО РАЗ, поскольку есть элементы в a" вместо "для каждого элемент в a, независимо от содержания a" ... так что это просто нюанс преднамеренного программирования.
Эрик Каплун
Спасибо за ваш пример. Я включил это в свой вопрос.
Hyperboreus
2
Чтобы получить список 'hello'с таким количеством элементов, как в списке a, используйтеb = ['hello'] * len(a)
steabert
2

Не Судя по комментариям, а также личного опыта, я говорю нет, нет необходимости в range(len(a)). Все, что вы можете сделать, range(len(a))можно сделать другим (обычно гораздо более эффективным) способом.

Вы привели много примеров в своем посте, поэтому я не буду их здесь повторять. Вместо этого я приведу пример для тех, кто говорит: «Что, если мне нужна только длина a, а не предметы?». Это один из немногих случаев, когда вы могли бы подумать об использовании range(len(a)). Однако даже это можно сделать так:

>>> a = [1, 2, 3, 4]
>>> for _ in a:
...     print True
...
True
True
True
True
>>>

Ответ Клементса (как показано Алликом) также можно переработать, чтобы удалить range(len(a)):

>>> a = [6, 3, 1, 2, 5, 4]
>>> sorted(range(len(a)), key=a.__getitem__)
[2, 3, 1, 5, 4, 0]
>>> # Note however that, in this case, range(len(a)) is more efficient.
>>> [x for x, _ in sorted(enumerate(a), key=lambda i: i[1])]
[2, 3, 1, 5, 4, 0]
>>>

Итак, в заключение, range(len(a))не нужно . Его единственный положительный момент - читаемость (его цель ясна). Но это всего лишь предпочтения и стиль кода.


источник
Большое спасибо. И опять же, читаемость (частично) в глазах смотрящего. Я интерпретирую for _ in a:как «Итерировать по a, но игнорировать его содержимое», но я интерпретирую for _ in range(len(a))как «Получить длину a, затем создать несколько целых чисел той же длины и, наконец, игнорировать содержимое».
Hyperboreus
1
@Hyperboreus - Совершенно верно. Это просто стиль кода. Моей целью было показать, что никогда не будет range(len(a))сценария «Я должен использовать или я не могу этого сделать».
Замечание: например, в erlang одиночное подчеркивание - это анонимная переменная. Это единственная переменная, которую можно переназначить (или «сопоставить»), в отличие от других переменных, поскольку erlang не допускает деструктивного присваивания (что, вообще говоря, является мерзостью и ослабляет завесу между нами и нижними мирами, где ОН ждет за стеной в своем дворце, построенном из истерзанного стекла).
Hyperboreus
2

Иногда Matplotlib требуется range(len(y)), например, в то время как y=array([1,2,5,6]), plot(y)работает отлично, scatter(y)не делает. Надо писать scatter(range(len(y)),y). (Лично я считаю, что это ошибка в scatter; plotи его друзьях, scatterи поэтому stemследует как можно чаще использовать одни и те же последовательности вызовов.)

Шарль Бонсле
источник
2

Приятно иметь, когда вам нужно использовать индекс для каких-то манипуляций, а наличия текущего элемента недостаточно. Возьмем, к примеру, двоичное дерево, которое хранится в массиве. Если у вас есть метод, который просит вас вернуть список кортежей, содержащий все прямые дочерние узлы, вам понадобится индекс.

#0 -> 1,2 : 1 -> 3,4 : 2 -> 5,6 : 3 -> 7,8 ...
nodes = [0,1,2,3,4,5,6,7,8,9,10]
children = []
for i in range(len(nodes)):
  leftNode = None
  rightNode = None
  if i*2 + 1 < len(nodes):
    leftNode = nodes[i*2 + 1]
  if i*2 + 2 < len(nodes):
    rightNode = nodes[i*2 + 2]
  children.append((leftNode,rightNode))
return children

Конечно, если элемент, над которым вы работаете, является объектом, вы можете просто вызвать метод get children. Но да, вам действительно нужен индекс, только если вы делаете какие-то манипуляции.

CleoR
источник
1

У меня есть вариант использования, который я не верю ни в один из ваших примеров.

boxes = [b1, b2, b3]
items = [i1, i2, i3, i4, i5]
for j in range(len(boxes)):
    boxes[j].putitemin(items[j])

Я относительно новичок в python, хотя очень рад узнать более элегантный подход.

Джим
источник
4
Мое невежество. Есть zip, гораздо более питонический способ параллельного перебора двух списков.
Джим
1
Ха, я пришел сюда с действительно похожим вариантом использования ... [a - b for a, b in zip(list1, list2)]он намного лучше, чем [list1[i] - list2[i] for i in range(len(list1))]... Спасибо!
kevlarr
1

Если вам нужно перебрать первые len(a)элементы объекта b(который больше чем a), вам, вероятно, следует использовать range(len(a)):

for i in range(len(a)):
    do_something_with(b[i])
алекспирин
источник
2
Это могло бы быть яснее:for b_elem in b[:len(a)]:...
aquirdturtle
@aquirdturtle Возможно, это яснее, но ваше решение создает новый список, который может быть дорогостоящим, если b & a большие.
PM 2Ring
itertools.isliceВместо этого следует использовать .
Мистер Мияги
1

Иногда вас действительно не волнует сама коллекция . Например, создание простой линии соответствия модели для сравнения «приближения» с необработанными данными:

fib_raw = [1, 1, 2, 3, 5, 8, 13, 21] # Fibonacci numbers

phi = (1 + sqrt(5)) / 2
phi2 = (1 - sqrt(5)) / 2

def fib_approx(n): return (phi**n - phi2**n) / sqrt(5)

x = range(len(data))
y = [fib_approx(n) for n in x]

# Now plot to compare fib_raw and y
# Compare error, etc

В этом случае значения самой последовательности Фибоначчи не имели значения. Все, что нам здесь нужно, это размер входной последовательности, с которой мы сравниваем.

Матин Улхак
источник
Я новичок в Python, что делают ** в этом случае? Я читал о * args и ** kwargs, но это выглядит иначе.
lukas_o
1
Возведение в степень. фи в степени n.
Mateen Ulhaq
0

Очень простой пример:

def loadById(self, id):
    if id in range(len(self.itemList)):
        self.load(self.itemList[id])

Я не могу придумать решение, в котором бы быстро не использовалась композиция range-len.

Но, вероятно, вместо этого это должно быть сделано, try .. exceptчтобы остаться питоническим, я думаю ...

IARI
источник
1
if id < len(self.itemList) Но try...except лучше, как вы говорите.
saulspatz
Это не учитывает id <0.
IARI,
0

Мой код:

s=["9"]*int(input())
for I in range(len(s)):
    while not set(s[I])<=set('01'):s[i]=input(i)
print(bin(sum([int(x,2)for x in s]))[2:])

Это двоичный сумматор, но я не думаю, что диапазон len или внутреннюю часть можно заменить, чтобы сделать его меньше / лучше.

Мэтт GSM MattGSM
источник