List wrapping for finding distance between indices - python

I have a random generated list that could look like:
[1, 0, 0, 1, 1, 0, 1, 0, 0, 0]
I need to find all of the distance between the 1's including the ones that wrap around.
For an example the list above, the first 1 has a distance of 3 to the next 1. The second 1 has a distance of 1 to the following 1 and so on.
How do I find the distance for the last 1 in the list using wrap around to the first 1?
def calc_dist(loc_c):
first = []
#lst2 = []
count = 0
for i in range(len(loc_c)):
if loc_c[i] == 0:
count += 1
#lst2.append(0)
elif loc_c[i] == 1:
first.append(i)
count += 1
loc_c[i] = count
#lst2.append(loc_c[i])
#if loc_c[i] + count > len(loc_c):
# x = loc_c[first[0] + 11 % len(loc_c)]
# loc_c[i] = x
count = 0
return loc_c
My expected outcome should be [3, 1, 2, 4].

Store the index of the first 1 you first reference, then when you get to the last 1 you only have to add the index of the first plus the number of 0 elements after the last 1 to get that distance (so len(inputlist) - lastindex + firstindex).
The other distances are the difference between the preceding 1 value and the current index.
from typing import Any, Generator, Iterable
def distances(it: Iterable[Any]) -> Generator[int, None, None]:
"""Produce distances between true values in an iterable.
If the iterable is not endless, the final distance is that of the last
true value to the first as if the sequence of values looped round.
"""
first = prev = None
length = 0
for i, v in enumerate(it):
length += 1
if v:
if first is None:
first = i
else:
yield i - prev
prev = i
if first is not None:
yield length - prev + first
The above generator calculates distances as it loops over the sequence seq, yielding them one by one:
>>> for distance in distances([1, 0, 0, 1, 1, 0, 1, 0, 0, 0]):
... print(distance)
...
3
1
2
4
Just call list() on the generator if you must have list output:
>>> list(distances([1, 0, 0, 1, 1, 0, 1, 0, 0, 0]))
[3, 1, 2, 4]
If there are no 1 values, this results in zero distances yielded:
>>> list(distances([0, 0, 0]))
[]
and 1 1 value gives you 1 distance:
>>> list(distances([1, 0, 0]))
[3]
I've made the solution generic enough to be able to handle any iterable, even if infinite; this means you can use another generator to feed it too. If given an infinite iterable that produces at least some non-zero values, it'll just keep producing distances.

Nice and tidy:
def calc_dist(l):
idx = [i for i, v in enumerate(l) if v]
if not idx: return []
idx.append(len(l)+idx[0])
return [idx[i]-idx[i-1] for i in range(1,len(idx))]
print(calc_dist([1, 0, 0, 1, 1, 0, 1, 0, 0, 0]))
# [3, 1, 2, 4]
print(calc_dist([0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0]))
# [3, 1, 2, 7]
print(calc_dist([0, 0, 0, 0])
# []

You can use numpy:
import numpy as np
L = np.array([1, 0, 0, 1, 1, 0, 1, 0, 0, 0])
id = np.where(test == 1)[0]
# id = array([0, 3, 4, 6], dtype=int64)
res = [id[i]-id[i-1] for i in range(1, len(id))]
# [3, 1, 2]
# Last distance missing:
res.append(len(L)- id[-1])
res = [3, 1, 2, 4]
Note that the information you ask for is comprised above, but maybe the output format is wrong. You were not really specific...
Edit: How to convert list to an array since you generate random list
L = [1, 0, 0, 1, 1, 0, 1, 0, 0, 0]
np.asarray(L)
Edit2: How to check if there is no 1 in the list:
import numpy as np
L = np.array([1, 0, 0, 1, 1, 0, 1, 0, 0, 0])
id = np.where(test == 1)[0]
if len(id) == 0:
res = []
else:
res = [id[i]-id[i-1] for i in range(1, len(id))]
res.append(len(L)- id[-1])
OR:
try:
res = [id[i]-id[i-1] for i in range(1, len(id))]
res.append(len(L)- id[-1])
except:
res = []

Related

if condition statment trigerred without condition being met for python

I'm solving a simple DSA problem and seem to grasp a general way to solve the question where Given an integer array called nums, I would move all 0's to the end of it while maintaining the relative order of the non-zero elements.
For example, nums = [0,1,0,3,12]
Then the expected output would be. [1,3,12,0,0]
My approach was as below.
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
count = 0
while count < len(nums):
if len(nums[count+1:]) == count:
return nums
elif nums[count] == 0:
nums.pop(nums[count])
nums.append(0)
else:
count += 1
print(nums)
I would expect this to work in my mind, but the output is not what I expected because stdout shows as
[1, 0, 3, 12, 0]
[1, 0, 3, 12, 0]
[0, 3, 12, 0, 0]
[0, 3, 12, 0, 0]
Now I understand why the firsto two prints show as such, because 0 was identifed in the first loop and popped
However I do not understand why in the third and forth iteration, the result shows as [0,3,12,0,0].
The if-conditiona says if nums[count]==0, then nums.pop[nums[count]].
How is the pop and append triggered when the condition is not met?
Am I missing something here?
I simplified in few lines:
def ceros_array(ar):
'''Given and array of integer return zeros at end of array'''
zeros = ar.count(0) # count zeros in array
new_array = [x for x in ar if x !=0] # new array with elements different that zero
for i in range(zeros): # cycle for zeros removed
new_array.append(0) # insert 0 at end of new array
return new_array
I tested with these arrays:
>>> ceros_array([1, 0, 3, 12, 0])
[1, 3, 12, 0, 0]
>>> ceros_array([0, 0, 3, 12, 0])
[3, 12, 0, 0, 0]
>>> ceros_array([0, 0, 3, 0, 0])
[3, 0, 0, 0, 0]
>>> ceros_array([0, 0, 0, 0, 1])
[1, 0, 0, 0, 0]
>>> ceros_array([0, 0, 0, 0, 0])
[0, 0, 0, 0, 0]
Inspired by dannisis's answer:
def shift1(nums: list[int]) -> list[int]:
"""Push all zeros in nums to the back (right) of the list."""
# Keep only non-zeroes
new_nums = [x for x in nums if x != 0]
# Append the correct number of zeroes
new_nums += [0] * nums.count(0)
return new_nums
for nums in [
[1, 0, 3, 12, 0],
[0, 0, 3, 12, 0],
[0, 0, 3, 0, 0],
[0, 0, 0, 0, 1],
[0, 0, 0, 0, 0],
]:
print(shift1(nums))
[1, 3, 12, 0, 0]
[3, 12, 0, 0, 0]
[3, 0, 0, 0, 0]
[1, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
Here's a more general, albeit less performant, solution:
def shift2(nums: list[int]):
_len = len(nums)
shift_ct = 0
for i in range(_len):
if nums[i] == 0:
shift_ct += 1
continue
else:
nums[i - shift_ct] = nums[i]
# "Back fill" end w/any zeroes
i = _len - shift_ct
while i < _len:
nums[i] = 0
i += 1
This mutates the list you pass in:
Nums = list[int] # input to function
Want = list[int] # what we expect in return
test_cases: list[tuple[Nums, Want]] = [
([0, 1], [1, 0]),
([0, 0, 1], [1, 0, 0]),
([0, 1, 2], [1, 2, 0]),
([0, 1, 0, 2], [1, 2, 0, 0]),
([0, 1, 0, 2, 0, 3], [1, 2, 3, 0, 0, 0]),
]
def test_shift2():
for nums, want in test_cases:
got = nums[:] # make copy to keep "input" separate from "result" in print-out, if test fails
shift2(got)
if got != want:
print(f"shift2({nums}) = {got}; want {want}")
I've run a number of timeit tests and profiled both, and I believe shift1 is slightly faster because even though it has two function calls (1 for the list comprehension, 1 for count()), it just has less lines to execute; shift2 only has the one function call (len()), but far more lines for the interpreter to step through.
You need to be very careful about changing structure of iterable objects WHILE iterating through them. Think about what you are doing. On your first loop, you hit 0 when i = 1, you pop that out and append it to the end. So now, all the elements from that point are shuffled up to an index that is one lower. The 2nd 0 was initially at index 2 but is shuffled up to index 1. You then ADD 1 to your index, meaning the next element is skipped and not processed.
Your best solution is to is to append non-zero elements to a second list and then fill with zeros:
def moveZeroes(nums) -> None:
new_list = []
zero_count = 0
for i in nums:
if i > 0:
new_list.append(i)
else:
zero_count += 1
new_list += [0] * zero_count
print(new_list)
moveZeroes([0, 1, 0, 3, 12])
I tested this and this works. (note that I used pop(count), not pop(nums[count]):
NOTE: After posting this answer, I noticed that it would fail if the initial list (my_numbers) starts with more than one zero. A quick and dirty fix for this would be to just call moveZeroes(my_numbers) twice.
def moveZeroes(nums):
count = 0
while count < len(nums):
if nums[count] == 0:
nums.pop(count)
nums.append(0)
count += 1
else:
count += 1
my_numbers = [0,1,0,2,12,0,4]
moveZeroes(my_numbers)
moveZeroes(my_numbers) # added as temporary fix for leading multiple zeroes
print(my_numbers) #prints [1,2,12,4,0,0,0]

Get number of keys matching a value in itertools.groupby

I have lists of binary values and I am trying to get the number of groups of consecutive 1s in each list.
Here's a few examples:
[0, 0, 0, 0, 0, 0, 0, 0] -> 0
[1, 1, 1, 1, 1, 1, 1, 1] -> 1
[0, 1, 1, 1, 1, 0, 0, 0] -> 1
[0, 1, 1, 1, 0, 0, 1, 0] -> 2
I use itertools.groupby() to split the lists into groups, which gets me an iterator with the keys and groups, but I can't quite figure out how to get the number of groups of 1s specifically.
Obviously, I could iterate over the keys and count up with an if statement but I'm sure there's a better way.
In this specific case, having the keys 0 and 1, you can omit the k == 1 check and include the zeros in the sum.
sum(k for k, _ in groupby([0, 1, 1, 1, 0, 0, 1, 0])) -> 2
While writing the question, I found the following solution (which was obvious in retrospect).
run_count = sum(k == 1 for k, g in itertools.groupby(labels_sample))
I'm not sure if it's the best, but it works.
Not with groupby, but to possibly answer the "a better way", this appears to be faster:
def count_groups_of_ones(lst):
it = iter(lst)
count = 0
while 1 in it:
count += 1
0 in it
return count
Benchmark results for your four small lists:
3.72 ms with_groupby
1.76 ms with_in_iterator
And with longer lists (your lists multiplied by 1000):
984.32 ms with_groupby
669.11 ms with_in_iterator
Benchmark code (Try it online!):
def with_groupby(lst):
return sum(k for k, _ in groupby(lst))
def with_in_iterator(lst):
it = iter(lst)
count = 0
while 1 in it:
count += 1
0 in it
return count
from timeit import repeat
from itertools import groupby
from collections import deque
from operator import itemgetter, countOf
funcs = [
with_groupby,
with_in_iterator,
]
def benchmark(lists, number):
print('lengths:', *map(len, lists))
for _ in range(3):
for func in funcs:
t = min(repeat(lambda: deque(map(func, lists), 0), number=number)) / number
print('%6.2f ms ' % (t * 1e6), func.__name__)
print()
lists = [
[0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 1, 0],
]
for func in funcs:
print(*map(func, lists))
benchmark(lists, 10000)
benchmark([lst * 1000 for lst in lists], 40)
Another option that is more general:
def count_groups(lst, value):
start = object()
return sum((a is start or a != value) and b == value for a, b in zip([start] + lst, lst))
count_groups([0, 1, 1, 1, 0, 0, 1, 0], 1) # 2
If optimizing for speed over a long list, try adapting this answer that uses numpy:
def count_groups(lst, value):
return np.diff(np.array(lst) == value, prepend=False, append=False).sum() // 2

How to get the max index of distinct groups of integers in a python list

Example:
[0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
In this case I need:
1st '0' group = index: 0-4 , length : 5
1st '1' group = index: 5-6 , length : 2
2nd '0' group = index: 7 , length : 1
2nd '1' group = index: 8-17 , length : 10 <---- NEED THIS the index of max length of '1's
3rd '0' group = index: 18 - 22 , length : 5
I think you are looking for itertools.groupby. With this you can get a list of lists by each grouping of integers in the original dataset.
>>> data = [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
>>> [list(group) for _, group in itertools.groupby(data)]
[[0, 0, 0, 0, 0], [1, 1], [0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0,0, 0]]
Or to get indexes, you can also do this in one line using itertools.groupby and .islice and operator.itemgetter
>>> [sorted(set(itemgetter(0, -1)([i[0] for i in g))) for _, g in groupby(enumerate(data), key=itemgetter(1))]
[[0, 4], [5, 6], [7], [8, 17], [18, 22]]
Or to get the starting or ending indexes, use this: (notice min and max determine the start or end index)
>>> [min(i[0] for i in group) for _, group in groupby(data)]
[0, 5, 7, 8, 18]
>>> [max(i[0] for i in group) for _, group in groupby(data)]
[4, 6, 7, 17, 22]
And to get the starting index of the largest group use:
>>> max(([next(group)[0], sum(1 for _ in group)] for _, group in groupby(enumerate(data), key=itemgetter(1))), key=itemgetter(1))[0]
8
The standard library provides itertools.groupby for this purpose. It's a bit tricky to use, because it does a lot of work:
>>> from itertools import groupby
>>> data = [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
>>> groupby(data)
<itertools.groupby object at 0x0000015AB6EB3C78>
Hmm. It doesn't seem very useful yet. But we look at the documentation and see that it's a generator, so let's try expanding it into a list:
>>> list(groupby(data))
[(0, <itertools._grouper object at 0x0000015AB6EC2BA8>), (1, <itertools._grouper
object at 0x0000015AB6ED82B0>), (0, <itertools._grouper object at 0x0000015AB6E
D8518>), (1, <itertools._grouper object at 0x0000015AB6EFE780>), (0, <itertools.
_grouper object at 0x0000015AB6F028D0>)]
The 0 and 1 values in here correspond to the 0s and 1s in the original data, but we still have these other objects. Those are also generators:
>>> [(value, list(grouper)) for value, grouper in groupby(data)]
[(0, [0, 0, 0, 0, 0]), (1, [1, 1]), (0, [0]), (1, [1, 1, 1, 1, 1, 1, 1, 1, 1,
1]), (0, [0, 0, 0, 0, 0])]
Now we can see what's going on: the grouper objects generate chunks from the list.
So all we need to do is check the len of those lists and get the maximum value. We fix the comprehension so that we ignore the value and get the len of each grouper, and feed the results to the built-in max instead of making a list:
>>> max(len(list(grouper)) for value, grouper in groupby(data))
10
you can do it another way without itertools:
j=0
for i,val in enumerate(data):
if i == 0:
out=[[val]]
if val == data[i-1]:
out[j] += [val]
else:
j+=1
out += [[val]]
output:
[[0, 0, 0, 0, 0, 0], [1, 1], [0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0]]
now, make a dict with the unique values and the lengths of the sublists for each value:
counts = {}
for o in out:
if o[0] not in counts.keys():
counts[o[0]] = [len(o)]
else:
counts[o[0]] += [len(o)]
output:
{0: [6, 1, 5], 1: [2, 10]}
now get the max length of the sequences with the value you are after, in your case it's 1:
max(counts[1])
output:
10
EDIT : to also get the indices of this specific sequence you can do this:
id0 = 0
for o in out:
if o[0] != 1 or len(o) != max(counts[1]):
id0 += len(o)
if o[0] == 1 and len(o) == max(counts[1]):
id0 -= 1
break
id1 = id0 + max(counts[1]) - 1
print(max(counts[1]), id0, id1)
output:
10 8 17
it isnt the prettiest...but it works :)
You could iterate using the following function:
def count_through_a_list(x):
"""
returns all distinct continuous groups of values in a list
output is in the form of records
"""
# Initialize these values
group_start = 0
group_count = 1
prev = x[0]
groups = []
for i,n in enumerate(x):
# if n is not the same as the previous value OR i is the last index
if n!=prev or i == len(x)-1:
groups.append({'start':group_start, 'end':i-1, 'value':prev, 'length':i-group_start, 'group_counter':group_count})
# Reset the appropriate values
group_count+=1
group_start = i
prev = n
return groups
groups = count_through_a_list(x)
pd.DataFrame(groups, columns=['start','end','value', 'length', 'group_counter'])
start end value length group_counter
0 0 4 0 5 1
1 5 6 1 2 2
2 7 7 0 1 3
3 8 17 1 10 4
4 18 21 0 4 5

How to sum up all neighbouring nonzero value in the middle of a list in Python

I am beginner in Python. I need to solve an issue with summing up neighboring nonzero value in a list.
Say, I have list called
a = [2, 3, 0, 0, 1, 0, 3, 3, 1, 0, 0].
In a, there will be multiple instances where the elements are nonzero. For example, 2 and 3 are neigbouring so I want to sum them up so I would get 5.
Then, there another neighbouring elements which are 3, 3 and 1. This is where I face problem because i want to sum it up to 7.
But in the code that I attempted on, it still print out 4, which is the sum of a[7] and a[8]. Is there any way I can avoid this?
c =[]
for i in range(1, len(a)):
if a[i-1] != 0:
if a[i] != 0:
tot = a[i] + a[i-1]
c.append(tot)
if a[i+1] != 0:
tot = tot + a[i+1]
c.append(tot)
else:
tot = 0;
continue
You can use itertools.groupby and a comprehension:
>>> import itertools
>>> a = [2, 3, 0, 0, 1, 0, 3, 3, 1, 0, 0]
>>> [sum(v) for k, v in itertools.groupby(a, key=lambda x: x != 0) if k != 0]
[5, 1, 7]
You can also use a simple generator function:
def groups(d):
_sum = 0
for i in d:
if not i:
if _sum:
yield _sum
_sum = 0
else:
_sum += i
if _sum:
yield _sum
print(list(groups([2, 3, 0, 0, 1, 0, 3, 3, 1, 0, 0])))
Output:
[5, 1, 7]
As I understand, you want to calculate sub-sums of consecutive non-zero elements.
How about:
#!python3
from typing import List
def solve(arr: List[int]) -> List[int]:
ret = []
tmp = []
for elem in arr+[0]:
if elem != 0:
tmp.append(elem)
else:
if len(tmp):
ret.append(sum(tmp))
tmp = []
return ret
arr = [2, 3, 0, 0, 1, 0, 3, 3, 1, 0, 0]
assert(solve(arr) == [5, 1, 7])
The original list 'a' is [2, 3, 0, 0, 1, 0, 3, 3, 1, 0, 0] where the neighboring non-zero elements in list 'a' are highlighted in bold text.
a = [2, 3, 0, 0, 1, 0, 3, 3, 1, 0, 0]
def fun(a):
tot =[] # sub-list of list a
val=0
for i in a:
if i==0:
if val!=0:
tot.append(val)
val=0
else:
val+=i
return tot
print(fun(a))
# output [5, 1, 7]

How to remove mirrors reflections values of itertools.product function?

I create a cartesian product using the itertools.product function:
from itertools import product
a = list(map(list, itertools.product(list(range(2)), repeat=3)))
Output:
[[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]
Then I get rid of mirrors reflections in the following way:
b = []
for k, v in enumerate(a):
if v[::-1] not in a[:k]:
b.append(v[::-1])
Output:
[[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 1], [1, 1, 1]]
But can I get the same effect step by step without saving all the results of itertools.product in the list? For example, with the usual approach on the for loop:
for i in list(map(list, itertools.product(list(range(2)), repeat=3))):
# blah blah blah
Because ultimately I will use large cartesian products, at least repeat = 18. And that is why I have to give up the approach on the lists. Unless there is another way to do it? I will be grateful for any tips.
import itertools
l = (list(i) for i in itertools.product(tuple(range(2)), repeat=3) if tuple(reversed(i)) >= tuple(i))
print list(l)
Output:
[[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 1], [1, 1, 1]]
Here is an idea for a recursive algorithm to generate only the necessary combinations (as opposed to generate the whole Cartesian product and discarding the unnecessary ones):
def noReflections(n, k, current=None, idx=0, symmetric=True):
# n: number of distinct elements
# k: sequences length
# current: sequence being generated
# idx: current generated index
# symmetric: true if the chosen elements up to now are symmetric
assert n >= 0 and k >= 0
if n == 0 or k == 0:
return
if idx == 0:
current = k * [0]
if idx < k // 2:
# Choose the value for current position (idx) and symmetric (idx2)
idx2 = k - idx - 1
for i in range(n):
# Value for current position
current[idx] = i
# If all previously selected values were symmetric,
# the symmetric position must have a value equal or greater
# than the current; otherwise it can take any value.
first = i if symmetric else 0
for j in range(first, n):
# Value for symmetric position
current[idx2] = j
# Recursive call
# Only keep symmetric flag if previously selected values
# and the ones selected now are symmetric.
yield from noReflections(n, k, current, idx + 1, symmetric and (i == j))
elif idx == k // 2 and (k % 2 == 1):
# In middle position of odd-length sequence
# Make one sequence with each possible value
for i in range(n):
current[idx] = i
yield tuple(current)
else:
# Even-length sequence completed
yield tuple(current)
print(list(noReflections(2, 3)))
>>> [(0, 0, 0), (0, 1, 0), (0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1)]
I'm not sure this should perform better than the other answer though, because of the recursion and so on (in a couple of quick tests bot performed similarly in my machine).

Categories

Resources