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
Другие вопросы по тегам