Python Forum
Why not use len(alist) in an iterator?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Why not use len(alist) in an iterator?
#1
Python lists are integer-indexed arrays. That is why you can retrieve an element from the list with a number: alphabet[13]

I often read you shouldn't use len(alist) like this (Although, I also see this often.):

for i in range(len(alphabet)):
    # do something
I would like to know why this is said.

The only reason I can think of is: enumerate is a generator, which is small.

However, unless the lists we are using are ginormous, you will not notice the difference on a modern computer. People who are tempted to create giant lists will, in all probability, put the list values in some form of database and use a generator to access the database instead, thereby not using giant lists at all.

from string import ascii_lowercase

alphabet = [a for a in ascii_lowercase]
for a in alphabet:
    print(a, end=' ')

for i in range(len(alphabet)):
    print(alphabet[i], end=' ')

for num, value in enumerate(alphabet, start=5):
    print(f'num = {num}, value =  {value}', end=' ')

gen = enumerate(alphabet, 5)
next(gen) # (5, 'a') enumerate is a generator
I don't know how enumerate works, but I can easily fake it:

# fake enumerate function
def my_enum(alist, num):
    for v in alist:
        tup = (alist.index(v) + num, v)
        yield tup

gen = my_enum(alphabet, 5)
next(gen) # (5, 'a')
If a list is not very big in memory, why should I complicate matters with enumerate to reach exactly the same end?

The generator seems more complicated and less straightforward.

gen = ((ascii_lowercase.index(a), a) for a in ascii_lowercase)
start = 5
for index, value in gen:
    print(f'index = {index + start}, value = {value}')
Reply
#2
Why would you do this?
for i in range(len(alphabet)):
    # do something with alphabet[i]
When you can do this instead?
for letter in alphabet:
    # do something with letter
One of my favorite things about Python that I almost never have to do indexing. It is surprising how often indexing is used in other languages just because there is no other way to iterate through a collection.

A big advantage of using iterators over indexing is that an iterator works when indexing is not supported. I cannot index a generator or a dictionary to get the keys (or the values). I cannot index a set.

I don't understand your point about enumerate. Your implementation of my_enum should be:
def my_enum(alist, num):
    for v in alist:
        yield v, num
        num += 1
alist.index[v] + num does not work if v appears multiple times in the list. And it is slow.
But what does this have to do with why you should interate directly over a collection instead of indirectly iterating over a range and using indexing to get values out of a collection?

And are you trying to convince me that this:
gen = ((ascii_lowercase.index(a), a) for a in ascii_lowercase)
start = 5
for index, value in gen:
    print(f'index = {index + start}, value = {value}')
Or this:
for index in range(len(ascii_lowercase)):
    print(f'index = {index + start}, value = {ascii_lowercase[index]}')
is clearer than this?
for index, value in enumerate(gen, start=5):
    print(f'index =  {index}, value = {value}')
That is a tough sell.
Reply
#3
Should not use range(len(sequence)) when can just simple loop over and get the result.
from string import ascii_lowercase

alphabet = [a for a in ascii_lowercase]
for letter in alphabet:
    print(letter, end=' ')
Output:
a b c d e f g h i j k l m n o p q r s t u v w x y z
So over there is no manipulation of index,the just use a simple loop

We see to often range(len(sequence)) used when need manipulation of index,often this is a not a good way.
To make simple example that need manipulation of index,only now we use enumerate.
from string import ascii_lowercase

alphabet = [a for a in ascii_lowercase]
increment = 5
for index, letter in enumerate(alphabet):
    new_index = index + increment
    print(f'original index = {index}, new index = {new_index}, letter = {letter}')
Output:
original index = 0, new index = 5, letter = a original index = 1, new index = 6, letter = b original index = 2, new index = 7, letter = c .....
It works with range(len(sequence)),but the way over with enumerate is better(explain last).
from string import ascii_lowercase

alphabet = [a for a in ascii_lowercase]
increment = 5
for index in range(len(alphabet)):
    letter = alphabet[index]
    new_index = index + increment
    print(f'original index = {index}, new index = {new_index}, letter = {letter}')
Output:
original index = 0, new index = 5, letter = a original index = 1, new index = 6, letter = b original index = 2, new index = 7, letter = c .....

Using range(len(sequence)) is less ideal because it separates the index and value retrieval,
making the code less readable and more prone to off-by-one errors or indexing mistakes.
In contrast, enumerate combines the index and value retrieval,making the code more straightforward and less error-prone.
Reply
#4
Thanks for the replies!

I seem to understand that your argument for using enumerate() is clarity and better is, do not use indexing at all. However the generator enumerate() works, I think it must be indexing the list somehow, or it will not know where it is in the list!

What will you do however, when you wish to make comparisons with elements farther down the list?

Like:

nums = [randint(1,9) for i in range(10)]
# lookahead for some condition
for i in range(len(nums) - 1):
    if nums[i] == nums[i+1]:
        print(f'found 2 equal numbers in sequence {nums[i]} and {nums[i+1]}')
An attempt at that using enumerate, which is a generator, and, as Dean said, "I cannot index a generator", but I am referencing the list nums directly, using the number index:

# complicated getting the equivalent of len(nums) - 1
# and produces weird results
for index, num in enumerate(nums):
    if nums[index] == nums[index+1]:
        print(f'found 2 equal numbers in sequence {nums[i]} and {nums[i+1]}')
Which gives:

Output:
found 2 equal numbers in sequence 9 and 5 found 2 equal numbers in sequence 9 and 5 found 2 equal numbers in sequence 9 and 5 found 2 equal numbers in sequence 9 and 5 Traceback (most recent call last): File "/usr/lib/python3.10/idlelib/run.py", line 578, in runcode exec(code, self.locals) File "<pyshell#11>", line 2, in <module> IndexError: list index out of range
Like this is even worse!

gen = enumerate(nums)
for i, n in gen:
    print(i,n)
    if n == nums[index+1]:
        print(f'found 2 equal numbers in sequence {nums[i]} and {nums[i+1]}')
Gives:

Output:
0 7 Traceback (most recent call last): File "/usr/lib/python3.10/idlelib/run.py", line 578, in runcode exec(code, self.locals) File "<pyshell#20>", line 3, in <module> IndexError: list index out of range
Reply
#5
Certainly there are situations where indexing into a list is the most reasonable solution, and it is available. It's just that in a lot of cases where you're dealing with every element in turn, it's unnecessary and alternatives are cleaner.

In the case you mention where you want to compare one element with the one following, there is an itertools function to do that.

from itertools import pairwise
from random import randint
nums = [randint(1,9) for i in range(10)]
# lookahead for some condition
for pair0, pair1 in pairwise(nums):
    if pair0 == pair1:
        print(f'found 2 equal numbers in sequence {pair0} and {pair1}')
Quote:However, unless the lists we are using are ginormous, you will not notice the difference on a modern computer.
If all the data is already present, that may be so. But I might be operating on data that requires a separate call for each piece of data. Each item takes a few milliseconds to generate. If I can avoid enumerating all of them and just go through them until I have the answer I need, fabulous.

Imagine the one above where I just need the first match. I could break at that match without creating the full list. That could save a lot of time even for relatively short lists on a very fast computer.
snippsat and Pedroski55 like this post
Reply
#6
parwise as used bye bowlofred is a ideal soltion for this.
So look into itertools and eg more-itertools they have lot of stuff that simplify looping and index manipulation.

Can also do like this with build in zip,but like pairwise from itertools better.
from random import randint

nums = [6, 2, 8, 4, 3, 3, 1, 7, 9, 7 ,7, 1]
for current, next_one in zip(nums, nums[1:]):
    if current == next_one:
        print(f'found 2 equal numbers in sequence {current} and {next_one}')
Pedroski55 likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  prime numbers with iterator and generator cametan_001 8 2,094 Dec-17-2022, 02:41 PM
Last Post: cametan_001
  resetting an iterator to full Skaperen 7 7,487 Feb-20-2022, 11:11 PM
Last Post: Skaperen
  popping an iterator Skaperen 11 4,036 Oct-03-2021, 05:08 PM
Last Post: Skaperen
  q re glob.iglob iterator and close jimr 2 2,381 Aug-23-2021, 10:14 PM
Last Post: perfringo
  Problem with an iterator grimm1111 9 4,632 Feb-06-2021, 09:22 PM
Last Post: grimm1111
  Multi-class iterator Pedroski55 2 2,510 Jan-02-2021, 12:29 AM
Last Post: Pedroski55
  is a str object a valid iterator? Skaperen 6 5,958 Jan-27-2020, 08:44 PM
Last Post: Skaperen
  discard one from an iterator Skaperen 1 2,107 Dec-29-2019, 11:02 PM
Last Post: ichabod801
  looking for a sprcil iterator Skaperen 7 3,586 Jun-13-2019, 01:40 AM
Last Post: Clunk_Head
  last pass of for x in iterator: Skaperen 13 6,283 May-20-2019, 10:05 PM
Last Post: Yoriz

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020