I want to count the variables that occur in an AST (e.g., a+b+a should return {a,b}). Is there a one liner that I can write for each of the AST cases? I tried:
def count(e):
if isinstance(e, Add): # a + b
# XXX can I write the following as a one liner?
vs = set()
for v in s.args:
vs.update(count(v))
return vs
elif isinstance(e, Variable):
return e
...
To answer your actual question, you should probably be tree-walking for Name nodes.
This answer includes a variation of ast.NodeVisitor that passes the full path down to the node to the visitor function, since we need to be read some of the context to know whether we're in an Attribute access, or Storeing or loading.
There may be some corner cases I didn't think of yet, naturally, and attribute access (e.a, z.w) is currently fully ignored.
import ast
import collections
code = """
a = b + c + e.a
q = a + b + c
z.w = b + c
print(a)
"""
class NodeVisitorWithPath:
def visit(self, path):
node = path[-1]
method = "visit_" + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
return visitor(path)
def generic_visit(self, path):
node = path[-1]
for field, value in ast.iter_fields(node):
if isinstance(value, list):
for item in value:
if isinstance(item, ast.AST):
self.visit(path + [item])
elif isinstance(value, ast.AST):
self.visit(path + [value])
class NameWalker(NodeVisitorWithPath):
def __init__(self):
self.events = collections.Counter()
def visit_Name(self, path):
if len(path) >= 1 and isinstance(
path[-2], ast.Attribute
): # Attribute access, ignore
return
event = (
"Assign"
if len(path) >= 1 and isinstance(path[-2], ast.Assign)
else "Load"
)
node = path[-1]
self.events[f"{event} {node.id}"] += 1
super().generic_visit(path)
tree = ast.parse(code, "x", "exec")
nw = NameWalker()
nw.visit([tree])
print(dict(nw.events))
outputs
{'Assign a': 1, 'Load b': 3, 'Load c': 3, 'Assign q': 1, 'Load a': 2, 'Load print': 1}
Related
I have trouble using recursion with linked lists.
I want to be able to add another node to an ordered linked list.
I have this class:
class Link:
def __init__(self,value,next=None):
self.value = value
self.next = next
Below is the recursive function, that so far doesn't do much except identifying base cases
Note: it's not a method in Link, it's a separate function:
def add(ll, v):
if ll == None:
return Link(v)
else:
ll.next = add(ll.next,v)
return ll
Example: I have this linked list A:
1->3->8->12->None
I call add(A, 10) which should add 10 in the right order.
1->3->8->10->12->None
How should I modify my code to make that work?
You need an extra base case, to stop the recursion when you find a value that is not less than the one you want to insert in order:
elif v <= ll.value:
return Link(v, ll)
So the complete code could be:
class Link:
def __init__(self,value,next=None):
self.value = value
self.next = next
def values(self):
yield self.value
if self.next:
yield from self.next.values()
def add(ll, v):
if ll is None:
return Link(v)
elif v <= ll.value:
return Link(v, ll)
else:
ll.next = add(ll.next, v)
return ll
# example list
A = Link(1, Link(3, Link(8, Link(12))))
# add 10 to it:
A = add(A, 10)
# print list
print(list(A.values()))
Recursion is a functional heritage and so using it with functional style yields the best results. We start with the smallest bits first -
# linked_list.py
empty = None
class node:
def __init__(self, value, next = empty):
self.value = value
self.next = next
def to_str(ll = empty):
if not ll:
return "None"
else:
return f"{ll.value}->{to_str(ll.next)}"
# main.py
from linked_list import node, to_str
t = node(1, node(3, node(8, node(12))))
print(to_str(t))
# 1->3->8->12->None
Now we implement add -
# linked_list.py
empty = # ...
class node: # ...
def to_str(ll = empty): # ...
def add(ll = empty, v = 0):
if not ll:
return node(v)
elif ll.value >= v:
return node(v, ll)
else:
return node(ll.value, add(ll.next, v))
# main.py
from linked_list import node, to_str, add
t = node(1, node(3, node(8, node(12))))
print(to_str(t))
# 1->3->8->12->None
t2 = add(t, 10)
print(to_str(t2))
# 1->3->8->10->12->None
Now we see how to make bigger bits by combining smaller bits. We could make a linked_list class to bundle it up -
# linked_list.py
empty = # ...
class node: # ...
def to_str(ll = empty): # ...
def add(ll = empty, v = 0): # ...
class linked_list:
def __init__(self, root = empty):
self.root = root
def __str__(self):
return to_str(self.root)
def add(self, v):
return linked_list(add(self.root, v))
Now we can use linked_list in object-oriented style but still get the persistent benefits of functional style -
#main.py
from linked_list import linked_list
t = linked_list().add(1).add(3).add(8).add(12)
print(t)
# 1->3->8->12->None
print(t.add(10))
# 1->3->8->10->12->None
print(t)
# 1->3->8->12->None
Maybe we could expand our linked_list module by defining from_list and to_list -
# linked_list.py
empty = # ...
class node: # ...
def to_str(ll = empty): # ...
def add(ll = empty, v = 0): # ...
def from_list(l = []):
if not l:
return empty
else:
return node(l[0], from_list(l[1:]))
def to_list(ll = empty):
if not ll:
return []
else:
return [ ll.value ] + to_list(ll.next)
class linked_list:
def __init__ # ...
def __str__ # ...
def add # ...
def from_list(l):
return linked_list(from_list(l))
def to_list(self):
return to_list(self.root)
# main.py
from linked_list import linked_list
t = linked_list.from_list([ 1, 3, 8, 12 ])
print(t)
# 1->3->8->12->None
print(t.add(10))
# 1->3->8->10->12->None
print(t.add(11).to_list())
# [1, 3, 8, 11, 12]
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
I am trying to build a iterable graph class with python 2.7. I want to be able to iterate though a dictionary containing the vertexes.
Cutting and pasting from https://github.com/joeyajames has got me so far but now I am confused as to how to make this work so that
I can test vertices dict for the presence of an vertice and add if not present. This part is maybe unneeded.
"if (a not in gra ):" because the validation is done in the Graph class itself.
The expected output is a dictionary with the vertices as keys. Actualy im not even sure a list is not better object to use.
class Vertex(object):
def __init__(self, n):
self.name = n
self.neighbors = list()
self.discovery = 0
self.finish = 0
self.color = 'black'
def add_neighbor(self, v):
if v not in self.neighbors:
self.neighbors.append(v)
self.neighbors.sort()
class Graph(object):
def __init__(self,size):
self.vertices = {}
self.hops = 0
self.count = 0
self.limit = size
def __iter__(self):
return self
def next(self):
self.count += 1
if self.count > self.limit:
raise StopIteration
def add_vertex(self,vertex):
if isinstance(vertex, Vertex) and vertex.name not in self.vertices:
self.vertices[vertex.name] = vertex
return True
else:
return False
def add_edge(u,v):
if u in self.vertices and v in self.vertices:
for key, value in self.vertices.items():
if key == u:
value.add_neighbor(v)
if key == v:
value.add_neighbor(u)
return True
else:
return False
def _dfs(self, vertex):
global hops
vertex.color = 'red'
vertex.discovery = hops
hops += 1
for v in vertex.neighbors:
if self.vertices[v].color == 'black':
self._dfs(self.vertices[v])
vertex.color = 'blue'
vertex.finish = hops
time += 1
input = ((5,3),(4 ,2),(0,1),(2 3),(0 4))
N,l = input[0]
print "N is " + str(N)
print "l is " + str(l)
gra = Graph(N)
for i in xrange(1,l):
a,b = input[i]
# Store a and b as vertices in graph object
print "a is " + str(a) + " b is " + str(b)
if (a not in gra ):
print "adding a"
gra.add_vertex(Vertex(chr(a)))
if (b not in gra ):
print "adding b"
gra.add_vertex(Vertex(chr(b)))
You are trying to use not in, which tests for containment; implement the __contains__ hook to facilitate that:
def __contains__(self, vertex):
return vertex.name in self.vertices
I've assumed you wanted to test for vertices, so create one before testing for containment:
a = Vertex(chr(a))
if a not in gra:
print "adding a"
gra.add_vertex(a)
For iteration, I'd not make Graph itself the iterator; that limits you to iterating just once. Your next() method also lacks a return statement, so all you are doing is produce a sequence of None objects.
Make it an iterable instead, so return a new iterator object each time __iter__ is called. You can most simply achieve this by making __iter__ a generator:
def __iter__(self):
for vertex in self.vertices.itervalues():
yield vertex
Note the yield. I've assumed you wanted to iterate over the vertices.
I was just wondering would anyone be able to help me. I am trying to do an inorder transversal of an AVL tree. But I keep getting an error that my function name 'r_in_order' is not defined. What is happening here and what am I missing? Here is the code:
class Node:
""" A node in a BST. It may have left and right subtrees """
def __init__(self, item, left = None, right = None):
self.item = item
self.left = left
self.right = right
class BST:
""" An implementation of a Binary Search Tree """
def __init__(self):
self.root = None
def recurse_add(self, ptr, item):
if ptr == None:
return Node(item)
elif item < ptr.item:
ptr.left = self.recurse_add(ptr.left, item)
elif item > ptr.item:
ptr.right = self.recurse_add(ptr.right, item)
return ptr
def add(self, item):
""" Add this item to its correct position on the tree """
self.root = self.recurse_add(self.root, item)
def r_count(self, ptr):
if ptr == None:
return 0
else:
return 1 + self.r_count(ptr.left) + self.r_count(ptr.right)
def count(self):
return self.r_count(self.root)
def r_height(self, ptr):
if ptr == None:
return 0
else:
return 1 + max(self.r_height(ptr.left), self.r_height(ptr.right))
def height(self):
return self.r_height(self.root)
def r_in_order(self, ptr):
if ptr != None:
r_in_order(ptr.left)
print(ptr.item + " ", end="")
r_in_order(ptr.right)
def in_order(self):
return self.r_in_order(self.root)
I am then testing the code with this:
import sys
from BST import BST
def main():
# Read each test case
line = sys.stdin.readline()
items = line.strip().split()
nums = [int(item) for item in items]
tree = BST()
for num in nums:
tree.add(num)
print("Print the elements of the tree in order:")
tree.in_order()
if __name__ == "__main__":
main()
r_in_order is a method of BST. It can only be called on a BST instance (or on the class with an instance as the first argument), but in the definition of r_in_order itself, you try to use it without one. So technically, it doesn't exist in the namespace you're trying to use it in.
Your function definition should be as follows:
def r_in_order(self, ptr):
if ptr != None:
self.r_in_order(ptr.left)
print(ptr.item + " ", end="")
self.r_in_order(ptr.right)
There is no general function r_in_order: you need to add self. to get a reference to the method you're already inside. There's also a syntax error lurking in the print statement. Try this:
def r_in_order(self, ptr):
if ptr != None:
self.r_in_order(ptr.left)
print(ptr.item, " ", end="")
self.r_in_order(ptr.right)
This runs, and yields the below (first line is input).
1 3 7 5 6 4 2
Print the elements of the tree in order:
1 2 3 4 5 6 7
Here's my code, the intention of which is to crawl a given folder and look for .md and .pdf files, and build a tree-like structure which describes it.
I'm probably really overthinking it, so I could really use a second set of eyes on this.
class Resource_Item:
def __init__(self, name=None, stub=None, path=None, parent=None, html_file_location=None, documents=[], children=[]):
self.name = name
self.stub = stub
self.path = path
self.parent = parent
self.html_file_location = html_file_location
self.documents = documents
self.children = children
def add_child(self, c):
self.children.append(c)
def to_json(self):
o = {
'name' : self.name,
'stub' : self.stub,
'path' : self.path,
'parent' : self.parent,
'html_file_location' : self.html_file_location,
'documents' : self.documents,
'children' : [c.to_json() for c in self.children] } #len(self.children)
return json.dumps(o)
def walk_dir(root, parent = None):
"""
>>> walk_dir("./test_docs/folder containing pdfs/").documents
['dummy_pdf 2.pdf', 'dummy_pdf 3.pdf', 'dummy_pdf 4.pdf', 'dummy_pdf.pdf']
>>> len(walk_dir("./test_docs/folder containing pdfs/").children)
0
>>> walk_dir("./test_docs/folder containing markdown and pdfs/").stub is None
False
>>> walk_dir("./test_docs/folder containing markdown and pdfs/").children
['dummy_pdf 2.pdf', 'dummy_pdf 3.pdf', 'dummy_pdf 4.pdf', 'dummy_pdf.pdf']
"""
file_or_folder_name_no_ext = os.path.splitext(os.path.basename(root))[0]
entry = Resource_Item( name=file_or_folder_name_no_ext, parent=parent, path=os.path.abspath(root) )
for item in os.listdir(root):
path = os.path.join(os.path.abspath(root), item)
if os.path.isfile(path):
if item.endswith(".pdf"):
entry.documents.append(item)
elif item.endswith(".md"):
entry.stub = read_markdown_file_as_html(path)
elif os.path.isdir(path):
if dir_contains_pdf(path):
print('found a path to contain PDFs: "'+str(path)+'"')
entry.add_child(walk_dir(path)) # broken!
#entry.add_child(path)
return entry
What appears to be happening is that on the entry.add_child(walk_dir(path)) line, walk_dir doesn't properly create a new instance of Resource_Item, since my testing shows that Resource_Item.children gets populated with all the pdfs in that file tree, not just those in the immediate folder.
As for my supporting functions, I'm pretty sure they work properly, but here they are for completeness:
def dir_contains_pdf(root):
"""
>>> dir_contains_pdf("./test_docs/folder containing pdfs/")
True
>>> dir_contains_pdf("./test_docs/folder containing nothing/")
False
>>> dir_contains_pdf("./test_docs/folder containing folders, markdown, and pdf/")
True
>>> dir_contains_pdf("./test_docs/folder containing markdown and pdfs/")
True
"""
root = os.path.abspath(root)
for item in os.listdir(root):
item_path = os.path.join(root, item)
if os.path.isfile(item_path):
if item.endswith(".pdf"):
return True
elif os.path.isdir(item_path):
if dir_contains_pdf(item_path):
return True
return False
def read_markdown_file_as_html(markdown_filename):
f = open(markdown_filename, 'r')
markdown_content = f.read()
return markdown.markdown(markdown_content)
As another view of how this recursion should be working, I built this other program in the same style to confirm that it works, and it does work properly, so I'm guessing the issue has to do with how I'm using the Python file API:
class Item:
def __init__(self, n=None):
self.n = n
self.children = []
def add_child(self, c):
self.children.append(c)
def to_o(self):
o = { 'n' : self.n, 'children' : [c.to_o() for c in self.children] }
return o
def bad(count):
item = Item(n=count)
print('count : '+str(count))
if count > 100 or count == 0:
return item
elif (count-1) % 2 == 0:
print(str(count) + ' is odd')
item.add_child(bad(count*3))
elif count % 2 == 0:
print(str(count) + ' is even')
item.add_child(bad(count/2))
return item
import json
print(json.dumps(bad(7).to_o()))