Идиоматический Ruby - выполняет функцию до тех пор, пока она не вернет ноль, собирая ее значения в список - PullRequest
13 голосов
/ 31 июля 2011

Я украл свой заголовок из этого поста: Выполняет функцию, пока не возвращает ноль, собирая ее значения в список

Этот вопрос относится к Лиспу и, честно говоря, над моей головой. Тем не менее, я думаю, что его вопрос - переведенный на Ruby - точно мой:

Каков наилучший способ создания условного цикла в [Ruby], который выполняет функцию до тех пор, пока не возвращает NIL, и в это время он собирает возвращенные значения в список?

Мой нынешний неуклюжий подход таков:

def foo
   ret = Array.new
   x = func() # parenthesis for clarity (I'm not a native Ruby coder...)
   until x.nil?
     ret << x
     x = func() 
   end
   ret
end

Этот фрагмент кода будет делать то, что я хочу ... но я знаю, что есть более чистый, более идиоматически подход Ruby ... верно?

Ответы [ 5 ]

10 голосов
/ 31 июля 2011

Забавно, как никто не предложил Enumerator и его take_while метод, мне кажется, это просто подходит:

# example function that sometimes returns nil
def func
  r = rand(5)
  r == 0 ? nil : r
end

# wrap function call into lazy enumerator
enum = Enumerator.new{|y|
  loop {
    y << func()
  }
}

# take from it until we bump into a nil
arr = enum.take_while{|elem|
  !elem.nil?
}

p arr
#=>[3, 3, 2, 4, 1, 1]
8 голосов
/ 31 июля 2011

Полагаю, это больше похоже на Ruby:

def foo
  r = nil; [].tap { |a| a << r until (r = yield).nil? }
end
3 голосов
/ 31 июля 2011

Это должно помочь:

def foo
  arr = []
  while true
    x = yield
    break if x.nil?
    arr << x
  end
  arr
end

Использование:

foo { doStuff }
foo &bar
1 голос
/ 31 июля 2011

Я бы написал это так, чтобы вы могли передать блок, который вызывает функцию или делает все, что вы хотите:

def gather
  [].tap do |collection|
    result=true; i=0
    until result.nil?
        unless (result=yield(i)).nil?
        collection << result
      end
      i += 1
      end
  end
end


# Silly test
$letters = *('a'..'z')
$current = -1
def next_char(max)
  $letters[$current+=1] if $current<max
end
some = gather{ next_char(10) }
p some
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]

some = gather{ |i| $letters[i] if i<=10 }
p some
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]

Обратите внимание, что вы можете написать метод следующим образом:

def gather
  [].tap do |result|
    x = true
    result << x unless (x=yield).nil? until x.nil?
  end
end

… но лично я не нахожу это очень читабельным.

0 голосов
/ 31 июля 2011

Я думаю, вы были довольно близки для начала.

def gather
  ret = []
  while x = yield
    ret << x
  end
  ret
end

Единственный трюк здесь - это понимание того, что присваивание "x = yield" возвращает значение x, поэтому "while x = yield" зацикливается, пока x не равен nil / false. Не путать с "while x == yield" ...

...