I have a list of integers, and I want to add 1 to each integer in the list with every iteration of a while loop. More specifically, I want to perform this operation on the list's integers one by one (left to right) instead of all at once, and stop the loop as soon as the its conditional becomes False, even if that means stopping before the end of the list.
First, I tried the following:
def myfunction(someinput):
myintegerlist = [0, 0, 0]
while sum(myintegerlist) < someinput:
myintegerlist[0:] = [x+1 for x in myintegerlist[0:]]
return myintegerlist, sum(myintegerlist)
This doesn't do what I want, because it simultaneously adds 1 to all integers in the list. Thus if someinput = 4, it returns 6 as the sum and [2, 2, 2] as the list, whereas I would like it to stop at [2, 1, 1] before exceeding the input number. So I tried
while sum(myintegerlist) < someinput:
myintegerlist[indexplace] = [x+1 for x in myintegerlist[indexplace]]
indexplace += 1
This was just a guess, and throws "TypeError: 'int' object is not iterable". But I am stuck on how to get through the list items one by one and add 1 to them. Is there a good way to move incrementally through the list with each iteration? Or should I be trying something completely different?
Keep track of the index where you need to add the 1 during the while loop:
def myfunction(someinput):
myintegerlist = [0, 0, 0]
increment_index = 0 # next place to add 1
while sum(myintegerlist) < someinput:
myintegerlist[increment_index % len(myintegerlist)] += 1 # add 1 to the element, modulo length of the list to keep the index in range
increment_index += 1
return myintegerlist, sum(myintegerlist)
print(myfunction(4))
Result:
([2, 1, 1], 4)
Normally, in order to iterate over the values in a list you would just use
for value in myintegerlist:
# ...
but this doesn't allow you to directly change the value in the list.
You have to create a loop over the indices of the list.
for i in range(len(myintegerlist)):
value = myintegerlist[i]
In particular, by assigning to myintegerlist[i] you can change the values in the list.
for i in range(len(myintegerlist)):
value = myintegerlist[i]
myintegerlist[i] = new_value
Now you need one additional step, because you don't just want to iterate over each list index once, but possibly many times, until a condition is met. In order to keep the iteration going, you can use the cycle function from the itertools module.
from itertools import cycle
for i in cycle(range(len(myintegerlist))):
# i = 0, 1, 2, 0, 1, 2, 0, 1, ...
Now you can just add 1 to the value at the current list index and break the loop if the sum of the values has reached the desired amount:
for i in cycle(range(len(myintegerlist))):
if sum(myintegerlist) >= someinput:
break
myintegerlist[i] += 1
It is bad idea to start every time from 0. Too much meaningless iterations if someinput is a large number.
def get_els(sum_value: int, num_of_els: int = 3) -> tuple:
_list = [int(sum_value / num_of_els)] * num_of_els
if sum(_list) == sum_value:
return _list, sum_value
while True:
for idx in range(0, num_of_els):
_list[idx] += 1
if sum(_list) == sum_value:
return _list, sum_value
print(get_els(2))
print(get_els(4))
print(get_els(5))
print(get_els(10))
print(get_els(20))
Output:
([1, 1, 0], 2)
([2, 1, 1], 4)
([2, 2, 1], 5)
([4, 3, 3], 10)
([7, 7, 6], 20)
More over, if length of list is only 3 elements then loop is not necessary at all:
def get_els(sum_value: int) -> tuple:
_list = [int(sum_value / 3)] * 3
if sum_value % 3 == 0:
return _list, sum_value
if sum_value % 3 > 1:
_list[0] += 1
_list[1] += 1
else:
_list[0] += 1
return _list, sum_value
I have a collection of lists each containing 16 items, and I want to find lists with 12 consecutive values either > or < than a specified threshold. For now, I have iterated through the lists and put 1 for values greater and -1 for values less than the threshold, and I used to following to eliminate those that don't have 12 of either.
if list.count(-1) >= 12 or list.count(1) >= 12:
How do I efficiently check for 12 consecutive values? (12 values can loop around) for example this would count
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1]
Currently I have 2 nested for loops, but I know this checks the same value multiple times.
for i in range(16):
light = 0
dark = 0
for j in range(12):
index = i + j
if index > 15:
index -= 15
if list[index] == 1:
light += 1
elif list[index] == -1:
dark += 1
else:
break
if dark > 0 and light > 0:
break
if dark == 12 or light == 12:
return True
I would harness itertools.groupby following way
import itertools
data = [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1]
runs = [len(list(g)) for _,g in itertools.groupby(data)]
if data[0] == data[-1]:
runs[0] += runs.pop()
print(max(runs) >= 12) # True
Explanation: I use itertools.groupby to get length of runs (in this case [7,4,5]) - itertools.groupby does group only adjacent equal elements, then if first and last values of data are equal I extract last element from runs and add it to first element (as you allow wraparound), then I check if longest run is equal or greater 12.
If I've understood correctly what you want, then here are some ways to do it:
First off you can sort the list, putting every element in an order, and then doing a linear search and incrementing a counter every time it's the same value with the previous list element
a.sort()
count = 0
for i in range(16):
if count == 12:
break
if a[i - 1] == a[i]:
count += 1
Another way to do it is with the modulo operator, which doesn't require you sorting the list. And you can add some variables to check if you've done a full loop.
flag_count = 0
count = 0
i = 0
while True:
if count == 12 or flag_count == 2:
break
if a[i % 15] == a[(i + 1) % 15]:
count += 1
if i % 15 == 0:
flag_count += 1
i += 1
You can loop through the list while keeping track how many times you've seen a specific value. Everytime you see a different value this counter resets (because you are looking for consecutive values).
def consective_values(input_list, target_value):
i = 0
count = 0
for _ in range(2 * len(input_list)):
if input_list[i] == target_value:
count += 1
else:
count = 0
if count == 12:
return True
i = (i + 1) % len(input_list)
return False
Because the consecutive values can loop around you have to be a bit more careful with the list indexing and the loop variable. The max number of loops is equal to twice the length of the list. When you still haven't found 12 consecutive values at this point you know there aren't any. TO have an index loop around a list you can always apply the modulo (%) operator
some balls of different colors in a line. When a continuous block of three or more balls of the same color is formed, it is removed from the line. In this case, all the balls are shifted to each other, and the situation may be repeated.
Write a function lines(a) that determines how many balls will be destroyed. There can be at most one continuous block of three or more same-colored balls at the initial moment.
Input data:
The function takes a list a with initial balls disposition. Balls number is less or equals 1000, balls colors can be from 0 to 9, each color has its own integer.
Output data:
The function has to return one number, the number of the balls that will be destroyed.
Input:[2,2,1,1,1,2,1] Output:6
input : [0, 0, 0, 0, 0], output: 5
input:[2, 3, 1, 4], output: 0
I try to use the two pointer approach, but not sure how to do it.
def lines(a):
left = 0
right = len(a)-1
s=[]
while left < right :
if a[left] == a[left:right+1]:
s.extend(a[left: right+1])
del a[left: right+1]
else:
left += 1
right -= 1
return len(s)
test_a = [2, 2, 1, 1, 1, 2, 1]
print(lines(test_a))
I think if a[left] == a[left:right+1]: did not work, i am try to compare elements from left to right is same as elements from right to left. Alsodel a[left: right+1] did not work, i try delete those elements which already extend to new list s.
Thanks for your advice.
This is an iterative solution using a lo and hi pointer traversing the input.
Note that by appending an end marker guaranteed not to be an input colour means that the logic checking for runs of colours does not need duplication outside the while loop.
The code
in_outs = [([2,2,1,1,1,2,1], 6),
([0, 0, 0, 0, 0], 5),
([2, 3, 1, 4], 0),
]
def test(f):
print(f"\nSTART Testing answer {f.__doc__}")
for arg, ans in in_outs:
try:
out = f(arg.copy())
except:
ans = '<Exception thrown!>'
if out != ans:
print(f" {f.__name__}({arg}) != {ans} # instead gives: {out}")
else:
print(f" {f.__name__}({arg}) == {out}")
print(f"STOP Testing answer {f.__doc__}\n")
#%% From me, Paddy3118
def lines(a):
"From Paddy3118"
a = a.copy() + [-1] # Add terminator
d = lo = hi = 0 # delete count, lo & hi pointers
lo_c = hi_c = a[0] # Colours at pointer positions
while hi +1 < len(a):
hi += 1; hi_c = a[hi]
if lo_c != hi_c:
if hi - lo > 2:
d += hi - lo
del a[lo: hi]
lo, hi, lo_c, hi_c = 0, 0, a[0], a[0]
else:
lo, lo_c = hi, hi_c
return d
test(lines)
Output
START Testing answer From Paddy3118
lines([2, 2, 1, 1, 1, 2, 1]) == 6
lines([0, 0, 0, 0, 0]) == 5
lines([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118
Checking other examples
Extend the above with the following harnesses to aid testing
#%% IA from Ignacio Alorre
import itertools
def grouping_balls(balls):
return ["".join(g) for k, g in itertools.groupby(balls)]
def destroy_balls(list_balls, destroyed):
if len(list_balls) < 1:
return destroyed
balls_grp = grouping_balls(list_balls)
# No more balls to destroy
if max(map(len,balls_grp)) < 3:
return destroyed
# Destroying and merging balls
else:
non_dest_balls = ""
for l in balls_grp:
if len(l) < 3:
non_dest_balls += l
else:
destroyed += len(l)
return destroy_balls(non_dest_balls, destroyed)
def lines_IA(a):
"From Ignacio Alorre"
return destroy_balls(''.join(map(str, a)), 0)
test(lines_IA)
#%% AHX from Ahx
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
def _lines_ahx(a):
"""
Args:
a (list): input array
Returns:
(int): total number of destroyed balls.
"""
global total_destroyed
global counter
global previous
global previous_index
for i, element in enumerate(a):
if element == previous:
counter += 1
else:
counter = 1
previous = element
previous_index = i
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
_lines_ahx(a)
return total_destroyed
def lines_AHX(a):
"From Ahx"
global total_destroyed
global counter
global previous
global previous_index
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
return _lines_ahx(a)
test(lines_AHX)
Full Output
All three examples work on the given testds. No timings are given as the tests are very small.
START Testing answer From Paddy3118
lines([2, 2, 1, 1, 1, 2, 1]) == 6
lines([0, 0, 0, 0, 0]) == 5
lines([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118
START Testing answer From Ignacio Alorre
lines_IA([2, 2, 1, 1, 1, 2, 1]) == 6
lines_IA([0, 0, 0, 0, 0]) == 5
lines_IA([2, 3, 1, 4]) == 0
STOP Testing answer From Ignacio Alorre
START Testing answer From Ahx
lines_AHX([2, 2, 1, 1, 1, 2, 1]) == 6
lines_AHX([0, 0, 0, 0, 0]) == 5
lines_AHX([2, 3, 1, 4]) == 0
STOP Testing answer From Ahx
You can use a stack for this (simple list implementation). Check if the last 3 balls are of the same kind. If they are, then keep popping all balls of the same kind. Else, add the ball to the stack and continue. Each ball can be added once and removed once to the list, so the complexity should be O(N) as well.
At the end, number of balls destroyed = count of original balls - length of stack.
We need to count the number of occurrences in the input list.
If the current ball color (element) is equals to the previous ball color (previous), then increase the counter by one.
for i, element in enumerate(a):
if element == previous:
counter += 1
If they are not equal, then there will be a possibility the next color may repeat more than three. Therefore store the current colors' index
for i, element in enumerate(a):
if element == previous:
counter += 1
else:
counter = 1
previous = element
previous_index = i
Now check if the color is repeated more than three times.
If it does we need to remove the balls from the list.
While removing we also need to count the number of destroyed balls.
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
There might be a confusion, why I did a.pop(previous_index)?
If you debug this part of code on an example. For instance: [2, 2, 1, 1, 1, 2, 1]
When i== 4, the current list is [2, 2, 1, 1, 1] which satisfies the count >= 3
Iteration = 1, the list will become
[2, 2, 1, 1]
If you say remove the next element, which will pop the final element
Iteration = 2, the list will become
[2, 2, 1]
Now, on Iteration 3, which element will be pop? index-out-of-bound.
Iteration = 3, the list won't change
[2, 2, 1]
Therefore, during the iteration always pop the current element. Since the next element will be the current one.
Now we need to call the method again, to see if there is any balls left
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
lines(a)
But, we have to be careful since we have declared previous, previous_index, counter and totally_destroyed as a local variable.
If we keep them as a local attributes, all variables will be re-iniitalized so the alogrithm result won't be true.
Therefore we have to initialize them as a global variable and return the total number of destroyed balls.
Code:
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
def lines(a):
"""
Args:
a (list): input array
Returns:
(int): total number of destroyed balls.
"""
global total_destroyed
global counter
global previous
global previous_index
for i, element in enumerate(a):
if element == previous:
counter += 1
else:
counter = 1
previous = element
previous_index = i
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
lines(a)
return total_destroyed
test_a = [2, 2, 1, 1, 1, 2, 1]
test_b = [0, 0, 0, 0, 0]
test_c = [2, 3, 1, 4]
print(lines(test_a))
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
print(lines(test_b))
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
print(lines(test_c))
Results:
6
5
0
My solution has two parts. One method to group by balls by their number grouping_balls. Then a recursive method first check there are still groups bigger than 3, if so destroy them and merge the rest for the next iteration destroy_balls.
import itertools
# Split balls by type
# For 2211121 you would get: ["22", "111", "2", "1"]
def grouping_balls(balls):
return ["".join(g) for k, g in itertools.groupby(balls)]
# Keeps destroying balls as long as there are groups of 3 or more
def destroy_balls(list_balls, destroyed):
if len(list_balls) < 1:
return destroyed
balls_grp = grouping_balls(list_balls)
# No more balls to destroy
if max(map(len,balls_grp)) < 3:
return destroyed
# Destroying and merging balls
else:
non_dest_balls = ""
for l in balls_grp:
if len(l) < 3:
non_dest_balls += l
else:
destroyed += len(l)
return destroy_balls(non_dest_balls, destroyed)
Input = [0,0,0,0,0]
destroy_balls(''.join(map(str,Input)), 0)
I thought of another algorithm that first does a run-length encoding of the whole input before using a single pointer traversing the list and deleting stuff. It works a lot better on larger input.
To test it I wrote a routine downto_zero that generates sequences of ones and zeroes which are all deleted, eventually.
I follow up by doing a timed run of all the examples.
Note: run in Ipython shell of Spyder IDE for timings.
Note: the example for Ahx makes great use of recursion and fails with an input of length 12, let alone 7500.
RLE code and Test Generator
#%% Test Generator for long tests
in_outs = [([2,2,1,1,1,2,1], 6),
([0, 0, 0, 0, 0], 5),
([2, 3, 1, 4], 0),
]
import sys
def test(f, in_outs=in_outs):
print(f"\nSTART Testing answer {f.__doc__}")
for arg, ans in in_outs:
arg_txt = arg if len(arg) <= 8 else f"[<{len(arg)} terms>]"
try:
out = f(arg.copy())
except:
out = f'<Exception thrown! {sys.exc_info()[0]}>'
if out != ans:
print(f" {f.__name__}({arg_txt}) != {ans} # instead gives: {out}")
else:
print(f" {f.__name__}({arg_txt}) == {out}")
print(f"STOP Testing answer {f.__doc__}\n")
def downto_zero(n=3):
"Test generator that reduces all input"
if n == 0:
return []
x = ([0, 1] * ((n+1)//2))[:n] # 0,1 ... of length n
e = x[-1] # end item
ne = 0 if e == 1 else 1 # not end item
r = ([e, e, ne, ne] * n)[:2*n]
return x + r
#%% RLE Runlengh encoded from me, Paddy
from itertools import groupby
def lines_RLE(a):
"From Paddy3118 using run-length encoding"
a = a.copy() + [-1] # Add terminator
a = [[key, len(list(group))] for key, group in groupby(a)] # RLE
d = pt = 0 # delete count, pointer
while pt +1 < len(a):
i0, n0 = a[pt] # item, count at pt
if n0 > 2:
d += n0
del a[pt]
if pt > 0:
if a[pt - 1][0] == a[pt][0]: # consolidate
a[pt - 1][1] += a[pt][1]
del a[pt]
pt -= 1
continue
else:
pt += 1
return d
test(lines_RLE, in_outs)
#%% Timed testing
print("TIMED TESTING\n=============")
n = 2_500
for f in (lines, lines_RLE, lines_IA, lines_AHX):
dn = downto_zero(n)
%time test(f, [(dn, len(dn))])
Output with timings
START Testing answer From Paddy3118 using run-length encoding
lines_RLE([2, 2, 1, 1, 1, 2, 1]) == 6
lines_RLE([0, 0, 0, 0, 0]) == 5
lines_RLE([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118 using run-length encoding
TIMED TESTING
=============
START Testing answer From Paddy3118
lines([<7500 terms>]) == 7500
STOP Testing answer From Paddy3118
Wall time: 2.44 s
START Testing answer From Paddy3118 using run-length encoding
lines_RLE([<7500 terms>]) == 7500
STOP Testing answer From Paddy3118 using run-length encoding
Wall time: 19 ms
START Testing answer From Ignacio Alorre
lines_IA([<7500 terms>]) == 7500
STOP Testing answer From Ignacio Alorre
Wall time: 10.9 s
START Testing answer From Ahx
lines_AHX([<7500 terms>]) != 7500 # instead gives: <Exception thrown! <class 'RecursionError'>>
STOP Testing answer From Ahx
Wall time: 16 ms
My third, and last answer builds on my second RLE entry by swapping to use a doubly linked list data structure to remove what can be costly array delete operations. The new code looks like it has better big-O characteristics compared to my other two solutions so may proove even faster for larger inputs.
Doubly-Linked list code:
#%% LL Linked-list and Runlengh encoded from me, Paddy
from itertools import groupby
def lines_LL(a):
"From Paddy3118 using a linked-list and run-length encoding"
a = a.copy() + [-1] # Add terminator
# a is list of [item, reps, prev_pointer, next_pointer]
a = [[key, len(list(group)), i - 1, i + 1]
for i, (key, group) in enumerate(groupby(a))] # linke-list RLE
a[0][-2] = None # No previous item
a[-1][-1] = None # No next item
d = pt = 0 # delete count, pointer
while pt is not None:
i0, n0, pre_pt, nxt_pt = a[pt] # item, count, next at pt
if n0 > 2:
d += n0
deleted_pt = pt
if pre_pt is not None:
a[pre_pt][-1] = pt = nxt_pt # del a[pt] & pt to next
if a[pre_pt][0] == a[pt][0]: # consolidate same items in pre
a[pre_pt][1] += a[pt][1]
a[pre_pt][-1] = a[pt][-1] # del a[pt]
pt = pre_pt # ... & pt to previous
else:
pt = nxt_pt
else:
pt = nxt_pt
return d
print("SIMPLE FUNCTIONAL TESTING\n=============")
test(lines_LL, in_outs)
#%% Timed testing
if 1:
print("TIMED TESTING\n=============")
n = 20_000
for f in (lines_LL, lines_RLE, lines): #, lines_IA, lines_AHX):
dn = downto_zero(n)
%time test(f, [(dn, len(dn))])
Output and timings
SIMPLE FUNCTIONAL TESTING
=============
START Testing answer From Paddy3118 using a linked-list and run-length encoding
lines_LL([2, 2, 1, 1, 1, 2, 1]) == 6
lines_LL([0, 0, 0, 0, 0]) == 5
lines_LL([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118 using a linked-list and run-length encoding
TIMED TESTING
=============
START Testing answer From Paddy3118 using a linked-list and run-length encoding
lines_LL([<60000 terms>]) == 60000
STOP Testing answer From Paddy3118 using a linked-list and run-length encoding
Wall time: 104 ms
START Testing answer From Paddy3118 using run-length encoding
lines_RLE([<60000 terms>]) == 60000
STOP Testing answer From Paddy3118 using run-length encoding
Wall time: 387 ms
START Testing answer From Paddy3118
lines([<60000 terms>]) == 60000
STOP Testing answer From Paddy3118
Wall time: 2min 31s