简体   繁体   中英

Checking if a python iterable is empty without relying on `len` or consuming the iterator

I've written the following code to reverse the edges of a graph, expressed as an adjacency list Mapping .

from typing import Mapping, Any, Sized
from collections import defaultdict

def edge_reversed_graph(g: Mapping[Any, Sized]):
    d = defaultdict(set)
    for src, dst_nodes in g.items():
        if len(dst_nodes):
            for dst in dst_nodes:
                d[dst].add(src)
        elif src not in d:
            d[src] = set()
    return d

Example:

>>> graph = dict(a='c', b='cd', c='abd', e='')
>>> assert edge_reversed_graph(graph) == {'c': {'a', 'b'}, 'd': {'c', 'b'}, 'a': {'c'}, 'b': {'c'}, 'e': set()}

I'd like to be able to just require the graph to be Mapping[Any, Iterable] , that is the adjacency "list" not to necessarily have to have a length. This is achievable using a flag variable, but I'm looking for a more elegant and efficient solution, otherwise making the function work with a larger group of objects wouldn't be worth it to me.

Checking whether the container/iterable of outgoing edges is empty is a red herring: Each node must always be ensured to exist.

  • If there are ingoing edges, it might have been added before. The node may not be overwritten even if it has no outgoing edges.
  • If there are outgoing but no ingoing edges, it must still be added. The node must exist regardless of ingoing edges.

This removes the need to check whether there are outgoing edges: Always add the src node if it does not exist . Always iterate all outgoing edges, of which there might be none.

from typing import Mapping, TypeVar, Iterable, Set
from collections import defaultdict

T = TypeVar('T')

def edge_reversed_graph(g: Mapping[T, Iterable[T]]) -> Mapping[T, Set[T]]:
    d = defaultdict(set)
    for src, dst_nodes in g.items():
        d.setdefault(src, set())  # add node if not present
        for dst in dst_nodes:     # empty iterable does nothing
            d[dst].add(src)
    return d

Similar to what I assume you mean with flag (having a bool variable set to True inside the loop), but more efficient:

def edge_reversed_graph(g: Mapping[Any, Iterable]):
    d = defaultdict(set)
    none = object()
    for src, dst_nodes in g.items():
        dst = none
        for dst in dst_nodes:
            d[dst].add(src)
        if dst is none:
            d[src] = set()
    return d

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM