2010-02-24 25 views
7

Trong mã này, tôi tạo ra một mảng của chuỗi "1" đến "10000":Tương đương với Ruby Enumerable.collect trả về một Enumerable?

array_of_strings = (1..10000).collect {|i| String(i)} 

Liệu API của Ruby cốt lõi cung cấp một cách để có được một đối tượng đếm được cho phép tôi liệt kê trong danh sách tương tự, tạo ra giá trị chuỗi theo yêu cầu, thay vì tạo ra một mảng các chuỗi?

Dưới đây là một ví dụ nữa mà hy vọng làm rõ những gì tôi đang cố gắng để làm:

def find_me_an_awesome_username 
    awesome_names = (1..1000000).xform {|i| "hacker_" + String(i) } 
    awesome_names.find {|n| not stackoverflow.userexists(n) } 
end 

đâu xform là phương pháp tôi đang tìm kiếm. awesome_names là một số, do đó, xform không phải tạo chuỗi chuỗi 1 triệu phần tử, mà chỉ tạo và trả về các chuỗi có dạng "hacker_ [N]" theo yêu cầu.

Bằng cách này, đây là những gì nó có thể trông giống như trong C#:

var awesomeNames = from i in Range(1, 1000000) select "hacker_" + i; 
var name = awesomeNames.First((n) => !stackoverflow.UserExists(n)); 

(Một Solution)

Dưới đây là một phần mở rộng cho Enumerator có thêm một phương pháp xform. Nó trả về một điều tra viên khác lặp lại các giá trị của điều tra ban đầu, với một biến đổi được áp dụng cho nó.

class Enumerator 
    def xform(&block) 
    Enumerator.new do |yielder| 
     self.each do |val| 
     yielder.yield block.call(val) 
     end 
    end 
    end 
end 

# this prints out even numbers from 2 to 10: 
(1..10).each.xform {|i| i*2}.each {|i| puts i} 
+0

... nên đọc '2-20' – mackenir

Trả lời

6

của Ruby 2.0 giới thiệu Enumerable#lazy cho phép một đến chuỗi map, select, vv ..., và chỉ tạo ra kết quả cuối cùng ở cuối cùng với to_a, first, v.v ... Bạn có thể sử dụng nó trong bất kỳ phiên bản Ruby nào với require 'backports/2.0.0/enumerable/lazy'.

require 'backports/2.0.0/enumerable/lazy' 
names = (1..Float::INFINITY).lazy.map{|i| "hacker_" + String(i) } 
names.first # => 'hacker_1' 

Nếu không, bạn có thể sử dụng Enumerator.new { with_a_block }. Nó mới trong Ruby 1.9, vì vậy require 'backports/1.9.1/enumerator/new' nếu bạn cần nó trong Ruby 1.8.x.

Theo ví dụ của bạn, sau đây sẽ không tạo ra một mảng trung gian và sẽ chỉ xây dựng các dây cần thiết:

require 'backports/1.9.1/enumerator/new' 

def find_me_an_awesome_username 
    awesome_names = Enumerator.new do |y| 
    (1..1000000).each {|i| y.yield "hacker_" + String(i) } 
    end 
    awesome_names.find {|n| not stackoverflow.userexists(n) } 
end 

Bạn thậm chí có thể thay thế 100000 bởi 1.0/0 (tức là Infinity), nếu bạn muốn .

Để trả lời bình luận của bạn, nếu bạn luôn được lập bản đồ giá trị của bạn 12:59, bạn có thể có một cái gì đó như:

module Enumerable 
    def lazy_each 
    Enumerator.new do |yielder| 
     each do |value| 
     yielder.yield(yield value) 
     end 
    end 
    end 
end 

awesome_names = (1..100000).lazy_each{|i| "hacker_#{i}"} 
+0

@marc, có vẻ như vậy! Bạn có biết làm thế nào tôi có thể biến mô hình này thành một phương pháp có thể sử dụng lại súc tích hơn, sử dụng 'điều có thể đếm' và 'chức năng biến áp'? Trong ví dụ này, 'chức năng biến áp' sẽ là '{| i | "hacker_" + String (i)} 'và 'điều có thể đếm' sẽ là' (1..100000) 'hoặc bất kỳ thứ gì. – mackenir

+0

Cảm ơn rất nhiều. Tôi cập nhật câu hỏi của tôi với một thực hiện có thể mà tôi đã làm việc ra khỏi câu trả lời của bạn, và cũng từ đọc liên kết mà @Telemachus đăng, trước khi tôi nhận thấy * cập nhật * của bạn :) Một lần nữa, cảm ơn! – mackenir

+0

@mackenir: Hàm chuyển đổi đếm được là 'bản đồ'. Bạn chỉ có thể thay đổi 'each' thành' map' và giữ nguyên thuật toán. – Chuck

0

danh sách có mỗi phương pháp:

(1..100000).each 
+1

... okay, tiếp tục đi . :) – mackenir

+1

... okay, bây giờ bạn bắt đầu tìm kiếm về Ruby iteration. – Geo

+0

Nhưng mã của bạn chỉ lặp lại trong phạm vi số nguyên. Nó không tạo ra một chuỗi số đếm mới. Hãy thử và đặt mình vào đôi giày ngu ngốc của tôi :). – mackenir

1

Có vẻ như bạn muốn một đối tượng Enumerator, nhưng không chính xác.

Tức là đối tượng Đếm là đối tượng mà bạn có thể sử dụng để gọi next theo yêu cầu (thay vì each làm toàn bộ vòng lặp). (Nhiều người sử dụng ngôn ngữ của nội bộ so với vòng lặp bên ngoài:.. each là nội bộ, và một Enumerator là bên ngoài Bạn lái xe nó)

Đây là cách một Enumerator có thể trông:

awesome_names = Enumerator.new do |y| 
    number = 1 
    loop do 
    y.yield number 
    number += 1 
    end 
end 

puts awesome_names.next 
puts awesome_names.next 
puts awesome_names.next 
puts awesome_names.next 

Dưới đây là một liên kết, để thảo luận thêm về cách bạn có thể sử dụng Enumerators lazily trong Ruby: http://www.michaelharrison.ws/weblog/?p=163

Ngoài ra còn có một phần trong cuốn sách Pickaxe (Lập trình Ruby bởi Dave Thomas).

+0

Cảm ơn. Hừm. Tìm chắc chắn dừng liệt kê khi nó findsa phù hợp với yếu tố. Bạn có thể xác nhận điều này bằng cách chạy tìm trên một phạm vi rất lớn, với biến vị ngữ 'false' và 'true' cái sau sẽ trả về ngay lập tức. Nếu cả hai đều liệt kê mọi thứ họ sẽ trở lại cùng một lúc. Re: đếm với tiếp theo, tôi đang cố gắng tìm cơ sở 'enumerable transformer' để viết thêm terse, declarative code, và tự liệt kê wont thực sự đạt được điều đó. Có thể câu trả lời là chỉ thực hiện nó. – mackenir

+0

Với tất cả các CR bị loại bỏ, điều đó ít dễ hiểu hơn. Ý tôi là, (1..1000000000000000000) .find {| i | true} là nhanh, và (1..1000000000000000000) .find {| i | false} là chậm. Ý nghĩa tìm thấy chỉ liệt kê cho đến khi nó 'tìm thấy'. – mackenir

+0

Liên kết hữu ích - Tôi nghĩ tôi hiểu nó, và nó giúp trả lời câu hỏi. – mackenir

1
class T < Range 
    def each 
    super { |i| yield String(i) } 
    end 
end 

T.new(1,3).each { |s| p s } 
$ ruby rsc.rb 
"1" 
"2" 
"3" 

Điều tiếp theo cần làm là để trả lại một Enumerator khi gọi mà không có khối ...

Các vấn đề liên quan