loop through array or list in variable steps - python

I'm trying to figure out how to loop through an array or list in variable steps.
So for example, if I have the following list...
a = [0,0,1,0,0,1,0]
...and want to use the following logic:
Start at index 0
If index two spots away is a 0, move two spots
If index two spots away is a 1, move one spot
However, I'm not quite clear how I can implement this logic as it seems like I can't change my index value I iterate through.
Why does this snippet still return values from 0-6 instead of 0,3,6?
for idx,val in enumerate(a):
print(idx)
idx+=3

Don't use a for loop.
for loops in python are different than in C or Java. In those languages, a for loop has an initial condition, a termination condition, and an increment for each time the loop runs. Whereas in python, a for loop is more of a for each loop - you give it an iterable object, and it runs the code for every item in that iterable object.
Modifying the iterable object while you're running through it is a bad idea that can have difficult-to-predict repercussions and will usually break your code.
However, you can always use a while loop:
a = [0,0,1,0,0,1,0]
idx = 0
while(idx < len(a) - 2):
print(idx)
if a[idx + 2] == 0:
idx += 2
elif a[idx + 2] == 1:
idx += 1
print(idx)
which produces the expected output
0 1 3 4 6
Or, if you change the increments to 3 and 2 respectively, rather than 2 and 1,
0 2 5

Your reasoning is pretty confusing, and I don't see ANY application for this, but here is how I understand your problem...
The reason is because you aren't actually returning the values, you're simply returning the index + 3, which is wrong to begin with. What you're trying to do is point to a new index of the array based on its value and return the index if it contains a value greater than 0.
You need to reference the index you want, check its value, then return the index which contains a value.
a = [0, 0, 1, 0, 0, 1, 0]
for i, v in enumerate(a):
if i == 0:
print(i)
next
if v == 0:
next
else
print(i)
But let's be honest, this is extremely ugly and un-pythonic. Let's simply check for whether a[i] contains a value, and if so, return the index...
for i, v in enumerate(a):
if v or i == 0:
print(i)
The purpose of if v or i == 0 is to check if v has a value, if so, print the index. OR if we are looking at the first element of i.
If you want to EXPLICITLY move the index by two, you must set your index at the start and use a while loop, since enumerate can't help you here and doesn't actually let you move the indicies...
a = [0, 0, 1, 0, 0, 1, 0]
i = 0
while i < len(a) - 1: # -1 to avoid out of bounds error
print(i)
if a[i + 2] == 0:
i += 2
elif a[i + 2] == 1:
i += 1
print(i) # Final print statement for the last index at the end of the while loop
I want to impress upon you the fact that this solution does NOT scale with different or larger lists, which is why it isn't recommended. By simply checking for whether a value does or doesn't exist, you guarantee your accuracy.
You should simply return the index based upon whether or not it contains a value. Even for very large lists, this will be extremely fast, and will always scale, even for values greater than 1.
The only other reason I would see you would want to do this differently is if you're doing string-search algorithms.

I believe that python refreshes the the variables each iteration. So at the end of the first run, idx would be 3 but then at the start of the second run, it gets reset to 1.
If you want to go through a loop by 3s, you can still use a for loop
for i in range(0,len(a),3):
print(i)
If you want a dynamic iteration value, then you need to use a while loop.
i=0
while i<len(a):
print(i)
if(a[i]==0):
i+=2
else:
i+=1

Related

Sum of pairs from list: memoization, design optimalization

From a list of integers and a single sum value, I have to return the first two values in order of appearance that adds up to the sum. source of the task
I think the most optimal way to scan the list is:
index 0, index 1
index 0, index 2
index 1, index 2
index 0, index 3
index 1, index 3
index 2, index 3
and so on. Am I right so far?
Then I used memoization to cut numbers appearing more than twice.
The code I wrote is functional but times-out on the more advanced tests. Here it is:
def sum_pairs(ints, s):
d={}
n2_index = 0
d[ints[0]] = 1
while True:
n2_index += 1
if ints[n2_index] not in d.keys():
d[ints[n2_index]] = 0
if d[ints[n2_index]] == 2:
if n2_index == len(ints)-1:
return None
continue
for n1_index in range (0, n2_index):
if ints[n1_index] + ints[n2_index] == s:
return [ints[n1_index], ints[n2_index]]
d[ints[n2_index]] += 1
if n2_index == len(ints)-1:
return None
I would appreciate it greatly if you could help me understand my mistake/mistakes and how to approach this kind of task.
Cheers!
The way to do this is to remember all the numbers you have seen before. This is normaly done in a set, a set is gives you O(1) (constant) look up time, so you determine very fast if you have seen a particular number or not already.
As you can through the list, you look in your set to see if you have seen the sum - current_value. If so you can output these two values, if not you add the current_value to the set and continue.
def sum(ints, s):
seen = set()
for current_value in ints:
if s - current_value in seen:
return s-current_value, current_value
else:
seen.add(current_value)
return None

Multiple python append functions in while loop crashing

Attempting to print a composite list by appending item from three smaller lists in sequence:
def final_xyz_lister():
global final_xyz_list
final_xyz_list = []
step=0
while step==0:
final_xyz_list.append(carbon_final_list[step])
final_xyz_list.append(oxygen_final_list[step])
final_xyz_list.append(hydrogen_final_list[step])
step=+1
while 0 < step < 50:
final_xyz_list.append(carbon_final_list[step])
final_xyz_list.append(oxygen_final_list[step])
final_xyz_list.append(hydrogen_final_list[step])
step=+1
else:
pass
If I comment out the second while loop the first element of the list is printed in a list as expected but introduction of the second while loop results in a MemoryError.
There is no need to append the three items in 2 different while loops. It would also be simpler if you used for loops. in this case:
for step in range(0, 50):
final_xyz_list.append(carbon_final_list[step])
final_xyz_list.append(oxygen_final_list[step])
final_xyz_list.append(hydrogen_final_list[step])
Edit: Also, I just noticed the error, you use step =+ 1, which is the same as saying step = +1 or step = 1. This is why you are getting a memory error, you keep defining step as 1, which is between 0 and 50, so the while loop just keeps going. what you probbly wanted to write was step += 1, this increases step by 1 and doesn't set it to 1

How to update array Index in loop (IndexError: list index out of range)

I should not use advance function, as this is a logical test during interview.
Trying to remove all digits which appear more than once in array.
testcase:
a=[1,1,2,3,2,4,5,6,7]
code:
def dup(a):
i=0
arraySize = len(a)
print(arraySize)
while i < arraySize:
#print("1 = ",arraySize)
k=i+1
for k in range(k,arraySize):
if a[i] == a[k]:
a.remove(a[k])
arraySize -= 1
#print("2 = ",arraySize)
i += 1
print(a)
result should be : 1,2,3,4,5,6,7
But i keep getting index out of range. i know that it is because the array list inside the loop changed, so the "while" initial index is different with the new index.
The question is : any way to sync the new index length (array inside the loop) with the parent loop (index in "while" loop) ?
The only thing i can think of is to use function inside the loop.
any hint?
Re-Calculating Array Size Per Iteration
It looks like we have a couple issues here. The first issue is that you can't update the "stop" value in your inner loop (the range function). So first off, let's remove that and use another while loop to give us the ability to re-calculate our array size every iteration.
Re-Checking Values Shifted Into Removed List Spot
Next, after you fix that you will run into a larger issue. When you use remove it moves a value from the end of the list or shifts the entire list to the left to use the removed spot, and you are not re-checking the value that got moved into the old values removed spot. To resolve this, we need to decrement i whenever we remove an element, this makes sure we are checking the value that gets placed into the removed elements spot.
remove vs del
You should use del over remove in this case. remove iterates over the list and removes the first occurrence of the value and it looks like we already know the exact index of the value we want to remove. remove might work, but it's usage here over complicates things a bit.
Functional Code with Minimal Changeset
def dup(a):
i = 0
arraySize = len(a)
print(arraySize)
while i < arraySize:
k = i + 1
while k < arraySize: # CHANGE: use a while loop to have greater control over the array size.
if a[i] == a[k]:
print("Duplicate found at indexes %d and %d." % (i, k))
del a[i] # CHANGE: used del instead of remove.
i -= 1 # CHANGE: you need to recheck the new value that got placed into the old removed spot.
arraySize -= 1
break
k += 1
i += 1
return a
Now, I'd like to note that we have some readability and maintainability issues with the code above. Iterating through an array and manipulating the iterator in the way we are doing is a bit messy and could be prone to simple mistakes. Below are a couple ways I'd implement this problem in a more readable and maintainable manner.
Simple Readable Alternative
def remove_duplicates(old_numbers):
""" Simple/naive implementation to remove duplicate numbers from a list of numbers. """
new_numbers = []
for old_number in old_numbers:
is_duplicate = False
for new_number in new_numbers:
if old_number == new_number:
is_duplicate = True
if is_duplicate == False:
new_numbers.append(old_number)
return new_numbers
Optimized Low Level Alternative
def remove_duplicates(numbers):
""" Removes all duplicates in the list of numbers in place. """
for i in range(len(numbers) - 1, -1, -1):
for k in range(i, -1, -1):
if i != k and numbers[i] == numbers[k]:
print("Duplicate found. Removing number at index: %d" % i)
del numbers[i]
break
return numbers
You could copy contents in another list and remove duplicates from that and return the list. For example:
duplicate = a.copy()
f = 0
for j in range(len(a)):
for i in range(len(duplicate)):
if i < len(duplicate):
if a[j] == duplicate[i]:
f = f+1
if f > 1:
f = 0
duplicate.remove(duplicate[i])
f=0
print(duplicate)

Need help to find python code bug

def remove_adjacent(nums):
i = 0
while i < len(nums):
if nums[i] == nums[i+1]:
nums.remove(nums[i])
i = i + 1
else: i = i + 1
return nums
IndexError: list index out of range
Who can tell me what's wrong with my code?
There are several issues with your code.
As AbhiP points out, you're looking at pairs of consecutive items in your list - there are len(nums)-1 pairs that you should be comparing, but you're trying to compare len(nums) pairs. That's one cause of the index error.
Secondly, as John mentions, you're removing items in your list while you loop through it. If you really want to keep your current structure, you need to not increment the loop variable when you remove an item.
Correction/Clarification: This second point won't cause an index error, but it will cause bugs by making the code skip the evaluation of certain pairs, e.g. for inputs such as [1, 1, 1, 2].
Taking these two points into consideration, your code will look like:
i = 0
while i < len(nums) - 1:
if nums[i] == nums[i+1]:
nums.remove(nums[i])
else:
i += 1
Which will remove the index error.
Thirdly, nums.remove(nums[i]) will also cause non-index error bugs. Try the above code with nums being [1, 2, 3, 1, 1]. You will see that the first 1 is removed, not the 4th or 5th item in the list. This is because remove on a list gets rid of the first instance that appears in the list. You should probably do del instead, as below:
i = 0
while i < len(nums) - 1:
if nums[i] == nums[i+1]:
del nums[i]
else:
i += 1
Finally, while not a bug, best practice would suggest that you shouldn't modify a list while looping through it - this makes the code hard to reason about, and can lead to subtle bugs. Instead it's usually better if you just create a new list and return that.
new_nums = []
for i in range(len(new_nums)-1):
if nums[i] != nums[i+1]:
new_nums.append(nums[i])
new_nums.append(nums[-1])
An alternative way of writing this using zip and list comprehensions, two of Python's cool features:
new_nums = [item1 for item1, item2 in zip(nums, nums[1:]) if item1 != item2]
new_nums.append(nums[-1])
Your problem is the line while i < len(nums):. It will iterate from 0 till len-1 but the next line you are doing if nums[i] == nums[i+1] so the index will go until len.
Change it to:
while i < len(nums) - 1:
You are removing items from the list while iterating through it. The for loop has no idea that the bounds have changed, thus the error.

Looping through a list not in order in Python

I am very new to programming, so please bear with me...I have been learning Python and I just did an assessment that involved looping through a list using your current value as the next index value to go to while looping. This is roughly what the question was:
You have a zero-indexed array length N of positive and negative integers. Write a function that loops through the list, creates a new list, and returns the length of the new list. While looping through the list, you use your current value as the next index value to go to. It stops looping when A[i] = -1
For example:
A[0] = 1
A[1] = 4
A[2] = -1
A[3] = 3
A[4] = 2
This would create:
newlist = [1, 4, 2, -1]
len(newlist) = 4
It was timed and I was not able to finish, but this is what I came up with. Any criticism is appreciated. Like I said I am new and trying to learn. In the meantime, I will keep looking. Thanks in advance!
def sol(A):
i = 0
newlist = []
for A[i] in range(len(A)):
e = A[i]
newlist.append(e)
i == e
if A[i] == -1:
return len(newlist)
This might be the easiest way to do it if your looking for the least lines of code to write.
A = [1,4,-1,3,2]
B = []
n = 0
while A[n] != -1:
B.append(A[n])
n = A[n]
B.append(-1)
print(len(B))
First of all, note that for A[i] in range(len(A)) is a pattern you certainly want to avoid, as it is an obscure construct that will modify the list A by storing increasing integers into A[i]. To loop over elements of A, use for val in A. To loop over indices into A, use for ind in xrange(len(A)).
The for loop, normally the preferred Python looping construct, is not the right tool for this problem because the problem requires iterating over the sequence in an unpredictable order mandated by the contents of the sequence. For this, you need to use the more general while loop and manage the list index yourself. Here is an example:
def extract(l):
newlist = []
ind = 0
while l[ind] != -1:
newlist.append(l[ind])
ind = l[ind]
newlist.append(-1) # the problem requires the trailing -1
print newlist # for debugging
return len(newlist)
>>> extract([1, 4, -1, 3, 2])
[1, 4, 2, -1]
4
Note that collecting the values into the new list doesn't really make sense in any kind of real-world scenario because the list is not visible outside the function in any way. A more sensible implementation would simply increment a counter in each loop pass and return the value of the counter. But since the problem explicitly requests maintaining the list, code like the above will have to do.
It's simpler to just use a while loop:
data = [1,4,-1,3,2]
ls = []
i = 0
steps = 0
while data[i] != -1:
ls.append(data[i])
i = data[i]
steps += 1
assert steps < len(data), "Infinite loop detected"
ls.append(-1)
print ls, len(ls)

Categories

Resources