A double preceding function with bug - python

It is a homework question that I am stuck on:
Your classmate claims to have written a function that replaces each value in a list with twice the preceding value (and the first value with 0). For example, if the list [1, 3, 7, 11] is passed as a parameter, the function is supposed to return [0, 2, 6, 14] -- Note: 22 is not part of the output. Here's the code:
def double_preceding(values):
if (values != []):
temp = values[0]
values[0] = 0
for i in range(1, len(values)):
values[i] = 2 * temp
temp = values[i]
Analyse this function and rewrite it so that it works as intended.
I couldn't even follow what the code was doing, can someone explain to me please

Here's an explanation of the given code, and the errors I see within it:
def double_preceding(values):
if (values != []): # this if checks for an empty list, to avoid exceptions
temp = values[0]
values[0] = 0 # but this line is not indented, so you may get an IndexError anyway
for i in range(1, len(values)): # this loops over all indexes but the first (0)
values[i] = 2 * temp # this replaces the current value with double temp
temp = values[i] # this line however saves the same doubled value as the new temp
So, the two errors I see is incorrect handling of empty lists, and a logic error in the assignment code that will cause the loop to replace values after the first with the list's original first value times successive powers of two.
A good way to solve the second issue is to do both of the assignments in the loop with a single statement. This is a neat thing that Python can do that many other languages cannot. Here's what a basic version would look like:
values[i], temp = temp*2, values[i]
The commas are the key things to pay attention to. The one on the right side of the assignment makes a tuple out of temp*2 and values[i]. The comma on the left hand side tells Python to unpack the tuple being assigned into the variables values[i] and temp. And the two parts are evaluated in that order (first the expression on the right side, then the unpacking and assignments). This means that the "old" values of temp and values[i] are used to build the tuple and it doesn't matter that they're both reassigned later.
If we're doing the assignments that way, we can solve the empty list situation elegantly too. Rather than treating the first value specially and needing a check to make sure values[0] is a valid expression, why not just set temp to 0 at the start and let the loop handle the first value as well as the later ones? Here's a fully fixed function:
def double_preceeding(values):
temp = 0
for i in range(len(values)): # loop over all indexes, not skipping the first
values[i], temp = temp*2, values[i]
The loop will do nothing if values is an empty list, since len([]) is 0 and range(0) is empty itself.
Example output:
>>> L=[]
>>> double_preceeding(L)
>>> L
[]
>>> L=[1, 3, 7, 11]
>>> double_preceeding(L)
>>> L
[0, 2, 6, 14]

If I guessed the indentation of the program correctly. See comments below:
Code:
def double_preceding(v):
values = v[:] # Make a copy of the list passed as argument
if (values != []): # If list is not empty, store the first value in 'temp'
temp = values[0]
else:
return
v[0] = 0 # Set the first value of the list as '0' (as the problem says it)
for i in range(1, len(values)): # Iterate 'n - 1' times, where n is the length of the list
v[i] = 2 * temp # Set the corresponding value to twice the precedent (the precedent is stored in 'temp')
temp = values[i]
Test:
v = [1, 3, 7, 11]
double_preceding(v)
print v
Output:
[0, 2, 6, 14, 22]

Related

Built in (remove) function not working with function variable

Have a good day everyone, pardon my lack of understanding, but I can't seem to figure out why python built in function does not work when being called with another function variable and it just doesn't do what I want at all. Here is the code
def ignoreten(h):
ignoring = False
for i in range (1,len(h)-2):
if ignoring == True and h[i]==10:
h.remove(10)
if ignoring == False and h[i] ==10:
ignoring = True
The basic idea of this is just to decided the first 10 in a list, keep it, continue iterating until you faced another 10, then just remove that 10 to avoid replication, I had searched around but can't seem to find any solution and that's why I have to bring it up here. Thank you
The code you listed
def ignoreten(h):
ignoring = False
for i in range (1,len(h)-2):
if ignoring == True and h[i]==10:
h.remove(10)
if ignoring == False and h[i] ==10:
ignoring = True
Will actually do almost the exact opposite of what you want. It'll iterate over h (sort of, see [1]), and if it finds 10 twice, it'll remove the first occurrence from the list. (And, if it finds 10 three times, it'll remove the first two occurrences from the list.)
Note that list.remove will:
Remove the first item from the list whose value is equal to x. It
raises a ValueError if there is no such item.
Also note that you're mutating the list you're iterating over, so there's some additional weirdness here which may be confusing you, depending on your input.
From your follow-up comment to my question, it looks like you want to remove only the second occurrence of 10, not the first and not any subsequent occurrences.
Here are a few ways:
Iterate, store index, use del
def ignoreten(h):
index = None
found_first = False
for i,v in enumerate(h):
if v == 10:
if not found_first:
found_first = True
else:
index = i
break
if index is not None:
del h[index]
A little more verbose than necessary, but explicit, safe, and modifiable without much fear.
Alternatively, you could delete inside the loop but you want to make sure you immediately break:
def ignoreten(h):
found_first = False
for i,v in enumerate(h):
if v == 10:
if not found_first:
found_first = True
else:
del h[i]
break
Collect indices of 10s, remove second
def ignoreten(h):
indices = [i for (i,v) in enumerate(h) if v == 10]
if len(indices) > 1:
del h[indices[1]] # The second index of 10 is at indices[1]
Clean, but will unnecessarily iterate past the second 10 and collect as many indices of 10s are there are. Not likely a huge issue, but worth pointing out.
Collect indices of 10s, remove second (v2, from comments)
def ignoreten(h):
indices = [i for (i,v) in enumerate(h) if v == 10]
for i in reversed(indices[1:]):
del h[i]
From your comment asking about removing all non-initial occurrences of 10, if you're looking for in-place modification of h, then you almost definitely want something like this.
The first line collects all the indices of 10 into a list.
The second line is a bit tricky, but working inside-out it:
[1:] "throws out" the first element of that list (since you want to keep the first occurrence of 10)
reversed iterates over that list backwards
del h[i] removes the values at those indices.
The reason we iterate backwards is because doing so won't invalidate the rest of our indices that we've yet to delete.
In other words, if the list h was [1, 10, 2, 10, 3, 10], our indices list would be [1, 3, 5].
In both cases we skip 1, fine.
But if we iterate forwards, once we delete 3, and our list shrinks to 5 elements, when we go to delete 5 we get an IndexError.
Even if we didn't go out of bounds to cause an IndexError, our elements would shift and we'd be deleting incorrect values.
So instead, we iterate backwards over our indices, delete 5, the list shrinks to 5 elements, and index 3 is still valid (and still 10).
With list.index
def ignoreten(h):
try:
second_ten = h.index(10, h.index(10)+1)
del h[second_ten]
except ValueError:
pass
The inner .index call finds the first occurrence, the second uses the optional begin parameter to start searching after that. Wrapped in try/except in case there are less than two occurrences.
⇒ Personally, I'd prefer these in the opposite order of how they're listed.
[1] You're iterating over a weird subset of the list with your arguments to range. You're skipping (not applying your "is 10" logic to) the first and last two elements this way.
Bonus: Walrus abuse
(don't do this)
def ignoreten(h):
x = 0
return [v for v in h if v != 10 or (x := x + 1) != 1]
(unlike the previous versions that operated on h in-place, this creates a new list without the second occurrence of 10)
But the walrus operator is contentious enough already, please don't let this code out in the wild. Really.

dictionary, comps and has maps

I have written a code and run it, and it works fine. But I wanted to understand what is happening in the following:
nums = [4, 5, 1, 8]
target = 12
def TwoSum(nums, target):
comps = dict()
for i in range(len(nums)):
comp = target - nums[i]
if nums[i] in comps:
return [comps[nums[i]], i]
else:
comps[comp] = i
print(TwoSum(nums, target))
I understand that this is using a dict, and the idea is to add elements from nums to it, then doing target - nums[i] and then checking if this is in the dict, and if it is, then returning the indices of the two numbers that sum to the target.
But how is comps = dict() used? Is it necessary? Because in the code it doesn't seem to be storing anything! Except for the last line it is used- but I don't understand what it does- can someone please explain?
First, your code was using self as first argument of TwoSum. It should be eliminated given that this is a static function, not a class method. (Fixed).
The line comp = dict() is an initialization of comp to an empty dict. It could be written in a more pythonic way: comp = {}.
comp appears to store the complement (the difference between target and nums[i]) as comps[diff] = i. Thereafter, when you examine a number nums[j], if the complement is already in comps, that j and the corresponding previous i is the pair of indices you are looking for.
In our case, it stores number as a key thats required to find a target, and index of opposit number in nums as value.
So, for example, when we have target at 12 and nums [4,5,1,8] , at the iteration on the number 4, 8->0 will be added to comps, when iteration will get to the number 8, we will check if we have number 8 in comps, and then return value of that number in comps, which is 0, and index of current iterating number, which is 3.

Breaking a list into smaller lists at a point

I've written a function that takes for example [["a",1],["b",2],["a",2],["b",3]], where each small list has a letter and a number, and returns, [["a",1,2,"b",2,3]].
There is a lot more to this problem, but to make things simple, the next step is to turn this into a form [["a",3],["b",5]]. The second item of each smaller list, is the sum of the numbers between the letters ie 1,2 are associated with "a", 2,3 are associated with "b", as seen in the original list. The number of occurrences of a letter is unlimited.
Another example To summarize: function([["a",1,3,4,"b",2,2,"c",4,5]]) => [["a",8],["b",4],["c",9]]
Nothing I've written has come close to accomplishing this. This is a kind of bare-bones challenge, no list comprehension and nothing can be imported
This code can help you:
# Assuming a random initial list:
data = [["a",1,3,4,4,2,"b",2,2,3,5,2,3,"c",4,3,5,5]]
# An empty list where it will be added the result:
new_data = []
# Variable to accumulate the sum of every letter:
sume = 0
# FOR loop to scan the "data" variable:
for i in data[0]:
# If type of the i variable is string, we assume it's a letter:
if type(i) == str:
# Add accumulated sum
new_data.append(sume)
# We restart *sume* variable:
sume = 0
# We add a new letter read:
new_data.append(i)
else:
# We accumulate the sum of each letter:
sume += i
# We extract the 0 added initially and added the last sum:
new_data = new_data[1::]+[sume]
# Finally, separate values in pairs with a FOR loop and add it to "new_data2":
new_data2 = []
for i in range(len(new_data)//2):
pos1 = i*2
pos2 = pos1+1
new_data2.append([new_data[pos1],new_data[pos2]])
# Print data and new_data2 to verify results:
print (data)
print (new_data2)
# Pause the script:
input()
This code can work once by script, but it can convert in a nested function to use it in the way you are looking for.
It’s normally expected you post your solution first, but it seems that you have tried some things and need help. For future questions make sure you include your attempt, since it helps us provide more help as to why your solution doesn't work, and what additional steps you can take to improve your solution.
Assuming that your list always starts with a letter or str, and all numbers are of type int, you could use a dictionary to do the counting. I have added comments to explain the logic.
def group_consecutive(lst):
groups = {}
key = None
for item in lst:
# If we found a string, set the key and continue to next iteration immediately
if isinstance(item, str):
key = item
continue
# Add item to counts
# Using dict.get() to initialize to 0 if ket doesn't exist
groups[key] = groups.get(key, 0) + item
# Replacing list comprehension: [[k, v] for k, v in groups.items()]
result = []
for k, v in groups.items():
result.append([k, v])
return result
Then you could call the function like this:
>>> group_consecutive(["a",1,3,4,"b",2,2,"c",4,5])
[['a', 8], ['b', 4], ['c', 9]]
A better solution would probably use collections.Counter or collections.defaultdict to do the counting, but since you mentioned no imports then the above solution adheres to that.

Why does one function work and other don't ? How is the mutability or immutability working here?

Suppose I have a list as such li = [1, 2, 3, 4, 5] and a scale function as
def scale(data, factor):
for j in range(len(data)):
data[j] *= factor
Now if I pass li to scale function with a factor = 2, my list gets modified. That is the actual parameter gets changed here. So if i print li after executing the above function it gives [2, 4, 6, 8, 10] and not the original one.
Another way scale function is implemented is as follows:-
def scale(data, factor):
for val in data:
val *= factor
Now if I execute scale(li, 2) then it doesn't modify the actual parameter. So li stays as [1, 2, 3, 4, 5].
So my question is why does one modify the list and the other doesn't?
Has this got anything to do with mutability or immutability?
data[j] *= factor is the same as data[j] = data[j] * factor so you are modifying data object. This operation calls __setitem__ method of list data, which puts the result of data[j] * factor into the corresponding position.
When you do for val in data: in val is stored a reference to an item in data. That item (object) doesn't not know anything about the list data it was taken from. You are now working only with the item itself, not the list which contains another reference to it (along with val).
val *= factor creates a new object (the result of the product) and puts a reference to it into variable val. data still contains a reference to the old object/value which was in val before the assignment.
See more here: https://stackoverflow.com/a/8140747/248296
In the second example, the for loop creates a variable val=1 (and so on). When you do val = val * factor, the value of val changes. However, this has no connection to the original list[0]. You are just changing a temporary variable val
On the first example however, you do list[0] = list[0] * factor, so the assignment is done to the list itself.
I wouldn't say that it's so much a matter of mutability/immutability here (the right hand of both assignments is the same, and multiplies an immutable object in both cases). Mutability would be an issue if, eg, in the second example, you were passing val instead of data to your function, and change its value. In that case, the original value would not change, because val would be immutable.
The main difference is the fact, that you use an index to access the list in the first example and store the result in the list directly. In the second example you store the result in a variable which is holding the value taken from the list.
You can think of for val in data: as a shorthand for:
for i in range(len(data)):
val = data[i]
Now changing val (through val *= factor) will not change data (Why should it?). And as this is, what your second example is doing, the data list is not modified.
On the other hand, when you do data[i] *= factor, you are actually modifying the list, because you store the result in data[i] instead of the "temporary" variable val.
You could simplify it further, and look at
data[0] = 1
and compare it to
val = data[0]
val = 1
It should be obvious why one of them changes data and one does not. Your loops do effectively the same things and therefor one changes data and one does not.
The inplace operator...
x *= y
...is more or less equivalent to...
x = x * y
In particular, the result will be reassigned to x.
In your first example, you assign to data[j], index access changes the value at index j. This mutates your list.
In your second example, the values from the list are assigned to val, the inline operator thus reassignes to val. This is not an index access, so the list is not mutated.
In other words, your second example behaves like this:
for i in range(len(data)):
# values from the list are assigned to val
val = data[i]
# A new value is assigned to val
val = value * factor

Python: Difficulty understanding double preceding function

in the process of learning python, stuck at understanding this piece of code. It works, and the book required me to test it, which I have successfully, but cannot understand how the first index ends up in the code.
def double_preceding(values):
"""(list) -> NoneType
Replace each item in the list with twice the value of the preceding item, and replace the first item with 0.
>>> L = [l , 2 , 3]
>>> double_preceding(L)
>>> L
(0, 2, 4]
"""
if values != []:
temp = values[0]
values[0] = 0
for i in range(1, len(values)):
double = 2 * temp
temp = values[i]
values[i] = double
The range starts from index 1, which would skip index 0, so how does the output of 2 get in there(from the doctest)? That would mean value 1 was doubled, but the range skips index 0...?
And another question, doesn't values[0] = 0 change the value in [0] to 0? How did the value "1" end up being doubled in the output list?
It's such simple code, but has made me lose my mind.
Thanks in advance! Appreciate your time
This is an interesting loop, and I will walk you through how it works.
The statement if values != []: checks if the loop is empty. and if not, it proceeds.
The statement temp = values[0] stores the original value of values[0] in temp. This is how the program knows to double the 1. So in our example, the value of temp would be 1.
The next step values[0] = 0 sets the value of the first element to 0, but we still know what the original value of the array was, since we stored it in temp.
Now we start the loop. The loop goes from 1 all the way to the end of the loop.
The variable double holds the value of temp multiplied by 2. So in our example, since temp is 1, double holds 2.
Now the statement temp = values[i] would store the current iteration value of the loop in temp. In our example, we would be at the first iteration, so since values[1] is 2, temp has 2 in it.
Finally, the statement values[i] = double stores the value of double in the array. currently, we are at index 1, and since double is 2, that is what that index will have.
We can loop through this sequence again. Currently, our array has {0, 2, 3}. In the next iteration of the for, double is temp*2. Since temp was 2, double is 4. Now that value of double is stored in the second index of the array. The line temp = values[i] would store 4 in temp, but it doesn't matter since the loop is finished, since the length of the array is 3, and the loop only repeats when i < 3.
When we are finished the final array is {0, 2, 4}.
def double_preceding(values):
"""(list) -> NoneType
Replace each item in the list with twice the value of the preceding item, and replace the first item with 0.
>>> L = [1 , 2 , 3]
>>> double_preceding(L)
>>> L
(0, 2, 4]
"""
if values != []:
temp = values[0] # temp is 1 now
values[0] = 0 #sets 1 to 0
for i in range(1, len(values)): # start at second item in array and go until end
double = 2 * temp # double it by the last (which is stored in temp)
temp = values[i] # temp is now equal to the item we just doubled (the "preceding item")
values[i] = double # change the value of the item at the index so it is actually the doubled item

Categories