Подтвердить что ты не робот

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... правильно?

4b9b3361

Ответ 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"...