2011-11-21 8 views
5

od projektowania kolekcji Scala Rozumiem, że coś takiego:wylesianie w kolekcji Scala

scala> BitSet(1,2,3) map (_ + "a") 
res7: scala.collection.immutable.Set[String] = Set(1a, 2a, 3a) 

nie buduje pośrednią datastructure: Nowy zestaw jest zbudowany jak BitSet się powtórzyć nad zastosowaniem Builder. W rzeczywistości w tym przypadku jest to oczywiste, ponieważ bitset ciągów znaków nie ma sensu.

Co z mapami z list? Jestem pewien, że dodaje się buduje listę pośredni:

scala> List(1,2,3) map (_ -> "foo") toMap 
res8: scala.collection.immutable.Map[Int,java.lang.String] = 
    Map(1 -> foo, 2 -> foo, 3 -> foo) 

mianowicie lista List((1,foo), (2,foo), (3,foo)). Jeśli nie, to w jaki sposób? A co z następującymi?

scala> Map.empty ++ (List(1,2,3) map (_ -> "foo")) 
res10: scala.collection.immutable.Map[Int,java.lang.String] = 
    Map(1 -> foo, 2 -> foo, 3 -> foo) 

Tym razem, z tego, co wydaje mi się zrozumieć od rodzaju ++:

def ++ [B >: (A, B), That] 
     (that: TraversableOnce[B]) 
     (implicit bf: CanBuildFrom[Map[A, B], B, That]): That 

myślę to może być przypadek, że mapa jest zbudowany na bieżąco, a które nie tworzona jest lista pośrednia.

Czy tak jest? Jeśli tak, czy jest to kanoniczny sposób zapewnienia wylesiania, czy też istnieje bardziej prosta składnia?

Odpowiedz

14

Można użyć breakOut, aby upewnić się, że nie utworzono kolekcji pośredniej. Na przykład:

// creates intermediate list. 
scala> List((3, 4), (9, 11)).map(_.swap).toMap 
res542: scala.collection.immutable.Map[Int,Int] = Map(4 -> 3, 11 -> 9) 

scala> import collection.breakOut 
import collection.breakOut 

// doesn't create an intermediate list. 
scala> List((3, 4), (9, 11)).map(_.swap)(breakOut) : Map[Int, Int] 
res543: Map[Int,Int] = Map(4 -> 3, 11 -> 9) 

Możesz przeczytać więcej na ten temat here.

UPDATE:

Jeśli czytasz definicji breakOut, można zauważyć, że jest to w zasadzie sposobem tworzenia CanBuildFrom przedmiot oczekiwanego typu i przepuszczenie go wyraźnie do tej metody. breakOut ogranicza się jedynie do wpisania poniższej tabeli.

// Observe the error message. This will tell you the type of argument expected. 
scala> List((3, 4), (9, 11)).map(_.swap)('dummy) 
<console>:16: error: type mismatch; 
found : Symbol 
required: scala.collection.generic.CanBuildFrom[List[(Int, Int)],(Int, Int),?] 
       List((3, 4), (9, 11)).map(_.swap)('dummy) 
               ^

// Let's try passing the implicit with required type. 
// (implicitly[T] simply picks up an implicit object of type T from scope.) 
scala> List((3, 4), (9, 11)).map(_.swap)(implicitly[CanBuildFrom[List[(Int, Int)], (Int, Int), Map[Int, Int]]]) 
// Oops! It seems the implicit with required type doesn't exist. 
<console>:16: error: Cannot construct a collection of type Map[Int,Int] with elements of type (Int, Int) based on a coll 
ection of type List[(Int, Int)]. 
       List((3, 4), (9, 11)).map(_.swap)(implicitly[CanBuildFrom[List[(Int, Int)], (Int, Int), Map[Int, Int]]]) 

// Let's create an object of the required type ... 
scala> object Bob extends CanBuildFrom[List[(Int, Int)], (Int, Int), Map[Int, Int]] { 
    | def apply(from: List[(Int, Int)]) = foo.apply 
    | def apply() = foo.apply 
    | private def foo = implicitly[CanBuildFrom[Nothing, (Int, Int), Map[Int, Int]]] 
    | } 
defined module Bob 

// ... and pass it explicitly. 
scala> List((3, 4), (9, 11)).map(_.swap)(Bob) 
res12: Map[Int,Int] = Map(4 -> 3, 11 -> 9) 

// Or let's just have breakOut do all the hard work for us. 
scala> List((3, 4), (9, 11)).map(_.swap)(breakOut) : Map[Int, Int] 
res13: Map[Int,Int] = Map(4 -> 3, 11 -> 9) 
+3

Wow, naprawdę potrzebowaliśmy 542 prób, aby to naprawić ;-) –

+0

Dzięki, właśnie tego szukałem. Nie jestem pewien, dlaczego scala narzeka na 'List ((3, 4), (9, 11)) map (_. Swap): Map [Int, Int]' zamiast używać ograniczenia typu Mam jawnie umieścić, aby wybrać prawo niejawne (dokładnie tak, jak czyta wybiera właściwą instancję typu w haśle w zależności od kontekstu). Czy to jest tak, że implicit nie działa w ten sposób (całkowicie to rozumiem, wiem, że subtyping czyni wszystko trudnym), czy też przeoczyłem coś w tym szczególnym przypadku? –

+3

@DuncanMcGregor: Nie zamknąłem REPL od ostatnich 5 dni. Używam go intensywnie w moim codziennym rozwoju. :-) – missingfaktor

3

Przykład 1) Prawidłowe, nie ma pośredni z wykazu

2) Tak, masz itermediate Lista.

3) Znowu tak, otrzymujesz listę Intermedytów z tego, co masz w nawiasach. Nie ma "magii". Jeśli masz coś w nawiasach, zostanie on najpierw oceniony.

Nie jestem pewien, co rozumiesz przez "wylesianie" tutaj: według Wikipedii oznacza to eliminację struktur drzewiastych. Jeśli chcesz wyeliminować listy pośrednie, powinieneś użyć widoku view. Zobacz na przykład tutaj: summing a transformation of a list of numbers in scala

Więc bez wyników pośrednich, twoje przykłady byłoby

BitSet(1,2,3).view.map(_ + "a").toSet 

(toSet jest wymagane, ponieważ w przeciwnym razie masz IterableView[String,Iterable[_]])

List(1,2,3).view.map(_ -> "foo").toMap 

Map.empty ++ (List(1,2,3).view.map(_ -> "foo")) 

Istnieje również metoda force do wykonywania operacji transformacji, ale wydaje się, że ma on nieprzyjemny zwyczaj dawania ci bardziej ogólnego typu (być może ktoś może komentować z jakiegoś powodu):

scala> Set(1,2,3).view.map(_ + 1).force 
res23: Iterable[Int] = Set(2, 3, 4) 
+0

Deforestacja oznacza eliminację pośrednich struktur drzewiastych. Lista jest po prostu zdegenerowanym drzewem, gdzie każdy węzeł '::' ma liść elementu jako lewe dziecko i albo inny węzeł '::' lub 'Nil' jako prawe dziecko, więc termin może być stosowany do list także. – hammar

+0

Dzięki. Około 3) Nie spodziewałem się, że magia się wydarzy, ale miałem nadzieję, że kontekst pisania doprowadzi ++ do wybrania prawa ukrytego dla danej pracy (dokładnie tak, jak czyta wybiera odpowiednią instancję klasy w haśle). –