Logical iteration over numpy array - python

Suppose that you have two equal-sized lists. First list contains only zeros and ones, and initial value of second list is equal to some fixed number. Other values of second array depends on same-indexed values of first list. Relation between them is that, if value in first list is equal to 0, same-indexed value of second list is equal to preceding one, in all other cases, is equal to some other value. In order to clarify my question, I've written the code below with help of for loop. What is the way to solve this like problem without for loop?
Code
a = np.array([0, 1, 0, 0, 0, 1, 0, 0, 1])
b = np.zeros_like(a)
b[0] = 5
for i in range(1, a.size):
if a[i] == 0:
b[i] = b[i-1]
else:
b[i] = np.random.randint(5)

Here's a vectorized approach -
offset = int(a[0]!=0)
N = (np.count_nonzero(a!=0)) - offset # no. of rand num to be generated
rand_num = np.append(5,np.random.randint(0,5,N))
out = rand_num[(a!=0).cumsum() - offset]

Related

Consecutive repeated identical elements

I have a list that will consist of 1s and 0s. An example is: [1,1,1,1,1,0,1,0,1,0,0,0,0,0,1,1,1]
I am trying to print the number of times there were consecutive 1s. The desired output is 2. What I have so far:
def span(test_list):
res = []
for idx in range(0, len(test_list) - 1):
# getting Consecutive elements
if test_list[idx] == test_list[idx + 1]:
if test_list[idx]!=0:
res.append(test_list[idx])
# getting count of unique elements
res = len(list(set(res)))
# printing result
print("Consecutive identical elements count : " + str(res))
This however returns 1, where as I need the answer to be 2. Any help will be appreciated.
Just for fun... a solution using itertools.groupby:
test_list = [1,1,1,1,1,0,1,0,1,0,0,0,0,0,1,1,1]
sum(g[0] == 1 and len(g) > 1 for g in [list(g) for _, g in groupby(test_list)])
Output:
2
This works by grouping the consecutive identical values in the list which yields:
[1, 1, 1, 1, 1]
[0]
[1]
[0]
[1]
[0, 0, 0, 0, 0]
[1, 1, 1]
We then sum all the cases where the first element in the group is 1 and the length of the group is > 1.
#JonClements came up with an interesting way of doing this using itertools.islice which avoids the list conversion:
sum(next(islice(g, 1, None), 0) for k, g in groupby(test_list))
This works by taking a slice of the resultant group starting at the second element (islice(g, 1, None)) and then attempts to read that element (next(..., 0)) returning 0 if there isn't one. Thus next(islice(g, 1, None), 0) will only return 1 if the group is at least two 1's long.
Inspired by #JonClements' comment, I also came up with:
sum(next(g) & next(g, 0) for _, g in groupby(test_list))
This works by bitwise and'ing the first and second (or 0 if there isn't a second) elements in each grouped list, so only produces a 1 value if the grouped list contains more than one 1.
Timing wise, on my computer this is the fastest solution, with #JonClements about 20% slower and my original solution another 20% slower again.
Your problem here is that you are trying to add to your result list the sequences of consecutive ones, but there will be nothing to separate and distinguish the different sequences. So your result will look something like this:
[1,1,1,1,1,1]
Which will be reduce to just 1 when you convert it to a set.
My proposition is to track only the beginning of each consecutive sequence of ones, and to sum it like this:
def span(test_list):
res = []
started = False
for idx in range(0, len(test_list) - 1):
if test_list[idx] == test_list[idx + 1]:
if test_list[idx]!=0 and not started:
started = True
res.append(test_list[idx])
else:
started = False
res = sum(res)
print("Consecutive identical elements count : " + str(res))
test_liste = [1,1,1,1,1,0,1,0,1,0,0,0,0,0,1,1,1]
span(test_liste)
Why don't you use a counter that you increment each time list(n+1) == list(n)?
It will be easier than having to add each time an element to a list and then at the end to get the length of your list.
Apart from that, the problem you have is a logic problem. When you check that element n+1 is similar to your element n and that your element n is 1, you add only one element to your list (initially empty) while you checked that you had two similar elements.
My advice is to first check that the first element you read is a 1.
If it is not, then you go to the next one.
If it is, then you increment your counter by 1 and check the next one. If the next one is 1 then you increment your counter by 1. And so on.
What about it ? :
def span(test_list):
res = 0
for idx in range(0, len(test_list) - 1):
# check that you have two consecutive 1
if test_list[idx] == 1 and test_list[idx + 1] == 1:
res += 1;
# As long as the following values are 1, idx is incremented without incrementing res
while test_list[idx +1] == 1: //
idx += 1;
print("Consecutive identical elements count : " + str(res))

Method for finding Length of intervals between 'True' values in a Boolean Array (Efficient method)

Say you have a boolean/binary array in Numpy called 'a', where 'a' is a sequence of 0s and 1s (or equivalently True/False). I want to find the distances between the 1s in 'a'.
Eg. a = [1,**0,0**,1,**0**,1,**0,0,0,0,0**,1]. Output = [2,1,5]
What is the most efficient way to evaluate this in Numpy Python? The actual dataset is of the order of 1,000,000 binary values.
a = [1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1]
output = []
i = 0
space = 0
for ii in a:
if a[i] == 1:
output.append(space)
space = 0
elif a[i] == 0:
space += 1
i += 1
print(output)
This gives a zero at the beginning, but otherwise is exactly what you want.
Here is a numpy way of getting the result:
np.diff(np.where(np.array(a)>0))-1

How to calculate numbers of "uninterrupted" repeats in an array in python?

I have a 0,1 numpy array like this:
[0,0,0,1,1,1,0,0,1,1,0,0,0,1,1,1,1,0,0,0]
I want to have a function that tells me number 1 is repeated 3,2,4 times in this array, respectively. Is there a simple numpy function for this?
This is one way to do it to find first the clusters and then get their frequency using Counter. The first part is inspired from this answer for 2d arrays. I added the second Counter part to get the desired answer.
If you find the linked original answer helpful, please visit it and upvote it.
from scipy.ndimage import measurements
from collections import Counter
arr = np.array([0,0,0,1,1,1,0,0,1,1,0,0,0,1,1,1,1,0,0,0])
cluster, freq = measurements.label(arr)
print (list(Counter(cluster).values())[1:])
# [3, 2, 4]
Assume you only have 0s and 1s:
import numpy as np
a = np.array([0,0,0,1,1,1,0,0,1,1,0,0,0,1,1,1,1,0,0,0])
# pad a with 0 at both sides for edge cases when a starts or ends with 1
d = np.diff(np.pad(a, pad_width=1, mode='constant'))
# subtract indices when value changes from 0 to 1 from indices where value changes from 1 to 0
np.flatnonzero(d == -1) - np.flatnonzero(d == 1)
# array([3, 2, 4])
A custom implementation?
def count_consecutives(predicate, iterable):
tmp = []
for e in iterable:
if predicate(e): tmp.append(e)
else:
if len(tmp) > 0: yield(len(tmp)) # > 1 if you want at least two consecutive
tmp = []
if len(tmp) > 0: yield(len(tmp)) # > 1 if you want at least two consecutive
So you can:
array = [0,0,0,1,1,1,0,0,1,1,0,0,0,1,1,1,1,0,0,0]
(count_consecutives(lambda x: x == 0, array)
#=> [3, 2, 4]
And also:
array = [0,0,0,1,2,3,0,0,3,2,1,0,0,1,11,10,10,0,0,100]
count_consecutives(lambda x: x > 1, array)
# => [2, 2, 3, 1]

Find Triplets smaller than a given number

I am trying to solve a problem where:
Given an array of n integers nums and a target, find the number of
index triplets i, j, k with 0 <= i < j < k < n that satisfy the
condition nums[i] + nums[j] + nums[k] < target.
For example, given nums = [-2, 0, 1, 3], and target = 2.
Return 2. Because there are two triplets which sums are less than 2:
[-2, 0, 1] [-2, 0, 3]
My algorithm: Remove a single element from the list, set target = target - number_1, search for doublets such that number_1 + number _2 < target - number_1. Problem solved.
The problem link is https://leetcode.com/problems/3sum-smaller/description/ .
My solution is:
def threeSumSmaller(nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
nums = sorted(nums)
smaller = 0
for i in range(len(nums)):
# Create temp array excluding a number
if i!=len(nums)-1:
temp = nums[:i] + nums[i+1:]
else:
temp = nums[:len(nums)-1]
# Sort the temp array and set new target to target - the excluded number
l, r = 0, len(temp) -1
t = target - nums[i]
while(l<r):
if temp[l] + temp[r] >= t:
r = r - 1
else:
smaller += 1
l = l + 1
return smaller
My solution fails:
Input:
[1,1,-2]
1
Output:
3
Expected:
1
I am not getting why is the error there as my solution passes more than 30 test cases.
Thanks for your help.
One main point is that when you sort the elements in the first line, you also lose the indexes. This means that, despite having found a triplet, you'll never be sure whether your (i, j, k) will satisfy condition 1, because those (i, j, k) do not come from the original list, but from the new one.
Additionally: everytime you pluck an element from the middle of the array, the remaining part of the array is also iterated (although in an irregular way, it still starts from the first of the remaining elements in tmp). This should not be the case! I'm expanding details:
The example iterates 3 times over the list (which is, again, sorted and thus you lose the true i, j, and k indexes):
First iteration (i = 0, tmp = [1, -2], t = 0).
When you sum temp[l] + temp[r] (l, r are 0, 1) it will be -1.
It satisfies being lower than t. smaller will increase.
The second iteration will be like the first, but with i = 1.
Again it will increase.
The third one will increase as well, because t = 3 and the sum will be 2 now.
So you'll count the value three times (despite only one tuple can be formed in order of indexes) because you are iterating through the permutations of indexes instead of combinations of them. So those two things you did not take care about:
Preserving indexes while sorting.
Ensuring you iterate the indexes in a forward-fashion only.
Try like this better:
def find(elements, upper_bound):
result = 0
for i in range(0, len(elements) - 2):
upper_bound2 = upper_bound - elements[i]
for j in range(i+1, len(elements) - 1):
upper_bound3 = upper_bound2 - elements[j]
for k in range(j+1, len(elements)):
upper_bound4 = upper_bound3 - elements[k]
if upper_bound4 > 0:
result += 1
return result
Seems like you're counting the same triplet more than once...
In the first iteration of the loop, you omit the first 1 in the list, and then increase smaller by 1. Then you omit the second 1 in the list and increase smaller again by 1. And finally you omit the third element in the list, -2, and of course increase smaller by 1, because -- well -- in all these three cases you were in fact considering the same triplet {1,1,-2}.
p.s. It seems like you care more about correctness than performance. In that case, consider maintaining a set of the solution triplets, to ensure you're not counting the same triplet twice.
There are already good answers , Apart that , If you want to check your algorithm result then you can take help of this in-built funtion :
import itertools
def find_(vector_,target):
result=[]
for i in itertools.combinations(vector_, r=3):
if sum(i)<target:
result.append(i)
return result
output:
print(find_([-2, 0, 1, 3],2))
output:
[(-2, 0, 1), (-2, 0, 3)]
if you want only count then:
print(len(find_([-2, 0, 1, 3],2)))
output:
2

Python list comprehension for identifying and modifying a sequence

I have a method which iterates over a list of numbers, and identifies for sequences of 0, non-zero, 0 and then 'normalizes' the value inbetween to 0.
Here is my code:
for index in range(len(array)-2):
if array[index] == 0 and array[index + 1] != 0 and array[index + 2] == 0:
array[index + 1] = 0
This currently works fine, and I have further methods to detect sequences of 0, nz, nz, 0 etc.
I've been looking into list comprehensions in Python, but having trouble figuring out where to start with this particular case. Is it possible to do this using list comprehension?
From the comments and advice given, it seems that my original code is the most simplest and perhaps most efficient way of performing the process. No further answers are necessary.
You might try something like
new_array = [ 0 if (array[i-1] == array[i+1] == 0)
else array[i]
for i in range(1,len(array)-1) ]
# More readable, but far less efficient
array = array[0] + new_array + array[-1]
# More efficient, but less readable
# array[1:-1] = new_array
I've adjusted the range you iterate over to add some symmetry to the condition, and take advantage of the fact that you don't really need to check the value of array[i]; if it's 0, there's no harm in explicitly setting the new value to 0 anyway.
Still, this is not as clear as your original loop, and unnecessarily creates a brand new list rather than modifying your original list only where necessary.
Not everything should be a comprehension. If you wish to be torturous though:
def f(a):
[a.__setitem__(i + 1, 0) for i, (x, y, z) in enumerate(zip(a, a[1:], a[2:]))
if x == z == 0 and y != 0]
Then
>>> a = [1, 2, 0, 1, 0, 4]
>>> f(a)
>>> a
[1, 2, 0, 0, 0, 4]

Categories

Resources