Question Scala List.filter avec deux conditions, appliqué une seule fois


Je ne sais pas si c'est possible, mais j'ai un code comme celui-ci:

val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
val evens = list.filter { e => e % 2 == 0 }

if(someCondition) {
  val result = evens.filter { e => e % 3 == 0 }
} else {
  val result = evens.filter { e => e % 5 == 0 }
}

Mais je ne veux pas parcourir deux fois tous les éléments, alors est-il possible de créer un "nombre générique de pick-all-the-evens" sur cette collection et d'appliquer une autre fonction, de sorte qu'elle ne soit itérée qu'une seule fois ?


10
2017-08-29 17:48


origine


Réponses:


Si vous tournez list dans une collection paresseuse, comme un Iterator, alors vous pouvez appliquer toutes les opérations de filtrage (ou d'autres choses comme map etc) en un seul passage:

val list = (1 to 12).toList
val doubleFiltered: List[Int] =
  list.iterator
    .filter(_ % 2 == 0)
    .filter(_ % 3 == 0)
    .toList
println(doubleFiltered)

Lorsque vous convertissez la collection en un itérateur avec .iterator, Scala gardera une trace des opérations à effectuer (ici, deux filters), mais attendra de les exécuter jusqu’à ce que le résultat soit réellement accessible (ici, via l’appel à .toList).

Je pourrais donc réécrire votre code comme ceci:

val list = (1 to 12).toList
val evens = list.iterator.filter(_ % 2 == 0)

val result = 
  if(someCondition)
    evens.filter(_ % 3 == 0)
  else
    evens.filter(_ % 5 == 0)

result foreach println

Selon exactement ce que vous voulez faire, vous voudrez peut-être un Iterator, une Streamou un View. Ils sont tous calculés paresseusement (l’aspect «un passage» s’appliquera), mais ils diffèrent sur des points tels que la possibilité de les répéter plusieurs fois (Stream et View) ou s’ils conservent la valeur calculée pour un accès ultérieur (Stream).

Pour vraiment voir ces différents comportements paresseux, essayez d'exécuter ce bit de code et de définir <OPERATION> soit toList, iterator, view, ou toStream:

val result =
  (1 to 12).<OPERATION>
    .filter { e => println("filter 1: " + e); e % 2 == 0 }
    .filter { e => println("filter 2: " + e); e % 3 == 0 }
result foreach println
result foreach println

Voici le comportement que vous verrez:

  • List (ou toute autre collection non paresseuse): Each filter Cela nécessite une itération séparée dans la collection. La collection filtrée résultante est stockée en mémoire afin que chaque foreach peut juste l'afficher.
  • Iterator: Tous les deux filters et le premier foreach se font en une seule itération. La deuxième foreach ne fait rien depuis le Iterator a été consommé. Les résultats ne sont pas stockés en mémoire.
  • View: Tous les deux foreach les appels entraînent leur propre itération en un seul passage sur la collection pour effectuer la filters. Les résultats ne sont pas stockés en mémoire.
  • Stream: Tous les deux filters et le premier foreach se font en une seule itération. La collection filtrée résultante est stockée en mémoire afin que chaque foreach peut juste l'afficher.

26
2017-08-29 17:56



Vous pouvez utiliser la composition de fonctions. someCondition ici seulement appelé une fois, en décidant quelle fonction composer avec:

def modN(n: Int)(xs: List[Int]) = xs filter (_ % n == 0)

val f = modN(2) _ andThen (if (someCondition) modN(3) else modN(5))

val result = f(list)


8
2017-08-29 18:38



Est-ce que ça ne marcherait pas:

list.filter{e => e % 2 == 0 && (if (someCondition) e % 3 == 0 else e % 5 == 0)}

également FYI e % 2 == 0 va vous donner tous les nombres pairs, à moins que vous ne nommiez le val odds pour une autre raison.


2
2017-08-29 17:59



Vous écrivez simplement deux conditions dans le filtre:

val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

var result = List(0)
val someCondition = true

result = if (someCondition) list.filter { e => e % 2 == 0 && e % 3 == 0 }
         else               list.filter { e => e % 2 == 0 && e % 5 == 0 }

1
2017-08-29 18:00