2016-09-05 55 views
6

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
+1

'val l = list.flatten; if (l.size == list.size) Some (l.sum) else Brak ' – Jus12

+0

@ Jus12 Kolejne nieefektywne rozwiązanie, wymaga to 2 pełnych przejść na liście, w rezultacie nie jest to poprawna sugestia. – flavian

Odpowiedz

3

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 
+0

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

+0

To rozwiązanie jest o wiele bardziej skomplikowane, niż powinno być. – michau

+0

@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

2

Jeśli używasz Scalaz to jest bardzo proste:

targetList.sequence.map(_.suml) 
+0

Imponujące! Zgadzam się z przysłowiem Knuth, ale: jak skuteczne jest to? – sscarduzio

+2

@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. –

0

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) 
+0

Czy naprawdę potrzebujesz słowa kluczowego "return"? – Jus12

+0

Tak, ponieważ chcę wrócić z 'sumOpt', a nie z funkcji anonimowej. To pozwala mi zatrzymać się natychmiast po napotkaniu 'Brak'. – michau

0

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