Чи потрібен діапазон (len (a))?


83

Вирази цього типу часто зустрічаються у питаннях 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

Я використовував лише range(len(a))тоді, коли вивчав Python. У наш час я цього не роблю, тому що, як ви заявили, замінити його досить легко.

не зовсім. Я range(len(a))часто використовую , оскільки мені потрібен не вміст списку a, а лише довжина.
aIKid

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]

Добре, це виглядає розумно. Дуже дякую. Але питання в тому: що ви зробите зі своїм нещодавно відсортованим списком індексів. Якщо через цей список ви знову отримаєте доступ до якогось ітератора, собака кусає свій хвіст.
Гіперборей

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]

Особисто я не задоволений жодним!


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: так, я щойно змінив свою відповідь за кілька секунд до вашого коментаря ... тож, я думаю, різниця полягає в тому, чи хочете ви бути явними щодо "повторюватиa СТОЛЬКІ ЧАСИ, скільки є елементів у " на відміну від "для кожного елемент у a, незалежно від змісту a" ... так що просто нюанс навмисного програмування.
Ерік Каплун,

Дякую за приклад. Я включив це у своє запитання.
Гіперборей

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
>>>

Відповідь Clements (як показав Allik) також можна переробити, щоб видалити 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, потім створити кількість цілих чисел однакової довжини, а потім остаточно ігнорувати вміст".
Гіперборей

1
@Hyperboreus - Дуже правда. Це просто стиль коду. Моєю метою було показати, що ніколи не буде range(len(a))сценарію "я повинен використовувати або я не можу цього зробити".

До примітки: Наприклад, в erlang одинарне підкреслення є анонімною змінною. Це єдина змінна, яку можна перепризначити (або "зіставити"), на відміну від інших змінних, оскільки erlang не дозволяє руйнівного присвоєння (що, загалом кажучи, є мерзотою і послаблює завісу між нами та нижніми сферами, де ВІН чекає за стіною у своєму палаці, побудованому із замученого скла).
Гіперборей

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. Але так, вам дійсно потрібен індекс, лише якщо ви робите якусь маніпуляцію.


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 Можливо, це зрозуміліше, але ваше рішення створює новий список, який може бути дорогим, якщо приватні готелі великі.
PM 2Кольцо

itertools.isliceНатомість із цим слід впоратися .
MisterMiyagi

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
Потенціювання. фі до влади н.
Mateen Ulhaq,

0

Дуже простий приклад:

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

Я не можу придумати рішення, яке не використовує діапазон-довжину складу швидко.

Але, напевно, натомість це слід зробити, try .. exceptщоб залишатися пітонічним, я думаю ..


1
if id < len(self.itemList) Але try...exceptкраще, як ти кажеш.
saulspatz

Це не враховує ідентифікатор <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 або внутрішню частину можна замінити, щоб зробити його меншим / кращим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.