Module sverchok.utils.tree_walk

Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE


from __future__ import annotations

from abc import ABC, abstractmethod
from collections import deque
from itertools import count
from typing import Generator, List, TypeVar, Generic, Callable, Iterable, Iterator

T = TypeVar('T')
NextFunc = Callable[[T], Iterable[T]]


def bfs_walk(nodes: Iterable[T], next_: NextFunc) -> Iterator[T]:
    """
    Walk from the current node, it will visit all next nodes
    First will be visited children nodes than children of children nodes etc.
    https://en.wikipedia.org/wiki/Breadth-first_search
    """
    waiting_nodes = deque(nodes)
    discovered = set(nodes)

    for i in range(20000):
        if not waiting_nodes:
            break
        n = waiting_nodes.popleft()
        yield n
        for next_node in next_(n):
            if next_node not in discovered:
                waiting_nodes.append(next_node)
                discovered.add(next_node)
    else:
        raise RecursionError(f'The tree has either more then={20000} nodes '
                             f'or most likely it is circular')


def recursion_dfs_walk(nodes: Iterable[T], next_: NextFunc, _count=None) -> Iterator[T]:
    if _count is None:
        _count = count(1)
    for node in nodes:
        yield node
        yield from recursion_dfs_walk(next_(node), next_, _count)
    if (i := next(_count)) and i > 20000:
        raise RecursionError(f'The tree has either more then={20000} nodes '
                             f'or most likely it is circular')


class Node(ABC):
    @property
    @abstractmethod
    def next_nodes(self) -> List[Node]: ...

    @property
    @abstractmethod
    def last_nodes(self) -> List[Node]: ...

    @property
    def is_input(self) -> bool:
        """doesn't have nodes before"""
        return not bool(self.last_nodes)

    @property
    def is_output(self):
        """doesn't have nodes after"""
        return not bool(self.next_nodes)


NodeType = TypeVar('NodeType', bound=Node)


class Tree(ABC, Generic[NodeType]):
    @property
    @abstractmethod
    def nodes(self): ...  # something iterable

    @property
    def input_nodes(self) -> List[NodeType]:
        """Nodes which don't have nodes before"""
        return [node for node in self.nodes if node.is_input]

    @property
    def output_nodes(self) -> List[NodeType]:
        """Nodes which don't have nodes after"""
        return [node for node in self.nodes if node.is_output]

    @staticmethod
    def bfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') -> Generator[NodeType]:
        """
        Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction
        First will be visited children nodes than children of children nodes and etc
        """
        # https://en.wikipedia.org/wiki/Breadth-first_search
        waiting_nodes = deque(nodes)
        discovered = set(nodes)

        safe_counter = count()
        max_node_number = 20000
        while waiting_nodes:
            node = waiting_nodes.popleft()
            yield node
            for next_node in (node.next_nodes if direction == 'FORWARD' else node.last_nodes):
                if next_node not in discovered:
                    waiting_nodes.append(next_node)
                    discovered.add(next_node)

            if next(safe_counter) > max_node_number:
                raise RecursionError(f'The tree has either more then={max_node_number} nodes '
                                     f'or most likely it is circular')

    @staticmethod
    def dfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') -> Generator[NodeType]:
        """
        Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction
        First will be visited first child and its children then second child and etc
        """
        # https://en.wikipedia.org/wiki/Depth-first_search
        stack = nodes
        visited = set()

        safe_counter = count()
        max_node_number = 20000
        while stack:
            current_node = stack.pop()
            yield current_node
            visited.add(current_node)
            for next_node in current_node.next_nodes if direction == 'FORWARD' else current_node.last_nodes:
                if next_node not in visited:
                    stack.append(next_node)

            if next(safe_counter) > max_node_number:
                raise RecursionError(f'The tree has either more then={max_node_number} nodes '
                                     f'or most likely it is circular')

    @staticmethod
    def sorted_walk(to_nodes: List[NodeType]) -> Generator[NodeType]:
        """
        If tree is 0--1--2--3 and node "3" is given the method will return nodes in next order [0, 1, 2, 3]
        """
        # https://en.wikipedia.org/wiki/Topological_sorting
        stack = []
        discovered = set()  # gray color
        visited = set()  # black color

        safe_counter = count()
        max_node_number = 20000

        for to_node in to_nodes:
            if to_node in visited:
                continue
            else:
                stack.append(to_node)
                discovered.add(to_node)

            while stack:
                node = stack[-1]
                next_undiscovered_node = None
                for next_node in node.last_nodes:
                    if next_node in discovered:
                        raise RecursionError(f'The tree is cycled')
                    if next_node not in visited:
                        next_undiscovered_node = next_node
                        break

                if next_undiscovered_node is not None:
                    stack.append(next_undiscovered_node)
                    discovered.add(next_undiscovered_node)
                else:
                    # last node in stack does not have next nodes or all of them are visited
                    stack.pop()
                    yield node
                    visited.add(node)
                    discovered.remove(node)

                if next(safe_counter) > max_node_number:
                    raise RuntimeError(f'The tree has either more then={max_node_number} nodes '
                                       f'or most likely it is circular')

Functions

def bfs_walk(nodes: Iterable[T], next_: NextFunc) ‑> Iterator[~T]

Walk from the current node, it will visit all next nodes First will be visited children nodes than children of children nodes etc. https://en.wikipedia.org/wiki/Breadth-first_search

Expand source code
def bfs_walk(nodes: Iterable[T], next_: NextFunc) -> Iterator[T]:
    """
    Walk from the current node, it will visit all next nodes
    First will be visited children nodes than children of children nodes etc.
    https://en.wikipedia.org/wiki/Breadth-first_search
    """
    waiting_nodes = deque(nodes)
    discovered = set(nodes)

    for i in range(20000):
        if not waiting_nodes:
            break
        n = waiting_nodes.popleft()
        yield n
        for next_node in next_(n):
            if next_node not in discovered:
                waiting_nodes.append(next_node)
                discovered.add(next_node)
    else:
        raise RecursionError(f'The tree has either more then={20000} nodes '
                             f'or most likely it is circular')
def recursion_dfs_walk(nodes: Iterable[T], next_: NextFunc) ‑> Iterator[~T]
Expand source code
def recursion_dfs_walk(nodes: Iterable[T], next_: NextFunc, _count=None) -> Iterator[T]:
    if _count is None:
        _count = count(1)
    for node in nodes:
        yield node
        yield from recursion_dfs_walk(next_(node), next_, _count)
    if (i := next(_count)) and i > 20000:
        raise RecursionError(f'The tree has either more then={20000} nodes '
                             f'or most likely it is circular')

Classes

class Node

Helper class that provides a standard way to create an ABC using inheritance.

Expand source code
class Node(ABC):
    @property
    @abstractmethod
    def next_nodes(self) -> List[Node]: ...

    @property
    @abstractmethod
    def last_nodes(self) -> List[Node]: ...

    @property
    def is_input(self) -> bool:
        """doesn't have nodes before"""
        return not bool(self.last_nodes)

    @property
    def is_output(self):
        """doesn't have nodes after"""
        return not bool(self.next_nodes)

Ancestors

  • abc.ABC

Subclasses

Instance variables

var is_input : bool

doesn't have nodes before

Expand source code
@property
def is_input(self) -> bool:
    """doesn't have nodes before"""
    return not bool(self.last_nodes)
var is_output

doesn't have nodes after

Expand source code
@property
def is_output(self):
    """doesn't have nodes after"""
    return not bool(self.next_nodes)
var last_nodes : List[Node]
Expand source code
@property
@abstractmethod
def last_nodes(self) -> List[Node]: ...
var next_nodes : List[Node]
Expand source code
@property
@abstractmethod
def next_nodes(self) -> List[Node]: ...
class Tree

Helper class that provides a standard way to create an ABC using inheritance.

Expand source code
class Tree(ABC, Generic[NodeType]):
    @property
    @abstractmethod
    def nodes(self): ...  # something iterable

    @property
    def input_nodes(self) -> List[NodeType]:
        """Nodes which don't have nodes before"""
        return [node for node in self.nodes if node.is_input]

    @property
    def output_nodes(self) -> List[NodeType]:
        """Nodes which don't have nodes after"""
        return [node for node in self.nodes if node.is_output]

    @staticmethod
    def bfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') -> Generator[NodeType]:
        """
        Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction
        First will be visited children nodes than children of children nodes and etc
        """
        # https://en.wikipedia.org/wiki/Breadth-first_search
        waiting_nodes = deque(nodes)
        discovered = set(nodes)

        safe_counter = count()
        max_node_number = 20000
        while waiting_nodes:
            node = waiting_nodes.popleft()
            yield node
            for next_node in (node.next_nodes if direction == 'FORWARD' else node.last_nodes):
                if next_node not in discovered:
                    waiting_nodes.append(next_node)
                    discovered.add(next_node)

            if next(safe_counter) > max_node_number:
                raise RecursionError(f'The tree has either more then={max_node_number} nodes '
                                     f'or most likely it is circular')

    @staticmethod
    def dfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') -> Generator[NodeType]:
        """
        Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction
        First will be visited first child and its children then second child and etc
        """
        # https://en.wikipedia.org/wiki/Depth-first_search
        stack = nodes
        visited = set()

        safe_counter = count()
        max_node_number = 20000
        while stack:
            current_node = stack.pop()
            yield current_node
            visited.add(current_node)
            for next_node in current_node.next_nodes if direction == 'FORWARD' else current_node.last_nodes:
                if next_node not in visited:
                    stack.append(next_node)

            if next(safe_counter) > max_node_number:
                raise RecursionError(f'The tree has either more then={max_node_number} nodes '
                                     f'or most likely it is circular')

    @staticmethod
    def sorted_walk(to_nodes: List[NodeType]) -> Generator[NodeType]:
        """
        If tree is 0--1--2--3 and node "3" is given the method will return nodes in next order [0, 1, 2, 3]
        """
        # https://en.wikipedia.org/wiki/Topological_sorting
        stack = []
        discovered = set()  # gray color
        visited = set()  # black color

        safe_counter = count()
        max_node_number = 20000

        for to_node in to_nodes:
            if to_node in visited:
                continue
            else:
                stack.append(to_node)
                discovered.add(to_node)

            while stack:
                node = stack[-1]
                next_undiscovered_node = None
                for next_node in node.last_nodes:
                    if next_node in discovered:
                        raise RecursionError(f'The tree is cycled')
                    if next_node not in visited:
                        next_undiscovered_node = next_node
                        break

                if next_undiscovered_node is not None:
                    stack.append(next_undiscovered_node)
                    discovered.add(next_undiscovered_node)
                else:
                    # last node in stack does not have next nodes or all of them are visited
                    stack.pop()
                    yield node
                    visited.add(node)
                    discovered.remove(node)

                if next(safe_counter) > max_node_number:
                    raise RuntimeError(f'The tree has either more then={max_node_number} nodes '
                                       f'or most likely it is circular')

Ancestors

  • abc.ABC
  • typing.Generic

Subclasses

Static methods

def bfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') ‑> Generator[NodeType]

Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction First will be visited children nodes than children of children nodes and etc

Expand source code
@staticmethod
def bfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') -> Generator[NodeType]:
    """
    Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction
    First will be visited children nodes than children of children nodes and etc
    """
    # https://en.wikipedia.org/wiki/Breadth-first_search
    waiting_nodes = deque(nodes)
    discovered = set(nodes)

    safe_counter = count()
    max_node_number = 20000
    while waiting_nodes:
        node = waiting_nodes.popleft()
        yield node
        for next_node in (node.next_nodes if direction == 'FORWARD' else node.last_nodes):
            if next_node not in discovered:
                waiting_nodes.append(next_node)
                discovered.add(next_node)

        if next(safe_counter) > max_node_number:
            raise RecursionError(f'The tree has either more then={max_node_number} nodes '
                                 f'or most likely it is circular')
def dfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') ‑> Generator[NodeType]

Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction First will be visited first child and its children then second child and etc

Expand source code
@staticmethod
def dfs_walk(nodes: List[NodeType], direction: str = 'FORWARD') -> Generator[NodeType]:
    """
    Walk from the current node, it will visit all next nodes in FORWARD of BACKWARD direction
    First will be visited first child and its children then second child and etc
    """
    # https://en.wikipedia.org/wiki/Depth-first_search
    stack = nodes
    visited = set()

    safe_counter = count()
    max_node_number = 20000
    while stack:
        current_node = stack.pop()
        yield current_node
        visited.add(current_node)
        for next_node in current_node.next_nodes if direction == 'FORWARD' else current_node.last_nodes:
            if next_node not in visited:
                stack.append(next_node)

        if next(safe_counter) > max_node_number:
            raise RecursionError(f'The tree has either more then={max_node_number} nodes '
                                 f'or most likely it is circular')
def sorted_walk(to_nodes: List[NodeType]) ‑> Generator[NodeType]

If tree is 0–1–2–3 and node "3" is given the method will return nodes in next order [0, 1, 2, 3]

Expand source code
@staticmethod
def sorted_walk(to_nodes: List[NodeType]) -> Generator[NodeType]:
    """
    If tree is 0--1--2--3 and node "3" is given the method will return nodes in next order [0, 1, 2, 3]
    """
    # https://en.wikipedia.org/wiki/Topological_sorting
    stack = []
    discovered = set()  # gray color
    visited = set()  # black color

    safe_counter = count()
    max_node_number = 20000

    for to_node in to_nodes:
        if to_node in visited:
            continue
        else:
            stack.append(to_node)
            discovered.add(to_node)

        while stack:
            node = stack[-1]
            next_undiscovered_node = None
            for next_node in node.last_nodes:
                if next_node in discovered:
                    raise RecursionError(f'The tree is cycled')
                if next_node not in visited:
                    next_undiscovered_node = next_node
                    break

            if next_undiscovered_node is not None:
                stack.append(next_undiscovered_node)
                discovered.add(next_undiscovered_node)
            else:
                # last node in stack does not have next nodes or all of them are visited
                stack.pop()
                yield node
                visited.add(node)
                discovered.remove(node)

            if next(safe_counter) > max_node_number:
                raise RuntimeError(f'The tree has either more then={max_node_number} nodes '
                                   f'or most likely it is circular')

Instance variables

var input_nodes : List[~NodeType]

Nodes which don't have nodes before

Expand source code
@property
def input_nodes(self) -> List[NodeType]:
    """Nodes which don't have nodes before"""
    return [node for node in self.nodes if node.is_input]
var nodes
Expand source code
@property
@abstractmethod
def nodes(self): ...  # something iterable
var output_nodes : List[~NodeType]

Nodes which don't have nodes after

Expand source code
@property
def output_nodes(self) -> List[NodeType]:
    """Nodes which don't have nodes after"""
    return [node for node in self.nodes if node.is_output]