def should_p_be_expanded(p, expanded): # expanded is a set of pairs (v, i)
v = p.v
for i, p1 in enumerate(sorted(c.all_positions_for_v(v))):
if p1 != p: continue
return (v, i) in expanded
return False
A pair (v, count) where count is integer showing how many times this same v node was seen during the traversal, can be used to make distinction between clones.
This scheme can also be broken by outline change, when this change affects ordering of clones, but it is resilient against inserting and deleting new nodes, and moving nodes that are not ancestors of a node.
Work on #1631 has taken an unexpected turn. The issue has been renamed "Eliminate v.expandedPositions". Instead of using this list (and trying to update it), c.shouldBeExpanded(p) uses no data that must be kept in sync with the outline.
def p_to_count(p):
v = p.v
if len(v.parents) == 1:
return 0
for i, p1 in enumerate(sorted(c.all_positions_for_v(v))):
if p1 == p:
return i
return -1 # position isn't valid
class ExpandController:
def __init__(self, c):
counts = {}
self._expanded = expanded = set()
for _p in c.all_positions():
i = counts.get(_p.v, 0)
if _p.isExpanded():
expanded.add((_p.v, i))
counts[_p.v] = i + 1
def should_be_expanded(self, p):
v = p.v
i = p_to_count(p)
return (v, i) in self._expanded
def expand_position(self, p):
i = p_to_count(p)
if i > -1:
v = p.v
self._expanded.add((v, i))
def collapse_position(self, p):
i = p_to_count(p)
if i > -1:
v = p.v
self._expanded.remove((v, i))
def toggle_position(self, p):
i = p_to_count(p)
if i > -1:
v = p.v
if (v, i) in self._expanded:
self._expanded.remove((v, i))
else:
self._expanded.add((v, i))
# now let's test if both methods give the same result
ec = ExpandController(c)
expandables = [x for x in c.all_positions() if x.v.children]
# only nodes that have children can be expanded
for p1 in expandables:
a = p1.isExpanded()
assert a == ec.should_be_expanded(p1), repr((p_to_count(p1), a, p1))
if a: # let's toggle position using p methods
p1.contract()
else:
p1.expand()
ec.toggle_position(p1) # let's toggle in ExpandController too
assert p1.isExpanded() == p1.isExpanded()
if a: # return to the previous state
p1.expand()
else:
p1.contract()
ec.toggle_position(p1) # in ExpandController too
g.es('all tests pass')
import timeit
def f1():
for p1 in expandables:
p1.isExpanded()
def f2():
for p1 in expandables:
ec.should_be_expanded(p1)
def tt(s, fun):
t1 = timeit.timeit(fun, number=1000)
g.es(f'{s} average: {t1:.3f}')
tt('using p.isExpanded', f1)
tt('using ExpandController', f2)using p.isExpanded average: 0.816
using ExpandController average: 0.861Vitalije