setting up iterative item query in python - python

I am trying to set up a function that will query an item for its sub components if those exists and return those else return the item itself.
Imagine an object that can contain more objects within it. To access those objects i would do object.GetSubComponentIds() now if that object contains sub objects it would return a list of those sub objects or EmptyList if there are none. In case that there are sub objects contained within it I want to keep going and then for each subobject i want to check if there are any subobjects contained within them. So for every SubObject.GetSubComponentIds() now if those do not contain anything then i would love to return them while maintaining nested structure of objects that they came from.
object1(contains 3 sub objects)
object2(contains 3 sub object and each sub object contains one more sub object)
object3(does not contain sub objects)
inputlist = [object1, object2]
outputlist = [[obj1sub1, obj1sub2, obj1sub3],[[obj2sub1sub1],[obj2sub2sub1],[obj2sub3sub1]],[obj3]]
I am interested in maintaining that nested list structure that will allow me to always trace back the origin of the sub object. Again, a method to get a sub object list is object.GetSubComponentIds() and it will either return a list or Empty List.
Can anyone help me set up an iterative function to retrieve them. Keep in mind that I do not know whether there are any sub objects contained within an object or haw many levels deep are they. It's basically that if it returns a list i need to check every item on that list for more sub objects.
Thank you in advance
Here's my humble first try:
#unwrap all elements to use with API
elements = []
for i in IN[0]:
elements.append(UnwrapElement(i))
#create element set from python list
elementSet = Autodesk.Revit.DB.ElementSet()
for i in elements:
elementSet.Insert(i)
#convert element set to List[Element]
setForCheck = List[Autodesk.Revit.DB.Element]()
elemIter = elementSet.ForwardIterator()
elemIter.Reset()
while elemIter.MoveNext():
curElem = elemIter.Current
setForCheck.Add(curElem)
#iterate throuh all elements to extract nested elements
setLoop = List[Autodesk.Revit.DB.Element]()
elemSet = List[Autodesk.Revit.DB.Element]()
itemOut = []
counter = 0
while setForCheck.Count >= 1:
setLoop.Clear()
for i in setForCheck:
itemOut.append(i)
if i.GetSubComponentIds().Count >= 1:
elem = Autodesk.Revit.DB.ElementSet()
for j in i.GetSubComponentIds():
elem.Insert(doc.GetElement(j))
elemIterA = elem.ForwardIterator()
elemIterA.Reset()
while elemIterA.MoveNext():
curElemA = elemIterA.Current
setLoop.Add(curElemA)
setForCheck.Clear()
elemIterB = setLoop.GetEnumerator()
elemIterB.Reset()
while elemIterB.MoveNext():
curElemB = elemIterB.Current
setForCheck.Add(curElemB)
counter += 1
if counter > 1000:
break
#Assign your output to the OUT variable
OUT = itemOut

You're using some specific libraries, like Autodesk, that I'm not familiar with. Let me answer your question in terms of an abstract example.
Suppose we're dealing with Thing objects, where Thing is defined as:
class Thing(object):
def __init__(self, name):
self.name = name
self.inside = []
We can make Things and put other things inside of them. The example you give in your post can be written:
ob1 = Thing("ob1")
ob1.inside.extend([Thing("ob1sub1"), Thing("ob1sub2"), Thing("ob1sub3")])
ob2 = Thing("ob2")
for i in xrange(1,4):
name = "ob2sub{}".format(i)
thing = Thing(name)
thing.inside.append(Thing(name + "sub1"))
ob2.inside.append(thing)
ob3 = Thing("ob3")
things = [ob1, ob2, ob3]
This makes a sort of tree. Now we'd like to return a nested list of all of the leaf nodes in the tree:
def search_things(things):
names = []
for thing in things:
if not thing.inside:
names.append(thing)
else:
names.append(search_things(thing.inside))
return names
A test:
>>> search_things(things)
[['ob1sub1', 'ob1sub2', 'ob1sub3'],
[['ob2sub1sub1'], ['ob2sub2sub1'], ['ob2sub3sub1']],
'ob3']
I'll let you transform this to your specific problem, but this is the general idea. Note that the algorithm is recursive, not iterative. You said you wanted an iterative algorithm -- and the above can be written iteratively -- but this gives you the idea.

Related

How to recursively create a nested dictionary of unknown size in python?

I have an object that has an unknown depth and width at every depth.
Sort of like a path but it's a different type of object.
My goal is to create a nested dictionary that represents this structure.
It starts as one item at the top of the hierarchy and I can grab the elements below it using a get_items() function. Also, if I call str() on the object it returns the name of the object.
For example:
str(object) = 'top hierarchy'
object.get_items() returns a list ~ [object_level1_part1, object_level1_part2]
str(object_level1_part1) = 'level1_part1'
object_level1_part1.get_items() returns a list ~ [object_level2_part1]
str(object_level2_part2) = 'level2_part1'
etc... There is no guarantee at any level how any objects in the list there will be.
Also, there is no guarantee how deep any level might go. However, there is probably nothing greater than a depth of 10.
I want to take this object & recursively search it in order to create a dictionary that would look like something below:
result_dict = {'top_hierarchy':{'level1_part1':{'level2_part1':{}}, 'level1_part2':{}}}
One of the challenges I am having is to continually add to the dictionary in a nested fashion I have to specify previous keys.
result_dict['top_hierarchy']['level1_part1']['level2_part1'] = {'level3_part1':{}}
Is there a way to create a nested dictionary recursively with unknown depth and width?
As your title already suggests, the problem you describe is a textbook example for recursive programming: each child object represents the original challenge on a smaller scale. Also, a recursive program doesn't care about the recursion depth nor the width of each level (as long as the maximum recursion depth built into Python isn't exceeded or your RAM overflows, that is). Therefore, applying a recursive function that calls itself on each child object is going to be the perfect fit. If we generically call the recursive function rf, the core of the solution will look something like rf(child) for child in parent.get_items(). The rest is just piecing together the requirements you have for the output: you'll be using a dictionary mapping str(parent) to all its children, so something (structurally) similar to {str(parent): [child for child in parent.get_items()]} will also be part of the solution
With all that in mind, the solution to your problem becomes as simple as
def unpack(obj):
return {str(o): unpack(o) for o in obj.get_items()}
result_dict = {str(object): unpack(object)}
Quick test with a class specifically thrown together to emulate your data structure:
class myObj:
def __init__(self, name, objects=[]):
self.name = name
self.objects = objects
def __str__(self):
return self.name
def get_items(self):
return self.objects
# build recursive structure bottom up
l2p1 = myObj('l2p1')
l1p1 = myObj('l1p1', [l2p1])
l1p2 = myObj('l1p2')
th = myObj('th', [l1p1, l1p2])
# test the unpacking
result_dict = {str(th): unpack(th)}
result_dict
{'th': {'l1p1': {'l2p1': {}}, 'l1p2': {}}}

Append an element to a double embedded dictionary adds to all parent elements

I'm a little bit stumped by a problem which I can't figure out and I feel it should be a trivial problem to solve.
I have an element budget of class Budget. In this class, it has a dictionary sections which contains Section class objects. In Section class, I have another dictionary which is called allowances of Allowance class objects. In the Allowance class, I have a list called operations to which I want to add Operation class objects. The hierarchy is as follows, for those a bit more visual :
budget -> sections -> allowances -> operations
I want to append an Operation class object to a specific combination of section and allowance (variables used are name_of_section and name_of_operation. My first try was
budget.sections[name_of_section].allowances[name_of_allowance].operations.append(Operation(name=name,
cost=cost, date=date)
For some unknown reason, it adds the Operation object to all my sections and allowances elements and I can't figure out why. I tried adding a method add_operation() to Section, which uses a method add_operation() to Allowance, but it just keeps adding every operation to all my Section and Allowance elements.
The weirdest thing is that I also have a list keywords in Allowance which is meant to be a list of strings. I append a string just a few lines later in exactly the same fashion:
budget.sections[name_of_section].allowances[name_of_allowance].keywords.append(keyword_str)
And it only adds to the appropriate section and allowance. Does anyone have an idea why when I'm trying to append my Operation object to a list, it adds to all the lists, although when I append to a list of str, it only adds to a single and the appropriate list?
Here's my Budget initialization and method to add an allowance which adds a section as needed.
def __init__(self):
self.sections = {"Income": Section(name = "Income")}
self.total_balance = 0.0
self.unsorted_operations = []
def add_allowance(self, name, section, projected_cost = 0.0, frequency = "monthly"):
if section in self.sections:
self.sections[section].new_allowance(name=name, section=section, projected_cost = projected_cost,
frequency = frequency)
else:
self.add_section(name = section)
self.sections[section].new_allowance(name=name, section=section, projected_cost=projected_cost,
frequency=frequency)
My Section class is initialized in this fashion and the _new_allowance()_ method is:
def __init__(self, name):
self.name = name
self.allowances = dict()
self.calculate_projected_cost()
def new_allowance(self, name, section, projected_cost, frequency = 'monthly'):
self.allowances[name] = Allowance(name = name, section = section, projected_cost = projected_cost,
frequency = frequency)
self.calculate_projected_cost()
My Allowance class is initialized this way:
def __init__(self, name, section, projected_cost = 0.0, frequency = "monthly"):
self.name = name
self.section = section
self.operations = []
self.cost = 0.0
self.frequency = frequency
self.calculate_projected_cost(projected_cost, frequency)
self.keywords = []
I don't know how you created your data structure,
as you didn't post that code.
But your symptom makes it clear that you did something like this:
>>> a = ['apple']
>>> fruit = [a, a, a]
>>> a.append('banana')
>>> fruit
[['apple', 'banana'], ['apple', 'banana'], ['apple', 'banana']]
That is, you have an inner mutable container (or similar object),
and your outer container has multiple references to that single inner container.
Suppose you display the outer container.
Now you change the inner container (a, above) --
that will affect the displayed output in multiple places,
since you have multiple references to what changed.
If you're curious about details you can use e.g.
print(list(map(id, fruit))) to see whether elements
are the same or different.
The python id(x) function is roughly equivalent
to a C program asking at which address was
storage for x allocated.
It turned out it might had been a problem with my saved JSON file which I loaded my budget object from. As J_H suggested to use id(x), the elements were one and the same. This tip was quite useful in debugging my problem.
Without any change to the code and by starting from scratch instead of loading my JSON file, I did not see that kind of behavior again. I saved it, stopped the program, ran it again and loaded my new saved file, and now, it is running as it should.

Sorting nested list

Im trying to sort my list which contains of 3 nested lists: paths, file names and finally file creation time. So I want to sort them to be able to get the latest files.
So Ive seen people been using lambda for this, but I dont feel comfortable using those and kind of dont get how to the sorting with that works.
I think the best way is just to switch the list components, but this does not work:
class FILE:
PATH = 0
NAME = 1
DATE = 2
mayaFiles = [[],[],[]]
mayaFiles[FILE.DATE] = [0,56,3,12,7,35,16]
doSwitch = True
while (doSwitch):
for ma in range(0, len(mayaFiles[FILE.DATE])-1):
doSwitch = False
doSwitch = mayaFiles[FILE.DATE][ma] > mayaFiles[FILE.DATE][ma+1]
hi = mayaFiles[FILE.DATE][ma]
lo = mayaFiles[FILE.DATE][ma+1]
if doSwitch:
mayaFiles[FILE.DATE][ma] = lo
mayaFiles[FILE.DATE][ma+1] = hi
else:
break
print mayaFiles[FILE.DATE]
Assuming these lists are already aligned, you'll have a much easier time by combing the there separate lists into a list of tuples arranged by your sort order. the namedtuple construct in the collections module is great for this sort of thing. I'm assuming you can get your data into three lists: paths, dates and names. I'm supplying some dummy data here so you can see what I'm assuming.
names = "a.ma", "b.ma", "c.ma", "d.ma"
paths = "c:/test", "c/test", "c:/other", "d:/extra"
dates = "17-01-01", "16-01-01", "17-02-01", "17-06-30"
# this creates a namedtuple, which is a
# mini-class with named fields that otherwise
# works like a tuple
from collections import namedtuple
record = namedtuple("filerecord", "date name path")
# in real use this should be a list comp
# but this is easier to read:
records = []
for date, name, path in zip(dates, names, paths):
records.append(record(date, name, path))
records.sort(reverse=True)
for item in records:
print item
# filerecord(date='17-06-30', name='d.ma', path='d:/extra')
# filerecord(date='17-02-01', name='c.ma', path='c:/other')
# filerecord(date='17-01-01', name='a.ma', path='c:/test')
# filerecord(date='16-01-01', name='b.ma', path='c/test')
You could sort on other fields using the 'key' argument to sort():
records.sort(key=lambda k: k.name)
for item in records:
print item
# filerecord(date='17-01-01', name='a.ma', path='c:/test')
# filerecord(date='16-01-01', name='b.ma', path='c/test')
# filerecord(date='17-02-01', name='c.ma', path='c:/other')
# filerecord(date='17-06-30', name='d.ma', path='d:/extra')

Having trouble with object creation

I am creating a program where a user can learn a number of skills which are predefined and are changed to a learned = True value later. I want to put all of the objects which are already learned into a list but can't find a way to collect all objects to sort through them and would appreciate if anyone could suggest one. This is my current strategy using a list in the class definition but it isn't working correctly so I would be appreciative if someone had a better way.
My class currently looks like this:
class attacks:
learned = False
def __init__(self,name,baseDMG,adMulti,apMulti,accuracy):
self.name = name
self.baseDMG = baseDMG
self.adMulti = adMulti
self.apMulti = apMulti
self.accuracy = accuracy
global adAbilities
global apAbilities
global mixedAbilities
adAbilities = []
apAbilities = []
mixedAbilities = []
if adMulti > 0 and apMulti > 0:
mixedAbilities.append(self)
elif adMulti > 0:
adAbilities.append(self)
elif apMulti > 0:
apAbilities.append(self)
But when I check the items in the lists it only stores one of the objects in the 2nd list and none in the others even though i have created these objects:
slash = attacks("slash",20,6,0,90)
smite = attacks("smite",20,0,6,90)
doubleStrike = attacks("doubleStrike",30,9,0,70)
blast = attacks("blast",30,0,9,70)
weaponThrow = attacks("weaponThrow",20,8,0,90)
Only the last object defined is the only one in any lists no matter which object is last defined (tested by printing lists after each object is defined).

Infinite loop when adding a row to a list in a class in python3

I have a script which contains two classes. (I'm obviously deleting a lot of stuff that I don't believe is relevant to the error I'm dealing with.) The eventual task is to create a decision tree, as I mentioned in this question.
Unfortunately, I'm getting an infinite loop, and I'm having difficulty identifying why. I've identified the line of code that's going haywire, but I would have thought the iterator and the list I'm adding to would be different objects. Is there some side effect of list's .append functionality that I'm not aware of? Or am I making some other blindingly obvious mistake?
class Dataset:
individuals = [] #Becomes a list of dictionaries, in which each dictionary is a row from the CSV with the headers as keys
def field_set(self): #Returns a list of the fields in individuals[] that can be used to split the data (i.e. have more than one value amongst the individuals
def classified(self, predicted_value): #Returns True if all the individuals have the same value for predicted_value
def fields_exhausted(self, predicted_value): #Returns True if all the individuals are identical except for predicted_value
def lowest_entropy_value(self, predicted_value): #Returns the field that will reduce entropy the most
def __init__(self, individuals=[]):
and
class Node:
ds = Dataset() #The data that is associated with this Node
links = [] #List of Nodes, the offspring Nodes of this node
level = 0 #Tree depth of this Node
split_value = '' #Field used to split out this Node from the parent node
node_value = '' #Value used to split out this Node from the parent Node
def split_dataset(self, split_value): #Splits the dataset into a series of smaller datasets, each of which has a unique value for split_value. Then creates subnodes to store these datasets.
fields = [] #List of options for split_value amongst the individuals
datasets = {} #Dictionary of Datasets, each one with a value from fields[] as its key
for field in self.ds.field_set()[split_value]: #Populates the keys of fields[]
fields.append(field)
datasets[field] = Dataset()
for i in self.ds.individuals: #Adds individuals to the datasets.dataset that matches their result for split_value
datasets[i[split_value]].individuals.append(i) #<---Causes an infinite loop on the second hit
for field in fields: #Creates subnodes from each of the datasets.Dataset options
self.add_subnode(datasets[field],split_value,field)
def add_subnode(self, dataset, split_value='', node_value=''):
def __init__(self, level, dataset=Dataset()):
My initialisation code is currently:
if __name__ == '__main__':
filename = (sys.argv[1]) #Takes in a CSV file
predicted_value = "# class" #Identifies the field from the CSV file that should be predicted
base_dataset = parse_csv(filename) #Turns the CSV file into a list of lists
parsed_dataset = individual_list(base_dataset) #Turns the list of lists into a list of dictionaries
root = Node(0, Dataset(parsed_dataset)) #Creates a root node, passing it the full dataset
root.split_dataset(root.ds.lowest_entropy_value(predicted_value)) #Performs the first split, creating multiple subnodes
n = root.links[0]
n.split_dataset(n.ds.lowest_entropy_value(predicted_value)) #Attempts to split the first subnode.
class Dataset:
individuals = []
Suspicious. Unless you want to have a static member list shared by all instances of Dataset you shouldn't do that. If you are setting self.individuals= something in the __init__, then you don't need to set individuals here too.
def __init__(self, individuals=[]):
Still suspicious. Are you assigning the individuals argument to self.individuals? If so, you are assigning the same individuals list, created at function definition time, to every Dataset that is created with a default argument. Add an item to one Dataset's list and all the others created without an explicit individuals argument will get that item too.
Similarly:
class Node:
def __init__(self, level, dataset=Dataset()):
All Node​s created without an explicit dataset argument will receive the exact same default Dataset instance.
This is the mutable default argument problem and the kind of destructive-iterations it would produce would seem very likely to be causing your infinite loop.
I suspect that you are appending to the same list that you are iterating over causing it to increase in size before the iterator can reach the end of it. Try iterating over a copy of the list instead:
for i in list(self.ds.individuals):
datasets[i[split_value]].individuals.append(i)

Categories