Is there a need for range(len(a))?

255,687

Solution 1

If you need to work with indices of a sequence, then yes - you use it... eg for the equivalent of numpy.argsort...:

>>> a = [6, 3, 1, 2, 5, 4]
>>> sorted(range(len(a)), key=a.__getitem__)
[2, 3, 1, 5, 4, 0]

Solution 2

Short answer: mathematically speaking, no, in practical terms, yes, for example for Intentional Programming.

Technically, the answer would be "no, it's not needed" because it's expressible using other constructs. But in practice, I use for i in range(len(a) (or for _ in range(len(a)) if I don't need the index) to make it explicit that I want to iterate as many times as there are items in a sequence without needing to use the items in the sequence for anything.

So: "Is there a need?"? — yes, I need it to express the meaning/intent of the code for readability purposes.

See also: https://en.wikipedia.org/wiki/Intentional_programming

And obviously, if there is no collection that is associated with the iteration at all, for ... in range(len(N)) is the only option, so as to not resort to i = 0; while i < N; i += 1 ...

Solution 3

What if you need to access two elements of the list simultaneously?

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

You can use this, but it's probably less clear:

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

Personally I'm not 100% happy with either!

Solution 4

It's nice to have when you need to use the index for some kind of manipulation and having the current element doesn't suffice. Take for instance a binary tree that's stored in an array. If you have a method that asks you to return a list of tuples that contains each nodes direct children then you need the index.

#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

Of course if the element you're working on is an object, you can just call a get children method. But yea, you only really need the index if you're doing some sort of manipulation.

Solution 5

Going by the comments as well as personal experience, I say no, there is no need for range(len(a)). Everything you can do with range(len(a)) can be done in another (usually far more efficient) way.

You gave many examples in your post, so I won't repeat them here. Instead, I will give an example for those who say "What if I want just the length of a, not the items?". This is one of the only times you might consider using range(len(a)). However, even this can be done like so:

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

Clements answer (as shown by Allik) can also be reworked to remove 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]
>>>

So, in conclusion, range(len(a)) is not needed. Its only upside is readability (its intention is clear). But that is just preference and code style.

Share:
255,687

Related videos on Youtube

Hyperboreus
Author by

Hyperboreus

Obsessive compulsive coder. PEP8: it is not a guide, it is a law graven in stone handed down from the heavens by Allah/Yahwe/God/Junjunahpu/Moctezuma/your-favourite-imaginary-buddy. Everytime you write in python for i in range(len(someList)) you tear down the veil between our world and the nether realms and HE who waits behind the wall feasts and gores on the tears of ravaged minds.

Updated on August 24, 2022

Comments

  • Hyperboreus
    Hyperboreus over 1 year

    One frequently finds expressions of this type in python questions on SO. Either for just accessing all items of the iterable

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

    Which is just a clumbersome way of writing:

    for e in a:
        print(e)
    

    Or for assigning to elements of the iterable:

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

    Which should be the same as:

    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]
    

    Or for filtering over the indices:

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

    Which could be expressed like this:

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

    Or when you just need the length of the list, and not its content:

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

    Which could be:

    for _ in a:
        doSomethingUnrelatedToA()
    

    In python we have enumerate, slicing, filter, sorted, etc... As python for constructs are intended to iterate over iterables and not only ranges of integers, are there real-world use-cases where you need in range(len(a))?

    • rlms
      rlms over 10 years
      I think range(len(a)) is usually people who are fairly inexperienced with Python (although not necessarily with programming in general).
    • Admin
      Admin over 10 years
      I only used range(len(a)) when I was learning Python. Nowadays, I don't because, as you stated, it's quite easy to replace.
    • aIKid
      aIKid over 10 years
      not really. I use range(len(a)) often, because i don't need the content of list a, but only the length.
    • Admin
      Admin over 10 years
      @aIKamili - That is one use of range(len(a)). However, even that can be replaced by: for _ in a:. The code I gave will only iterate for as many times as there are items in list a.
    • rlms
      rlms over 10 years
      @alKamili I'd personally use enumerate or for _in list_ instead, but I guess in that case it is mostly just personal preference.
    • Zhang18
      Zhang18 over 7 years
      What if in the loop I need to access the element before and after the current one? I usually have for i in range(len(a)): doSomethingAbout(a[i+1] - a[i]) How to get around that?
    • Jaakko Seppälä
      Jaakko Seppälä almost 6 years
      @Zhang18 I think your example gives an IndexError. If you have for example a=range(10) then your loops goes from doSomethingAbout(a[1] - a[0]) to doSomethingAbout(a[10] - a[9]) but there is no element a[10]. You can slice the list and then iterate over the sliced list: a = range(10) for i in a[:-1]: print(a[i+1]-a[i])
    • Zhang18
      Zhang18 almost 6 years
      @JaakkoSeppälä agreed. I was just giving an example to illustrate the main issue of having to loop through indices, not just values, understanding there is a corner case at the end which is besides the main point.
    • ytu
      ytu over 4 years
      Should be an use case here: stackoverflow.com/questions/59112685/…
    • Roly
      Roly almost 3 years
      This is a great question.
    • jerclarke
      jerclarke almost 3 years
      My suspicion: Old-fashioned tutorials are stupid and teach the range(len(list)) solution when they should be using enumerate as the starting point! Just suffered through 30 mins of trying to explain the range(len(list)) chorizo to someone I'm tutoring, just because that's what Codecademy decided to burden us with. Why??? Obscure excuses aside, the default should be enumerate() but range(len(list)) persists. This standard makes PHP's foreach() look good, for goodness sake (^_^)
    • jerclarke
      jerclarke almost 3 years
      FWIW enumerate() was only added to Python in 2003 (python.org/dev/peps/pep-0279), 3 years after Python 2 came out and 12 years after the original release in 1991. I suspect that range(len(list)) was the standard for so long that it's just hard to get rid of.
  • Hyperboreus
    Hyperboreus over 10 years
    What advantages has for _ in range(len(a)) over for _ in a?
  • Erik Kaplun
    Erik Kaplun over 10 years
    @Hyperboreus: yeah, I just amended my answer a few seconds before your comment... so I guess the difference is whether you want to be really explicit about "repeat AS MANY TIMES as there are items in a" as opposed to "for every element in a, regardless of the content of a"... so just an Intentional Programming nuance.
  • Hyperboreus
    Hyperboreus over 10 years
    Thank you for your example. I've included it in my question.
  • Hyperboreus
    Hyperboreus over 10 years
    OK, this looks sensible. Thank you very much. But the question is: what will you do with your newly sorted list of indices. If via this list again you access some iterable, the dog bites its own tail.
  • Erik Kaplun
    Erik Kaplun over 10 years
    Equivalent to: [ix for ix, _ in sorted(enumerate(a), key=lambda i: i[1])] though, although yours is arguably nicer/geekier.
  • Hyperboreus
    Hyperboreus over 10 years
    Thank you very much. And then again, readability is (partially) in the eye of the beholder. I interpret for _ in a: as "Iterate over a but ignore its content", but I interpret for _ in range(len(a)) as "Get the length of a, then create a number of integers of the same length, and then finally ignore the content".
  • Admin
    Admin over 10 years
    @Hyperboreus - Very true. It's just code style. My goal was to show there will never be a "I must use range(len(a)) or I can't do this" scenario.
  • Hyperboreus
    Hyperboreus over 10 years
    On a side note: E.g. in erlang the single underscore is the anonymous variable. It is the only variable which can be re-assigned (or "matched"), unlike other variables, as erlang doesn't allow destructive assignment (which generally speaking is an abomination and weakens the veil between us and the nether realms, where HE waits behind the wall in his palace built of tortured glass).
  • Jim
    Jim about 9 years
    My ignorance. There's zip, a much more pythonic way of iterating over 2 lists in parallel.
  • saulspatz
    saulspatz over 8 years
    if id < len(self.itemList) But try...except is better, as you say.
  • IARI
    IARI over 8 years
    This does not account for id < 0.
  • steabert
    steabert over 8 years
    To get a list of 'hello' with as many items as in list a, use b = ['hello'] * len(a)
  • kevlarr
    kevlarr over 7 years
    Hah, I came here with a really similar use case... [a - b for a, b in zip(list1, list2)] is so much nicer than [list1[i] - list2[i] for i in range(len(list1))].. Thanks!
  • aquirdturtle
    aquirdturtle over 6 years
    This might be clearer: for b_elem in b[:len(a)]:...
  • lukas_o
    lukas_o about 6 years
    I am new to Python, what do the ** do in this case? I have read about *args and **kwargs, but this looks different.
  • Mateen Ulhaq
    Mateen Ulhaq about 6 years
    Exponentiation. phi to the power of n.
  • Erik Kaplun
    Erik Kaplun almost 6 years
    for ix, i in enumerate(a) seems to be equivalent, no?
  • flying sheep
    flying sheep over 4 years
    One should use pairwise instead.
  • PM 2Ring
    PM 2Ring over 4 years
    @aquirdturtle Perhaps it's clearer, but your solution creates a new list, which may be expensive if b & a are large.
  • MisterMiyagi
    MisterMiyagi over 4 years
    This should be handled using itertools.islice instead.
  • Luca
    Luca over 4 years
    In those situations I do: for a1,a2 in zip(a[:-1],a[1:])
  • Matiiss
    Matiiss about 3 years
    for index, item in enumerate(a)
  • stidmatt
    stidmatt over 2 years
    zip is the preferred method. Please use zip.
  • Joshua P. Swanson
    Joshua P. Swanson almost 2 years
    @stidmatt Why would zip be the preferred method when it is much less readable?