Compare neighbouring cells in a 2d array - python-3.x

Suppose this is a 3X3 matrix and I need to find the number of elements that are greater than their neighbours.
[[1 2 7],
[4 5 6],
[3 8 9]]
Neighbours are those cells whose corners touch each other.
1 has neighbours 2,4,5.
2 has neighbours 1,7,4,5,6.
7 has 2,5,6.
5 has 1,2,7,4,6,3,8,9 and so on.

This problem can be solved in two steps/functions: 1) get_neighbors(matrix, r, c), and 2) compare_neighbors(matrix). In the 2nd function, compare_neighbors we just call get_neighbors and passing all coordinates by leveraging itertools.product.
# code snippet:
from itertools import product
def get_neighbors(matrix, r, c):
sum((row[c -(c>0): c+2]
for row in matrix[r -(r>0):r+2]), []) # sum() beats itertools.chain()
vals.remove(grid[r][c]) # rm itself.
return set(vals) # keep distinct nums. ONLY
def compare_neighbors(matrix):
ROW, COL = len(matrix), len(matrix[0])
result = []
for x, y in product(range(ROW), range(COL)):
current = matrix[x][y]
all_nums = get_neighbors(matrix, x, y)
if all(x < current for x in all_nums):
result.append(current)
return result
Program running:
grid = [[1, 5, 4, 9],
[2, 6, 3, 2],
[8, 3, 6, 3],
[5, 4, 7, 1]]
matrix = [[1, 2, 7],
[4, 5, 6],
[3, 8, 9]]
print(f' {compare_neighbors(matrix)} ') # [7, 9]
print(f' {compare_neighbors(grid) } ') # [9, 8, 7]

You need to tackle different problems:
ensure you got data that fits the problemstatement - a matrix of strings f.e. does not work nor does a non-quadratic data input
get the actual neighbouring indexes based on a potential neighbourhood
check all neighbours for bigger then testing-index
You can get the dimensions directly from the provided data (after assertioning it conforms to some base rules) and provide a generic check based on this like so:
# different neighbourhood tuples
neigh8 = tuple((a,b) for a in range(-1,2) for b in range(-1,2))
neigh4 = tuple((a,b) for (a,b) in neigh8 if a or b)
def assertions(data):
"""Check if data is list of lists and all dims are same.
Check if all elements are either ints or floats.
Exit with exception if not."""
assert isinstance(data, list)
outer_dim = len(data)
for inner in data:
assert outer_dim == len(inner), f"Inner element not of len {outer_dim}: {inner}"
assert isinstance(inner, list), f"Inner element not list: {inner}"
allNumbers = all(isinstance(i, (int, float)) for i in inner)
assert allNumbers, f"Not all elements ints or floats: {inner}"
return outer_dim
def test_surrounded_by_lower_numbers(data, idx, n_idx):
"""Test one element at 'idx' in 'data' for a given neighbourhood 'n_idx'
and return True if surrounded only by smaller numbers."""
def get_idx(data, idx, n_idx):
"""Get all indexes that conform to the given neighbourhood and are
not identical to idx nor out of bounds."""
n = []
for (a,b) in n_idx:
# identical to input idx
if (idx[0]+a , idx[1]+b) == idx:
continue
# out of bounds
if idx[0]+a < 0 or idx[1]+b < 0:
continue
if idx[0]+a >= len(data) or idx[1]+b >= len(data):
continue
n.append( (idx[0]+a , idx[1]+b ))
return n
value = data[idx[0]][idx[1]]
n = get_idx(data, idx, n_idx)
# check if all are smaller as the current value
return all (data[a][b] < value for a,b in n)
def test_matrix(matrix, n_idx = neigh8):
"""Check all matrix values for given neighbourhood. Output alle values that are
surrounded only by strictly smaller values."""
print()
for i in matrix:
for n in i:
print(f"{float(n):>5.2f} ".replace(".00"," "), end=" ")
print()
print()
dim = assertions(matrix)
for (a,b) in ((a,b) for a in range(dim) for b in range(dim)):
if test_surrounded_by_lower_numbers(matrix,(a,b)):
print(f"{(a,b)} = {matrix[a][b]} is biggest.")
Program:
# 3 x 3
test_matrix( [[1, 2, 7], [4, 5, 6], [3, 8, 9]] )
# 5 x 5
test_matrix([[1, 2, 7, 11, 9],
[4, 5, 6, -2, .5],
[9.1, 3,99.99, 8, 9.7],
[1,2,3,4,5],
[40,50,60,70,80]])
Output for 3x3 testcase:
1 2 7
4 5 6
3 8 9
(0, 2) = 7 is biggest.
(2, 2) = 9 is biggest.
Output for 5x5 testcase:
1 2 7 11 9
4 5 6 -2 0.50
9.10 3 99.99 8 9.70
1 2 3 4 5
40 50 60 70 80
(0, 3) = 11 is biggest.
(2, 0) = 9.1 is biggest.
(2, 2) = 99.99 is biggest.
(2, 4) = 9.7 is biggest.
(4, 4) = 80 is biggest.

Related

I want an efficient way of comparing columns of data to make a decision

I have a CSV file that I read using pandas. I would like to make a comparison between some of the columns and then use the outcome of the comparison to make a decision. An example of the data is shown below.
A
B
C
D
6
[5, 3, 4, 1]
-4.2974843
[-5.2324843, -5.2974843, -6.2074043, -6.6974803]
2
[3, 6,4, 7]
-6.4528433
[-6.2324843, -7.0974845, -7.2034041, -7.6974804]
3
[6, 2, 4, 5]
-3.5322451
[-4.3124440, -4.9073840, -5.2147042, -6.1904800]
1
[4, 3, 6,2]
-5.9752843
[-5.2324843, -5.2974843, -6.2074043, -6.6974803]
7
[2, 3, 4, 1]
-1.2974652
[-3.1232843, -4.2474643, -5.2074043, -6.1994802]
5
[1, 3, 7, 2]
-9.884843
[-8.0032843, -8.0974843, -9.2074043, -9.6904603]
4
[7, 3, 1, 4]
-2.3984843
[-7.2324843, -8.2094845, -9.2044013, -9.7914001]
Here is the code I am using:
n_A = data['A']
n_B = data['B']
n_C = data['C']
n_D = data['D']
result_compare = []
for w, e in enumerate(n_A):
for ro, ver in enumerate(n_B):
for row, m in enumerate(n_C):
for r, t in enumerate(n_D):
if ro==w:
if r ==row:
if row==ro:
if r==0:
if t[r]>m:
b = ver[r]
result_compare.append(b)
else:
b = e
result_compare.append(b)
elif r>=0:
q = r-r
if t[q]>m:
b = ver[q]
result_compare.append(b)
else:
b = e
result_compare.append(b)
I had to select only the columns required for the comparison and that was why I did the following.
n_A = data['A']
n_B = data['B']
n_C = data['C']
n_D = data['D']
Results could be as:
result_compare = [6, 3 , 3, 4, 7 , 1, 4 ]
The values in D are arranged in descending order which is why the first element of the list is selected in this case. So when the first element in the row of the list D is greater than the one of C, we choose the first element of the list B, otherwise A. I would like an efficient way since my code takes lots of time to provide results most especially in the case of large data.
I would do this in your case
data['newRow']=data.apply(lambda row: row["B"][0] if row["D"][0] > row["C"] else row['A'], axis=1)
And if you need it as a list by the end:
list(data['newRow'])

Python return specific items from list in same line

I have a list of items from which I need to separate items with specific "key". Let's say I need all items that follow "X" -> the list may look like this: Y1 1-2 X1 3-5 Z1 6-8, Y2 3-5 X2 5-7 Z2 5-9 so I need to take the X "values" that are 3-5 and 5-7. These should be returned this way: 3 4 5 and 5 6 7 and on their own lines so that they can be used in another functions.
I have also tried taking "X"s to its own dictionary but the problem is still the same. I also know about end="" but it does not help me with this.
def get_x_values(list_parameter):
list_of_items = []
list_of_x = []
for i in list_parameter:
i = i.split(' ')
for item in i:
if item != '':
list_of_items.append(item)
for item, next_item in zip(list_of_items, list_of_items[1:]):
if item == 'X':
list_of_x.append(next_item)
for x in list_of_x:
for i in range(int(x[0]), int(x[-1]) + 1):
yield i
When I loop the yield values trough, I get the X values like this:
3
4
5
5
6
7
When I need them this way:
3 4 5
5 6 7
Any help appreciated.
I modified you code, so that it will work.
def get_x_values(list_parameter):
list_of_items = []
for i in list_parameter:
i = i.split(' ')
for item in i:
if item != '':
list_of_items.append(item)
for item, next_item in zip(list_of_items, list_of_items[1:]):
if item == 'X':
range_list = list(range(int(next_item[0]), int(next_item[-1]) + 1))
yield " ".join(str(number) for number in range_list)
lst = ["Y 1-2 X 3-5 Z 6-8", "Y 3-5 X 5-7 Z 5-9"]
result = get_x_values(lst)
for x in result:
print(x)
However, this is not the most elegant solution. But I guess it's easier to understand for you as it's pretty close to your own attempt.
I hope it helps you. Let me know if there are any questions left. Have a nice day!
You need to
split your list (you got it)
put key and value together (you use zip, I use a dict comprehension for that)
split your values into numbers and convert to int
create a range from your int-converted values to fill in missing numbers
for example like so:
# there is a pesky , in your string, we strip it out
inp = "Y1 1-2 X1 3-5 Z1 6-8, Y2 3-5 X2 5-7 Z2 5-9"
formatted_input = [a.rstrip(",") for a in inp.split(" ")]
print(formatted_input)
# put keys and values together and convert values to int-list
as_dict = {formatted_input[a]:list(map(int,formatted_input[a+1].split("-")))
for a in range(0,len(formatted_input),2)}
print(as_dict)
# create correct ranges from int-list
as_dict_ranges = {key:list(range(a,b+1)) for key,(a,b) in as_dict.items()}
print(as_dict_ranges)
# you could put all the above in a function and yield the dict-items from here:
# yield from as_dict_ranges.item()
# and filter them for key = X.... outside
# filter for keys that start with X
for k,v in as_dict_ranges.items():
if k.startswith("X"):
print(*v, sep=" ") # decompose the values and print seperated onto one line
Outputs:
# formatted_input
['Y1', '1-2', 'X1', '3-5', 'Z1', '6-8', 'Y2', '3-5', 'X2', '5-7', 'Z2', '5-9']
# as_dict
{'Y1': [1, 2], 'X1': [3, 5], 'Z1': [6, 8],
'Y2': [3, 5], 'X2': [5, 7], 'Z2': [5, 9]}
# as_dict_ranges
{'Y1': [1, 2], 'X1': [3, 4, 5], 'Z1': [6, 7, 8],
'Y2': [3, 4, 5], 'X2': [5, 6, 7], 'Z2': [5, 6, 7, 8, 9]}
# output for keys X...
3 4 5
5 6 7
You can omit one list conversion if you do not want to print the map(int, ...) values:
as_dict = {formatted_input[a]:map(int,formatted_input[a+1].split("-"))
for a in range(0,len(formatted_input),2)}
Documentation:
range()
map
str.split()
str.rstrip()

Is there a way to find the sum of the number after the ending of 9?

The question is:
Return the sum of the numbers in the array, except ignore sections of numbers starting with a 6 and extending to the next 9 (every 6 will be followed by at least one 9).
sixty_nine([4, 5, 6, 7, 8, 9]) --> 9
sixty_nine([4, 5,7,8, 6, 7, 8, 9,34]) --> it should return -- 58
but it returns -- 24(by ignoring 34)
i am not able to understand how to take the sum of numbers after 9
arr = [4, 5,7,8, 6, 7, 8, 9,34]
def sixty_nine(arr):
sums = 0
l = len(arr)
for i in range(l):
if arr[i] == 6 and 9 in arr[i:]:
for j in arr[0:i]:
sums = sums + j
return sums
sixty_nine(arr)
t=The output of this program is --- 24
You code contains various errors, among them you are returning inside your for-loop which prevents it from traversing the whole list.
I suggest treating this problem in two steps. First write a generator which filters out sublists of the form [6, ..., 9].
Then use sum which can sum any iterable, generators included.
def ignore_between(lst, a, b):
i = 0
while i < len(lst):
if lst[i] == a:
try:
i = lst.index(b, i) + 1
continue
except ValueError:
pass
yield lst[i]
i += 1
lst = [4, 5, 7, 8, 6, 7, 8, 9, 34]
output = sum(ignore_between(lst, 6, 9))
print(output) # 58

Returning the N largest values' indices in a multidimensional array (can find solutions for one dimension but not multi-dimension)

I have a numpy array X, and I'd like to return another array Y whose entries are the indices of the n largest values of X i.e. suppose I have:
a =np.array[[1, 3, 5], [4, 5 ,6], [9, 1, 7]]
then say, if I want the first 5 "maxs"'s indices-here 9, 7 , 6 , 5, 5 are the maxs, and their indices are:
b=np.array[[2, 0], [2 2], [ 2 1], [1 1], [0 , 2])
I've been able to find some solutions and make this work for a one dimensional array like
c=np.array[1, 2, 3, 4, 5, 6]:
def f(a,N):
return np.argsort(a)[::-1][:N]
But have not been able to generate something that works in more than one dimension. Thanks!
Approach #1
Get the argsort indices on its flattened version and select the last N indices. Then, get the corresponding row and column indices -
N = 5
idx = np.argsort(a.ravel())[-N:][::-1] #single slicing: `[:N-2:-1]`
topN_val = a.ravel()[idx]
row_col = np.c_[np.unravel_index(idx, a.shape)]
Sample run -
# Input array
In [39]: a = np.array([[1,3,5],[4,5,6],[9,1,7]])
In [40]: N = 5
...: idx = np.argsort(a.ravel())[-N:][::-1]
...: topN_val = a.ravel()[idx]
...: row_col = np.c_[np.unravel_index(idx, a.shape)]
...:
In [41]: topN_val
Out[41]: array([9, 7, 6, 5, 5])
In [42]: row_col
Out[42]:
array([[2, 0],
[2, 2],
[1, 2],
[1, 1],
[0, 2]])
Approach #2
For performance, we can use np.argpartition to get top N indices without keeping sorted order, like so -
idx0 = np.argpartition(a.ravel(), -N)[-N:]
To get the sorted order, we need one more round of argsort -
idx = idx0[a.ravel()[idx0].argsort()][::-1]

Make specific elements of numpy matrix as 0

I have got index of 0 elements of Numpy Matrix (M) using:
index_array = numpy.argwhere(M == 0)
Now, I want to make these index elements (index present in index_array) as 0 in other matrix B. Is there any numpy way to do this?
For eg : index_array contains
[[2 1]
[4 4]]
, so make element present at (2,1) and (4,4) in Matrix B as 0.
You should have used np.where which returns a tuple of row and col index and thus can be used as indexing directly, instead of argwhere, so long as the index is not out of bound for B, you can do:
B[np.where(M == 0)] = 0
Example:
M = np.array([[1,2],[3,0],[0,1]])
M
#array([[1, 2],
# [3, 0],
# [0, 1]])
B = np.array([[1,2,3],[4,5,6],[7,8,9]])
B
#array([[1, 2, 3],
# [4, 5, 6],
# [7, 8, 9]])
B[np.where(M == 0)] = 0
B
#array([[1, 2, 3],
# [4, 0, 6],
# [0, 8, 9]])
If you want to stick to np.argwhere, you can get the row index and col index respectively and then do the assignment:
index_array = np.argwhere(M == 0)
B[index_array[:,0], index_array[:,1]] = 0

Resources