2010-01-17 25 views
38

Tôi có một đối tượng Kết quả có chứa một mảng các đối tượng result cùng với một số thống kê được lưu trong bộ nhớ cache về các đối tượng trong mảng. Tôi muốn đối tượng Results có thể hoạt động như một mảng. Lần cắt đầu tiên của tôi tại đây là thêm các phương thức như thế nàyLàm cách nào để thêm phương thức 'each' vào đối tượng Ruby (hoặc tôi nên mở rộng Mảng)?

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

Điều này cảm thấy rất giống và tôi biết Ruby có cách tốt hơn.

Tôi cũng muốn để có thể làm được điều này

Results.each do |result| 
    result.do_stuff 
end 

nhưng không chắc chắn những gì các phương pháp each được thực sự làm dưới mui xe.

Hiện tại tôi chỉ đơn giản là trả về mảng cơ bản thông qua một phương thức và gọi mỗi phương thức đó không giống như giải pháp thanh lịch nhất.

Mọi trợ giúp sẽ được đánh giá cao.

Trả lời

61

Đối trường hợp chung của việc thực hiện các phương thức giống như mảng, có, bạn phải tự mình thực hiện chúng. Câu trả lời của Vava cho thấy một ví dụ về điều này. Trong trường hợp bạn đưa ra, mặc dù, những gì bạn thực sự muốn làm là đại biểu nhiệm vụ xử lý each (và có thể một số phương pháp khác) để mảng chứa, và có thể được tự động.

require 'forwardable' 

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

Lớp này sẽ nhận được tất cả các hành vi Enumerable Array Avatar của cũng như Array << điều hành và tất cả nó sẽ đi qua các mảng nội tại.


Lưu ý, rằng khi bạn chuyển mã của bạn từ mảng thừa kế để lừa này, << phương pháp của bạn sẽ bắt đầu quay trở lại không phải là intself đối tượng, như mảng thực của << đã - điều này có thể chi phí bạn tuyên bố khác mọi biến bạn sử dụng <<.

+1

Tôi biết có một cái gì đó như thế trong thư viện ruby. Bây giờ, nếu tôi chỉ có thể gắn dấu sao câu trả lời này để tìm nó sau này ... – vava

+5

Bạn cần phải 'mở rộng Chuyển tiếp', không phải' bao gồm' nó. –

+0

Thông tin tuyệt vời về chuyển tiếp nhưng làm theo cách đó không cho phép anh ta thao túng các giá trị. Anh ấy gọi một phương pháp vì vậy tôi giả sử anh ta muốn làm một cái gì đó với dữ liệu trước khi trả lại nó. Tôi có thể sai mặc dù. –

8

Điều này cảm thấy rất giống và tôi biết Ruby có cách tốt hơn.

Nếu bạn muốn một đối tượng 'cảm thấy' như một mảng, hơn là ghi đè < < là một ý tưởng hay và rất 'Ruby'-ish.

nhưng không chắc chắn mỗi phương pháp thực sự đang làm gì dưới mui xe.

Mỗi phương pháp cho mảng chỉ lặp qua tất cả các phần tử (sử dụng vòng lặp for, Tôi nghĩ). Nếu bạn muốn thêm mỗi phương pháp riêng của bạn (mà cũng rất 'Ruby'-ish), bạn có thể làm một cái gì đó như thế này:

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

Tại sao không lặp trên '@ result_array' sử dụng' each' vì nó đã là một phương pháp của Array? –

35

each chỉ đi qua mảng và gọi khối đưa ra với mỗi yếu tố, đó là đơn giản. Vì bên trong lớp bạn đang sử dụng mảng là tốt, bạn chỉ có thể chuyển hướng phương thức each của bạn đến một từ mảng, đó là nhanh chóng và dễ đọc/duy trì.

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 

Lưu ý rằng tôi đã trộn trong Enumerable. Điều đó sẽ cung cấp cho bạn một loạt các phương thức mảng như all?, map, v.v ... miễn phí.

BTW với Ruby bạn có thể quên về kế thừa. Bạn không cần thừa kế giao diện vì việc gõ vịt không thực sự quan tâm đến loại thực tế, và bạn không cần thừa kế mã vì các mixin chỉ tốt hơn cho loại thứ đó.

+0

Không thực sự giải thích việc thực hiện phương pháp 'mỗi' mặc dù, và taht là câu hỏi. –

+0

@Ed: Chắc chắn rồi. Bạn có mỗi phương thức làm bất cứ điều gì là cần thiết để lặp qua các phần tử của đối tượng, mà phụ thuộc vào việc thực hiện thực tế của lớp. Trong trường hợp này, sự lựa chọn hiển nhiên là có 'mỗi' lặp qua các phần tử của mảng. – Chuck

+0

Có vẻ như một phương pháp bao bọc đơn giản cho tôi. –

5

Nếu bạn tạo một lớp Kết quả kế thừa từ mảng, bạn sẽ kế thừa tất cả các chức năng.

Sau đó, bạn có thể bổ sung các phương pháp cần thay đổi bằng cách xác định lại chúng và bạn có thể gọi siêu cho chức năng cũ.

Ví dụ:

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 

Ngoài ra, bạn có thể sử dụng thư viện được xây dựng trong forwardable. Điều này đặc biệt hữu ích nếu bạn không thể kế thừa từ mảng bởi vì bạn cần phải kế thừa từ một lớp khác:

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 

Trong cả hai hình thức, bạn có thể sử dụng nó như bạn muốn:

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

Phương pháp < < của bạn hoàn toàn ổn và rất giống với Ruby.

Để làm cho lớp hoạt động như một mảng, mà không thực sự kế thừa trực tiếp từ mảng, bạn có thể kết hợp mô-đun Enumerable và thêm một vài phương thức.

Dưới đây là một ví dụ (trong đó có gợi ý tuyệt vời của Chuck để sử dụng Forwardable):

# 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}" 

Những kết quả đầu ra:

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

Nếu bạn thực sự muốn thực hiện phương pháp #each của riêng bạn, và giả sử bạn không muốn chuyển tiếp, bạn nên trả lại một ĐTV nếu không có khối nào được cấp

class MyArrayLikeClass 
    include Enumerable 

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

Điều này sẽ trả về một đối tượng Enumerable nếu không có khối nào được đưa ra, cho phép phương thức Enumerable chaining

+0

Với tôi, thực tế Ruby cho phép người dùng bắt chước đầy đủ các tính năng ngôn ngữ bản địa là điều khiến Ruby trở nên đẹp. – pedz

3

Không chắc chắn tôi đang thêm bất kỳ điều gì mới, nhưng quyết định hiển thị mã rất ngắn mà tôi ước mình có thể tìm thấy trong câu trả lời hiển thị các tùy chọn có sẵn. Ở đây nó không có điều tra mà @shelvacu nói về.

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 

Cho phép thực hiện kiểm tra:

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 

Dưới đây là kết quả:

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

Điều này có nghĩa là yield nhẹ nhanh hơn & khối những gì chúng ta đã biết btw.

CẬP NHẬT: Đây là IMO cách tốt nhất để tạo ra một mỗi phương pháp đó cũng sẽ chăm sóc của trở về một Enumerator

class Test 
    def each 
    if block_given? 
     @data.each{|x| yield(x)} 
    else  
     return @data.each 
    end 
    end 
end 
Các vấn đề liên quan