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
Related
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.
Let's say I have a Numpy array truth array that looks something like the following:
truths = [True, False, False, False, True, True]
and I have another array of values that looks something like:
nums = [1, 2, 3]
I want to create a loop that will replace all the truth values in the truths array with the next number from the nums array and replace all the False values with 0.
I want to end up with something that looks like:
array = [1, 0, 0, 0, 2, 3]
I would recommend numpy.putmask(). Since we're converting from type bool to int64, we need to do some conversions first.
First, initialization:
truths = np.array([ True, False, False, False, True, True])
nums = np.array([1, 2, 3])
Then we convert and replace based on our mask (if element of truth is True):
truths = truths.astype('int64') # implicitly changes all the "False" values to 0
numpy.putmask(truths, truths, nums)
The end result:
>>> truths
array([1, 0, 0, 0, 2, 3])
Note that we just pass in truths into the "mask" argument of numpy.putmask(). This will simply check to see if each element of array truths is truthy; since we converted the array to type int64, it will replace only elements that are NOT 0, as required.
If we wanted to be more pedantic, or needed to replace some arbitrary value, we would need numpy.putmask(truths, truths==<value we want to replace>, nums) instead.
If we want to go EVEN more pedantic and not make the assumption that we can easily convert types (as we can from bool to int64), as far as I'm aware, we'd either need to make some sort of mapping to a different numpy.array where we could make that conversion. The way I'd personally do that is to convert my numpy.array into some boolean array where I can do this easy conversion, but there may be a better way.
You can use cycle from itertools to cycle through your nums list. Then just zip it with your booleans and use a ternary list comprehension.
from itertools import cycle
>>> [num if boolean else 0 for boolean, num in zip(truths, cycle(nums))]
[1, 0, 0, 0, 2, 3]
You could use itertools here as you said you want a loop.
from itertools import cycle, chain, repeat
import numpy as np
truths = np.array([True, False, False, False, True, True])
nums = np.array([1, 2, 3])
#you have 2 options here.
#Either repeat over nums
iter_nums = cycle(nums)
#or when nums is exhausted
#you just put default value in it's place
iter_nums = chain(nums, repeat(0))
masked = np.array([next(iter_nums) if v else v for v in truths])
print(masked)
#[1, 0, 0, 0, 2, 3]
Okay, so this is a bit of both Python 2.7, and Ren'Py, so bear with me (I'm rusty, so I might just be doing something incredibly stupid)
I have an input:
input default "0" length 20 value VariableInputValue('playstore_search')
This goes on to run a function to check for matches in (currently one) nested list:
if playstore_search.strip():
$ tempsearch = playstore_search.strip()
text tempsearch:
color "#000"
yalign .5 # this is just temporary to show me what the tempsearch looks like
$ realtimesearchresult = realtime_search(tempsearch,playstore_recommended)
if realtimesearchresult:
text "[realtimesearchresult]":
color "#000"
yalign .6
This goes on to call this function:
def realtime_search(searchterm=False,listname=False):
if searchterm and listname:
indices = [i for i, s in enumerate(listname) if searchterm in s]
if indices:
return indices
And, this is a modified list of what it searches:
default playstore_recommended = [
['HSS','Studio Errilhl','hss'],
['Making Movies','Droid Productions','makingmovies'],
['Life','Fasder','life'],
['xMassTransitx','xMTx','xmasstransitx'],
['Parental Love','Luxee','parentallove'],
['A Broken Family','KinneyX23','abrokenfamily'],
['Inevitable Relations','KinneyX23','inevitablerelations'],
['The DeLuca Family','HopesGaming','thedelucafamily'],
['A Cowboy\'s Story','Noller72','acowboysstory']
]
Now, if I search for hss, it'll find that - and if I search for makingmovies it'll find that - however, if I search for droid (or Droid as it isn't case-insensitive currently) it won't find anything.
So, this is at least a twofold question:
1. How do I make this whole thing case-insensitive
2. How do I make it match partial strings
EDIT:
Okay, so stuff is now sort of working. However, there are still some issues. The complete list to match against is quite a bit more complex than what was posted above, and it seems that it doesn't match on string hits "in the middle of a string" - just on the first word. So, if I have something like this:
[
['This is a test string with the words game and move in it'],
['This is another test string, also containing game']
]
and I search for "game", one would expect two results. But I get 0. If, however, I search for "this", I get two results.
I recommend converting the entries in the nested-list to lowercase first and then search for the term using find(). Consider following function:
myListOfLists = [
['HSS','Studio Errilhl','hss'],
['Making Movies','Droid Productions','makingmovies'],
['Life','Fasder','life'],
['xMassTransitx','xMTx','xmasstransitx'],
['Parental Love','Luxee','parentallove'],
['A Broken Family','KinneyX23','abrokenfamily'],
['Inevitable Relations','KinneyX23','inevitablerelations'],
['The DeLuca Family','HopesGaming','thedelucafamily'],
['A Cowboy\'s Story','Noller72','acowboysstory']
]
searchFor = 'hss'
result = [ [ l.lower().find(searchFor) == 0 for l in thisList ] for thisList in myListOfLists ]
Using above code, value of result is:
[[True, False, True],
[False, False, False],
[False, False, False],
[False, False, False],
[False, False, False],
[False, False, False],
[False, False, False],
[False, False, False],
[False, False, False]]
If you wish to find just one boolean value from the entire list of lists do:
any([any(r) for r in result])
If you use searchFor = 'droid', it should return True as well.
To find index of True, I recommend using where command from numpy
import numpy as np
idx = np.where(result)
For example, searchFor = 'life', value of idx will be:
(array([2, 2], dtype=int64), array([0, 2], dtype=int64))
To find index without using numpy (not as elegant):
indices = [ [idx if val else -1 for idx, val in enumerate(r) ] for r in result ]
This will give positive values corresponding to index where match occurs, else will give -1.
[[-1, -1, -1],
[-1, -1, -1],
[0, -1, 2],
[-1, -1, -1],
[-1, -1, -1],
[-1, -1, -1],
[-1, -1, -1],
[-1, -1, -1],
[-1, -1, -1]]
Hope that helps!
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]])
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)]