Jak mogę podsumować listę opcji List[Option[Double]]
z następującymi regułami?Suma listy opcji w Scali
List(Some(1), ..., Some(n)) --> Some(1 + ... + n)
List(Some(1), ..., Some(n), None) --> None
List(None, ..., None) --> None
Jak mogę podsumować listę opcji List[Option[Double]]
z następującymi regułami?Suma listy opcji w Scali
List(Some(1), ..., Some(n)) --> Some(1 + ... + n)
List(Some(1), ..., Some(n), None) --> None
List(None, ..., None) --> None
Aby uniknąć przechodzenie całej listy z forall
aby sprawdzić, czy cały zestaw opcji jest zdefiniowana, można użyć foldLeft
i wykorzystaj fakt, że możesz uzyskać None
przy pierwszym znalezieniu pustego elementu w łańcuchu.
def sumList(list: List[Option[T]])(implicit ev: Numeric[T]): Option[T] = {
list.foldLeft(Option(ev.zero)) { case (acc, el) =>
el.flatMap(value => acc.map(ac => ev.plus(ac, value)))
}
}
sumList(List(None, None, Some(5)))
res10: Option[Int] = None
scala> sumList(List(None, None, Some(5F)))
res11: Option[Float] = None
scala> sumList[Double](List(None, None, None))
res13: Option[Double] = None
scala> sumList(List(Some(5), Some(15)))
res14: Option[Int] = Some(20)
i uniknąć return
można po prostu użyć rekurencji (aktualizacji, zwrot nie jest potrzebny powyżej, ale może to jest łatwiejsze do naśladowania):
@annotation.tailrec
def sumListRec[T](list: List[Option[T]], acc: T)(implicit ev: Numeric[T]): Option[T] = {
list match {
// if the list still has elements
case head :: tail => head match {
// add the value to the accumulator and keep going
case Some(value) => sumListRec(tail, ev.plus(acc, value))
// if you found a None, disregard whatever sum we built so far
// and just return None
case None => None
}
// If the list is empty, it means we've successfully reached
// the end of the list, so we just return the sum we built.
case Nil => Some(acc)
}
}
oglądać go w akcji:
scala> sumListRec(List(Some(5D), Some(5D)), 0D)
res5: Option[Double] = Some(10.0)
sumListRec(List(None, None, Some(5D)), 0D)
res2: Option[Double] = None
scala> sumListRec(List(None, None), 0D)
res6: Option[Double] = None
Podoba mi się rozwiązanie rekurencyjne. Po prostu użyłem tej samej funkcji bez "acc" i zainicjowałem ją z 'niejawnie [Numeric [T]]. Zero'. – DennisVDB
To rozwiązanie jest o wiele bardziej skomplikowane, niż powinno być. – michau
@michau To dlatego, że twój jest nieefektywny i będzie kontynuował podróż bez względu na to, czy napotkałeś 'Brak'. Diabeł tkwi w szczegółach. Jest to również brzydka Scala mądra, a ty obracasz się do bezpośredniego połączenia z '.get'. – flavian
Jeśli używasz Scalaz to jest bardzo proste:
targetList.sequence.map(_.suml)
Imponujące! Zgadzam się z przysłowiem Knuth, ale: jak skuteczne jest to? – sscarduzio
@sscarduzio Jeśli na liście jest pełno niektórych wystąpień, będzie ją dwukrotnie (efektywnie) przechodzić, aby uzyskać wynik. Za pierwszym razem wyświetli się lista wszystkich wartości Int, a następnie przejdzie tę listę, aby wytworzyć sumę. Ale jeśli to jest powolne, prawdopodobnie lepiej wyeliminować Listę opcji przed czymkolwiek innym. –
Możesz to zrobić w ten sposób. Edytuj: zmienił kod, aby powrócić natychmiast po zetknięciu się z None
. Przyznaję, że ta optymalizacja ani nie komplikuje kodu zbyt mocno, ani nie utrudnia czytelności (jak myślałem wcześniej).
def sumOpt(list: List[Option[Double]]): Option[Double] = list.reduce((x,y) => {
if (x.isEmpty || y.isEmpty) return None
else Some(x.get + y.get)
})
sumOpt(list)
Jest jeszcze prostszy sposób tylko przy użyciu krotnie:
val ls = List(Some(2.1d), Some(5.6d), Some(4.3d), Some(1.2))
ls.fold(Option(0d))((rs,x) => for(n <- x; m <- rs) yield {n+m})
=> Niektóre (13,2)
val ls = List(Some(2.1d),None, Some(5.6d), Some(4.3d), Some(1.2))
ls.fold(Option(0d))((rs,x) => for(n <- x; m <- rs) yield {n+m})
=> Brak
'val l = list.flatten; if (l.size == list.size) Some (l.sum) else Brak ' – Jus12
@ Jus12 Kolejne nieefektywne rozwiązanie, wymaga to 2 pełnych przejść na liście, w rezultacie nie jest to poprawna sugestia. – flavian