Algorithm to Render a Horizontal Binary-ish Tree in Text/ASCII form
If there are N
end nodes, there must be N-1
internal nodes with 2 children. (There can be any number of internal nodes with 1 child, which we will have to count to get the depths but otherwise ignore.) Generating the tree is thus equivalent to positioning these nodes on a grid, where:
- the number of rows in the grid is
N
- I think the number of columns is between
1+floor(log2(N))
and2*N-1
, depending on how much overlap there is; this probably doesn't matter much for our purposes, though - each endpoint appears on a different row
- all nodes at the same depth appear in the same column
- all internal nodes appear on the same row as their rightmost descendant endpoint
- Walk the tree depth-first, right-to-left.
- For each endpoint, record its depth and label.
- For each 2-child internal, record its depth, label and the indices of both rightmost and leftmost child endpoints.
- Sort the whole lot by depth -- this gives you the column ordering, with the number of distinct depths giving the actual number of columns. (All other ordering should come out automatically from the walk, I think, but that's not the case here because any branch can be any depth.)
- Place all the nodes in the grid.
- Mark empty cells to the right of each non-endpoint node as horizontal branches.
Mark empty cells down from each internal node to the row above its left child as vertical branches, and the cell at the level of the left child as a junction.
Print with appropriate ASCII decoration.
As you say, the positioning is enough to unambiguously determine the connections, but you still need to do some bottom-up work to get that right, so I'd probably still do the "mark" steps during the grid building.
I sort of thought the printing was trivial enough to gloss over, but:
- Iterate down each column and determine the column width as
size of fixed elements + max label length + floor(log10(depth) + 1)
. (Fixed elements might be[
and]-
, for example. We can substitute]\n
as the suffix for endpoints.) - For each row
- for each column
- if cell contains a node or endpoint
- print fixed prefix
- print label
- print depth
- print fill spaces (max label length - current label length)
- print appropriate suffix
- if node is an endpoint, skip to next row
- if cell is empty, print fill spaces to width of column
- if cell contains a vertical, print some chosen prefix number of spaces, a bar, and fill with spaces
- if cell contains a junction, print some chosen prefix number of spaces, a backslash, and fill with hyphens
- if cell contains a horizontal, print full column width of hyphens
- if cell contains a node or endpoint
- for each column
At some point I may try to put this into code, but it probably won't be today -- stuff to do!
print a binary tree on its side
Here's some code that implements the general, recursive approach described elsewhere. The internal representation of a tree is either a string (leaf) or a tuple (pair) of sub-nodes. The internal representation of the intermediate "fragment" of a node is the tuple (above, below, lines)
, where above
and below
are number of lines above and below the root, and lines
is an iterator over each partial line (without spaces to the left).
#!/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))
Here's some example output: _/rrrr
_/ \_/rrrlr
/ \ \rrrll
_/ \_/rrlr
/ \ \rrll
/ \ _/rlrr
/ \_/ \rlrl
_/ \_/rllr
\ \_/rlllr
\ \rllll
\ _/lrrr
\ _/ \lrrl
\ / \_/lrlr
\_/ \lrll
\ _/llrr
\_/ \llrl
\_/lllr
\_/llllr
\lllll
And a bit more asymmetric one that shows, perhaps, why I don't pad lines with spaces to the left until the end (via flatten
). If the lower half had been padded on the left some of the upper arm would cross the padded area. _/rrrrr
_/ \rrrrl
_/ \rrrl
_/ \_/rrlr
/ \ \rrll
/ \_/rlr
/ \rll
/ /lrrr
/ _/ _/lrrlrr
/ / \_/ \lrrlrl
/ / \lrrll
_/ _/ _/lrlrrr
\ / \ _/ \lrlrrl
\ / \_/ \lrlrl
\_/ \lrll
\ _/llrrr
\ _/ \llrrl
\_/ \llrl
\lll
It's the "obvious" recursive algorithm - the devil is in the details. It was easiest to write without the "_", which makes the logic slightly more complex.Perhaps the only "insight" is gap_above = l_above
- that's saying that the right "arm" has the length of the right side of the left subtree (you'll need to read that a few times). It makes things relatively balanced. See the asymmetric example above.
A good way of understanding things in more detail is to modify the pad
routine to take a character instead of ' '
and give a different character for each call. Then you can see exactly which logic generated which space. This is what you get using A. B, C and D for the calls to pad from top to bottom, above (obviously there's no character when the amount of space is zero):
_/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
There's more explanation here (although the tree is very slightly different). Algorithm for maintaining balanced binary tree when adding a new node
Consider this approach:
At every node, maintain two values:
- Max Left Depth: Maximum depth of a leaf in the left subtree
- Max Right Depth: Maximum depth of a leaf in the right subtree
Recurse.
BTW, the example is misleading, you might start from a steady state when two sides are nearly balanced.
Is there a way to create a binary tree in Erlang using processes?
loop(N, Root, LeftNode, RightNode) ->
receive
create_nodes ->
MyPid = self(),
NewLeftNode = spawn(?MODULE, loop, [N-1, MyPid, "empty-child-node", "empty-child-node"),
NewRightNode = spawn(?MODULE, loop, [N-1, MyPid, "empty-child-node", "empty-child-node"]),
NewLeftNode ! create_nodes,
NewRightNode ! create_nodes,
loop(N, Root, NewLeftNode, NewRightNode);
_ -> ok
end,
loop(N, Root, LeftNode, RightNode).
Try this one out. Binary tree maze generation in Elixir
On pattern you can use when mapping OO to sequential Elixir (functional language in general), you can create a data object (not an OO object) and pass it around as the first argument to your functions. This way, you are transforming that data on each call.
So, your api would be shaped like def link(maze, cell, bidirectional \\ true)
. Using a map to represent the maze with an {x,y}
tuple as the key and a map as the value lets you access individual cells and update them.
Here is some untested code as an example.
def Maze do
def new, do: %{cells: %{], links: %{}, start: {0,0}}}
def link(maze, cell1, cell2, bidirectional \\ true) do
maze
|> put_in([:links, cell2], true)
|> link_bidirectional(cell1, bidirectional)
end
defp link_bidirectional(maze, _, _, false), do: maze
defp link_bidirectional(maze, cell1, cell2, _) do
link(maze, cell2, cell1, false)
end
end
EDIT: Here is a functional solution for linkingdefmodule Maze do
def new do
%{cells: %{{0, 0} => Cell.create(0,0)}, tree: {{0, 0}, nil, nil}}
end
def new_cell(maze, row, column) do
# ignoring the tree for now
put_in(maze, [:cells, {row, column}], Cell.create(row, column))
end
def link(maze, cell1, cell2, bidirectional \\ true)
def link(maze, %{} = cell1, %{} = cell2, bidirectional) do
maze
|> update_in([:cells, cell1[:origin]], &(Cell.link(&1, cell2)))
|> do_bidirectional(cell1, cell2, bidirectional, &link/4)
end
def link(maze, {_, _} = pt1, {_, _} = pt2, bidirectional) do
link(maze, maze[:cells][pt1], maze[:cells][pt2], bidirectional)
end
def unlink(maze, %{} = cell1, %{} = cell2, bidirectional \\ true) do
maze
|> update_in([:cells, cell1[:origin]], &(Cell.unlink(&1, cell2)))
|> do_bidirectional(cell1, cell2, bidirectional, &unlink/4)
end
defp do_bidirectional(maze, _, _, false, _), do: maze
defp do_bidirectional(maze, cell1, cell2, _, fun) do
fun.(maze, cell2, cell1, false)
end
end
defmodule Cell do
def create(row,column), do: %{origin: {row, column}, links: %{}}
def link(self, cell) do
update_in(self, [:links, cell[:origin]], fn _ -> true end)
end
def unlink(self, cell) do
update_in(self, [:links], &Map.delete(&1, cell[:origin]))
end
end
iex(26)> Maze.new() |>
...(26)> Maze.new_cell(0,1) |>
...(26)> Maze.new_cell(1,0) |>
...(26)> Maze.link({0,0}, {0,1}) |>
...(26)> Maze.link({0,0}, {1,0})
%{cells: %{{0,
0} => %{links: %{{0, 1} => true, {1, 0} => true}, origin: {0, 0}},
{0, 1} => %{links: %{{0, 0} => true}, origin: {0, 1}},
{1, 0} => %{links: %{{0, 0} => true}, origin: {1, 0}}},
tree: {{0, 0}, nil, nil}}
iex(27)>
Delete method in binary search tree: Original node not being deleted
Setting root = nil
just nils the local reference. It doesn't nil the parent's right
or left
value, which is what you'd want to actually drop the node from the tree.
One way to work this might be to pass a block which gets invoked if the child doesn't have any child nodes. This captures the desired operation, and lets the recursed method call invoke it if it determines that the deletion should in fact occur.
def delete(root=@root, value, &block)
return root if root.data.nil?
if root.data < value
delete(root.right, value) { root.right = nil }
elsif root.data > value
delete(root.left, value) { root.left = nil }
elsif root.right.nil? && root.left.nil?
yield if block_given?
end
root
end
(Additionally: there is a potential bug here. The first two conditions don't check if root.right
or root.left
are nil, and will attempt to recurse into them regardless. You may want to add additional guards for that possibility.) Find deepest node(s) of a binary tree
Assumptions:
- The Binary Tree is actually the root node
- child_nodes returns the collection of child nodes as an array
class BinaryNode
attr_reader :element
def initialize(element,lchild,rchild)
@element, @lchild, @rchild = element, lchild, rchild
end
def height
if @lchild.nil? && @rchild.nil?
return 0
else
[@lchild, @rchild].collect {|n| n.nil ? 0 : n.height + 1 }.max
end
end
def deepest_nodes
return [self] if self.height == 1
[@lchild, @rchild].select {|n| !n.nil? && (n.height == self.height - 1)}.collect {|n| n.deepest_nodes}.flatten
end
end
Refactor:
class BinaryNode
attr_reader :element
def initialize(element,lchild,rchild)
@element, @lchild, @rchild = element, lchild, rchild
end
def child_nodes
[@lchild, @rchild].compact
end
def height
if self.child_nodes.empty?
return 0
else
self.child_nodes.collect {|n| n.height + 1 }.max
end
end
def deepest_nodes
return [self] if self.depth == 1
self.child_nodes.select {|n| n.height == self.height - 1}.collect {|n| n.deepest_nodes}.flatten
end
end
Getting the elements:
BinaryNode.new(1,BinaryNode.new(2,leaf,leaf),BinaryNode.new(3,leaf,leaf)).deepest_nodes.collect {|n| n.element }
Related Topics
How Do Erlang Actors Differ from Oop Objects
Rails Validating Search Params
How Does String.Unpack Work in Ruby
Using Google Search Rest API in Ruby
How to Update All of My Products to a Specific User When Seeding
Mongodb Server Doesn't Start at Gitlab Runner Using Gitlab-Ci
Using Rails Form Helpers with Serialized Custom Classes
How to Include Actionmailer Class in Rake Task
Error: Failed to Build Gem Native Extension on Windows
Ruby Dynamic Arguments in Dynamically Created Methods
Ruby/Rails 3.1: Given a Url String, Remove Path
Capybara-Webkit: Automatically Save a Screenshot on an Rspec Test Failure
How to Handle Serialized Edit Fields in an Active Admin Resource
How to Get Source and Variable Values in Ruby Tracebacks
How to Use (Ruby) Rgeo to Transform (Unproject) Coordinates