Checking if two points are connected in boolean 2D list - python

Is there any quick way to find out, if two points on 2D boolean area are connected and you can mobe only up, down, left and right on a square with value True?
Let's say you would have following 6x6 2D list:
In code, that would be:
bool2DList = [6][6]
bool2DList = { True, True, False, False, True, True,
False, True, True, False, True, True,
False, True, True, False, False, True,
False, False, False, False, False, True,
False, False, False, False, True, True,
False, True, True, True, True, True }
Green squares have value True and blue ones False. I was thinking about function( it would probably need to be recursive ), in which you would put a 2D list as a argument alongside with a list of tuples ( coordinates ) of several points and finaly one tuple of special point, it could have header like this:
def FindWay( bool2DList,listOfPoints,specialPointCoord )
In this example the special point would be the point P with coordinates 5;1. Let's imagine you would start walking from that special points. What points could you reach without stepping on the blue squares? In this example, only points P4 and P5 ( the output could be let's say the coordinates of those points, so 0;5 and 5;3 ). It would probably need to be recursive, but I have no idea, how the body should look like.
Thank you.

I'm afraid there is no trivial way to do this. It's a graph traversal problem, and Python doesn't have built-in functions supporting that. I expect that you'll want a simple implementation of a breadth-first graph search.
Very briefly, you keep a list of nodes you've visited, but not handled; another list of nodes you've handled. The steps look like this:
handled = []
visited = [P]
while visited is not empty:
remove a node A from the visited list
for each node B you can reach directly from A:
if B is new (not in visited or handled list):
put B on the visited list
put A on the handled list
This will find all nodes you can reach. If you're worried about a particular node, then inside the loop, check to see whether B is your target node. When you put B on the visited list, put it on the front for depth-first, on the back for breadth-first.
In this application, "all the nodes you can reach" consists of the bordering ones with the same Boolean label.

Here is an option how you can code it:
A = np.array([[0, 1, 1, 0], [1, 0, 1, 1], [1, 0, 1, 0], [0, 1, 0, 0]]).astype(bool)
print A
[[False True True False]
[ True False True True]
[ True False True False]
[False True False False]]
We can upgrade standard dfs function for our needs:
def dfs_area(A, start):
graph = {}
res = np.zeros_like(A)
for index, x in np.ndenumerate(A):
graph[index] = x
visited, stack = set(), [start]
while stack:
vertex = stack.pop()
x, y = vertex[0], vertex[1]
if vertex not in visited and graph[vertex] == True:
visited.add(vertex)
if x < A.shape[0]-1:
stack.append((x+1, y))
if x > 1:
stack.append((x-1, y))
if y < A.shape[1]-1:
stack.append((x, y+1))
if y > 1:
stack.append((x, y-1))
res[tuple(np.array(list(visited)).T)] = 1
return res
Assume that we want points connected to (1, 2) - second row, third value:
mask = dfs_area(A, (1,2))
>> mask
array([[0, 1, 1, 0],
[0, 0, 1, 1],
[0, 0, 1, 0],
[0, 0, 0, 0]])

Related

Find intervals of true values in vector

I am looking for a quick way to find the start and end indexes of each "block" of consecutive trues in a Vector.
Both julia or python would do the job for me. I'll write my example in julia syntax:
Say I have a vector
a = [false, true, true, true, false, true, false, true, true, false]
what I want to get is something like this (with 1-based indexing):
[[2, 4], [6, 6], [8, 9]]
The exact form/type of the returned value does not matter, I am mostly looking for a quick and syntactically easy solution. Single trues surrounded by falses should also be detected, as given in my example.
My use-case with this is that I want to find intervals in a Vector of data where the values are below a certain threshold. So I get a boolean array from my data where this is true. Ultimately I want to shade these intervals in a plot, for which I need the start and end indeces of each interval.
function intervals(a)
jumps = diff([false; a; false])
zip(findall(jumps .== 1), findall(jumps .== -1) .- 1)
end
Quick in terms of keystrokes, maybe not in performance or readability :)
My use-case with this is that I want to find intervals in a Vector of data where the values are below a certain threshold.
Let's say your vector is v and your threshold is 7:
julia> println(v); threshold
[9, 6, 1, 9, 5, 9, 4, 5, 6, 1]
7
You can use findall to get the indices where the value is below the threshold, and get the boundaries from that:
julia> let start = 1, f = findall(<(threshold), v), intervals = Tuple{Int, Int}[]
for i in Iterators.drop(eachindex(f), 1)
if f[i] - f[i - 1] > 1
push!(intervals, (f[start], f[i - 1]))
start = i
end
end
push!(intervals, (f[start], last(f)))
end
3-element Vector{Tuple{Int64, Int64}}:
(2, 3)
(5, 5)
(7, 10)
Here's a version that avoids running findall first, and is a bit faster as a consequence:
function intervals(v)
ints = UnitRange{Int}[]
i = firstindex(v)
while i <= lastindex(v)
j = findnext(v, i) # find next true
isnothing(j) && break
k = findnext(!, v, j+1) # find next false
isnothing(k) && (k = lastindex(v)+1)
push!(ints, j:k-1)
i = k+1
end
return ints
end
It also returns a vector of UnitRanges, since that seemed a bit more natural to me.
try this:
a = [False, True, True, True, False, True, False, True, True, False]
index = 0
foundTrue = False
booleanList = []
sublist = []
for i in a:
index += 1
if foundTrue:
if i == False:
foundTrue = False
sublist.append(index-1)
booleanList.append(sublist)
sublist = []
else:
if i == True:
foundTrue = True
sublist.append(index)
print(booleanList)
output should be: [[2, 4], [6, 6], [8, 9]]
This iterates in the a list and when it finds a True it marks a flag (foundTrue) and stores its index on sublist. Now with the maked flag (foundTrue), if it finds a False, then we store the previous index from that False into sublist, appends it to the booleanList and resets sublist.
This is not the shortest but very fast without using any find functions.
function find_intervals(v)
i = 0
res = Tuple{Int64, Int64}[]
while (i+=1) <= length(v)
v[i] || continue
s = f = i
while i < length(v) && v[i+=1]
f = i
end
push!(res, (s,f))
end
res
end
For a = [false, true, true, true, false, true, false, true, true, false], it gives:
find_intervals(a)
3-element Vector{Tuple{Int64, Int64}}:
(2, 4)
(6, 6)
(8, 9)

How to count groups of certain value in a numpy 1d array?

I have a numpy 1d arrays with boolean values, that looks like
array_in = np.array([False, True, True, True, False, False, True, True, False])
This arrays have different length. As you can see, there are parts, where True values located next to each other, so we have groups of Trues and groups of Falses. I want to count the number of True groups. For our case, we have
N = 2
I tried to do some loops with conditions, but it got really messy and confusing.
You can use np.diff to determine changes between groups. By attaching False to the start and the end of this difference calculation we make sure that True groups at the start and end are properly counted.
import numpy as np
array_in = np.array([False, True, True, True, False, False, True, True, False, True, False, True])
true_groups = np.sum(np.diff(array_in, prepend=False, append=False))//2
#print(true_groups)
>>>4
If you don't want to write loops and conditions, you could take a shortcut by looking at this like a connected components problem.
import numpy as np
from skimage import measure
array_in = np.array([False, True, True, True, False, False, True, True, False])
N = max(measure.label(array_in))
When an array is passed into the measure.label() function, it treats the 0 values in that array as the "background". Then it looks at all the non-zero values, finds connected regions, and numbers them.
For example, the label output on the above array will be [0, 1, 1, 1, 0, 0, 2, 2, 0]. Naturally, then doing a simple max on the output gives you the largest group number (here it's 2) -- which is also the same as the number of True groups.
A straightforward way of finding the number of groups of True is by counting the number of False, True sequences in the array. With a list comprehension, that will look like:
sum([1 for i in range(1, len(x)) if x[i] and not x[i-1]])
Alternatively you can convert the initial array into a string of '0's and '1's and count the number of '01' occurences:
''.join([str(int(k)) for k in x]).count('01')
In native numpy, a vectorised solution can be done by checking how many times F changes to T sequentially.
For example,
np.bitwise_and(array_in[1:], ~array_in[:-1]).sum() + array_in[0]
I am computing a bitwise and of every element of the array with it's previous element after negating the previous element. However, in doing so, the first element is ignored, so I am adding that in manually.

I don't understand how they are initializing current solution. Could someone explain what this does?

I don't understand how a boolean can by multiplied by a length. I'm fairly new to coding
def __init__(self, capacity, items):
self.currentSolution = [False]*len(items)
The notation [value] * number builds a list containing value at each index, with a length of number
Example
[False]*2 => [False, False]
[False]*10 => [False, False, False, False, False, False, False, False, False, False]
When you multiply a list by N it's actually creates a new list composed of N original lists.
Let me give you an example. When we'll use the following command:
[1, 2, 3] * 2
We'll get the following list:
[1, 2, 3, 1, 2, 3, 1, 2, 3]
So performing [False]*len(items) will actually create a list with the len of len(items) which every is False.
Another way to do the same thing could be:
[False for _ in range(len(items))]

Finding consecutive values in a multidimensional array - python

I'm completely new to python really. I need some help with multidimensional arrays. I am making a seat booking system for a theatre. (just practice). I have set up a 5 row by 10 seat matrix. And all I really need to be able to do is assign 'available' or 'unavailable' to each seat. I can do this obviously, but what I have no idea how to do is be able to search or loop through a row to see if there are 6 seats available next to each other. Any help would be great. Bear in mind I am completely new to python.
The simplest way I can think of is to iterate over the rows, keeping track of the row number as index.
Then, we count available seats until we find six in a row or until we encounter an unavailable seat (we reset the count if this happens).
seats = [[True, True, True, False, True, True, True, False, False, True],
[True, True, True, True, True, True, True, False, False, True],
[True, True, True, False, True, True, True, False, False, True],
[True, True, True, False, True, True, True, False, False, True],
[True, True, True, True, True, True, True, False, False, True]]
for index, row in enumerate(seats):
consecutive_seats = 0
for seat in row:
if seat:
consecutive_seats += 1
if consecutive_seats >= 6:
print('There are at least six seats available on row', index)
break
else:
consecutive_seats = 0
Further explanation
The python enumerate function allows you to iterate over the sequence of seats, giving you back an index and the current item at that index. Optionally, you can pass it a parameter to set the starting index (so if you want your seat rows to start from one, you could use the following instead:
for index, row in enumerate(seats, start=1): ...
In fact, the details of what's happening here are interesting: enumerate returns a two-item Tuple (think of it as an immutable – unchangeable – list) which you are unpacking to index and row. You can then use these two variables just like any others.
For each pair of index and row, you iterate over the row and check if seat is True (you can, but shouldn't, write seat == True – it's redundant information). If it is True, you consider it available and increase the counter for available consecutive seats by one.
Immediately thereafter, you need to check if you've found enough free seats, in which case you can break out of the loop, in other words, you're skipping the rest of the seats in the row because you already know enough of them are free, and continuing with the next iteration of the outer loop, which will yield the next row index and row.
If, on the other hand, the seat is False (unavailable), you reset the count of consecutive available seats to zero but you continue checking the rest of the row.
Suggested improvements
Make a class Seat and give it an attribute is_available
Introduce constants to get rid of magic numbers
Assuming you have a seat arrangement similar to below
seats=[ [1, 0, 0, 0, 1, 1, 1, 0, 1, 0],
[0, 0, 0, 1, 1, 0, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 0, 1, 0, 0, 1],
[0, 0, 1, 0, 0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]]
Instead of maintaining as a nested list, my suggestion is to maintain a list of strings. Searching a pattern (here sequence of '0's or '1's or even something complex like middle seat or end seat) is faster and easier through string search. Even for complex searches you can employ regex
Suggested Data Structure
seats=[ '1000111010',
'0001101110',
'0011101001',
'0010000011',
'0000000100']
Now to search for consecutive 6 empty seats , you have to search '0'*6 something like
>>> any('0'*6 in row for row in seats)
True
>>> next((i,row.index('0'*6)) for i,row in enumerate(seats) if '0'*6 in row )
(4, 0)
Explanation
next((i,row.index('0'*6)) for i,row in enumerate(seats) if '0'*6 in row ) returns the first item from the generator expression. Assuming you know about the built-in
enumerate: Returns a list of tuples of (index , element)
The expression can be equivalently written as
for i,row in enumerate(seats):
if '0'*6 in row
print (i,row.index('0'*6)
break
any('0'*6 in row for row in seats) can be equivalently written as
def search(seats):
for row in seats:
if '0'*6 in row:
return True
return False
you may consider to use sparse matrix.
and Iterate it like that:
https://stackoverflow.com/a/4319159/1031417

Python: Mapping with Index of Previous Occurrence

I'm often met with an analog of the following problem, and have had trouble writing clean code to solve it. Usually, I have something involving a temporary variable and a for loop, but is there a more elegant way?
Suppose I have a list of booleans or values which evaluate to booleans:
[True, False, True, False, False, True]
How would I map this to a list of values, with the index of the previous True, inclusive?
[0, 0, 2, 2, 2, 5]
[EDIT] Have tried something along the lines of:
def example(lst):
rst, tmp = [], None
for i in range(len(lst)):
if lst[i]:
tmp = i
rst.append(tmp)
return rst
Assuming the first element of the list is always True.
While it still uses a for loop and a temporary variable, it's still relatively clean, I think. If you want, you could replace the yield and append to a list and return that.
def get_indexes(booleans):
previous = 0
for index, b in enumerate(booleans):
if b:
previous = index
yield previous
>>> b = [True, False, True, False, False, True]
>>> list(get_indexes(b))
[0, 0, 2, 2, 2, 5]
This is even shorter (although potentially less readable):
def get_indexes(booleans):
previous = 0
for index, b in enumerate(booleans):
previous = index if b else previous
yield previous
Try this:
index = 0
bools = [True, False, True, False, False, True]
result = []
for i in range(len(bools)):
index = i if bools[i] else index
result.append(index)
Not tested, but should work.
[i if b else i-lst[i::-1].index(True) for i,b in enumerate(lst)]

Categories

Resources