ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Programming Ruby (4) 배열, 해시, 블록
    Ruby 2016. 10. 22. 23:27
    [출처] Programming Ruby 
    (본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)


    루비에는 컬렉션을 다루기 위한 배열과 해시라는 두개의 내장클래스가 준비되어 있다. 

    4.1. 배열

    배열의 인덱스는 0 부터 시작한다. 배열의 위치에 아무것도 없다면 nil을 반환한다. 
    음수로 위치를 지정하면 배열의 뒤에서부터 위치를 계산해 해당하는 위치의 값을 반환한다. 

    a = [ 1, 3, 5, 7, 9 ]
    a[-1]     # => 9
    a[-2]     # => 7
    a[-99]   # => nil


    배열 인덱스를 [start, count] 처럼 숫자 쌍으로 지정할 수도 있다. 이는 시작점(start)에서 count 만큼의 객체 참조를 뽑아서 새로운 배열을 만들어 반환한다. 

    a = [ 1, 3, 5, 7, 9 ]
    a[1, 3]     # => [3, 5, 7]
    a[3, 1]     # => [7]
    a[-3, 2]    # => [5, 7]


    인덱스에서 범위를 사용할 수도 있는데, 시작 위치와 끝 위치를 점 두 개 또는 세 개로 구분해서 적어주면 된다. 
    점 두 개를 사용하는 형식은 마지막 경곗값을 포함하고, 세 개를 사용하는 형식에는 포함하지 않는다. 

    a = [ 1, 3, 5, 7, 9 ]
    a[1..3]     # => [3, 5, 7]
    a[1…3]   # => [3, 5]
    a[3..3]     # => [7]
    a[-3..-1]  # => [5, 7, 9]


    [ ]연산자에 대응하는 [ ]= 연산자도 있다. 이 연산자를 이용해 배열의 특정 위치에 값을 대입할 수 잇다.
    인덱스 사이에 간격이 생기면 이 사이의 값은 nil 로 채워진다.

    a = [ 1, 3, 5, 7, 9]     #=>     [1, 3, 5, 7, 9]
    a[1] = ‘bat’               #=>      [1, “bat”, 5, 7, 9]
    a[-3] = ‘cat’              #=>      [1, “bat”, “cat”, 7, 9]
    a[3] = [9, 8]             #=>      [1, “bat”, “cat”, [9, 8] , 9]
    a[6] = 99                 #=>      [1, “bat”, “cat”, [9, 8] , 9, nil, 99]


    [ ]= 연산자에 쓰인 인덱스가 두 개(시작 위치와 길이)거나 범위라면, 원래 배열의 해당하는 위치에 있는 원소들이 대입문의 오른쪽 편에 있는 값으로 바뀐다. 길이가 0이라면 오른편의 값이 시작 위치 바로 앞에 삽입될 것이다. 

    a = [ 1, 3, 5, 7, 9]     #=>     [1, 3, 5, 7, 9]
    a[2, 2] = ‘cat’           #=>      [1, 3, “cat”, 9]
    a[2, 0] = ‘dog’          #=>      [1, 3, “dog”, “cat”, 9]
    a[1, 1] = [ 9, 8, 7]    #=>      [1, 9, 8, 7, “dog”, “cat”, 9]
    a[0..3] = [ ]              #=>      ["dog", "cat", 9]
    a[5..6] = 99, 98       #=>      ["dog", "cat", 9, nil, nil, 99, 98]


    push와 pop을 사용하면 배열의 맨 뒤에 요소를 추가하거나 맨 뒤의 요소를 제거하여 배열을 스택으로 사용할 수 있다. 

    stack = []
    stack.push "red" 
    stack.push "green"
    stack.push "blue"
    stack          # => ["red", "green", "blue"]

    stack.pop     # => "blue"
    stack.pop     # => "green"
    stack.pop     # => "red"
    stack = []


    비슷하게 unshift와 shift를 사용하면 배열 맨 앞의 요소를 추가하거나 삭제할 수 있다. shift와 push를 조합하면 배열을 선입선출(FIFO) 큐로 사용할 수 있다.

    queue = []
    queue.push "red"
    queue.push "green"
    queue.shift     # => "red"
    queue.shift     # => "green"


    first와 last 메서드는 배열의 맨 앞이나 맨 뒤에서 n개의 요소를 반환한다. 이때 앞선 메서드들과 달리 배열의 요소를 삭제하지는 않는다. 

    array = [ 1, 2, 3, 4, 5, 6, 7 ]
    array.first(4)     # => [1, 2, 3, 4]
    array.last(4)     # => [4, 5, 6, 7]


    4.2. 해시

    해시(연관, 맵, 사전이라고도 불린다)는 객체 참조가 색인된 컬렉션이라는 점에서 배열과 비슷하다. 하지만 배열은 정수를 인덱스로 사용하는 반면에, 해시의 인덱스로는 심벌, 문자열, 정규표현식, 심지어 어떤 객체라도 사용할 수 있다.  해시에 하나의 값을 저장할 때 두 개의 객체가 필요하다. 하나는 인덱스로 흔히 키라고 불리는 것이고, 나머지 하나는 이 키에 대응하는 값 객체다. 
    해시 리터럴은 중괄호 사이에 키와 값을 쌍으로 입력해 사용할 수 있다.

    h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }

    h.length     # => 3
    h['dog']      # => "canine"
    h['cow'] = 'bovine'
    h[12] = 'dodecine'
    h['cat'] = 99

    h     # => {"dog"=>"canine", "cat"=>99, "donkey"=>"asinine", "cow"=>"bovine",
           # => .. 12=>"dodecine"}


    루비 1.9부터는 키가 심벌인 경우에 한해서 축약 표현을 사용할 수 있다.

    h = { :dog => 'canine', :cat => 'feline', :donkey => 'asinine' }

    위와 같이 키가 심벌인 경우 심벌 앞의 콜론(:)을 지우고 => 를 콜론(:) 으로 대체해 다음과 같이 사용할 수 있다.

    h =  { dog: 'canine', cat : 'feline', donkey : 'asinine' }


    배열과 비교해서 해시의 가장 큰 장점은 인덱스로 어떤 객체라도 사용할 수 있다는 점이다. 또한 루비의 해시는 요소를 추가한 순서를 기억한다...!!! 따라서 해시를 반복할 때 루비는 요소를 추가한 순서대로 반복한다. 


    해시와 배열을 사용한 단어 출현 빈도 계산

    특정 텍스트에서 단어의 출현 빈도를 세는 프로그램을 작성해보자. 
    1) 문자열을 단어 리스트로 분할
    2) 단어를 인덱스로 사용하고 대응하는 출현 빈도를 기록.

    문자열을 단어로 나누는 메서드

    def words_from_string(string)
         string.downcase.scan(/[\w']+/)
    end

    downcase - 문자열을 소문자로 변환
    scan - 주어진 패턴에 매치되는 부분 문자열을 배열로 반환
     /[\w']+/ 패턴은 단어 문자와 작은따옴표를 포함하는 문자열에 매치된다.

    p words_from_string("But I didn't inhale, he said (emphtically)"

    실행결과
    ["but", "i", "didnt't", "inhale", "he", "said", "emphatically" ]


    출현빈도를 계산하기 위해 단어가 인덱스가 되는 해시 객체를 생성한다.

    if counts.has_key?(next_word)
         counts[next_word] += 1
    else
         counts[next_word] = 1
    end

    위의 방법보다 더 좋은 방법은 Hash.new(0)으로 해시를 생성하면 매개 변수 0이 해시의 기본값으로 사용된다. 이를 사용해 count_frequency 메서드를 다음과 같이 작성할 수 있다.

    def count_frequency(word_list) 
         counts = Hash.new(0)

         for word in word_list
              counts[word] += 1
         end
         counts
    end

    p count_frequency(["sparky", "the", "cat", "sat", "on", "the", "mat"])

    실행결과
    {"sparky"=>1, "the"=>2, "cat"=>1, "sat"=>1, "on"=>1, "mat"=>1}

    sort_by를 사용하면 블록을 통해 정렬할 때 어떤 값을 사용할지 지정할 수 있다.

    require_relative "words_from_string.rb"
    require_relative "count_frequency.rb"

    raw_text = %{The problem is blabal ~~... }

    word_list = words_from_string(raw_text)
    counts = count_frequency(word_list)
    sorted = counts.sort_by { |word, count| count}
    top_five = sorted.last(5) 

    for i in 0...5
         word = top_five[i][0]
         count = top_five[i][1]
         puts "#{word}: #{count}" 



    4.3. 블록과 반복자


    단어의 출현빈도를 분석하는 프로그램에는 다음과 같은 반복문이 포함되어 있었다.

    for i in 0..4 
         word = top_five[i][0]
         count = top_five[i][1]
         puts "#{word} : #{count}"
    end

    위의 코드는 문제없이 작동한다. 하지만 더 자연스러운 방법이 있다. 앞의 코드와 같은 일을 하는 코드를 다음과 같이 작성할 수 있다.

    top_five.each do |word, count|
         puts "#{word}: #{count}"
    end

    each 메서드는 반복자라고 불리며 주어진 블록의 코드를 반복적으로 호출한다. 더 나아가 다음과 같이 더 간결한 코드를 작성하는 프로그래머도 있다.

    puts top_five. map{ |word, count| "#{word} : #{count}" }




    블록


    블록은 중괄호나 do와 end 키워드로 둘러싸인 코드 덩어리이다. 한줄에 작성할 수 있을땐 중괄호, 그렇지 않으면 do/end 블록을 사용한다. 

    some_array.each {|value| puts value * 3 }

    sum = 0 
    other_array.each do |value|
         sum += value
         puts value / sum
    end 


    블록은 익명 메서드의 본문과 비슷한 무언가라고 생각해도 무방하다. 블록 또한 매개변수를 받을수 있다. 

    단 메서드와 달리 넘겨받는 매개 변수를 표현할때 블록의 시작 부분에 두 개의 막대 ( | ) 사이에 이름을 넣어준다. 위의 코드에서는 둘 다 value 라는 하나의 매개 변수를 받는다. 루비가 처음 이 부분을 해석하는 동안에는 실행하지 않고 이후에 호출할 수 있도록 블록을 저장해 둔다. 


    블록은 반드시 함수 메서드 호출 바로 다음에 위치해야 한다. 다음의 예제는 배열의 각 요소의 제곱의 합을 구한다.


    sum = 0 

    [1, 2, 3, 4].each do |value|

         square = value * value

         sum += squre

    end

    puts sum


    실행결과

    30 



    위의 예제에서 sum 변수는 블록 밖에서 정의되고 블록 안에서 갱신된다. 즉 블럭 밖에서 선언되고 이와 같은 이름의 변수가 블록 내에서 사용될 때는 두 변수는 같은 변수다. 하지만 변수가 블록 안에만 있다면 변수는 블럭 내부에서만 사용 가능한 지역변수가 되고 블록 밖에서는 출력할 수 없다. 



    블록 내/외부의 변수의 영역에서 발생할 수 있는 문제를 해결하기 위한 몇가지 기능이 있다.

    1) 블록에 넘겨지는 매개 변수는 항상 블록의 지역변수로 다뤄진다. 


    value = "some shape"

    [1, 2].each {|value| puts value }

    puts value 


    실행결과

    1

    2

    some shape



    2) 블록 매개변수 리스트에서 필요한 변수 앞에 세미콜론을 붙이면 명시적으로 블록의 지역변수라는 것을 지정할 수 있다.


    square = "some shape"


    sum = 0 

    [1, 2, 3, 4].each do |value; square|

         squre = value * value     #앞에 정의한  square와는 다른 변수다.

         sum += squre

    end


    puts sum

    puts squre 


    실행결과 

    30

    some shape












































    'Ruby ' 카테고리의 다른 글

    Programming Ruby (6) 열거자와 외부 반복자  (0) 2016.10.24
    Programming Ruby (5) 반복자  (0) 2016.10.24
    Programming Ruby (3) 클래스, 객체, 변수  (0) 2016.10.19
    Ruby Naming Guide - 루비 네이밍 가이드  (0) 2016.10.17
    What is Ruby  (0) 2016.10.06
Designed by Tistory.