Scala: carte avec deux options ou plus
-
27-10-2019 - |
Question
En gros je cherche le plus scala comme façon de faire ce qui suit:
def sum(value1: Option[Int], value2: Option[Int]): Option[Int] =
if(value1.isDefined && value2.isDefined) Some(value1.get + value2.get)
else if(value1.isDefined && value2.isEmpty) value1
else if(value1.isEmpty && value2.isDefined) value2
else None
Cela donne une sortie correcte:
sum(Some(5), Some(3)) // result = Some(8)
sum(Some(5), None) // result = Some(5)
sum(None, Some(3)) // result = Some(3)
sum(None, None) // result = None
Cependant, pour résumer plus de deux options faudrait que je me sens d'utiliser trop de if
s ou utiliser une sorte de boucle.
EDIT-1:
Tout en écrivant la question que je suis venu avec une sorte de réponse:
def sum2(value1: Option[Int], value2: Option[Int]): Option[Int] =
value1.toList ::: value2.toList reduceLeftOption { _ + _ }
Celui-ci semble très idiomatiques à mes yeux inexpérimentés. Ce serait même travailler avec plus de deux valeurs. Cependant, il est possible de faire la même chose sans la conversion des listes?
EDIT-2:
J'ai fini avec cette solution (grâce à ziggystar ):
def sum(values: Option[Int]*): Option[Int] =
values.flatten reduceLeftOption { _ + _ }
EDIT-3:
Une autre alternative grâce à Landei :
def sum(values: Option[Int]*): Option[Int] =
values collect { case Some(n) => n } reduceLeftOption { _ + _ }
La solution
Que diriez-vous:
scala> def sum(values: Option[Int]*): Option[Int] = values.flatten match {
| case Nil => None
| case l => Some(l.sum)
| }
sum: (values: Option[Int]*)Option[Int]
scala> sum(Some(1), None)
res0: Option[Int] = Some(1)
scala> sum(Some(1), Some(4))
res1: Option[Int] = Some(5)
scala> sum(Some(1), Some(4), Some(-5))
res3: Option[Int] = Some(0)
scala> sum(None, None)
res4: Option[Int] = None
Modifier
Peut-être qu'il serait sain d'esprit de revenir 0 si tous les arguments étaient Aucun. Dans ce cas, la fonction réduirait à values.flatten.sum
.
Autres conseils
scala> def sum(a: Option[Int], b: Option[Int]) = (a,b) match {
| case (Some(x), Some(y)) => Some(x + y)
| case (Some(x), None) => Some(x)
| case (None, Some(y)) => Some(y)
| case _ => None
| }
sum: (a: Option[Int],b: Option[Int])Option[Int]
scala> sum(Some(5), Some(3))
res0: Option[Int] = Some(8)
scala> sum(Some(5), None)
res1: Option[Int] = Some(5)
scala> sum(None, Some(3))
res2: Option[Int] = Some(3)
scala> sum(None, None)
res3: Option[Int] = None
Une autre solution est:
def sum(values: Option[Int]*): Int = values.collect{case Some(n) => n}.sum
Alors que dans le flatten
en cours est nettement plus pratique, la version collect
est plus souple, car elle permet d'effectuer les correspondances et d'avoir des conditions de filtrage supplémentaires ou des motifs complexes. Par exemple. imaginez que vous voulez avoir la somme des carrés de tous les nombres pairs en valeurs:
values.collect{case Some(n) if n mod 2 == 0 => n*n}.sum
Vous pouvez le faire très concis en utilisant le fait qu'il existe une instance de Semigroup
pour Option
qui fait exactement ce que vous voulez. Vous pouvez utiliser scalaz ou chats . Voici un exemple en utilisant cats
:
import cats.std.option._
import cats.syntax.semigroup._
import cats.std.int._
Option(1) |+| Option(2) // Some(3)
Option(1) |+| None // Some(1)
None |+| Option(2) // Some(2)
Ainsi, votre sum
devient:
def sum(v1: Option[Int], v2: Option[Int]): Option[Int] = v1 |+| v2
solution réduite de michael.kebe avec un petit regard à certaines règles mathématiques de base:
def sum(a: Option[Int], b: Option[Int]) = (a,b) match {
case (None,None) => None
case _ => Some(a.getOrElse(0)+b.getOrElse(0))
}
scala> sum(Some(5), Some(3)) // result = Some(8)
res6: Option[Int] = Some(8)
scala> sum(Some(5), None) // result = Some(5)
res7: Option[Int] = Some(5)
scala> sum(None, Some(3)) // result = Some(3)
res8: Option[Int] = Some(3)
scala> sum(None, None) // result = None
res9: Option[Int] = None