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.
In the python multiprocessing library, do processes spawned via Pool only have access to global variables bound at the time of Pool construction?
Why is this?
This appears to be the case, based on this experiment.
This code:
from multiprocessing import Pool
x = 0
class MyClass:
def get_x(self, i):
global x
return x
def foo():
global x
p = Pool(5)
for i in range(3):
x = i
c = MyClass()
print(list(p.imap(c.get_x, range(10*i, 10*i+10))))
foo()
produces the 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]
Whereas moving the Pool construction inside the loop, i.e.
from multiprocessing import Pool
x = 0
class MyClass:
def get_x(self, i):
global x
return x
def foo():
global x
for i in range(3):
p = Pool(5)
x = i
c = MyClass()
print(list(p.imap(c.get_x, range(10*i, 10*i+10))))
foo()
produces the output
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
(Note that my motivation for wanting to do this is that x is a large pandas DataFrame in my real application, that needs to be read, but not modified, by several processes.)
My two questions are:
Is it merely impossible to rebind x once the processes are created?
Or do the processes each make a copy of x, and modification is impossible?
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
A = [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0]
B = 9
I want to find out the distances between the index[9] (B) and each of it's closest's 1's. For example, If we look at list A, we see that index 9 is this:
A = [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0]
^
I would like to figure out the distances between B(index 9) and it's nearest 1's. For example, the nearest 1's in this case would be this:
A = [1, 0, 0, 0, 1, 0, 0, 1, 0, B, 0, 0, 1, 0]
^ ^
So in this case the output would be:
>> [2, 3] ##Since the distance between 1, 0, B is 2 and the distance between B, 0, 0, 1 is 3.
So far I've come up with the following code:
def checkDistance(A, B):
for i in A:
if i == 1:
#Check if it is the closest to B
#Check distance
Sadly I'm a beginner in python and I am struggling to finish. Any help would be much appreciated :)
def distance(lst,index):
c=[i for i,j in enumerate(lst) if j==1]
for k,l in zip(c[:-1],c[1:]):
if k < index < l:
return [index-k, l-index]
a = [1, 0, 0, 0, 1, 0, 0, 1, 0, B, 0, 0, 1, 0]
b = 9
distance(a, b)
Out: [2, 3]
You could use the following function. In this case, to make the function more abstract, you needn't force the value for the comparison to be 1 in the function.
In the function below, you do a for loop starting at the position you specified (in Python indexes start at 0, not at 1) and finishing when the list finishes.
The if statement compares element with the value of the list at a given position i
def checkDistance(lst,index,element):
counter = 0
results = []
for i in range(index,len(lst)):
if lst[i] == element:
print("Detected element at distance: " + str(counter))
results.append(counter)
counter += 1
return results
The idea is to print name_1, name_2 or name_3 by random.
list = [['name_1', 0, 0, 0], ['name_2', 0, 0, 0], ['name_3', 0, 0, 0]]
random.choice(list)
There is a nested list so if I use random.choice(list) the result will be...
'name_1', 0, 0, 0
or
'name_2', 0, 0, 0
or
'name_3', 0, 0, 0
... however I want 'name_1', 'name_2' or 'name_3' only.
Thanks for answer. However I have another issue.
If I have the same list...
list = [['name_1', 0, 0, 0], ['name_2', 0, 0, 0], ['name_3', 0, 0, 0]]
... How can I make a 'for' loop for 'name' elements only (so list[0][0], list[1][0] and list [2][0])?
Currently, random.choice(list) will return the whole sublist (eg ['name_2', 0, 0, 0]. You know the name are at the start of the sublist, so you can just get the 0th index of random.choice's ouput:
random.choice(list)[0]
# example output: 'name_1'
We need to select only the first item of the sublists at random, so we make a list out of the first items and do random.choice on that list.
Or we can just choose the first element once we choose a random sublist via
random.choice
import random
list = [['name_1', 0, 0, 0], ['name_2', 0, 0, 0], ['name_3', 0, 0, 0]]
#Option 1
print(random.choice([item[0] for item in list]))
#Option 2
print(random.choice(list)[0])