Ответ 1
Здесь приведен код, который реализует общий, рекурсивный подход, описанный в другом месте. Внутреннее представление дерева - это либо строка (лист), либо кортеж (пара) узлов. Внутреннее представление промежуточного "фрагмента" node представляет собой набор (above, below, lines)
, где above
и below
- количество строк выше и ниже корня, а lines
- итератор по каждой частичной линии (без пробелов слева).
#!/usr/local/bin/python3.3
from itertools import chain
from random import randint
def leaf(t):
return isinstance(t, str)
def random(n):
def extend(t):
if leaf(t):
return (t+'l', t+'r')
else:
l, r = t
if randint(0, 1): return (l, extend(r))
else: return (extend(l), r)
t = ''
for _ in range(n-1): t = extend(t)
return t
def format(t):
def pad(prefix, spaces, previous):
return prefix + (' ' * spaces) + previous
def merge(l, r):
l_above, l_below, l_lines = l
r_above, r_below, r_lines = r
gap = r_below + l_above
gap_above = l_above
gap_below = gap - gap_above
def lines():
for (i, line) in enumerate(chain(r_lines, l_lines)):
if i < r_above:
yield ' ' + line
elif i - r_above < gap_above:
dash = '_' if i - r_above == gap_above - 1 else ' '
if i < r_above + r_below:
yield pad(dash + '/', 2 * (i - r_above), line)
else:
yield pad(dash + '/', 2 * gap_below - 1, line)
elif i - r_above - gap_above < gap_below:
if i < r_above + r_below:
yield pad(' \\', 2 * gap_above - 1, line)
else:
spaces = 2 * (r_above + gap_above + gap_below - i - 1)
yield pad(' \\', spaces, line)
else:
yield ' ' + line
return (r_above + gap_above, gap_below + l_below, lines())
def descend(left, t):
if leaf(t):
if left:
return (1, 0, [t])
else:
return (0, 1, [t])
else:
l, r = t
return merge(descend(True, l), descend(False, r))
def flatten(t):
above, below, lines = t
for (i, line) in enumerate(lines):
if i < above: yield (' ' * (above - i - 1)) + line
else: yield (' ' * (i - above)) + line
return '\n'.join(flatten(descend(True, t)))
if __name__ == '__main__':
for n in range(1,20,3):
tree = random(n)
print(format(tree))
Вот пример вывода:
_/rrrr
_/ \_/rrrlr
/ \ \rrrll
_/ \_/rrlr
/ \ \rrll
/ \ _/rlrr
/ \_/ \rlrl
_/ \_/rllr
\ \_/rlllr
\ \rllll
\ _/lrrr
\ _/ \lrrl
\ / \_/lrlr
\_/ \lrll
\ _/llrr
\_/ \llrl
\_/lllr
\_/llllr
\lllll
И немного более асимметричный, который показывает, возможно, почему я не накладываю строки с пробелами слева до конца (через flatten
). Если нижняя половина была заполнена слева, то часть плеча пересекла бы мягкую область.
_/rrrrr
_/ \rrrrl
_/ \rrrl
_/ \_/rrlr
/ \ \rrll
/ \_/rlr
/ \rll
/ /lrrr
/ _/ _/lrrlrr
/ / \_/ \lrrlrl
/ / \lrrll
_/ _/ _/lrlrrr
\ / \ _/ \lrlrrl
\ / \_/ \lrlrl
\_/ \lrll
\ _/llrrr
\ _/ \llrrl
\_/ \llrl
\lll
Это "очевидный" рекурсивный алгоритм - дьявол находится в деталях. Легче всего писать без "_", что делает логику несколько более сложной.
Возможно, единственное "прозрение" - это gap_above = l_above
- то, что правая "рука" имеет длину правой части левого поддерева (вам нужно будет это прочитать несколько раз). Это делает вещи относительно сбалансированными. См. Асимметричный пример выше.
Хороший способ понять вещи более подробно - это изменить подпрограмму pad
, чтобы взять символ вместо ' '
и дать другой символ для каждого вызова. Затем вы можете точно определить, какая логика сгенерировала это пространство. Это то, что вы используете с помощью A. B, C и D для вызовов сверху вниз сверху (очевидно, нет символа, когда объем пространства равен нулю):
_/rrrr
/ \rrrl
_/B _/rrlrr
/ \_/ \rrlrl
/AA \rrll
_/BBB _/rlrrr
/ \DD _/ \rlrrl
/AA \_/ \_/rlrlr
/AAAA \C \rlrll
/AAAAAA \_/rllr
_/AAAAAAAA \rlll
\DDDDDDDD _/lrrrr
\DDDDDD _/ \lrrrl
\DDDD / \lrrl
\DD _/B _/lrlrr
\_/ \_/ \lrlrl
\C \lrll
\_/llr
\lll
Здесь больше объяснений здесь (хотя дерево очень немного отличается).