2010-01-17 2 views
38

개체가 배열에있는 개체에 대한 캐시 된 통계와 함께 result 개체의 배열을 포함합니다. 결과 개체가 배열처럼 동작 할 수 있기를 바랍니다. 이에 나의 첫번째 컷이 매우 C-같은 느낌이Ruby 객체에 'each'메소드를 어떻게 추가합니까 (아니면 Array를 확장해야합니까?).

def <<(val) 
    @result_array << val 
end 

같은 방법을 추가하고 있었고, 난 루비가 더 나은 방법이 알고있다. 나는 또한 싶습니다

Results.each do |result| 
    result.do_stuff 
end 

을 할 수 있지만, each 방법은 정말 후드 아래에 무엇을하고 있는지 모르겠습니다합니다.

현재 메서드를 통해 기본 배열을 반환하고 가장 우아한 솔루션처럼 보이지 않는 각각을 호출합니다.

도움을 주시면 감사하겠습니다.

답변

61

의 경우 이러한 형태의 모두에서

require 'forwardable' 
class Results 
    extend Forwardable 
    def_delegator :@result_array, :<<, :each, :concat # etc... 

    def best 
    @result_array.find {|result| result.is_really_good? } 
    end 

    # Array functionality that needs change 
    def compact 
    @result_array.delete(ininteresting_result) 
    @result_array.compact 
    self 
    end 
end 

당신이 원하는대로 당신이 그것을 사용할 수 있습니다 : 다른 클래스에서 상속해야하기 때문에 당신이 배열에서 상속 할 수 없습니다 경우에 특히 유용합니다 배열과 같은 메소드를 구현하는 일반적인 경우, 네, 직접 구현해야합니다. Vava의 대답은 이것의 한 예를 보여줍니다. 그래도 주어진 경우에는 포함 된 배열에 each (및 다른 방법 일 수도 있음) 처리 작업을 위임하고 자동화 할 수 있습니다.

require 'forwardable' 

class Results 
    include Enumerable 
    extend Forwardable 
    def_delegators :@result_array, :each, :<< 
end 

이 클래스는 배열의 Enumerable에서 행동의 모든뿐만 아니라 배열 << 연산자를 얻을 것이다 그것은 모든 내부 배열을 통해 이동합니다.


주,이 트릭에 배열 상속에서 코드를 전환 할 때, 당신의 << 방법은 같은 객체 intself을하지 반환하기 시작할 것이라는 점을 실제 배열의 << 않았다 - 이것은 당신이 다른 변수 매번 당신을 선언 요할 수있다 <<을 사용하십시오.

+1

루비 라이브러리에 뭔가 있어야한다는 것을 알았습니다. 자, 나중에 찾을 때이 대답에 별표를 붙일 수만 있다면 ... – vava

+5

'include '가 아니라'Forwardable'을 확장해야합니다. –

+0

전달할 수있는 좋은 정보이지만 그렇게하면 값을 조작 할 수 없습니다. 그는 메서드를 호출하므로 데이터를 반환하기 전에 데이터를 처리하려고한다고 가정합니다. 나는 틀릴 수 있었다. –

8

이 느낌은 매우 C와 같습니다. Ruby 이 더 좋은 방법임을 알고 있습니다. < <를 오버라이드 (override)보다는 당신이 배열과 같은 '느낌'에 객체를 원하는 경우

는 좋은 아이디어와 매우 'Ruby' 틱입니다.

그러나 각 방법 이 실제로 어떤 작업을하는지 잘 모르겠습니다.

배열에 대한 각각의 방법은 (내가 생각 루프, 의 사용) 모든 요소를 ​​통해 반복합니다. 당신은 (또한 매우 'Ruby' 틱입니다) 자신의 각각의 방법을 추가하려는 경우, 당신은 같은 것을 할 수 있습니다 :

def each 
    0.upto(@result_array.length - 1) do |x| 
    yield @result_array[x] 
    end 
end 
+4

을 반환 처리를 취하는 각각의 방법을 만들 수있는 가장 좋은 방법입니다 배열의? –

35

each 단지 배열을 통해 이동하고 각 요소에 주어진 블록을 호출, 즉 단순한. 클래스 내부에서 배열을 사용하기 때문에 each 메서드를 배열에서 하나의 것으로 리디렉션 할 수 있습니다. 즉, 읽기 쉽고 빠르게 유지 관리 할 수 ​​있습니다.

class Result 
    include Enumerable 

    def initialize 
     @results_array = [] 
    end 

    def <<(val) 
     @results_array << val 
    end 

    def each(&block) 
     @results_array.each(&block) 
    end 
end 

r = Result.new 

r << 1 
r << 2 

r.each { |v| 
    p v 
} 

#print: 
# 1 
# 2 

Enumerable에 유의하십시오. 그러면 all?, map 등과 같은 배열 메서드가 무료로 제공됩니다.

BTW 루비를 사용하면 상속을 잊을 수 있습니다. 오리 - 타이핑은 실제 타입을 정말로 신경 쓰지 않기 때문에 인터페이스 상속이 필요하지 않으며 믹스틴이 그런 종류의 것들에 더 좋기 때문에 코드 상속이 필요 없습니다.

+0

'각'방법의 구현을 실제로 설명하지는 않으며, taht가 질문이었습니다. –

+0

@Ed : 물론 있습니다. 각 메소드는 클래스의 실제 구현에 따라 객체의 요소를 반복하는 데 필요한 모든 작업을 수행합니다. 이 경우 확실한 선택은'each'가 배열 요소를 반복하는 것입니다. – Chuck

+0

나에게 간단한 래퍼 방법처럼 보입니다. –

5

배열에서 상속하는 결과 클래스를 만드는 경우 모든 기능이 상속됩니다.

그런 다음 다시 정의하여 변경해야하는 메소드를 보완 할 수 있으며 이전 기능에 대해 super를 호출 할 수 있습니다.예를 들어

는 :

class Results < Array 
    # Additional functionality 
    def best 
    find {|result| result.is_really_good? } 
    end 

    # Array functionality that needs change 
    def compact 
    delete(ininteresting_result) 
    super 
    end 
end 

다른 방법으로는 내장 라이브러리 forwardable를 사용할 수 있습니다.

r = Results.new 
r << some_result 
r.each do |result| 
    # ... 
end 
r.compact 
puts "Best result: #{r.best}" 
8

< <은 매우 훌륭하고 루비와 비슷합니다.

실제로 클래스를 Array에서 직접 상속받지 않고 배열처럼 작동하게하려면 Enumerable 모듈을 혼합하고 몇 가지 메서드를 추가 할 수 있습니다.

# You have to require forwardable to use it 
require "forwardable" 

class MyArray 
    include Enumerable 
    extend Forwardable 

    def initialize 
    @values = [] 
    end 

    # Map some of the common array methods to our internal array 
    def_delegators :@values, :<<, :[], :[]=, :last 

    # I want a custom method "add" available for adding values to our internal array 
    def_delegator :@values, :<<, :add 

    # You don't need to specify the block variable, yield knows to use a block if passed one 
    def each 
    # "each" is the base method called by all the iterators so you only have to define it 
    @values.each do |value| 
     # change or manipulate the values in your value array inside this block 
     yield value 
    end 
    end 

end 

m = MyArray.new 

m << "fudge" 
m << "icecream" 
m.add("cake") 

# Notice I didn't create an each_with_index method but since 
# I included Enumerable it knows how and uses the proper data. 
m.each_with_index{|value, index| puts "m[#{index}] = #{value}"} 

puts "What about some nice cabbage?" 
m[0] = "cabbage" 
puts "m[0] = #{m[0]}" 

puts "No! I meant in addition to fudge" 
m[0] = "fudge" 
m << "cabbage" 
puts "m.first = #{m.first}" 
puts "m.last = #{m.last}" 

출력 : 여기에

은 (FORWARDABLE를 사용하는 척 우수한 제안 포함) 예입니다

m[0] = fudge 
m[1] = icecream 
m[2] = cake 
What about some nice cabbage? 
m[0] = cabbage 
No! I meant in addition to fudge 
m.first = fudge 
m.last = cabbage 
2

당신이 정말로 자신의 #each 방법을 원하는 경우, 당신을 가정 전달하지 않으려면 블록을 지정하지 않으면 열거자를 반환해야합니다.

class MyArrayLikeClass 
    include Enumerable 

    def each(&block) 
    return enum_for(__method__) if block.nil? 
    @arr.each do |ob| 
     block.call(ob) 
    end 
    end 
end 

블록을 지정하지 않으면 Enumerable 객체를 반환합니다. Enumerable 메서드 체이닝을 허용합니다.

+0

저에게 Ruby는 사용자가 모국어 기능을 완벽하게 모방 할 수있게 해 주므로 Ruby를 매우 아름답게 만듭니다. – pedz

3

확실하지 않습니다. 새로운 것을 추가하고 있지만 매우 짧은 코드를 보여 주기로 결정했습니다. 사용 가능한 옵션을 표시하십시오. @shelvacu가 말하고있는 것은 열거자가 아닙니다. 여기

require 'benchmark' 
test = Test.new 
n=1000*1000*100 
Benchmark.bm do |b| 
    b.report { 1000000.times{ test.each_y{|x| @foo=x} } } 
    b.report { 1000000.times{ test.each_b{|x| @foo=x} } } 
end 

를 결과입니다 :

class Test 
    def initialize 
    @data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38] 
    end 

    # approach 1 
    def each_y 
    @data.each{ |x| yield(x) } 
    end 

    #approach 2 
    def each_b(&block) 
    @data.each(&block) 
    end 
end 

체크 성능을 수 있습니다

 user  system  total  real 
    1.660000 0.000000 1.660000 ( 1.669462) 
    1.830000 0.000000 1.830000 ( 1.831754) 

yield은 소폭 빨리 우리가 이미 BTW 알고 & 블록보다 의미합니다.

UPDATE : 이것은 이미 방법이기 때문에 왜`each`를 사용 result_array` @`를 반복하지 IMO 또한 열거

class Test 
    def each 
    if block_given? 
     @data.each{|x| yield(x)} 
    else  
     return @data.each 
    end 
    end 
end 
관련 문제