tags:

views:

414

answers:

9

What is the pythonic way to perform this loop. I'm trying to pick a random key that will return a subtree and not the root. Hence: 'parent == None' cannot be true. Nor can 'isRoot==True' be true.

thekey = random.choice(tree.thedict.keys())
while (tree.thedict[thekey].parent == None)or(tree.thedict[thekey].isRoot == True):
        thekey = random.choice(tree.thedict.keys())
.......

edit: it works now

A: 

break that while clause up into a few chunks, it will be easier to understand and debug.

Dustin Getz
Browse some Python code and see how often you see "entire while clauses" in parentheses.
Triptych
Yes. I like THC4k and Bonvallet's solutions
Peter Stewart
+1  A: 
thekey = random.choice(tree.thedict.keys())
parent = thedict[thekey].parent
while parent is None or parent.isRoot:
    thekey = random.choice(tree.thedict.keys())
    parent = thedict[thekey].parent
Jeff Ober
You negated the first conditional test.
Triptych
much more readable.
Jweede
Also, `thekey.parent` makes no sense.
Triptych
thekey is just a key and not a node in the tree. Also I think he wants to know if thedict[thekey] is the root, not if the parent is the root.
ntownsend
@ ntownsend, you're right. I want to know if the key is the root , not if the parent is the root.
Peter Stewart
+3  A: 

get a random subtree that is not the root

not_root_nodes = [key, node for key,node in tree.thedict.iteritems() if not ( node.parent is None or node.isRoot)]
item = random.choice( not_root_nodes )
THC4k
This won't work, since `random.choice` doesn't take generators as arguments. The argument should be a list.
Roberto Bonvallet
Add brackets [generator].
Tristan
thanks i edited it
THC4k
This builds a list of all the nodes in the tree, each time you run it; then it picks one node and discards the list. For large trees, this will be very expensive. I hope we can find a solution that is more efficient.
steveha
+3  A: 
key = random.choice([key for key, subtree in tree.thedict.items()
                         if subtree.parent and not subtree.isRoot])

(Corrected after comments and question edition)

Roberto Bonvallet
That doesn't exactly do the same thing. This may be omitting loop body code from a larger loop that does stuff to thedict.
Jeff Ober
There is no larger loop. This looks very succinct.
Peter Stewart
This is a very expensive way to solve the problem. You are building a list of every key and subtree in the tree, which will be slow and will take a lot of memory if the tree is large. Then this list is used just once and discarded. If you know the tree will always be small, then this succinct code might be a good choice. But if you were putting this in a library, I wouldn't do it this way. Other solutions, which use a loop to retry if the random choice is the root, will almost always execute the loop exactly once, and don't need any extra storage.
steveha
If this is a one-time operation, I think the succintness is worth the little penalty. If the choice needs to be made several times and it has been found to be the bottleneck, one choice would be to create the candidate list once and then reuse it.
Roberto Bonvallet
...besides, the question was about pythonicity, not efficiency :P
Roberto Bonvallet
Well, I have a personal preference for code that is both simple and flexible. Even if you know today that the code will only be run once, do you know the program will never be changed to need that code to run multiple times? If you cache the list, you need to be sure the tree didn't change after you cached it. In this case, there is a simple solution that is also fast (see my answer), so I vote for it. Socking, I know, that I would prefer my own answer, but there you go. :-) You don't have to agree, of course.
steveha
Er, "shocking" is spelled "shocking" and not "socking". Sorry about that.
steveha
+1  A: 

I think that's a bit better:

theDict = tree.thedict

def getKey():
    return random.choice(theDict.keys())

theKey = getKey()

while theDict[thekey].parent in (None, True):
    thekey = getKey()

What do you think?

Johannes Weiß
What does it mean for theDict[thekey].parent to be True? You need to check if theDict[thekey].isRoot is True.
ntownsend
I think he was checking for isRoot==True, not parent==True. :-)
Ken
I wanted to do it without using a function
Peter Stewart
A: 
def is_root(v): 
  assert (v.parent != None) == (v.isRoot)
  return v.isRoot
  #note how dumb this function looks when you guarantee that assertion

def get_random_nonroot_key():
  while True:
    thekey = random.choice(tree.thedict.keys())
    value = tree.thedict[thekey]
    if not is_root(value): return key

or a refactoring of Roberto Bonvallet's answer

def get_random_nonroot_key():
  eligible_keys = [k for k, v in tree.thedict.items() if not is_root(v)]
  return random.choice(eligible_keys)
Dustin Getz
A: 

I think your while condition is flawed:

I think you expect this: tree.thedict[thekey].parent == None
should be equal to this: tree.thedict[thekey].parent.isRoot == True

When in fact, for both to mean "this node is not the root", you should change the second statement to: tree.thedict[thekey].isRoot == True

As written, your conditional test says "while this node is the root OR this node's parent is the root". If your tree structure is a single root node with many leaf nodes, you should expect an infinite loop in this case.

Here's a rewrite:

thekey = random.choice(k for k in tree.thedict.keys() if not k.isRoot)
Triptych
Yes, you're correct. After I changed my code it now works
Peter Stewart
A: 
thekey = random.choice(tree.thedict.keys())
parent = tree.thedict[thekey].parent
while parent is None or tree.thedict[thekey].isRoot:
    thekey = random.choice(tree.thedict.keys())
    parent = thedict[thekey].parent
ntownsend
That looks a little more readable. I see you caught my error referencing '.parent' in the second condition.
Peter Stewart
A: 

Personally, I don't like the repetition of initializing thekey before the while loop and then again inside the loop. It's a possible source of bugs; what happens if someone edits one of the two initializations and forgets to edit the other? Even if that never happens, anyone reading the code needs to check carefully to make sure both initializations match perfectly.

I would write it like so:

while True:
    thekey = random.choice(tree.thedict.keys())
    subtree = tree.thedict[thekey]
    if subtree.parent is not None and not subtree.isRoot:
        break

P.S. If you really just want the subtree, and don't care about the key needed to lookup the subtree, you could even do this:

while True:
    subtree = random.choice(tree.thedict.values())
    if subtree.parent is not None and not subtree.isRoot:
        break

Some people may not like the use of "while True:" but that is the standard Python idiom for "loop forever until something runs break". IMHO this is simple, clear, idiomatic Python.

P.P.S. This code should really be wrapped in an if statement that checks that the tree has more than one node. If the tree only has a root node, this code would loop forever.

steveha
I didn't know about 'while true: break ' and would not have seen it as pythonic. I will be a handy thing to know.
Peter Stewart