diff --git a/ko/overviews/collections/arrays.md b/ko/overviews/collections/arrays.md new file mode 100644 index 0000000000..17489da133 --- /dev/null +++ b/ko/overviews/collections/arrays.md @@ -0,0 +1,118 @@ +--- +layout: overview-large +title: Arrays + +disqus: true + +partof: collections +num: 10 +language: ko +--- + +[Array](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html) is a special kind of collection in Scala. On the one hand, Scala arrays correspond one-to-one to Java arrays. That is, a Scala array `Array[Int]` is represented as a Java `int[]`, an `Array[Double]` is represented as a Java `double[]` and a `Array[String]` is represented as a `Java String[]`. But at the same time, Scala arrays offer much more than their Java analogues. First, Scala arrays can be _generic_. That is, you can have an `Array[T]`, where `T` is a type parameter or abstract type. Second, Scala arrays are compatible with Scala sequences - you can pass an `Array[T]` where a `Seq[T]` is required. Finally, Scala arrays also support all sequence operations. Here's an example of this in action: + + scala> val a1 = Array(1, 2, 3) + a1: Array[Int] = Array(1, 2, 3) + scala> val a2 = a1 map (_ * 3) + a2: Array[Int] = Array(3, 6, 9) + scala> val a3 = a2 filter (_ % 2 != 0) + a3: Array[Int] = Array(3, 9) + scala> a3.reverse + res1: Array[Int] = Array(9, 3) + +Given that Scala arrays are represented just like Java arrays, how can these additional features be supported in Scala? In fact, the answer to this question differs between Scala 2.8 and earlier versions. Previously, the Scala compiler somewhat "magically" wrapped and unwrapped arrays to and from `Seq` objects when required in a process called boxing and unboxing. The details of this were quite complicated, in particular when one created a new array of generic type `Array[T]`. There were some puzzling corner cases and the performance of array operations was not all that predictable. + +The Scala 2.8 design is much simpler. Almost all compiler magic is gone. Instead the Scala 2.8 array implementation makes systematic use of implicit conversions. In Scala 2.8 an array does not pretend to _be_ a sequence. It can't really be that because the data type representation of a native array is not a subtype of `Seq`. Instead there is an implicit "wrapping" conversion between arrays and instances of class `scala.collection.mutable.WrappedArray`, which is a subclass of `Seq`. Here you see it in action: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> val a4: Array[Int] = s.toArray + a4: Array[Int] = Array(1, 2, 3) + scala> a1 eq a4 + res2: Boolean = true + +The interaction above demonstrates that arrays are compatible with sequences, because there's an implicit conversion from arrays to `WrappedArray`s. To go the other way, from a `WrappedArray` to an `Array`, you can use the `toArray` method defined in `Traversable`. The last REPL line above shows that wrapping and then unwrapping with `toArray` gives the same array you started with. + +There is yet another implicit conversion that gets applied to arrays. This conversion simply "adds" all sequence methods to arrays but does not turn the array itself into a sequence. "Adding" means that the array is wrapped in another object of type `ArrayOps` which supports all sequence methods. Typically, this `ArrayOps` object is short-lived; it will usually be inaccessible after the call to the sequence method and its storage can be recycled. Modern VMs often avoid creating this object entirely. + +The difference between the two implicit conversions on arrays is shown in the next REPL dialogue: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> seq.reverse + res2: Seq[Int] = WrappedArray(3, 2, 1) + scala> val ops: collection.mutable.ArrayOps[Int] = a1 + ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) + scala> ops.reverse + res3: Array[Int] = Array(3, 2, 1) + +You see that calling reverse on `seq`, which is a `WrappedArray`, will give again a `WrappedArray`. That's logical, because wrapped arrays are `Seqs`, and calling reverse on any `Seq` will give again a `Seq`. On the other hand, calling reverse on the ops value of class `ArrayOps` will give an `Array`, not a `Seq`. + +The `ArrayOps` example above was quite artificial, intended only to show the difference to `WrappedArray`. Normally, you'd never define a value of class `ArrayOps`. You'd just call a `Seq` method on an array: + + scala> a1.reverse + res4: Array[Int] = Array(3, 2, 1) + +The `ArrayOps` object gets inserted automatically by the implicit conversion. So the line above is equivalent to + + scala> intArrayOps(a1).reverse + res5: Array[Int] = Array(3, 2, 1) + +where `intArrayOps` is the implicit conversion that was inserted previously. This raises the question how the compiler picked `intArrayOps` over the other implicit conversion to `WrappedArray` in the line above. After all, both conversions map an array to a type that supports a reverse method, which is what the input specified. The answer to that question is that the two implicit conversions are prioritized. The `ArrayOps` conversion has a higher priority than the `WrappedArray` conversion. The first is defined in the `Predef` object whereas the second is defined in a class `scala.LowPritoryImplicits`, which is inherited from `Predef`. Implicits in subclasses and subobjects take precedence over implicits in base classes. So if both conversions are applicable, the one in `Predef` is chosen. A very similar scheme works for strings. + +So now you know how arrays can be compatible with sequences and how they can support all sequence operations. What about genericity? In Java you cannot write a `T[]` where `T` is a type parameter. How then is Scala's `Array[T]` represented? In fact a generic array like `Array[T]` could be at run-time any of Java's eight primitive array types `byte[]`, `short[]`, `char[]`, `int[]`, `long[]`, `float[]`, `double[]`, `boolean[]`, or it could be an array of objects. The only common run-time type encompassing all of these types is `AnyRef` (or, equivalently `java.lang.Object`), so that's the type to which the Scala compiler maps `Array[T]`. At run-time, when an element of an array of type `Array[T]` is accessed or updated there is a sequence of type tests that determine the actual array type, followed by the correct array operation on the Java array. These type tests slow down array operations somewhat. You can expect accesses to generic arrays to be three to four times slower than accesses to primitive or object arrays. This means that if you need maximal performance, you should prefer concrete over generic arrays. Representing the generic array type is not enough, however, There must also be a way to create generic arrays. This is an even harder problem, which requires a little bit of help from you. To illustrate the problem, consider the following attempt to write a generic method that creates an array. + + // this is wrong! + def evenElems[T](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +The `evenElems` method returns a new array that consist of all elements of the argument vector `xs` which are at even positions in the vector. The first line of the body of `evenElems` creates the result array, which has the same element type as the argument. So depending on the actual type parameter for `T`, this could be an `Array[Int]`, or an `Array[Boolean]`, or an array of some of the other primitive types in Java, or an array of some reference type. But these types have all different runtime representations, so how is the Scala runtime going to pick the correct one? In fact, it can't do that based on the information it is given, because the actual type that corresponds to the type parameter `T` is erased at runtime. That's why you will get the following error message if you compile the code above: + + error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ +What's required here is that you help the compiler out by providing some runtime hint what the actual type parameter of `evenElems` is. This runtime hint takes the form of a class manifest of type `scala.reflect.ClassManifest`. A class manifest is a type descriptor object which describes what the top-level class of a type is. Alternatively to class manifests there are also full manifests of type `scala.reflect.Manifest`, which describe all aspects of a type. But for array creation, only class manifests are needed. + +The Scala compiler will construct class manifests automatically if you instruct it to do so. "Instructing" means that you demand a class manifest as an implicit parameter, like this: + + def evenElems[T](xs: Vector[T])(implicit m: ClassManifest[T]): Array[T] = ... + +Using an alternative and shorter syntax, you can also demand that the type comes with a class manifest by using a context bound. This means following the type with a colon and the class name `ClassManifest`, like this: + + // this works + def evenElems[T: ClassManifest](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +The two revised versions of `evenElems` mean exactly the same. What happens in either case is that when the `Array[T]` is constructed, the compiler will look for a class manifest for the type parameter T, that is, it will look for an implicit value of type `ClassManifest[T]`. If such a value is found, the manifest is used to construct the right kind of array. Otherwise, you'll see an error message like the one above. + +Here is some REPL interaction that uses the `evenElems` method. + + scala> evenElems(Vector(1, 2, 3, 4, 5)) + res6: Array[Int] = Array(1, 3, 5) + scala> evenElems(Vector("this", "is", "a", "test", "run")) + res7: Array[java.lang.String] = Array(this, a, run) + +In both cases, the Scala compiler automatically constructed a class manifest for the element type (first, `Int`, then `String`) and passed it to the implicit parameter of the `evenElems` method. The compiler can do that for all concrete types, but not if the argument is itself another type parameter without its class manifest. For instance, the following fails: + + scala> def wrap[U](xs: Array[U]) = evenElems(xs) + :6: error: could not find implicit value for + evidence parameter of type ClassManifest[U] + def wrap[U](xs: Array[U]) = evenElems(xs) + ^ +What happened here is that the `evenElems` demands a class manifest for the type parameter `U`, but none was found. The solution in this case is, of course, to demand another implicit class manifest for `U`. So the following works: + + scala> def wrap[U: ClassManifest](xs: Array[U]) = evenElems(xs) + wrap: [U](xs: Array[U])(implicit evidence$1: ClassManifest[U])Array[U] + +This example also shows that the context bound in the definition of `U` is just a shorthand for an implicit parameter named here `evidence$1` of type `ClassManifest[U]`. + +In summary, generic array creation demands class manifests. So whenever creating an array of a type parameter `T`, you also need to provide an implicit class manifest for `T`. The easiest way to do this is to declare the type parameter with a `ClassManifest` context bound, as in `[T: ClassManifest]`. + diff --git a/ko/overviews/collections/concrete-immutable-collection-classes.md b/ko/overviews/collections/concrete-immutable-collection-classes.md new file mode 100644 index 0000000000..ba9de1ec8c --- /dev/null +++ b/ko/overviews/collections/concrete-immutable-collection-classes.md @@ -0,0 +1,215 @@ +--- +layout: overview-large +title: 변경 불가능한 컬렉션에 속하는 구체적인 클래스들 + +disqus: true + +partof: collections +num: 8 +language: ko +--- + +스칼라에는 필요에 따라 선택 가능한 구체적인 선택 불가능 컬렉션 클래스가 많이 있다. 각각의 차이는 어떤 트레잇을 구현하고 있는가(맵, 집합, 열), 무한한가 유한한가, 각각의 연산의 수행 시간(복잡도) 등에 있다. 여기서는 스칼라에서 가장 많이 사용되는 변경 불가능한 컬렉션 타입들을 설명할 것이다. + +## 리스트(List) + +[List(리스트)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html)는 유한한 변경 불가능한 열이다. 리스트의 첫 원소를 가져오거나, 첫 원소를 제외한 나머지를 가져오는 연산에는 상수 시간이 걸린다. 또한 새 원소를 리스트 맨 앞에 붙이는(보통 콘스cons라 부름) 연산도 상수시간이 소요된다. 다른 연산들은 보통 선형 시간(즉, 리스트의 길이 N에 비례)이 걸린다. + +리스트는 계속해서 스칼라 프로그래밍시 사용하는 주요 데이터 구조였다. 따라서 여기서 다시 자세히 설명할 필요는 없을 것이다. 2.8에서 가장 큰 변화는 `List` 클래스, 그리고 그 하위 클래스인 `::`와 하위 객체 `Nil`이 이제는 논리적인 위치에 맞게 `scala.collection.immutable` 패키지에 들어가 있다는 점이다. 여전히 `scala` 패키지에도 타입 별칭 `List`, `Nil`, `::`가 존재한다. 따라서 사용자 관점에서 볼때는 예전에 사용하던 방식대로 계속 사용 가능하다. + +또 다른 변화는 이제 리스트도 컬렉션 프레임워크에 더 밀접하게 통합되어 있따는 점이다. 따라서 이전보다 덜 특별하게 취급된다. 예를 들어 원래는 `List` 짝 객체에 존재하던 여러 연산이 이제는 사용하지 않도록 표시되었다. 이러한 연산들은 모든 컬렉션이 상속하는 [일정한 생성 메소드들]({{ site.baseurl }}/overviews/collections/creating-collections-from-scratch.html)로 대치되었다. + +## 스트림 + +[Stream(스트림)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html)은 리스트와 같지만 각 원소가 지연계산된다는 점에서 차이가 있다. 따라서 무한한 스트림도 가능하다. 요청을 받을 때만 원소를 계산한다. 그 점을 제외하면 나머지 동작 특성은 리스트와 같다. +리스트가 `::` 연산으로 구성되는 반면, 스트림은 비슷한 `#::`로 구성된다. 다음은 정수 1, 2, 3을 포함하는 스트림을 만드는 과정을 보여준다. + + scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty + str: scala.collection.immutable.Stream[Int] = Stream(1, ?) + +이 스트림의 머리는 1이고, 꼬리는 2와 3이다. 꼬리는 출력이 되지 않는다. 왜냐하면 아직 그 값을 계산하지 않았기 때문이다! 스트림은 지연계산을 위한 것이기 때문에, 스트림의 `toString` 메소드는 추가 계산을 수행하지 않게 조심하도록 구현되어 있다. + +다음은 좀 더 복잡한 예제이다. 이 예에서는 주어진 두 정수로 시작하는 피보나치 수열의 스트림을 계산한다. 피보나치 수열은 각 원소가 바로 이전 두 원소의 합인 수열이다. + + scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) + fibFrom: (a: Int,b: Int)Stream[Int] + +이 함수는 믿을 수 없이 간단하다. 열의 첫번째 원소는 분명 `a`이고, 나머지 열은 `b` 다음에 `a + b`가 오는 피보나치 수열이다. 교묘한 부분은 이 열을 계산할 때 무한한 재귀호출을 하지 않는다는 점이다. `::`를 `#::` 대신 사용했다면 함수 호출이 자기 자신을 호출하게 되어서 무한 재귀 호출이 발생했을 것이다. 하지만 `#::`를 사용하기 때문에 우항은 요청되기 전까지 계산되지 않는다. + +다음은 1을 두개 사용해 생성되는 피보나치 수열의 앞 부분 항을 몇 개 보여준다. + + scala> val fibs = fibFrom(1, 1).take(7) + fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) + scala> fibs.toList + res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) + +## 벡터 + +Lists are very efficient when the algorithm processing them is careful to only process their heads. Accessing, adding, and removing the head of a list takes only constant time, whereas accessing or modifying elements later in the list takes time linear in the depth into the list. + +[Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) is a collection type (introduced in Scala 2.8) that addresses the inefficiency for random access on lists. Vectors allow accessing any element of the list in "effectively" constant time. It's a larger constant than for access to the head of a list or for reading an element of an array, but it's a constant nonetheless. As a result, algorithms using vectors do not have to be careful about accessing just the head of the sequence. They can access and modify elements at arbitrary locations, and thus they can be much more convenient to write. + +Vectors are built and modified just like any other sequence. + + scala> val vec = scala.collection.immutable.Vector.empty + vec: scala.collection.immutable.Vector[Nothing] = Vector() + scala> val vec2 = vec :+ 1 :+ 2 + vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) + scala> val vec3 = 100 +: vec2 + vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) + scala> vec3(0) + res1: Int = 100 + +Vectors are represented as trees with a high branching factor (The branching factor of a tree or a graph is the number of children at each node). Every tree node contains up to 32 elements of the vector or contains up to 32 other tree nodes. Vectors with up to 32 elements can be represented in a single node. Vectors with up to `32 * 32 = 1024` elements can be represented with a single indirection. Two hops from the root of the tree to the final element node are sufficient for vectors with up to 215 elements, three hops for vectors with 220, four hops for vectors with 225 elements and five hops for vectors with up to 230 elements. So for all vectors of reasonable size, an element selection involves up to 5 primitive array selections. This is what we meant when we wrote that element access is "effectively constant time". + +Vectors are immutable, so you cannot change an element of a vector and still retain a new vector. However, with the `updated` method you can crate a new vector that differs from a given vector only in a single element: + + scala> val vec = Vector(1, 2, 3) + vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + scala> vec updated (2, 4) + res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) + scala> vec + res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + +As the last line above shows, a call to `updated` has no effect on the original vector `vec`. Like selection, functional vector updates are also "effectively constant time". Updating an element in the middle of a vector can be done by copying the node that contains the element, and every node that points to it, starting from the root of the tree. This means that a functional update creates between one and five nodes that each contain up to 32 elements or subtrees. This is certainly more expensive than an in-place update in a mutable array, but still a lot cheaper than copying the whole vector. + +Because vectors strike a good balance between fast random selections and fast random functional updates, they are currently the default implementation of immutable indexed sequences: + + + scala> collection.immutable.IndexedSeq(1, 2, 3) + res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) + +## Immutable stacks + +If you need a last-in-first-out sequence, you can use a [Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stack.html). You push an element onto a stack with `push`, pop an element with `pop`, and peek at the top of the stack without removing it with `top`. All of these operations are constant time. + +Here are some simple operations performed on a stack: + + + scala> val stack = scala.collection.immutable.Stack.empty + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> val hasOne = stack.push(1) + hasOne: scala.collection.immutable.Stack[Int] = Stack(1) + scala> stack + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> hasOne.top + res20: Int = 1 + scala> hasOne.pop + res19: scala.collection.immutable.Stack[Int] = Stack() + +Immutable stacks are used rarely in Scala programs because their functionality is subsumed by lists: A `push` on an immutable stack is the same as a `::` on a list and a `pop` on a stack is the same a `tail` on a list. + +## Immutable Queues + +A [Queue](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html) is just like a stack except that it is first-in-first-out rather than last-in-first-out. + +Here's how you can create an empty immutable queue: + + scala> val empty = scala.collection.immutable.Queue[Int]() + empty: scala.collection.immutable.Queue[Int] = Queue() + +You can append an element to an immutable queue with `enqueue`: + + scala> val has1 = empty.enqueue(1) + has1: scala.collection.immutable.Queue[Int] = Queue(1) + +To append multiple elements to a queue, call `enqueue` with a collection as its argument: + + scala> val has123 = has1.enqueue(List(2, 3)) + has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) + +To remove an element from the head of the queue, you use `dequeue`: + + scala> val (element, has23) = has123.dequeue + element: Int = 1 + has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) + +Note that `dequeue` returns a pair consisting of the element removed and the rest of the queue. + +## Ranges + +A [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) is an ordered sequence of integers that are equally spaced apart. For example, "1, 2, 3," is a range, as is "5, 8, 11, 14." To create a range in Scala, use the predefined methods `to` and `by`. + + scala> 1 to 3 + res2: scala.collection.immutable.Range.Inclusive + with scala.collection.immutable.Range.ByOne = Range(1, 2, 3) + scala> 5 to 14 by 3 + res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) + +If you want to create a range that is exclusive of its upper limit, then use the convenience method `until` instead of `to`: + + scala> 1 until 3 + res2: scala.collection.immutable.Range.Inclusive + with scala.collection.immutable.Range.ByOne = Range(1, 2) + +Ranges are represented in constant space, because they can be defined by just three numbers: their start, their end, and the stepping value. Because of this representation, most operations on ranges are extremely fast. + +## Hash Tries + +Hash tries are a standard way to implement immutable sets and maps efficiently. They are supported by class [immutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html). Their representation is similar to vectors in that they are also trees where every node has 32 elements or 32 subtrees. But the selection of these keys is now done based on hash code. For instance, to find a given key in a map, one first takes the hash code of the key. Then, the lowest 5 bits of the hash code are used to select the first subtree, followed by the next 5 bits and so on. The selection stops once all elements stored in a node have hash codes that differ from each other in the bits that are selected up to this level. + +Hash tries strike a nice balance between reasonably fast lookups and reasonably efficient functional insertions (`+`) and deletions (`-`). That's why they underly Scala's default implementations of immutable maps and sets. In fact, Scala has a further optimization for immutable sets and maps that contain less than five elements. Sets and maps with one to four elements are stored as single objects that just contain the elements (or key/value pairs in the case of a map) as fields. The empty immutable set and the empty immutable map is in each case a single object - there's no need to duplicate storage for those because and empty immutable set or map will always stay empty. + +## Red-Black Trees + +Red-black trees are a form of balanced binary trees where some nodes are designated "red" and others designated "black." Like any balanced binary tree, operations on them reliably complete in time logarithmic to the size of the tree. + +Scala provides implementations of immutable sets and maps that use a red-black tree internally. Access them under the names [TreeSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) and [TreeMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeMap.html). + + + scala> scala.collection.immutable.TreeSet.empty[Int] + res11: scala.collection.immutable.TreeSet[Int] = TreeSet() + scala> res11 + 1 + 3 + 3 + res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) + +Red black trees are the standard implementation of `SortedSet` in Scala, because they provide an efficient iterator that returns all elements in sorted order. + +## Immutable BitSets + +A [BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html) represents a collection of small integers as the bits of a larger integer. For example, the bit set containing 3, 2, and 0 would be represented as the integer 1101 in binary, which is 13 in decimal. + +Internally, bit sets use an array of 64-bit `Long`s. The first `Long` in the array is for integers 0 through 63, the second is for 64 through 127, and so on. Thus, bit sets are very compact so long as the largest integer in the set is less than a few hundred or so. + +Operations on bit sets are very fast. Testing for inclusion takes constant time. Adding an item to the set takes time proportional to the number of `Long`s in the bit set's array, which is typically a small number. Here are some simple examples of the use of a bit set: + + scala> val bits = scala.collection.immutable.BitSet.empty + bits: scala.collection.immutable.BitSet = BitSet() + scala> val moreBits = bits + 3 + 4 + 4 + moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) + scala> moreBits(3) + res26: Boolean = true + scala> moreBits(0) + res27: Boolean = false + +## List Maps + +A [ListMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html) represents a map as a linked list of key-value pairs. In general, operations on a list map might have to iterate through the entire list. Thus, operations on a list map take time linear in the size of the map. In fact there is little usage for list maps in Scala because standard immutable maps are almost always faster. The only possible difference is if the map is for some reason constructed in such a way that the first elements in the list are selected much more often than the other elements. + + scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") + map: scala.collection.immutable.ListMap[Int,java.lang.String] = + Map(1 -> one, 2 -> two) + scala> map(2) + res30: String = "two" + + + + + + + + + + + + + + + + + + + + + + diff --git a/ko/overviews/collections/concrete-mutable-collection-classes.md b/ko/overviews/collections/concrete-mutable-collection-classes.md new file mode 100644 index 0000000000..32f8bb4331 --- /dev/null +++ b/ko/overviews/collections/concrete-mutable-collection-classes.md @@ -0,0 +1,182 @@ +--- +layout: overview-large +title: Concrete Mutable Collection Classes + +disqus: true + +partof: collections +num: 9 +language: ko +--- + +You've now seen the most commonly used immutable collection classes that Scala provides in its standard library. Take a look now at the mutable collection classes. + +## Array Buffers + +An [ArrayBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) buffer holds an array and a size. Most operations on an array buffer have the same speed as for an array, because the operations simply access and modify the underlying array. Additionally, array buffers can have data efficiently added to the end. Appending an item to an array buffer takes amortized constant time. Thus, array buffers are useful for efficiently building up a large collection whenever the new items are always added to the end. + + scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + scala> buf += 1 + res32: buf.type = ArrayBuffer(1) + scala> buf += 10 + res33: buf.type = ArrayBuffer(1, 10) + scala> buf.toArray + res34: Array[Int] = Array(1, 10) + +## List Buffers + +A [ListBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ListBuffer.html) is like an array buffer except that it uses a linked list internally instead of an array. If you plan to convert the buffer to a list once it is built up, use a list buffer instead of an array buffer. + + scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] + buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() + scala> buf += 1 + res35: buf.type = ListBuffer(1) + scala> buf += 10 + res36: buf.type = ListBuffer(1, 10) + scala> buf.toList + res37: List[Int] = List(1, 10) + +## StringBuilders + +Just like an array buffer is useful for building arrays, and a list buffer is useful for building lists, a [StringBuilder](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html) is useful for building strings. String builders are so commonly used that they are already imported into the default namespace. Create them with a simple `new StringBuilder`, like this: + + scala> val buf = new StringBuilder + buf: StringBuilder = + scala> buf += 'a' + res38: buf.type = a + scala> buf ++= "bcdef" + res39: buf.type = abcdef + scala> buf.toString + res41: String = abcdef + +## Linked Lists + +Linked lists are mutable sequences that consist of nodes which are linked with next pointers. They are supported by class [LinkedList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/LinkedList.html). In most languages `null` would be picked as the empty linked list. That does not work for Scala collections, because even empty sequences must support all sequence methods. In particular `LinkedList.empty.isEmpty` should return `true` and not throw a `NullPointerException`. Empty linked lists are encoded instead in a special way: Their `next` field points back to the node itself. Like their immutable cousins, linked lists are best traversed sequentially. In addition linked lists make it easy to insert an element or linked list into another linked list. + +## Double Linked Lists + +Double linked lists are like single linked lists, except that they have besides `next` another mutable field `prev` that points to the element preceding the current node. The main benefit of that additional link is that it makes element removal very fast. Double linked lists are supported by class [DoubleLinkedList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/DoubleLinkedList.html). + +## Mutable Lists + +A [MutableList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) consists of a single linked list together with a pointer that refers to the terminal empty node of that list. This makes list append a constant time operation because it avoids having to traverse the list in search for its terminal node. [MutableList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) is currently the standard implementation of [mutable.LinearSeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/LinearSeq.html) in Scala. + +## Queues + +Scala provides mutable queues in addition to immutable ones. You use a `mQueue` similarly to how you use an immutable one, but instead of `enqueue`, you use the `+=` and `++=` operators to append. Also, on a mutable queue, the `dequeue` method will just remove the head element from the queue and return it. Here's an example: + + scala> val queue = new scala.collection.mutable.Queue[String] + queue: scala.collection.mutable.Queue[String] = Queue() + scala> queue += "a" + res10: queue.type = Queue(a) + scala> queue ++= List("b", "c") + res11: queue.type = Queue(a, b, c) + scala> queue + res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) + scala> queue.dequeue + res13: String = a + scala> queue + res14: scala.collection.mutable.Queue[String] = Queue(b, c) + +## Array Sequences + +Array sequences are mutable sequences of fixed size which store their elements internally in an `Array[Object]`. They are implemented in Scala by class [ArraySeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html). + +You would typically use an `ArraySeq` if you want an array for its performance characteristics, but you also want to create generic instances of the sequence where you do not know the type of the elements and you do not have a `ClassManifest` to provide it at run-time. These issues are explained in the section on [arrays]({{ site.baseurl }}/overviews/collections/arrays.html). + +## Stacks + +You saw immutable stacks earlier. There is also a mutable version, supported by class [mutable.Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html). It works exactly the same as the immutable version except that modifications happen in place. + + scala> val stack = new scala.collection.mutable.Stack[Int] + stack: scala.collection.mutable.Stack[Int] = Stack() + scala> stack.push(1) + res0: stack.type = Stack(1) + scala> stack + res1: scala.collection.mutable.Stack[Int] = Stack(1) + scala> stack.push(2) + res0: stack.type = Stack(1, 2) + scala> stack + res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.top + res8: Int = 2 + scala> stack + res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.pop + res10: Int = 2 + scala> stack + res11: scala.collection.mutable.Stack[Int] = Stack(1) + +## Array Stacks + +[ArrayStack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayStack.html) is an alternative implementation of a mutable stack which is backed by an Array that gets re-sized as needed. It provides fast indexing and is generally slightly more efficient for most operations than a normal mutable stack. + +## Hash Tables + +A hash table stores its elements in an underlying array, placing each item at a position in the array determined by the hash code of that item. Adding an element to a hash table takes only constant time, so long as there isn't already another element in the array that has the same hash code. Hash tables are thus very fast so long as the objects placed in them have a good distribution of hash codes. As a result, the default mutable map and set types in Scala are based on hash tables. You can access them also directly under the names [mutable.HashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html) and [mutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html). + +Hash sets and maps are used just like any other set or map. Here are some simple examples: + + scala> val map = scala.collection.mutable.HashMap.empty[Int,String] + map: scala.collection.mutable.HashMap[Int,String] = Map() + scala> map += (1 -> "make a web site") + res42: map.type = Map(1 -> make a web site) + scala> map += (3 -> "profit!") + res43: map.type = Map(1 -> make a web site, 3 -> profit!) + scala> map(1) + res44: String = make a web site + scala> map contains 2 + res46: Boolean = false + +Iteration over a hash table is not guaranteed to occur in any particular order. Iteration simply proceeds through the underlying array in whichever order it happens to be in. To get a guaranteed iteration order, use a _linked_ hash map or set instead of a regular one. A linked hash map or set is just like a regular hash map or set except that it also includes a linked list of the elements in the order they were added. Iteration over such a collection is always in the same order that the elements were initially added. + +## Weak Hash Maps + +A weak hash map is a special kind of hash map where the garbage collector does not follow links from the map to the keys stored in it. This means that a key and its associated value will disappear from the map if there is no other reference to that key. Weak hash maps are useful for tasks such as caching, where you want to re-use an expensive function's result if the function is called again on the same key. If keys and function results are stored in a regular hash map, the map could grow without bounds, and no key would ever become garbage. Using a weak hash map avoids this problem. As soon as a key object becomes unreachable, it's entry is removed from the weak hashmap. Weak hash maps in Scala are implemented by class [WeakHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html) as a wrapper of an underlying Java implementation `java.util.WeakHashMap`. + +## Concurrent Maps + +A concurrent map can be accessed by several threads at once. In addition to the usual [Map](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html) operations, it provides the following atomic operations: + +### Operations in class ConcurrentMap + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| `m putIfAbsent(k, v)` |Adds key/value binding `k -> m` unless `k` is already defined in `m` | +| `m remove (k, v)` |Removes entry for `k` if it is currently mapped to `v`. | +| `m replace (k, old, new)` |Replaces value associated with key `k` to `new`, if it was previously bound to `old`. | +| `m replace (k, v)` |Replaces value associated with key `k` to `v`, if it was previously bound to some value.| + +`ConcurrentMap` is a trait in the Scala collections library. Currently, its only implementation is Java's `java.util.concurrent.ConcurrentMap`, which can be converted automatically into a Scala map using the [standard Java/Scala collection conversions]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html). + +## Mutable Bitsets + +A mutable bit of type [mutable.BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html) set is just like an immutable one, except that it is modified in place. Mutable bit sets are slightly more efficient at updating than immutable ones, because they don't have to copy around `Long`s that haven't changed. + + scala> val bits = scala.collection.mutable.BitSet.empty + bits: scala.collection.mutable.BitSet = BitSet() + scala> bits += 1 + res49: bits.type = BitSet(1) + scala> bits += 3 + res50: bits.type = BitSet(1, 3) + scala> bits + res51: scala.collection.mutable.BitSet = BitSet(1, 3) + + + + + + + + + + + + + + + + + + diff --git a/ko/overviews/collections/conversions-between-java-and-scala-collections.md b/ko/overviews/collections/conversions-between-java-and-scala-collections.md new file mode 100644 index 0000000000..0be674d3e2 --- /dev/null +++ b/ko/overviews/collections/conversions-between-java-and-scala-collections.md @@ -0,0 +1,58 @@ +--- +layout: overview-large +title: Conversions Between Java and Scala Collections + +disqus: true + +partof: collections +num: 17 +language: ko +--- + +Like Scala, Java also has a rich collections library. There are many similarities between the two. For instance, both libraries know iterators, iterables, sets, maps, and sequences. But there are also important differences. In particular, the Scala libraries put much more emphasis on immutable collections, and provide many more operations that transform a collection into a new one. + +Sometimes you might need to pass from one collection framework to the other. For instance, you might want to access to an existing Java collection, as if it was a Scala collection. Or you might want to pass one of Scala's collections to a Java method that expects its Java counterpart. It is quite easy to do this, because Scala offers implicit conversions between all the major collection types in the [JavaConversions](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) object. In particular, you will find bidirectional conversions between the following types. + + + Iterator <=> java.util.Iterator + Iterator <=> java.util.Enumeration + Iterable <=> java.lang.Iterable + Iterable <=> java.util.Collection + mutable.Buffer <=> java.util.List + mutable.Set <=> java.util.Set + mutable.Map <=> java.util.Map + mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap + +To enable these conversions, simply import them from the [JavaConversions](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) object: + + scala> import collection.JavaConversions._ + import collection.JavaConversions._ + +You have now automatic conversions between Scala collections and their corresponding Java collections. + + scala> import collection.mutable._ + import collection.mutable._ + scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> val buf: Seq[Int] = jul + buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2) + m: java.util.Map[String,Int] = {hello=2, abc=1} + +Internally, these conversion work by setting up a "wrapper" object that forwards all operations to the underlying collection object. So collections are never copied when converting between Java and Scala. An interesting property is that if you do a round-trip conversion from, say a Java type to its corresponding Scala type, and back to the same Java type, you end up with the identical collection object you have started with. + +The are some other common Scala collections than can also be converted to Java types, but which to not have a corresponding conversion in the other sense. These are: + + Seq => java.util.List + mutable.Seq => java.utl.List + Set => java.util.Set + Map => java.util.Map + +Because Java does not distinguish between mutable and immutable collections in their type, a conversion from, say, `scala.immutable.List` will yield a `java.util.List`, where all mutation operations throw an "UnsupportedOperationException". Here's an example: + + scala> jul = List(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> jul.add(7) + java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:131) + diff --git a/ko/overviews/collections/creating-collections-from-scratch.md b/ko/overviews/collections/creating-collections-from-scratch.md new file mode 100644 index 0000000000..37743e6565 --- /dev/null +++ b/ko/overviews/collections/creating-collections-from-scratch.md @@ -0,0 +1,58 @@ +--- +layout: overview-large +title: Creating Collections From Scratch + +disqus: true + +partof: collections +num: 16 +language: ko +--- + +You have syntax `List(1, 2, 3)` to create a list of three integers and `Map('A' -> 1, 'C' -> 2)` to create a map with two bindings. This is actually a universal feature of Scala collections. You can take any collection name and follow it by a list of elements in parentheses. The result will be a new collection with the given elements. Here are some more examples: + + Traversable() // An empty traversable object + List() // The empty list + List(1.0, 2.0) // A list with elements 1.0, 2.0 + Vector(1.0, 2.0) // A vector with elements 1.0, 2.0 + Iterator(1, 2, 3) // An iterator returning three integers. + Set(dog, cat, bird) // A set of three animals + HashSet(dog, cat, bird) // A hash set of the same animals + Map(a -> 7, 'b' -> 0) // A map from characters to integers + +"Under the covers" each of the above lines is a call to the `apply` method of some object. For instance, the third line above expands to + + List.apply(1.0, 2.0) + +So this is a call to the `apply` method of the companion object of the `List` class. That method takes an arbitrary number of arguments an constructs a list from them. Every collection class in the Scala library has a companion object with such an `apply` method. It does not matter whether the collection class represents a concrete implementation, like `List`, or `Stream` or `Vector`, do, or whether it is an abstract base class such as `Seq`, `Set` or `Traversable`. In the latter case, calling apply will produce some default implementation of the abstract base class. Examples: + + scala> List(1, 2, 3) + res17: List[Int] = List(1, 2, 3) + scala> Traversable(1, 2, 3) + res18: Traversable[Int] = List(1, 2, 3) + scala> mutable.Traversable(1, 2, 3) + res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) + +Besides `apply`, every collection companion object also defines a member `empty`, which returns an empty collection. So instead of `List()` you could write `List.empty`, instead of `Map()`, `Map.empty`, and so on. + +Descendants of `Seq` classes provide also other factory operations in their companion objects. These are summarized in the following table. In short, there's + +* `concat`, which concatenates an arbitrary number of traversables together, +* `fill` and `tabulate`, which generate single or multi-dimensional sequences of given dimensions initialized by some expression or tabulating function, +* `range`, which generates integer sequences with some constant step length, and +* `iterate`, which generates the sequence resulting from repeated application of a function to a start element. + +### Factory Methods for Sequences + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| `S.empty` | The empty sequence. | +| `S(x, y, z)` | A sequence consisting of elements `x, y, z`. | +| `S.concat(xs, ys, zs)` | The sequence obtained by concatenating the elements of `xs, ys, zs`. | +| `S.fill(n){e}` | A sequence of length `n` where each element is computed by expression `e`. | +| `S.fill(m, n){e}` | A sequence of sequences of dimension `m×n` where each element is computed by expression `e`. (exists also in higher dimensions). | +| `S.tabulate(n){f}` | A sequence of length `n` where the element at each index i is computed by `f(i)`. | +| `S.tabulate(m, n){f}` | A sequence of sequences of dimension `m×n` where the element at each index `(i, j)` is computed by `f(i, j)`. (exists also in higher dimensions). | +| `S.range(start, end)` | The sequence of integers `start` ... `end-1`. | +| `S.range(start, end, step)`| The sequence of integers starting with `start` and progressing by `step` increments up to, and excluding, the `end` value. | +| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | diff --git a/ko/overviews/collections/equality.md b/ko/overviews/collections/equality.md new file mode 100644 index 0000000000..4258a943f1 --- /dev/null +++ b/ko/overviews/collections/equality.md @@ -0,0 +1,31 @@ +--- +layout: overview-large +title: Equality + +disqus: true + +partof: collections +num: 13 +language: ko +--- + +The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences. Collections in different categories are always unequal. For instance, `Set(1, 2, 3)` is unequal to `List(1, 2, 3)` even though they contain the same elements. On the other hand, within the same category, collections are equal if and only if they have the same elements (for sequences: the same elements in the same order). For example, `List(1, 2, 3) == Vector(1, 2, 3)`, and `HashSet(1, 2) == TreeSet(2, 1)`. + +It does not matter for the equality check whether a collection is mutable or immutable. For a mutable collection one simply considers its current elements at the time the equality test is performed. This means that a mutable collection might be equal to different collections at different times, depending what elements are added or removed. This is a potential trap when using a mutable collection as a key in a hashmap. Example: + + scala> import collection.mutable.{HashMap, ArrayBuffer} + import collection.mutable.{HashMap, ArrayBuffer} + scala> val buf = ArrayBuffer(1, 2, 3) + buf: scala.collection.mutable.ArrayBuffer[Int] = + ArrayBuffer(1, 2, 3) + scala> val map = HashMap(buf -> 3) + map: scala.collection.mutable.HashMap[scala.collection. + mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) + scala> map(buf) + res13: Int = 3 + scala> buf(0) += 1 + scala> map(buf) + java.util.NoSuchElementException: key not found: + ArrayBuffer(2, 2, 3) + +In this example, the selection in the last line will most likely fail because the hash-code of the array `xs` has changed in the second-to-last line. Therefore, the hash-code-based lookup will look at a different place than the one where `xs` was stored. diff --git a/ko/overviews/collections/introduction.md b/ko/overviews/collections/introduction.md new file mode 100644 index 0000000000..e65d97905e --- /dev/null +++ b/ko/overviews/collections/introduction.md @@ -0,0 +1,72 @@ +--- +layout: overview-large +title: 컬렉션 소개 + +disqus: true + +partof: collections +num: 1 +language: ko +--- + +**마틴 오더스키(Martin Odesky)와 렉스 스푼(Lex Spoon) 씀** + +대부분의 사람들에게 있어 스칼라 2.8의 가장 중요한 변화는 새 컬렉션 프레임워크일 것이다. +스칼라에는 예전부터 컬렉션이 포함되어 있었다(실제 새 프레임워크도 이전 컬렉션과 상당부분 호환성이 있다). 하지만 다양한 컬렉션 유형을 포괄하는 일반적이면서 균일한 프레임워크를 제공하는 것은 스칼라 2.8 부터이다. + +처음 봤을 땐 컬렉션에 추가된 내용이 잘 알기 어려울 정도로 작게 느껴질 수도 있다. 하지만, 그 +변화는 여러분의 프로그래밍 스타일에 큰 영향을 끼칠 것이다. 실제로, 컬렉션의 개별 원소 보다는 +전체 컬렉션을 프로그램의 기본 구성 요소로 사용하는 더 높은 레벨에서 작업하는 느낌을 받을 수 +있다. 이런 프로그래밍 스타일에 익숙해지려면 적응이 필요하다. 다행히 새 스칼라 컬렉션이 +제공하는 몇몇 특징 덕분에 더 쉽게 적응할 수 있다. 새 스칼라 컬렉션은 쓰기 쉽고, +간결하며, 안전하고, 빠른 데다가, 범용이다. + +**간편성:** 대부분의 경우 컬렉션과 관련된 문제를 해결하기 위해 필요한 메소드는 수는 +20-50개 정도이며 두세 연산을 조합하여 해결 가능하다. 복잡하게 루프를 돌거나 재귀 호출을 +하기 위해 골머리를 썩힐 필요가 없다. 영속적인 컬렉션과 부작용이 없는 연산을 사용하면 실수로 +기존 컬렉션을 오염시킬 염려가 없다. 반복자와 컬렉션 업데이트간의 간섭도 없다. + +**간결성:** 하나 이상의 루프가 필요했던 작업을 한 단어로 수행할 수 있다. 간결한 문법으로 +연산을 표현할 수 있고, 각 연산을 힘들이지 않고 조합할 수 있다. 따라서 컬렉션 전용으로 고안된 +대수식을 사용하는 것 같은 느낌을 받게될 것이다. + +**안전성:** 경험해 보지 않고 이 부분을 알아채기는 어렵다. 스칼라 컬렉션은 정적 타이핑과 +함수적 특성을 가지기 때문에 프로그래머가 저지를 수 있는 오류의 대부분을 컴파일시 잡아낼 +수 있다. 이유는 (1) 컬렉션 연산을 많이 사용하기 때문에 연산에 대해 충분한 테스트가 되어 있고, +(2) 컬렉션 연산을 사용할 때 입력과 출력을 매개 변수로 넘기는 함수와 결과값으로 명확히 해야 +하며, (3) 이런 명시적인 입/출력이 정적인 타입 검사를 거쳐야 한다는 점 때문이다. 요약하면, +대부분의 잘못된 사용은 타입 오류라로 나타나게 된다. 수백줄 짜리 코드가 첫 시도시 실행되는 +경우도 그리 드물지 않다. + +**속도:** 라이브라리 안의 컬렉션 연산은 최적화와 미세조정이 이루어져 있다. 그 결과 컬렉션을 +사용하는 것은 일반적으로 아주 효율적이다. 손으로 직접 주의깊게 미세조정을 거친 데이터 구조와 +연산을 사용하면 조금 더 나은 결과를 얻을 수도 있을 것이다. 하지만 구현 도중에 최적이 아닌 +선택을 하거나 해서 훨씬 더 나쁜 결과를 가져올 수도 있다. 더 나아가, 최근에는 다중코어 +시스템에서 병렬로 수행되는 컬렉션이 도입되었다. 병렬 컬렉션은 순차적 컬렉션과 동일한 연산을 +지원한다. 따라서 새로운 연산을 배우거나 코드를 재작성할 필요가 없다. 순차적 컬렉션을 병렬 +컬렉션으로 변경하려면 단지 `par` 메소드를 호출하기만 하면 된다. + +**범용:** 어떤 컬렉션 연산이든, 가능한 모든 컬렉션에서 이를 제공하게 되어 있다. 따라서 알고 +있는 연산의 갯수가 적어도 많은 일을 할 수 있다. 예를 들어 문자열은 개념적으로 문자의 +시퀀스이다. 따라서, 스칼라 컬렉션의 문자열은 모든 시퀀스 연산을 지원한다. 배열도 마찬가지이다. + +**예제:** 다음 코드는 스칼라 컬렉션의 여러 장점을 보여준다. + + val (minors, adults) = people partition (_.age < 18) + +이 코드가 하는 일은 직접적이며 분명하다. `people`의 컬렉션을 나이에 따라 `minors`과 +`adults`로 구획한다. `partition` 메소드는 최상위 컬렉션 타입인 `TraversableLike`에 +구현되어 있다. 따라서 배열을 포함한 모든 컬렉션에서 이 코드가 동작할 수 있다. 결과로 +나오는 `minors`과 `adults`는 `people` 컬렉션과 같은 타입이 된다. + +전통적인 컬렉션 처리를 사용하는 경우 루프를 최대 세 개 사용해야 한다는 점과 비교해 보면 이 +코드는 매우 간결하다(배열을 사용하는 경우 중간 결과를 다른곳에 버퍼링하기 위해 루프가 세 개 +필요하다). 일단 컬렉션의 기본 어휘를 배우고 나면, 직접 루프를 도는 것보다 이렇게 코드를 +작성하는 편이 더 쉽고 안전하다는 사실을 알게 될 것이다. 또한, `partition` 연산은 +꽤 빠르며, 다중코어에서 병렬 컬렉션으로 수행한다면 훨씬 더 빨라진다(병렬 컬렉션은 +스칼라 2.9에 포함되어 배포되었다). + +이 문서는 스칼라 컬렉션 클래스의 API를 사용자 관점에서 자세히 논의한다. 이제 모든 기반 +클래스와 그 안에 정의된 메소드에 대해 여행을 떠나 보자. + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/iterators.md b/ko/overviews/collections/iterators.md new file mode 100644 index 0000000000..298f914648 --- /dev/null +++ b/ko/overviews/collections/iterators.md @@ -0,0 +1,176 @@ +--- +layout: overview-large +title: Iterators + +disqus: true + +partof: collections +num: 15 +language: ko +--- + +An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator `it` are `next` and `hasNext`. A call to `it.next()` will return the next element of the iterator and advance the state of the iterator. Calling `next` again on the same iterator will then yield the element one beyond the one returned previously. If there are no more elements to return, a call to `next` will throw a `NoSuchElementException`. You can find out whether there are more elements to return using [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)'s `hasNext` method. + +The most straightforward way to "step through" all the elements returned by an iterator `it` uses a while-loop: + + while (it.hasNext) + println(it.next()) + +Iterators in Scala also provide analogues of most of the methods that you find in the `Traversable`, `Iterable` and `Seq` classes. For instance, they provide a `foreach` method which executes a given procedure on each element returned by an iterator. Using `foreach`, the loop above could be abbreviated to: + + it foreach println + +As always, for-expressions can be used as an alternate syntax for expressions involving `foreach`, `map`, `withFilter`, and `flatMap`, so yet another way to print all elements returned by an iterator would be: + + for (elem <- it) println(elem) + +There's an important difference between the foreach method on iterators and the same method on traversable collections: When called to an iterator, `foreach` will leave the iterator at its end when it is done. So calling `next` again on the same iterator will fail with a `NoSuchElementException`. By contrast, when called on on a collection, `foreach` leaves the number of elements in the collection unchanged (unless the passed function adds to removes elements, but this is discouraged, because it may lead to surprising results). + +The other operations that Iterator has in common with `Traversable` have the same property. For instance, iterators provide a `map` method, which returns a new iterator: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it.map(_.length) + res1: Iterator[Int] = non-empty iterator + scala> res1 foreach println + 1 + 6 + 2 + 5 + scala> it.next() + java.util.NoSuchElementException: next on empty iterator + +As you can see, after the call to `it.map`, the `it` iterator has advanced to its end. + +Another example is the `dropWhile` method, which can be used to find the first elements of an iterator that has a certain property. For instance, to find the first word in the iterator above that has at least two characters you could write: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it dropWhile (_.length < 2) + res4: Iterator[java.lang.String] = non-empty iterator + scala> it.next() + res5: java.lang.String = number + +Note again that `it` has changed by the call to `dropWhile`: it now points to the second word "number" in the list. In fact, `it` and the result `res4` returned by `dropWhile` will return exactly the same sequence of elements. + +There is only one standard operation which allows to re-use the same iterator: The call + + val (it1, it2) = it.duplicate + +gives you _two_ iterators which each return exactly the same elements as the iterator `it`. The two iterators work independently; advancing one does not affect the other. By contrast the original iterator `it` is advanced to its end by `duplicate` and is thus rendered unusable. + +In summary, iterators behave like collections _if one never accesses an iterator again after invoking a method on it_. The Scala collection libraries make this explicit with an abstraction [TraversableOnce](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html), which is a common superclass of [Traversable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) and [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). As the name implies, `TraversableOnce` objects can be traversed using `foreach` but the state of that object after the traversal is not specified. If the `TraversableOnce` object is in fact an `Iterator`, it will be at its end after the traversal, but if it is a `Traversable`, it will still exist as before. A common use case of `TraversableOnce` is as an argument type for methods that can take either an iterator or a traversable as argument. An example is the appending method `++` in class `Traversable`. It takes a `TraversableOnce` parameter, so you can append elements coming from either an iterator or a traversable collection. + +All operations on iterators are summarized below. + +### Operations in class Iterator + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Abstract Methods:** | | +| `it.next()` | Returns next element on iterator and advances past it. | +| `it.hasNext` | Returns `true` if `it` can return another element. | +| **Variations:** | | +| `it.buffered` | A buffered iterator returning all elements of `it`. | +| `it grouped size` | An iterator that yields the elements elements returned by `it` in fixed-sized sequence "chunks". | +| `xs sliding size` | An iterator that yields the elements elements returned by `it` in sequences representing a sliding fixed-sized window. | +| **Duplication:** | | +| `it.duplicate` | A pair of iterators that each independently return all elements of `it`. | +| **Additions:** | | +| `it ++ jt` | An iterator returning all elements returned by iterator `it`, followed by all elements returned by iterator `jt`. | +| `it padTo (len, x)` | The iterator that first returns all elements of `it` and then follows that by copies of `x` until length `len` elements are returned overall. | +| **Maps:** | | +| `it map f` | The iterator obtained from applying the function `f` to every element returned from `it`. | +| `it flatMap f` | The iterator obtained from applying the iterator-valued function f to every element in `it` and appending the results. | +| `it collect f` | The iterator obtained from applying the partial function `f` to every element in `it` for which it is defined and collecting the results. | +| **Conversions:** | | +| `it.toArray` | Collects the elements returned by `it` in an array. | +| `it.toList` | Collects the elements returned by `it` in a list. | +| `it.toIterable` | Collects the elements returned by `it` in an iterable. | +| `it.toSeq` | Collects the elements returned by `it` in a sequence. | +| `it.toIndexedSeq` | Collects the elements returned by `it` in an indexed sequence. | +| `it.toStream` | Collects the elements returned by `it` in a stream. | +| `it.toSet` | Collects the elements returned by `it` in a set. | +| `it.toMap` | Collects the key/value pairs returned by `it` in a map. | +| **Coying:** | | +| `it copyToBuffer buf` | Copies all elements returned by `it` to buffer `buf`. | +| `it copyToArray(arr, s, n)`| Copies at most `n` elements returned by `it` to array `arr` starting at index `s`. The last two arguments are optional. | +| **Size Info:** | | +| `it.isEmpty` | Test whether the iterator is empty (opposite of `hasNext`). | +| `it.nonEmpty` | Test whether the collection contains elements (alias of `hasNext`). | +| `it.size` | The number of elements returned by `it`. Note: `it` will be at its end after this operation! | +| `it.length` | Same as `it.size`. | +| `it.hasDefiniteSize` | Returns `true` if `it` is known to return finitely many elements (by default the same as `isEmpty`). | +| **Element Retrieval Index Search:**| | +| `it find p` | An option containing the first element returned by `it` that satisfies `p`, or `None` is no element qualifies. Note: The iterator advances to after the element, or, if none is found, to the end. | +| `it indexOf x` | The index of the first element returned by `it` that equals `x`. Note: The iterator advances past the position of this element. | +| `it indexWhere p` | The index of the first element returned by `it` that satisfies `p`. Note: The iterator advances past the position of this element. | +| **Subiterators:** | | +| `it take n` | An iterator returning of the first `n` elements of `it`. Note: it will advance to the position after the `n`'th element, or to its end, if it contains less than `n` elements. | +| `it drop n` | The iterator that starts with the `(n+1)`'th element of `it`. Note: `it` will advance to the same position. | +| `it slice (m,n)` | The iterator that returns a slice of the elements returned from it, starting with the `m`'th element and ending before the `n`'th element. | +| `it takeWhile p` | An iterator returning elements from `it` as long as condition `p` is true. | +| `it dropWhile p` | An iterator skipping elements from `it` as long as condition `p` is `true`, and returning the remainder. | +| `it filter p` | An iterator returning all elements from `it` that satisfy the condition `p`. | +| `it withFilter p` | Same as `it` filter `p`. Needed so that iterators can be used in for-expressions. | +| `it filterNot p` | An iterator returning all elements from `it` that do not satisfy the condition `p`. | +| **Subdivisions:** | | +| `it partition p` | Splits `it` into a pair of two iterators; one returning all elements from `it` that satisfy the predicate `p`, the other returning all elements from `it` that do not. | +| **Element Conditions:** | | +| `it forall p` | A boolean indicating whether the predicate p holds for all elements returned by `it`. | +| `it exists p` | A boolean indicating whether the predicate p holds for some element in `it`. | +| `it count p` | The number of elements in `it` that satisfy the predicate `p`. | +| **Folds:** | | +| `(z /: it)(op)` | Apply binary operation `op` between successive elements returned by `it`, going left to right and starting with `z`. | +| `(it :\ z)(op)` | Apply binary operation `op` between successive elements returned by `it`, going right to left and starting with `z`. | +| `it.foldLeft(z)(op)` | Same as `(z /: it)(op)`. | +| `it.foldRight(z)(op)` | Same as `(it :\ z)(op)`. | +| `it reduceLeft op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going left to right. | +| `it reduceRight op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going right to left. | +| **Specific Folds:** | | +| `it.sum` | The sum of the numeric element values returned by iterator `it`. | +| `it.product` | The product of the numeric element values returned by iterator `it`. | +| `it.min` | The minimum of the ordered element values returned by iterator `it`. | +| `it.max` | The maximum of the ordered element values returned by iterator `it`. | +| **Zippers:** | | +| `it zip jt` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`. | +| `it zipAll (jt, x, y)` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`, where the shorter iterator is extended to match the longer one by appending elements `x` or `y`. | +| `it.zipWithIndex` | An iterator of pairs of elements returned from `it` with their indices. | +| **Update:** | | +| `it patch (i, jt, r)` | The iterator resulting from `it` by replacing `r` elements starting with `i` by the patch iterator `jt`. | +| **Comparison:** | | +| `it sameElements jt` | A test whether iterators it and `jt` return the same elements in the same order. Note: At least one of `it` and `jt` will be at its end after this operation. | +| **Strings:** | | +| `it addString (b, start, sep, end)`| Adds a string to `StringBuilder` `b` which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | +| `it mkString (start, sep, end)` | Converts the collection to a string which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | + +### Buffered iterators + +Sometimes you want an iterator that can "look ahead", so that you can inspect the next element to be returned without advancing past that element. Consider for instance, the task to skip leading empty strings from an iterator that returns a sequence of strings. You might be tempted to write the following + + + def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} + +But looking at this code more closely, it's clear that this is wrong: The code will indeed skip leading empty strings, but it will also advance `it` past the first non-empty string! + +The solution to this problem is to use a buffered iterator. Class [BufferedIterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) is a subclass of [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html), which provides one extra method, `head`. Calling `head` on a buffered iterator will return its first element but will not advance the iterator. Using a buffered iterator, skipping empty words can be written as follows. + + def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } + +Every iterator can be converted to a buffered iterator by calling its `buffered` method. Here's an example: + + scala> val it = Iterator(1, 2, 3, 4) + it: Iterator[Int] = non-empty iterator + scala> val bit = it.buffered + bit: java.lang.Object with scala.collection. + BufferedIterator[Int] = non-empty iterator + scala> bit.head + res10: Int = 1 + scala> bit.next() + res11: Int = 1 + scala> bit.next() + res11: Int = 2 + +Note that calling `head` on the buffered iterator `bit` does not advance it. Therefore, the subsequent call `bit.next()` returns the same value as `bit.head`. diff --git a/ko/overviews/collections/maps.md b/ko/overviews/collections/maps.md new file mode 100644 index 0000000000..e447e5aea2 --- /dev/null +++ b/ko/overviews/collections/maps.md @@ -0,0 +1,166 @@ +--- +layout: overview-large +title: 맵(Map) + +disqus: true + +partof: collections +num: 7 +language: ko +--- + +[Map(맵)](http://www.scala-lang.org/api/current/scala/collection/Map.html)은 키와 값의 쌍으로 구성된 [Iterable(반복가능)](http://www.scala-lang.org/api/current/scala/collection/Iterable.html)한 컬렉션이다(_맵핑_ 또는 _연관_ 이라고도 한다). 스칼라의 [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) 클래스에는 `key -> value`를 `(key, value)` 대신 쓸수 있도록 하는 묵시적 변환이 정의되어 있다. 예를 들어 `Map("x" -> 24, "y" -> 25, "z" -> 26)`와 `Map(("x", 24), ("y", 25), ("z", 26))`는 의미가 완전히 동일하다. 다만 전자가 더 보기 좋을 뿐이다. + +맵에서 제공하는 기본 연산들은 집합과 비슷하다. 이를 분류하면 아래와 같다. 전체는 그 이후 표에 요약해 두었다. + +* **찾기** 연산 `apply`, `get`, `getOrElse`, `contains`, `isDefinedAt` 들은 맵을 키를 받아 값을 반환하는 부분함수로 바꾼다. 맵에서 바탕이 되는 찾기 메소드는 `def get(key): Option[Value]`이다. 연산 "`m get key`"는 맵에 주어진 `key`와 대응하는 연관관계쌍이 들어 있는지를 본다. 만약 그런 쌍이 있다면 그 연관쌍을 `Some`으로 포장해서 내어 놓는다. 맵에 키가 정의되어 있지 않다면 `get`은 `None`을 반환한다. 맵에는 또한 키에 대해 값을 `Option`으로 감싸지 않고 바로 반환하는 `apply` 메소드도 정의되어 있다. 만약 키가 없다면 예외가 발생한다. +* **추가와 변경** 연산 `+`, `++`, `updated`를 사용하면 새로운 연관관계를 맵에 추가하거나, 기존의 관계를 변경할 수 있다. +* **제거** 연산 `-`, `--`는 맵에서 연관 관계를 제거하기 위해 사용한다. +* **부분 컬렉션** 생성 메소드 `keys`, `keySet`, `keysIterator`, `values`, `valuesIterator`는 맵의 모든 키나 모든 값을 별도로 여러 형태로 반환한다. +* **변환** 연산 `filterKeys`와 `mapValues`는 맵을 필터링해 새 맵을 만들거나 기존 맵의 연관관계를 변환할 때 사용한다. + +### 맵 클래스의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **찾기:** | | +| `ms get k` |맵 `ms`에 있는 키 `k`에 대응하는 값을 옵션으로 반환한다. 찾을 수 없다면 `None`이 반환된다.| +| `ms(k)` |(`ms apply k`라고 명시적으로 쓸 수 있음) 맵 `ms`에서 키 `k`에 대응하는 값을 반환한다. 값이 없다면 예외를 던진다.| +| `ms getOrElse (k, d)` |맵 `ms`에서 키 `k`에 대응하는 값을 반환한다. 값이 없다면 디폴트 값으로 지정된 `d`를 반환한다.| +| `ms contains k` |맴 `ms`에 키 `k`에 대한 맵핑이 정의되어 있는지 여부를 반환한다.| +| `ms isDefinedAt k` |`contains`와 같다. | +| **추가와 변경:**| | +| `ms + (k -> v)` |`ms`의 모든 연관관계와 더불어 맵핑 `k -> v`, 즉 키 `k`에서 `v`로 가는 연관관계가 추가된 맵을 반환한다.| +| `ms + (k -> v, l -> w)` |`ms`의 모든 연관관계와 더불어 주어진 모든 키-값 연관관계가 추가된 맵을 반환한다.| +| `ms ++ kvs` |`ms`의 모든 연관관계와 더불어 `kvs`에 있는 연관관계가 추가된 맵을 반환한다. (단, `kvs`는 튜플이 원소이거나 맵 타입이어야 정상적으로 동작한다.)| +| `ms updated (k, v)` |`ms + (k -> v)`과 같다.| +| **제거:** | | +| `ms - k` |`ms`의 모든 연관관계 중에서 키 `k`에 대한 연관관계만이 포함되지 않은 맵을 반환한다.| +| `ms - (k, 1, m)` |`ms`의 모든 연관관계 중에서 주어진 여러 키들에 대한 연관관계들이 포함되지 않은 맵을 반환한다.| +| `ms -- ks` |`ms`의 모든 연관관계 중에서 `ks`에 있는 키들에 대한 연관관계들이 포함되지 않은 맵을 반환한다.| +| **부분 컬렉션 :** | | +| `ms.keys` |`ms`의 모든 키를 포함하는 반복가능 객체를 반환한다. | +| `ms.keySet` |`ms`의 모든 키를 포함하는 집합 객체를 반환한다. | +| `ms.keyIterator` |`ms`의 키를 내어놓는 반복자를 반환한다. | +| `ms.values` |`ms`에서 키에 연관된 모든 값을 포함하는 반복가능 객체를 반환한다. | +| `ms.valuesIterator` |`ms`에서 키에 연관된 모든 값을 포함하는 반복자를 반환한다. | +| **변환:** | | +| `ms filterKeys p` |`ms`에서 키가 술어 `p`를 만족하는 연관관계만을 포함하는 새 맵 뷰를 반환한다.| +| `ms mapValues f` |`ms`에서 키에 연관된 모든 값에 대해 함수 `f`를 적용해 얻을 수 있는 새 맵 뷰를 반환한다.| + +변경 가능 맵은 아래 표에 정리된 연산을 추가로 지원한다. + + +### mutable.Map 클래스에 정의된 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추가와 변경:**| | +| `ms(k) = v` |(`ms.update(x, v)`라고 명시적으로 쓸 수도 있음). 키 `k`에서 값 `v`로 가는 맵핑을 맵 `ms`에 부작용을 사용해 추가한다. 이미 키 `k`에 대한 값이 정의되어 있었다면 이를 덮어쓴다.| +| `ms += (k -> v)` |키 `k`에서 값 `v`로 가는 맵핑을 맵 `ms`에 부작용을 사용해 추가하고 `ms` 자신을 반환한다.| +| `ms += (k -> v, l -> w)` |지정된 맵핑들을 맵 `ms`에 부작용을 사용해 추가하고 `ms` 자신을 반환한다.| +| `ms ++= kvs` |`kvs`에 있는 모든 연관관계들을 맵 `ms`에 부작용을 사용해 추가하고 `ms` 자신을 반환한다.| +| `ms put (k, v)` |키 `k`에서 값 `v`로 가는 맵핑을 맵 `ms`에 부작용을 사용해 추가하고, 이전에 `k`와 연관된 값이 있었다면 이를 옵션 객체로 반환한다.| +| `ms getOrElseUpdate (k, d)`|키 `k`가 맵 `ms`에 정의되어 있다면 그 값을 반환하고, 그렇지 않다면 `ms`에 새 연관관계 `k -> d`를 추가한 다음 `d`를 반환한다.| +| **제거:**| | +| `ms -= k` |키 `k`에 해당하는 맵핑을 맵 `ms`에서 부작용을 사용해 제거한 후, `ms`자신을 반환한다.| +| `ms -= (k, l, m)` |지정된 키들에 해당하는 맵핑을 맵 `ms`에서 부작용을 사용해 제거한 후, `ms`자신을 반환한다.| +| `ms --= ks` |`ks`에 있는 키들에 해당하는 맵핑을 맵 `ms`에서 부작용을 사용해 제거한 후, `ms`자신을 반환한다.| +| `ms remove k` |키 `k`에 대한 맵핑을 맵 `ms`에서 부작용을 사용해 제거하고, 이전에 `k`와 연관된 값이 있었다면 이를 옵션 객체로 반환한다.| +| `ms retain p` |`ms`에서 (키,값) 튜플에 대한 술어 `p`를 만족하는 연관 관계들만 남기고 나머지는 다 제거한 다음, `ms` 자신을 반환한다| +| `ms.clear()` |`ms`에서 모든 매핑을 제거한 다음, | +| **변환:** | | +| `ms transform f` |`ms`의 모든 연관 쌍을 `f`를 사용해 변환한다. `ms`자신을 반환한다.| +| **복사:** | | +| `ms.clone` |`ms`과 같은 맵핑들을 포함하는 새로운 변경가능한 맵을 반환한다.| + +맵의 추가와 제거 연산은 집합의 그것과 비슷하다. 집합에서와 마찬가지로 변경 가능한 맵도 부작용을 사용하지 않는 추가 연산 `+`, `-`, `updated`를 지원한다. 하지만 이런 연산들은 변경 가능 맵을 복사하기 때문에 자주 사용되지는 않는다. 대신 변경 가능한 맵 `m`은 보통 "그 자리에서" `m(key) = value`이나 `m += (key -> value)` 연산을 사용해 변경된다. 업데이트 연산에는 이전에 `key`와 연관되어 있던 값을 `Option`으로 돌려주는 `m put (key, value)`도 있다. `put`은 만약 `key`가 맵에 존재하지 않았다면 `None`을 반환한다. + +`getOrElseUpdate`는 캐시처럼 동작하는 맵을 억세스할 때 유용하다. `f`를 호출하면 비용이 많이 드는 계산을 수행해야 하는 경우를 생각해 보자. + + scala> def f(x: String) = { + println("taking my time."); sleep(100) + x.reverse } + f: (x: String)String + +더 나아가 `f`에 부작용이 없다고 한다면, 동일한 인자로 이 함수를 호출할 때마다 항상 같은 결과를 받을 것이다. 이런 경우 예전에 계산했던 값을 인자와 `f`의 결과값을 연관시켜 맴에 저장해 두고, 새로운 인자 값이 들어와 맵에서 예전에 계산해 둔 결과를 찾을 수 없는 경우에만 `f`의 결과를 계산하게 한다면 비용이 줄어든다. 이 경우 맵을 함수 `f`의 계산 결과에 대한 _캐시(cache)_ 라 부를 수도 있다. + + val cache = collection.mutable.Map[String, String]() + cache: scala.collection.mutable.Map[String,String] = Map() + +이제 더 효율이 좋은 `f` 함수의 캐시된 버전을 만들 수 있다. + + scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) + cachedF: (s: String)String + scala> cachedF("abc") + taking my time. + res3: String = cba + scala> cachedF("abc") + res4: String = cba + +`getOrElseUpdate`의 두번째 인자는 "이름에 의한 호출(by-name)"이므로, 위의 `f("abc")` 계산은 오직 `getOrElseUpdate`가 두번째 인자 값을 필요로 하는 경우에만 수행된다. 이는 정확하게 말하자면 첫 번째 인자를 `cache` 맵에서 못 찾은 경우이다. 맵의 기본 연산을 활용해 `cachedF`를 직접 구현할 수도 있었겠지만, 그렇게 하려면 조금 길게 코드를 작성해야 한다. + + def cachedF(arg: String) = cache get arg match { + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result + } + +### 동기화된 집합과 맵 ### + +쓰레드 안전한 변경 가능한 맵을 만들고 싶다면 `SynchronizedMap` 트레잇을 원하는 맵 구현에 끼워 넣으면 된다. 예를 들어 아래 코드처럼 `SynchronizedMap`을 `HashMap`에 끼워 넣을 수 있다. 아래 예제는 두 트레잇 `Map`과 `SynchronizedMap`, 그리고 클래스 `HashMap`을 패키지 `scala.collection.mutable`에서 임포트한다. 나머지 부분은 싱글턴 `MapMaker`를 만드는 것이다. 이 객체는 메소드 `makeMap`를 구현한다. `makeMap` 메소드는 문자열에서 문자열로 맵핑하는 동기화된 해시맵을 반환한다. + + import scala.collection.mutable.{Map, + SynchronizedMap, HashMap} + object MapMaker { + def makeMap: Map[String, String] = { + new HashMap[String, String] with + SynchronizedMap[String, String] { + override def default(key: String) = + "Why do you want to know?" + } + } + } + +
`SynchronizedMap`트레잇 끼워 넣기
+ +`makeMap`의 첫 문장은 새로운 변경 가능한 `HashMap`을 만들고 `SynchronizedMap` 트레잇을 끼워 넣는다. + + new HashMap[String, String] with + SynchronizedMap[String, String] + +스칼라 컴파일러는 이 코드를 보고 `SynchronizedMap`을 끼워 넣은 `HashMap`의 하위 클래스를 만들고, 그 클래스의 인스턴스를 만든다(그리고 반환한다). 이 합성 클래스는 아래와 같이 `default`라는 메소드를 재정의한다. + + override def default(key: String) = + "Why do you want to know?" + +사용자가 맵에게 어떤 키와 연관된 값을 물어봤는데 그런 연관이 맵에 존재하지 않는다면 기본 동작은 `NoSuchElementException` 예외를 발생시키는 것이다. 하지만 새 맵 클래스를 만들면서 `default` 메소드를 재정의하면 존재하지 않는 키에 대한 질의가 들어올 때 `default` 메소드가 정의하는 값을 반환하게 된다. 따라서 컴파일러가 만든 동기화된 합성 `HashMap` 하위 클래스는 존재하지 않는 키에 대한 질의를 받으면 `"Why do you want to know?"`라는 문자열을 반환한다. + +`makeMap` 메소드가 반환하는 변경 가능한 맵에 `SynchronizedMap` 트레잇을 끼워 넣었기 때문에, 동시에 여러 쓰레드에서 이를 사용할 수 있다. 맵에 대한 억세스는 동기화될 것이다. 다음은 인터프리터상에서 한 쓰레드를 사용해 이 맵을 사용하는 예를 보여준다. + + scala> val capital = MapMaker.makeMap + capital: scala.collection.mutable.Map[String,String] = Map() + scala> capital ++ List("US" -> "Washington", + "Paris" -> "France", "Japan" -> "Tokyo") + res0: scala.collection.mutable.Map[String,String] = + Map(Paris -> France, US -> Washington, Japan -> Tokyo) + scala> capital("Japan") + res1: String = Tokyo + scala> capital("New Zealand") + res2: String = Why do you want to know? + scala> capital += ("New Zealand" -> "Wellington") + scala> capital("New Zealand") + res3: String = Wellington + +동기화된 맵을 만드는 것과 비슷한 방식으로 동기화된 집합도 만들 수 있다. 예를 들어 `SynchronizedSet` 트레잇을 끼워 넣으면 동기화된 `HashSet`을 만들 수 있다. 다음과 같다. + + import scala.collection.mutable + val synchroSet = + new mutable.HashSet[Int] with + mutable.SynchronizedSet[Int] + +마지막으로, 어떤 상황에서 동기화된 컬렉션을 사용하는 것을 고려하게 된다면, 그 상황이 `java.util.concurrent` 컬렉션을 필요로 하는 경우는 아닌지 한번 더 생각해 보도록 하라. + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/migrating-from-scala-27.md b/ko/overviews/collections/migrating-from-scala-27.md new file mode 100644 index 0000000000..a4e774f6ed --- /dev/null +++ b/ko/overviews/collections/migrating-from-scala-27.md @@ -0,0 +1,45 @@ +--- +layout: overview-large +title: Migrating from Scala 2.7 + +disqus: true + +partof: collections +num: 18 +outof: 18 +language: ko +--- + +Porting your existing Scala applications to use the new collections should be almost automatic. There are only a couple of possible issues to take care of. + +Generally, the old functionality of Scala 2.7 collections has been left in place. Some features have been deprecated, which means they will removed in some future release. You will get a _deprecation warning_ when you compile code that makes use of these features in Scala 2.8. In a few places deprecation was unfeasible, because the operation in question was retained in 2.8, but changed in meaning or performance characteristics. These cases will be flagged with _migration warnings_ when compiled under 2.8. To get full deprecation and migration warnings with suggestions how to change your code, pass the `-deprecation` and `-Xmigration` flags to `scalac` (note that `-Xmigration` is an extended option, so it starts with an `X`.) You can also pass the same options to the `scala` REPL to get the warnings in an interactive session. Example: + + >scala -deprecation -Xmigration + Welcome to Scala version 2.8.0.final + Type in expressions to have them evaluated. + Type :help for more information. + scala> val xs = List((1, 2), (3, 4)) + xs: List[(Int, Int)] = List((1,2), (3,4)) + scala> List.unzip(xs) + :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) + List.unzip(xs) + ^ + res0: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> xs.unzip + res1: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> val m = xs.toMap + m: scala.collection.immutable.Map[Int,Int] = Map((1,2), (3,4)) + scala> m.keys + :8: warning: method keys in trait MapLike has changed semantics: + As of 2.8, keys returns Iterable[A] rather than Iterator[A]. + m.keys + ^ + res2: Iterable[Int] = Set(1, 3) + +There are two parts of the old libraries which have been replaced wholesale, and for which deprecation warnings were not feasible. + +1. The previous `scala.collection.jcl` package is gone. This package tried to mimick some of the Java collection library design in Scala, but in doing so broke many symmetries. Most people who wanted Java collections bypassed `jcl` and used `java.util` directly. Scala 2.8 offers automatic conversion mechanisms between both collection libraries in the [JavaConversions]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.md) object which replaces the `jcl` package. +2. Projections have been generalized and cleaned up and are now available as views. It seems that projections were used rarely, so not much code should be affected by this change. + +So, if your code uses either `jcl` or projections there might be some minor rewriting to do. + diff --git a/ko/overviews/collections/overview.md b/ko/overviews/collections/overview.md new file mode 100644 index 0000000000..3d9ba47f82 --- /dev/null +++ b/ko/overviews/collections/overview.md @@ -0,0 +1,142 @@ +--- +layout: overview-large +title: 가변성 컬렉션과 불변성 컬렉션 + +disqus: true + +partof: collections +num: 2 +language: ko +--- + +스칼라 컬렉션에서는 불변성(immutable)인 컬렉션과 가변성(mutable)인 컬렉션을 +체계적으로 구분한다. _가변(mutable)_ 컬렉션은 변경하거나 확장이 가능하다. 즉, +부작용을 통해 원소를 변경하거나 추가하거나 삭제할 수 있다. 반대로 _불변(immutable)_ +컬렉션은 결코 변화되지 않는다. 원소 삭제, 추가, 변경을 모방하는 연산이 있긴 하지만, +그런 경우 새로운 컬렉션이 반환될 뿐 원래의 컬렉션은 변하지 않는다. + + +All collection classes are found in the package `scala.collection` or +one of its sub-packages `mutable`, `immutable`, and `generic`. Most +collection classes needed by client code exist in three variants, +which are located in packages `scala.collection`, +`scala.collection.immutable`, and `scala.collection.mutable`, +respectively. Each variant has different characteristics with respect +to mutability. + +A collection in package `scala.collection.immutable` is guaranteed to +be immutable for everyone. Such a collection will never change after +it is created. Therefore, you can rely on the fact that accessing the +same collection value repeatedly at different points in time will +always yield a collection with the same elements. + +A collection in package `scala.collection.mutable` is known to have +some operations that change the collection in place. So dealing with +mutable collection means you need to understand which code changes +which collection when. + +A collection in package `scala.collection` can be either mutable or +immutable. For instance, [collection.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html) +is a superclass of both [collection.immutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html) +and +[collection.mutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/mutable/IndexedSeq.html) +Generally, the root collections in +package `scala.collection` define the same interface as the immutable +collections, and the mutable collections in package +`scala.collection.mutable` typically add some side-effecting +modification operations to this immutable interface. + +The difference between root collections and immutable collections is +that clients of an immutable collection have a guarantee that nobody +can mutate the collection, whereas clients of a root collection only +promise not to change the collection themselves. Even though the +static type of such a collection provides no operations for modifying +the collection, it might still be possible that the run-time type is a +mutable collection which can be changed by other clients. + +By default, Scala always picks immutable collections. For instance, if +you just write `Set` without any prefix or without having imported +`Set` from somewhere, you get an immutable set, and if you write +`Iterable` you get an immutable iterable collection, because these +are the default bindings imported from the `scala` package. To get +the mutable default versions, you need to write explicitly +`collection.mutable.Set`, or `collection.mutable.Iterable`. + +A useful convention if you want to use both mutable and immutable +versions of collections is to import just the package +`collection.mutable`. + + import scala.collection.mutable + +Then a word like `Set` without a prefix still refers to an an immutable collection, +whereas `mutable.Set` refers to the mutable counterpart. + +The last package in the collection hierarchy is `collection.generic`. This +package contains building blocks for implementing +collections. Typically, collection classes defer the implementations +of some of their operations to classes in `generic`. Users of the +collection framework on the other hand should need to refer to +classes in `generic` only in exceptional circumstances. + +For convenience and backwards compatibility some important types have +aliases in the `scala` package, so you can use them by their simple +names without needing an import. An example is the `List` type, which +can be accessed alternatively as + + scala.collection.immutable.List // that's where it is defined + scala.List // via the alias in the scala package + List // because scala._ + // is always automatically imported + +Other types so aliased are +[Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html), [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html), [Stream](http://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html), [Vector](http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/current/scala/collection/mutable/StringBuilder.html), and [Range](http://www.scala-lang.org/api/current/scala/collection/immutable/Range.html). + +The following figure shows all collections in package +`scala.collection`. These are all high-level abstract classes or traits, which +generally have mutable as well as immutable implementations. + +[]({{ site.baseurl }}/resources/images/collections.png) + +The following figure shows all collections in package `scala.collection.immutable`. + +[]({{ site.baseurl }}/resources/images/collections.immutable.png) + +And the following figure shows all collections in package `scala.collection.mutable`. + +[]({{ site.baseurl }}/resources/images/collections.mutable.png) + +(All three figures were generated by Matthias at decodified.com). + +## An Overview of the Collections API ## + +The most important collection classes are shown in the figures above. There is quite a bit of commonality shared by all these classes. For instance, every kind of collection can be created by the same uniform syntax, writing the collection class name followed by its elements: + + Traversable(1, 2, 3) + Iterable("x", "y", "z") + Map("x" -> 24, "y" -> 25, "z" -> 26) + Set(Color.red, Color.green, Color.blue) + SortedSet("hello", "world") + Buffer(x, y, z) + IndexedSeq(1.0, 2.0) + LinearSeq(a, b, c) + +The same principle also applies for specific collection implementations, such as: + + List(1, 2, 3) + HashMap("x" -> 24, "y" -> 25, "z" -> 26) + +All these collections get displayed with `toString` in the same way they are written above. + +All collections support the API provided by `Traversable`, but specialize types wherever this makes sense. For instance the `map` method in class `Traversable` returns another `Traversable` as its result. But this result type is overridden in subclasses. For instance, calling `map` on a `List` yields again a `List`, calling it on a `Set` yields again a `Set` and so on. + + scala> List(1, 2, 3) map (_ + 1) + res0: List[Int] = List(2, 3, 4) + scala> Set(1, 2, 3) map (_ * 2) + res0: Set[Int] = Set(2, 4, 6) + +This behavior which is implemented everywhere in the collections libraries is called the _uniform return type principle_. + +Most of the classes in the collections hierarchy exist in three variants: root, mutable, and immutable. The only exception is the Buffer trait which only exists as a mutable collection. + +In the following, we will review these classes one by one. + diff --git a/ko/overviews/collections/performance-characteristics.md b/ko/overviews/collections/performance-characteristics.md new file mode 100644 index 0000000000..e621a0c91a --- /dev/null +++ b/ko/overviews/collections/performance-characteristics.md @@ -0,0 +1,85 @@ +--- +layout: overview-large +title: Performance Characteristics + +disqus: true + +partof: collections +num: 12 +language: ko +--- + +The previous explanations have made it clear that different collection types have different performance characteristics. That's often the primary reason for picking one collection type over another. You can see the performance characteristics of some common operations on collections summarized in the following two tables. + +Performance characteristics of sequence types: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| **immutable** | | | | | | | | +| `List` | C | C | L | L | C | L | - | +| `Stream` | C | C | L | L | C | L | - | +| `Vector` | eC | eC | eC | eC | eC | eC | - | +| `Stack` | C | C | L | L | C | C | L | +| `Queue` | aC | aC | L | L | L | C | - | +| `Range` | C | C | C | - | - | - | - | +| `String` | C | L | C | L | L | L | - | +| **mutable** | | | | | | | | +| `ArrayBuffer` | C | L | C | C | L | aC | L | +| `ListBuffer` | C | L | L | L | C | C | L | +|`StringBuilder`| C | L | C | C | L | aC | L | +| `MutableList` | C | L | L | L | C | C | L | +| `Queue` | C | L | L | L | C | C | L | +| `ArraySeq` | C | L | C | C | - | - | - | +| `Stack` | C | L | L | L | C | L | L | +| `ArrayStack` | C | L | C | C | aC | L | L | +| `Array` | C | L | C | C | - | - | - | + +Performance characteristics of set and map types: + +| | lookup | add | remove | min | +| -------- | ---- | ---- | ---- | ---- | +| **immutable** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `TreeSet`/`TreeMap`| Log | Log | Log | Log | +| `BitSet` | C | L | L | eC1| +| `ListMap` | L | L | L | L | +| **mutable** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `WeakHashMap` | eC | eC | eC | L | +| `BitSet` | C | aC | C | eC1| +| `TreeSet` | Log | Log | Log | Log | + +Footnote: 1 Assuming bits are densely packed. + +The entries in these two tables are explained as follows: + +| | | +| --- | ---- | +| **C** | The operation takes (fast) constant time. | +| **eC** | The operation takes effectively constant time, but this might depend on some assumptions such as maximum length of a vector or distribution of hash keys.| +| **aC** | The operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken. | +| **Log** | The operation takes time proportional to the logarithm of the collection size. | +| **L** | The operation is linear, that is it takes time proportional to the collection size. | +| **-** | The operation is not supported. | + +The first table treats sequence types--both immutable and mutable--with the following operations: + +| | | +| --- | ---- | +| **head** | Selecting the first element of the sequence. | +| **tail** | Producing a new sequence that consists of all elements except the first one. | +| **apply** | Indexing. | +| **update** | Functional update (with `updated`) for immutable sequences, side-effecting update (with `update` for mutable sequences. | +| **prepend**| Adding an element to the front of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | +| **append** | Adding an element and the end of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | +| **insert** | Inserting an element at an arbitrary position in the sequence. This is only supported directly for mutable sequences. | + +The second table treats mutable and immutable sets and maps with the following operations: + +| | | +| --- | ---- | +| **lookup** | Testing whether an element is contained in set, or selecting a value associated with a key. | +| **add** | Adding a new element to a set or key/value pair to a map. | +| **remove** | Removing an element from a set or a key from a map. | +| **min** | The smallest element of the set, or the smallest key of a map. | + diff --git a/ko/overviews/collections/seqs.md b/ko/overviews/collections/seqs.md new file mode 100644 index 0000000000..815832bc2c --- /dev/null +++ b/ko/overviews/collections/seqs.md @@ -0,0 +1,102 @@ +--- +layout: overview-large +title: 열 트레잇 Seq, IndexedSeq, LinearSeq + +disqus: true + +partof: collections +num: 5 +language: ko +--- + +[Seq(열)](http://www.scala-lang.org/api/current/scala/collection/Seq.html) 트레잇은 순서가 있는 열을 표현한다. +열이란 `length`가 있고 원소들이 고정된 첨자로 표현되는 위치를 가지고 있는 반복가능한 객체를 말한다. 첨자는 `0`부터 시작한다. + +표에 열이 제공하는 연산이 정리되어 있다. 각 연산은 다음과 같이 분류할 수 있다. + +* **참조와 길이** 연산으로 `apply`, `isDefinedAt`, `length`, `indices`, `lengthCompare`가 있다. `Seq`에 있어, `apply` 연산은 첨자에 의해 원소를 참조하는 것이다. 따라서 `Seq[T]` 타입의 열은 `Int` 인자(첨자)를 받아서 열의 원소인 타입 `T` 객체를 반환하는 부분함수로 볼 수 있다. 즉, `Seq[T]`는 `PartialFunction[Int, T]`를 구현한다. 열의 원소에는 0부터 열의 `length`-1 까지 첨자가 부여된다. `length` 메소드는 일반적인 컬렉션의 `size`에 대한 별명이다. `lengthCompare` 메소드는 두 열(무한열인 경우도 포함됨)의 길이를 비교해준다. +* **첨자 검색 연산** `indexOf`, `lastIndexOf`, `indexofSlice`, `lastIndexOfSlice`, `indexWhere`, `lastIndexWhere`, `segmentLength`, `prefixLength`는 주어진 값과 같거나 주어진 술어와 일치하는 원소의 첨자를 반환한다. +* **덧붙임** 연산 `+:`, `:+`, `padTo`는 열의 맨 앞이나 뒤에 원소를 추가해 만들어지는 새 열을 반환한다. +* **갱신** 연산 `updated`, `patch`는 원래의 열의 일부 원소를 다른 값으로 대치한 새 열을 반환한다. +* **정렬** 연산 `sorted`, `sortWith`, `sortBy`는 여러 기준에 의해 열을 정렬한다. +* **반전** 연산 `reverse`, `reverseIterator`, `reverseMap`은 열의 원소를 역순으로 처리하거나 내어 놓는다. +* **비교** `startsWith`, `endsWith`, `contains`, `containsSlice`, `corresponds`는 두 열을 연관짓거나 열 안에서 원소를 찾는다. +* **중복집합(Multiset)** 연산인 `intersect`, `diff`, `union`, `distinct`는 두 열의 원소에 대해 집합과 비슷한 연산을 수행하거나, 중복을 제거한다. + +열이 변경 가능하다면 추가로 부작용을 사용하는 `update` 메소드를 제공한다. 이를 사용해 열의 원소를 변경할 수 있다. 스칼라에서 항상 그렇듯이 `seq(idx) = elem`는 단지 `seq.update(idx, elem)`를 짧게 쓴 것 뿐이다. 따라서 `update`를 정의하면 공짜로 대입 문법을 사용할 수 있게 된다. `update`와 `updated`가 다름에 유의하라. `update`는 어떤 열의 원소를 그 자리(새 열을 만들지 않고 열 자체를 갱신)에서 변경하며 변경 가능한 열에서만 사용할 수 있다. `updated`는 모든 열에서 사용 가능하며 항상 원래의 열은 그대로 두고 새로운 열을 반환한다. + +### Seq 클래스의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **참조와 길이:** | | +| `xs(i)` |(명시적으로 `xs apply i`라고 쓸수도 있음)`xs`에서 첨자 `i` 번째에 있는 원소를 반환한다.| +| `xs isDefinedAt i` |`i`가 `xs.indices` 안에 있는지 여부를 반환한다.| +| `xs.length` |열의 길이를 반환한다(`size`와 동일).| +| `xs.lengthCompare ys` |`xs`가 `ys`보다 짧으면 `-1`, 길면 `+1`, 같은 길이이면 `0`을 반환한다. 길이가 무한한 경우라도 동작한다(현재는 인자로 정수를 받아 열의 길이와 비교하는 것으로 바뀜)| +| `xs.indices` |`xs`의 첨자 범위를 반환한다. `0`부터 `xs.length - 1`까지 범위를 반환한다.| +| **첨자 검색:** | | +| `xs indexOf x` |`xs`에서 `x`와 같은 첫번째 원소의 첨자를 반환한다(몇 가지 변형이 존재한다).| +| `xs lastIndexOf x` |`xs`에서 `x`와 같은 마지막 원소의 첨자를 반환한다(몇 가지 변형이 존재한다).| +| `xs indexOfSlice ys` |`xs`에서 해당 위치에서부터 시작하는 슬라이스(slice,부분열)이 `ys`와 같은 첫번째 위치를 반환한다.| +| `xs lastIndexOfSlice ys` |`xs`에서 해당 위치에서부터 시작하는 슬라이스(slice,부분열)이 `ys`와 같은 마지막 위치를 반환한다.| +| `xs indexWhere p` |`xs`에서 `p`를 만족하는 첫번째 원소의 첨자를 반환한다(몇 가지 변형이 존재한다).| +| `xs segmentLength (p, i)`|`xs(i)`부터 시작해서 연속적으로 `p`를 만족하는 가장 긴 열의 길이를 구한다.| +| `xs prefixLength p` |`xs`의 맨 앞에서부터 시작해서 연속적으로 `p`를 만족하는 가장 긴 열의 길이를 구한다.| +| **덧붙임:** | | +| `x +: xs` |`xs`의 앞에 `x`를 붙여서 만들어진 새 열을 반환한다.| +| `xs :+ x` |`xs`의 뒤에 `x`를 붙여서 만들어진 새 열을 반환한다.| +| `xs padTo (len, x)` |`xs`의 뒤에 `x`를 전체 길이가 `len`이 될 때까지 붙여서 만들어진 새 열을 반환한다.| +| **갱신:** | | +| `xs patch (i, ys, r)` |`xs`에서 첨자 `i` 번째부터 `r`개 만큼의 원소를 `ys`의 원소로 바꿔치기 해서 얻은 새 열을 반환한다.| +| `xs updated (i, x)` |`xs`에서 첨자 `i`에 있는 원소를 `x`로 바꾼 새 열을 반환한다.| +| `xs(i) = x` |(또는 명시적으로 `xs.update(i, x)`라고 쓴다. `mutable.Seq`에서만 사용 가능하다). `xs`의 `i` 번째 원소를 `x`로 변경한다.| +| **정렬:** | | +| `xs.sorted` |`xs`의 원소를 원소 타입의 표준적인 순서를 사용해 정렬한 결과 열을 반환한다.| +| `xs sortWith lt` |`xs`의 원소를 `lt`를 비교 연산으로 사용해 정렬한 결과 열을 반환한다.| +| `xs sortBy f` |`xs`의 원소를 정렬한 결과 열을 반환한다. 비교시 두 원소에 각각 `f`를 적용한 다음 그 결과값을 서로 비교한다.| +| **반전:** | | +| `xs.reverse` |`xs`의 원소를 역순으로 나열한 열을 반환한다.| +| `xs.reverseIterator` |`xs`의 원소를 역순으로 내어 놓는 이터레이터이다.| +| `xs reverseMap f` |`xs`의 원소를 역순으로 순회하면서 `f`를 적용해 나온 결과 값으로 이루어진 열을 반환한다.| +| **비교:** | | +| `xs startsWith ys` |`xs`가 열 `ys`로 시작하는지 여부를 반환한다(몇 가지 변형이 존재한다).| +| `xs endsWith ys` |`xs`가 열 `ys`로 끝나는지 여부를 반환한다(몇 가지 변형이 존재한다).| +| `xs contains x` |`xs`에 `x`원소가 존재하는지 여부를 반환한다.| +| `xs containsSlice ys` |`xs`에 `ys`과 같은 부분열이 존재하는지 여부를 반환한다.| +| `(xs corresponds ys)(p)` |`xs`와 `ys`에서 서로 대응하는 원소가 술어 `p`를 만족하는지 여부를 반환한다.| +| **중복집합 연산:** | | +| `xs intersect ys` |`xs`와 `ys`의 중복 교집합 연산 결과인 열을 반환한다. `xs`에서의 원소 순서를 유지한다.| +| `xs diff ys` |`xs`와 `ys`의 중복 차집합 연산 결과인 열을 반한한다. `xs`에서의 원소 순서를 유지한다.| +| `xs union ys` |중복 합집합 연산 결과인 열을 반환한다. `xs ++ ys`와 같다.| +| `xs.distinct` |`xs`에서 중복을 제거한(중복된 원소는 하나만 남기고, 하나만 있는 원소는 그대로 둔) 부분열을 반환한다.| + +트레잇 [열(Seq)](http://www.scala-lang.org/api/current/scala/collection/Seq.html)에는 [선형열(LinearSeq)](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)과 [첨자열(IndexedSeq)](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html) 두 하위 트레잇이 있다. 이 두 트레잇에 새로 추가된 메소드는 없지만, 성능상 차이가 나는 특성을 각각 제공한다. 선형 열은 효율적인 `head`와 `tail` 연산이 제공되는 반면, 첨자열은 효율적인 `apply`, `length`, 그리고 (변경 가능한 경우) `update` 연산을 제공한다. 자주 사용되는 선형 열로는 `scala.collection.immutable.List`, `scala.collection.immutable.Stream`이 있다. 자주 사용되는 첨자열로는 `scala.Array`와 `scala.collection.mutable.ArrayBuffer`를 들 수 있다. `Vector(벡터)` 클래스는 첨자열과 선형열 사이에서 흥미로운 절충안을 제공한다. 벡터는 상수 시간에 첨자에 의한 참조가 가능하며 선형 억세스도 상수시간에 가능하다. 이로 인해 첨자 참조와 선형 억세스가 동시에 사용되는 억세스 패턴인 경우 벡터가 좋은 선택일 수 있다. 벡터에 대해서는 [나중에](#vectors) 다룰 것이다. + +### 버퍼 ### + +변경 가능한 열의 하위 범주 중 중요한 것은 `Buffer(버퍼)`이다. 버퍼는 기존 원소를 변경할 수 있을 뿐 아니라, 원소를 추가, 삭제 하거나, 버퍼의 마지막에 효율적으로 원소를 추가할 수 있다. 버퍼가 추가로 지원하는 주요 메소드로는 원소를 끝에 추가하기 위한 `+=`, `++=`, 원소를 앞에 추가하기 위한 `+=:` and `++=:`, 원소를 삽입하기 위한 `insert`와 `insertAll`, 그리고 원소를 제거하기 위한 `remove`, `-=`가 있다. 이 연산을 아래 표에 정리해 두었다. + +자주 사용되는 버퍼 구현을 두 가지 들자면 `ListBuffer`와 `ArrayBuffer`가 있다. 이름이 암시하든 `ListBuffer`는 `List`에 의해 뒷받침되며 원소를 효율적으로 `List`로 변환할 수 있다. 반면 `ArrayBuffer`는 배열에 의해 뒷받침되며 배열로 쉽게 변환 가능하다. + +#### 버퍼 클래스의 연산 #### + +| 사용법 | 하는 일 | +| ------ | ------ | +| **추가:** | | +| `buf += x` |원소 `x`를 버퍼의 끝에 추가하고 `buf` 자신을 반환한다.| +| `buf += (x, y, z)` |여러 원소를 버퍼의 끝에 추가한다.| +| `buf ++= xs` |`xs`의 모든 원소를 버퍼의 끝에 추가한다.| +| `x +=: buf` |`x`를 버퍼의 앞에 추가한다.| +| `xs ++=: buf` |`xs`의 모든 원소를 버퍼의 앞에 추가한다.| +| `buf insert (i, x)` |원소 `x`를 버퍼의 첨자 `i` 번째 원소 앞에 삽입한다.| +| `buf insertAll (i, xs)` |`xs`의 모든 원소를 버퍼의 첨자 `i`번째 원소 앞에 삽입한다.| +| **제거:** | | +| `buf -= x` |원소 `x`를 버퍼에서 제거한다. (중복이 있는 경우 맨 첫 `x`만을 제거한다.)| +| `buf remove i` |첨자 `i` 번째에 이는 원소를 버퍼에서 제거한다.| +| `buf remove (i, n)` |첨자 `i`번째에 있는 원소 부터 `n`개의 원소를 버퍼에서 제거한다.| +| `buf trimStart n` |처음 `n` 개의 원소를 버퍼에서 제거한다.| +| `buf trimEnd n` |마지막 `n` 개의 원소를 버퍼에서 제거한다.| +| `buf.clear()` |모든 원소를 버퍼에서 제거한다.| +| **복사:** | | +| `buf.clone` |`buf` 같은 원소를 포함하고 있는 새로운 버퍼를 만든다.| diff --git a/ko/overviews/collections/sets.md b/ko/overviews/collections/sets.md new file mode 100644 index 0000000000..aee1bf3267 --- /dev/null +++ b/ko/overviews/collections/sets.md @@ -0,0 +1,151 @@ +--- +layout: overview-large +title: 집합(Set) + +disqus: true + +partof: collections +num: 6 +language: ko +--- + +`Set(집합)`은 `Iterable(반복가능)` 중에서 중복 원소가 없는 것이다. 일반적인 집합의 연산은 다음 표에 정리되어 있고, 변경 가능한 집합의 연산은 그 다음에 오는 표에 정리되어 있다. 연산들은 다음과 같은 범주에 들어간다. + +* **검사** 연산으로 `contains`, `apply`, `subsetOf`가 있다. `contains` 메소드는 집합에 원소가 속해 있는지를 검사한다. 집합에 있어 `apply` 메소드는 `contains`과 동일하다. 따라서 `set(elem)`과 `set contains elem`는 같다. 따라서 집합을 원소가 포함되어있는지를 검사하는 검사 함수로 사용 가능하다. + +예를 들면 다음과 같다. + + + val fruit = Set("apple", "orange", "peach", "banana") + fruit: scala.collection.immutable.Set[java.lang.String] = + Set(apple, orange, peach, banana) + scala> fruit("peach") + res0: Boolean = true + scala> fruit("potato") + res1: Boolean = false + + +* **추가** 연산에는 `+` and `++`가 있다. 이들은 집합에 하나 이상의 원소를 추가한 새 집합을 만들어 낸다. +* **제거** 연산인 `-`, `--`는 집합에서 하나 이상의 원소를 제거한 새 집합을 만들어 낸다. +* **집합 연산**으로 합집합, 교집합, 차집합이 있다. 각 연산은 두가지 버전이 존재한다. 하나는 영어문자를 사용한 것이고, 다른 하나는 기호로 된 이름을 사용한 것이다. 영문자 버전은 `intersect`, `union`, `diff`이며, 각각 순서대로 `&`, `|`, `&~`이란 기호 이름과 대응된다. 사실은 `Traversable`에서 상속받은 `++`도 `union` 또는 `|`에 대한 별칭이라 생각할수 있다. 다만 차이가 나는 것은 `++`는 `Traversable`을 매개변수로 받을 수 있지만, `union`과 `|`에는 집합만을 허용한다는 점이다. + +### 집합(Set)의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **검사:** | | +| `xs contains x` |`x`가 `xs`의 원소인지 여부를 반환한다. | +| `xs(x)` |`xs contains x`와 같다. | +| `xs subsetOf ys` |`xs`가 `ys`의 부분집합인지 여부를 반환한다. | +| **추가:** | | +| `xs + x` |`xs`의 모든 원소와 `x`를 원소로 하는 새 집합을 반환한다.| +| `xs + (x, y, z)` |`xs`의 모든 원소와 덧붙인 모든 값들을 원소로 하는 새 집합을 반환한다.| +| `xs ++ ys` |`xs`의 모든 원소와 `ys`의 모든 원소를 원소로 하는 새 집합을 반환한다.| +| **제거:** | | +| `xs - x` |`xs`의 모든 원소 중 `x`를 제거한 나머지를 원소로 하는 새 집합을 반환한다.| +| `xs - (x, y, z)` |`xs`의 모든 원소 중 열거한 원소들을 제외한 나머지를 원소로 하는 새 집합을 반환한다.| +| `xs -- ys` |`xs`의 모든 원소 중 `ys`의 원소들을 제거한 나머지를 원소로 하는 새 집합을 반환한다.| +| `xs.empty` |`xs`와 동일한 타입의 빈 집합을 반환한다. | +| **이항 연산:** | | +| `xs & ys` |`xs`와 `ys`의 교집합 연산이다. | +| `xs intersect ys` |`xs & ys`와 같다. | +| xs | ys |`xs`와 `ys`의 합집합 연산이다. | +| `xs union ys` |xs | ys와 같다.. | +| `xs &~ ys` |`xs`와 `ys`의 차집합 연산이다. | +| `xs diff ys` |`xs &~ ys`와 같다.. | + +변경 가능한 집합은 원소를 추가, 삭제, 변경하는 연산을 추가로 제공한다. 아래 표에 정리되어 있다. + +### 변경 가능 집합(mutable.Set)의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추가:** | | +| `xs += x` |`xs`에 원소 `x`를 부작용을 사용해 추가하고, `xs` 자신을 반환한다.| +| `xs += (x, y, z)` |`xs`에 지정된 원소들을 부작용을 사용해 추가하고, `xs` 자신을 반환한다.| +| `xs ++= ys` |`xs`에 `ys`의 원소들을 부작용을 사용해 추가하고, `xs` 자신을 반환한다.| +| `xs add x` |`xs`에 원소 `x`를 부작용을 사용해 추가하되, `x`가 집합에 이미 포함되어 있었다면 거짓을, 그렇지 않았다면 참을 반환한다.| +| **제거:** | | +| `xs -= x` |`xs`에서 원소 `x`를 부작용을 사용해 제거하고, `xs` 자신을 반환한다.| +| `xs -= (x, y, z)` |`xs`에서 지정된 원소들을 부작용을 사용해 제거하고, `xs` 자신을 반환한다.| +| `xs --= ys` |`xs`에서 `ys`의 원소들을 부작용을 사용해 제거하고, `xs` 자신을 반환한다.| +| `xs remove x` |`xs`에서 원소 `x`를 부작용을 사용해 제거하되, `x`가 집합에 이미 포함되어 있었다면 참을, 그렇지 않았다면 거짓을 반환한다.| +| `xs retain p` |`xs`에서 술어 `p`를 만족하는 원소를 남기고 나머지를 제거한다.| +| `xs.clear()` |`xs`의 모든 원소를 제거한다.| +| **변경:** | | +| `xs(x) = b` |(명시적으로 `xs.update(x, b)`라고 쓸 수 있음) `b`가 `true`면 `x`를 `xs`에 추가하고, 그렇지 않으면 `x`를 `xs`에서 제거한다.| +| **복제:** | | +| `xs.clone` |`xs`와 같은 원소를 포함하는 새 변경 가능한 집합을 만든다.| + +변경 불가능한 집합과 마찬가지로 변경가능한 집합도 원소 추가를 위한 `+`, `++`와 원소 제거를 위한 `-`, `--` 연산을 제공한다. 하지만 이 연산들은 집합을 복사하기 때문에 변경 가능한 집합에서는 잘 사용되지 않는다. 더 효율적인 방식으로 `+=`, `-=`가 있다. `s += elem`는 `elem`을 집합 `s`에 부작용을 통해 추가하며, 결과로 집합 자신을 반환한다. 마찬가지로, `s -= elem`은 원소 `elem`을 집합에서 제거하고, 집합 자신을 결과로 반환한다. `+=`와 `-=`와 별개로 반복자나 순회가능 클래스의 원소를 한꺼번에 추가, 삭제하는 `++=`와 `--=` 연산도 있다. + +메소드 이름 `+=`와 `-=`는 변경 가능하거나 불가능한 집합 모두에 아주 비슷한 코드를 사용할 수 있음을 의미한다. 먼저 변경 불가능한 집합 `s`를 사용하는 REPL 실행예를 보자. + + scala> var s = Set(1, 2, 3) + s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + scala> s -= 2 + scala> s + res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) + +여기서 타입이 `immutable.Set`인 `var`에 `+=`와 `-=` 연산을 적용했다. `s += 4` 식은 `s = s + 4`을 줄인 것이다. 따라서 집합 `s`에 대해 추가 메소드 `+`가 호출 된 다음 이 결과가 다시 변수 `s`에 대입된다. 이제 이와 비슷한 변경 가능한 집합의 동작을 살펴보자. + + + scala> val s = collection.mutable.Set(1, 2, 3) + s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + res3: s.type = Set(1, 4, 2, 3) + scala> s -= 2 + res4: s.type = Set(1, 4, 3) + +최종 결과는 앞의 예와 아주 비슷하다. `Set(1, 2, 3)`에서 시작해서 `Set(1, 3, 4)`으로 끝난다. 비록 사용된 명령은 앞에서와 같지만 실제 하는 일은 많이 다른 것이다. `s += 4`는 이제 변경 가능한 집합 `s`의 `+=` 메소드를 호출해 집합의 내부 상태를 변경한다. 마찬가지로 `s -= 2` 또한 같은 집합의 `-=` 메소드를 호출한다. + +이 둘 간의 차이는 중요한 원칙을 보여준다. 바로 때로 `val`에 저장된 변경 가능한 컬렉션을 `var`에 저장된 변경 불가능한 것으로 바꾸거나, 그 _역으로_ 바꾸는 것이 가능하다는 사실이다. 외부에서 새 컬렉션이 만들어졌거나 내용이 부작용을 통해 변경되었는지를 관찰할 수 있는 동일 객체에 대한 다른 이름의 참조(alias)가 존재하지 않는 한 이 방식은 잘 동작한다. + +변경 가능한 집합은 또한 `+=`와 `-=`의 변형으로 `add`와 `remove`도 제공한다. 차이는 `add`와 `remove`는 연산에 집합에 작용했는지 여부를 알려주는 불린 값을 돌려준다는 점에 있다. + +현재 변경 가능 집합의 기본 구현은 해시 테이블을 사용해 원소를 저장한다. 변경 불가능한 집합의 기본 구현은 원소의 갯수에 따라 다른 표현방식을 사용한다. 빈 집합은 싱글턴 객체로 표현된다. 원소가 4개 이하인 집합은 모든 원소 객체를 필드로 저장하는 객체로 표현한다. 그보다 큰 변경 불가능 집합은 [해시 트라이(hash trie)](#hash-tries)로 구현되어 있다. + +이런 구현의 차이로 인해 더 작은 크기(4 이하)인 경우 변경 불가능한 집합이 변경 가능한 집합보다 더 작고 효율적이다. 따라서 크기가 작은 집합을 예상한다면 변경 불가능한 집합을 사용하도록 하라. + +집합에는 `SortedSet(정렬된 집합)`과 `BitSet(비트집합)`이 있다. + +### 정렬된 집합 ### + +[SortedSet(정렬된 집합)](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html)은 주어진 순서에 따라 원소를 내어놓는(`iterator`나 `foreach` 사용) 집합이다(순서는 집합을 만들 때 자유롭게 결정할 수 있다). [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html)의 기본 구현은 어떤 노드의 왼쪽 하위 트리에 속한 모든 원소가 오른쪽 하위 트리에 속한 모든 원소보다 작다는 불변조건(invariant)을 만족시키는 순서가 있는 이진 트리이다. 따라서 간단한 중위순회(in order traversal)를 통해 트리의 모든 원소를 증가하는 순서로 반환할 수 있다. 스칼라의 클래스 [immutable.TreeSet(트리집합)](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html)은 이 불변조건을 유지하면서 동시에 _균형잡힌(balanced)_ 특성을 유지하기 위해 _적-흑(red-black)_ 트리를 구현한다. 균형이 잡힌 트리는 루트(root) 노드로부터 리프(leaf) 노드에 이르는 길이가 1 이하로 차이가 나는 경우를 말한다. + +빈 [TreeSet(트리집합)](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html)을 만들려면 우선 원하는 순서를 지정해야 한다. + + scala> val myOrdering = Ordering.fromLessThan[String](_ > _) + myOrdering: scala.math.Ordering[String] = ... + +그리고 나서 이 순서를 사용해 빈 트리를 다음과 같이 만든다. + + scala> TreeSet.empty(myOrdering) + res1: scala.collection.immutable.TreeSet[String] = TreeSet() + +트리의 순서를 지정하지 않는 대신 원소의 타입을 지정해 빈 트리를 만들 수도 있다. 그렇게 하면 원소의 타입에 따른 기본 순서를 사용하게 된다. + + scala> TreeSet.empty[String] + res2: scala.collection.immutable.TreeSet[String] = TreeSet() + +트리 집합으로부터 (트리를 서로 붙이거나, 걸러내는 등의 방법을 사용해) 새 집합을 만드는 경우, 원래의 집합과 같은 순서를 사용할 것이다. 예를 들면 다음과 같다. + +scala> res2 + ("one", "two", "three", "four") +res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) + +정렬된 집합은 원소의 범위도 지원한다. 예를 들어 `range` 메소드는 지정된 시작 원소로부터 시작해서 지정된 끝 엘리먼트 직전까지의 모든 원소를 반환한다. `from` 메소드는 집합에서의 순서상 지정된 원소와 같거나 큰 모든 원소를 반환한다. 다음은 그 예이다. + + scala> res3 range ("one", "two") + res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) + scala> res3 from "three" + res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) + + +### 비트집합(Bitset) ### + +[BitSet(비트집합)](http://www.scala-lang.org/api/current/scala/collection/BitSet.html)은 음이 아닌 정수 원소들로 이루어진 집합으로, 비트를 한데 묶은(packed) 하나 또는 그 이상의 워드로 되어 있다. [BitSet(비트집합)](http://www.scala-lang.org/api/current/scala/collection/BitSet.html)의 내부 표현은 `Long`의 배열을 사용한다. 첫 `Long`은 0부터 63을 나타내고, 두번째 것은 64부터 127을 나타내는 방식을 사용한다(0부터 127 사이의 수만 표현하는 변경 불가능한 비트셋은 배열을 최적화해서 한두개의 `Long` 필드만을 사용한다). 어떤 원소가 속해 있다면 해당 원소에 대응하는 `Long` 필드내의 비트가 1로 설정되고, 그렇지 않으면 0으로 설정된다. 따라서 비트집합의 크기는 내부에 저장되는 가장 큰 정수의 값에 따라 결정된다. 만약 가장 큰 정수가 `N`이라면 집합의 크기는 `N/64`개의 `Long`워드 또는 `N/8` 바이트이며, 상태정보 저장을 위해 몇 바이트가 추가된다. + +따라서 크기가 작은 정수 원소를 여러개 포함하는 경우 비트집합을 사용하면 다른 집합에 비해 작은 크기로 가능하다. 비트 집합의 또 다른 잇점은 `contains`를 사용한 포함관계 검사나 `+=`, `-=` 등을 사용한 원소 추가/제거가 모두 아주 효율적이라는 점이다. + +번역: 오현석(enshahar@gmail.com) \ No newline at end of file diff --git a/ko/overviews/collections/strings.md b/ko/overviews/collections/strings.md new file mode 100644 index 0000000000..726f40827b --- /dev/null +++ b/ko/overviews/collections/strings.md @@ -0,0 +1,27 @@ +--- +layout: overview-large +title: Strings + +disqus: true + +partof: collections +num: 11 +language: ko +--- + +Like arrays, strings are not directly sequences, but they can be converted to them, and they also support all sequence operations on strings. Here are some examples of operations you can invoke on strings. + + scala> val str = "hello" + str: java.lang.String = hello + scala> str.reverse + res6: String = olleh + scala> str.map(_.toUpper) + res7: String = HELLO + scala> str drop 3 + res8: String = lo + scala> str slice (1, 4) + res9: String = ell + scala> val s: Seq[Char] = str + s: Seq[Char] = WrappedString(h, e, l, l, o) + +These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. diff --git a/ko/overviews/collections/trait-iterable.md b/ko/overviews/collections/trait-iterable.md new file mode 100644 index 0000000000..3b89b814de --- /dev/null +++ b/ko/overviews/collections/trait-iterable.md @@ -0,0 +1,67 @@ +--- +layout: overview-large +title: 반복가능(Iterable) 트레잇 + +disqus: true + +partof: collections +num: 4 +language: ko +--- + +컬렉션 계층 구조의 맨 위에서 두번째에 있는 것이 `Iterable`(반복가능)이다. 이 트레잇에 있는 모든 메소드는 추상 메소드 `iterator`를 기반으로 정의되어 있다. 이 추상 메소드는 컬렉션의 원소를 하나씩 내어 놓는다. 트레잇 `Traversable`의 `foreach`는 `Iterable`에서 `iterator`를 기반으로 정의되어 있다. 다음은 실제 구현이다. + + def foreach[U](f: Elem => U): Unit = { + val it = iterator + while (it.hasNext) f(it.next()) + } + +`Iterable`의 하위 클래스 중 상당수는 이 `foreach` 표준 구현을 재정의(override)하고 있다. 왜냐하면 더 효율적인 구현이 가능하기 때문이다. `foreach`가 `Traversable`의 모든 메소드 구현에 사용됨을 기억하라. 따라서, 성능을 진지하게 고려해야 한다. + +`Iterable`에는 반복자를 반환하는 메소드가 두 개 더 있다. 이들은 각각 `grouped`와 `sliding`이다. 그러나 이 반복자들은 원래의 컬렉션의 한 원소만을 반환하는 것이 아니고, 전체 원소의 부분 열을 반환한다. 각 메소드는 이런 부분 열의 최대 크기를 인자로 받는다. `grouped` 메소드는 원소를 일정한 "덩어리(chunked)" 단위로 반환하는 반면, `sliding`은 원소에 대한 "미닫이 창(sliding window)"을 반환한다. 아래 REPL 수행 예를 보면 이 둘 사이의 차이를 명확히 알 수 있을 것이다. + + scala> val xs = List(1, 2, 3, 4, 5) + xs: List[Int] = List(1, 2, 3, 4, 5) + scala> val git = xs grouped 3 + git: Iterator[List[Int]] = non-empty iterator + scala> git.next() + res3: List[Int] = List(1, 2, 3) + scala> git.next() + res4: List[Int] = List(4, 5) + scala> val sit = xs sliding 3 + sit: Iterator[List[Int]] = non-empty iterator + scala> sit.next() + res5: List[Int] = List(1, 2, 3) + scala> sit.next() + res6: List[Int] = List(2, 3, 4) + scala> sit.next() + res7: List[Int] = List(3, 4, 5) + +`Traversable` 트레잇의 메소드 중 이터레이터가 있는 경우에만 효율적으로 구현할 수 있는 메소드 몇 가지를 `Iterable` 트레잇에서 재정의하고 있다. 다음 표에서 이를 요약하였다. + +### Iterable 트레잇의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추상 메소드:** | | +| `xs.iterator` |`iterator`는 `xs`의 모든 원소를 `foreach`가 순회하는 순서대로 하나씩 제공하는 반복자이다.| +| **다른 반복자:** | | +| `xs grouped size` |고정된 크기의 "덩어리"를 컬렉션에서 내어놓는 반복자이다.| +| `xs sliding size` |고정된 크기의 미닫이 창을 내어놓는 반복자이다.| +| **부분 컬렉션:** | | +| `xs takeRight n` |`xs`의 마지막 `n`개의 원소로 이루어진 컬렉션을 반환한다(순서가 없는 컬렉션이라면 임의의 `n`개를 반환한다).| +| `xs dropRight n` |`xs takeRight n`의 결과를 제외한 나머지 컬렉션을 반환한다.| +| **묶기(zip):** | | +| `xs zip ys` |`xs`와 `ys`에서 같은 위치에 있는 원소를 각각 가져와 만든 튜플로 이루어진 컬렉션을 반환한다. 길이가 다르다면 짧은 컬렉션의 원소 갯수 만큼만 반환한다. | +| `xs zipAll (ys, x, y)` |`zip`과 같지만, 길이가 다른 경우 `x`나 `y`를 더 짧은쪽 리스트의 원소가 모자란 경우 대신 사용한다.| +| `xs.zipWithIndex` |`xs`의 원소와 그 위치를 나타내는 첨자를 튜플로 만든 컬렉션을 반환한다.| +| **비교:** | | +| `xs sameElements ys` |`xs`와 `ys`가 같은 순서로 같은 원소를 포함하고 있는지 비교한다.| + +상속 계층에서 Iterable의 하위에는 다음 세 트레잇이 있다. [열(Seq)](http://www.scala-lang.org/docu/files/collections-api/collections_5.html), [집합(Set)](http://www.scala-lang.org/docu/files/collections-api/collections_7.html), [맵(Map)](http://www.scala-lang.org/docu/files/collections-api/collections_10.html)이 그것이다. 이 세 트레잇의 공통점은 모두 다 [부분함수(PartialFunction)](http://www.scala-lang.org/api/current/scala/PartialFunction.html) 트레잇의 `apply`와 `isDefinedAt` 메소드를 정의하고 있다는 것이다. 하지만, 각 트레잇이 [부분함수(PartialFunction)](http://www.scala-lang.org/api/current/scala/PartialFunction.html)를 구현한 방법은 각각 다르다. + +열에서 `apply`는 위치에 따라 첨자를 부여한다. 첨자는 첫번째 원소 `0` 부터 시작한다. 따라서 `Seq(1, 2, 3)(1)`은 `2`를 반환한다. 집합의 경우 `apply`는 포함관계 검사이다. 예를 들어 `Set('a', 'b', 'c')('b')`은 `true`를, `Set()('a')`는 `false`를 반환한다. 마지막으로 맵의 경우 `apply`는 선택(검색)이다. 예를 들어 `Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')`의 결과는 `10`이다. + +다음 글에서는 위 세 컬렉션을 더 자세히 살펴볼 것이다. + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/trait-traversable.md b/ko/overviews/collections/trait-traversable.md new file mode 100644 index 0000000000..aca7d5b83f --- /dev/null +++ b/ko/overviews/collections/trait-traversable.md @@ -0,0 +1,120 @@ +--- +layout: overview-large +title: 순회가능(Traversable) 트레잇 + +disqus: true + +partof: collections +num: 3 +language: ko +--- + +컬렉션 계층의 최상위에는 트레잇 `Traversable(순회가능)`이 있다. 이 트레잇에 +있는 유일한 추상적인 연산이 바로 `foreach`이다. + + def foreach[U](f: Elem => U) + +`Traversable`을 구현하는 컬렉션 클래스는 단지 이 메소드만 정의하면 된다. +다른 메소드는 자동으로 `Traversable`에서 상속된다. + +`foreach` 메소드는 컬렉션의 모든 원소를 차례로 방문하면서 주어진 연산 f를 각 원소에 +적용한다. 이 연산 f의 타입은 `Elem => U`로 `Elem`은 컬렉션의 원소의 +타입이며, `U`는 임의의 결과 타입이다. `f`는 부작용을 위해 호출된다. 따라서 +f가 내놓는 결과값은 `foreach`가 무시한다. + +`Traversable`에는 여러 구체적 메소드가 정의되어 있다. 이들은 아래 표에 나열되어 있다. +각 메소드들은 다음과 같은 분류에 속한다. + +* **병합** 연산 `++`는 두 방문가능한 객체를 함께 이어붙이거나, 어떤 방문가능 객체에 다른 반복자의 모든 원소를 추가한다. +* **맵** 연산인 `map`, `flatMap`, `collect`는 인자로 넘겨진 함수를 컬렉션 원소에 적용한 결과로 이루어진 새 컬렉션을 만들어낸다. +* **변환** 연산인 `toArray`, `toList`, `toIterable`, `toSeq`, `toIndexedSeq`, `toStream`, `toSet`, `toMap`은 `Traversable` +컬렉션을 더 구체적인 데이터 구조로 변환한다. 런타임에 수신자가 이미 변환 결과 컬렉션 타입이었다면, 각 변환 메소드는 수신자를 그대로 반환한다. 예를 들어 리스트에 `toList`를 적용하면 그 리스트 자신이 반환된다. +* **복사** 연산으로 `copyToBuffer`와 `copyToArray`가 있다. 이름이 말하는데로 각각 컬렉션 원소를 버퍼나 배열에 복사한다. +* **크기 정보** 연산 `isEmpty`, `nonEmpty`, `size`, `hasDefiniteSize`: 순회가능한 컬렉션은 유한할 수도 있고, 무한할 수도 있다. +무한한 순회가능한 컬렉션의 예를 들자면 자연수의 스트림 `Stream.from(0)`이 있다. 메소드 `hasDefiniteSize`는 컬렉션이 무한 컬렉션일 가능성이 있는지를 알려준다. `hasDefiniteSize`가 참을 반환하면 컬렉션이 유한하다는 것이 확실하다. 하지만, 컬렉션이 내부 원소를 아직 완전히 계산해 채우지 않은 경우에는 거짓을 반환하기 때문에, 거짓을 반환한다 해도 유한할 수도 있고 무한할 수도 있다. +* **원소 가져오기** 연산 `head`, `last`, `headOption`, `lastOption`, `find`등은 컬렉션의 첫 원소나 마지막 원소를 선택하거나, 조건을 만족하는 첫 원소를 선택한다. 하지만 모든 컬렉션에서 "첫번째"나 "마지막"의 의미가 잘 정의되어 있는 것은 아니라는 점에 유의하라. 예를 들어 해시 집합은 해시값에 따라 원소를 저장하는데, 이 해시값은 매 실행시마다 변할 수 있다. 이런 경우 해시 집합의 +"첫번째" 원소는 프로그램이 실행될 때마다 바뀔 수 있다. 어떤 컬렉션이 항상 같은 순서로 원소를 표시한다면 이를 _순서있다_ 고 한다. 대부분의 컬렉션은 순서가 있으나, 일부(_e.g._ 해시 집합)는 그렇지 않다 -- 이들은 순서를 포기하는 대신 효율을 택한 것이다. 동일한 테스트를 반복하거나, 디버깅을 할 때 때로 순서가 꼭 필요하다. 이 때문에 모든 스칼라 컬렉션 타입에는 순서가 있는 대체물이 반드시 존재한다. 예를 들어 `HashSet`에 순서가 부여된 것은 `LinkedHashSet`이다. +* **부분 컬렉션**을 가져오는 연산에는 `tail`, `init`, `slice`, `take`, `drop`, `takeWhile`, `dropWhile`, `filter`, `filterNot`, `withFilter` 등이 있다. 이들은 모두 어떤 첨자 범위나 술어 함수(predicate, 참-거짓을 반환하는 함수)에 의해 식별되는 부분 컬렉션을 반환한다. +* **분할** 연산인 `splitAt`, `span`, `partition`, `groupBy` 등은 대상 컬렉션의 원소를 구분해서 여러 부분 컬렉션으로 나눈다. +* **원소 검사** 연산 `exists`, `forall`, `count`는 주어진 술어 함수를 가지고 컬렉션 원소들을 검사한다. +* **폴드** 연산 `foldLeft`, `foldRight`, `/:`, `:\`, `reduceLeft`, `reduceRight`는 인접한 두 원소에 대해 이항 연산자(binary operator)를 반복적용한다. +* **특정 폴드** `sum`, `product`, `min`, `max`는 특정 타입(비교가능하거나 수)의 컬렉션에만 작용한다. +* **문자열** 연산 `mkString`, `addString`, `stringPrefix`는 컬렉션을 문자열로 바꾸는 여러가지 방법을 제공한다. +* **뷰** 연산은 `view` 메소드를 오버로딩한 두 메소드이다. 뷰는 지연 계산될 수 있는 컬렉션이다. 뷰에 대해서는 [나중에](#Views) 다룰 것이다. + +### Traversable 클래스의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추상 메소드:** | | +| `xs foreach f` |함수 `f`를 `xs`의 모든 원소에 적용한다.| +| **병합:** | | +| `xs ++ ys` |`xs`와 `ys`의 모든 원소들로 이루어진 컬렉션. `ys`는 [1회 순회가능(TraversableOnce)](http://www.scala-lang.org/api/current/scala/collection/TraversableOnce.html) 컬렉션이다. 즉, [순회가능(Traversable)](http://www.scala-lang.org/api/current/scala/collection/Traversable.html)이거나 [반복자(Iterator)](http://www.scala-lang.org/api/current/scala/collection/Iterator.html)여야 한다.| +| **맵:** | | +| `xs map f` |함수 `f`를 `xs`의 모든 원소에 적용해 반환된 결과로 이루어진 컬렉션을 반환한다.| +| `xs flatMap f` |결과값이 컬렉션인 함수 `f`를 `xs`의 모든 원소에 적용해 반환된 결과를 차례로 이어붙여서 이루어진 컬렉션을 반환한다.| +| `xs collect f` |부분함수(partial function) `f`를 모든 `xs`에 호출해서 결과값이 정의되어 있는 경우에 대해서만 그 값들을 모아서 이루어진 컬렉션을 반환한다.| +| **변환:** | | +| `xs.toArray` |컬렉션을 배열(Array)로 변환한다.| +| `xs.toList` |컬렉션을 리스트(List)로 변환한다.| +| `xs.toIterable` |컬렉션을 반복가능객체(Iterable)로 변환한다.| +| `xs.toSeq` |컬렉션을 열(Seq)로 변환한다.| +| `xs.toIndexedSeq` |컬렉션을 첨자가 있는 열(IndexedSeq)로 변환한다.| +| `xs.toStream` |컬렉션을 스트림(Stream)으로 변환한다.| +| `xs.toSet` |컬렉션을 집합(Set)으로 변환한다.| +| `xs.toMap` |컬렉션을 키/값 쌍의 맵으로 변환한다. 컬렉션의 원소가 튜플이 아니라면 이 메소드 호출은 컴파일시 타입 오류가 난다.| +| **Copying:** | | +| `xs copyToBuffer buf` |컬렉션의 모든 원소를 버퍼 `buf`에 복사한다.| +| `xs copyToArray(arr, s, n)`|첨자 `s`부터 시작해 최대 `n`개의 원소를 배열 `arr`에 복사한다. 마지막 두 매개변수는 생략할 수 있다.| +| **크기 정보:** | | +| `xs.isEmpty` |컬렉션이 비어있다면 참을 반환한다.| +| `xs.nonEmpty` |컬렉션에 원소가 하나라도 있다면 참을 반환한다.| +| `xs.size` |컬렉션의 원소의 갯수를 반환한다.| +| `xs.hasDefiniteSize` |`xs`가 유한한 크기를 가졌는지 알려져 있다면 참을 반환한다.| +| **원소 가져오기:** | | +| `xs.head` |컬렉션의 첫 원소(순서가 없는 컬렉션이라면 임의의 원소)를 반환한다.| +| `xs.headOption` |`xs`가 비어있다면 None, 그렇지 않다면 첫 원소를 Option에 담아서 반환한다.| +| `xs.last` |컬렉션의 마지막 원소(순서가 없는 컬렉션이라면 임의의 원소)를 반환한다.| +| `xs.lastOption` |`xs`가 비어있다면 None, 그렇지 않다면 마지막 원소를 Option에 담아서 반환한다.| +| `xs find p` |`xs`에서 `p`를 만족하는 첫번째 원소를 Option에 담아 반환한다. 만족하는 원소가 없다면 None을 반환한다.| +| **부분 컬렉션:** | | +| `xs.tail` |`xs.head`를 제외한 나머지 컬렉션이다.| +| `xs.init` |`xs.last`를 제외한 나머지 컬렉션이다.| +| `xs slice (from, to)` |컬렉션 `xs`에서 첨자 범위에 속하는 원소들(`from`부터 시작해서 `to`까지. 단, `from`에 있는 원소는 포함하고, `to`에 있는 원소는 포함하지 않음)로 이루어진 컬렉션을 반환한다.| +| `xs take n` |컬렉션 `xs`에서 앞에서부터 `n`개의 원소로 구성된 컬렉션(만약 순서가 없는 컬렉션이라면 임의의 `n`개의 원소가 선택된다)이다.| +| `xs drop n` |컬렉션에서 `xs take n`하고 난 나머지 컬렉션을 반환한다.| +| `xs takeWhile p` |컬렉션 `xs`의 맨 앞에서부터 술어 `p`를 만족하는 동안 원소를 수집해 만들어진 컬렉션. `p`를 만족하지 않는 첫 원소에서 수집은 끝난다. 따라서, 만약 `xs`의 첫 원소가 `p`를 만족하지 않으면 빈 컬렉션을 반환한다.| +| `xs dropWhile p` |컬렉션 `xs`의 맨 앞에서부터 따져서 술어 `p`를 최초로 만족하는 원소로부터 `xs`의 마지막 원소까지로 이루어진 컬렉션이다.| +| `xs filter p` |`xs`의 원소 중 술어 `p`를 만족하는 원소로 이루어진 컬렉션이다.| +| `xs withFilter p` |컬렉션에 필요시 계산하는 필터를 추가한다. 이 결과 컬렉션에 `map`, `flatMap`, `foreach`, `withFilter` 등이 호출되면 `xs` 중에 술어 `p`를 만족하는 원소에 대해서만 처리가 이루어진다.| +| `xs filterNot p` |`xs`의 원소 중 술어 `p`를 만족하지 않는 원소로 이루어진 컬렉션이다.| +| **분할:** | | +| `xs splitAt n` |`n`위치를 기준으로 `xs`를 둘로 분할한다. `(xs take n, xs drop n)` 쌍과 동일한 컬렉션 쌍을 반환한다.| +| `xs span p` |`xs`를 술어 `p`를 가지고 둘로 분할하되, `(xs takeWhile p, xs.dropWhile p)`과 같은 컬렉션 쌍을 반환한다.| +| `xs partition p` |`xs`를 술어 `p`를 만족하는 원소들과 만족하지 않는 원소의 두 컬렉션으로 분할한 튜플을 반환한다. `(xs filter p, xs.filterNot p)`과 같은 결과를 반환한다.| +| `xs groupBy f` |`xs`를 분류 함수 `f`에 따르는 컬렉션의 맵으로 분할한다.| +| **원소 검사:** | | +| `xs forall p` |술어 `P`가 `xs`의 모든 원소에 대해 성립하는지 여부를 반환한다.| +| `xs exists p` |술어 `P`를 만족하는 원소가 `xs`에 있는지 여부를 반환한다.| +| `xs count p` |`xs`에서 술어 `P`를 만족하는 원소의 갯수를 반환한다.| +| **폴드:** | | +| `(z /: xs)(op)` |이항 연산 `op`를 `xs`의 인접 원소에 대해 `z`부터 시작해 왼쪽부터 오른쪽으로 차례로 적용한다.| +| `(xs :\ z)(op)` |이항 연산 `op`를 `xs`의 인접 원소에 대해 `z`부터 시작해 오른쪽부터 왼쪽으로 차례로 적용한다.| +| `xs.foldLeft(z)(op)` |`(z /: xs)(op)`과 같다.| +| `xs.foldRight(z)(op)` |`(xs :\ z)(op)`과 같다.| +| `xs reduceLeft op` |이항 연산 `op`를 비어있지 않은 `xs`의 인접 원소에 대해 왼쪽부터 오른쪽으로 차례로 적용한다.| +| `xs reduceRight op` |이항 연산 `op`를 비어있지 않은 `xs`의 인접 원소에 대해 오른쪽부터 왼쪽으로 차례로 적용한다.| +| **특정 폴드:** | | +| `xs.sum` |컬렉션 `xs`의 모든 수 원소를 곱한 값이다.| +| `xs.product` |컬렉션 `xs`의 모든 수 원소를 곱한 값이다.| +| `xs.min` |순서가 있는 컬렉션 `xs`에서 가장 작은 원소이다.| +| `xs.max` |순서가 있는 컬렉션 `xs`에서 가장 큰 원소이다.| +| **문자열:** | | +| `xs addString (b, start, sep, end)`|`StringBuilder` `b`에 `xs`의 모든 원소를 `sep`으로 구분해 `start`와 `end` 사이에 나열한 문자열을 추가한다. `start`, `sep`, `end`는 생략 가능하다.| +| `xs mkString (start, sep, end)`|컬렉션 `xs`의 모든 원소를 `sep`로 구분해 문자열 `start`와 `end` 사이에 넣는다. `start`, `sep`, `end`는 생략 가능하다.| +| `xs.stringPrefix` |`xs.toString`이 반환하는 문자열의 맨 앞에 표시될 컬렉션 이름이다.| +| **뷰:** | | +| `xs.view` |`xs`에 대한 뷰를 만든다.| +| `xs view (from, to)` |`xs`에 대해 첨자 범위에 속하는 원소에 대한 뷰를 만든다.| + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/views.md b/ko/overviews/collections/views.md new file mode 100644 index 0000000000..7f894d1d5b --- /dev/null +++ b/ko/overviews/collections/views.md @@ -0,0 +1,129 @@ +--- +layout: overview-large +title: Views + +disqus: true + +partof: collections +num: 14 +language: ko +--- + +Collections have quite a few methods that construct new collections. Examples are `map`, `filter` or `++`. We call such methods transformers because they take at least one collection as their receiver object and produce another collection in their result. + +There are two principal ways to implement transformers. One is _strict_, that is a new collection with all its elements is constructed as a result of the transformer. The other is non-strict or _lazy_, that is one constructs only a proxy for the result collection, and its elements get constructed only as one demands them. + +As an example of a non-strict transformer consider the following implementation of a lazy map operation: + + def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[T] { + def iterator = coll.iterator map f + } + +Note that `lazyMap` constructs a new `Iterable` without stepping through all elements of the given collection `coll`. The given function `f` is instead applied to the elements of the new collection's `iterator` as they are demanded. + +Scala collections are by default strict in all their transformers, except for `Stream`, which implements all its transformer methods lazily. However, there is a systematic way to turn every collection into a lazy one and _vice versa_, which is based on collection views. A _view_ is a special kind of collection that represents some base collection, but implements all transformers lazily. + +To go from a collection to its view, you can use the view method on the collection. If `xs` is some collection, then `xs.view` is the same collection, but with all transformers implemented lazily. To get back from a view to a strict collection, you can use the `force` method. + +Let's see an example. Say you have a vector of Ints over which you want to map two functions in succession: + + scala> val v = Vector(1 to 10: _*) + v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + scala> v map (_ + 1) map (_ * 2) + res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +In the last statement, the expression `v map (_ + 1)` constructs a new vector which is then transformed into a third vector by the second call to `map (_ * 2)`. In many situations, constructing the intermediate result from the first call to map is a bit wasteful. In the example above, it would be faster to do a single map with the composition of the two functions `(_ + 1)` and `(_ * 2)`. If you have the two functions available in the same place you can do this by hand. But quite often, successive transformations of a data structure are done in different program modules. Fusing those transformations would then undermine modularity. A more general way to avoid the intermediate results is by turning the vector first into a view, then applying all transformations to the view, and finally forcing the view to a vector: + + scala> (v.view map (_ + 1) map (_ * 2)).force + res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Let's do this sequence of operations again, one by one: + + scala> val vv = v.view + vv: scala.collection.SeqView[Int,Vector[Int]] = + SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +The application `v.view` gives you a `SeqView`, i.e. a lazily evaluated `Seq`. The type `SeqView` has two type parameters. The first, `Int`, shows the type of the view's elements. The second, `Vector[Int]` shows you the type constructor you get back when forcing the `view`. + +Applying the first `map` to the view gives: + + scala> vv map (_ + 1) + res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) + +The result of the `map` is a value that prints `SeqViewM(...)`. This is in essence a wrapper that records the fact that a `map` with function `(_ + 1)` needs to be applied on the vector `v`. It does not apply that map until the view is `force`d, however. The "M" after `SeqView` is an indication that the view encapsulates a map operation. Other letters indicate other delayed operations. For instance "S" indicates a delayed `slice` operations, and "R" indicates a `reverse`. Let's now apply the second `map` to the last result. + + scala> res13 map (_ * 2) + res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) + +You now get a `SeqView` that contains two map operations, so it prints with a double "M": `SeqViewMM(...)`. Finally, forcing the last result gives: + +scala> res14.force +res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Both stored functions get applied as part of the execution of the `force` operation and a new vector is constructed. That way, no intermediate data structure is needed. + +One detail to note is that the static type of the final result is a Seq, not a Vector. Tracing the types back we see that as soon as the first delayed map was applied, the result had static type `SeqViewM[Int, Seq[_]]`. That is, the "knowledge" that the view was applied to the specific sequence type `Vector` got lost. The implementation of a view for some class requires quite a lot of code, so the Scala collection libraries provide views mostly only for general collection types, but not for specific implementations (An exception to this are arrays: Applying delayed operations on arrays will again give results with static type `Array`). + +There are two reasons why you might want to consider using views. The first is performance. You have seen that by switching a collection to a view the construction of intermediate results can be avoided. These savings can be quite important. As another example, consider the problem of finding the first palindrome in a list of words. A palindrome is a word which reads backwards the same as forwards. Here are the necessary definitions: + + def isPalindrome(x: String) = x == x.reverse + def findPalidrome(s: Seq[String]) = s find isPalindrome + +Now, assume you have a very long sequence words and you want to find a palindrome in the first million words of that sequence. Can you re-use the definition of `findPalidrome`? If course, you could write: + + findPalindrome(words take 1000000) + +This nicely separates the two aspects of taking the first million words of a sequence and finding a palindrome in it. But the downside is that it always constructs an intermediary sequence consisting of one million words, even if the first word of that sequence is already a palindrome. So potentially, 999'999 words are copied into the intermediary result without being inspected at all afterwards. Many programmers would give up here and write their own specialized version of finding palindromes in some given prefix of an argument sequence. But with views, you don't have to. Simply write: + + findPalindrome(words.view take 1000000) + +This has the same nice separation of concerns, but instead of a sequence of a million elements it will only construct a single lightweight view object. This way, you do not need to choose between performance and modularity. + +The second use case applies to views over mutable sequences. Many transformer functions on such views provide a window into the original sequence that can then be used to update selectively some elements of that sequence. To see this in an example, let's suppose you have an array `arr`: + + scala> val arr = (0 to 9).toArray + arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + +You can create a subwindow into that array by creating a slice of a view of `arr`: + + scala> val subarr = arr.view.slice(3, 6) + subarr: scala.collection.mutable.IndexedSeqView[ + Int,Array[Int]] = IndexedSeqViewS(...) + +This gives a view `subarr` which refers to the elements at positions 3 through 5 of the array `arr`. The view does not copy these elements, it just provides a reference to them. Now, assume you have a method that modifies some elements of a sequence. For instance, the following `negate` method would negate all elements of the sequence of integers it's given: + + scala> def negate(xs: collection.mutable.Seq[Int]) = + for (i <- 0 until xs.length) xs(i) = -xs(i) + negate: (xs: scala.collection.mutable.Seq[Int])Unit + +Assume now you want to negate elements at positions 3 through five of the array `arr`. Can you use `negate` for this? Using a view, this is simple: + + scala> negate(subarr) + scala> arr + res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) + +What happened here is that negate changed all elements of `subarr`, which were a slice of the elements of `arr`. Again, you see that views help in keeping things modular. The code above nicely separated the question of what index range to apply a method to from the question what method to apply. + +After having seen all these nifty uses of views you might wonder why have strict collections at all? One reason is that performance comparisons do not always favor lazy over strict collections. For smaller collection sizes the added overhead of forming and applying closures in views is often greater than the gain from avoiding the intermediary data structures. A probably more important reason is that evaluation in views can be very confusing if the delayed operations have side effects. + +Here's an example which bit a few users of versions of Scala before 2.8. In these versions the Range type was lazy, so it behaved in effect like a view. People were trying to create a number of actors like this: + + + val actors = for (i <- 1 to 10) yield actor { ... } + +They were surprised that none of the actors was executing afterwards, even though the actor method should create and start an actor from the code that's enclosed in the braces following it. To explain why nothing happened, remember that the for expression above is equivalent to an application of map: + + val actors = (1 to 10) map (i => actor { ... }) + +Since previously the range produced by `(1 to 10)` behaved like a view, the result of the map was again a view. That is, no element was computed, and, consequently, no actor was created! Actors would have been created by forcing the range of the whole expression, but it's far from obvious that this is what was required to make the actors do their work. + +To avoid surprises like this, the Scala 2.8 collections library has more regular rules. All collections except streams and views are strict. The only way to go from a strict to a lazy collection is via the `view` method. The only way to go back is via `force`. So the `actors` definition above would behave as expected in Scala 2.8 in that it would create and start 10 actors. To get back the surprising previous behavior, you'd have to add an explicit `view` method call: + + val actors = for (i <- (1 to 10).view) yield actor { ... } + +In summary, views are a powerful tool to reconcile concerns of efficiency with concerns of modularity. But in order not to be entangled in aspects of delayed evaluation, you should restrict views to two scenarios. Either you apply views in purely functional code where collection transformations do not have side effects. Or you apply them over mutable collections where all modifications are done explicitly. What's best avoided is a mixture of views and operations that create new collections while also having side effects. + + + diff --git a/ko/overviews/core/collections.md b/ko/overviews/core/collections.md new file mode 100644 index 0000000000..ecf09c3be1 --- /dev/null +++ b/ko/overviews/core/collections.md @@ -0,0 +1,7 @@ +--- +layout: overview +overview: collections +partof: collections +language: ko +title: Scala 컬렉션 라이브러리 +--- diff --git a/ko/overviews/index.md b/ko/overviews/index.md new file mode 100755 index 0000000000..a790542aae --- /dev/null +++ b/ko/overviews/index.md @@ -0,0 +1,13 @@ +--- +layout: guides-index +language: ko +title: 가이드 및 개요 +--- + +
+

핵심 필수요소들...

+
+ +{% include localized-overview-index.txt %} + + diff --git a/overviews/collections/arrays.md b/overviews/collections/arrays.md index ff8227b3c2..73dd9875cf 100644 --- a/overviews/collections/arrays.md +++ b/overviews/collections/arrays.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 10 -languages: [ja] +languages: [ja, ko] --- [Array](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html) is a special kind of collection in Scala. On the one hand, Scala arrays correspond one-to-one to Java arrays. That is, a Scala array `Array[Int]` is represented as a Java `int[]`, an `Array[Double]` is represented as a Java `double[]` and a `Array[String]` is represented as a `Java String[]`. But at the same time, Scala arrays offer much more than their Java analogues. First, Scala arrays can be _generic_. That is, you can have an `Array[T]`, where `T` is a type parameter or abstract type. Second, Scala arrays are compatible with Scala sequences - you can pass an `Array[T]` where a `Seq[T]` is required. Finally, Scala arrays also support all sequence operations. Here's an example of this in action: diff --git a/overviews/collections/concrete-immutable-collection-classes.md b/overviews/collections/concrete-immutable-collection-classes.md index 6c3b9d4290..9969ca2af6 100644 --- a/overviews/collections/concrete-immutable-collection-classes.md +++ b/overviews/collections/concrete-immutable-collection-classes.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 8 -languages: [ja] +languages: [ja, ko] --- Scala provides many concrete immutable collection classes for you to choose from. They differ in the traits they implement (maps, sets, sequences), whether they can be infinite, and the speed of various operations. Here are some of the most common immutable collection types used in Scala. diff --git a/overviews/collections/concrete-mutable-collection-classes.md b/overviews/collections/concrete-mutable-collection-classes.md index e60459b334..66d4596c0a 100644 --- a/overviews/collections/concrete-mutable-collection-classes.md +++ b/overviews/collections/concrete-mutable-collection-classes.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 9 -languages: [ja] +languages: [ja, ko] --- You've now seen the most commonly used immutable collection classes that Scala provides in its standard library. Take a look now at the mutable collection classes. diff --git a/overviews/collections/conversions-between-java-and-scala-collections.md b/overviews/collections/conversions-between-java-and-scala-collections.md index 1517d31075..f28d5ef89a 100644 --- a/overviews/collections/conversions-between-java-and-scala-collections.md +++ b/overviews/collections/conversions-between-java-and-scala-collections.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 17 -languages: [ja] +languages: [ja, ko] --- Like Scala, Java also has a rich collections library. There are many similarities between the two. For instance, both libraries know iterators, iterables, sets, maps, and sequences. But there are also important differences. In particular, the Scala libraries put much more emphasis on immutable collections, and provide many more operations that transform a collection into a new one. diff --git a/overviews/collections/creating-collections-from-scratch.md b/overviews/collections/creating-collections-from-scratch.md index 88e29af8b9..a800378167 100644 --- a/overviews/collections/creating-collections-from-scratch.md +++ b/overviews/collections/creating-collections-from-scratch.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 16 -languages: [ja] +languages: [ja, ko] --- You have syntax `List(1, 2, 3)` to create a list of three integers and `Map('A' -> 1, 'C' -> 2)` to create a map with two bindings. This is actually a universal feature of Scala collections. You can take any collection name and follow it by a list of elements in parentheses. The result will be a new collection with the given elements. Here are some more examples: @@ -55,4 +55,4 @@ Descendants of `Seq` classes provide also other factory operations in their comp | `S.tabulate(m, n){f}` | A sequence of sequences of dimension `m×n` where the element at each index `(i, j)` is computed by `f(i, j)`. (exists also in higher dimensions). | | `S.range(start, end)` | The sequence of integers `start` ... `end-1`. | | `S.range(start, end, step)`| The sequence of integers starting with `start` and progressing by `step` increments up to, and excluding, the `end` value. | -| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | \ No newline at end of file +| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | diff --git a/overviews/collections/equality.md b/overviews/collections/equality.md index 8d36b26fa6..328c5f63ac 100644 --- a/overviews/collections/equality.md +++ b/overviews/collections/equality.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 13 -languages: [ja] +languages: [ja, ko] --- The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences. Collections in different categories are always unequal. For instance, `Set(1, 2, 3)` is unequal to `List(1, 2, 3)` even though they contain the same elements. On the other hand, within the same category, collections are equal if and only if they have the same elements (for sequences: the same elements in the same order). For example, `List(1, 2, 3) == Vector(1, 2, 3)`, and `HashSet(1, 2) == TreeSet(2, 1)`. diff --git a/overviews/collections/introduction.md b/overviews/collections/introduction.md index ae57e0167f..7ac7e9bc72 100644 --- a/overviews/collections/introduction.md +++ b/overviews/collections/introduction.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 1 -languages: [ja] +languages: [ja, ko] --- **Martin Odersky, and Lex Spoon** @@ -93,4 +93,4 @@ as part of Scala 2.9.) This document provides an in depth discussion of the APIs of the Scala collections classes from a user perspective. They take you on -a tour of all the fundamental classes and the methods they define. \ No newline at end of file +a tour of all the fundamental classes and the methods they define. diff --git a/overviews/collections/iterators.md b/overviews/collections/iterators.md index f5b8117e9b..cf0ac3ea4a 100644 --- a/overviews/collections/iterators.md +++ b/overviews/collections/iterators.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 15 -languages: [ja] +languages: [ja, ko] --- An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator `it` are `next` and `hasNext`. A call to `it.next()` will return the next element of the iterator and advance the state of the iterator. Calling `next` again on the same iterator will then yield the element one beyond the one returned previously. If there are no more elements to return, a call to `next` will throw a `NoSuchElementException`. You can find out whether there are more elements to return using [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)'s `hasNext` method. diff --git a/overviews/collections/maps.md b/overviews/collections/maps.md index 24d53a937b..e917859493 100644 --- a/overviews/collections/maps.md +++ b/overviews/collections/maps.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 7 -languages: [ja] +languages: [ja, ko] --- A [Map](http://www.scala-lang.org/api/current/scala/collection/Map.html) is an [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html) consisting of pairs of keys and values (also named _mappings_ or _associations_). Scala's [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) class offers an implicit conversion that lets you write `key -> value` as an alternate syntax for the pair `(key, value)`. For instance `Map("x" -> 24, "y" -> 25, "z" -> 26)` means exactly the same as `Map(("x", 24), ("y", 25), ("z", 26))`, but reads better. diff --git a/overviews/collections/migrating-from-scala-27.md b/overviews/collections/migrating-from-scala-27.md index b37fd5e2c5..12c4d6c76b 100644 --- a/overviews/collections/migrating-from-scala-27.md +++ b/overviews/collections/migrating-from-scala-27.md @@ -7,7 +7,7 @@ disqus: true partof: collections num: 18 outof: 18 -languages: [ja] +languages: [ja, ko] --- Porting your existing Scala applications to use the new collections should be almost automatic. There are only a couple of possible issues to take care of. diff --git a/overviews/collections/overview.md b/overviews/collections/overview.md index bb18ec2b17..b2a372a202 100644 --- a/overviews/collections/overview.md +++ b/overviews/collections/overview.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 2 -languages: [ja] +languages: [ja, ko] --- Scala collections systematically distinguish between mutable and @@ -139,4 +139,4 @@ This behavior which is implemented everywhere in the collections libraries is ca Most of the classes in the collections hierarchy exist in three variants: root, mutable, and immutable. The only exception is the Buffer trait which only exists as a mutable collection. -In the following, we will review these classes one by one. \ No newline at end of file +In the following, we will review these classes one by one. diff --git a/overviews/collections/performance-characteristics.md b/overviews/collections/performance-characteristics.md index 057a5b8155..3e37b00bd1 100644 --- a/overviews/collections/performance-characteristics.md +++ b/overviews/collections/performance-characteristics.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 12 -languages: [ja] +languages: [ja, ko] --- The previous explanations have made it clear that different collection types have different performance characteristics. That's often the primary reason for picking one collection type over another. You can see the performance characteristics of some common operations on collections summarized in the following two tables. diff --git a/overviews/collections/seqs.md b/overviews/collections/seqs.md index 0187b91106..4c32c906eb 100644 --- a/overviews/collections/seqs.md +++ b/overviews/collections/seqs.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 5 -languages: [ja] +languages: [ja, ko] --- The [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) trait represents sequences. A sequence is a kind of iterable that has a `length` and whose elements have fixed index positions, starting from `0`. diff --git a/overviews/collections/sets.md b/overviews/collections/sets.md index 6d65274f32..5db970bfca 100644 --- a/overviews/collections/sets.md +++ b/overviews/collections/sets.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 6 -languages: [ja] +languages: [ja, ko] --- `Set`s are `Iterable`s that contain no duplicate elements. The operations on sets are summarized in the following table for general sets and in the table after that for mutable sets. They fall into the following categories: diff --git a/overviews/collections/strings.md b/overviews/collections/strings.md index 68a57ecc3b..61158306e1 100644 --- a/overviews/collections/strings.md +++ b/overviews/collections/strings.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 11 -languages: [ja] +languages: [ja, ko] --- Like arrays, strings are not directly sequences, but they can be converted to them, and they also support all sequence operations on strings. Here are some examples of operations you can invoke on strings. @@ -24,4 +24,4 @@ Like arrays, strings are not directly sequences, but they can be converted to th scala> val s: Seq[Char] = str s: Seq[Char] = WrappedString(h, e, l, l, o) -These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. \ No newline at end of file +These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. diff --git a/overviews/collections/trait-iterable.md b/overviews/collections/trait-iterable.md index 0fb7ab53b2..f89eca4b75 100644 --- a/overviews/collections/trait-iterable.md +++ b/overviews/collections/trait-iterable.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 4 -languages: [ja] +languages: [ja, ko] --- The next trait from the top in the collections hierarchy is `Iterable`. All methods in this trait are defined in terms of an an abstract method, `iterator`, which yields the collection's elements one by one. The `foreach` method from trait `Traversable` is implemented in `Iterable` in terms of `iterator`. Here is the actual implementation: diff --git a/overviews/collections/trait-traversable.md b/overviews/collections/trait-traversable.md index 09ae378b4b..10526e88c1 100644 --- a/overviews/collections/trait-traversable.md +++ b/overviews/collections/trait-traversable.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 3 -languages: [ja] +languages: [ja, ko] --- At the top of the collection hierarchy is trait `Traversable`. Its only abstract operation is `foreach`: @@ -106,4 +106,4 @@ The `foreach` method is meant to traverse all elements of the collection, and ap | `xs.stringPrefix` |The collection name at the beginning of the string returned from `xs.toString`.| | **Views:** | | | `xs.view` |Produces a view over `xs`.| -| `xs view (from, to)` |Produces a view that represents the elements in some index range of `xs`.| \ No newline at end of file +| `xs view (from, to)` |Produces a view that represents the elements in some index range of `xs`.| diff --git a/overviews/collections/views.md b/overviews/collections/views.md index 8304d0419d..a43606d6c9 100644 --- a/overviews/collections/views.md +++ b/overviews/collections/views.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 14 -languages: [ja] +languages: [ja, ko] --- Collections have quite a few methods that construct new collections. Examples are `map`, `filter` or `++`. We call such methods transformers because they take at least one collection as their receiver object and produce another collection in their result. diff --git a/overviews/index.md b/overviews/index.md index dba3e7dcc8..0e78d19971 100644 --- a/overviews/index.md +++ b/overviews/index.md @@ -1,7 +1,7 @@ --- layout: guides-index title: Guides and Overviews -languages: [es, ja] +languages: [es, ja, ko] ---