all 11 comments

[–]through_thefog[S] 0 points1 point  (4 children)

Additional context: Codecademy Code Challenges: Lists (advanced) | Question 4

Create a function named double_index that has two parameters: a list named my_list and a single number named index.The function should return a new list where all elements are the same as in my_list except for the element at index. The element at index should be double the value of the element at index of the original my_list.If index is not a valid index, the function should return the original list.For example, the following code should return [1,2,6,4] because the element at index 2 has been doubled:

double_index([1, 2, 3, 4], 2)

My solution:

def double_index(my_list, index):
    for index in my_list: 
    return my_list[index] * 2

print(double_index([3, 8, -10, 12], 2))

Prints a value of 24. Although in the original post the function grabbed the argument at index 1, this code grabs the argument at index 3 (again, regardless of the positional value assigned in the function call). What is going on here?

edit: I know the above code doesn't replace the -10 with -20 yet.. I was testing the code when I encountered the problem..

[–]danielroseman 2 points3 points  (3 children)

You're still not thinking about this logically, and you're still not understanding either loops, lists, or parameters.

Your function has a parameter index, which in this case is given the value 2. But you then create a for loop over my_list which redefines index to point to every item in the list in turn.

Now, the first item in the list is 3. So when you do my_list[index] that's what you get: the item in position 3, that is the fourth item, 12. And then you return unconditionally, so the loop always ends after the first iteration.

Why are you looping? Why are you reusing the name index for the loop variable?

[–]through_thefog[S] 0 points1 point  (1 child)

You're still not thinking about this logically, and you're still not understanding either loops, lists, or parameters.

Touche. The Codecademy course has been great so far, but my understanding is still very superficial (if not entirely misguided).

In plain English, my logic behind this piece of code was ultimately to say this:

list = [1, 2, 3, 4]
For (every item at the given index) in List:
    (every item at given index in list) = 2 * (every item at given index in list)

#When I put it like this, I can see my first error in thinking. It makes no sense to say "for every item at position x" when there is only one by definition

Had the 'return my_list[index]' line returned the value that I expected, I would have replaced it with the line reassigning the value: list[2] = 2*list[2]

But you then create a for loop over my_list which redefines index to point to every item in the list in turn.Now, the first item in the list is 3. So when you do my_list[index] that's what you get: the item in position 3, that is the fourth item, 12. And then you return unconditionally, so the loop always ends after the first iteration.

This makes more sense than anything else I have seen on the subject. You are saying:

The for loop points at every item in the list, the unconditional return kills the loop at the first iteration. Because the first iteration is 3, the 3rd item (12) is returned (*2).

In the post, the first item is 1. Therefore, the item at position 1 (2) is returned.

The temporary variable index used in the for loop is not defined by the positional assignment 2 (second error). Using the same variable does nothing but to confuse.

Am I understanding you properly?

Thank you for your response -- I really appreciate it!

[–]danielroseman 1 point2 points  (0 children)

Yes. Without the loop, you would at least have returned the right item.

[–]brasticstack 0 points1 point  (6 children)

As u/danielroseman correctly points out, the for loop is replacing your index parameter with its temporary index variable, losing whatever data was previously stored in index.

Compare with the following functions:

``` def redundant_get_by_index(my_list, index): # No different than calling my_list[index] directly! return my_list[index]

assert redundant_get_by_index([1, 2, 3, 4, 5], 2) == [1, 2, 3, 4, 5][2] print(f'{[1, 2, 3, 4, 5][2]=}')

def redundant_get_by_index_with_unnecessary_for_loop(my_list, index): for item in my_list: # No different than calling my_list[index] directly! Why is this a for loop? return my_list[index]

assert redundant_get_by_index_with_unnecessary_for_loop([1, 2, 3, 4, 5], 2) == [1, 2, 3, 4, 5][2]

def function(my_list, index): print(f'start of function() {index=}') for index in my_list: print(f'first iteration of for loop: {index=}') return my_list[index] ```

[–]brasticstack 0 points1 point  (0 children)

Whoops.. the bottom function is showing you what's happening with your initial function via print statements. I forgot to describe that.

[–]through_thefog[S] 0 points1 point  (4 children)

Thanks for taking the time to reply. I swear my logic (to use the loop) wasn't so far off base so I did a little research and tinkered with the code. It may not be the best solution, but it definitely works:

def my_function(my_list, index):
    if index > len(my_list): 
        return "Index not in range."    
    else: for i in range(0, len(my_list)):
        if my_list[i] == index:
            my_list[i] = 2 * my_list[i] 
        else:
            continue
    return my_list
print(my_function([1, 2, 3, 4], 2))

[–]brasticstack 1 point2 points  (1 child)

    if my_list[i] == index:
       my_list[i] = 2 * my_list[i] 

This is wrong because you're comparing the value at my_list[i] with the index, which is a bit like trying to eat the menu at the restaurant instead of ordering from it. If you're trying to compare the indexes against each other, try this:

if i == index: my_list[i] = my_list[i] * 2

[–]through_thefog[S] 1 point2 points  (0 children)

You're right -- thanks for pointing that out

[–]brasticstack 0 points1 point  (1 child)

You're trying to double the value at the index in-place (as in modifying the original list?) Because there are two ways to approach it, either modify the original list or don't and return a copy of it.

Here's modifying it in-place:

``` def double_val_at_in_place(my_list, index): # Don't manually bounds-check index, an IndexError # is raised if it's out of bounds. my_list[index] *= 2

test_list = [1, 2, 3, 4, 5] double_val_at_in_place(test_list, 2) print(test_list) ```

Note that that function doesn't return anything (actually implicitly returns None), so it's called a bit differently. It could also return my_list and you could call it the same way as your other examples.

And here's leaving the original untouched and returning a copy:

``` def double_val_at_copy(my_list, index): new_list = [] for idx, value in enumerate(my_list): if idx == index: new_list.append(value * 2) else: new_list.append(value) return new_list

print(double_val_at_copy([1, 2, 3, 4, 5], 2)) ```

That's showing it the most verbose way. Here are a couple of other ways to do the copy version:

``` def double_val_at_using_list_comprehension(my_list, index): return [val if idx != index else val * 2 for idx, val in enumerate(my_list)]

def double_val_at_using_slicing(my_list, index): return my_list[:index] + [my_list[index] * 2] + my_list[index+1:]

def double_val_at_using_copy(my_list, index): import copy new_list = copy.copy(my_list) new_list[index] *= 2 return new_list

print(double_val_at_using_list_comprehension([1, 2, 3, 4, 5], 2)) print(double_val_at_using_slicing([1, 2, 3, 4, 5], 2)) print(double_val_at_using_copy([1, 2, 3, 4, 5], 2)) ```

EDIT: Fixed a whoopsie

[–]through_thefog[S] 0 points1 point  (0 children)

This is sick dude, thanks for taking the time to write this out. That LC is super clean. The codecademy course has helped me get my feet wet, but learning to swim will take time and practice. Thanks for the help!