Sample problem in Python

Hi. Question on another exam sample:
Result shows [3,1], but I am confused bec. of a phrase in the code below:

my_list = [i for i in range(5)]   # i understand this will result to [0,1,2,3,4]
m = [my_list[i] for i in range (4,0,-1) if my_list[i] % 2 != 0] # it is the whole phrase before "if" that i don't understand.
print(m)

I understood it as:
my_list[0] in (4,0,-1)
my_list[1] in (4,0,-1)
my_list[2] in (4,0,-1)
and so on and so forth
But nowhere will it result to [3,1]

Thank you so much in advance.

my_list = [i for i in range(5)]

is equivalent to:

my_list = []
for i in range(5):
    my_list.append(i)

and:

m = [my_list[i] for i in range (4,0,-1) if my_list[i] % 2 != 0]

is equivalent to:

m = []
for i in range (4,0,-1):
    if my_list[i] % 2 != 0:
        m.append(my_list[i])

counts down from 4 to 0 (not including the 0), that’s why you get [3, 1] as final result.
To count up you have to do for i in range(5) as in the defintion of my_list.
@MRAB explained how the if works inside the list comprehension.

Hi,

all the first part (before the if statement) does is ask is which numbers of the my_list overlap with the numbers in the range (4,0,-1)

So,

my_list = [0,1,2,3,4]
range(4, 0, -1) numbers include: 4,3,2,1

You can verify this by:

for i in range(4,0,-1):
    print(i)

The numbers that overlap from both lists are: 1, 2, 3, 4

You can also verify this by:

y = [my_list[i] for i in range (4,0,-1)]
>>> y
>>> [4, 3, 2, 1]

From there, the expression after the if conditional statement requires us to include only those numbers that overlap that are not even. In this case, 1, and 3.

This part tells us which values of i will be used in the comprehension, and in what order. It will run four times, with values 4, 3, 2, 1.

This part tells us the condition for whether to use a given i value to make a new element of m.

This part tells us the rule for making the new elements.

1 Like

Others have answered the question, directly. Perhaps the real issue is how to solve a problem that [quote=“Moon Light, post:1, topic:41545, username:Moonlight001”]i don't understand[/quote]? ie where the computer is doing something differently - and need to work-out, what is it doing?

Taking the non-question first: [:=“Moon Light, post:1, topic:41545, username:Moonlight001”]# i understand this will result to [0,1,2,3,4][/quote], the understanding can be proven by adding a “debug-print statement”:

print( my_list )

There is now “proof” that computer has behaved as-expected - more importantly, both thinking on the same lines. Others have shown how to re-wire a list-comprehension into its long-form equivalent, so going completely over-board:

m = list()
for i in range( 4, 0, -1, ):
    print( i, end=", ", )
    if my_list[ i ] % 2 != 0:
        print( "even" )
        m.append( i )
    else:
        print( "odd" )

by adding the debug-print statements there is a blow-by-blow illustration of what the computer is doing with the code.

Assumptions: that you have learned about list-comprehensions, and the effect of the modulo-operator, before attempting the exam.

Speaking personally: no longer use debug-prints because have to remember to put them in first, and take them out, last!

The Python tool for the job is called a “Debugger”. Such are built-in to competent developer tools, such as PyCharm. However, that’s a whole extra skill to learn, so Phil Guo’s excellent Python Tutor is recommended for developing and investigating snippets (like this one) and providing the desired visual insight. The Thonny IDE for beginners provides something similar.

I see… I get it now.
Although a portion really confused me at first…
Which is the “for i in range (4,0,-1)” part, which I thought only made the code longer and this seemed redundant
But upon trying out the effect of this code, now I understand it is to achieve that reverse order. [3,1]
instead of [1,3]

Thank your for the breakdown and explanation!

Thank you!

Exactly. This part really confused me… as to why it was necessary to include.
Now i get it that its purpose is to make it reverse.

Thanks!

Thank you for always taking the time out to explain!
Appreciate it a lot.

Hi. thank you for the explanation.
I am just a few months into python and yeah, have heard people recommending debugger.
But I have no idea where to access it…or how to operate. will have to look into it more…
Thanks for the reco!

ooooh… but how come below code outputs [4,2], and not [2,4]?

my_list = [i for i in range (5,0,-1)]
m = [my_list[i] for i in range (5) if my_list[i] % 2 == 0]
print(m)

You’ve been given ideas and tools for solving these sorts of problems (as above)!

Does my_list actually contain what you expect?
If so, can you re-state the list-comprehension and make its functioning ‘visible’?

Plenty of folk can give you a direct answer to the question. However, it is up to you to learn…

2 Likes

Hi. Yes, i have gone through the explanations above, and my latest question wasn’t really about a re-explanation of how the code works. It is about the order of the resulting digits. From what I understood previously, we take on the order of the digits from the middle portion of the 2nd line… but that latest example shows the opposite. I don’t know…maybe I am missing something or interpreted it wrong.
I have attached the two instances below. Hope it clarified my question:

(p.s. these are questions from an exam dump. and been going through it with a time deadline. so pardon me if i have lots of questions… thank you.)

Screenshot_59
Screenshot_60

Think carefully about the list comprehension: [my_list[i] for i in range (5) if my_list[i] % 2 == 0]. Notice the my_list[i] part. What result do you get if you put just i there instead? Now, considering what my_list is beforehand, do you see how using those values of i, would give you those results?

Just to note, indeed, it does make the code longer and more redundant, and in your case harder to understand. Instead of iterating over a range with the (hardcoded) values of your list end and start indicies, and then indexing into the list to get the values (which appears to be the bit causing your confusion in your latest question):

…You can instead simply iterate over values of the reversed list directly using the reversed() builtin function:

m = [n for n in reversed(my_list) if n % 2]

Or by using the third (step) argument to the slicing operator, just like with range():

m = [n for n in my_list[::-1] if n % 2]

Additionally, there’s no need to use a comprehension to convert a range to a list:

Instead, simply call list() on the range directly:

my_list = list(range(5))

With that in mind, we can likewise simplify the followup example you were confused on:

with the same two changes, yielding:

my_list = list(range(5,0,-1))  # or reversed(list(range(5)))
m = [n for n in my_list if n % 2 == 0]

This should make it easier to see why the result is as shown in your example, and make the code shorter, simpler and easier to follow for both you and others.

1 Like

Hi C.A.M. Gerlach, thanks for the response and explanation.
Hmm… I guess I am unable to explain “that spot” that I am confused on.

Oh by the way, this question comes from an exam dump sample question, and not mine.
I would also love a simplified one, so i guess they purposefully made this tricky to see if one comprehends each part.

Back to that “spot”, I displayed the two codes in a very simple form:

Code 1

my_list = [i for i in (0,1,2,3,4)]
m = [my_list[i] for i in (4,3,2,1) if my_list[i] % 2 != 0]
print(m)

RESULT: [3,1]

Code 2

my_list = [i for i in (5,4,3,2,1)]
m = [my_list[i] for i in (0,1,2,3,4) if my_list[i] % 2 == 0]
print(m)

RESULT: [4,2]

Question is:
Code 1 uses the order of the second range mentioned, which is descending. Resulting to [3,1]
But Code 2 uses the order of the first range mentioned. Resulting to [4,2]
Why are the orders of results different? Isn’t the order listing based on the second range mentioned?

hmmm…i hope i was able to ask this clearly this time…after many tries… thank you.

Or possibly the exam writers, often overworked and underpaid grad students (at least in my country), simply didn’t do a good job writing idiomatic, Pythonic code. Understanding poorly written Python is (for better or worse) a very useful skill in many contexts. However, in addition to explaining how that snippit could have been written more simply, Pythonically and efficiently, which you should apply in your own code, I sought to make the point how it is easier to understand and follow, as it would avoided your confusion here in the future by avoiding the need for the klunky bit you’re getting stuck on.

The specific “spot” you’re confused on, as @kknechtel and others have pointed out, appears to be with the indexing into my_list, which the refactored code avoids, while still having the same visible behavior on each line. If we further simplify your second example that you’re getting confused by, following my suggestion above:

my_list = list(range(4,0,-1))
m = [n for n in my_list if n % 2 == 0]
print(m)

Is it clear why at least this form should produce the result [4, 2]? If so, given the behavior is ultimately identical to your “Code 2” above, with the exception of skipping the redundant bit causing your confusion, does that help to determine what “Code 2” is actually doing, and therefore why you get the indicated result?

If not, to further avoid confusion, consider replacing the values of my_list with a different set of numbers that doesn’t happen to coincide those of the list indices (which I suspect is contributing to your confusion), e.g.:

my_list = [14, 13, 12, 12, 11]

Can you figure it out now?

3 Likes

Thank you so much! It is the last line (changing the values for my_list) that clarified it for me!
All this time i thought the second range “for i in (4,3,2,1)” are the values to be compared to, turns out they are for the index (as you also explained). Again, thank you! This forum site is of great help!

1 Like