Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby의 마법 같은 Enumerable 모듈

Ruby Magic의 또 다른 에피소드를 볼 시간입니다! 이번에는 Array와 같은 Ruby의 열거 가능한 클래스로 작업할 때 사용할 대부분의 메서드를 제공하는 Ruby의 가장 마법 같은 기능 중 하나를 살펴보겠습니다. , HashRange . 이 과정에서 우리는 열거 가능한 개체로 무엇을 할 수 있는지, 열거가 어떻게 작동하는지, 단일 메서드를 구현하여 개체를 열거할 수 있게 만드는 방법을 배웁니다.

Enumerable , #eachEnumerator

열거 물체를 가로지르는 것을 말합니다. Ruby에서는 객체를 열거 가능이라고 부릅니다. 항목 집합과 각 항목을 반복하는 방법을 설명할 때.

내장 enumerable은 Enumerable을 포함하여 열거 기능을 얻습니다. #include?와 같은 메소드를 제공하는 모듈 , #count , #map , #select#uniq , 무엇보다도. 배열 및 해시와 관련된 대부분의 메서드는 실제로 이러한 클래스 자체에서 구현되지 않고 포함되어 있습니다.

참고 :#count와 같은 일부 메소드 및 #take Array 클래스, 이다 Enumerable의 것을 사용하는 대신 배열을 위해 특별히 구현되었습니다. 기준 치수. 이는 일반적으로 작업을 더 빠르게 하기 위해 수행됩니다.

Enumerable 모듈은 #each라는 메서드에 의존합니다. , 포함된 모든 클래스에서 구현해야 합니다. 배열의 블록과 함께 호출될 때 #each 메소드는 배열의 각 요소에 대한 블록을 실행합니다.

irb> [1,2,3].each { |i| puts "* #{i}" }
* 1
* 2
* 3
=> [1,2,3]

#each를 호출하면 없는 배열의 메소드 각 요소에 대해 실행할 블록을 전달하면 Enumerator 인스턴스가 수신됩니다. .

irb> [1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>

Enumerator의 인스턴스 객체를 반복하는 방법을 설명합니다. 열거자는 개체를 수동으로 반복하고 열거를 연결합니다.

irb> %w(dog cat mouse).each.with_index { |a, i| puts "#{a} is at position #{i}" }
dog is at position 0
cat is at position 1
mouse is at position 2
=> ["dog", "cat", "mouse"]

#with_index 메소드는 변경된 열거자가 어떻게 작동하는지 보여주는 좋은 예입니다. 이 예에서 #each 열거자를 반환하기 위해 배열에서 호출됩니다. 그런 다음 #with_index 각 요소의 인덱스를 인쇄할 수 있도록 배열의 각 요소에 인덱스를 추가하기 위해 호출됩니다.

객체를 열거 가능하게 만들기

내부적으로 #max와 같은 메소드 , #map#take #each에 의존 작동하는 방법.

def max
  max = nil
 
  each do |item|
    if !max || item > max
      max = item
    end
  end
 
  max
end

내부적으로 Enumerable 의 메소드에는 C 구현이 있지만 위의 예는 대략적으로 #max 공장. #each를 사용하여 모든 값을 반복하고 가장 높은 값을 기억하려면 최대값을 반환합니다.

def map(&block)
  new_list = []
 
  each do |item|
    new_list << block.call(item)
  end
 
  new_list
end

#map 함수는 각 항목과 함께 전달된 블록을 호출하고 모든 값을 반복한 후 반환할 새 목록에 결과를 넣습니다.

Enumerable의 모든 메소드 이후 #each 사용 메서드를 어느 정도는 열거할 수 있도록 사용자 지정 클래스를 만드는 첫 번째 단계는 #each를 구현하는 것입니다. 방법.

#each 구현

#each 구현 함수 및 Enumerable 포함 모듈이 클래스에 있으면 열거 가능해지고 #min과 같은 메소드를 수신합니다. , #take#inject 무료입니다.

대부분의 상황에서 배열과 같은 기존 객체로 폴백하고 #each 그 방법에 대해 처음부터 직접 작성해야 하는 예를 살펴보겠습니다. 이 예에서는 #each를 구현합니다. 연결 목록에서 열거할 수 있도록 합니다.

연결 목록:배열이 없는 목록

연결 목록은 각 요소가 다음 요소를 가리키는 데이터 요소의 모음입니다. 목록의 각 요소에는 head라는 두 개의 값이 있습니다. 그리고 꼬리 . 머리 부분은 요소의 값을 보유하고 꼬리 부분은 목록의 나머지 부분에 대한 링크입니다.

[42, [12, [73, nil]]

3개의 값(42, 12, 73)이 있는 연결 목록의 경우 첫 번째 요소의 머리는 42이고 꼬리는 두 번째 요소에 대한 링크입니다. 두 번째 요소의 머리는 12이고 꼬리는 세 번째 요소를 보유합니다. 세 번째 요소의 머리는 73이고 꼬리는 nil입니다. , 목록의 끝을 나타냅니다.

Ruby에서는 @head라는 두 개의 인스턴스 변수를 보유하는 클래스를 사용하여 연결 목록을 만들 수 있습니다. 및 @tail .

class LinkedList
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
end

#<< 메서드는 목록에 새 값을 추가하는 데 사용되며, 전달된 값을 머리로, 이전 목록을 꼬리로 사용하여 새 목록을 반환하는 방식으로 작동합니다.

이 예에서 #inspect 메소드가 추가되어 목록에 포함된 요소를 확인할 수 있습니다.

irb> LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]

이제 연결 목록이 있으므로 #each를 구현해 보겠습니다. 그 위에. #each 함수는 블록을 가져와 객체의 각 값에 대해 실행합니다. 연결 목록에서 구현할 때 목록의 @head에서 전달된 블록을 호출하여 목록의 재귀적 특성을 유리하게 사용할 수 있습니다. , #each 호출 @tail , 존재하는 경우.

class LinkedList
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
 
  def each(&block)
    block.call(@head)
    @tail.each(&block) if @tail
  end
end

#each를 호출할 때 연결된 목록의 인스턴스에서 현재 @head로 전달된 블록을 호출합니다. . 그런 다음 @tail의 연결 목록에서 각각을 호출합니다. 꼬리가 nil이 아닌 경우 .

irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each { |item| puts item }
42
12
73
=> nil

이제 연결 목록이 #each에 응답합니다. , include Enumberable할 수 있습니다. 목록을 열거할 수 있도록 합니다.

class LinkedList
  include Enumerable
 
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
 
  def each(&block)
    block.call(@head)
    @tail.each(&block) if @tail
  end
end
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.count
=> 3
irb> list.max
=> 73
irb> list.map { |item| item * item }
=> [1764, 144, 5329]
irb> list.select(&:even?)
=> [42, 12]

Enumerator 반환 인스턴스

이제 연결 목록의 모든 값을 반복할 수 있지만 아직 열거 가능한 함수를 연결할 수는 없습니다. 그렇게 하려면 Enumerator를 반환해야 합니다. #each 함수가 블록 없이 호출됩니다.

class LinkedList
  include Enumerable
 
  def initialize(head, tail = nil)
    @head, @tail = head, tail
  end
 
  def <<(item)
    LinkedList.new(item, self)
  end
 
  def inspect
    [@head, @tail].inspect
  end
 
  def each(&block)
    if block_given?
      block.call(@head)
      @tail.each(&block) if @tail
    else
      to_enum(:each)
    end
  end
end

열거자에서 개체를 래핑하려면 #to_enum을 호출합니다. 그것에 대한 방법. :each를 전달합니다. , 열거자가 내부적으로 사용해야 하는 방법이기 때문입니다.

이제 #each를 호출합니다. 블록이 없는 메서드를 사용하면 열거형을 연결할 수 있습니다.

irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each
=> #<Enumerator: [42, [12, [73, nil]]]:each>
irb> list.map.with_index.to_h
=> {42=>0, 12=>1, 73=>2}

9줄의 코드 및 포함

#each 구현 Enumerable 사용 모듈 및 반환 Enumerator 우리는 9줄의 코드와 포함을 추가하여 연결 목록을 강화할 수 있었습니다.

이것으로 Ruby의 열거형에 대한 개요를 마칩니다. 이 기사에 대한 귀하의 생각이나 질문이 있는 경우 알고 싶습니다. 우리는 항상 조사하고 설명할 주제를 찾고 있으므로 Ruby에 대해 읽고 싶은 마법 같은 것이 있다면 주저하지 말고 지금 @AppSignal로 알려주세요!