Idiomatic Ruby - Выполняет функцию до тех пор, пока она не вернет нуль, собирая ее значения в список
Я украл свой титул из этого сообщения: Выполняет функцию, пока не вернет нуль, собирая его значения в списке
Этот вопрос относится к Lisp и, честно говоря, над моей головой. Тем не менее, я думаю, что его вопрос, переведенный на 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... правильно?
Ответы
Ответ 1
Забавно, как никто не предлагал 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]
Ответ 2
Я думаю, это больше похоже на Ruby:
def foo
r = nil; [].tap { |a| a << r until (r = yield).nil? }
end
Ответ 3
Это должно сделать трюк:
def foo
arr = []
while true
x = yield
break if x.nil?
arr << x
end
arr
end
Использование:
foo { doStuff }
foo &bar
Ответ 4
Я бы лично написал его так, чтобы вы могли передать блок, который вызывает функцию или делает все, что вы хотите:
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
... но я лично не считаю, что это очень читаемо.
Ответ 5
Я думаю, вы были близки к началу.
def gather
ret = []
while x = yield
ret << x
end
ret
end
Единственным трюком здесь является понимание того, что присваивание "x = yield" возвращает значение x, поэтому "while x = yield" пеет, а x не равен nil/false. Не путать с "while x == yield"...