Bullet Lists in python-docx - python

I am trying to get this to work in python-docx:
A bullet list I can get using this:
from docx import Document
doc = Document()
p = doc.add_paragraph()
p.style = 'List Bullet'
r = p.add_run()
r.add_text("Item 1")
# Something's gotta come here to get the Sub-Item 1
r = p.add_run()
r.add_text("Item 2")
# Something's gotta come here to get the Sub-Item 2
I figure, adding another paragraph in the middle won't help because that essentially would mean I am making another List Bullet with the same formatting as its parent and not the child-like formatting I want. Also, adding another run to the same paragraph doesn't help either(I tried this, messes up the whole thing..). Any way to do it?

There is a way to do it, but it involves a bit of extra work on your part. There is currently no "native" interface in python-docx for doing this. Each bulleted item must be an individual paragraph. Runs apply only to the text characters.
The idea is that list bulleting or numbering is controlled by a concrete bullet or number style, which refers to an abstract style. The abstract style determines the styling of the afflicted paragraph, while the concrete numbering determines the number/bullet within the abstract sequence. This means that you can have paragraphs without bullets and numbering interspersed among the bulleted paragraphs. At the same time, you can restart the numbering/bulleting sequence at any point by creating a new concrete style.
All this information is hashed out (in detail but unsuccessfully) in Issue #25. I don't have the time or resources to lay this to rest right now, but I did write a function that I left in a comment in the discussion thread. This function will look up an abstract style based on the level of indentation and paragraph style you want. It will then create or retrieve a concrete style based on that abstract style and assign it to your paragraph object:
def list_number(doc, par, prev=None, level=None, num=True):
"""
Makes a paragraph into a list item with a specific level and
optional restart.
An attempt will be made to retreive an abstract numbering style that
corresponds to the style of the paragraph. If that is not possible,
the default numbering or bullet style will be used based on the
``num`` parameter.
Parameters
----------
doc : docx.document.Document
The document to add the list into.
par : docx.paragraph.Paragraph
The paragraph to turn into a list item.
prev : docx.paragraph.Paragraph or None
The previous paragraph in the list. If specified, the numbering
and styles will be taken as a continuation of this paragraph.
If omitted, a new numbering scheme will be started.
level : int or None
The level of the paragraph within the outline. If ``prev`` is
set, defaults to the same level as in ``prev``. Otherwise,
defaults to zero.
num : bool
If ``prev`` is :py:obj:`None` and the style of the paragraph
does not correspond to an existing numbering style, this will
determine wether or not the list will be numbered or bulleted.
The result is not guaranteed, but is fairly safe for most Word
templates.
"""
xpath_options = {
True: {'single': 'count(w:lvl)=1 and ', 'level': 0},
False: {'single': '', 'level': level},
}
def style_xpath(prefer_single=True):
"""
The style comes from the outer-scope variable ``par.style.name``.
"""
style = par.style.style_id
return (
'w:abstractNum['
'{single}w:lvl[#w:ilvl="{level}"]/w:pStyle[#w:val="{style}"]'
']/#w:abstractNumId'
).format(style=style, **xpath_options[prefer_single])
def type_xpath(prefer_single=True):
"""
The type is from the outer-scope variable ``num``.
"""
type = 'decimal' if num else 'bullet'
return (
'w:abstractNum['
'{single}w:lvl[#w:ilvl="{level}"]/w:numFmt[#w:val="{type}"]'
']/#w:abstractNumId'
).format(type=type, **xpath_options[prefer_single])
def get_abstract_id():
"""
Select as follows:
1. Match single-level by style (get min ID)
2. Match exact style and level (get min ID)
3. Match single-level decimal/bullet types (get min ID)
4. Match decimal/bullet in requested level (get min ID)
3. 0
"""
for fn in (style_xpath, type_xpath):
for prefer_single in (True, False):
xpath = fn(prefer_single)
ids = numbering.xpath(xpath)
if ids:
return min(int(x) for x in ids)
return 0
if (prev is None or
prev._p.pPr is None or
prev._p.pPr.numPr is None or
prev._p.pPr.numPr.numId is None):
if level is None:
level = 0
numbering = doc.part.numbering_part.numbering_definitions._numbering
# Compute the abstract ID first by style, then by num
anum = get_abstract_id()
# Set the concrete numbering based on the abstract numbering ID
num = numbering.add_num(anum)
# Make sure to override the abstract continuation property
num.add_lvlOverride(ilvl=level).add_startOverride(1)
# Extract the newly-allocated concrete numbering ID
num = num.numId
else:
if level is None:
level = prev._p.pPr.numPr.ilvl.val
# Get the previous concrete numbering ID
num = prev._p.pPr.numPr.numId.val
par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = num
par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_ilvl().val = level
Using the styles in the default built-in document stub, you can do something like this:
d = docx.Document()
p0 = d.add_paragraph('Item 1', style='List Bullet')
list_number(d, p0, level=0, num=False)
p1 = d.add_paragraph('Item A', style='List Bullet 2')
list_number(d, p1, p0, level=1)
p2 = d.add_paragraph('Item 2', style='List Bullet')
list_number(d, p2, p1, level=0)
p3 = d.add_paragraph('Item B', style='List Bullet 2')
list_number(d, p3, p2, level=1)
The style will not only affect the tab stops and other display characteristics of the paragraph, but will also help look up the appropriate abstract numbering scheme. When you implicitly set prev=None in the call for p0, the function creates a new concrete numbering scheme. All the remaining paragraphs will inherit the same scheme because they get a prev parameter. The calls to list_number don't have to be interleaved with the calls to add_paragraph like that, as long as the numbering for the paragraph used as prev is set before the call.
You can find an implementation of this function in a library I maintain, called haggis, available on GitHub and PyPi: haggis.files.docx.list_number.

I found that #Mad Physicist's answer didn't work for me with indented bulleted lists. I modified it to only put in the value for numId if the boolean num was True - but that exposed that the get_abstract_id() function used "num" as its own local variable. So I changed "num" to "numbr" throughout that function, and added a boolean if to the next-to-last line:
if num:
par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = numbr
So for me here's the whole function:
def get_abstract_id():
"""
Select as follows:
1. Match single-level by style (get min ID)
2. Match exact style and level (get min ID)
3. Match single-level decimal/bullet types (get min ID)
4. Match decimal/bullet in requested level (get min ID)
3. 0
"""
for fn in (style_xpath, type_xpath):
for prefer_single in (True, False):
xpath = fn(prefer_single)
ids = numbering.xpath(xpath)
if ids:
return min(int(x) for x in ids)
return 0
if (prev is None or
prev._p.pPr is None or
prev._p.pPr.numPr is None or
prev._p.pPr.numPr.numId is None):
if level is None:
level = 0
numbering = doc.part.numbering_part.numbering_definitions._numbering
# Compute the abstract ID first by style, then by num
anum = get_abstract_id()
# Set the concrete numbering based on the abstract numbering ID
numbr = numbering.add_num(anum)
# Make sure to override the abstract continuation property
numbr.add_lvlOverride(ilvl=level).add_startOverride(1)
# Extract the newly-allocated concrete numbering ID
numbr = numbr.numId
else:
if level is None:
level = prev._p.pPr.numPr.ilvl.val
# Get the previous concrete numbering ID
numbr = prev._p.pPr.numPr.numId.val
if num:
par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = numbr
par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_ilvl().val = level
With profound gratitude to Mad Physicist, scanny, and everyone else who has worked so hard on python-docx; you've been tremendous help!!!
EDIT: I should add that I also made use of scanny's suggestion to start from a document with the bullet styles I wanted, rather than from a blank document. In my template I was able to correct some issues with the bullets (some of which were set to numbers, incorrectly). I then save the result to my desired filename, and all works very well.

Bullet have unicode 9679, so the easyest way do that:
r.add_text("\n " + chr(9679) + " Item 1")

Related

How can I display information on separate lines in my GUI? [duplicate]

I have a working Tkinter.Listbox object, but I want to set it up so that its elements can have carriage returns without having to somehow set up multiple linked items.
For instance, if I want to generate a selection pane with items that look like this..
# Here are four elements for the selector Listbox..
lb_items = ('mama', 'luigi', 'my birds', \
'this is a single element\n spanning two lines!')
# This generates and displays the selector window..
tk_selector = SingleSelect(lb_items, "TEST SELECTOR")
tk_selector.run_selector()
..it would be great if I could get the output to look like this mockup..
..instead of what it actually generates, which is this..
Listboxes seem to ignore '\n' and triple-quote strings with line-returns entirely; if \n is used, neither the characters nor the line break appears.
Is it possible to have individual, selectable Listbox elements that appear with line breaks?
I would also be satisfied with a word-wrap option, but after some looking, I couldn't find any such option in Listbox or Tk in general.
I could probably fake the effect by making multiline strings into multiple elements then setting it up to return the whole line if any of them are called, but it feels like an ordeal for something that could have a simple solution.
Like Bryan Oakley said, there is no native support in Listbox for carriage returns, so I tried building the 'fake' version I mentioned in the question, and it turns out that it isn't really that hard.
My solution is to parse each 'raw' string before inserting them into the Listbox, breaking strings into individual lines using splitlines, recording the number of lines and which indices in the Listbox's element roll correspond to which unbroken input string, and then selecting all the parts whenever the Listbox's selection changes using Listbox.bind('<<ListboxSelect>>', self._reselection_fxn).
There's an abridged and annotated sample below, or you can see my complete, working, even-more-heavily annotated code here.
class Multiline_Single_Selector(object):
## Go ahead and choose a better class name than this, too. :/
def __init__(self, itemlist, ..):
# ..
lb_splitlines = self._parse_strings(itemlist)
# ^ splits the raw strings and records their indices.
# returns the split strings as a list of Listbox elements.
self.my_Listbox.insert(0, *lb_splitlines)
# ^ put the converted strings into the Listbox..
self.my_Listbox.bind('<<ListboxSelect>>', self._reselect)
# ^ Whenever the Listbox selection is modifed, it triggers the
# <<ListboxSelect>> event. Bind _reselect to it to determine
# which lines ought to be highlighted when the selection updates.
# ..
def _parse_strings(self, string_list):
'''Accepts a list of strings and breaks each string into a series of lines,
logs the sets, and stores them in the item_roster and string_register attributes.
Returns the split strings to be inserted into a Listbox.'''
self.index_sets = index_sets = []
# ^ Each element in this list is a tuple containing the first and last
# Listbox element indices for a set of lines.
self.string_register = register = {}
# ^ A dict with a whole string element keyed to the index of the its
# first element in the Listbox.
all_lines = []
# ^ A list of every Listbox element. When a string is broken into lines,
# the lines go in here.
line_number = 0
for item in string_list:
lines = item.splitlines()
all_lines.extend(lines) # add the divided string to the string stack
register[line_number] = item
# ^ Saves this item keyed to the first Listbox element it's associated
# with. If the item is selected when the Listbox closes, the original
# (whole) string is found and returned based on this index number.
qty = len(lines)
if qty == 1: # single line item..
index_sets.append((line_number, line_number))
else: # multiple lines in this item..
element_range = line_number, line_number + qty - 1
# ^ the range of Listbox indices..
index_sets.extend([element_range] * qty)
# ^ ..one for each element in the Listbox.
line_number += qty # increment the line number.
return all_lines
def _reselect(self, event=None):
"Called whenever the Listbox's selection changes."
selection = self.my_Listbox.curselection() # Get the new selection data.
if not selection: # if there is nothing selected, do nothing.
return
lines_st, lines_ed = self.index_sets[selection[0]]
# ^ Get the string block associated with the current selection.
self.my_Listbox.selection_set(lines_st, lines_ed)
# ^ select all lines associated with the original string.
def _recall(self, event=None):
"Get the complete string for the currently selected item."
selection = self.my_Listbox.curselection()
if selection: # an item is selected!
return self.string_register[selection[0]]
return None # no item is selected.
If you adjust this code to match your own and drop it into an existing Listbox setup, it ought to let you simulate carriage returns. It's not the same as a native line-wrap or \n-parsing function directly in Listbox, but it does basically the same thing.
To get the whole string corresponding to the current selection, just bind _recall to whatever input or event you want to have return it. In this case, it returns None when no item is selected.
It's also kind of a lot of work considering the sophistication of the desired effect and it may not be appropriate to every situation. But at least you can do it.
It is not possible for a listbox item to be spread across more than one line or row.
This solution seems to me much easier:
Instead of trying:
lb_items = ('mama', 'luigi', 'my birds', \
'this is a single element\n spanning two lines!')
make two elements:
lb_items = ('mama', 'luigi', 'my birds', \
'this is a single element', 'spanning two lines!')

How to get the list of root parents created in a scene - Autodesk Maya / Python?

I am a bit new to python and I am trying to get a list containing all root parent existing in a scene of type joint.
for example, my scene outliner is something like that:
group1>>group2>>joint1>>joint2>>joint3
group3>>joint4>>joint5
joint16>>joint17>>joint18
I want a script that travels through the outliner and returns a list, in my example:
[joint1, joint4, joint16]
Any tips would be really appreciated. thank you so much.
Im not sure if it is any of use has Haggi Krey solution works fine but
You can use also the flag : -long from cmds.ls
# list all the joints from the scene
mjoints = cmds.ls(type='joint', l=True)
# list of the top joints from chain
output = []
# list to optimise the loop counter
exclusion = []
# lets iterate joints
for jnt in mjoints:
# convert all hierarchy into a list
pars = jnt.split('|')[1:]
# lets see if our hierarchy is in the exclusion list
# we put [1:] because maya root is represented by ''
if not set(pars) & set(exclusion):
# we parse the hierarchy until we reach the top joint
# then we add it to the output
# we add everything else to the exclusion list to avoid
for p in pars:
if cmds.nodeType(p) == 'joint':
output.append(p)
exclusion+=pars
break
print(output)
I just put this because there is not one way to go. I hope the construction of this code could help your python skills. It is exactly the same, just the way to find the parent nodes is different !
I've used DrWeeny's idea before where you traverse the hierarchy by the object's long name. The difference in this answer is that the script won't crash if there's objects with duplicate names in the scene. What I mean by that is let's say you have a situation where you have 2 hierachies:
group1>>joint1>>joint2>>group2>>joint3
and
group3>>joint1>>joint2>>group2>>joint3
Maya easily allows this, like when duplicating a top node, so we need to prevent the script from crashing in this case. When there's multiple objects with duplicate names Maya will crash if you try to access the object's short name (it doesn't know what one you're referring to!), so instead we must always use its long name:
import maya.cmds as cmds
jnts = cmds.ls(type="joint", l=True) # Collect all joints in the scene by their long names.
output = set() # Use a set to avoid adding the same joint.
for jnt in jnts:
pars = jnt.split("|") # Split long name so we can traverse its hierarchy.
root_jnt = None
while pars:
obj = "|".join(pars)
del pars[-1] # Remove last word to "traverse" up hierarchy on next loop.
# If this is a joint, mark it as the new root joint.
if obj and cmds.nodeType(obj) == "joint":
root_jnt = obj
# If a root joint was found, append it to our final list.
if root_jnt is not None:
output.add(root_jnt)
print(list(output))
Using this script on the hierarchies above would return
[u'|group1|joint1', u'|group3|joint1']
I'd suggest to list all joints and for every joint you can check if it's parent is not a joint. In your definition, these joints should be your root joints.
I use this method to get a joint hierarchy. I have given up on trying to figure out a sexier way to do this.
myItems = cmds.ls(selection = True, type='joint')
theParentJnt = cmds.listRelatives(myItems, parent = True)
jntRel = cmds.listRelatives(myItems, allDescendents = True)
allJnt = jntRel + myItems
#Green Cell
Your method worked once, and never worked again. Restarted maya 2020 more then 5 times and only displays to the top node joint, never again does it return the all the joints in one list.

just another evil hangman quesiton

Yes, this is related to homework and no I'm not looking for you to code it for me just a point in the right direction.
I'm trying to implement a linked list to represent the list of the current possible words in the list after the user has guessed a letter.
Currently I am doing the following to read in the lenght of the word the player wants to guess upon and then appending all possible words to the linked list.
However, I fail to see how I could continually update the linked list to compute the biggest word family and print the current word progress
(Ex. the dashed word with all of the current letters the user has guessed in there).
Mainly having problems because I fail to fully understand how linked lists work and how to implement them in my programs.
How I'm making the initial linked list of all possible words.
def make_ll(self):
linked_list = ll.LinkedList()
print(linked_list)
for word in self.init_list:
if len(word) == int(self.word_length):
linked_list.append(str(word))
self.linked_list = linked_list
def init_family(self, guess):
self.dictionary = {}
cursor = self.linked_list.head
while cursor != None:
word = cursor.data
cursor = cursor.next
word = self.make_families(word, guess)
def make_families(self, word, guess):
count = 0
new_word = ""
for c in word:
if guess == c:
count += 1
else:
new_word = word.replace(c, "-")
if count == 0:
self.linked_list.delete(word)
key = new_word
if key not in self.dictionary:
self.dictionary[key] = []
self.dictionary[key].append(word)
max = max(self.dictionary.values(), key = len)
new_linked_list = ll.linked_list
for word in max:
new_linked_list.append(word)
self.linked_list = new_linked_list
return word
Skeletor,
This can be done using a Linked List, but it is not the optimal solution.
You can think of a Linked List as a bunch of train cars. Each car does two things:
1) It holds something (in programming, this is the data - in your case it is a word).
2) It connects to other cars (in programming this is usually something called a pointer).
Both of these things are a part of the train car. In Linked Lists, this is called the node.
Node1 Node2 Node3
----- ----- -----
|Data1| -> |Data2| -> |Data3|
----- ----- -----
There are a few operations you can perform on train cars:
1) You can add a new car anywhere if you adjust the connectors appropriately.
2) You can walk through cars, depending on the rules for traffic, and look at the data inside.
3) Being in a car let's you easily find the next: you just have to exit and board the next car.
Linked lists have these same rules:
1) They allow you to add a node anywhere you like, but you have to adjust the references to the node (the pointers.)
2) When you're looking at the node you can easily access the data.
3) You can access the next node easily by looking at that node's reference. If you have a doubly linked list, you can look at both the previous and next node.
Most confusion understanding linked lists is the linked part of the definition. Like I mentioned, this is usually a pointer. To understand how that works you'll have to understand pointers, however, you can still understand linked lists by walking through one yourself.
First, let's define our train car(or our node):
class TrainCar:
def __init__(self, data):
# this is the contents of our train car
self.data = data
# This is the reference to the next node, or the train car's connector.
# Accessing it gets you the next TrainCar, if one exists
self.next_car = None
Once you get this, it already starts to fall into place. Basically you have a bunch of nodes with each one referencing the next node! All that's left to do is to implement our operations:
# making a new linked list
class Train:
def __init__(self):
self.head_car = None
def add_car(self, data):
# make new car/node
new_car = TrainCar()
# add our data to the new car as desired
new_car.data = data
# This is where the magic happens! We're updating the reference inside
# the node we just made to point to the last car we were on.
# We are just adding a new car to the end of the train.
new_car.next_car = self.head_car
# Now we're setting the head car to be the one we just made!
self.head_car = new_car
Now, other operations - such as traversing the linked list and deleting nodes - are left to you.

What is the most effective way to incremente a large number of values in Python?

Okay, sorry if my problem seems a bit rough. I'll try to explain it in a figurative way, I hope this is satisfactory.
10 children. 5 boxes. Each child chooses three boxes. Each box is opened:
- If it contains something, all children selected this box gets 1 point
- Otherwise, nobody gets a point.
My question is about what I put in bold. Because in my code, there are lots of kids and lots of boxes.
Currently, I proceed as follows:
children = {"child_1" : 0, ... , "child_10": 0}
gp1 = ["child_3", "child_7", "child_10"] #children who selected the box 1
...
gp5 = ["child_2", "child_5", "child_8", "child_10"]
boxes = [(0,gp1), (0,gp2), (1,gp3), (1,gp4), (0,gp5)]
for box in boxes:
if box[0] == 1: #something inside
for child in box[1]:
children[child] += 1
I worry mainly about the for loop that assigns each child an extra point. Because in my final code, I have many many children, I fear that doing so would slow the program too.
Is there a more efficient way for all children of the same group may have their point faster?
Represent children as indices into arrays, not as strings:
childrenScores = [0] * 10
gp1 = [2,6,9] # children who selected box 1
...
gp5 = [1,4,7,9]
boxes = [(0,gp1), (0,gp2), (1,gp3), (1,gp4), (0,gp5)]
Then, you can store childrenScores as a NumPy array and use advanced indexing:
childrenScores = np.zeros(10, dtype=int)
...
for box in boxes:
if box[0]:
childrenScores[box[1]] += 1 # NumPy advanced indexing
This still involves a loop somewhere, but the loop is deep inside NumPy instead, which should provide a meaningful speedup.
The only speed up that I can think of is to use numpy arrays and stream the sum operation.
children[child] += np.ones(len(children[child]))
You should benchmark the operation and see if that is too slow for your business case.
What I would do
In the gpX lists don't save the "name of the child" (e.g. "child_10") but save a reference to the child's number of points.
How to do that
Using the fact that lists are objects in python, you can:
Change the children dict to look like: children = {"child_0": [0], "child_1": [0], ...} and so on.
When you assign to group, don't assign the key but assign the value (e.g. gp1.append(children["child_0"])).
The loop should then look like: for child in box[1]: child[0]+=1. This WILL update the children dict.
EDIT:
Why this is faster:
Because you leave out the part where you search for children[child], which might be costly.
This technique works because by storing the totals in a mutable type, and appending those values to the group lists, both the dict value and each box's list value will point to the same list entries, and changing one will change the other.
Two general points:
(1) Based on what you've told us, there's no reason to focus your energy on minor performance optimizations. Your time would be better spent thinking about ways to make your data structures less awkward and more communicative. A bunch of interrelated dicts, lists, and tuples quickly becomes difficult to maintain. For an alternative, see the example below.
(2) As the game designer, you understand that events follow a certain sequence: first the kids select their boxes, and later they discover whether they get points for them. But you don't have to implement it that way. A kid can choose a box and get points (or not) immediately. If there's a need to preserve the child's ignorance about such outcomes, the parts of your algorithm that depend on such ignorance can enforce that veil of secrecy as needed. The upshot: there is no need for a box to loop through its children, awarding points to each one; instead, award the points immediately to kids as boxes are selected.
import random
class Box(object):
def __init__(self, name):
self.name = name
self.prize = random.randint(0,1)
class Child(object):
def __init__(self, name):
self.name = name
self.boxes = []
self.score = 0
self._score = 0
def choose(self, n, boxes):
bs = random.sample(boxes, n)
for b in bs:
self.boxes.append(b)
self._score += b.prize
def reveal_score(self):
self.score = self._score
boxes = [Box(i) for i in range(5)]
kids = [Child(i) for i in range(10)]
for k in kids:
k.choose(3, boxes)
# Later in the game ...
for k in kids:
k.reveal_score()
print (k.name, k.score), '=>', [(b.name, b.prize) for b in k.boxes]
One way or another, you're going to be looping over the children, and your answer appears to avoid looping over children who don't get any points.
It might be slightly faster to use filter or itertools.ifilter to pick the boxes that have something in them:
import itertools
...
for box in itertools.ifilter(lambda x: x[0], boxes):
for child in box[1]
children[child] += 1
If you don't need to immediately print the number of points for every child, you could calculate it on demand, thus saving time. This could help if you only need to query a child every now and then for how many points it has. You can cache each result as you obtain is so you don't go about calculating it again the next time you need it.
Firstly, you'll need to know which groups a child belongs to. We'll store this information as map we'll call childToGroupsMap, which will map each child to an array holding the boxes it belongs to, like so:
childToGroupsMap = {}
for child in children:
childToGroupsMap[child[0]] = []
for box in boxes:
for child in box[1]:
if (box[1] not in childToGroupsMap[child]):
childToGroupsMap[child].append(box[1])
This constructs the reverse map from children to boxes.
It'll also help to have a map from each box to a boolean representing whether it's been opened:
boxToOpenedMap = {}
for box in boxes:
boxToOpenedMap[box[1]] = box[0]
Now, when someone queries how many points a child has, you can go through each of the boxes it belongs to (using childToGroupsMap, of course), and simply count how many of those boxes have been mapped to 1 in the map boxes:
def countBoxesForChild(child):
points = 0
for box in childToGroupsMap[child]
if boxToOpenedMap[box] == 1:
points += 1
return points
To make this better, you can cache the resulting number of points. Make a map like so:
childToPointsCalculated = {}
for child in children:
childToPointsCalculated[child[0]] = -1
Where the -1 denotes that we don't yet know how many points this child has.
Finally, you can modify your countBoxesForChild function to exploit the cache:
def countBoxesForChild(child):
if childToPointsCalculated[child] != -1
return childToPointsCalculated[child]
points = 0
for box in childToGroupsMap[child]
if boxToOpenedMap[box] == 1:
points += 1
childToPointsCalculated[child] = points
return points

Python Recursion through objects and child objects, Print child depth numbers

I have a simple class with an attribute that can contain a list of objects of the same class
class BoxItem:
def __init__(self, name, **kw):
self.name = name
self.boxItems = []
... #more attributes here
box1 = BoxItem('Normal Box')
box2 = BoxItem('Friendly Box')
box3 = BoxItem('Cool Box')
box4 = BoxItem('Big Box', [box1, box2]) #contains some children
example = BoxItem('Example Box', [box4,box3]) #contains another level of children
Working with our 'example' box object, I would like to maneuver through the depths of all possible box children it may have, and print out the objects formatted like so:
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box
The Tabbing between isn't needed, just wanting to show the tree format clearly. I am able to walk down the objects themselves and print out their titles, but I am not able to print out the front numbers that show the parent/child relationship. (1, 1.1, 1.2...)
Thanks in advance for your help :)
Edit
Here is what I have been working with so far
def print_boxes(box_list):
node_count = 0
for box in box_list:
node_count += 1
print str(node_count)+' '+box.name #prints out the root box
recursive_search(box,node_count)
def recursive_search(box,node_count): #recursive automatically
level = 0
for child in box.boxItems:
level += 1
for x in range(len(child.boxItems)):
print x+1 #this prints out some proper numbers
print "level: "+str(level) #experiment with level
print child.name #prints out child box name
recursive_search(child,node_count) #runs the function again inside the function
I think it might be more helpful to you if I post a working example of how to do this, as opposed to going through where you code is having problems. We might get to the point of understanding a lot faster that way. Your code has the correct idea that it needs to track the depth as it goes. But the only thing it is missing is a sense of nested depth (tree). It only knows the previous node_count, and then its current child count.
My example uses a closure to start the depth tracking object, and then creates an inner function to do the recursive part.
def recurse(box):
boxes = not isinstance(box, (list, tuple)) and [box] or box
depth = [1]
def wrapped(box):
depthStr = '.'.join([str(i) for i in depth])
print "%s %s" % (depthStr, box.name)
depth.append(1)
for child in box.boxItems:
wrapped(child)
depth[-1] += 1
depth.pop()
for box in boxes:
wrapped(box)
depth[0] += 1
Sample output from your examples:
>>> recurse(example)
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box
>>> recurse([example, example])
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box
2 Example Box
2.1 Big Box
2.1.1 Normal Box
2.1.2 Friendly Box
2.2 Cool Box
Breaking this down:
We first accept a box argument, and automatically convert it locally to a list, if you had only passed in a single box item. That way you can pass either one box objects, or a list/tuple of them.
depth is our depth tracker. Its a list of ints that we will build up and shrink down as the recursion happens. It starts at 1 for the first item / first level. Over time it can look like this: [1,1,2,3,1] depending on how deep it traverses. This is the major difference between my code and yours. Each recursion has access to this state.
Now we have this inner wrapped function. Its going to take a current box item and print it, and then iterate over its children. We get our print string by joining the current depth list, and then the name.
Every time we drop down into a child list, we add a starting level 1 to our depth list, and when we come out of that child loop, we pop it back off again. For every child in that loop, we increment that last item up.
Outside of that wrapped inner function, we then start the whole thing by looping over our initial boxes, calling wrapped and then incrementing our first level.
The inner wrapped function uses the depth list in a closure. I am willing to bet that others can offer some further improvements on this, but its what I came up with for an example.
Note about the args for the function
We could have also designed recurse to instead take a variable length argument list, instead of checking for a list. It would look like this (and would get rid of that first boxes = check):
def recurse(*boxes):
#boxes will always come in as a tuple no matter what
>>> recurse(example)
>>> recurse(example, example, example)
And if you originally start with a list of box items, you can pass it by doing:
>>> boxes = [example, example, example]
>>> recurse(*example) # this will unpack your list into args
You have two options:
Keep track of the additional information as an additional parameter in your recursion, e.g. myRecursiveFunction(..., ancestry=[])
Have each BoxItem keep track of its parent, whenever it is embedded in a BoxItem (in the __init__ constructor, set child.parent = self for each child). This is bad if you plan to have a BoxItem in more than one box.

Categories