Ruby Magic의 또 다른 에피소드를 볼 시간입니다! 이번에는 Array
와 같은 Ruby의 열거 가능한 클래스로 작업할 때 사용할 대부분의 메서드를 제공하는 Ruby의 가장 마법 같은 기능 중 하나를 살펴보겠습니다. , Hash
및 Range
. 이 과정에서 우리는 열거 가능한 개체로 무엇을 할 수 있는지, 열거가 어떻게 작동하는지, 단일 메서드를 구현하여 개체를 열거할 수 있게 만드는 방법을 배웁니다.
Enumerable
, #each
및 Enumerator
열거 물체를 가로지르는 것을 말합니다. 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로 알려주세요!