(본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)
열거자(Enumerator)와 외부 반복자
다른 언어에서는 컬렉션이 자신의 반복자를 포함하지 않고 외부 반복자 헬퍼 객체를 만들어 사용한다(Iterator..) 루비는 투명한 언어이다. 루비 프로그램을 작성할 때는 하고자 하는 일에만 집중하면 되고, 언어 자체적으로 지원하는 발판 코드를 작성하느라 고민할 필요가 없다.
루비 내장 Enumerator 클래스는 외부 반복자를 제공한다. 배열이나 해시에 대해 to_num 메서드를 호출하는 것만으로 Enumerator 객체를 생성할 수 있다.(enum_for도 같은 메서드다)
a = [1, 3, "cat"]
h = [ dog: "canine", fox: "vulpine" }
enum_a = a.to_enum
enum_h = h.to_enum
enum_a.next # => 1
enum_h.next # => [:dog, "canine"]
enum_a.next # => 3
enum_h.next # => [:fox, "vulpine"]
대부분의 내부 반복자와 메서드들은 블록 없이 호출하면 Enumerator 객체를 반환한다.
a = [ 1, 3, "cat"]
enum_a = a.each # 내부 반복자를 사용해 외부 반복자를 생성
enum_a.next # => 1
enum_a.next # => 3
루비에는 블록을 그저 반복적으로 실행하기만 하는 loop라는 메서드가 있다. 이 loop를 Enumerator와 함께 사용하면 편리하게 사용할 수 있다. loop 안에서 열거자 객체를 전부 반복하고 loop는 깔끔하게 종료된다. 아래의 예제는 세 개의 요소를 가지고 있는 열거자 객체의 모든 값을 반복하고 루프는 종료된다.
short_enum = [1, 2, 3].to_enum
long_enum = ('a'..'z').to_enum
loop do
puts "#{short_enum.next} - #{long_enum.next}"
end
실행결과
1 - a
2 - b
3 - c
열거자도 객체다
열거자는 일반적으로 실행 가능한 코드를 인자로 받아들여 객체로 변환한다. 일반적인 반복으로 작성하기 어려운 처리도 열거자를 통해 처리할 수 있다. 예를 들어 Enumerable 모듈에는 each_with_index 메소드가 정의되어 있다. 이 호스트 클래스의 each 를 호출하며, 컬렉션의 다음 값과 함께 위치 정보를 반환한다.
result = [.]
[ 'a', 'b', 'c'].each_with_index {|item, index| result << [item, index] }
result # => [["a",0], ["b", 1], ["c", 2]]
다른 반복자 메서드에서도 이 인덱스를 사용하고자 하면 어떻게 할까? String 클래스에 each_char_with_index와 같은 메서드는 없다. String클래스에서 each_char 메서드는 블록을 넘기지 않으면 열거자 객체를 반환한다.
result = [.]
"cat".each_char.each_with_index {|item, index| result << [item, index] }
result # => [["c",0], ["a", 1], ["t", 2]]
이는 열거자를 활용하는 기본적인 방법이다. 마츠는 이에 착안해 with_index 라는 좀 더 간결한 해결책을 준비해 두었다.
result = [.]
"cat".each_char.with_index {|item, index| result << [item, index] }
result # => [["c", 0], ["a", 1], ["t", 2] ]
Enumerator를 좀 더 명시적으로 생성할 수 있다. each_char 메서드를 호출한 결과를 Enumerator 객체로 생성한다. 이 열거자 객체에 to_a를 호출해 반복처리한 결과를 얻을 수 있다.
enum = "cat".enum_for(:each_char)
enum.to_a # => ["c", "a", "t"]
끄읕