Как я могу преобразовать это выражение foldLeft: Double, чтобы использовать вместо него Option [Double]? - PullRequest
9 голосов
/ 01 сентября 2010

Кто-нибудь может помочь этому новичку в Scala?Ранее мы суммировали количество величин в списке объектов с этими величинами следующим образом:

sum = entities.foldLeft(0.0)(_ + _.quantity)

Теперь количество равно Option[Double], и поэтому сумма равна.Как я могу конвертировать это с помощью идиоматического Scala?

Если количество какой-либо сущности равно None, то сумма также должна быть None.В противном случае сумма должна быть Some(total).

Редактировать: положить эту вещь в модульный тест, чтобы я мог проверить все ваши ответы.Обратите внимание, что мне нужно, чтобы результат был None, если любое количество равно None, потому что отсутствующие количества означают, что мы еще не закончили, поэтому итоговое значение должно отражать это.Даже если вы не получите правильный ответ, если вы поможете мне или другим понять его или узнаете что-то новое, я отзовусь.

Редактировать: @ sepp2k выигрывает за рабочее решение и объяснение,Спасибо всем за обучение!

Ответы [ 10 ]

9 голосов
/ 01 сентября 2010

Вы можете использовать Option flatMap и map методы для комбинирования двух Option с, так что результат будет Some(f(x,y)), если два Option с Some(x) и Some(y) или None в противном случае.

entities.foldLeft(Some(0.0):Option[Double]) {
    (acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}

Изменить в ответ на ваши комментарии:

Вот пример использования:

scala> case class Foo(quantity:Option[Double]) {}
defined class Foo
scala> val entities: List[Foo] = List(Foo(Some(2.0)), Foo(Some(1.0)), Foo(None))
scala> entities.foldLeft(Some(0.0):Option[Double]) {
    (acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
res0: Option[Double] = None

scala> val entities: List[Foo] = List(Foo(Some(2.0)), Foo(Some(1.0)))                                  
scala> entities.foldLeft(Some(0.0):Option[Double]) {
    (acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
res1: Option[Double] = Some(3.0)

Так что да, он вернет None, если какая-либо из сущностей будет None.

Относительно map и flatMap:

map принимает функцию f типа A => B и возвращает Some(f(x)) для Some(x) и None для None.

xo.flatMap(f), где f - функция типа A => Option[B], а xo - Option[A], возвращает Some(y), если xo - Some(x) и f(x) - Some(y). Во всех других случаях (то есть, если xo равно None или f(x) равно None), возвращается None.

Таким образом, выражение acco.flatMap(acc => x.quantity.map(_ + acc)) возвращает y + acc, если x.quantity равно Some(y) и acco равно Some(acc). Если один или оба из x.quantity и acco равны None, результат будет нулевым. Поскольку это внутри сгиба, это означает, что для следующей итерации значение acco также будет None, и, следовательно, конечный результат будет None.

4 голосов
/ 01 сентября 2010

Мне нравится использовать for при работе с Option:

// ========= Setup ===============
case class Entity(x: Double){
  // Dummy
  def quantity = if (x < 2) None
    else Some(x)
}

val entities = List(Entity(1), Entity(5), Entity(7))

// ========= Calculate ===============
val quantities = for{
   entity <- entities
   q <- entity.quantity
} yield q

val qSum = quantities.sum

Это должно быть просто для Java-людей, которым нужно следовать ..

(Извините за реализацию Entity, мне было трудно придумать реализацию quantity(), которая фактически возвращала None в некоторых моментах.)

РЕДАКТИРОВАТЬ: Добавлено объяснение

Что выхотел было посчитать сумму, верно?При таком решении, если quantity() возвращает None для всех объектов в списке, тогда сумма будет равна 0. Почему?Поскольку коллекция quantities не содержит элементов.

При использовании Option с for вы можете удалить все None элементы из полученного списка очень хорошим способом.Это строка:

 q <- entity.quantity 

.., которая фактически удаляет все None результаты из результирующего списка и извлекает Double из Some(x).Итак:

yield q

.. вернет только Double типов.Это дает вам возможность использовать функцию sum () в результирующей коллекции, поскольку коллекция содержит Double вместо Option[Double].Операция sum очень удобочитаема!

3 голосов
/ 01 сентября 2010

«Идиоматика» удивительно подходит, потому что это называется « приложение идиоматической функции », то есть «поднятие» функции в «идиому» (более современно: «аппликативный функтор»).

В Scalaz это можно сделать следующим образом:

import scalaz._
import Scalaz._

val add: (Int, Int) => Int = (x, y) => x + y
val addOptions: (Option[Int], Option[Int]) => Option[Int] = add.lift

Или вот так:

List(1,2,3).map(some(_)).foldLeft(some(0))(add.lift)
2 голосов
/ 28 февраля 2014

Давайте не будем забывать, что старший брат сгиб вправо может выйти из рекурсии, как только она встретит None. Все очень элегантно:

def sumOptsFoldRight = (entities:List[Option[Double]]) =>    
    entities.foldRight(Some(0.0):Option[Double])((accOpt,xOpt) => xOpt match {
        case None => None
        case Some(xVal) => accOpt.map(xVal + _)
    })
2 голосов
/ 01 сентября 2010

Многие существующие решения работают (и принятое является каноническим, и это то, что я обычно использую), но вот то, которое является более эффективным, если нажатие на None является распространенным; оно замыкает оценку, когда достигает первого значения None. Обратите внимание, что это хвостовая рекурсия.

// Replace Option[Double] by your entity type, and it.next with it.next.quantity
def total(it: Iterator[Option[Double]], zero: Double = 0.0): Option[Double] = {
  if (it.hasNext) {
    it.next match {
      case Some(x) => total(it,zero+x)
      case None => None
    }
  }
  else Some(zero)
}
// To use: total(entities.iterator)
2 голосов
/ 01 сентября 2010

РЕДАКТИРОВАТЬ: , поскольку объект также является необязательным, код значения адаптируется к этому

Хотя ответ @ sepp2k верен, если у вас есть Option[Entity] с полем Double quantity, вам нужно следующее:

entities.foldLeft(Option(0d)) {
  (sum, oe) => for {
    s <- sum
    e <- oe
    q <- e.quantity
  } yield s + q
}

Понятие for внутри замыкания эквивалентно flatMap / map, как в ответе @ sepp2k, но в моем опыте его легче читать новичкам.

2 голосов
/ 01 сентября 2010

Хорошо, это то, что вы хотите, я думаю.(Предыдущий ответ неверно истолковывает ваши требования.)

entities.find(_.quantity == None) match {
  case Some(_) => None
  case None => Some(entities.map(_.quantity).flatten.reduceLeft(_ + _))
}

Я думаю, что другой ответ более "идиоматичен", но, на мой взгляд, это намного легче понять.

1 голос
/ 01 сентября 2010

Это тот же ответ, что и sepp2k / Мориц , но разделенный на две функции, чтобы прояснить ситуацию.

def addOptionDouble(optionalA: Option[Double], optionalB: Option[Double]): Option[Double] =
  for {
    a <- optionalA
    b <- optionalB
  } yield a + b

def sumQuantitiesOfEntities(entities: Traversable[Entity]): Option[Double] = 
  entities.foldLeft(Option(0.0)) {
    (acc, entity) => addOptionDouble(acc, entity.quantity)
  }
1 голос
/ 01 сентября 2010
val sum = entities.foldLeft(Some(0.0):Option[Double]){
  (s,e) => if (s.isEmpty || e.quantity.isEmpty) None else Some(s.sum + e.quantity.sum)}

или

val sum = if(entities.exists(_.quantity.isEmpty)) None
          else Some(entities.flatMap(_.quantity).sum)
1 голос
/ 01 сентября 2010

Я бы явно проверил на отсутствие использования None

entities.forall(_.quantity.isDefined)

Например:

scala> case class Entity(quantity: Option[Double])
defined class Entity

scala> val entities = List(Entity(Some(10.0)), Entity(None), Entity(Some(15.0)))
entities: List[Entity] = List(Entity(Some(10.0)), Entity(None), Entity(Some(15.0)))

scala> if (entities.forall(_.quantity.isDefined)) {
     |   Some(entities.flatMap(_.quantity).reduceLeft(_+_))
     | } else None
res6: Option[Double] = None
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...