Adding to list a class instance - python

I'm implementing a code to find the shortest path between two nodes, but
why when I change the first line of the DFS function the output change too .
Isn't it true that
path += [start] is equivalent to path = path + [start]?
the output before changing is ::
Current DFS path: 0
Current DFS path: 0->1
Current DFS path: 0->1->2
Current DFS path: 0->1->2->3
Current DFS path: 0->1->2->3->4
Current DFS path: 0->1->2->3->5
Current DFS path: 0->1->2->4
Current DFS path: 0->2
Current DFS path: 0->2->3
Current DFS path: 0->2->3->1
Current DFS path: 0->2->3->4
Current DFS path: 0->2->3->5
Current DFS path: 0->2->4
shortest path is 0->2->3->5
after changing is ::
Current DFS path: 0
Current DFS path: 0->1
Current DFS path: 0->1->2
Current DFS path: 0->1->2->3
Current DFS path: 0->1->2->3->4
Current DFS path: 0->1->2->3->4->5
shortest path is 0->1->2->3->4->5
The code ::
class Node(object):
def __init__(self, name):
"""Assumes name is a string"""
self.name = name
def getName(self):
return self.name
def __str__(self):
return self.name
class Edge(object):
def __init__(self, src, dest):
"""Assumes src and dest are nodes"""
self.src = src
self.dest = dest
def getSource(self):
return self.src
def getDestination(self):
return self.dest
def __str__(self):
return self.src.getName() + '->' + self.dest.getName()
class WeightedEdge(Edge):
def __init__(self, src, dest, weight = 1.0):
"""Assumes src and dest are nodes, weight a number"""
self.src = src
self.dest = dest
self.weight = weight
def getWeight(self):
return self.weight
def __str__(self):
return self.src.getName() + '->(' + str(self.weight) + ')'\
+ self.dest.getName()
#Figure 12.8
class Digraph(object):
#nodes is a list of the nodes in the graph
#edges is a dict mapping each node to a list of its children
def __init__(self):
self.nodes = []
self.edges = {}
def addNode(self, node):
if node in self.nodes:
raise ValueError('Duplicate node')
else:
self.nodes.append(node)
self.edges[node] = []
def addEdge(self, edge):
src = edge.getSource()
dest = edge.getDestination()
if not (src in self.nodes and dest in self.nodes):
raise ValueError('Node not in graph')
self.edges[src].append(dest)
def childrenOf(self, node):
return self.edges[node]
def hasNode(self, node):
return node in self.nodes
def __str__(self):
result = ''
for src in self.nodes:
for dest in self.edges[src]:
result = result + src.getName() + '->'\
+ dest.getName() + '\n'
return result[:-1] #omit final newline
class Graph(Digraph):
def addEdge(self, edge):
Digraph.addEdge(self, edge)
rev = Edge(edge.getDestination(), edge.getSource())
Digraph.addEdge(self, rev)
#Figure 12.9
def printPath(path):
"""Assumes path is a list of nodes"""
result = ''
for i in range(len(path)):
result = result + str(path[i])
if i != len(path) - 1:
result = result + '->'
return result
def DFS(graph, start, end, path, shortest, toPrint = False):
"""Assumes graph is a Digraph; start and end are nodes;
path and shortest are lists of nodes
Returns a shortest path from start to end in graph"""
path = path + [start]
if toPrint:
print('Current DFS path:', printPath(path))
if start == end:
return path
for node in graph.childrenOf(start):
if node not in path: #avoid cycles
if shortest == None or len(path) < len(shortest):
newPath = DFS(graph, node, end, path, shortest,
toPrint)
if newPath != None:
shortest = newPath
return shortest
def shortestPath(graph, start, end, toPrint = False):
"""Assumes graph is a Digraph; start and end are nodes
Returns a shortest path from start to end in graph"""
return DFS(graph, start, end, [], None, toPrint)
#Figure 12.10
def testSP():
nodes = []
for name in range(6): #Create 6 nodes
nodes.append(Node(str(name)))
g = Digraph()
for n in nodes:
g.addNode(n)
g.addEdge(Edge(nodes[0],nodes[1]))
g.addEdge(Edge(nodes[1],nodes[2]))
g.addEdge(Edge(nodes[2],nodes[3]))
g.addEdge(Edge(nodes[2],nodes[4]))
g.addEdge(Edge(nodes[3],nodes[4]))
g.addEdge(Edge(nodes[3],nodes[5]))
g.addEdge(Edge(nodes[0],nodes[2]))
g.addEdge(Edge(nodes[1],nodes[0]))
g.addEdge(Edge(nodes[3],nodes[1]))
g.addEdge(Edge(nodes[4],nodes[0]))
sp = shortestPath(g, nodes[0], nodes[5])
print('Shortest path found by DFS:', printPath(sp))
Note :: this code is from this book enter link description here

They are not the same
path += [start] is equivalent to path.extend([start]) -- it mutates path.
On the other hand
path = path + [start] creates a new list and names it start.
Consider the following experiment, and note the IDs:
>>> a = [1]
>>> id(a)
55937672
>>> a += [2,3]
>>> id(a)
55937672
>>> b = [1]
>>> id(b)
55930440
>>> b = b + [1,2]
>>> id(b)
55937288
The ID of b changed but the ID of a didn't.
As to why it makes a difference in your code -- DFS is a function. In the version which uses path += [start], you are modifying the passed parameter path -- and this modification persists after the call returns. On the other hand, in the version which uses path = path + [start], you are creating a new local variable named path, one which goes out of scope when the call returns, without any changes to the parameter path.

In line
path=path+[start]
you create new list object.
In line
path+=[start]
you modify list object that already exists.
You can try this:
path2=path[:]
path2+=[start]

Related

Discrepancy of list append in python

I am getting a different result when I am using append(path) vs. append(list(path))
I have the following code to find all paths for a sum:
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def find_paths(root, sum):
allPaths = []
dfs(root, sum, [], allPaths)
return allPaths
def dfs(root, sum, path, res):
if not root:
return
path.append(root.val)
if root.val == sum and root.left is None and root.left is None:
res.append(path)
dfs(root.left, sum - root.val, path, res)
dfs(root.right, sum - root.val, path, res)
del path[-1]
def main():
root = TreeNode(12)
root.left = TreeNode(7)
root.right = TreeNode(1)
root.left.left = TreeNode(4)
root.right.left = TreeNode(10)
root.right.right = TreeNode(5)
sum = 23
print("Tree paths with sum " + str(sum) +
": " + str(find_paths(root, sum)))
main()
This has the following output:
Tree paths with sum 23: [[], []]
But if I change the res.append(path) to res.append(list(path)) which will then return the correct answer Tree paths with sum 23: [[12, 7, 4], [12, 1, 10]]. I am confused on why using the list operation would change the answer.
res.append(path) appends the object path itself to the list res. After that line, when you modify the path object (like del path[-1]), the modification is also applied to the appended object in res, because, well, they are the same object.
list(path), on the other hand, "copies" the path. So this one is now a different object from path. When you modify path after that, the modification does not propagates to this different object.
You will have the same result if you do path[:] or path.copy() instead of list(path).
res.append(path) appends the actual path object, not a copy of it. So if path changes later on, the change will appear in res also.
res.append(list(path)) appends a copy.

Store all paths from start to end node using BFS

I have the implemetation of BFS but it stores only one path. How can I modify this code to store all paths from a starting to node to the end. Any ideas?
def BFS(G, user1, user2):
path = []
for v in G:
v.setDistance(0)
v.setPred(None)
vertQueue = Queue()
vertQueue.enqueue(G.getVertex(user1))
while vertQueue.size() > 0:
currentVert = vertQueue.dequeue()
for nbr in currentVert.getConnections():
if nbr.getColor() == 'white':
nbr.setColor('gray')
nbr.setDistance(currentVert.getDistance() + 1)
nbr.setPred(currentVert)
vertQueue.enqueue(nbr)
currentVert.setColor('black')
prev = G.getVertex(user2)
while prev.getPred():
path.append(prev.getPred().getId())
prev = prev.getPred()
path = path[::-1]
path.append(user2)
return ' -> '.join(path)

How to use returned value from a function

I have a function that finds all paths through a graph. The function returns a list of all paths. How do I use this value later in my code?
def findpaths(attachednodes,startID,endID,path = []):
path = path + [startID]
if startID == endID:
return [path]
if startID not in attachednodes:
return []
paths = []
for n in attachednodes[startID]:
if n not in path:
newpath = findpaths(attachednodes,n,endID,path)
for new in newpath:
paths.append(new)
for i in range(len(paths)):
numflight = i
flight = paths[i]
flights.update({numflight: flight})
return paths
you put the function call to the right side of a variable assignment. The variable will have the return value:
e.g.
def some_function():
return 10
x = some_function()
print(x) # will print 10

How to return a list of lists in recursive function in Python

I am playing around with some toy code in Python. But somehow cant get through. I am using a recursion in a Tree data structure to generate paths from a particular node to each children leaf nodes.
The idea behind the recursive function is to have a list which would collect each path to the individual leaf node and then collect each paths in another list.
class Tree:
def __init__(self):
self._ancestors = []
self._store_nodes = {}
def add_node(self, node):
assert isinstance(node, Node)
self._store_nodes[node.name] = node
def get_c_path(self, node):
subpath = []
path = []
path = self.ret_path(node, subpath, path)
return path
## recursive function to fetch paths
def ret_path(self, node, subpath=[], pathstore=[]):
if len(node.children) == 0:
pathstore.append(subpath)
return
else:
for c in node.children:
subpath.append(c)
self.ret_path(c, subpath, pathstore)
class Node(object):
def __init__(self, name=''):
self._name = name
self._children = set([])
self._parents = set([])
#property
def name(self):
return self._name
#property
def children(self):
return self._children
#property
def parents(self):
return self._parents
def add_child(self, node):
assert isinstance(node, Node)
self._children.add(node)
def add_parent(self, node):
assert isinstance(node, Node)
self._parents.add(node)
if __name__ == '__main__':
node_store = {1 : [2,3,4,5,6], 6 : [7,2,8,9,5], 2 : [10,11,5], 12 : [13,14,15,16], 5 : [21,22,23]}
tree = Tree()
## build the tree and set parents and children of each node
for k, v in node_store.items():
parent_node = None
if k in tree._store_nodes:
parent_node = tree._store_nodes[k]
else:
parent_node = Node(k)
tree.add_node(parent_node)
for c in v:
child_node = None
if c in tree._store_nodes:
child_node = tree._store_nodes[c]
else:
child_node = Node(c)
tree.add_node(child_node)
parent_node.add_child(child_node)
child_node.add_parent(parent_node)
print '-------------'
path = tree.get_c_path(tree._store_nodes[2])
for p in path:
for t in p:
print t.name
print "-----"
The result I am expecting is a list of list for Node-2 as follows:
path = [[10], [11], [5, 21], [5, 22], [5, 23]]
How can I correct my recursive function?
Here's two methods that would accomplish this goal. I'm not quite sure how to fix your structure; it seemed easier to start from scratch.
def get_c_path(self, node):
branches = [[c] for c in node.children]
expanded = self.expand_branches(branches)
while branches != expanded:
branches = expanded
expanded = self.expand_branches(expanded)
return expanded
def expand_branches(self, branches):
new_branches = []
for branch in branches:
last_node = branch[-1]
if last_node.children:
for child in last_node.children:
new_branches.append(branch + [child])
else:
new_branches.append(branch)
return new_branches

Python find next file by natural sort

I'm trying to find the next file by natural sorting with a depth variable but am facing some problems.
the folder structure is following:
tests/
------test1/
-----------test2/
----------------...
----------------30.jpg
----------------31.jpg
-----------test3/
----------------...
----------------30.jpg
----------------31.jpg
-----------1.jpg
------1.jpg
I want to reach the next or the item before my current item, iterating over them with the forward and backward function.
Getting items on the same level is working currently, also to get one on the max depth level.
For example I want to get with the backwards function on
path=tests/test1/test2/1.jpg
the result
tests/test1/1.jpg
but with
path=tests/test1/test3/1.jpg
the result
tests/test1/test2/31.jpg
obviously reversed results same with the forward functions.
My current problem is finding the next file on the next level without repeating myself and building a loop, iterating through the folders worked completely fine so far, but I'm currently completely stuck on this one.
My current code so far:
import os
import re
import wx
class PathSelect(wx.App):
"""
path select application
"""
def __init__(self):
"""
initializing function
:return:
"""
super(PathSelect, self).__init__()
#staticmethod
def ask_path():
"""
ask for our starting path
:return:
"""
wildcard = ("Image Files (*.*)|*.jpeg;*.jpg;*.png;*.bmp|"
"Joint Photographic Experts Group (*.jpeg;*.jpg)|*.jpeg;*.jpg|"
"Portable Network Graphics (*.png)|*.png|"
"Bitmap (*.bmp)|*.bmp|"
"All files (*.*)|*.*")
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", wildcard, wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if dialog.ShowModal() == wx.ID_OK:
return dialog.GetPath()
dialog.Destroy()
class PathingAlgorithm(object):
"""
our pathing algorithm
"""
def __init__(self, depth=1):
"""
initializing function
:return:
"""
self.depth = depth
self.image_path = ""
#staticmethod
def natural_sort(current_list):
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(current_list, key=alphanum_key)
def current(self):
"""
return the current path or ask for the path
:return:
"""
if not self.image_path:
self.image_path = PathSelect.ask_path()
if self.image_path:
return self.image_path
def backward(self, path="", depth=0, ghost=False):
"""
return path for the previous picture
:param path:
:param depth:
:param ghost:
:return:
"""
# max recursion case, break our function here
if self.depth < depth:
return None
depth += 1
if path == "":
path = self.image_path
folder = os.path.dirname(path)
file_name = os.path.basename(path)
folder_content = self.natural_sort(os.listdir(folder))
file_index = folder_content.index(file_name)
if file_index == 0:
path = self.backward(folder, depth, ghost)
# handle max depth case
if path is None:
return None
# get in the same level of the foldertree again if possible
for x in xrange(depth):
path_list = os.listdir(path)
if path_list:
path = os.path.join(path, self.natural_sort(path_list)[len(path_list) - 1])
else:
path = os.path.join(folder, folder_content[folder_content.index(file_name) - 1])
if not ghost:
self.image_path = path
return path
def forward(self, path="", depth=0, ghost=False):
"""
return path for the next picture
:param path:
:param depth:
:return:
"""
depth += 1
# max recursion case, break our function here
if self.depth < depth:
return None
# on start use current path, on recursion skip this
if path == "":
path = self.image_path
folder = os.path.dirname(path)
file_name = os.path.basename(path)
if os.path.isfile(os.path.join(folder, file_name)):
folders = os.listdir(folder)
else:
folders = [name for name in os.listdir(folder) if os.path.isdir(os.path.join(folder, name))]
folder_content = self.natural_sort(folders)
file_index = folder_content.index(file_name)
if file_index == len(folder_content) - 1:
if self.depth - 1 < depth:
files = [name for name in os.listdir(folder) if os.path.isfile(os.path.join(folder, name))]
if files:
return os.path.join(folder, files[0])
path = self.forward(folder, depth, ghost)
# handle max depth case
if path is None:
return None
# get in the same level of the foldertree again if possible
for x in xrange(depth):
if not os.path.isfile(path):
file_list = os.listdir(path)
if file_list:
path = os.path.join(path, self.natural_sort(file_list)[0])
else:
path = os.path.join(folder, folder_content[folder_content.index(file_name) + 1])
if not ghost:
self.image_path = path
return path
if __name__ == "__main__":
app = wx.App()
app.MainLoop()
ps = PathingAlgorithm(depth=3)
# print ps.current()
# print ps.backward(ghost=True)
# print ps.forward(ghost=True)
print ps.forward(
path='../tests/test1/test2/31.jpg',
ghost=True,
)
thanks for any help in advance
I was too focused on the recursive function, solving it over a sorted file tree was the solution, currently not the best performance is the depth is too big and it wants to get all files, but good enough for my case
def get_file_tree(self, path):
"""
return a natural sorted file tree and the index of your original file
:param path:
:return:
"""
if not os.path.exists(path):
return None
filename = os.path.basename(path)
basepath = os.path.abspath(os.path.dirname(path))
for _ in xrange(self.depth):
path = os.path.abspath(os.path.join(basepath, os.pardir))
# list all files
configfiles = [os.path.join(dirpath, f)
for dirpath, dirnames, files in os.walk(path)
for f in fnmatch.filter(files, '*')]
# use natural sort for the tree
configfiles = self.natural_sort(configfiles)
original_path = os.path.join(basepath, filename)
original_index = configfiles.index(original_path)
return configfiles, original_index
def backward(self, path="", ghost=False):
"""
get the next file of our current or defined path
:param path:
:param ghost:
:return:
"""
if path == "":
path = self.image_path
path = os.path.abspath(path)
configfiles, original_index = self.get_file_tree(path)
# if file was non existant or the index was 0 return None
if original_index is None or original_index == 0:
return None
new_path = configfiles[original_index - 1]
if new_path.count("\\") > path.count("\\"):
return None
if not ghost:
self.image_path = new_path
return new_path
def forward(self, path="", ghost=False):
"""
get the next file of our current or defined path
:param path:
:param ghost:
:return:
"""
if path == "":
path = self.image_path
path = os.path.abspath(path)
configfiles, original_index = self.get_file_tree(path)
# if file was non existant or was the last file, return None
if original_index is None or len(configfiles) <= original_index + 1:
return None
new_path = configfiles[original_index + 1]
if not ghost:
self.image_path = new_path
return new_path

Categories