while condition true if value appears in list of tuples

Viewed 75

I'm new to Python and got stuck while implementing an algorithm for drawing graphs. I have a list of tuples that contain certain nodes and their 'level' (for layering). Initially that looks like this:

[(0, 10), (1, 'empty'), (2, 'empty'), (3, 'empty'), (4, 'empty'), (5, 'empty'), (6, 'empty'), (7, 'empty'), (8, 'empty'), (9, 'empty')] (node,level)

Now I need to assign levels to these nodes as long as any node has no level, respective the 'empty' attribute.

I tried several structures of possible while conditions, that syntactically weren't wrong but semantically didn't make any sense, since the while loop didn't terminate:

while (True for node, level in g.nodes(data='level') if level == 'empty'):

or

while ( 'empty' in enumerate(g.nodes(data='level') ):

and certain other similar constructs, that didn't work and I don't remember..

Until now it doesn't seem clear to me why this won't work - python didn't even enter the while loop with these conditions. Can you explain me why and hand me a clue how to fix it?

hint: g.nodes(data='level') is a networkx function, that returns the upper list of tuples

5 Answers

So the things you have in the parentheses are generator expressions and they just always evaluate to True.

You have the right idea but maybe need to learn a bit more about these expressions and what to do with them. In your first suggestions, here is what I _ think_ you mean:

while any(level == 'empty' for node, level in g.nodes(data='level')):
  ...

In your version, what you do is create a generator expression that will container a number of True values, one for each level that is empty. However, even if that generator expression will have no elements in it, it is in and of itself not an empty object, and thus it evaluates to True.

You can try this out:

bool([]) # --> False because empty sequence
bool(list(range(1, -10))) # --> False because empty sequence
bool((i for i in range(1,-10))) # --> True because non-None generator expression

So you need to turn your generator expression into a truth value that actually reflects whether it has any true elements in it, and that's what the any function does: Take an iterator (or a generator expression) and return true if any of its elements are true.

You're trying to make an elemental check do your searching for you. Instead, concentrate on extracting exactly the values you need to check. Let's start with the list of tuples as

g = [(0, 10), (1, 'empty'), (2, 'empty'), (3, 'empty'), (4, 'empty'),
     (5, 'empty'), (6, 'empty'), (7, 'empty'), (8, 'empty'), (9, 'empty')]

Now get a list of only the levels:

level = [node[1] for node in g]

Now, your check is simply

while "empty" in level:
    # Assign levels as appropriate
    # Repeat the check for "empty"
    level = [node[1] for node in g]

If you're consistently updating the main graph, g, then fold the level extraction into the while:

while "empty" in [node[1] for node in g]:
    # Assign levels

Originally, your while failed because you return True so long as there's anything in g:

while (True for node, level in ...)

You have a good approach, but made it one level too complex, and got stuck with True.

Your enumerate attempt fails because you search for a string in an enumerator, rather than in its returned values. You could make a list of the returned values, but then we're back where we started, with a list of tuples. This would compare "empty" against each tuple, and fail to find the desired string -- it's one level farther down.

The other answers may be best. But the specific test you requested can be written a few ways:

This is similar to what you had, but creates a list instead of a generator. The list will evaluate as False if it is empty, but the generator doesn't.

while [True for node, level in g.nodes(data='level') if level == 'empty']:
   ...

This is an equally effective version. The list just has to be empty or not; it doesn't matter whether the elements are True or False or something else:

while [node for node, level in g.nodes(data='level') if level == 'empty']:
   ...

Or you can use Python's any function, which was made for this and will be a little more efficient (it stops checking elements after the first match). This can use a generator or a list.

while any(level == 'empty' for node, level in g.nodes(data='level')):
   ...

If you're just trying to assign levels for each if one is empty then a simple for loop should suffice:

data = [(0, 10), (1, 'empty'), (2, 'empty'), (3, 'empty'), (4, 'empty'), (5, 'empty'), (6, 'empty'), (7, 'empty'), (8, 'empty'), (9, 'empty')]
output = []

for node, level in data:
    if level == 'empty':
        level = ? #question mark is whatever level logic you wish to insert
    output.append((node, level))

It is often more Pythonic just to use a for loop:

nodes = [(0, 10),
         (1, 'empty'),
         (2, 'empty'),
         (3, 'empty'),
         (4, 'empty'),
         (5, 'empty'),
         (6, 'empty'),
         (7, 'empty'),
         (8, 'empty'),
         (9, 'empty')]

for node in nodes:
    if node[1]=="empty":
        node[1] = set_level_func() # customise this function as you wish
    
Related