Czy potrzebny jest zakres (len (a))?

85

Tego typu wyrażenia często można znaleźć w pytaniach Pythona dotyczących SO. Albo po prostu uzyskać dostęp do wszystkich elementów iterowalnej

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

Co jest po prostu nieporęcznym sposobem pisania:

for e in a:
    print(e)

Lub do przypisywania do elementów iterowalnych:

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

Która powinna być taka sama jak:

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]

Lub do filtrowania indeksów:

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

Który można wyrazić w ten sposób:

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

Lub gdy potrzebujesz tylko długości listy, a nie jej zawartości:

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

Który może być:

for _ in a:
    doSomethingUnrelatedToA()

W Pythonie mamy enumerate, krojenie, filter, sorted, itd ... jak Python forkonstrukcje są przeznaczone do iteracyjnego iterables i to nie tylko w zakresie liczb całkowitych, czy są rzeczywistym świecie używa-przypadki, gdzie trzeba in range(len(a))?

Hyperboreus
źródło
5
Myślę, że range(len(a))zwykle są to ludzie, którzy nie mają doświadczenia w Pythonie (chociaż niekoniecznie w ogóle w programowaniu).
rlms
Używałem go tylko range(len(a))wtedy, gdy uczyłem się Pythona. Obecnie nie robię tego, ponieważ, jak powiedziałeś, dość łatwo jest go wymienić.
nie całkiem. Używam range(len(a))często, bo nie potrzebuję zawartości listy a, tylko długość.
aIKid
8
Co jeśli w pętli potrzebuję uzyskać dostęp do elementu przed i po obecnym? Zwykle mam for i in range(len(a)): doSomethingAbout(a[i+1] - a[i])Jak to obejść?
Zhang18
1
@ JaakkoSeppälä zgodził się. Podałem tylko przykład, aby zilustrować główną kwestię konieczności przeglądania indeksów, a nie tylko wartości, rozumiejąc, że na końcu znajduje się przypadek narożny, który jest poza głównym punktem.
Zhang18

Odpowiedzi:

17

Jeśli potrzebujesz pracować z indeksami ciągu, to tak - używasz go ... np. Jako odpowiednik numpy.argsort ...:

>>> a = [6, 3, 1, 2, 5, 4]
>>> sorted(range(len(a)), key=a.__getitem__)
[2, 3, 1, 5, 4, 0]
Jon Clements
źródło
OK, to wygląda rozsądnie. Dziękuję Ci bardzo. Ale pytanie brzmi: co zrobisz z nowo posortowaną listą indeksów. Jeśli za pośrednictwem tej listy ponownie uzyskasz dostęp do niektórych iteracji, pies gryzie własny ogon.
Hyperboreus
1
Odpowiednik: [ix for ix, _ in sorted(enumerate(a), key=lambda i: i[1])]chociaż, chociaż twój jest prawdopodobnie ładniejszy / bardziej geekowy.
Erik Kaplun
10

A jeśli potrzebujesz dostępu do dwóch elementów listy jednocześnie?

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

Możesz tego użyć, ale prawdopodobnie jest to mniej jasne:

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

Osobiście nie jestem z żadnego w 100% zadowolony!

Giswok
źródło
1
for ix, i in enumerate(a)wydaje się być równoważne, nie?
Erik Kaplun
2
Zamiast tego należy używać parami .
latające owce
W takich sytuacjach robię:for a1,a2 in zip(a[:-1],a[1:])
Luca Amerio,
7

Krótka odpowiedź : mówiąc matematycznie nie, w praktyce tak, na przykład w przypadku programowania intencjonalnego.

Technicznie rzecz biorąc, odpowiedź brzmiałaby „nie, nie jest to potrzebne”, ponieważ można to wyrazić za pomocą innych konstrukcji. Ale w praktyce używam for i in range(len(a)(lub for _ in range(len(a))jeśli nie potrzebuję indeksu), aby wyraźnie zaznaczyć, że chcę iterować tyle razy, ile jest elementów w sekwencji, bez konieczności używania elementów w sekwencji do niczego.

Więc: "Czy jest taka potrzeba?" ? - tak, potrzebuję go do wyrażenia znaczenia / przeznaczenia kodu ze względu na czytelność.

Zobacz też: https://en.wikipedia.org/wiki/Intentional_programming

I oczywiście, jeśli w ogóle nie ma kolekcji, która jest powiązana z iteracją, for ... in range(len(N))jest to jedyna opcja, aby nie uciekać się doi = 0; while i < N; i += 1 ...

Erik Kaplun
źródło
Jakie zalety ma for _ in range(len(a))ponad for _ in a?
Hyperboreus
@Hyperboreus: tak, właśnie poprawiłem swoją odpowiedź na kilka sekund przed Twoim komentarzem ... więc wydaje mi się, że różnica polega na tym, czy naprawdę chcesz powiedzieć „powtórz TAK WIELE RAZY, ile jest elementów w a w przeciwieństwie do ”dla każdego element w a, niezależnie od zawartości a ... więc tylko niuans w programowaniu intencjonalnym.
Erik Kaplun
Dziękuję za przykład. Zawarłem to w moim pytaniu.
Hyperboreus
2
Aby uzyskać listę 'hello'zawierającą tyle pozycji, ile na liście a, użyjb = ['hello'] * len(a)
steabert
2

Idąc przez komentarze, a także osobistego doświadczenia, ja nie powiedzieć, nie ma potrzeby do range(len(a)). Wszystko, co możesz zrobić, range(len(a))możesz zrobić w inny (zwykle znacznie bardziej efektywny) sposób.

Podałeś wiele przykładów w swoim poście, więc nie będę ich tutaj powtarzał. Zamiast tego podam przykład dla tych, którzy mówią: „A jeśli chcę tylko długości a, a nie elementów?”. To jeden z niewielu przypadków, w których możesz rozważyć użycie range(len(a)). Jednak nawet to można zrobić w następujący sposób:

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

Odpowiedź Clementsa (jak pokazuje Allik) można również przerobić, aby usunąć 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]
>>>

Podsumowując, range(len(a))nie jest potrzebne . Jedynym plusem jest czytelność (intencja jest jasna). Ale to tylko preferencje i styl kodu.


źródło
Dziękuję Ci bardzo. Z drugiej strony, czytelność jest (częściowo) w oku patrzącego. Interpretuję for _ in a:jako „Iteruję po a, ale ignoruję jego zawartość”, ale interpretuję for _ in range(len(a))jako „Uzyskaj długość a, następnie tworzę liczbę liczb całkowitych o tej samej długości, a na koniec ignoruję zawartość”.
Hyperboreus
1
@Hyperboreus - Bardzo prawda. To tylko styl kodu. Moim celem było pokazanie, że nigdy nie będzie range(len(a))scenariusza „Muszę użyć albo nie mogę tego zrobić”.
Na marginesie: np. W erlang pojedynczy znak podkreślenia jest zmienną anonimową. Jest to jedyna zmienna, która może zostać ponownie przypisana (lub "dopasowana"), w przeciwieństwie do innych zmiennych, ponieważ erlang nie pozwala na destrukcyjne przypisanie (co ogólnie jest obrzydliwością i osłabia zasłonę między nami a królestwami dolnymi, gdzie HE czeka za ścianą w swoim pałacu zbudowanym z torturowanego szkła).
Hyperboreus
2

Czasami matplotlib wymaga range(len(y))np. While y=array([1,2,5,6]), plot(y)działa dobrze, scatter(y)a nie. Trzeba pisać scatter(range(len(y)),y). (Osobiście uważam, że to błąd scatter; ploti jego przyjaciół scatteri stempowinni używać tych samych sekwencji wywołań tak często, jak to możliwe.)

Charles Boncelet
źródło
2

Dobrze jest mieć, gdy potrzebujesz użyć indeksu do jakiejś manipulacji, a posiadanie bieżącego elementu nie wystarczy. Weźmy na przykład drzewo binarne, które jest przechowywane w tablicy. Jeśli masz metodę, która prosi o zwrócenie listy krotek, która zawiera bezpośrednie dzieci każdego węzła, potrzebujesz indeksu.

#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

Oczywiście, jeśli element, nad którym pracujesz, jest obiektem, możesz po prostu wywołać metodę get children. Ale tak, naprawdę potrzebujesz indeksu tylko wtedy, gdy wykonujesz jakąś manipulację.

CleoR
źródło
1

Mam przypadek użycia, którego nie wydaje mi się żaden z twoich przykładów.

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

Jestem stosunkowo nowy w Pythonie, ale cieszę się, że mogę nauczyć się bardziej eleganckiego podejścia.

Jim
źródło
4
Moja ignorancja. Jest zip, znacznie bardziej pythonowy sposób na równoległe iterowanie na 2 listach.
Jim
1
Hah, przyszedłem tutaj z bardzo podobnym przypadkiem użycia… [a - b for a, b in zip(list1, list2)]jest o wiele ładniejszy niż [list1[i] - list2[i] for i in range(len(list1))]… Dzięki!
kevlarr
1

Jeśli musisz iterować po pierwszych len(a)elementach obiektu b(czyli większych niż a), prawdopodobnie powinieneś użyć range(len(a)):

for i in range(len(a)):
    do_something_with(b[i])
alekspiryna
źródło
2
To może być jaśniejsze:for b_elem in b[:len(a)]:...
aquirdturtle
@aquirdturtle Być może jest to bardziej przejrzyste, ale Twoje rozwiązanie tworzy nową listę, która może być kosztowna, jeśli b & a są duże.
PM 2Ring
Należy to obsłużyć za pomocą itertools.islicezamiast tego.
MisterMiyagi,
1

Czasami naprawdę nie przejmujesz się samą kolekcją . Na przykład utworzenie prostej linii dopasowania modelu w celu porównania „przybliżenia” z surowymi danymi:

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

W tym przypadku wartości samego ciągu Fibonacciego były nieistotne. Wszystko, czego potrzebowaliśmy, to rozmiar sekwencji wejściowej, z którą porównaliśmy.

Mateen Ulhaq
źródło
Jestem nowy w Pythonie, co ** robię w tym przypadku? Czytałem o * args i ** kwargs, ale wygląda to inaczej.
lukas_o
1
Potęgowanie. phi do potęgi n.
Mateen Ulhaq
0

Bardzo prosty przykład:

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

Nie przychodzi mi do głowy rozwiązanie, które nie wykorzystuje szybko kompozycji pasmowo-soczewkowej.

Ale prawdopodobnie zamiast tego należy to zrobić, try .. exceptaby pozostać pytonicznym, jak sądzę ...

IARI
źródło
1
if id < len(self.itemList) Ale try...exceptjest lepiej, jak mówisz.
saulspatz
To nie uwzględnia identyfikatora <0.
IARI
0

Mój kod to:

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:])

Jest to sumator binarny, ale nie sądzę, aby zakres len lub wnętrze można było zastąpić, aby był mniejszy / lepszy.

Matt GSM MattGSM
źródło