Creating a list of positional relationships between items

How can I form a flat ordered list from a set of elements, each of which may have requirements that they appear before and / or after some other element in the list?

Sample input
-----------------------
3: before 5 and after 2
8: before 5
2: before 3
5: no constraint

Sample output
-------------
[2, 3, 8, 5]

Obviously, the general solution to this problem is not unique (consider the case of many elements without restrictions), which is excellent - any result that satisfies the restrictions will be satisfied.

I also know that there are a lot of errors (duplicate elements, two elements that both want to be in front of each other, etc.). Right now I'm interested in the “happy path” - I will add error handling later.

Here are some test cases in Python. They are far from comprehensive, but this should be enough to give you an idea:

def test_some_unconstrained_elements():
    l = ListBuilder()
    l.add(1, after=None, before=None)
    l.add(7, after=None, before=None)
    assert set(l.to_list()) == {1, 7}

def test_element_which_should_appear_after_already_added_element():
    l = ListBuilder()
    l.add(5, after=None, before=None)
    l.add(3, after=5, before=None)
    assert l.to_list() == [5, 3]

def test_element_which_should_appear_after_element_added_later():
    l = ListBuilder()
    l.add(3, after=5, before=None)
    l.add(5, after=None, before=None)
    assert l.to_list() == [5, 3]

def test_element_which_should_appear_between_two_already_added_elements():
    l = ListBuilder()
    l.add(4, after=None, before=None)
    l.add(2, after=None, before=None)
    l.add(6, after=2, before=4)
    assert l.to_list() == [2, 6, 4]

def test_two_elements_either_side_of_new_element():
    l = ListBuilder()
    l.add(4, after=6, before=None)
    l.add(2, after=None, before=6)
    l.add(6, after=None, before=None)
    assert l.to_list() == [2, 6, 4]

def test_element_which_should_appear_after_missing_element():
    l = ListBuilder()
    l.add(4, after=6, before=None)
    assert l.to_list() == [4]

def test_two_elements_which_should_appear_after_the_same_element():
    l = ListBuilder()
    l.add(4, after=None, before=None)
    l.add(6, after=4, before=None)
    l.add(8, after=4, before=None)
    assert l[0] == 4
    assert set(l[1:]) == {6, 8}

def test_fully_constrained_short_list():
    l = ListBuilder()
    l.add(3, after=4, before=None)
    l.add(4, after=5, before=3)
    l.add(5, after=None, before=4)
    assert l.to_list() == [5, 4, 3]

NB. . , ( ; , ), : (

+4
2

, - ( ) , . :

def toposort2(data):
    # modified from http://rosettacode.org/wiki/Topological_sort#Python
    for k, v in data.items():
        v.discard(k) # Ignore self dependencies
    extra_items_in_deps = reduce(set.union, data.values()) - set(data.keys())
    data.update({item:set() for item in extra_items_in_deps})
    while True:
        ordered = set(item for item,dep in data.items() if not dep)
        if not ordered:
            break
        for x in sorted(ordered):
            yield x
        data = {item: (dep - ordered) for item,dep in data.items()
                if item not in ordered}
    if data:
        raise ValueError("a cyclic dependency exists")

def toposort_wrap(data):
    dep_dict = {}
    for d in data:
        for bef in d.get("before", ()):
            dep_dict.setdefault(bef, set()).add(d["value"])
        dep_dict.setdefault(d["value"], set()).update(d.get("after", ()))
    print dep_dict
    result = list(toposort2(dep_dict))
    return result

>>> data = [dict(value=3, before=(5,), after=(2,)),
...         dict(value=8, before=(5,)),
...         dict(value=2, before=(3,)),
...         dict(value=5)]
>>> toposort_wrap(data)
{8: set([]), 2: set([]), 3: set([2]), 5: set([8, 3])}
[2, 8, 3, 5]

(, , - , .)

+3

. : node. a b, b a.

0

Source: https://habr.com/ru/post/1524174/


All Articles