Skip to content

Instantly share code, notes, and snippets.

@snipsnipsnip
Last active August 29, 2015 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snipsnipsnip/0e267f6a39cc2397da3d to your computer and use it in GitHub Desktop.
Save snipsnipsnip/0e267f6a39cc2397da3d to your computer and use it in GitHub Desktop.
class Node
attr_accessor :value, :left, :right
def initialize(val)
@value = val # ノードが保持する値
@left = nil # 左側のノード
@right = nil # 右側のノード
end
end
# 描画に必要な情報を計算
def render_info(canvas, margin, node, x, y)
# 左のノードがあれば、そのノード以下の木の横幅を計算
x = render_info(canvas, margin, node.left, x, y + 1) + margin if node.left
# このノードの分の描画に必要な情報(X座標, Y座標, そのもの)を追加
canvas << [x, y, node]
# このノードの桁数を計算
x += node.value.to_s.size
# 右のノードがあれば、そのノード以下の木の横幅を計算
x = render_info(canvas, margin, node.right, x + margin, y + 1) if node.right
# 呼び出した親のために横幅を返す
x
end
# render_infoで描画情報を集めて返す
def render(root, margin=0)
canvas = []
render_info(canvas, margin, root, 0, 0)
canvas
end
# 9
# 4 15
# 2 6 12 17
# このメソッドはクラス外でOK
def print_canvas(canvas)
# X座標よりY座標を優先してソート
canvas = canvas.sort_by {|x, y, *| [y, x] }
last_x = 0
last_y = 0
# 各ノードをループで描画
canvas.each do |x, y, node|
text = node.value.to_s
# y座標が増えたら改行
if y != last_y
last_x = 0
puts
end
# 必要な分だけスペースを入れてプリント
print " " * (x - last_x)
print text
# カーソルを進める
last_x = x + text.to_s.size
last_y = y
end
puts
end
# _9__
# 4 15
# 2 6 12 17
def print_canvas2(canvas)
canvas = canvas.sort_by {|x, y, *| [y, x] }
last_x = 0
last_y = 0
# 各ノードをループで描画
canvas.each do |x, y, node|
text = node.value.to_s
if y != last_y
last_x = 0
puts
end
if node.left
# 「次の行の数字で、この数字のすぐ左にあるもの」を探してX座標をとる
ileft = canvas.index {|nx, ny, *| y + 1 == ny && x <= nx } - 1
left_x, * = canvas[ileft]
left_x_end = left_x + node.left.value.to_s.size
print " " * (left_x_end - last_x)
print "_" * (x - left_x_end)
else
print " " * (x - last_x)
end
print text
x += text.to_s.size
# 数字右側の下線
if node.right
# 「次の行の数字で、この数字のすぐ右にあるもの」を探してX座標をとる
iright = canvas.index {|nx, ny, *| y + 1 == ny && x <= nx }
right_x, * = canvas[iright]
print "_" * (right_x - x)
x = right_x
end
last_x = x
last_y = y
end
puts
end
# 9_
# / \
# 4 15
# 2 6 12 17
def print_canvas3(canvas)
# Y座標ごとにグループ分け
canvas_lines = canvas.group_by {|x, y, *| y }.sort.map {|_, line| line.sort }
canvas_lines.each do |line|
# 斜線の描画を記憶しておく配列
slashes = []
# この行に属す数字を描画していく
last_x = 0
line.each do |x, y, node|
text = node.value.to_s
# 数字左側の下線
if node.left
next_line = canvas_lines[y + 1]
ileft = next_line.index {|nx, *| x <= nx } - 1
left_x, * = next_line[ileft]
left_x_end = left_x + node.left.value.to_s.size
print " " * (left_x_end - last_x)
if left_x_end < x
# 下線の左端を欠けさせる (スペースにする)
print " "
print "_" * (x - left_x_end - 1)
# 斜線の描画を予約 (左手ノードなのでスラッシュ)
slashes << [left_x_end, '/']
end
else
print " " * (x - last_x)
end
print text
x += text.to_s.size
# 数字右側の下線
if node.right
next_line = canvas_lines[y + 1]
iright = next_line.index {|nx, *| x <= nx }
right_x, * = next_line[iright]
if x < right_x
# 下線の右端を欠けさせる (スペースにする)
print "_" * (right_x - x - 1)
print " "
# 斜線の描画を予約 (右手ノードなのでバックスラッシュ)
slashes << [right_x - 1, '\\']
end
x = right_x
end
last_x = x
end
puts
# 改行して斜線を描画
last_x = 0
slashes.each do |x, char|
print " " * (x - last_x)
print char
last_x = x + 1
end
puts unless slashes.empty?
end
end
# ツリーの宣言を短く書くために定義
def Node(val, left=nil, right=nil)
node = Node.new(val)
node.left = left
node.right = right
node
end
tree = Node(9, Node(4, Node(2), Node(6)), Node(15, Node(12), Node(17)))
print_canvas render(tree)
print_canvas2 render(tree)
print_canvas3 render(tree)
print_canvas3 render(Node(4, tree, tree))
print_canvas3 render(Node(4, tree, tree), 2)
print_canvas3 render(Node(4, Node(4), tree), 1)
# バグ: 右手にノードがないと落ちる
# print_canvas3 render(Node(4, tree, nil), 1)
# print_canvas3のバグを修正して整理しました
class Node
attr_accessor :value, :left, :right
def initialize(val)
@value = val # ノードが保持する値
@left = nil # 左側のノード
@right = nil # 右側のノード
end
end
# 描画位置の決まったノード
RenderedNode = Struct.new(:x, :y, :node) do
# ノードの描画に必要な幅
def width
node.value.to_s.size
end
end
# 各ノードの位置を計算する
class Renderer
def initialize(margin=0)
@margin = margin
end
# render_infoで描画情報を集めて返す (外部用)
def render(root)
@nodes = []
render_info(root, 0, 0)
@nodes
end
private
# 描画に必要な情報を計算 (内部用)
def render_info(node, x, y)
# 左のノードがあれば、そのノード以下の木の横幅が返ってくる
x = render_info(node.left, x, y + 1) + @margin if node.left
# このノードの分の描画に必要な情報を追加
@nodes << RenderedNode.new(x, y, node)
# このノードの分の描画に必要な情報を追加
x += node.value.to_s.size
x = render_info(node.right, x + @margin, y + 1) if node.right
# 呼び出した親のために横幅を返す
x
end
end
# Rendererの返した座標データから二分木のAAを表示する
class Printer
# コンストラクタで座標データをうけとる
def initialize(nodes)
@lines = group_by_line(nodes)
@slashes = nil
end
# 旧print_canvas
def run
@slashes = []
@lines.each do |line|
print_numbers(line)
print_slashes
@slashes.clear
end
end
private
# 座標データを行ごとにグループ分け
def group_by_line(nodes)
nodes.group_by {|n| n.y }.sort.map {|_, line| line.sort_by {|n| n.x } }
end
# 数字と下線からなる行の表示
def print_numbers(line)
last_x = 0
line.each do |n|
x = print_left_space(n, last_x)
x = print_node(n, x)
x = print_right_space(n, x)
last_x = x
end
puts
end
# 数字左の空白と下線の表示
# 右端の座標(現在のカーソル位置)を返す
def print_left_space(n, last_x)
x = n.x
if left_child = find_left_child(n)
left_x_end = left_child.x + left_child.width
print " " * (left_x_end - last_x)
if left_x_end < x
print " ", "_" * (x - left_x_end - 1)
# 斜線の描画を予約 (左手ノードなのでスラッシュ)
@slashes << [left_x_end, '/']
end
else
print " " * (x - last_x)
end
x
end
# 数字本体の表示
# 右端の座標(現在のカーソル位置)を返す
def print_node(n, x)
print n.node.value
x + n.width
end
# 数字右の空白と下線の表示
# 右端の座標(現在のカーソル位置)を返す
def print_right_space(n, x)
if right_child = find_right_child(n)
right_x = right_child.x
if x < right_x
print "_" * (right_x - x - 1), " "
# 斜線の描画を予約 (右手ノードなのでバックスラッシュ)
@slashes << [right_x - 1, '\\']
end
x = right_x
end
x
end
# 斜線からなる行の表示
def print_slashes
last_x = 0
@slashes.each do |x, char|
print " " * (x - last_x)
print char
last_x = x + 1
end
puts unless @slashes.empty?
end
# 右手の子ノードを探す
def find_right_child(n)
@lines[n.y + 1][index_right_child(n)] if n.node.right
end
# 左手の子ノードを探す
def find_left_child(n)
if n.node.left
next_line = @lines[n.y + 1]
# * バグ修正部分 *
# 右手のノードを探してからそのすぐ左を取る方法なので
# 右手にノードがない場合に失敗する
i = index_right_child(n)
if i
next_line[i - 1]
else
next_line.last
end
end
end
# 右手の子ノードの添字を見つける
def index_right_child(n)
next_line = @lines[n.y + 1]
next_line.index {|m| n.x <= m.x }
end
end
# ツリーの宣言を短く書くために定義
def Node(val, left=nil, right=nil)
node = Node.new(val)
node.left = left
node.right = right
node
end
# AAを描画
def draw_tree(root_node, margin)
nodes = Renderer.new(margin).render(root_node)
Printer.new(nodes).run
end
tree = Node(9, Node(4, Node(2), Node(6)), Node(15, Node(12), Node(17)))
draw_tree Node(4, tree, Node(3, tree, nil)), 1
__END__
_________4_________________
/ \
__9___ _________3
/ \ /
4 15 __9___
/ \ / \ / \
2 6 12 17 4 15
/ \ / \
2 6 12 17
9
4 15
2 6 12 17
_9__
4 15
2 6 12 17
9_
/ \
4 15
2 6 12 17
_____4__
/ \
9_ 9_
/ \ / \
4 15 4 15
2 6 12 17 2 6 12 17
_________4______
/ \
__9___ __9___
/ \ / \
4 15 4 15
/ \ / \ / \ / \
2 6 12 17 2 6 12 17
_4__________
/ \
_____________9 ____9_____
/ / \
____9_____ _4_ _15_
/ \ / \ / \
_4_ _15_ 2 6 12 17
/ \ / \
2 6 12 17
_________________4______________
/ \
______9_______ ______9_______
/ \ / \
__4__ __15__ __4__ __15__
/ \ / \ / \ / \
2 6 12 17 2 6 12 17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment