Pergunta

Neste código, crio uma matriz de strings "1" para "10000":

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

A API do Ruby Core fornece uma maneira de obter um objeto enumerável que me permite enumerar sobre a mesma lista, gerando os valores da string sob demanda, em vez de gerar uma matriz das cordas?

Aqui está um exemplo adicional que, esperançosamente, esclarece o que estou tentando fazer:

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

Onde xform é o método que estou procurando. Awesome_names é um enumerável, então xform Não está criando uma matriz de 1 milhão de elementos de cordas, mas apenas gerando e retornando strings da forma "hacker_ [n] sob demanda.

A propósito, eis como pode ser em C#:

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

(Uma solução)

Aqui está uma extensão para o enumerador que adiciona um método xform. Ele retorna outro enumerador que itera sobre os valores do enumerador original, com uma transformação aplicada a ele.

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}
Foi útil?

Solução

Ruby 2.0 introduzido Enumerable#lazy o que permite cadeia map, select, etc ... e apenas gerar os resultados finais no final com to_a, first, etc ... você pode usá -lo em qualquer versão rubi com 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'

Caso contrário, você pode usar Enumerator.new { with_a_block }. É novo em Ruby 1.9, então require 'backports/1.9.1/enumerator/new' Se você precisar no Ruby 1.8.x.

De acordo com o seu exemplo, o seguinte não criará uma matriz intermediária e só construirá as seqüências necessárias:

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

Você pode até substituir o 100000 por 1,0/0 (ou seja, infinito), se quiser.

Para responder ao seu comentário, se você está sempre mapeando seus valores um a um, pode ter algo como:

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

Outras dicas

Parece que você deseja um objeto enumerador, mas não exatamente.

Isto é, um objeto enumerador é um objeto que você pode usar para ligar next sob demanda (ao invés de each que faz todo o loop). (Muitas pessoas usam o idioma dos iteradores internos versus externos: each é interno e um enumerador é externo. Você dirige.)

Veja como um enumerador pode parecer:

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

Aqui está um link, para mais discussões sobre como você pode usar os enumeradores preguiçosamente em Ruby: http://www.michaelharrison.ws/weblog/?p=163

Há também uma seção sobre isso no livro de picareta (Programação rubi por Dave Thomas).

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"

A próxima coisa a fazer é devolver um enumerador quando chamado sem um bloco ...

As listas têm um método:

(1..100000).each
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top