fork download
  1. class Node
  2. attr_accessor :value, :left, :right
  3. def initialize(val)
  4. @value = val # ノードが保持する値
  5. @left = nil # 左側のノード
  6. @right = nil # 右側のノード
  7. end
  8. end
  9.  
  10. # 描画に必要な情報を計算
  11. def render_info(canvas, margin, node, x, y)
  12. # 左のノードがあれば、そのノード以下の木の横幅を計算
  13. x = render_info(canvas, margin, node.left, x, y + 1) + margin if node.left
  14.  
  15. # このノードの分の描画に必要な情報(X座標, Y座標, そのもの)を追加
  16. canvas << [x, y, node]
  17.  
  18. # このノードの桁数を計算
  19. x += node.value.to_s.size
  20.  
  21. # 右のノードがあれば、そのノード以下の木の横幅を計算
  22. x = render_info(canvas, margin, node.right, x + margin, y + 1) if node.right
  23.  
  24. # 呼び出した親のために横幅を返す
  25. x
  26. end
  27.  
  28. # render_infoで描画情報を集めて返す
  29. def render(root, margin=0)
  30. canvas = []
  31. render_info(canvas, margin, root, 0, 0)
  32. canvas
  33. end
  34.  
  35. # 9
  36. # 4 15
  37. # 2 6 12 17
  38.  
  39. # このメソッドはクラス外でOK
  40. def print_canvas(canvas)
  41. # X座標よりY座標を優先してソート
  42. canvas = canvas.sort_by {|x, y, *| [y, x] }
  43.  
  44. last_x = 0
  45. last_y = 0
  46.  
  47. # 各ノードをループで描画
  48. canvas.each do |x, y, node|
  49. text = node.value.to_s
  50.  
  51. # y座標が増えたら改行
  52. if y != last_y
  53. last_x = 0
  54. puts
  55. end
  56.  
  57. # 必要な分だけスペースを入れてプリント
  58. print " " * (x - last_x)
  59. print text
  60.  
  61. # カーソルを進める
  62. last_x = x + text.to_s.size
  63. last_y = y
  64. end
  65. puts
  66. end
  67.  
  68. # _9__
  69. # 4 15
  70. # 2 6 12 17
  71.  
  72. def print_canvas2(canvas)
  73. canvas = canvas.sort_by {|x, y, *| [y, x] }
  74.  
  75. last_x = 0
  76. last_y = 0
  77.  
  78. # 各ノードをループで描画
  79. canvas.each do |x, y, node|
  80. text = node.value.to_s
  81.  
  82. if y != last_y
  83. last_x = 0
  84. puts
  85. end
  86.  
  87. if node.left
  88. # 「次の行の数字で、この数字のすぐ左にあるもの」を探してX座標をとる
  89. ileft = canvas.index {|nx, ny, *| y + 1 == ny && x <= nx } - 1
  90. left_x, * = canvas[ileft]
  91. left_x_end = left_x + node.left.value.to_s.size
  92. print " " * (left_x_end - last_x)
  93. print "_" * (x - left_x_end)
  94. else
  95. print " " * (x - last_x)
  96. end
  97.  
  98. print text
  99.  
  100. x += text.to_s.size
  101.  
  102. # 数字右側の下線
  103. if node.right
  104. # 「次の行の数字で、この数字のすぐ右にあるもの」を探してX座標をとる
  105. iright = canvas.index {|nx, ny, *| y + 1 == ny && x <= nx }
  106. right_x, * = canvas[iright]
  107.  
  108. print "_" * (right_x - x)
  109. x = right_x
  110. end
  111.  
  112. last_x = x
  113. last_y = y
  114. end
  115. puts
  116. end
  117.  
  118. # 9_
  119. # / \
  120. # 4 15
  121. # 2 6 12 17
  122.  
  123. def print_canvas3(canvas)
  124. # Y座標ごとにグループ分け
  125. canvas_lines = canvas.group_by {|x, y, *| y }.sort.map {|_, line| line.sort }
  126.  
  127. canvas_lines.each do |line|
  128. # 斜線の描画を記憶しておく配列
  129. slashes = []
  130.  
  131. # この行に属す数字を描画していく
  132. last_x = 0
  133. line.each do |x, y, node|
  134. text = node.value.to_s
  135.  
  136. # 数字左側の下線
  137. if node.left
  138. next_line = canvas_lines[y + 1]
  139. ileft = next_line.index {|nx, *| x <= nx } - 1
  140. left_x, * = next_line[ileft]
  141. left_x_end = left_x + node.left.value.to_s.size
  142. print " " * (left_x_end - last_x)
  143. if left_x_end < x
  144. # 下線の左端を欠けさせる (スペースにする)
  145. print " "
  146. print "_" * (x - left_x_end - 1)
  147. # 斜線の描画を予約 (左手ノードなのでスラッシュ)
  148. slashes << [left_x_end, '/']
  149. end
  150. else
  151. print " " * (x - last_x)
  152. end
  153.  
  154. print text
  155.  
  156. x += text.to_s.size
  157.  
  158. # 数字右側の下線
  159. if node.right
  160. next_line = canvas_lines[y + 1]
  161. iright = next_line.index {|nx, *| x <= nx }
  162. right_x, * = next_line[iright]
  163.  
  164. if x < right_x
  165. # 下線の右端を欠けさせる (スペースにする)
  166. print "_" * (right_x - x - 1)
  167. print " "
  168. # 斜線の描画を予約 (右手ノードなのでバックスラッシュ)
  169. slashes << [right_x - 1, '\\']
  170. end
  171. x = right_x
  172. end
  173.  
  174. last_x = x
  175. end
  176.  
  177. puts
  178. # 改行して斜線を描画
  179. last_x = 0
  180. slashes.each do |x, char|
  181. print " " * (x - last_x)
  182. print char
  183. last_x = x + 1
  184. end
  185. puts unless slashes.empty?
  186. end
  187. end
  188.  
  189. # ツリーの宣言を短く書くために定義
  190. def Node(val, left=nil, right=nil)
  191. node = Node.new(val)
  192. node.left = left
  193. node.right = right
  194. node
  195. end
  196.  
  197. tree = Node(9, Node(4, Node(2), Node(6)), Node(15, Node(12), Node(17)))
  198. print_canvas render(tree)
  199. print_canvas2 render(tree)
  200. print_canvas3 render(tree)
  201. print_canvas3 render(Node(4, tree, tree))
  202. print_canvas3 render(Node(4, tree, tree), 2)
  203. print_canvas3 render(Node(4, Node(4), tree), 1)
  204.  
  205. # バグ: 右手にノードがないと落ちる
  206. print_canvas3 render(Node(4, tree, nil), 1) rescue puts $!
Success #stdin #stdout 0.01s 7432KB
stdin
Standard input is empty
stdout
   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______ 
 /       \
4       __9___ 
       /      \
      4        15 
     / \      /  \
    2   6   12    17
undefined method `-' for nil:NilClass