質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

2回答

2445閲覧

Rubyで独自に作成したクラスにeachメソッドを定義する場合について

sachatete

総合スコア18

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2018/09/08 12:21

前提・実現したいこと

こんにちは。
プログラミング初心者なのですが、Rubyの練習問題を解いていてわからないところが出てきてしまったので質問させていただきました。

以下の問題は二分探索木の問題で、解答例を見ていて理解できていない部分を見つけました。

Rubyで独自にクラスを作成した場合、そのクラスのインスタンスメソッドとしてeachメソッドは定義されていないため、使いたい場合は自分で定義する必要があるということは分かるのですが、ここで定義されているBst#eachの仕組みがよく分からないです。
なぜ、Bst#each内では@leftに対してeachを呼び出すことで@leftに入ったBstクラスのインスタンス変数である@dataの2を取り出すことができるのでしょうか?
ためしにBst#each内で、self.each(&block)としてみたのですが、SystemStacErrorが発生してしまいました。SystemStacErrorはなぜ@left.eachの場合は発生しないのでしょうか?

発生している問題・エラーメッセージ

エラーなし

該当のソースコード

README

1# Binary Search Tree 2 3Insert and search for numbers in a binary tree. 4 5When we need to represent sorted data, an array does not make a good 6data structure. 7 8Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes 9`[1, 3, 4, 5, 2]` now we must sort the entire array again! We can 10improve on this by realizing that we only need to make space for the new 11item `[1, nil, 3, 4, 5]`, and then adding the item in the space we 12added. But this still requires us to shift many elements down by one. 13 14Binary Search Trees, however, can operate on sorted data much more 15efficiently. 16 17A binary search tree consists of a series of connected nodes. Each node 18contains a piece of data (e.g. the number 3), a variable named `left`, 19and a variable named `right`. The `left` and `right` variables point at 20`nil`, or other nodes. Since these other nodes in turn have other nodes 21beneath them, we say that the left and right variables are pointing at 22subtrees. All data in the left subtree is less than or equal to the 23current node's data, and all data in the right subtree is greater than 24the current node's data. 25 26For example, if we had a node containing the data 4, and we added the 27data 2, our tree would look like this: 28 29 4 30 / 31 2 32 33If we then added 6, it would look like this: 34 35 4 36 / \ 37 2 6 38 39If we then added 3, it would look like this 40 41 4 42 / \ 43 2 6 44 \ 45 3 46 47And if we then added 1, 5, and 7, it would look like this 48 49 4 50 / \ 51 / \ 52 2 6 53 / \ / \ 54 1 3 5 7 55 56* * * * 57

Ruby

1class Bst 2 attr_reader :data, :left, :right 3 4 def initialize(data) 5 @data = data 6 end 7 8 def insert(data) 9 data > @data ? self.go_right(data) : self.go_left(data) 10 end 11 12 def go_right(data) 13 @right ? @right.insert(data) : @right = Bst.new(data) 14 end 15 16 def go_left(data) 17 @left ? @left.insert(data) : @left = Bst.new(data) 18 end 19 20 def each(&block) 21 return enum_for(:each) unless block_given? 22 @left.each(&block) if @left 23 block.call(data) 24 @right.each(&block) if @right 25 end 26end

Ruby

1require 'minitest/autorun' 2require_relative 'binary_search_tree' 3 4class BstTest < Minitest::Test 5 def test_data_is_retained 6 assert_equal 4, Bst.new(4).data 7 end 8 9 def test_inserting_less 10 #skip 11 four = Bst.new 4 12 four.insert 2 13 assert_equal 4, four.data 14 assert_equal 2, four.left.data 15 end 16 17 def test_inserting_same 18 #skip 19 four = Bst.new 4 20 four.insert 4 21 assert_equal 4, four.data 22 assert_equal 4, four.left.data 23 end 24 25 def test_inserting_right 26 #skip 27 four = Bst.new 4 28 four.insert 5 29 assert_equal 4, four.data 30 assert_equal 5, four.right.data 31 end 32 33 def test_complex_tree 34 #skip 35 four = Bst.new 4 36 four.insert 2 37 four.insert 6 38 four.insert 1 39 four.insert 3 40 four.insert 7 41 four.insert 5 42 assert_equal 4, four.data 43 assert_equal 2, four.left.data 44 assert_equal 1, four.left.left.data 45 assert_equal 3, four.left.right.data 46 assert_equal 6, four.right.data 47 assert_equal 5, four.right.left.data 48 assert_equal 7, four.right.right.data 49 end 50 51 def record_all_data(bst) 52 all_data = [] 53 bst.each { |data| all_data << data } 54 all_data 55 end 56 57 def test_iterating_one_element 58 #skip 59 assert_equal [4], record_all_data(Bst.new(4)) 60 end 61 62 def test_iterating_over_smaller_element 63 #skip 64 four = Bst.new 4 65 four.insert 2 66 assert_equal [2, 4], record_all_data(four) 67 end 68 69 def test_iterating_over_larger_element 70 #skip 71 four = Bst.new 4 72 four.insert 5 73 assert_equal [4, 5], record_all_data(four) 74 end 75 76 def test_iterating_over_complex_tree 77 #skip 78 four = Bst.new 4 79 four.insert 2 80 four.insert 1 81 four.insert 3 82 four.insert 6 83 four.insert 7 84 four.insert 5 85 assert_equal [1, 2, 3, 4, 5, 6, 7], record_all_data(four) 86 end 87 88 def test_each_returns_enumerator_if_no_block 89 #skip 90 91 tree = Bst.new 4 92 [2, 1, 3, 6, 7, 5].each { |x| tree.insert x } 93 each_enumerator = tree.each 94 95 assert_kind_of Enumerator, each_enumerator 96 97 (1..7).each { |x| assert_equal(x, each_enumerator.next) } 98 99 assert_raises(StopIteration) { each_enumerator.next } 100 end 101end 102

試したこと

ここに問題に対して試したことを記載してください。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

SystemStackErrorとは、メソッド呼び出しが多すぎて
仮引数を置く場所がなくなった時にでる一種の無限ループ的なエラーです。

インスタンスメソッドeachからself.eachを呼び出すということは

rb

1def m 2 m 3end

と同じなのでメソッドを呼び出し続けて終わりません。

一方、@leftをたどっていくと、そのうち未初期化にたどり着きます。

初期化されていない インスタンス変数を参照した時の値はnil

なので、@left.each(&block) if @leftは、条件を満たさず実行されないときがいつか来ます
そしたら、自身をブロックに渡し(block.call(data))ます。


追記
値を取り出せる事を不思議がってるようなので簡単にしてみます。

rb

1class Val 2 def initialize(val) 3 @val = val 4 end 5 def each(&blk) 6 blk.call @val 7 end 8end 9 10Val.new(100).each{|el| p el} # => 100

クラスValは値1つを保持し、Val#eachメソッドは保持してる値を引数としてブロックを呼び出します。

次に連結リストを考えます。

rb

1class LinkedList 2 def initialize(val) 3 @val = val 4 end 5 def <<(val) 6 @link = LinkedList.new val 7 end 8 def each(&blk) 9 blk.call(@val) 10 @link.each(&blk) if @link 11 end 12 def rev_each(&blk) 13 @link.rev_each(&blk) if @link 14 blk.call(@val) 15 end 16end 17 18ll = LinkedList.new(1) 19ll << 2 << 3 << 4 << 5 20pp ll 21# => #<LinkedList:0x0000000006a9e128 22# @link= 23# #<LinkedList:0x0000000006b7f8d0 24# @link= 25# #<LinkedList:0x0000000006b7f880 26# @link= 27# #<LinkedList:0x0000000006b7f858 28# @link=#<LinkedList:0x0000000006b7f830 @val=5>, 29# @val=4>, 30# @val=3>, 31# @val=2>, 32# @val=1> 33 34ll.each{|el| p el} 35# => 1 36# 2 37# 3 38# 4 39# 5 40ll.rev_each{|el| p el} 41# => 5 42# 4 43# 3 44# 2 45# 1

@valをブロックに渡した後に
@listが偽でないならば@listのメソッドも同様にeachメソッドを呼び出してやる事で
値を取り出していく事ができます。

投稿2018/09/08 13:05

編集2018/09/08 15:32
asm

総合スコア15147

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sachatete

2018/09/08 14:26

回答ありがとうございます! 処理を順に追って理解するのに少し時間がかかりそうなので、よろしければまた質問させて頂けると嬉しいです。 一つ別の疑問が浮かんだのですが、なぜ Array#eachを持っていないBst#each ではBstクラスのインスタンスを一つずつ取り出すことができるのでしょうか? Array#each とここで使われている Bst#each は全く別物という解釈は正しいでしょうか? インスタンス変数からインスタンス自身を取り出すような処理は書いていないように見えるので混乱してしまいました。
asm

2018/09/08 15:45

> Array#each とここで使われている Bst#each は全く別物 別物ですが、 「メソッド名がeachで結果が値を一つずつ取り出してブロックに投げる」 のでRuby的にはあんまり区別する必要はないですね(強いて言えば戻り値が違いますが) あるBstインスタンスの leftには自身より小さい値のBstインスタンスが rightには自身よりも大きい値のBstインスタンスが 格納されています 自身の値を取り出してブロックへ投げた後に、rightへも同様にしろと命令すると 取り出される値はどんどん大きくなっていくでしょう。 そんな感じです
sachatete

2018/09/08 23:34

納得することができました!!! eachの中でeachを呼び出す時にレシーバがどんどん変わって行くのですね。 丁寧な説明ありがとうございました、とても助かりました!
guest

0

@left.each(&block) if @left block.call(data) @right.each(&block) if @right

は、 left を each で処理してから、 data を処理して、 right を each で処理しています。
そのあたりのことは参考にしているテキストで説明がされているはずとおもいます。

self.each(&block) は対象操作が全然進んでいかないので stack over flow となってしまいます。
処理対象が小さくなっていくことで、再帰処理が tree の末端まで進み return してくるようになっていることが重要です。

投稿2018/09/08 12:59

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sachatete

2018/09/08 14:27

回答ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問