Scala: отображение с двумя или более параметрами

#scala #scala-option

#scala #scala-опция

Вопрос:

В принципе, я ищу наиболее похожий на scala способ сделать следующее:

 def sum(value1: Option[Int], value2: Option[Int]): Option[Int] = 
  if(value1.isDefined amp;amp; value2.isDefined) Some(value1.get   value2.get)
  else if(value1.isDefined amp;amp; value2.isEmpty) value1
  else if(value1.isEmpty amp;amp; 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 ов или использовать какой-то цикл.

РЕДАКТИРОВАТЬ-1:

При написании вопроса я придумал своего рода ответ:

 def sum2(value1: Option[Int], value2: Option[Int]): Option[Int] = 
  value1.toList ::: value2.toList reduceLeftOption { _   _ }
  

На мой неопытный взгляд, это выглядит очень идиоматично. Это будет работать даже с более чем двумя значениями. И все же возможно ли сделать то же самое без преобразования в списки?

РЕДАКТИРОВАТЬ-2:

В итоге я пришел к этому решению (благодаря ziggystar):

 def sum(values: Option[Int]*): Option[Int] = 
  values.flatten reduceLeftOption { _   _ }
  

РЕДАКТИРОВАТЬ-3:

Еще одна альтернатива благодаря Landei:

 def sum(values: Option[Int]*): Option[Int] = 
  values collect { case Some(n) => n } reduceLeftOption { _   _ }
  

Ответ №1:

Как насчет:

 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 .

Комментарии:

1. Спасибо за flatten Я думаю, это то, чего мне не хватало.

Ответ №2:

 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
  

Комментарии:

1. Спасибо. Это более идиоматично в том смысле, что использует match вместо if . В противном случае это тот же код, что и в моем первом примере, с теми же ограничениями — вам пришлось бы добавить гораздо больше case s, чтобы суммировать более 2 значений.

Ответ №3:

Другим решением является:

 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
  

Комментарии:

1. Компилятор жалуется на возвращаемый тип: найдено Int , требуется Option[Int] . Я понимаю вашу точку зрения о collect методе вместо flatten , но мне все еще нужно сделать reduceLeftOption , чтобы получить желаемый результат.

Ответ №4:

Вы можете сделать его очень кратким, используя тот факт, что существует Semigroup экземпляр для Option , который делает именно то, что вы хотите. Вы можете использовать scalaz или cats. Вот пример использования 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
  

Ответ №5:

Сокращенное решение Майкла.кеб с небольшим обзором некоторых базовых математических правил:

 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