Ruby Enumerable: получить первое, которое не равно нулю, но результатом должно быть то, что находится внутри блока

Метод Enumerable#find работает путем оценки до тех пор, пока не найдет элемент, соответствующий условию в блоке. Есть ли что-то подобное для возврата в первый раз, когда блок не оценивается до нуля? представьте, что у вас будет коллекция хэшей:

value = nil
options.each do |o|                     
  break if value = o[:desired]
end                                     
value ||= DEFAULT

разве нет метода, который уже выполняет это?

Нет смысла делать много преобразований в коллекции, я хотел бы свести к минимуму количество выделений, поэтому любое решение, которое выделяет новый массив, мне не подойдет.


person ChuckE    schedule 07.01.2016    source источник
comment
Не уверен, что это то, о чем вы спрашиваете, но разве простое отрицание проверки nil? не соответствует вашим требованиям?   -  person ndnenkov    schedule 07.01.2016


Ответы (5)


Метод find будет работать для поиска первого элемента с ключом :desired с минимальными итерациями.

Я думаю, вы хотите получить значение ключа desired из блока вместо самого элемента - в Enumerable нет метода, который ведет себя как смесь find и map - вам придется использовать внешнюю переменную, которой присваивается значение внутри блок, как показано ниже.

options = [{foo: 1}, {desired: 2}, {bar: 3}]

value = nil
options.find do |o|
  value = o[:desired]
  break if value
end

p value
#=> 2

Это более или менее похоже на ваш код, который также должен работать нормально.

Ниже приведен один из способов, который вы можете использовать, если хотите использовать методы Enumerable, но он будет перебирать все элементы.

p value = options.map { |o| value = o[:desired] }.compact.first
person Wand Maker    schedule 07.01.2016
comment
Я думаю, он хочет, чтобы метод сначала нашел ключ :desired - person SteveTurczyn; 07.01.2016
comment
@SteveTurczyn Я понял, о чем он сейчас спрашивает - спасибо, что указали на ошибку в моем предыдущем ответе. - person Wand Maker; 07.01.2016

Вы можете использовать reduce:

value = options.reduce(nil){|memo, entry|  memo || entry[:desired]  } || DEFAULT
person lorefnon    schedule 07.01.2016
comment
не будет ли он анализировать коллекцию опций до конца? это то, чего я хотел избежать. #find прекратит оценку, как только найдет совпадение. - person ChuckE; 08.01.2016
comment
Правильно, но со значением можно порвать - см. мой ответ stackoverflow.com/a/57630623/2993768 - person stujo; 23.08.2019

Начиная с Ruby 2.0, это можно сделать, объединив #map и #find с ленивые перечисления:

value = options.lazy.map { |o| o[:desired] }.find { |x| !x.nil? } # or find(&:present?) with ActiveSupport
value ||= DEFAULT
person Ryan Lue    schedule 07.08.2018

Это пришло мне в голову сегодня: я думаю, что мы можем использовать break со значением из сокращения

treasure = [1,2,3].reduce(nil) do |memo, value|
  break memo if memo
  foo(value)
end
person stujo    schedule 23.08.2019

Как насчет

options.reduce{ |_,o|
   break o if o[:desired]
   DEFAULT
}

or

catch do |tag|
   options.each{ |_,o| o[:desired] and throw tag, o }
   DEFAULT
end

Последний допускает рекурсию.

person guest    schedule 14.05.2020
comment
Не могли бы вы отредактировать свой ответ, включив в него краткое объяснение того, чем он отличается от предыдущих существующих ответов? - person Jeremy Caney; 15.05.2020