Unable to reset counters in for loop - python

I am trying to amend a list of integers in a way that every 2 duplicating integers will be multiplied by 2 and will replace the duplicates. here is an example:
a = [1, 1, 2, 3] = [2, 2 ,3] = [4 ,3]
also : b = [2, 3, 3, 6 ,9] = [2 , 6 , 6, 9] = [2, 12 , 9]
I am using the code below to achieve this. Unfortunately, every time I find a match my index would skip the next match.
user_input = [int(a) for a in input().split()]
for index, item in enumerate(user_input):
while len(user_input)-2 >= index:
if item == user_input[index + 1]:
del user_input[index]
del user_input[index]
item += item
user_input.insert(index,item)
break
print(*user_input)

In Python, you should never modify a container object while you are iterating over it. There are some exceptions if you know what you are doing, but you certainly should not change the size of the container object. That is what you are trying to do and that is why it fails.
Instead, use a different approach. Iterate over the list but construct a new list. Modify that new list as needed. Here is code that does what you want. This builds a new list named new_list and either changes the last item(s) in that list or appends a new item. The original list is never changed.
user_input = [int(a) for a in input().split()]
new_list = []
for item in user_input:
while new_list and (item == new_list[-1]):
new_list.pop()
item *= 2
new_list.append(item)
print(*new_list)
This code passes the two examples you gave. It also passes the example [8, 4, 2, 1, 1, 7] which should result in [16, 7]. My previous version did not pass that last test but this new version does.

Check if this works Rory!
import copy
user_input = [1,1,2,3]
res = []
while res!=user_input:
a = user_input.pop(0)
if len(user_input)!=0
b = user_input.pop(0)
if a==b:
user_input.insert(0,a+b)
else:
res.append(a)
user_input.insert(0,b)
else:
res.append(a)
user_input = copy.deepcopy(res)

You can use itertools.groupby and a recursion:
Check for same consecutive elements:
def same_consec(lst):
return any(len(list(g)) > 1 for _, g in groupby(lst))
Replace consecutive same elements:
def replace_consec(lst):
if same_consec(lst):
lst = [k * 2 if len(list(g)) > 1 else k for k, g in groupby(lst)]
return replace_consec(lst)
else:
return lst
Usage:
>>> a = [8, 4, 2, 1, 1, 7]
>>> replace_consec(a)
[16, 7]

Related

Python: How to merge two lists like a zip with for loop

I am given a problem where I have a function that takes two integer lists and returns a single integer list (new list) with the two integer lists zipped together.
For example:
list1 = [1,2,3]
list2 = [4,5,6]
Should give [1, 4, 2, 5, 3, 6] not [1, 2, 3, 4, 5, 6]
Another case is if one list is longer than another, e.g:
list1 = [1,2]
list2 = [4,5,6,9]
Once the shorter list runs out of items, the longer list should add its remaining elements. E.g: [1, 4, 2, 5, 6, 9]
I have tried using conditional statements to check which list is longer and use a for loop to append to a new list that should be returned. I tried using for loops that loop for the duration of the shorter list, and once the for loop is over, it adds the remaining elements from longer list to the new list. Also if both lists are empty I have to return an empty new list.
The code:
def main():
list1 = [1,2]
list2 = [4,5,6,9]
print(zip_lists(list1, list2))
def zip_lists(list1, list2):
new_list = []
if len(list1) > len(list2):
last_num = list1[len(list1):]
for num in range(0, len(list2)):
new_list.append(list1[num])
new_list.append(list2[num])
new_list.append(last_num)
return new_list
elif len(list2) > len(list1):
last_num = list2[len(list2):]
for num in range(0, len(list1)):
new_list.append(list1[num])
new_list.append(list2[num])
new_list.append(last_num)
return new_list
elif len(list1) and len(list2) == 0:
return new_list
main()
However, I have a problem where I cannot add the remaining elements from the longer list and instead returns a partially zipped list with empty square brackets.
The test case:
list1 = [1,2]
list2 = [4,5,6,9]
Should be [1, 4, 2, 5, 6, 9] but I'm getting [1, 4, 2, 5, []].
Is my code showing the right way of thinking about this problem?
def zip_lists(list1, list2):
n1=len(list1)
n2=len(list2)
k = []
n = min(n1, n2)
for i in range(n):
k.append(list1[i])
k.append(list2[i])
if n1==n2:
return k
if n1>n2:
return k+list1[n:]
else:
return k+list2[n:]
Test:
list1 = [1,2]
list2 = [4,5,6,9]
zip_lists(list1, list2)
Output: [1, 4, 2, 5, 6, 9]
You can use zip_longest to do that like:
Code:
from itertools import zip_longest
def merge(l1, l2):
for i in zip_longest(l1, l2):
for j in i:
if j is not None:
yield j
Test Code:
list1 = [1, 2, 3]
list2 = [4, 5, 6]
ans1 = [1, 4, 2, 5, 3, 6]
list3 = [1, 2]
list4 = [4, 5, 6, 9]
ans2 = [1, 4, 2, 5, 6, 9]
assert list(merge(list1, list2)) == ans1
assert list(merge(list3, list4)) == ans2
A general version of this (i.e., for any number of source iterables, of possibly different lengths) is shown in the Python documentation for the itertools standard library, named roundrobin in the "recipes" section. Copied here, with the original credit:
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
num_active = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
while num_active:
try:
for next in nexts:
yield next()
except StopIteration:
# Remove the iterator we just exhausted from the cycle.
num_active -= 1
nexts = cycle(islice(nexts, num_active))
Here, cycle and islice are the functions provided by itertools (from itertools import cycle, islice); the rest is built-in. Note that this is a generator, not a normal function; you will need to iterate over it yourself (or directly create e.g. a list from it, e.g. merged = list(roundrobin(list1, list2))).
As far as debugging your attempt at the code, let's consider the case where list1 is longer (the case where list2 is longer has parallel problems; and the case where they're equal is working fine, yes?):
last_num = list1[len(list1):] # problem 1
for num in range(0, len(list2)):
new_list.append(list1[num])
new_list.append(list2[num])
new_list.append(last_num) # problem 2
Problem 1: you want to extract the "excess" elements from list1, which you determine as all the ones past a certain point. This is just a typo or not thinking about it clearly; you want to use the length of list2 for the number of elements to slice off; of course, using the length of list1 to remove elements from list1 removes everything.
Problem 2: you have a list whose elements you want to concatenate to the end of another list. .append treats its parameter as a single new list element, even if it's a list; like the Zen of Python says, special cases aren't special enough to break the rules (keep in mind that sometimes you might want to do this, after all). The method you're looking for, instead, is .extend.
import numpy as np
l=len(a) if len(a)<len(b) else len(b)
a=np.array(list1[:l])
b=np.array(list2[:l])
ab=np.vstack((a, b)).T.flatten()
res=list(ab)+list1[l:]+list2[l:]

How do I stop the function when I have a unique list?

I tried a function that would remove both adjacent duplicates in a list. The remove any new duplicate pair and the function will keep going until there are no more duplicate pairs in the list.
I ran into the issue of figuring out how to tell the function to stop once I have a list without adjacent duplicates.
def removepair(no):
i = 1
if len(no) == 0 or len(no) == 1:
return no
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
return removepair(no)
So far the function will return 0 or single elements after removal:
input: [1, 2, 2, 1] output: []
or
input: [4, 4, 4, 4, 4] output: [4]
But the problem is I don't know how to stop the recursive function once it has a list with more than 1 element:
input: [1,2,3,3,2,1,5,6,7]
expected output: [5,6,7]
We may be able to avoid boolean flags and counters if we set up our recursion carefully:
def removepairs(numbers):
if not numbers: # base case #1, empty
return numbers
first, *second_on = numbers
if not second_on: # base case #2, one element
return numbers
second, *third_on = second_on
if first == second:
return removepairs(third_on)
result = [first] + removepairs(second_on)
if result == numbers:
return numbers # base case #3, no change!
return removepairs(result)
print(removepairs([1, 2, 3, 3, 2, 1, 5, 6, 7]))
OUTPUT
> python3 test.py
[5, 6, 7]
>
If recursive function is not a requirement, it can be simply done using the following code. I have commented the print statement.
def removepair(input_list):
unique_input_list = list(set(input_list))
output_list = list(x for x in unique_input_list if input_list.count(x)%2 == 1)
#print('Input List: ', input_list)
#print('Output list: ', output_list)
return output_list
Input List: [1, 2, 3, 3, 2, 1, 5, 6, 7]
Output list: [5, 6, 7]
Input List: [4, 4, 4, 4, 4]
Output list: [4]
Input List: [1, 2, 3, 3, 2, 1]
Output list: []
Your recursion should stop when no elements where popped from the list, not when the list is almost empty:
def removepair(no):
L = len(no)
if L <= 1:
return no
i = 1
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
if len(no) < L:
# elements where popped since the list len has decreased
return removepair(no)
else:
return no
Your code is difficult to understand since it uses a mix of recursion and side effects. Usually, you use either one or the other. Here you can replace your recursive call with a while:
def removepair(no):
while True:
L = len(no)
if L <= 1:
return no
i = 1
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
if len(no) == L: # no elements where popped
return no
But it's not really Pythonic and I think you should not modify the parameter no inside the function but rather return a new list. Why not iterate over the list and do not copy the duplicates in the result?
def removepair(no):
ret = []
for e in no:
if ret and e == ret[-1]: # current element is the same as the last element
ret.pop()
else:
ret.append(e)
return ret
Or with a fold:
def removepair(no):
import functools
return functools.reduce(lambda acc, x: acc[:-1] if acc and acc[-1]==x else acc+[x], no, [])

Strange behavior of python function [duplicate]

This question already has answers here:
Delete from a list while iterating [duplicate]
(3 answers)
Closed 6 years ago.
I'm in a online course at edx of Python and I have to do this little program, I think that the function is right but it has the bug when a element is deleted from the List suddenly the next element is not consider into the test.
def f(i):
return i + 2
def g(i):
return i > 5
def applyF_filterG(L, f, g):
"""
Assumes L is a list of integers
Assume functions f and g are defined for you.
f takes in an integer, applies a function, returns another integer
g takes in an integer, applies a Boolean function,
returns either True or False
Mutates L such that, for each element i originally in L, L contains
i if g(f(i)) returns True, and no other elements
Returns the largest element in the mutated L or -1 if the list is empty
"""
# Your code here
i = 0
if len(L) == 0:
return -1
while i < len(L):
if not g(f(L[i])):
del L[i]
i += 1
return max(L)
If I try with this example L = [0, -10, 5, 6, -4, -2], the value of L should be L = [5,6] but the result is this [-10, 5, 6, -2] the element -10 is skipped when the 0 was deleted and the same happens with -4 and -2. Please help, I don't know how can solve this.
Try not to iterate through the list that you mutate inside the loop in Python. In this example, the index order changed after you delete the element. I created a copy of L before iterating it and it does the trick.
def applyF_filterG(L, f, g):
copied_L = L[:]
for i in copied_L:
if not g(f(i)):
L.remove(i)
if len(L)==0:
return -1
else:
return max(L)
The problem:
You're deleting elements from the list as you iterate over the list. Due to that, i no longer refers to the right element in the list.
To illustrate this problem, here's a sample run through your code.
For this example we are going to assume that the if statement deletes an element if it's value is even.
I also assume that i has already been initialized.
L = [1, 2, 6, 3, 4]
iteration 1
i == 0, L[i] == 1, we don't delete the element.
L == [1, 2, 6, 3, 4]
iteration 2
i == 1, L[i] == 2, element is deleted.
L == [1, 6, 3, 4]
iteration 3
i == 2, L[i] == 3, we don't delete the element.
L == [1, 6, 3, 4]
# Did you notice that we just skipped checking the 6 because its index moved?!
iteration 4
i == 3, L[i] == 4, element is deleted.
L == [1, 6, 3]
We're finished!
There's a couple of ways to do this. Although #Meerness already supplied one way to do it, here's another way you could do it just for completeness.
i = len(L) - 1
if i == -1:
return -1
while i >= 0:
if not g(f(L[i])):
del L[i]
i -= 1
How this works:
In this way of doing it, you count down from the topmost index. That way, the deletion of an element doesn't affect the indexes of elements that you still haven't checked.
My explanation of this way of doing it is a slightly reworded version of the comment by #JohnColeman.
JSYK, though, I had already written this solution before I saw his comment so I didn't borrow the idea from him -- I only borrowed his explanation. :)
Here's an example of what happens when we count down instead of counting up:
L = [1, 2, 6, 3, 4]
iteration 1
i == 4, L[i] == 4, element is deleted.
L == [1, 2, 6, 3]
iteration 2
i == 3, L[i] == 3, we don't delete the element.
L == [1, 2, 6, 3]
iteration 3
i == 2, L[i] == 6, element is deleted.
L == [1, 2, 3]
iteration 4
i == 1, L[i] == 2, element is deleted.
L == [1, 3]
iteration 5
i == 0, L[i] == 1, we don't delete the element.
L == [1, 3]
We're finished!
PS: Examples automatically generated with python3 script. :)
Mutating a list as you're iterating over it is a bad idea and will lead to unexpected behaviour. In general, you're better off creating a new list to work on.
In this specific case, your code can be fixed with an easy modification. Only iterate the index if you aren't deleting an element, so as not to skip ahead in the list.
while i < len(L):
if not g(f(L[i])):
del L[i]
else:
i += 1
def applyF_filterG(L, f, g):
"""
Assumes L is a list of integers
Assume functions f and g are defined for you.
f takes in an integer, applies a function, returns another integer
g takes in an integer, applies a Boolean function,
returns either True or False
Mutates L such that, for each element i originally in L, L contains
i if g(f(i)) returns True, and no other elements
Returns the largest element in the mutated L or -1 if the list is empty
"""
M =[]
for i in range(len(L)):
if g(f(L[i])):
M.append(L[i])
L = M[:]
if len(L)==0:
return -1
else:
return max(L)

How to set output as a list without space?

n is an integer and xs is a list of integers.
n = 2
xs = [1, 2, 3, 4, 5, 6]
def multiples(n,xs):
empty = []
for i in range(len(xs)):
if xs[i] % n == 0:
print(xs[i])
return empty
It should give me the output of 2, 4, 6 in three separate lines. Is any way I can merge them into a list that without space and only commas?
n=3
xs=[11, 13]
Will the output become '[]', the empty set?
You can just change your for loop to this:
print(",".join(str(x) for x in xs if not x % n))
A generator expression that does it all. I am assuming that your return empty line is just indented incorrectly because at that indentation, it would print only the first one.
You have a couple of problems in your code. The first problem is that you are only checking the first element in your array, and then you are returning out of your function. So, you are never actually completing iterating over your entire list.
Second, you are simply printing your items out, and per your requirements, and based on the fact that you created a list called empty, you want to collect this data and output it when you are finished.
With that being said, what you want to do instead is change your print statement to append to your list:
empty.append(xs[i])
Then when you are finished your for loop return empty.
Like this:
def multiples(n,xs):
empty = []
for i in range(len(xs)):
if xs[i] % n == 0:
empty.append(xs[i])
return empty
Use a list comprehension:
n = 2
xs = [1, 2, 3, 4, 5, 6, 7]
>>> [x for i, x in enumerate(xs, 1) if not i % n]
[2, 4, 6]
xs = [2, 3, 4, 5, 6, 7]
>>> [x for i, x in enumerate(xs, 1) if not i % n]
[3, 5, 7]
n = 3
xs = [11, 13]
>>> [x for i, x in enumerate(xs, 1) if not i % n]
[]
This results in a list of integers instead of strings.
As you want to take every n'th item from your list, you need to use enumerate (starting with a value of 1). The if xs[i] %n == 0 solution just happened to work because the list a continuous range. Try xs = [3, 3, 3, 3] and see what happens with your function and the other solutions...
To help understand what is going on, here is a table of the interim values.
i x i % 2 not i % 2
== == ===== =========
1 3 1 False
2 3 0 True
3 3 1 False
4 3 0 True

Nested lists , check if one list has common elements with other and if so join [duplicate]

This question already has answers here:
Union find implementation using Python
(4 answers)
Closed 7 years ago.
I have a lists in list say [[1, 3, 5], [2, 4], [1,7,9]]
my requirement is i want to iterate through the list and reduce it to
[[1,3,5,7,9], [2,4]].
How would i do it??
Algo:
Get base element from the new method.
Remove First item from input list and create new variable for that.
Iterate on every item from the new list.
Check any element from item is present in the base set or not by set intersection method.
If present then do 6,7,8,9
Update base with current item by set update method.
Remove current item from the list.
Set flag to True.
break the for loop because need to check again from first item.
Create final result list add adding base and remaining list.
[Edit:]
Issue:
Previous code considering base item as first item from the given list, but when there is no matching of this item with other items and other items have matching then code will not work.
Updated:
Get base item from the given list which have matching with any one item from the list.
[Edit2]:
Inserted merged item into respective position
Demo:
import copy
a = [[13, 15, 17], [66,77], [1, 2, 4], [1,7,9]]
#- Get base
base = None
length_a = len(a)
base_flag = True
i = -1
while base_flag and i!=length_a-1:
i += 1
item = a[i]
for j in xrange(i+1, length_a):
tmp = set(item).intersection(set(a[j]))
if tmp:
base = set(item)
base_flag = False
break
print "Selected base:", base
if base:
tmp_list = copy.copy(a)
target_index = i
tmp_list.pop(target_index)
flag = True
while flag:
flag = False
for i, item in enumerate(tmp_list):
tmp = base.intersection(set(item))
if tmp:
base.update(set(item))
tmp_list.pop(i)
flag = True
break
print "base:", base
print "tmp_list:", tmp_list
result = tmp_list
result.insert(target_index, list(base))
else:
result = a
print "\nFinal result:", result
Output:
$ python task4.py
Selected base: set([1, 2, 4])
base: set([1, 2, 4, 7, 9])
tmp_list: [[13, 15, 17], [66, 77]]
Final result: [[13, 15, 17], [66, 77], [1, 2, 4, 7, 9]]
It's quite inefficient, but this does the trick:
def combine_lists(lists):
# Keep a set of processed items
skip = set()
for i, a in enumerate(lists):
# If we already used this list, skip it
if i in skip:
continue
for j, b in enumerate(lists[i + 1:], i + 1):
# Use a set to check if there are common numbers
if set(a) & set(b):
skip.add(j)
for x in b:
if x not in a:
a.append(x)
# yield all lists that were not added to different lists
for i, a in enumerate(lists):
if i not in skip:
yield a
[edit] Just noticed that the order doesn't matter anymore (your output suggests that it does), that makes things easier :)
This version should be fairly optimal:
def combine_lists(lists):
# Keep a set of processed items
skip = set()
sets = map(set, lists)
for i, a in enumerate(sets):
# If we already returned this set, skip it
if i in skip:
continue
for j, b in enumerate(sets[i + 1:], i + 1):
# Use a set to check if there are common numbers
if a & b:
skip.add(j)
a |= b
# yield all sets that were not added to different sets
for i, a in enumerate(sets):
if i not in skip:
yield a
a = [[1,2], [3,4 ], [1,5,3], [5]] # output: [set([1, 2, 3, 4, 5])]
# a = [[1, 3, 5], [2, 4], [1,7,9]] # output: [set([1, 3, 5, 7, 9]), set([2, 4])]
# convert them to sets
a = [set(x) for x in a]
go = True
while go:
merged = False
head = a[0]
for idx, item in enumerate(a[1:]):
if head.intersection(item):
a[0] = head.union(item)
a.pop(idx + 1)
merged = True
break
if not merged:
go = False
print a

Categories

Resources