J'ai un map
qui modifie une valeur ou la définit à zéro. Je souhaite ensuite supprimer les entrées nil de la liste. La liste n'a pas besoin d'être conservée.
Voici ce que j'ai actuellement:
# 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]
Je suis conscient que je pourrais simplement faire une boucle et collecter conditionnellement dans un autre tableau comme celui-ci:
new_items = []
items.each do |x|
x = transform(x)
new_items.append(x) unless x.nil?
end
items = new_items
Mais cela ne semble pas si idiomatique. Existe-t-il un bon moyen de mapper une fonction sur une liste, en supprimant / excluant les nils au fur et à mesure?
filter_map
, ce qui semble être parfait pour cela. Enregistre le besoin de retraiter la baie, au lieu de la récupérer comme souhaité pour la première fois. Plus d'infos ici.Réponses:
Ruby 2.7+
Il y a maintenant!
Ruby 2.7 introduit
filter_map
dans ce but précis. C'est idiomatique et performant, et je m'attends à ce qu'il devienne la norme très bientôt.Par exemple:
Dans votre cas, comme le bloc est évalué à falsey, simplement:
" Ruby 2.7 ajoute Enumerable # filter_map " est une bonne lecture sur le sujet, avec quelques repères de performances par rapport à certaines des approches précédentes de ce problème:
la source
Vous pouvez utiliser
compact
:Je voudrais rappeler aux gens que si vous obtenez un tableau contenant nils en sortie d'un
map
bloc, et que ce bloc essaie de renvoyer des valeurs de manière conditionnelle, alors vous avez une odeur de code et vous devez repenser votre logique.Par exemple, si vous faites quelque chose qui fait ceci:
Alors ne le fais pas. Au lieu de cela, avant le
map
,reject
les choses que vous ne voulez pas ouselect
ce que vous voulez:J'envisage d'utiliser
compact
pour nettoyer un gâchis comme un ultime effort pour se débarrasser de choses que nous n'avons pas traitées correctement, généralement parce que nous ne savions pas ce qui nous arrivait. Nous devons toujours savoir quel type de données est diffusé dans notre programme; Les données inattendues / inconnues sont mauvaises. Chaque fois que je vois des nils dans un tableau sur lequel je travaille, je cherche pourquoi ils existent et je vois si je peux améliorer le code générant le tableau, plutôt que de permettre à Ruby de perdre du temps et de la mémoire à générer des nils puis de parcourir le tableau pour le supprimer les plus tard.la source
nil
entrées, pas les chaînes vides. BTW,nil
n'est pas la même chose qu'une chaîne vide.reduce
ouinject
?compact
est plus rapide, mais en réalité, l'écriture correcte du code au début élimine la nécessité de traiter complètement les nils.Essayez d'utiliser
reduce
ouinject
.Je suis d'accord avec la réponse acceptée que nous ne devrions pas
map
etcompact
, mais pas pour les mêmes raisons.Je me sens à l' intérieur profond qui
map
alorscompact
est équivalent àselect
alorsmap
. Considérez:map
est une fonction un-à-un. Si vous mappez à partir d'un ensemble de valeurs, et vousmap
, alors vous voulez une valeur dans le jeu de sortie pour chaque valeur dans le jeu d'entrée. Si vous devez auselect
préalable, vous ne voulez probablement pas unmap
sur le plateau. Si vous devez le faire par laselect
suite (oucompact
), vous ne voudrez probablement pas en avoirmap
sur le plateau. Dans les deux cas, vous répétez deux fois l'ensemble du jeu, alors qu'unreduce
ne doit y aller qu'une seule fois.De plus, en anglais, vous essayez de "réduire un ensemble d'entiers en un ensemble d'entiers pairs".
la source
Dans votre exemple:
il ne semble pas que les valeurs aient changé autre que d'être remplacées par
nil
. Si tel est le cas, alors:suffira.
la source
Si vous vouliez un critère de rejet plus lâche, par exemple, pour rejeter les chaînes vides ainsi que zéro, vous pouvez utiliser:
Si vous vouliez aller plus loin et rejeter des valeurs nulles (ou appliquer une logique plus complexe au processus), vous pourriez passer un bloc à rejeter:
la source
blank?
n'est disponible que dans les rails, nous pourrions utiliseritems.reject!(&:nil?) # [1, nil, 3, nil, nil] => [1, 3]
ce qui n'est pas couplé aux rails. (n'exclurait pas les chaînes vides ou les 0 cependant)C'est certainement
compact
la meilleure approche pour résoudre cette tâche. Cependant, nous pouvons obtenir le même résultat avec une simple soustraction:la source
each_with_object
est probablement le moyen le plus propre d'aller ici:À mon avis,
each_with_object
c'est mieux queinject
/reduce
dans des cas conditionnels car vous n'avez pas à vous soucier de la valeur de retour du bloc.la source
Une autre façon de le faire sera comme indiqué ci-dessous. Ici, nous utilisons
Enumerable#each_with_object
pour collecter des valeurs, et utilisonsObject#tap
pour se débarrasser de la variable temporaire qui est autrement nécessaire pournil
vérifier le résultat de laprocess_x
méthode.Exemple complet pour illustration:
Approche alternative:
En regardant la méthode que vous appelez
process_x url
, il n'est pas clair quel est le but de l'entréex
dans cette méthode. Si je suppose que vous allez traiter la valeur de enx
en passant un peuurl
et déterminer lequel desx
s est vraiment traité en résultats non nuls valides - alors, peut-êtreEnumerabble.group_by
est une meilleure option queEnumerable#map
.la source