ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Programming Ruby (5) 반복자
    Ruby 2016. 10. 24. 23:46
    [출처] Programming Ruby 

    (본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)



    반복자 구현하기


    루비에서 반복자란 코드 블록을 호출할 수 있는 메서드를 이야기한다. 먼저 블록은 메서드를 호출한 다음에만 나온다는 것과 코드 블록은 루비 해석기가 이를 해석하는 순간에 실행되는 것이 아니라는 점을 설명했다. 

    루비는 지역변수, 현재 객체 등과 같은 블록이 나타낸 시점의 맥락을 저장해 두고 메서드를 실행해 간다.


    메서드에서 yield 문을 사용해서 마치 코드 블록을 하나의 메서드인 것처럼 호출할 수 있다. yield를 사용해 메서드 안에서 언제든 코드블록 호출이 가능 하다. 다음은 yield를 사용하는 예이다.


    def two_times

         yield

         yield

    end

    two_times { puts "Hello" }


    실행결과

    Hello

    Hello


    블록(중괄호 사이 코드) 은 two_times 메서드의 호출에 연관 지어졌다. yield가 호출될 때마다 블록 코드가 실행되며 Hellow 가 출력된다. 블록이 재미있는 점은 매개변수를 블록에 넘겨줄 수도 있고 블록의 실행 결과를 다시 받아올 수도 있다는 점이다. 


    다음은 피보나치 수열을 출력하는 간단한 예제 프로그램이다.


    def fib_up_to(max)

         i1, i2 = 1, 1                # 병렬대입 { i1 = 1, i2 = 1 }

         while i1 <= max

              yield i1

              i1, i2 = i2, i1+i2

         end

    end


    fib_up_to(1000) {|f| print f, " "}


    puts


    실행결과

    1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 



    위의 예제에서 yield는 매개 변수를 가진다. 이 변수는 연관 지어진 블록으로 넘겨진다. 블록을 정의할 때 이러한 인자 목록은 블록 첫 부분의 막대( | ) 사이에 나열한다. 블록의 인자는 하나인 경우가 많지만 인자의 수에 제한이 있는 것은 아니다.


    몇몇 반복자는 다양한 루비 컬렉션 객체에서 사용된다. each, collect, find에 대해 살펴보자.


    each는 가장 간단한 반복자다. 다음과 같이 컬렉션의 각 요소에 대해 yield를 실행할 뿐이다.


    [ 1, 3, 5, 7, 9 ].each { |i| puts i }


    실행결과

    1

    3

    5

    7

    9



    또한 블록은 메서드에 자신의 평가 결과를 반환한다. 블록의 마지막 표현식을 평가한 결과는 yield 결과값으로 메서드에 반환된다. 이는 Array 클래스의 find 메서드가 동작하는 원리이기도 하다. 


    class Array

         def find

              each do |value|

                   return value if yield(value)

              end

         end

    end


    [1, 3, 5, 7, 9].find {|v| v*v > 30 }           # => 7



    위의 코드에서 each를 사용해 다음 요소를 연관된 블록 (do ... end) 에 넘겨준다. 이때 블록이 true를 반환하면 메서드는 해당하는 요소를 즉시 반환한다. 매치하는 요소가 없다면 메서드는 nil을 반환한다.  


    자주 사용되는 또 다른 반복자는 collect다. map이라고도 불리는 이 메서드는, 컬렉션으로부터 각 요소를 넘겨받아 이를 블록에 넘겨준다. 그리고 블록을 평가한 결과값을 모은 새로운 배열을 만들어 반환한다. 아래의 코드는 다음 문자를 반환하는  succ 메서드를 블록에서 사용하고 있다.


    ["H", "A", "L"].collect {|x| x.succ } # => ["I", "B", "M"]



    반복자는 배열이나 해시 안의 데이터만 접근 가능한것이 아니다. 피보나치 수열의 예처럼 반복자는 유도된 값을 반환할 수 있다. 이는 루비의 입출력 클래스에서 사용된다.


    f = File.open("testfile")

    f.each do |line|

         puts "The line is : #{line}"

    end


    f.close


    실행결과

    The line is: This is line one

    The line is: This is line two

    The line is: This is line three

    The line is: And so on... 




    블록이 몇 번 실행되었는지 정보는 with_index  메서드로 알 수 있다. 



    f = File.open("testfile")

    f.each.with_index do |line, index|

         puts "Line #{index} is : #{line}"

    end

    f.close


    실행결과

    Line 0 is: This is line one

    Line 1 is: This is line two

    Line 2 is: This is line three

    Line 3 is: And so on... 




    유용한 반복자 하나를 더 살펴보자. Enumerable에 정의된 inject 라는 메서드는 컬렉션의 모든 멤버에 특정 연산을 누적해 적용할 수 있도록 해준다. 예를들어 배열내 모든 합계나 곱은 아래와 같이 작성할 수 있다.

    [1, 3, 5, 7].inject(0) {|sum, element| sum+element}          # => 16

    [1, 3, 5, 7].inject(1) {|product, element| product*element} # => 105 


    inject의 동작원리는 이렇다. 맨 처음 sum을 inject에 넘겨진 매개 변수로 초기화하고 연관된 블록을 호출한다. 그리고 컬렉션의 첫 번재 요소 element를 블록에 넘긴다. 두번째부터 sum은 바로 이전에 실행된 블록의 반환값이 된다. inject의 결과값은 마지막 실행된 블록의 반환값이 된다.

    매개변수 없이 inject를 호출하면 컬렉션의 첫번재 요소가 초기값이 되고 두번째 요소부터 반복을 시작하여 좀 더 간결하게 아래와 같이 표현할 수 있다.


    [1, 3, 5, 7].inject {|sum, element| sum+element}               # => 16

    [1, 3, 5, 7].inject {|product, element| product*element}      # => 105 





    끄읕.


Designed by Tistory.