Eu tenho uma matriz Ruby contendo alguns valores de string. Eu preciso:
- Encontre todos os elementos que correspondem a algum predicado
- Execute os elementos correspondentes por meio de uma transformação
- Retorna os resultados como uma matriz
No momento, minha solução é assim:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Existe um método Array ou Enumerable que combina selecionar e mapear em uma única instrução lógica?
Enumerable#grep
método faz exatamente o que foi pedido e está em Ruby há mais de dez anos. Leva um argumento de predicado e um bloco de transformação. @hirolau dá a única resposta correta para esta pergunta.filter_map
exatamente para esse propósito. Mais informações aqui .Respostas:
Eu geralmente uso
map
ecompact
junto com meus critérios de seleção como um postfixif
.compact
se livrar dos nils.fonte
map
+compact
teria realmente um desempenho melhor do queinject
e postei meus resultados de benchmark em um tópico relacionado: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
eselect
, é apenascompact
um caso especialreject
que funciona em nils e tem um desempenho um pouco melhor por ter sido implementado diretamente em C.Você pode usar
reduce
para isso, o que requer apenas uma passagem:Em outras palavras, inicialize o estado para ser o que você deseja (no nosso caso, uma lista vazia para preencher:
[]
, então sempre certifique-se de retornar este valor com modificações para cada elemento na lista original (no nosso caso, o elemento modificado empurrado para a lista).Este é o mais eficiente uma vez que só faz um loop na lista com uma passagem (
map
+select
oucompact
requer duas passagens).No seu caso:
fonte
each_with_object
faz um pouco mais de sentido? Você não precisa retornar o array no final de cada iteração do bloco. Você pode simplesmente fazermy_array.each_with_object([]) { |i, a| a << i if i.condition }
.reduce
primeiro 😊Ruby 2.7+
Existe agora!
Ruby 2.7 está apresentando
filter_map
exatamente para esse propósito. É idiomático e de alto desempenho, e espero que se torne a norma em breve.Por exemplo:
Aqui está uma boa leitura sobre o assunto .
Espero que seja útil para alguém!
fonte
filter
, uma vez queselect
efind_all
são sinônimos, assim comomap
ecollect
são, pode ser difícil lembrar o nome desse método. Éfilter_map
,select_collect
,find_all_map
oufilter_collect
?Outra maneira diferente de abordar isso é usando o novo (em relação a esta questão)
Enumerator::Lazy
:O
.lazy
método retorna um enumerador preguiçoso. Chamar.select
ou.map
em um enumerador preguiçoso retorna outro enumerador preguiçoso. Somente quando você chama.uniq
, ele realmente força o enumerador e retorna um array. Então, o que efetivamente acontece é a sua.select
e.map
as chamadas são combinados em um único - você só iterar@lines
uma vez para fazer as duas coisas.select
e.map
.Meu instinto é que o
reduce
método de Adam será um pouco mais rápido, mas acho que isso é muito mais legível.A principal consequência disso é que nenhum objeto de array intermediário é criado para cada chamada de método subsequente. Em uma
@lines.select.map
situação normal ,select
retorna uma matriz que é então modificada pormap
, novamente retornando uma matriz. Em comparação, a avaliação preguiçosa cria um array apenas uma vez. Isso é útil quando o objeto da coleção inicial é grande. Ele também permite que você trabalhe com recenseadores infinitas - egrandom_number_generator.lazy.select(&:odd?).take(10)
.fonte
reduce
como uma transformação do tipo "fazer tudo", sempre me parece bastante complicado.@lines
uma vez para fazer ambos.select
e.map
". Usar.lazy
não significa que operações de operações encadeadas em um enumerador preguiçoso ficarão "reduzidas" em uma única iteração. Este é um mal-entendido comum de avaliação preguiçosa em operações de encadeamento de uma coleção. (Você pode testar isso adicionando umaputs
instrução ao início dos blocosselect
emap
no primeiro exemplo. Você verá que eles imprimem o mesmo número de linhas).lazy
ele imprime o mesmo número de vezes. Esse é o meu ponto - seumap
bloco e seuselect
bloco são executados o mesmo número de vezes nas versões lazy e eager. A versão preguiçosa não "combina seu.select
e.map
chama"lazy
combina porque um elemento que falha naselect
condição não é passado para omap
. Em outras palavras: prefixarlazy
é aproximadamente equivalente a substituirselect
emap
com um únicoreduce([])
, e "inteligentemente" tornarselect
o bloco de uma pré-condição para inclusão noreduce
resultado de.Se você tem um
select
que pode usar ocase
operador (===
),grep
é uma boa alternativa:Se precisarmos de uma lógica mais complexa, podemos criar lambdas:
fonte
Não tenho certeza se há um. O módulo Enumerable , que adiciona
select
emap
, não mostra um.Você teria que passar dois blocos para o
select_and_transform
método, o que seria um IMHO um pouco não intuitivo.Obviamente, você pode apenas encadea-los, o que é mais legível:
fonte
Resposta simples:
Se você tiver n registros e quiser
select
e commap
base na condição, entãoAqui, o atributo é o que você quiser do registro e a condição você pode colocar qualquer verificação.
compacto é limpar os nulos desnecessários que saíram dessa condição se
fonte
Não, mas você pode fazer assim:
Ou melhor ainda:
fonte
reject(&:nil?)
é basicamente o mesmo quecompact
.Acho que dessa forma é mais legível, porque divide as condições do filtro e o valor mapeado, mantendo claro que as ações estão conectadas:
E, no seu caso específico, elimine a
result
variável completamente:fonte
No Ruby 1.9 e 1.8.7, você também pode encadear e agrupar iteradores simplesmente não passando um bloco para eles:
Mas não é realmente possível neste caso, uma vez que os tipos de bloco retornam valores de
select
emap
não coincidem. Faz mais sentido para algo assim:AFAICS, o melhor que você pode fazer é o primeiro exemplo.
Aqui está um pequeno exemplo:
Mas o que você realmente quer é
["A", "B", "C", "D"]
.fonte
select
retorna um valor booleano que decide se mantém o elemento ou não,map
retorna o valor transformado. O valor transformado em si provavelmente será verdadeiro, portanto, todos os elementos serão selecionados.Você deve tentar usar minha biblioteca Rearmed Ruby na qual adicionei o método
Enumerable#select_map
. Aqui está um exemplo:fonte
select_map
nesta biblioteca apenas implementa a mesmaselect { |i| ... }.map { |i| ... }
estratégia de muitas respostas acima.Se você não quiser criar dois arrays diferentes, pode usar,
compact!
mas tenha cuidado com isso.Curiosamente,
compact!
faz uma remoção de nada no local. O valor de retorno decompact!
é o mesmo array se houver alterações, mas nulo se não houver nulos.Seria um forro.
fonte
Sua versão:
Minha versão:
Isso fará 1 iteração (exceto a classificação) e tem o bônus adicional de manter a exclusividade (se você não se importa com o uniq, então apenas faça dos resultados uma matriz e
results.push(line) if ...
fonte
Aqui está um exemplo. Não é o mesmo que o seu problema, mas pode ser o que você deseja, ou pode dar uma pista para sua solução:
fonte