Eu tenho um map
que altera um valor ou define como nulo. Desejo então remover as entradas nulas da lista. A lista não precisa ser mantida.
Isto é o que eu tenho atualmente:
# A simple example function, which returns a value or nil
def transform(n)
rand > 0.5 ? n * 10 : nil }
end
items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]
Estou ciente de que eu poderia fazer um loop e coletar condicionalmente em outra matriz como esta:
new_items = []
items.each do |x|
x = transform(x)
new_items.append(x) unless x.nil?
end
items = new_items
Mas não parece tão idiomático. Existe uma boa maneira de mapear uma função em uma lista, removendo / excluindo os nils à medida que avança?
filter_map
, o que parece ser perfeito para isso. Economiza a necessidade de reprocessar a matriz, obtendo-a como desejado na primeira vez. Mais informações aqui.Respostas:
Ruby 2.7+
Existe agora!
O Ruby 2.7 está sendo apresentado
filter_map
para esse fim exato. É idiomático e com bom desempenho, e eu espero que isso se torne a norma muito em breve.Por exemplo:
No seu caso, como o bloco é avaliado como falsey, basta:
" Ruby 2.7 adiciona o enumerável # filter_map " é uma boa leitura sobre o assunto, com alguns benchmarks de desempenho em relação a algumas das abordagens anteriores para esse problema:
fonte
Você poderia usar
compact
:Gostaria de lembrar às pessoas que se você estiver obtendo uma matriz contendo nils como a saída de um
map
bloco, e esse bloco tenta retornar valores condicionalmente, então você tem cheiro de código e precisa repensar sua lógica.Por exemplo, se você estiver fazendo algo que faça isso:
Então não. Em vez disso, antes do
map
,reject
o que você não deseja ouselect
o que deseja:Considero usar
compact
a limpeza de uma bagunça como um esforço de última hora para nos livrar de coisas que não lidamos corretamente, geralmente porque não sabíamos o que estava vindo para nós. Sempre devemos saber que tipo de dados estão sendo lançados em nosso programa; Dados inesperados / desconhecidos estão incorretos. Sempre que vejo nulos em uma matriz em que estou trabalhando, analiso por que eles existem e vejo se posso melhorar o código que gera a matriz, em vez de permitir que Ruby perca tempo e nils geradores de memória e, em seguida, vasculhando a matriz para remover depois.fonte
nil
entradas, não as cadeias vazias. BTW,nil
não é o mesmo que uma string vazia.reduce
ouinject
?compact
é mais rápido, mas escrever o código corretamente no início remove a necessidade de lidar com nils completamente.Tente usar
reduce
ouinject
.Concordo com a resposta aceita de que não devemos
map
ecompact
, mas não pelas mesmas razões.Sinto profundamente que
map
issocompact
é equivalente aselect
entãomap
. Considere:map
é uma função individual. Se você estiver mapeando a partir de algum conjunto de valores e desejarmap
, deseja um valor no conjunto de saída para cada valor no conjunto de entrada. Se você está precisandoselect
de antemão, provavelmente não quer ummap
no set. Se você tiver que fazer issoselect
depois (oucompact
), provavelmente não quer ummap
no set. Em ambos os casos, você está iterando duas vezes em todo o conjunto, quandoreduce
é necessário apenas uma vez.Além disso, em inglês, você está tentando "reduzir um conjunto de números inteiros para um conjunto de números pares".
fonte
No seu exemplo:
não parece que os valores mudaram além de serem substituídos por
nil
. Se for esse o caso, então:será suficiente.
fonte
Se você deseja um critério mais flexível para rejeição, por exemplo, para rejeitar cadeias vazias e nulas, você pode usar:
Se você quiser ir além e rejeitar valores zero (ou aplicar lógica mais complexa ao processo), poderá passar um bloco para rejeitar:
fonte
blank?
está disponível apenas nos trilhos, poderíamos usar oitems.reject!(&:nil?) # [1, nil, 3, nil, nil] => [1, 3]
que não está acoplado aos trilhos. (não excluiria seqüências de caracteres vazias ou 0s) #Definitivamente,
compact
é a melhor abordagem para resolver esta tarefa. No entanto, podemos alcançar o mesmo resultado apenas com uma simples subtração:fonte
each_with_object
é provavelmente a maneira mais limpa de ir aqui:Na minha opinião,
each_with_object
é melhor queinject
/reduce
em casos condicionais, porque você não precisa se preocupar com o valor de retorno do bloco.fonte
Mais uma maneira de conseguir isso será como mostrado abaixo. Aqui, usamos
Enumerable#each_with_object
para coletar valores e utilizamosObject#tap
para nos livrar da variável temporária que é necessária paranil
verificar o resultado doprocess_x
método.Exemplo completo para ilustração:
Abordagem alternativa:
Observando o método que você está chamando
process_x url
, não está claro qual é o objetivo da entradax
nesse método. Se eu presumir que você vai processar o valorx
passando um pouco para eleurl
e determinar qual dosx
realmente é processado em resultados válidos e não nulos - então, pode serEnumerabble.group_by
uma opção melhor do queEnumerable#map
.fonte