Scala: карта с двумя или более опциями
По сути, я ищу самый похожий на скалы способ сделать следующее:
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
Это дает правильный вывод:
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
Но для суммирования более двух вариантов мне пришлось бы использовать слишком много if
или использовать какой-то цикл.
EDIT-1:
При написании вопроса я придумал что-то вроде ответа:
def sum2(value1: Option[Int], value2: Option[Int]): Option[Int] =
value1.toList ::: value2.toList reduceLeftOption { _ + _ }
Это выглядит очень идиоматично для моего неопытного глаза. Это будет работать даже с более чем двумя значениями. Но можно ли сделать то же самое без конвертации в списки?
EDIT-2:
Я закончил с этим решением (спасибо ziggystar):
def sum(values: Option[Int]*): Option[Int] =
values.flatten reduceLeftOption { _ + _ }
EDIT-3:
Еще одна альтернатива благодаря Landei:
def sum(values: Option[Int]*): Option[Int] =
values collect { case Some(n) => n } reduceLeftOption { _ + _ }
5 ответов
Как насчет:
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
редактировать
Может быть, было бы разумно вернуть 0, если бы все аргументы были None. В этом случае функция будет уменьшена до values.flatten.sum
,
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
Другое решение:
def sum(values: Option[Int]*): Int = values.collect{case Some(n) => n}.sum
Пока в текущем случае flatten
явно удобнее, тем collect
Версия более гибкая, так как позволяет выполнять сопоставления и иметь дополнительные условия фильтрации или сложные шаблоны. Например, представьте, что вы хотите получить сумму квадратов всех четных чисел в значениях:
values.collect{case Some(n) if n mod 2 == 0 => n*n}.sum
Вы можете сделать это очень кратким, используя тот факт, что есть Semigroup
экземпляр для Option
это именно то, что вы хотите. Вы можете использовать скалаз или кошек. Вот пример использования 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)
Так что ваши sum
будет выглядеть так:
def sum(v1: Option[Int], v2: Option[Int]): Option[Int] = v1 |+| v2
Сокращенное решение michael.kebe с небольшим обзором некоторых основных математических правил:
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