J'ai un tableau Ruby contenant des valeurs de chaîne. J'ai besoin de:
- Trouvez tous les éléments qui correspondent à un prédicat
- Exécutez les éléments correspondants via une transformation
- Renvoie les résultats sous forme de tableau
En ce moment, ma solution ressemble à ceci:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Existe-t-il une méthode Array ou Enumerable qui combine select et map en une seule instruction logique?
Enumerable#grep
méthode fait exactement ce qui a été demandé et est en Ruby depuis plus de dix ans. Il prend un argument de prédicat et un bloc de transformation. @hirolau donne la seule réponse correcte à cette question.filter_map
précisément dans ce but. Plus d'infos ici .Réponses:
J'utilise généralement
map
etcompact
avec mes critères de sélection comme suffixeif
.compact
se débarrasse des nils.la source
map
+compact
serait vraiment plus performant queinject
et j'ai publié mes résultats de référence sur un fil de discussion connexe: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
etselect
c'est justecompact
un cas particulierreject
qui fonctionne sur nils et fonctionne un peu mieux car il a été implémenté directement dans C.Vous pouvez utiliser
reduce
pour cela, qui ne nécessite qu'un seul passage:En d'autres termes, initialisez l'état pour qu'il soit ce que vous voulez (dans notre cas, une liste vide à remplir:
[]
, puis assurez-vous toujours de renvoyer cette valeur avec des modifications pour chaque élément de la liste d'origine (dans notre cas, l'élément modifié poussé dans la liste).C'est le plus efficace car il ne parcourt la liste qu'en un seul passage (
map
+select
oucompact
nécessite deux passes).Dans ton cas:
la source
each_with_object
un peu plus de sens? Vous n'êtes pas obligé de renvoyer le tableau à la fin de chaque itération du bloc. Vous pouvez simplement le fairemy_array.each_with_object([]) { |i, a| a << i if i.condition }
.reduce
premier 😊Ruby 2.7+
Il y a maintenant!
Ruby 2.7 présente
filter_map
précisément dans ce but. C'est idiomatique et performant, et je m'attendrais à ce que cela devienne la norme très bientôt.Par exemple:
Voici une bonne lecture sur le sujet .
J'espère que c'est utile à quelqu'un!
la source
filter
,select
etfind_all
sont synonymes, tels quemap
etcollect
sont, il peut être difficile de se souvenir du nom de cette méthode. Est - cefilter_map
,select_collect
,find_all_map
oufilter_collect
?Une autre façon différente d'aborder cela consiste à utiliser le nouveau (par rapport à cette question)
Enumerator::Lazy
:La
.lazy
méthode renvoie un énumérateur paresseux. L'appel de.select
ou.map
sur un énumérateur paresseux renvoie un autre énumérateur paresseux. Ce n'est qu'une fois que vous appelez.uniq
qu'il force réellement l'énumérateur et renvoie un tableau. Donc, ce qui se passe effectivement, c'est que vos appels.select
et vos.map
appels sont combinés en un seul - vous ne répétez qu'une seule@lines
fois pour faire les deux.select
et.map
.Mon instinct est que la
reduce
méthode d'Adam sera un peu plus rapide, mais je pense que c'est beaucoup plus lisible.La principale conséquence de ceci est qu'aucun objet de tableau intermédiaire n'est créé pour chaque appel de méthode suivant. Dans une
@lines.select.map
situation normale ,select
renvoie un tableau qui est ensuite modifié parmap
, renvoyant à nouveau un tableau. Par comparaison, l'évaluation paresseuse ne crée un tableau qu'une seule fois. Ceci est utile lorsque votre objet de collection initial est volumineux. Il vous permet également de travailler avec des recenseurs infinis - par exemplerandom_number_generator.lazy.select(&:odd?).take(10)
.la source
reduce
comme une transformation «tout faire» me semble toujours assez désordonnée.@lines
fois pour faire les deux.select
et.map
". L'utilisation.lazy
ne signifie pas que les opérations chaînées sur un énumérateur paresseux seront «réduites» en une seule itération. Il s'agit d'un malentendu courant de l'évaluation paresseuse des opérations de chaînage sur une collection. (Vous pouvez tester cela en ajoutant uneputs
instruction au début des blocsselect
etmap
dans le premier exemple. Vous constaterez qu'ils impriment le même nombre de lignes).lazy
il imprime le même nombre de fois. C'est mon point - votremap
bloc et votreselect
bloc sont exécutés le même nombre de fois dans les versions paresseuses et avides. La version paresseuse ne "combine pas votre.select
et vos.map
appels"lazy
combine car un élément qui échoue à laselect
condition n'est pas passé aumap
. En d 'autres termes: le préfixelazy
équivaut à peu près à remplacerselect
etmap
par un seulreduce([])
, et "intelligemment", faireselect
du bloc de une condition préalable à l' inclusion dansreduce
le résultat de.Si vous avez un
select
qui peut utiliser l'case
opérateur (===
),grep
est une bonne alternative:Si nous avons besoin d'une logique plus complexe, nous pouvons créer des lambdas:
la source
Je ne suis pas sûr qu'il y en ait un. Le module Enumerable , qui ajoute
select
etmap
, n'en affiche pas.Vous seriez obligé de passer en deux blocs à la
select_and_transform
méthode, ce qui serait un peu peu intuitif à mon humble avis.De toute évidence, vous pouvez simplement les enchaîner, ce qui est plus lisible:
la source
Réponse simple:
Si vous avez n enregistrements et que vous voulez
select
et enmap
fonction de la condition, alorsIci, l'attribut est ce que vous voulez de l'enregistrement et la condition que vous pouvez vérifier.
compact est de rincer les nil inutiles qui sont sortis de cette condition si
la source
Non, mais vous pouvez le faire comme ceci:
Ou encore mieux:
la source
reject(&:nil?)
est fondamentalement le même quecompact
.Je pense que cette façon est plus lisible, car divise les conditions de filtre et la valeur mappée tout en restant clair que les actions sont connectées:
Et, dans votre cas spécifique, éliminez la
result
variable tous ensemble:la source
Dans Ruby 1.9 et 1.8.7, vous pouvez également enchaîner et encapsuler les itérateurs en ne leur passant simplement pas de bloc:
Mais ce n'est pas vraiment possible dans ce cas, car les types du bloc renvoient des valeurs de
select
etmap
ne correspondent pas. Cela a plus de sens pour quelque chose comme ça:AFAICS, le mieux que vous puissiez faire est le premier exemple.
Voici un petit exemple:
Mais ce que vous voulez vraiment, c'est
["A", "B", "C", "D"]
.la source
select
renvoie une valeur booléenne qui décide de conserver l'élément ou non,map
retourne la valeur transformée. La valeur transformée elle-même va probablement être véridique, donc tous les éléments sont sélectionnés.Vous devriez essayer d'utiliser ma bibliothèque Rearmed Ruby dans laquelle j'ai ajouté la méthode
Enumerable#select_map
. Voici un exemple:la source
select_map
dans cette bibliothèque implémente simplement la mêmeselect { |i| ... }.map { |i| ... }
stratégie à partir de nombreuses réponses ci-dessus.Si vous ne souhaitez pas créer deux tableaux différents, vous pouvez utiliser
compact!
mais faites attention à ce sujet.Fait intéressant,
compact!
est-ce qu'une suppression en place de zéro. La valeur de retour decompact!
est le même tableau s'il y a eu des changements mais nil s'il n'y a pas eu de nils.Serait une ligne unique.
la source
Ta version:
Ma version:
Cela fera 1 itération (sauf le tri), et a l'avantage supplémentaire de garder l'unicité (si vous ne vous souciez pas d'uniq, alors faites simplement des résultats un tableau et
results.push(line) if ...
la source
Voici un exemple. Ce n'est pas le même que votre problème, mais peut être ce que vous voulez ou peut donner un indice sur votre solution:
la source