У меня есть массив Ruby, содержащий некоторые строковые значения. Мне нужно:
- Найдите все элементы, соответствующие некоторому предикату
- Проведите соответствующие элементы через преобразование
- Вернуть результаты в виде массива
Сейчас мое решение выглядит так:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Есть ли метод Array или Enumerable, который объединяет выбор и отображение в один логический оператор?
Enumerable#grep
Метод делает именно то , что было предложено и уже в Рубине на протяжении более десяти лет. Требуется аргумент предиката и блок преобразования. @hirolau дает единственно правильный ответ на этот вопрос.filter_map
для этой цели. Больше информации здесь .Ответы:
Я обычно использую
map
иcompact
вместе с моим критерием выбора в качестве постфиксаif
.compact
избавляется от нулей.источник
map
+compact
действительно будет работать лучше,inject
и опубликовал результаты моих тестов в соответствующем потоке: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
иselect
, это просто , чтоcompact
это особый случай ,reject
который работает на Nils и выполняет несколько лучше из - за того , были реализованы непосредственно в С.reduce
Для этого можно использовать, для чего требуется всего один проход:Другими словами, инициализируйте состояние таким образом, чтобы оно было желаемым (в нашем случае это пустой список для заполнения :)
[]
, а затем всегда возвращайте это значение с изменениями для каждого элемента в исходном списке (в нашем случае измененный элемент помещен в список).Это наиболее эффективно, так как он перебирает список только за один проход (
map
+select
илиcompact
требует двух проходов).В твоем случае:
источник
each_with_object
больше смысла? Вам не нужно возвращать массив в конце каждой итерации блока. Вы можете просто сделатьmy_array.each_with_object([]) { |i, a| a << i if i.condition }
.reduce
исхожу из функционального фона, поэтому я нашел первым 😊Рубин 2.7+
Есть сейчас!
Ruby 2.7 вводится именно
filter_map
для этой цели. Это идиоматично и производительно, и я ожидаю, что очень скоро это станет нормой.Например:
Вот хорошее чтение по этой теме .
Надеюсь, это кому-то пригодится!
источник
filter
, что, посколькуselect
иfind_all
являются синонимами, так же какmap
иcollect
являются, может быть трудно запомнить имя этого метода. Является ли этоfilter_map
,select_collect
,find_all_map
илиfilter_collect
?Другой способ подойти к этому - использовать новый (относительно этого вопроса)
Enumerator::Lazy
:.lazy
Метод возвращает ленивый перечислитель. Вызов.select
или.map
на ленивом перечислителе возвращает другой ленивый перечислитель. Только после того, как вы вызываете.uniq
, он фактически заставляет перечислитель и возвращает массив. Так что фактически происходит, ваши.select
и.map
вызовы будут объединены в один - вы только итерации через@lines
один раз , чтобы сделать как.select
и.map
.reduce
Мне кажется, что метод Адама будет немного быстрее, но я думаю, что он гораздо удобнее для чтения.Основным следствием этого является то, что при каждом последующем вызове метода не создаются промежуточные объекты массива. В нормальной
@lines.select.map
ситуацииselect
возвращает массив, который затем модифицируетсяmap
, снова возвращая массив. Для сравнения, ленивое вычисление создает массив только один раз. Это полезно, когда ваш исходный объект коллекции большой. Это также дает вам возможность работать с бесконечными счетчиками - напримерrandom_number_generator.lazy.select(&:odd?).take(10)
.источник
reduce
трансформация "делать все" всегда кажется мне довольно беспорядочной.@lines
один раз, чтобы сделать и то,.select
и другое.map
». Использование.lazy
не означает, что связанные операции в ленивом перечислителе будут «свернуты» в одну итерацию. Это распространенное заблуждение относительно ленивых вычислений, связанных с операциями цепочки над коллекцией. (Вы можете проверить это, добавивputs
заявление в началеselect
иmap
блоков в первом примере Вы увидите , что они печатают такое же количество строк.).lazy
он будет печататься столько же раз. Это моя точка зрения - вашmap
блок и вашselect
блок выполняются одинаковое количество раз в ленивой и нетерпеливой версиях. Ленивая версия не «объединяет ваши.select
и.map
звонки»lazy
объединяет их, потому что элемент, который не соответствуетselect
условию, не передается вmap
. Другими словами: добавление в началоlazy
примерно эквивалентно заменеselect
иmap
однимreduce([])
, и «разумно» превращениеselect
блока в предварительное условие для включения вreduce
результат.Если у вас есть оператор,
select
который может использоватьcase
оператор (===
),grep
это хорошая альтернатива:Если нам нужна более сложная логика, мы можем создать лямбды:
источник
Я не уверен, что он есть. Модуль Enumerable , который добавляет
select
иmap
, не показывает его.Вам потребуется передать два блока
select_and_transform
методу, что было бы немного неинтуитивно, ИМХО.Очевидно, вы можете просто связать их вместе, что более читабельно:
источник
Простой ответ:
Если у вас есть n записей, и вы хотите
select
и вmap
зависимости от условия, тогдаЗдесь атрибут - это все, что вы хотите от записи, а условие вы можете поставить любой галочкой.
compact - это смыть ненужные нули, которые возникли в результате условия if
источник
Нет, но это можно сделать так:
Или даже лучше:
источник
reject(&:nil?)
в основном то же самое, что иcompact
.Я думаю, что этот способ более читабелен, потому что разделяет условия фильтра и отображаемое значение, оставаясь при этом ясным, что действия связаны:
И в вашем конкретном случае удалите
result
переменную все вместе:источник
В Ruby 1.9 и 1.8.7 вы также можете связать и обернуть итераторы, просто не передавая им блок:
Но в данном случае это невозможно, поскольку типы возвращаемых значений блоков
select
иmap
не совпадают. Это имеет больше смысла для чего-то вроде этого:AFAICS, лучшее, что вы можете сделать, - это первый пример.
Вот небольшой пример:
Но на самом деле вы хотите
["A", "B", "C", "D"]
.источник
select
возвращает логическое значение, которое решает, сохранить элемент или нет,map
возвращает преобразованное значение. Само преобразованное значение, вероятно, будет правдивым, поэтому будут выбраны все элементы.Вам следует попробовать использовать мою библиотеку Rearmed Ruby, в которую я добавил этот метод
Enumerable#select_map
. Вот пример:источник
select_map
в этой библиотеке просто реализует ту жеselect { |i| ... }.map { |i| ... }
стратегию из многих ответов выше.Если вы не хотите создавать два разных массива, вы можете использовать,
compact!
но будьте осторожны.Интересно,
compact!
что вместо удаления nil. Возвращаемое значениеcompact!
- тот же массив, если были изменения, но nil, если не было nils.Был бы одним лайнером.
источник
Ваша версия:
Моя версия:
Это выполнит 1 итерацию (кроме сортировки) и имеет дополнительный бонус в виде сохранения уникальности (если вас не волнует uniq, просто сделайте результаты массивом и
results.push(line) if ...
источник
Вот пример. Это не то же самое, что ваша проблема, но может быть тем, что вы хотите, или может дать ключ к вашему решению:
источник