2015-06-15 9 views
7

Jestem nowy w Scali i próbuję wymyślić najlepszy sposób filtrowania & mapować kolekcję. Oto zabawny przykład wyjaśniający mój problem.Scala: Najlepszy sposób filtrowania i mapowania w jednej iteracji

Podejście 1: Jest to bardzo źle, ponieważ I iteracji poprzez listę dwa razy i obliczania tej samej wartości w każdej iteracji.

val N = 5 
val nums = 0 until 10 
val sqNumsLargerThanN = nums filter { x: Int => (x * x) > N } map { x: Int => (x * x).toString } 

Podejście 2: Jest to nieco lepiej, ale nadal trzeba obliczyć (x * x) dwukrotnie.

val N = 5 
val nums = 0 until 10 
val sqNumsLargerThanN = nums collect { case x: Int if (x * x) > N => (x * x).toString } 

Tak, jest to możliwe do obliczenia tego bez iteracja kolekcji dwa razy i uniknąć powtarzania tych samych obliczeń?

Odpowiedz

2

Można użyć collect który dotyczy częściowego funkcję do każdej wartości kolekcji, która jest zdefiniowana w. Twój przykład może być przepisana następująco:

val sqNumsLargerThanN = nums collect { 
    case (x: Int) if (x * x) > N => (x * x).toString 
} 
+0

Dlaczego ktoś dół głosowania tę odpowiedź? 'collect' to bardzo idiomatyczny sposób na zrobienie tego. –

+0

Czy to nie jest dokładnie to samo co moje "podejście 2"? –

+0

Tak, to jest to samo, co podejście nr 2 powyżej i idąc przez definicję _collect_, ten wydaje mi się całkowicie uzasadniony; dokładnie mówi, co robi. Nie znaczy to, że inne podejścia wyjaśnione powyżej są lepsze lub gorsze. – Nirmalya

4

Typowym rozwiązaniem jest użyć iterator (jeśli to możliwe) lub view (jeśli iterator nie będzie działać). To nie wymaga dwóch przechyleń, ale pozwala uniknąć utworzenia pełnowymiarowego zbioru pośredniego. Następnie map pierwszy i filter potem a następnie map ponownie w razie potrzeby:

xs.iterator.map(x => x*x).filter(_ > N).map(_.toString) 

Zaletą tego podejścia jest to, że jest to naprawdę łatwe do odczytania, a ponieważ nie istnieją zbiory pośrednie, to dość skuteczny.

Jeśli proszą, bo to jest wąskim gardłem wydajności, to odpowiedź jest zazwyczaj napisać funkcję ogon rekurencyjnej lub używać starego stylu while metodą pętli. Na przykład, w przypadku

def sumSqBigN(xs: Array[Int], N: Int): Array[String] = { 
    val ysb = Array.newBuilder[String] 
    def inner(start: Int): Array[String] = { 
    if (start >= xs.length) ysb.result 
    else { 
     val sq = xs(start) * xs(start) 
     if (sq > N) ysb += sq.toString 
     inner(start + 1) 
    } 
    } 
    inner(0) 
} 

Można również przekazać parametr do przodu w inner zamiast przy użyciu konstruktora zewnętrznego (szczególnie przydatnych dla sum).

+0

Cześć Rex - co masz na myśli mówiąc, że nie unika dokładnie dwóch przejazdów? – sourcedelica

+0

@sourcedelica - każdy iterator, przechodząc przez listę, również (koniecznie) przechodzi przez poprzednie iteratory. Tak więc wszyscy przechodzą przez blokadę, ale jeśli mapujesz, a następnie filtrujesz, a następnie mapujesz, faktycznie masz następne/hasNext wywołania zagnieżdżone trzy głębokie. –

7

przydałby się foldRight

nums.foldRight(List.empty[Int]) { 
    case (i, is) => 
    val s = i * i 
    if (s > N) s :: is else is 
    } 

foldLeft również osiągnąć podobny cel, ale uzyskany lista byłaby w odwrotnej kolejności (z powodu skojarzeń z foldLeft.

Ewentualnie gdybyś Lubi grać ze Scalazem

import scalaz.std.list._ 
import scalaz.syntax.foldable._ 

nums.foldMap { i => 
    val s = i * i 
    if (s > N) List(s) else List() 
} 
+0

Należy zauważyć, że przy domyślnym 'foldRight' przepełnisz swój stos, jeśli twoja lista zawiera więcej niż tysiąc elementów. Ponadto wersja Scalaz nie ma żadnej przewagi nad 'flatMap'. –

3

Bardzo proste podejście, które wykonuje tylko operację multiplikacji o nce. Jest również leniwy, więc będzie wykonywać kod tylko wtedy, gdy będzie potrzebny.

nums.view.map(x=>x*x).withFilter(x => x> N).map(_.toString) 

Spójrz here różnice między filter i withFilter.

+0

To jest bardzo interesujące. W wątku, do którego linkowałeś, jest komentarz "Nie sądzę, że powinieneś używaćFilter siebie (poza niejawnie w wyrażeniach wyrażeń)". Czy istnieje powód, aby nie używać 'withFilter' –

+0

Używam' filter' tylko wtedy, gdy chcę utworzyć nową kolekcję do wykorzystania w dalszej części drogi. Jeśli chcę tylko filtr jako pośredni etap potoku operacji, zawsze używam 'withFilter'. – marios

2

mam jeszcze potwierdzić, że jest to naprawdę jeden karnet, ale:

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    if (square > N) Some(x) else None 
    } 
+0

Chcę zapytać czy ładowanie owijania każdego elementu warstwy opcji będzie mniejsze niż dwukrotne obliczanie x * x? Koszt tworzenia obiektu opcji można zignorować? (Jestem nowy w Scali z C++.) –

+1

Aby bezpośrednio odpowiedzieć na twoje pytanie, nie, przydział opcji nie jest bezpłatny. Jest to jednak tanie.JVM GC zyskało bardzo dobre wyniki w zakresie przydzielania i zbierania małych obiektów w pętlach. Więc nie będąc wolnym, prawie nigdy nie jest to miejsce, w którym zacznę optymalizować. – triggerNZ

+2

Co więcej, powinienem wspomnieć, że chociaż jest to zabawna zagadka do rozwiązania, próba zminimalizowania liczby przebiegów nad kolekcją w świecie programowania funkcjonalnego zwykle nie jest najlepszym sposobem uzyskania wydajności. Te rzeczy są powszechne w świecie C/C++ i są dużo mniej powszechne w JVM. Powiedziawszy to, załóżmy, że twoja kolekcja jest ogromna, powiedzmy, 8 GB. Wtedy naprawdę chcesz przejść tylko raz, a ja trzymałbym się zbierania lub używania leniwych kolekcji. Podwójne mnożenie zostanie zoptymalizowane przez JIT – triggerNZ

2

Rozważ to do zrozumienia,

for (x <- 0 until 10; v = x*x if v > N) yield v.toString 

który rozwija do flatMap całym zakresie oraz (leniwy) withFilter na jedyny raz obliczony kwadrat i daje kolekcję z filtrowanymi wynikami. Uwaga: wymagana jest jedna iteracja i jedno obliczenie kwadratu (oprócz tworzenia zakresu).

+0

@ErikMadsen naprawdę, dzięki kupie, naprawione :) – elm

0

Można użyć flatMap.

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    if (square > N) Some(square.toString) else None 
} 

Albo z Scalaz,

import scalaz.Scalaz._ 

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    (square > N).option(square.toString) 
} 

rozwiązuje zadawane pytanie, jak to zrobić z jednej iteracji. Może to być przydatne podczas przesyłania strumieniowego danych, jak w przypadku Iteratora.

Jednak ... jeśli chcesz zamiast tego absolutnej implementacji , to nie jest to. W rzeczywistości podejrzewam, że używałbyś zmiennego ArrayList i pętli while. Ale czy po profilowaniu wiesz na pewno. W każdym razie to kolejne pytanie.

0

Używanie do zrozumienia będzie działać:

val sqNumsLargerThanN = for {x <- nums if x*x > N } yield (x*x).toString 

Ponadto, nie jestem pewien, ale myślę, że kompilator Scala jest mądry o filtrze przed mapą i zrobi 1 podaje tylko, jeśli to możliwe.

-2

Jestem również początkujący zrobił to następująco

for(y<-(num.map(x=>x*x)) if y>5) { println(y)} 
Powiązane problemy