This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 2 years ago.
I'm trying to make the input maze of Astar algorithm(alogorithm to find the shortest path between start and destination and there can be some blockages within the maze,
which takes input a maze representing blockages only, as shown below).
From the GUI using the Click1 command in each button,
I intend to get an output like this(where I inserted a blockage at [3][2]).
1 represents blockage which is to avoided to find the path from start to end.
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
but I get a output as following,I can't understand why it's blocking the same column of each and every row
[[0, 1, 0, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0]]
I created the maze in the init(): of class App() using this:
def __init__(self, master,dimension,indexes):
self.maze=[[0]*self.dimension]*self.dimension
this entire thing is within a class App():
for creating the grid of buttons, and storing their reference
self.gid = []
for i in range(self.dimension):
row = []
Grid.rowconfigure(self.frame1, i + 1, weight=3)
for j in range(self.dimension):
Grid.columnconfigure(self.frame1, j + 1, weight=3)
btn=Button(self.frame1,command=lambda i=i, j=j: self.Click1(i, j))
btn.grid(sticky=N+S+E+W,padx=2,pady=2,ipadx=1,ipady=1)
row.append(btn)
row[-1].grid(row=i + 1, column=j+1)
self.gid.append(row)
the Click1 method/Command that also within this class:
def Click1(self, i, j):
self.indxes.append((i,j))
if len(self.indxes)==1:
self.gid[i][j]["bg"]="blue" #indicates start
elif len(self.indxes)==2:
self.gid[i][j]["bg"]="green" #indicates destinations
else:
self.gid[i][j]["bg"] = "black"
self.maze[i][j] = 1 #how I insert blockage within the maze
Try this in your init:
def __init__(self, master,dimension,indexes):
self.maze = [[0] * self.dimension] for _ in range(self.dimension)]
The latter * self.dimension call was assigning the same reference to all your inner lists (dimension number of times) - meaning when one is changed all will change.
This creates a unique list for each sublist
Related
I am now using lists to represent the graph, which would be similar to previous question. I found out that the dict approach would be very long and complex, so decided to go with the list approach. But I am still facing a few roadblocks.
So for example, the graph:
is now represented as:
nodes = ["1", "2", "3", "4", "5"]
edges = [
[0, 2, 1, 2, 0],
[1, 0, 1, 0, 0],
[0, 2, 0, 0, 0],
[1, 0, 1, 0, 2],
[1, 2, 0, 0, 0],
]
Here, edge weights can only be 1 or 2 and 0 represents no edge from one node to other. The edges are directed, so every list in the matrix represents the edges coming toward the node.
Similar to the last question, I want all possible two-edge modifications on the graph. So, for example, if we add an edge from node "4" to "5" with weight of 1, and remove the edge with weight 1 coming from node "1" to "4", the new graph will look like:
edges = [
[0, 2, 1, 2, 0],
[1, 0, 1, 0, 0],
[0, 2, 0, 0, 0],
[0, 0, 1, 0, 2],
[1, 2, 0, 1, 0],
]
and this is one of the possible modifications.
I want to build a generator that can create all such modifications sequentially and pass it to me so that I can use them to test.
My code so far is like this:
def all_modification_generation(graph: list[list], iter_count: int = 0):
possible_weights = {-1, 0, 1}
node_len = len(graph)
for i in range(node_len**2):
ix_x = i // node_len
ix_y = i % node_len
if i == ix_y:
continue
for possible_pertubs in possible_weights - {graph[ix_x][ix_y]}:
graph[ix_x][ix_y] = possible_pertubs
if iter_count == 0:
all_modification_generation(graph=graph, iter_count=iter_count + 1)
else:
yield all_modification_generation(graph=graph)
My logic is, once I do one change, I can then loop over all other elements that come after it in the matrix. So this problem could be solved recursively. And once a node is explored, we do not need to take it into consideration for next loops, because it will just give us a duplicate result that we have already found. And because I need to check for 2 modifications, I am increasing iter_count after first iteration and then yielding the next time. I am skipping ix_x == ix_y cases because a self-looping edge does not make any sense in this context, so that change is not required to be recorded.
But even then, this does not output any result. What am I doing wrong? Any help is appreciated, thanks!
Edit: I think I have figured out a way to do the double modification without repetitive generation of modified matrices. Now the only problem is that there is quite a bit of code repetition and a 4-level nested for-loop.
I'm not sure how to call a generator recursively, but I feel that should be the way to go! Thanks J_H for pointing me to the right direction.
The working code is:
def all_modification_generation(graph: list[list]):
possible_weights = {-1, 0, 1}
node_len = len(graph)
for i in range(node_len**2):
ix_x1 = i // node_len
ix_y1 = i % node_len
if ix_x1 == ix_y1:
continue
for possible_pertubs in possible_weights - {graph[ix_x1][ix_y1]}:
cc1_graph = deepcopy(graph)
cc1_graph[ix_x1][ix_y1] = possible_pertubs
for j in range(i + 1, node_len**2):
ix_x2 = j // node_len
ix_y2 = j % node_len
if ix_x2 == ix_y2:
continue
for possible_perturbs2 in possible_weights - {cc1_graph[ix_x2][ix_y2]}:
cc2_graph = deepcopy(cc1_graph)
cc2_graph[ix_x2][ix_y2] = possible_perturbs2
yield cc2_graph
The quadratic looping is an interesting technique.
We do wind up with quite a few repeated
division results, from // node_len, but that's fine.
I had a "base + edits" datastructure in mind for this problem.
Converting array to list-of-lists would be straightforward.
After overhead, a 5-node graph consumes 25 bytes -- pretty compact.
Numpy offers good support for several styles of sparse
graphs, should that become of interest.
from typing import Generator, Optional
import numpy as np
class GraphEdit:
"""A digraph with many base edge weights plus a handful of edited weights."""
def __init__(self, edge: np.ndarray, edit: Optional[dict] = None):
a, b = edge.shape
assert a == b, f"Expected square matrix, got {a}x{b}"
self.edge = edge # We treat these as immutable weights.
self.edit = edit or {}
#property
def num_nodes(self):
return len(self.edge)
def __getitem__(self, item):
return self.edit.get(item, self.edge[item])
def __setitem__(self, item, value):
self.edit[item] = value
def as_array(g: GraphEdit) -> np.ndarray:
return np.array([[g[i, j] for j in range(g.num_nodes)] for i in range(g.num_nodes)])
def all_single_mods(g: GraphEdit) -> Generator[GraphEdit, None, None]:
"""Generates all possible single-edge modifications to the graph."""
orig_edit = g.edit.copy()
for i in range(g.num_nodes):
for j in range(g.num_nodes):
if i == j: # not an edge -- we don't support self-loops
continue
valid_weights = {0, 1, 2} - {g[i, j]}
for w in sorted(valid_weights):
yield GraphEdit(g.edge, {**orig_edit, (i, j): w})
def all_mods(g: GraphEdit, depth: int) -> Generator[GraphEdit, None, None]:
assert depth >= 1
if depth == 1:
yield from all_single_mods(g)
else:
for gm in all_single_mods(g):
yield from all_mods(gm, depth - 1)
def all_double_mods(g: GraphEdit) -> Generator[GraphEdit, None, None]:
"""Generates all possible double-edge modifications to the graph."""
yield from all_mods(g, 2)
Here's the associated test suite.
import unittest
from numpy.testing import assert_array_equal
import numpy as np
from .graph_edit import GraphEdit, all_double_mods, all_single_mods, as_array
class GraphEditTest(unittest.TestCase):
def setUp(self):
self.g = GraphEdit(
np.array(
[
[0, 2, 1, 2, 0],
[1, 0, 1, 0, 0],
[0, 2, 0, 0, 0],
[1, 0, 1, 0, 2],
[1, 2, 0, 0, 0],
],
dtype=np.uint8,
)
)
def test_graph_edit(self):
g = self.g
self.assertEqual(5, self.g.num_nodes)
self.assertEqual(2, g[0, 1])
g[0, 1] = 3
self.assertEqual(3, g[0, 1])
del g.edit[(0, 1)]
self.assertEqual(2, g[0, 1])
def test_non_square(self):
with self.assertRaises(AssertionError):
GraphEdit(np.array([[0, 0], [1, 1], [2, 2]]))
def test_all_single_mods(self):
g = GraphEdit(np.array([[0, 0], [1, 0]]))
self.assertEqual(4, len(list(all_single_mods(g))))
expected = [
np.array([[0, 1], [1, 0]]),
np.array([[0, 2], [1, 0]]),
np.array([[0, 0], [0, 0]]),
np.array([[0, 0], [2, 0]]),
]
for ex, actual in zip(
expected,
map(as_array, all_single_mods(g)),
):
assert_array_equal(ex, actual)
# Now verify that original graph is untouched.
assert_array_equal(
np.array([[0, 0], [1, 0]]),
as_array(g),
)
def test_all_double_mods(self):
g = GraphEdit(np.array([[0, 0], [1, 0]]))
self.assertEqual(16, len(list(all_double_mods(g))))
expected = [
np.array([[0, 0], [1, 0]]),
np.array([[0, 2], [1, 0]]),
np.array([[0, 1], [0, 0]]),
np.array([[0, 1], [2, 0]]),
np.array([[0, 0], [1, 0]]), # note the duplicate
np.array([[0, 1], [1, 0]]),
np.array([[0, 2], [0, 0]]), # and it continues on in this vein
]
for ex, actual in zip(
expected,
map(as_array, all_double_mods(g)),
):
assert_array_equal(ex, actual)
def test_many_mods(self):
self.assertEqual(40, len(list(all_single_mods(self.g))))
self.assertEqual(1_600, len(list(all_double_mods(self.g))))
self.assertEqual(1_600, len(list(all_mods(self.g, 2))))
self.assertEqual(64_000, len(list(all_mods(self.g, 3))))
self.assertEqual(2_560_000, len(list(all_mods(self.g, 4))))
One could quibble about the fact that
it produces duplicates, since inner and outer loops
know nothing of one another.
It feels like this algorithm wants to use an
itertools.combinations
approach, generating all modifications in lexicographic order.
Below is my code.
check = [[0] * 4] * 2
check[0][1] = 1
print(check)
check2 = [[0, 0, 0, 0], [0, 0, 0,0]]
check2[0][1] = 1
print(check2)
result:
[[0, 1, 0, 0], [0, 1, 0, 0]]
[[0, 1, 0, 0], [0, 0, 0, 0]]
I expect check and check2 get same result but it is different.
why check[0][1] and check[1][1] are changed?? not only check[0][0]
check is a shallow copy
check = [[0] * 4] * 2 - Creates only one list [0,0,0,0] and rows 1 and 2 refer to this list.
So changing the elements of one row reflects changes in every other row.
To avoid such a scenario you can use a for-loop to create the list like this
check = [[0 for _ in range(4)] for _ in range(2)]
Now, this will be a deep copy and not shallow copy.
check2 is a deep copy.
check2 = [[0, 0, 0, 0], [0, 0, 0,0]] - Creates two separate lists [0,0,0,0]. Row 1 and Row 2 refers to different copies of [0,0,0,0]
So changing the elements of one row will not reflect any changes in the other row.
Please read this SO answer to fully understand the concepts. https://stackoverflow.com/a/37830340
Expected Output:
indenitiy_matrix(3)
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Actual Output with Error:
indenitiy_matrix(3)
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
def identity_matrix(n):
list_template = [[]]
list_n = list_template*n
for sub_l in list_n:
sub_l.append(0)
for val in range(n):
# I have the feeling that the problem lies somewhere around here.
list_n[val][val]=1
return(list_n)
list_template*n does not create n copies, instead but all those n copies reference to only one copy. For example see this
a = [[0,0,0]]*2
# Now, lets change first element of the first sublist in `a`.
a[0][0] = 1
print (a)
# but since both the 2 sublists refer to same, both of them will be changed.
Output:
[[1, 0, 0], [1, 0, 0]]
Fix for your code
def identity_matrix(n):
list_n = [[0]*n for i in range(n)]
for val in range(n):
list_n[val][val]=1
return list_n
print (identity_matrix(5))
Output:
[[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 1]]
No, the problem lies here:
list_template = [[]]
list_n = list_template*n
After this, try doing:
list_n[0].append(1) # let's change the first element
The result:
[[1], [1], [1], [1], [1]]
is probably not what you expect.
Briefly, the problem is that after its construction, your list consists of multiple references to same list. A detailed explanation is at the link given by #saint-jaeger : List of lists changes reflected across sublists unexpectedly
Finally, the numpy library is your friend for creating identity matrices and other N-dimensional arrays.
How do I find if a sequence of numbers exists in a two-dimensional list? i.e.
matrix: [[1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [0,
0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0]]
if [1,1,1] in matrix:
print("It's in there")
else: print("It's not there")
I guess I could turn every int into a string but is there a slicker way?
Using an iterator over each cell of the matrix, I've managed to get a basic idea of what you wanted to achieve in Python script.
matrix = [[1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0]]
matchCount = 0
lastNumber = None
for cell in matrix:
for number in cell:
if number == 1 and lastNumber == 1 or lastNumber == None:
matchCount += 1
if matchCount >= 3:
print("MATCH in cell " + str(cell))
lastNumber = number
matchCount = 0
lastNumber = None
What happens is, it steps into the cell. It it's the first iteration then allow entry into our iterator. We don't know if it's a match list yet, so push it back in our little list.
Stepping over and over, if we get enough matches in a row, then wonderful! Print we found a match in our matrix's cell!
I'm trying to create a game board using lists, but when I'm printing the board it prints way too many lists.
from random import randint
import pprint
class Gameboard:
def __init__(self, rows, columns):
self._rows = rows
self._columns = columns
self._board = []
for i in range(rows):
row = [0] * columns
self._board.append(row)
def drawBoard(self):
for i in range(self._rows):
for j in range(self._columns):
pprint.pprint(self._board)
Main program:
from gameboard import Gameboard
import pprint
def main():
myObject = Gameboard(7, 7)
myObject.drawBoard()
main()
My goal is to create a board that consists of (in this case) 7 lists with 7 elements within a list.
When I run the program now, it prints about 7*49 lists or something. What must I change to only print out one list with 7 lists within?
You could use an inline for loop to generate a list of "[]" strings and then iterate that print function however many times you needed, like the below:
import pprint
for i in range(7):
pprint.pprint(["[]" for j in range(7)])
By using .join() on the list you could also concatenate every element in the list into one long string, which looks arguably better.
import pprint
for i in range(7):
pprint.pprint(' '.join(["[]" for j in range(7)]))
Keep it simple:
def drawBoard(self):
print(*self._board, sep='\n')
OUTPUT
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]