diff --git a/_overviews/collections-2.13/arrays.md b/_overviews/collections-2.13/arrays.md index 4b353c8f29..ab5214bd25 100644 --- a/_overviews/collections-2.13/arrays.md +++ b/_overviews/collections-2.13/arrays.md @@ -11,6 +11,7 @@ num: 10 previous-page: concrete-mutable-collection-classes next-page: strings +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/concrete-immutable-collection-classes.md b/_overviews/collections-2.13/concrete-immutable-collection-classes.md index b5bbbb2009..6a19b859f5 100644 --- a/_overviews/collections-2.13/concrete-immutable-collection-classes.md +++ b/_overviews/collections-2.13/concrete-immutable-collection-classes.md @@ -11,6 +11,7 @@ num: 8 previous-page: maps next-page: concrete-mutable-collection-classes +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/concrete-mutable-collection-classes.md b/_overviews/collections-2.13/concrete-mutable-collection-classes.md index 77eb9d8fcb..29f91a2927 100644 --- a/_overviews/collections-2.13/concrete-mutable-collection-classes.md +++ b/_overviews/collections-2.13/concrete-mutable-collection-classes.md @@ -11,6 +11,7 @@ num: 9 previous-page: concrete-immutable-collection-classes next-page: arrays +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md b/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md index b005d28ffd..82eb9a946c 100644 --- a/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md +++ b/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md @@ -10,6 +10,7 @@ overview-name: Collections num: 17 previous-page: creating-collections-from-scratch +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/creating-collections-from-scratch.md b/_overviews/collections-2.13/creating-collections-from-scratch.md index 7305a4b13d..bb47075e18 100644 --- a/_overviews/collections-2.13/creating-collections-from-scratch.md +++ b/_overviews/collections-2.13/creating-collections-from-scratch.md @@ -11,6 +11,7 @@ num: 16 previous-page: iterators next-page: conversions-between-java-and-scala-collections +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/equality.md b/_overviews/collections-2.13/equality.md index 6be2ab45a6..9bf09666da 100644 --- a/_overviews/collections-2.13/equality.md +++ b/_overviews/collections-2.13/equality.md @@ -11,6 +11,7 @@ num: 13 previous-page: performance-characteristics next-page: views +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/introduction.md b/_overviews/collections-2.13/introduction.md index 2b238045da..9343c7cd95 100644 --- a/_overviews/collections-2.13/introduction.md +++ b/_overviews/collections-2.13/introduction.md @@ -10,6 +10,7 @@ overview-name: Collections num: 1 next-page: overview +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/iterators.md b/_overviews/collections-2.13/iterators.md index 37080c8fbe..4a48e165d6 100644 --- a/_overviews/collections-2.13/iterators.md +++ b/_overviews/collections-2.13/iterators.md @@ -11,6 +11,7 @@ num: 15 previous-page: views next-page: creating-collections-from-scratch +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/maps.md b/_overviews/collections-2.13/maps.md index aac6c5eeda..3f896e1f5f 100644 --- a/_overviews/collections-2.13/maps.md +++ b/_overviews/collections-2.13/maps.md @@ -11,6 +11,7 @@ num: 7 previous-page: sets next-page: concrete-immutable-collection-classes +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/overview.md b/_overviews/collections-2.13/overview.md index 5cba5c8aa1..69b05bb449 100644 --- a/_overviews/collections-2.13/overview.md +++ b/_overviews/collections-2.13/overview.md @@ -11,6 +11,7 @@ num: 2 previous-page: introduction next-page: trait-iterable +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/performance-characteristics.md b/_overviews/collections-2.13/performance-characteristics.md index e706b078a1..30dd48a183 100644 --- a/_overviews/collections-2.13/performance-characteristics.md +++ b/_overviews/collections-2.13/performance-characteristics.md @@ -11,6 +11,7 @@ num: 12 previous-page: strings next-page: equality +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/seqs.md b/_overviews/collections-2.13/seqs.md index 6edcfebb13..24f14b2435 100644 --- a/_overviews/collections-2.13/seqs.md +++ b/_overviews/collections-2.13/seqs.md @@ -11,6 +11,7 @@ num: 5 previous-page: trait-iterable next-page: sets +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/sets.md b/_overviews/collections-2.13/sets.md index c854a60461..63ad4ac023 100644 --- a/_overviews/collections-2.13/sets.md +++ b/_overviews/collections-2.13/sets.md @@ -11,6 +11,7 @@ num: 6 previous-page: seqs next-page: maps +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/strings.md b/_overviews/collections-2.13/strings.md index f869e5dd67..c88a038b2c 100644 --- a/_overviews/collections-2.13/strings.md +++ b/_overviews/collections-2.13/strings.md @@ -11,6 +11,7 @@ num: 11 previous-page: arrays next-page: performance-characteristics +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/trait-iterable.md b/_overviews/collections-2.13/trait-iterable.md index de8bcc2b03..c05867ed0c 100644 --- a/_overviews/collections-2.13/trait-iterable.md +++ b/_overviews/collections-2.13/trait-iterable.md @@ -11,6 +11,7 @@ num: 4 previous-page: overview next-page: seqs +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections-2.13/views.md b/_overviews/collections-2.13/views.md index 97401e510f..7669f14998 100644 --- a/_overviews/collections-2.13/views.md +++ b/_overviews/collections-2.13/views.md @@ -11,6 +11,7 @@ num: 14 previous-page: equality next-page: iterators +languages: [ru] permalink: /overviews/collections-2.13/:title.html --- diff --git a/_overviews/collections/migrating-from-scala-27.md b/_overviews/collections/migrating-from-scala-27.md index 939ef0987c..942b1c652e 100644 --- a/_overviews/collections/migrating-from-scala-27.md +++ b/_overviews/collections/migrating-from-scala-27.md @@ -41,7 +41,7 @@ Generally, the old functionality of Scala 2.7 collections has been left in place 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 mimic 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.html) object which replaces the `jcl` package. +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.html) 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/_ru/overviews/collections-2.13/arrays.md b/_ru/overviews/collections-2.13/arrays.md new file mode 100644 index 0000000000..199c129b78 --- /dev/null +++ b/_ru/overviews/collections-2.13/arrays.md @@ -0,0 +1,125 @@ +--- +layout: multipage-overview +title: Массивы + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 10 +previous-page: concrete-mutable-collection-classes +next-page: strings +language: ru + +--- + +[Массивы](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html) особый вид коллекций в Scala. +С одной стороны, Scala массивы соответствуют массивам из Java. Например, Scala массив `Array[Int]` реализован в виде Java `int[]`, а `Array[Double]` как Java `double[]` и `Array[String]` как Java `String[]` + С другой стороны, Scala массивы дают намного больше чем их Java аналоги. Во-первых Scala массивы могут быть обобщены (_generic_). То есть вы можете описать массив как `Array[T]`, где `T` дополнительный `параметр-тип` массива или же абстрактный тип. + Во-вторых, Scala массивы совместимы со списками (`Seq`) Scala - вы можете передавать `Array[T]` на вход туда, где требуется `Seq[T]`. Ну и наконец, Scala массивы также поддерживают все операции, которые есть у списков. Вот пример: + + 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 + res0: Array[Int] = Array(9, 3) + +Учитывая то что Scala массивы соответствуют массивам из Java, каким же образом реализованы остальные дополнительные возможности массивов в Scala? +Реализация массивов в Scala постоянно использует неявные преобразования. В Scala массив не пытается _притворяться_ последовательностью. Он и не может, потому что тип данных лежащий в основе массива не является подтипом `Seq`. Вместо этого, используя "упаковывание", происходит неявное преобразование между массивами и экземплярами класса `scala.collection.mutable.ArraySeq`, который является подклассом `Seq`. Вот как это работает: + + scala> val seq: collection.Seq[Int] = a1 + seq: scala.collection.Seq[Int] = ArraySeq(1, 2, 3) + scala> val a4: Array[Int] = seq.toArray + a4: Array[Int] = Array(1, 2, 3) + scala> a1 eq a4 + res1: Boolean = false + +Пример выше показывает, что массивы совместимы с последовательностями, потому как происходит неявное преобразование из массивов в `ArraySeq`ы. Чтобы перейти обратно от `ArraySeq` к `Array`, можно использовать метод `toArray`, описанный в `Iterable`. Последняя строка в консоле показывает, что упаковка и затем распаковка с помощью `toArray` создает копию исходного массива. + +Существует еще одно неявное преобразование, которое применяется к массивам. Такое преобразование просто "добавляет" все методы последовательностей (`Seq`) к массивам, но не превращает сам массив в последовательность. "Добавление" означает, что массив обернут в другой объект типа `ArrayOps`, который поддерживает все методы последовательности. Объект `ArrayOps` недолговечный, обычно он недоступен после обращения к методу последовательности и он может быть удален. Современные виртуальные машины могут избегать создания такого промежуточного объекта. + +Разница между двумя неявными преобразованиями на массивах показана в следующем примере: + + scala> val seq: collection.Seq[Int] = a1 + seq: scala.collection.Seq[Int] = ArraySeq(1, 2, 3) + scala> seq.reverse + res2: scala.collection.Seq[Int] = ArraySeq(3, 2, 1) + scala> val ops: collection.ArrayOps[Int] = a1 + ops: scala.collection.ArrayOps[Int] = scala.collection.ArrayOps@2d7df55 + scala> ops.reverse + res3: Array[Int] = Array(3, 2, 1) + +Вы видите, что вызов `reverse` на `seq`, который является `ArraySeq`, даст снова `ArraySeq`. Это логично, потому что массивы - это `Seqs`, и вызов `reverse` на любом `Seq` даст снова `Seq`. С другой стороны, вызов `reverse` на экземпляре класса `ArrayOps` даст значение `Array`, а не `Seq`. + +Пример `ArrayOps`, приведенный выше искусственный и используется лишь, чтоб показать разницу с `ArraySeq`. Обычно, вы никогда не создаете экземпляры класса `ArrayOps`. Вы просто вызываете методы `Seq` на массиве: + + scala> a1.reverse + res4: Array[Int] = Array(3, 2, 1) + +Объект `ArrayOps` автоматически вставляется через неявное преобразование. Так что строка выше эквивалентна + + scala> intArrayOps(a1).reverse + res5: Array[Int] = Array(3, 2, 1) + +где `intArrayOps` - неявное преобразование, которое было вставлено ранее. В связи с этим возникает вопрос, как компилятор выбрал `intArrayOps` вместо другого неявного преобразования в `ArraySeq` в строке выше. В конце концов, оба преобразования преобразуют массив в тип, поддерживающий метод reverse. Ответ на этот вопрос заключается в том, что два неявных преобразования имеют приоритет. Преобразование `ArrayOps` имеет больший приоритет, чем преобразование `ArraySeq`. Первый определяется в объекте `Predef`, а второй - в классе `scala.LowPriorityImplicits`, который `Predef` наследует. Неявные преобразования в дочерних классах и дочерних объектах имеют более высокий приоритет над преобразованиями в базовых классах. Таким образом, если оба преобразования применимы, выбирается вариант в `Predef`. Очень похожая схема используется для строк. + +Итак, теперь вы знаете, как массивы могут быть совместимы с последовательностями и как они могут поддерживать все операции последовательностей. А как же обобщения? В Java нельзя написать `T[]`, где `T` является параметром типа. Как же представлен Scala `Array[T]`? На самом деле обобщенный массив типа `Array[T]` может быть любым из восьми примитивных типов массивов Java `byte[]`, `short[]`, `char[]`, `int[] `, `long[] `, `float[]`, `double ` или может быть массивом объектов. Единственным общим типом, включающим все эти типы, является `AnyRef` (или, равнозначно `java.lang.Object`), так что это тот тип в который компилятор Scala отобразит `Array[T]`. Во время исполнения, при обращении к элементу массива типа `Array[T]`, происходит последовательность проверок типов, которые определяют тип массива, за которыми следует подходящая операция на Java-массиве. Эти проверки типов замедляют работу массивов. Можно ожидать падения скорости доступа к обобщенным массивам в три-четыре раза, по сравнению с обычными массивами или массивами объектов. Это означает, что если вам нужна максимальная производительность, вам следует выбирать конкретные массивы, вместо обобщенных. Отображать обобщенный массив еще пол беды, нам нужен еще способ создания обобщенных массивов. Это куда более сложная задача, которая требует, от вас, небольшой помощи. Чтобы проиллюстрировать проблему, рассмотрим следующую попытку написания обобщенного метода, который создает массив. + + // это неправильно! + 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 + } + +Метод `evenElems` возвращает новый массив, состоящий из всех элементов аргумента вектора `xs`, находящихся в четных позициях вектора. В первой строке тела `evenElems` создается результирующий массив, который имеет тот же тип элемента, что и аргумент. Так что в зависимости от фактического типа параметра для `T`, это может быть `Array[Int]`, или `Array[Boolean]`, или массив некоторых других примитивных типов Java, или массив какого-нибудь ссылочного типа. Но эти типы имеют разные представления при исполнении программы, и как же Scala подберет правильное представление? В действительности, Scala не может сделать этого, основываясь на предоставленной информации, так как при выполнении стирается фактический тип, соответствующий параметру типа `T`. Поэтому при компиляции показанного выше кода, появится следующее сообщение об ошибке: + + error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ + +Тут нужно немного помочь компилятору, указав какой в действительности тип параметра `evenElems`. Это указание во время исполнения принимает форму манифеста класса типа `scala.view.ClassTag`. _Манифест класса_ - это объект дескриптор типа, который описывает, какой тип у класса верхнего уровня. В качестве альтернативы манифестам классов существуют также _полные манифесты_ типа `scala.Refect.Manifest`, которые описывают все аспекты типа. Впрочем для создания массива требуются только _манифесты класса_. + +Компилятор Scala автоматически создаст манифесты классов, если вы проинструктируете его на это. "Инструктирование" означает, что вы требуете манифест класса в качестве неявного параметра, как в примере: + + def evenElems[T](xs: Vector[T])(implicit m: ClassTag[T]): Array[T] = ... + +Используя альтернативный и более короткий синтаксис, вы также можете потребовать, чтобы тип приходил с манифестом класса, используя _контекстное связывание_ (`context bound`). Это означает установить связь с типом `ClassTag` идущим после двоеточия в описании типа, как в примере: + + import scala.reflect.ClassTag + // так будет работать + def evenElems[T: ClassTag](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 + } + +Обе показанные версии `evenElems` означают одно и то же. Что бы не случилось, когда построен `Array[T]`, компилятор будет искать манифест класса для параметра типа `T`, то есть искать неявное значение (implicit value) типа `ClassTag[T]`. Если такое значение найдено, то этот манифест будет использоваться для построения требуемого типа массива. В противном случае вы увидите сообщение об ошибке, такое же как мы показывали выше. + +Вот некоторые примеры из консоли, использующие метод `evenElems`. + + 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) + +В обоих случаях компилятор Scala автоматически построил манифест класса для типа элемента (сначала `Int`, затем `String`) и передал его в качестве неявного параметра метода `evenElems`. Компилятор может сделать это для всех конкретных типов, но не тогда, когда аргумент сам параметризован типом, который не содержит манифест класса. Например, следующий пример не скомпилируется: + + scala> def wrap[U](xs: Vector[U]) = evenElems(xs) + :6: error: No ClassTag available for U. + def wrap[U](xs: Vector[U]) = evenElems(xs) + ^ +В данном случае `evenElems` требует наличия класса манифеста для параметра типа `U`, однако ни одного не найдено. Чтоб решить такую проблему, конечно, необходимо запросить манифест от неявного класса `U`. Поэтому следующее будет работать: + + scala> def wrap[U: ClassTag](xs: Vector[U]) = evenElems(xs) + wrap: [U](xs: Vector[U])(implicit evidence$1: scala.reflect.ClassTag[U])Array[U] + +Этот пример также показывает, что контекстное связывание с `U`, является лишь сокращением для неявного параметра, названного здесь `evidence$1` типа `ClassTag[U]`. + +Подводя итог, можно сказать, что для создания обобщенных массивов требуются манифесты классов. Поэтому при создании массива параметризированного типом `T`, вам также необходимо предоставить неявный класс манифест для `T`. Самый простой способ сделать это - объявить параметр типа `ClassTag` с контекстной привязкой, как `[T: ClassTag]`. diff --git a/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md b/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md new file mode 100644 index 0000000000..e6dde817c8 --- /dev/null +++ b/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md @@ -0,0 +1,224 @@ +--- +layout: multipage-overview +title: Реализации Неизменяемых Коллекций + +discourse: true + +partof: collections-213 +overview-name: Collections + +previous-page: maps +next-page: concrete-mutable-collection-classes + +num: 8 +language: ru + +--- + +Scala предлагает множество конечных реализаций неизменяемых коллекций. Они отличаются реализуемыми трейтами (мапы (map), множества(set), последовательности(seq)), они могут быть бесконечными, и различаются производительностью операций. Вот некоторые из наиболее распространенных неизменяемых типов коллекций, используемых в Scala. + +## Списки (Lists) + +[List](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html) представляет из себя конечную неизменяемую последовательность. Он обеспечивает быстрый (за [постоянное время](https://ru.wikipedia.org/wiki/%D0%92%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0)) доступ как к первому элементу, так и к остальному списку, а также быструю операцию добавления нового элемента в начало списка. Большинство оставшихся операции занимают линейное время исполнения. + +## Ленивые Списки (LazyLists) + +[LazyList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/LazyList.html) похож на список, за исключением того, что его элементы вычисляются лениво. Поэтому ленивый список может быть бесконечно длинным. Обрабатываются только те элементы, которые запрашиваются. В остальном, у ленивых списков те же параметры производительности, что и обычных. + +Если списки создаются с помощью оператора `::`, то ленивые списки создаются схожей операцией `#::`. Вот простой пример ленивого списка с целыми числами 1, 2 и 3: + + scala> val lazyList = 1 #:: 2 #:: 3 #:: LazyList.empty + lazyList: scala.collection.immutable.LazyList[Int] = LazyList(?) + +На первом месте в этом ленивом списке - 1, а на втором - 2 и 3. Но ни один из элементов здесь не выводится, потому что список еще не вычислен! Ленивые списки задуманны обрабатываться лениво, поэтому метод `toString`, не выводит всех элементов, не заставляя производить дополнительные вычисления. + +Ниже приводится более сложный пример. Вычисления ленивого списка, содержащего последовательность Фибоначчи, которая начинается с заданных двух чисел. Последовательность Фибоначчи - это последовательность, в которой каждый элемент представляет собой сумму двух предыдущих элементов в серии. + + scala> def fibFrom(a: Int, b: Int): LazyList[Int] = a #:: fibFrom(b, a + b) + fibFrom: (a: Int,b: Int)LazyList[Int] + +Эта функция обманчиво проста. Первый элемент очевидно `a`, остальная часть - это последовательность Фибоначчи, начинающаяся с `b`, за которой следует `a+b`. Сложность состоит в том, чтобы вычислить эту последовательность, не вызывая бесконечной рекурсии. Если бы функция использовала `::` вместо `#::`, то каждый вызов функции приводил бы к очередному вызову, вызывая тем самым бесконечную рекурсию. Но так как он использует `#::`, то вычисление правой части не производится до тех пор, пока она не будет запрошена. + +Ниже приведены первые элементы последовательности Фибоначчи, начиная с двух едениц: + + scala> val fibs = fibFrom(1, 1).take(7) + fibs: scala.collection.immutable.LazyList[Int] = LazyList(?) + scala> fibs.toList + res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) + +## Неизменяемые ArraySeqs + +Списки очень эффективны в алгоритмах которые активно использует `head`. Получение, добавление и удаление к переднему (`head`) элементу списка занимает постоянное время, в то время как доступ или изменение остальных элементов в списке занимает линейное время. + +[Последовательный Массив (ArraySeq)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ArraySeq.html) это тип коллекции + (добавленной в Scala 2.13) который решает проблему неэффективности случайного доступа к спискам. + + ArraySeq позволяют получить доступ к любому элементу коллекции за постоянное время. +В результате алгоритмы, использующие ArraySeq, могут быстро получать доступ к элементам в произвольных местах коллекции, из-за чего проще создавать эффективные алгоритмы. + +ArraySeqs создаются и изменяются также, как и любые другие последовательности. + +~~~ +scala> val arr = scala.collection.immutable.ArraySeq(1, 2, 3) +arr: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3) +scala> val arr2 = arr :+ 4 +arr2: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3, 4) +scala> arr2(0) +res22: Int = 1 +~~~ + +ArraySeqs являются неизменяемыми, поэтому вы не можете изменять элементы непосредственно в коллекции. Однако операции `updated`, `appended` и `prepended` создают новые ArraySeqs, которые отличаются от базового ArraySeq только в одном элементе: + +~~~ +scala> arr.updated(2, 4) +res26: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 4) +scala> arr +res27: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3) +~~~ + +Как видно из последней строки выше, вызов `updated` не влияет на исходный ArraySeq `arr`. + +ArraySeqs хранят свои элементы в приватном [Массиве](arrays.html). Таким образом достигается компактное представление и обеспечивается быстрый индексированный доступ к элементам, но обновление или добавление одного элемента занимает линейное время, так как требует создания другого массива и копирования всех элементов исходного массива. + +## Вектора (Vectors) + +В предыдущих разделах мы увидели, что `List` и `ArraySeq` эффективные структуры данных в некоторых специфичных ситуациях, но они неэффективны в других: например, добавление элемента происходит за постоянное время для `List`, но линейно для `ArraySeq`, и наоборот, индексированный доступ является постоянным для `ArraySeq`, но линейным для `List`. + +[Вектор](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) - тип коллекции, который обеспечивает хорошую производительность для всех своих операций. Вектора позволяют получить доступ к любому элементу последовательности за "практически" постоянное время. Это значит что константа больше, чем при получении переднего (`head`) элемента списка или при чтения элемента из ArraySeq, но, тем не менее, это константа. Избегайте использование векторов в алгоритмах базирующихся на активной работе с передними (`head`) элементами. Вектора могут получать доступ к элементам и изменять их в произвольных местах, что делает разработку более простой и удобной. + +Вектора создаются и модифицируются также как и другие последовательности. + + 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 + +Вектора представленны деревьями с высоким уровнем ветвления (уровень ветвления дерева или графа - это количество дочерних элементов у каждого узла). Каждый узел дерева содержит до 32х элементов вектора или содержит до 32х других узлов. Вектора с размером до 32х элементов могут быть представленны одним узлом. Вектора `32 * 32 = 1024` элементы могут быть представлены одним витком. +Для векторов с 215 элементами достаточно двух переходов от корня дерева до конечного элемента узла, трех переходов для векторов с 220 элементами, четырех переходов для 225 элементами и пяти переходов для 230 элементами. Таким образом, для всех векторов разумных размеров выбор элемента включает до 5 простых выборок массивов. Именно это мы подразумевали, когда писали, что доступ к элементам осуществляется с "практически постоянным временем". + +Также как и доступ к элементу, операция обновления в векторах занимает "практически" постоянное время. Добавление элемента в середину вектора может быть выполнено через копирование узла содержащего этот элемент и каждого ссылающегося на него узла, начиная от корня дерева. Это означает, что процесс обновления элемента создает от одного до пяти узлов, каждый из которых содержит до 32 элементов или поддеревьев. Это, конечно, дороже, чем просто обновление элемента в изменяемом массиве, но все же намного дешевле, чем копирование вообще всего вектора. + +Поскольку вектора обладают хорошим балансом между быстрой случайной выборкой и быстрым случайным обновлением элементов, они используются в качестве реализации неизменяемых индексированных последовательностей: + + scala> collection.immutable.IndexedSeq(1, 2, 3) + res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) + +## Неизменяемые Очереди (Immutable Queues) + +[Очередь](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html) это последовательность с [FIFO](https://ru.wikipedia.org/wiki/FIFO) (первым пришёл — первым ушёл). +Вы добавляете элемент в очередь методом `enqueue` и достаете элемент из очереди используя метод `dequeue`. Эти операции - выполняются за постоянное время. + +Вот как можно создать пустую неизменяемую очередь: + + scala> val empty = scala.collection.immutable.Queue[Int]() + empty: scala.collection.immutable.Queue[Int] = Queue() + +Вы можете добавить элемент в неизменяемую очередь используя `enqueue`: + + scala> val has1 = empty.enqueue(1) + has1: scala.collection.immutable.Queue[Int] = Queue(1) + +Чтобы добавить несколько элементов в очередь, испольуйте метод `enqueueAll` с коллекцией в качестве аргумента: + + scala> val has123 = has1.enqueueAll(List(2, 3)) + has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) + +Для удаления элемента из начала очереди используется команда `dequeue`: + + scala> val (element, has23) = has123.dequeue + element: Int = 1 + has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) + +Обратите внимание, что `dequeue` возвращает пару, состоящую из удаленного элемента и остальной части очереди. + +## Диапазоны (Ranges) + +[Диапазон](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) +представляет собой упорядоченную последовательность целых чисел, которые отделены друг от друга одинаковыми размерами. Например, "1, 2, 3" - это диапазон, так же как и "5, 8, 11, 14". Для создания диапазона в Scala используйте заготовленные методы `to` и `by`. + + scala> 1 to 3 + res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) + scala> 5 to 14 by 3 + res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) + +Если вы хотите создать диапазон, исключающий верхнюю границу, то для удобства используйте метод `until` вместо `to`: + + scala> 1 until 3 + res2: scala.collection.immutable.Range = Range(1, 2) + +Диапазоны занимают константный размер, потому что они могут быть определены только тремя цифрами: их началом, концом и значением шага. Благодаря этому представлению большинство операций на диапазонах выполняется очень быстро. + +## Compressed Hash-Array Mapped Prefix-trees + +Хэш деревья - это стандартный способ эффективного создания неизменяемых множеств и ассоциативных массивов (мап). [Compressed Hash-Array Mapped Prefix-trees](https://github.com/msteindorfer/oopsla15-artifact/) - это специальные хэш деревья для JVM, которые улучшают локальность и обеспечивают компактную и элегантную реализацию деревьев. Они базируются на классе [immutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html). Их представление очень похоже на реализацию векторов, которые также являются деревьями, где каждый узел имеет либо 32 элемента либо 32 поддерева. Но в данном случае ключ выбирается на основе хэш-кода. Например, чтобы найти ключ на мапе, сначала берут хэш-код ключа. Затем самые младшие 5 бит хэш-кода используются для выбора первого поддерева, за которым следуют следующие 5 бит и так далее. Выбор прекращается, когда для всех битов будут найдены ключи. + +Хэш деревья пытаются предоставить разумный баланс между достаточно быстрым поиском и достаточно эффективными операциями вставки (`+`) и удаления (`-`) элементов. Именно поэтому они лежат в основе стандартных реализаций Scala неизменяемых множеств и ассоциативных массивов (мап). На самом деле, в Scala есть дополнительная оптимизацию для неизменяемых множеств и мап, которые содержат менее пяти элементов. Множества и мапы от одного до четырех элементов хранятся как обычные объекты, которые содержат только элементы (или пары ключ/значение в случае мапы) как поля. Пустое неизменяемое множество и пустая неизменяемая мапа - это всегда объект-сингэлтон - нет необходимости размножать сущности для них, потому что пустое неизменяемое множество или мапа всегда будут оставаться пустыми. + +## Красно-Черные Деревья (Red-Black Trees) + +Красно-черные деревья представляют собой разновидность сбалансированного двоичного дерева, где одни узлы помечаются как "красные", а другие - как "черные". Как и любое сбалансированное двоичное дерево, операции над ним занимают по времени логарифм от количества элементов дерева. + +Scala предлагает реализацию неизменяемых множеств и мап, использующих красно-черное дерево, в классах [TreeSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) и [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) + +Красно-черные деревья - стандартная реализацией `SortSet` в Scala, поскольку они предоставляют эффективный итератор, который выдает все элементы в отсортированном порядке. + +## Неизменяемые Битовые Наборы (Immutable BitSets) + +[BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html) +представляет собой набор маленьких целых чисел в виде набора битов большего целого числа. Например, набор битов, содержащий 3, 2 и 0, будет представлен как целое число 1101 в двоичном виде, т.е. 13 в десятичном. + +Внутри битового набора используется массив 64-битных `Long`ов. Первый `Long` в массиве для целых чисел от 0 до 63, второй для чисел от 64 до 127 и так далее. Таким образом, наборы битов очень компактны до тех пор, пока наибольшее целое число в наборе меньше нескольких сотен или около того. + +Операции с битовым набором выполняются очень быстро. Проверка на наличие занимает постоянное время. Добавление элемента в набор занимает время, пропорциональное количеству `Long`ов в массиве битов, которых обычно совсем не много. Вот несколько простых примеров использования битового набора: + + 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 + +## VectorMaps + +[VectorMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/VectorMap.html) +представляет собой мапу, использующую и `Vector` ключей и `HashMap`. У него есть итератор, который возвращает все записи в порядке их вставки. + +~~~ +scala> val vm = scala.collection.immutable.VectorMap.empty[Int, String] +vm: scala.collection.immutable.VectorMap[Int,String] = + VectorMap() +scala> val vm1 = vm + (1 -> "one") +vm1: scala.collection.immutable.VectorMap[Int,String] = + VectorMap(1 -> one) +scala> val vm2 = vm1 + (2 -> "two") +vm2: scala.collection.immutable.VectorMap[Int,String] = + VectorMap(1 -> one, 2 -> two) +scala> vm2 == Map(2 -> "two", 1 -> "one") +res29: Boolean = true +~~~ + +Первые строки показывают, что содержимое `VectorMap` сохраняет порядок вставки, а последняя строка показывает, что `VectorMap` сравнимы с другими `Map` и что это сравнение не учитывает порядок элементов. + +## ListMaps + +[ListMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html) +представляет собой мапу в виде связанного списка пар ключ-значение. В общем, операции на связанной мапе могут потребовать обхода по всему связанному списку. Таким образом, время выполнении обхода на связанной мапе линейно зависит от размера мапы. На самом деле, для связанных мапов в Scala практически нет вариантов для использования, так как стандартные мапы практически всегда быстрее. Единственным возможным исключением из этого, является то, что мапа по каким-либо причинам построена таким образом, что первые элементы в списке запрашиваются намного чаще, чем все остальные. + + 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/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md b/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md new file mode 100644 index 0000000000..58462a7bb2 --- /dev/null +++ b/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md @@ -0,0 +1,162 @@ +--- +layout: multipage-overview +title: Реализации Изменяемых Коллекций + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 9 +previous-page: concrete-immutable-collection-classes +next-page: arrays + +language: ru + +--- + +Вы уже успели увидеть наиболее часто используемые неизменяемые типы коллекции, которые есть в стандартной библиотеке Scala. Настало время посмотреть на изменяемые (mutable) типы коллекции. + +## Array Buffers + +[ArrayBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) - буферизированный массив в своем буфере хранит массив и его размер. Большинство операций с буферизированным массивом выполняются с той же скоростью, что и с массивом, так как операции просто обращаются и изменяют исходный массив. Кроме того он может эффективно добавлять данные к своему концу. Присоединение элемента к такому массиву занимает амортизированное константное время. Поэтому буферизированные массивы будут полезны, если вы строите большую коллекцию данных регулярно добавляя новые элементы в конец. + + 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 + +похож на буферизированный массив, за исключением того, что он базируется на связанном списке, а не массиве. Если после создания буферизированного объекта, вы планируете преобразовать его в список, то лучше используйте `ListBuffer` вместо `ArrayBuffer`. + + 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 + +Так же, как буферизированный массив полезен для создания массивов, а буферизированный список полезен для построения списков, [StringBuilder](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html) полезен для создания строк. StringBuilders настолько широко используются, что они уже импортированы по умолчанию в стандартную область видимости. Можете создать их с помощью `new StringBuilder`, как в следующем примере: + + 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 + +## ArrayDeque + +[ArrayDeque](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayDeque.html) +это последовательность, поддерживающая эффективное добавление элементов как спереди, так и сзади. +Реализован на основе массива с изменяемым размером. + +Если вам нужно добавить элементы к началу или концу буфера, используйте `ArrayDeque` вместо `ArrayBuffer`. + +## Queues (Очереди) + +Scala предоставляет не только неизменяемые, но и изменяемые очереди. Работать с изменяемой очередью - `mQueue` можно аналогично неизменяемой, но вместо `enqueue` для добавления элементов используете операторы `+=` и `++=` . Кроме того, в изменяемой очереди метод `dequeue` просто удалит передний элемент из очереди, возвратив его в качестве результата работы метода. Например: + + 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) + +## Stacks (Стэки) + +Вы видели неизменяемые стэки раньше. Существует еще и изменяемая версия, предоставляемая классом [mutable.Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html). Который работает точно так же, как и неизменяемая версия, за исключением того, что изменения происходят прямо в нём самом. + + 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) + +## ArraySeqs (Изменяемый Последовательные Массивы) + +Последовательные массивы - это изменяемые массивы со свойствами последовательности фиксированного размера, которые хранят свои элементы внутри `Array[Object]`. Они реализованы в Scala классом [ArraySeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html). + +Вам стоит использовать `ArraySeq`, если вам нужен массив из-за его показателей производительности, но вы дополнительно хотите использовать обобщеные экземпляры последовательности, в которых вы не знаете тип элементов и у которого нет `ClassTag` который будет предоставлен непосредственно во время исполнения. Эти аспекты рассматриваются в разделе [arrays]({{ site.baseurl }}/overviews/collections/arrays.html). + +## Hash Tables (Хэш Таблицы) + +Хэш-таблица хранит свои элементы в массиве, помещая каждый элемент на ту позицию, которая определяется хэш-кодом этого элемента. Добавление элемента в хэш-таблицу занимает константное время, если в массиве ещё нет другого элемента с таким же хэш-кодом. Таким образом, хэш-таблицы работают очень быстро, до тех пор пока размещенные в них объекты имеют хорошее распределение хэш-кодов. Поэтому в Scala изменяемые мапы и множества основываются на хэш-таблицах. Чтоб получить доступ к ним, можно использовать классы - [mutable.HashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html) и [mutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html). + +Хэш-таблицы и хэш-мапы используются так же, как и любая другая мапа или множество. Вот несколько простых примеров: + + 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 + +При итерировании по хэш-таблице нет никаких гарантий по поводу порядка обхода. Итерирование проходит по базовому массиву в произвольном порядке. Чтобы получить гарантированный порядок обхода, используйте _связанную_ хэш-мапу (`linked Hashmap`) или множество (`linked Set`) вместо обычной. Связанная хэш-мапа или множество похожи на обычную, за исключением того, что между их элементами есть связь выстроенная в том порядке, в котором эти элементы были добавлены. Поэтому итерирование по такой коллекции всегда происходит в том же порядке, в котором элементы и были добавлены. + +## Weak Hash Maps (Ослабленные Хэш-Мапы) + +Ослабленный хэш-мап это специальный вид хэш-мапы, при которой сборщик мусора не ходит по ссылкам с мапы к её ключам. Это означает, что ключ и связанное с ним значение исчезнут из мапы, если на ключ не будет никаких ссылок. Ослабленные хэш-мапы полезны для таких задач, как кэширование, в которых вы можете переиспользовать результат дорогостоящей функции, сохранив результат функции и вызывая его снова используя тот же аргумент в качестве ключа. Если результаты работы будут хранить на обычной хэш-мапе, мапа будет расти без ограничений и ни один из ключей никогда не будет утилизирован сборщиком мусора. Использование ослабленной хэш-мапы позволяет избежать этой проблемы. Данные из ослабленной хэш-мапы удаляются, как только ключ становится недоступным. Ослабленные хэш-мапы в Scala реализованы классом [WeakHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html), который в свою очередь является оберткой над Java `java.util.WeakHashMap`. + +## Concurrent Maps (Конкурентные Мапы) + +Несколько потоков могут получить паралелльный доступ к такой мапе на [конкуретной основе](https://en.wikipedia.org/wiki/Concurrent_data_structure). В дополнение к обычным операциям на [Map](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html) добавленны следующие атомарные операции: + +### Операции на классе concurrent.Map + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| `m.putIfAbsent(k, v)` | Добавляет пару ключа/значение `k -> v`, если `k` отсутствует в `m`. | +| `m.remove(k, v)` |Удаляет запись для `k`, если для этого ключа соответствует значение `v`. | +| `m.replace(k, old, new)` |Заменяет значение, связанное с ключом `k` на `new`, если ранее оно было равно `old`. | +| `m.replace (k, v)` | Заменяет значение, связанное с ключом `k` на `v`, если ранее значение вообще существовало.| + +`concurrent.Map` это трейт в библиотеке коллекций Scala. В настоящее время он реализуется двумя способами. Первый - через Java мапу `java.util.concurrent.ConcurrentMap`, который может быть автоматически преобразован в Scala мапу с помощью [стандартного механизма преобразования Java/Scala коллекций]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html). Вторая реализация через [TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), которая представляет из себя не блокируемую хэш-таблицу привязанную к дереву. + +## Mutable Bitsets (Изменяемый Битовый Набор) + +Изменяемый набор типа [mutable.BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html) практически такойже как и неизменяемый набор, за исключением того, что он при изменении сам меняется. Изменяемые битовые наборы немного эффективнее при обновлении, чем неизменяемые, так как им не нужно копировать `Long`, которые не изменились. + + 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/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md b/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md new file mode 100644 index 0000000000..ba656a22ec --- /dev/null +++ b/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md @@ -0,0 +1,65 @@ +--- +layout: multipage-overview +title: Преобразования между Java и Scala коллекциями + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 17 +previous-page: creating-collections-from-scratch + +language: ru + +--- + +Как и в Scala, в Java есть богатая библиотека коллекций. Между ними много общего. Например, обе библиотеки предоставляют итераторы, итерируемые сущности, множества, мапы и списки. Но есть и серьезные различия. В частности, библиотека Scala фокусируют больше внимания на неизменяемых коллекциях, предоставляя больше возможностей для преобразования исходной коллекции в новую. + +Иногда вам может понадобиться передать данные из одного фреймворка с коллекциями в другой. Например, вам может понадобиться доступ к существующей коллекции в Java, как если бы это была коллекция Scala. Или вы захотите передать одну из коллекций Scala методу в Java, который ожидает схожую коллекцию из Java. Сделать это довольно просто, потому что Scala предоставляет неявные преобразования всех основных типов коллекций используя [JavaConverters](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConverters$.html) объект. В частности, вы найдете двухсторннее преобразование между следующими типами: + + 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 + +Чтобы задействовать эти неявные преобразования, просто импортируйте объект [JavaConverters](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConverters$.html) : + + scala> import collection.JavaConverters._ + import collection.JavaConverters._ + +Это позволит преобразовывать коллекции Scala в соответствующие коллекции Java с помощью методов расширения, называемых `asScala` и `asJava`: + + scala> import collection.mutable._ + import collection.mutable._ + + scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava + jul: java.util.List[Int] = [1, 2, 3] + + scala> val buf: Seq[Int] = jul.asScala + buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + + scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2).asJava + m: java.util.Map[String,Int] = {abc=1, hello=2} + +Внутри эти преобразования работают путем установки объекта "обертки", который перенаправляет все операции на базовый объект коллекции. Таким образом, коллекции никогда не копируются при конвертировании между Java и Scala. Интересным свойством является то, что если вы выполняете преобразование из типа Java в соответствующий тип Scala и обратно в тот же тип Java, вы получаете идентичный объект коллекции, с которого начали. + +Некоторые коллекции Scala могут быть преобразованы в Java, но не могут быть преобразованы обратно в исходный тип Scala: + + Seq => java.util.List + mutable.Seq => java.util.List + Set => java.util.Set + Map => java.util.Map + +Поскольку Java не различает изменяемые и неизменяемые коллекции по их типам, преобразование из, скажем, `scala.immutable.List` даст результат `java.util.List`, в котором любые операции преобразования кидают исключение "UnsupportedOperationException". Вот пример: + + scala> val jul = List(1, 2, 3).asJava + jul: java.util.List[Int] = [1, 2, 3] + + scala> jul.add(7) + java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:148) diff --git a/_ru/overviews/collections-2.13/creating-collections-from-scratch.md b/_ru/overviews/collections-2.13/creating-collections-from-scratch.md new file mode 100644 index 0000000000..e705778625 --- /dev/null +++ b/_ru/overviews/collections-2.13/creating-collections-from-scratch.md @@ -0,0 +1,65 @@ +--- +layout: multipage-overview +title: Создание коллекций с нуля + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 16 +previous-page: iterators +next-page: conversions-between-java-and-scala-collections + +language: ru + +--- + +У вас есть синтаксис `List(1, 2, 3)` для создания списка из трех целых чисел и `Map('A' -> 1, 'C' -> 2)` для создания мапы с двумя элементами. На самом деле, это универсальная функциональность коллекций Scala. Можно получить любую коллекцию написав ее название и указав следом список элементов в круглых скобках. В результате получится новая коллекция с заданными элементами. Вот еще несколько примеров: + + Iterable() // Пустая коллекция + List() // Пустой список + List(1.0, 2.0) // Список с элементами 1.0, 2.0 + Vector(1.0, 2.0) // Вектор с элементами 1.0, 2.0 + Iterator(1, 2, 3) // Итератор возвращающий три целых числа. + Set(dog, cat, bird) // Множество с тремя объектами + HashSet(dog, cat, bird) // Хэш-множество с темиже объектами + Map('a' -> 7, 'b' -> 0) // Мапа с привязкой цифр к буквам + +Каждая из вышеперечисленных строк это вызов метода `apply` какого-то объекта. Например, третья строка выше разворачивается следующим образом + + List.apply(1.0, 2.0) + +Так что это вызов метода `apply` объекта-компаньона класса `List`. Этот метод берет произвольное количество аргументов и строит из них список. Каждый класс коллекций библиотеки Scala имеет объект-компаньон с таким `apply` методом. Не имеет значения, является ли класс конкретной реализацией коллекции, такой как `List`, `LazyList`, `Vector`, или это абстрактный базовый класс, такой как `Seq`, `Set` или `Iterable`. В последнем случае вызов `apply` приведет к некой реализации по умолчанию для базового абстрактного класса. Примеры: + + scala> List(1, 2, 3) + res17: List[Int] = List(1, 2, 3) + scala> Iterable(1, 2, 3) + res18: Iterable[Int] = List(1, 2, 3) + scala> mutable.Iterable(1, 2, 3) + res19: scala.collection.mutable.Iterable[Int] = ArrayBuffer(1, 2, 3) + +Помимо `apply`, каждый объект-компаньон коллекции еще определяет элемент `empty`, который возвращает пустую коллекцию. Так что вместо `List()` можно написать `List.empty`, вместо `Map()`, `Map.empty` и так далее. + +Операции, предоставляемые объектом-компаньоном коллекции, обобщены в следующей таблице. Короче говоря. + +* `concat`, которая объединяет произвольное количество коллекций, +* `fill` и `tabulate`, которые генерируют одно- или многомерные коллекции заданных размеров, инициализированные некоторой табулируемой функцией или выражением, +* `range`, которая генерирует целочисленные коллекции с постоянной длиной шага, +* `iterate` и `unfold`, который генерирует коллекцию в результате многократного применения функции к начальному элементу или состоянию. + +### Производящие методы для последовательностей + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| `C.empty` | Пустая коллекция. | +| `C(x, y, z)` | Коллекция состоящая из элементов `x, y, z`. | +| `C.concat(xs, ys, zs)` | Коллекция, полученная путем объединения элементов `xs, ys, zs`. | +| `C.fill(n){e}` | Коллекция длины `n`, где каждый элемент вычисляется выражением`e`. | +| `C.fill(m, n){e}` | Коллекция коллекций размерности `m×n`, где каждый элемент вычисляется выражением `e`. (существует и в более высоких измерениях). | +| `C.tabulate(n){f}` | Коллекция длины `n`, где элемент каждого индекса i вычисляется с помощью `f(i)`. | +| `C.tabulate(m, n){f}` | Коллекция коллекций измерений `m×n`, где элемент каждого индекса `(i, j)` вычисляется с помощью `f(i, j)`. (существует также в более высоких измерениях). | +| `C.range(start, end)` | Коллекция из целых чисел от `start` до `end-1`. | +| `C.range(start, end, step)`| Коллекция целых чисел, начиная со `start` и продвигаясь на шаг `step` увеличиваясь до конечного значения `end` (не включая его самого) . | +| `C.iterate(x, n)(f)` | Коллекция длины `n` с элементами `x`, `f(x)`, `f(f(x))`, ... | +| `C.unfold(init)(f)` | Коллекция, использующая функцию `f` для вычисления следующего элемента и состояния, начиная с начального состояния `init`.| \ No newline at end of file diff --git a/_ru/overviews/collections-2.13/equality.md b/_ru/overviews/collections-2.13/equality.md new file mode 100644 index 0000000000..b63d7165f7 --- /dev/null +++ b/_ru/overviews/collections-2.13/equality.md @@ -0,0 +1,37 @@ +--- +layout: multipage-overview +title: Равенство + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 13 +previous-page: performance-characteristics +next-page: views + +language: ru + +--- + +Коллекций придерживаются единого подхода к определению равенства и получению хэшей. Идея состоит, во-первых, в том, чтобы разделить коллекции на группы: множества, мапы и последовательности. Коллекции в разных группах всегда различаются. Например, `Set(1,2,3)` неравнозначен списку `List(1,2,3)`, хотя они содержат одни и те же элементы. С другой стороны, в рамках одной и той же группы коллекции равны тогда и только тогда, когда они имеют одинаковые элементы (для последовательностей: одни и те же элементы в одном порядке). Например `List(1, 2, 3) == Vector(1, 2, 3)`, и `HashSet(1, 2) == TreeSet(2, 1)`. + +Для проверки на равенство не важно, является ли коллекция изменяемой или неизменяемой. Для изменяемой коллекции достаточно просто рассмотреть ее текущие элементы на момент проведения проверки на равенство. Это означает, что коллекция в разные моменты может быть эквивалентна разным коллекциям, в зависимости от того, какие элементы в неё добавляются или удаляются. В этом кроится потенциальная опасность при использовании изменяемой коллекции в качестве ключа для хэшмапы. Пример: + + 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) + +В этом примере запрос в последней строке, скорее всего, не сработает, так как хэш-код массива `buf` изменился во второй с конца строке. Таким образом, в поиске элемента на основе хэша, будет запрашиваться совсем другой элемент, чем тот что был первоначально сохранен в `map`. \ No newline at end of file diff --git a/_ru/overviews/collections-2.13/introduction.md b/_ru/overviews/collections-2.13/introduction.md new file mode 100644 index 0000000000..c965ef979e --- /dev/null +++ b/_ru/overviews/collections-2.13/introduction.md @@ -0,0 +1,67 @@ +--- +layout: multipage-overview +title: Введение + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 1 +next-page: overview + +language: ru + +--- + +**Martin Odersky и Lex Spoon** + +Фреймворк коллекций является основой стандартной библиотеки Scala 2.13. Он обеспечивает общие схемы и подходы распостроняемые на все типы коллекций. +Этот фреймворк позволяет вам работать с данными на высоком уровне абстракции, в котором основными строительным блоком программы становятся коллекцкии целиком, а не её отдельные элементы. + +К такому стилю программирования необходимо небольшое привыкание. +К счастью, адаптация облегчается приятными свойствами характерными для новых коллекций Scala. +Они обладают простотой в использовании, лаконичностью, безопасностью и универсальностью. + +**Простота:** Небольшого набора из 20-50 методов достаточно для решения большинства задач. +Нет необходимости использовать запутанные циклы или рекурсии. +Персистентные коллекции и операции без побочных эффектов означают, +что вам не нужно беспокоиться о случайном повреждении существующих коллекций новыми данными. +Больше нет путаницы из-за использования итераторов и одновременного изменения коллекций. + +**Лаконичность:** Одной командой можно достичь, столько же, сколько обычно получают используя несколько вложенных операций с циклами. +Вы можете выразить функциональные операции с помощью простого синтаксиса и с легкостью сочетать операции, создавая ощущение +работы со специализированным под задачу языком. + +**Безопасность:** Требуется определенный опыт, чтоб погрузится в эту специфику. +Статически типизированная и функциональная природа коллекций Scala подразумевает, что подавляющее большинство ошибок, которые вы можете совершить, будут пойманы во время компиляции. +Это потому что: + 1. Коллекции сами по себе очень активно используются, поэтому хорошо протестированы. + 2. Использование операции в коллекциях создает явное описание того, что мы ожидаем для входящих данных и для результата. + 3. Это явное описание для входа и результата подвергается статической проверке типа. В результате подавляющее большинство проблем будет проявляться в виде ошибок типов. +Поэтому запуск программ из нескольких сотен строк, с первого раза, вполне типичная ситуация. + + +**Скорость:** Все операции в коллекциях уже настроены и оптимизированы. В результате, работа коллекций получается очень эффективной. +Можно конечно сделать немного более эффективную настройку структур данных и операций с ними, но при этом, +вероятно, вы получите хуже эффективность в непосредственной реализации и использовании. +Кроме того, коллекции оптимизированы для параллельного исполнения на нескольких ядрах. Параллельные коллекции поддерживают те же операции, что и последовательные, поэтому нет необходимости в изучении новых операций или переписывании кода. +Вы можете превратить последовательную коллекцию в параллельную просто вызвав метод `par`. + +**Универсальность:** Коллекции обеспечивают одинаковые операции на любом типе коллекций, где это имеет смысл. Таким образом, можно достичь многого, имея сравнительно небольшой набор операций. Например строка, в принципе, представляет собой последовательность символов. Следовательно, в коллекциях Scala строки поддерживают все операции которые есть у последовательностей (`Seq`). То же самое относится и к массивам. + +**Пример:** Вот лишь одна строчка кода, демонстрирующая преимущества Scala коллекций. + + val (minors, adults) = people partition (_.age < 18) + +Сразу становится понятно, что делает эта строчка: она разделяет коллекцию людей `people` на несовершеннолетних `minors` и взрослых `adults` в зависимости от возраста `age`. +Поскольку метод `partition` определен в базовом типе коллекции `TraversableLike`, этот код работает для любого типа коллекций, включая массивы. +Полученные в результате коллекции `minors` и `adults` будут иметь тот же тип, что и коллекция `people` . + +Этот код намного лаконичнее, чем несколько циклов, необходимых для того, чтоб провести обработку традиционным методом +(три цикла для массива, т.к. промежуточные результаты должны быть сохранены где-то в другом месте). +Как только вы освоите базовую схему работы с коллекциями, вы оцените на сколько проще и безопаснее написание такого кода, в отличии от более низкоуровневого кода с циклами. +Кроме того, сама операция `partition` работает довольно быстро и будет работать еще быстрее на многоядерных процессорах при использовании параллельных коллекций. (Параллельные коллекции стали частью Scala начиная с версии 2.9.) + +В этом разделе подробно рассматриваются API классов коллекций Scala с пользовательской точки зрения. +Вы также познакомитесь со всеми фундаментальными классами и методами, которые есть у коллекций. diff --git a/_ru/overviews/collections-2.13/iterators.md b/_ru/overviews/collections-2.13/iterators.md new file mode 100644 index 0000000000..b755b5685e --- /dev/null +++ b/_ru/overviews/collections-2.13/iterators.md @@ -0,0 +1,239 @@ +--- +layout: multipage-overview +title: Итераторы + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 15 +previous-page: views +next-page: creating-collections-from-scratch + +language: ru + +--- + +Итератор (Iterator) - это не коллекция, а скорее способ поочередного доступа к элементам коллекции. Есть две основные операции у итератора - это `next` и `hasNext`. Вызов метода `it.next()` на итераторе `it` вернет следующий элемент и изменит его состояние. Повторный вызов `next` на том же итераторе выведит следующий элемент идущий после ранее возвращённого. Если больше нет элементов для возврата, вызов команды `next` кинет исключение `NoSuchElementException`. Вы можете узнать, есть ли еще элементы для возврата с помощью метода `hasNext` у [Итератора](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). + +Самый простой способ "обойти" все элементы, возвращаемые итератором `it` - это использование циклов с while-loop: + + while (it.hasNext) + println(it.next()) + +У итераторов в Scala есть аналоги большинству методов, которые вы можете найдти в классах `Traversable`, `Iterable` и `Seq`. Например, метод "foreach", который на итераторе выполняет процедуру на каждом элементе, возвращаемого итератором. Используя `foreach`, описанный выше цикл можно сократить до: + + it foreach println + +Как всегда, "for выражения" могут быть использованы в качестве альтернативного синтаксиса для выражений, включающих в себя `foreach`, `map`, ` WithFilter` и `flatMap`, поэтому еще одним способом вывести все элементы, возвращаемые итератором, будет: + + for (elem <- it) println(elem) + +Существует важное различие между методом foreach на итераторах и тем же методом на _traversable_ коллекциях: При вызове метода `foreach` на итераторе, итератор остается в конце, указывая что все элементы закончились. Поэтому очередной вызов `next` на томже самом итераторе выбросит исключение `NoSuchElementException`. В отличие от этого, при обращении к коллекции, команда `foreach` оставляет количество элементов в коллекции неизменным (если только переданная функция не добавляет элементов, но это крайне не желательно, так как может привести к неожиданным результатам). + +Другие общие операции между `Iterator` и `Iterable`, имеют одни и те же свойства. Например, итераторы предоставляют метод `map`, который возвращает новый итератор: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = + scala> it.map(_.length) + res1: Iterator[Int] = + scala> it.hasNext + res2: Boolean = true + scala> res1 foreach println + 1 + 6 + 2 + 5 + scala> it.hasNext + res4: Boolean = false + +Как видите, после вызова функции `it.map` итератор `it` не остановился в конце, в отличии от `res1.foreach` который проходит через весь итератор, после чего `it` остается в самом конце. + +Другой пример - метод `dropWhile`, который можно использовать для поиска первых элементов итератора, соответствующих условию. Например, чтобы найти первое слово в итераторе выше, которое содержит хотя бы два символа, можно было бы написать: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = + scala> it dropWhile (_.length < 2) + res4: Iterator[java.lang.String] = + scala> res4.next() + res5: java.lang.String = number + +Обратите внимание еще раз, что сам итератор`it` был изменен вызовом `dropWhile`: теперь он указывает на второе слово `number` в списке. +Фактически, `it` и результат `res4` возвращаемый после`dropWhile` возвращают одну и туже последовательность элементов. + +Чтоб избежать такое поведение, как вариант можно использовать `duplicate` (дублировать используемый итератор), вызывая методы на разных итераторах. +Каждый из _двух_ итераторов будет обрабатывать точно такие же элементы, как и тот из которого состоит итераторатор `it`: + + scala> val (words, ns) = Iterator("a", "number", "of", "words").duplicate + words: Iterator[String] = + ns: Iterator[String] = + + scala> val shorts = words.filter(_.length < 3).toList + shorts: List[String] = List(a, of) + + scala> val count = ns.map(_.length).sum + count: Int = 14 + +Оба итератора работают независимо друг от друга: продвижение одного не влияет на другого, так что каждый из них может быть модифицирован независимо от другого. +Может создаться впечатление что итератор подвергается двойному обходу над элементами, но это не так, результат достигается за счет внутреннего буферизации. +Как обычно, базовый итератор `it` не пригоден для прямого использования и должен быть исключен из дальнейших операций. + +Обобщая вышесказанное, итераторы ведут себя как коллекции _если после вызова метода на них сам итератор больше не вызывается_. В библиотеке коллекции Scala это достигается явным образом с помощью абстракции [IterableOnce](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IterableOnce.html), который является общим суперкласом для [Iterable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) и [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). У `IterableOnce[A]` только два метода: `iterator: Iterator[A]` и `knownSize: Int`. + +Если объект `IterableOnce` является `Iterator`, то его операция `iterator` всегда возвращает себя, в своем текущем состоянии, но если он `Iterable`, то операция `iterator` всегда возвращает новый `Iterator`. Типовой вариант использования `IterableOnce` - в качестве типа аргумента для методов, которые могут принимать или итератор или коллекцию в качестве аргумента. Примером может служить метод соединения `concat` в классе `Iterable`. Он принимает `IterableOnce` параметр, поэтому вы можете соединять элементы, поступающие или из итератора или коллекции. + +Все операции на итераторах собраны в таблице ниже. + +### Операции на классе Iterator + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Абстрактные Методы:** | | +| `it.next()` | Возвращает следующий элемент итератора переводя его _позицию_ на следующее место. | +| `it.hasNext` | Возвращает `true` если итератор `it` может выдать следующий элемент. | +| **Варьирования:** | | +| `it.buffered` | Буффиризированный итератор возвращающий все элементы `it`. | +| `it grouped size` | Итератор, который производит элементы от `it` в последовательностях фиксированного размера ("чанках"). | +| `it sliding size` | Итератор, который производит элементы от `it` в последовательностях полученных от прохождения окна фиксированного размера над элементами итератора. | +| **Дублирования:** | | +| `it.duplicate` | Пара итераторов, каждый из которых может независимо возвращать все элементы `it`. | +| **Сложения:** | | +| `it concat jt`
либо `it ++ jt` | Итератор, возвращающий все элементы, от итератора `it`, за которым следуют все элементы, возвращаемые итератором `jt`. | +| `it.padTo(len, x)` | Итератор, который в начале возвращает все элементы `it` а затем следуют копии `x` пока не будет достигнута длина `len`. | +| **Отображения:** | | +| `it map f` | Итератор, получаемый при применении функции `f` к каждому элементу, возвращаемому из `it`. | +| `it flatMap f` | Итератор, получаемый при применении производящей итераторы функции `f` к каждому элементу `it` с последующим объединением результатов. | +| `it collect f` | Итератор, получаемый при применении частично определенной функции `f` к каждому элементу `it` для которого она определена с последующим сбором результатов. | +| **Преобразования:** | | +| `it.toArray` | Собирает элементы, полученные от `it` в массив. | +| `it.toList` | Собирает элементы, полученные от `it` в список. | +| `it.toIterable` | Собирает элементы, полученные от `it` в итерируемую сущность. | +| `it.toSeq` | Собирает элементы, полученные от `it` в последовательность. | +| `it.toIndexedSeq` | Собирает элементы, полученные от `it` в индексируюмую последовательность. | +| `it.toLazyList` | Собирает элементы, полученные от `it` в ленивый лист. | +| `it.toSet` | Собирает элементы, полученные от `it` во множество. | +| `it.toMap` | Собирает пары ключ/значение, полученные от `it` в мапу. | +| **Копирования:** | | +| `it.copyToArray(arr, s, n)`| Копирует максимум `n` элементов, возвращаемых `it` в массив `arr`, начиная с индекса `s`. Два последних аргумента необязательные.| +| **Иформации о размере:** | | +| `it.isEmpty` | Проверяет, пуст ли итератор ( в противоположность `hasNext` ). | +| `it.nonEmpty` | Проверяет, содержит ли коллекция элементы (псевдоним для `hasNext`). | +| `it.size` | Выдает количество элементов итератора `it`. Примечание: после этой операции `it` будет находится в конце! | +| `it.length` | Тоже что и `it.size`. | +| `it.knownSize` |Выдает количество элементов итератора, если их можно узнать без изменения состояния итератора, иначе `-1`. | +| **Поиск и получение элементов:**| | +| `it find p` | Опшен возвращаемый из `it`, содержащий первый элемент, который удовлетворяет условию `p`, или `None`, если ни один из элементов не подходит. Примечание: итератор находится на следующем после найденного элемента или в конце, если ни одного не найдено. | +| `it indexOf x` | Индекс первого элемента, возвращаемого `it`, который равен `x`. Примечание: итератор перемещается в позицию после найденого элемента. | +| `it indexWhere p` | Индекс первого элемента, возвращаемого `it`, который удовлетворяет условию `p`. Примечание: итератор перемещается в позицию после этого элемента. | +| **Дочернии итераторы:** | | +| `it take n` | Итератор, возвращающий первые `n` элементов `it`. Примечание: он переместится в позицию после `n` элемента, или в конец, если содержит меньше, чем `n` элементов.| +| `it drop n` | Итератор, который начинается с `(n+1)` элемента `it`. Примечание: `it` переместится в ту же самую позицию. | +| `it.slice(m,n)` | Итератор, возвращающий фрагмент элементов из `it`, начиная с `m` элемента и заканчивая перед `n` элементом. | +| `it takeWhile p` | Итератор, возвращающий элементы из `it` до тех пор, пока условие `p` истинно. | +| `it dropWhile p` | Итератор пропускает элементы из `it` до тех пор, пока условие `p` является истинным, после чего возвращает остаток.| +| `it filter p` | Итератор, возвращающий все элементы из `it`, удовлетворяющие условию `p`. | +| `it withFilter p` | То же самое, что и `it filter p`. Требуется для возможности использовать итераторы в _for-выражениях_. | +| `it filterNot p` | Итератор, возвращающий все элементы из `it`, которые не удовлетворяют условию `p`. | +| `it.distinct` | Итератор, возвращающий элементы из `it` без дубликатов. | +| **ПодГруппы:** | | +| `it partition p` | Разделяет `it` на пару из двух итераторов: один возвращает все элементы из `it`, которые удовлетворяют предикату `p`, а другой возвращает все элементы из `it`, которые не удовлетворяют. | +| `it span p` | Разделяет `it` на пару из двух итераторов: один возвращает все начальные элементы `it`, удовлетворяющие предикату `p`, а другой возвращает все остальные элементы `it`. | +| **Сведения об элементах:** | | +| `it forall p` | Логический показатель, указывающий, все ли элементы из `it` соответствуют условию `p`. | +| `it exists p` | Логический показатель, указывающий, есть ли хоть один элемент из `it`, который соответствует условию `p`. | +| `it count p` | Возвращает количество элементов из `it`, удовлетворяющих предикату `p`.| +| **Свертки:** | | +| `it.foldLeft(z)(op)` | Применяет двустороннюю операцию `op` между последовательно идущими элементами, возвращаемыми итератором `it`, слева направо и начинающимися с `z`. | +| `it.foldRight(z)(op)` | Применяет двустороннюю операцию `op` между последовательно идущими элементами, возвращаемыми итератором `it`, справа налево и начинающимися с `z`. | +| `it reduceLeft op` | Применяет двустороннюю операцию `op` между последовательно идущими элементами, возвращаемыми итератором `it`, идущие слева направо. | +| `it reduceRight op` | Применяет двустороннюю операцию `op` между последовательно идущими элементами, возвращаемыми итератором `it`, идущие справа налево. | +| **Специальные Свертки:** | | +| `it.sum` | Сумма значений числовых элементов, возвращаемых итератором `it`. | +| `it.product` | Произведение значений числовых элементов, возвращаемых итератором `it`. | +| `it.min` | Минимальное из значений элементов у которых есть порядок, возвращаемых итератором `it`. | +| `it.max` | Максимальное из значений элементов у которых есть порядок, возвращаемых итератором `it`. | +| **Связывания:** | | +| `it zip jt` | Итератор состоящий из пар соответствующих элементов, возвращаемых итераторами `it` и `jt` | +| `it.zipAll(jt, x, y)` | Итератор состоящий из пар соответствующих элементов, возвращаемых итераторами `it` и `jt` , где более короткий итератор расширяется, чтобы соответствовать более длинному, добавляя элементы `x` или `y`. | +| `it.zipWithIndex` | Итератор состоящий из пар элементов итератора `it` и его индекса | +| **Обновления:** | | +| `it.patch(i, jt, r)` | Итератор, получаемый из `it`, заменив `r` элементов, начиная с `i` на итератор `jt`. | +| **Сравнения:** | | +| `it sameElements jt` | Проверяет, возвращают ли итераторы `it` и `jt` одни и те же элементы в одном и том же порядке. Примечание: Использование итераторов после этой операции не определено и может быть изменено в дальнейшем. | +| **Строковые:** | | +| `it.addString(b, start, sep, end)`| Добавляет строку в `StringBuilder` `b`, который выводит все элементы `it` через разделитель `sep`, заключенный между строками `start` и `end`. `start`, `sep`, `end` - необязательные параметры.| +| `it.mkString(start, sep, end)` | Преобразует коллекцию в строку, которая выводит все элементы `it` через разделитель `sep`, заключенный между строками `start` и `end`. `start`, `sep`, `end` - необязательные параметры.| + +### Ленивость + +В отличие от операций непосредственно на конкретных коллекциях типа `List`, операции на `Iterator` ленивы. + +Ленивая операция не сразу вычисляет результаты. Вместо этого она рассчитывает результаты тогда когда они непосредственно запрашиваются. + +Поэтому выражение `(1 to 10).iterator.map(println)` не выведет ничего на экран. +Метод `map` в данном случае не применяет функцию в аргументе к значениям в диапазоне, вместо этого будет возвращен новый `Iterator`, который будет выполнять операции тогда когда будет запрошен их результат. Добавление `.toList` в конец этого выражения фактически вызовет вывод элементов на печать. + +Как следствие такие методы как `map` или `filter` не обязательно применят функцию в аргументе ко всем входным элементам. Выражение `(1 to 10).iterator.map(println).take(5).toList` выводит только значения от `1` до `5`, поскольку это те значения, которые запрашиваются у `Iterator`, возвращаемого из `map`. + +Это одна из причин, того почему важно использовать только чистые функции в качестве аргументов для `map`, `filter`, `fold` и подобных методов. Помните, что чистая функция не имеет побочных эффектов, поэтому `println` обычно не используется в `map`. Здесь `println` используется лишь для демонстрации "ленивости", которую с чистыми функциями не заметно. + +Ленивость ценна, несмотря на то, что часто невидима, так как она может предотвратить ненужные вычисления и позволить работать с бесконечными последовательностями, как в следующем примере: + + def zipWithIndex[A](i: Iterator[A]): Iterator[(Int, A)] = + Iterator.from(0).zip(i) + +### Буферезированные итераторы + +Иногда вам нужен итератор, который может "заглянуть вперед", чтобы вы могли оценить следующий элемент, который будет возвращен без перемещения позиции. Рассмотрим, например, задачу пропуска впереди идущих пустых строк из итератора, который возвращает последовательность строк. У вас может возникнуть соблазн написать следующее + + + def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} + +Но если присмотреться к этому коду внимательнее, то становится понятно, что такой код не корректен: код действительно пропустит ведущие пустые строки, но он также пропустит первую непустую строку `it`! + +Решение этой проблемы заключается в использовании буферизированного итератора. Класс [BufferedIterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) базирующийся на классе [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) предоставляет один дополнительный метод, `head`. Вызов `head` на буферизированном итераторе вернет его первый элемент, но не продвинет итератор дальше. С помощью буферизированного итератора можно пропустить пустые слова следующим образом. + + def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } + +Каждый итератор может быть преобразован в буферизированный после вызова метода `buffered`. Например: + + scala> val it = Iterator(1, 2, 3, 4) + it: Iterator[Int] = + scala> val bit = it.buffered + bit: scala.collection.BufferedIterator[Int] = + scala> bit.head + res10: Int = 1 + scala> bit.next() + res11: Int = 1 + scala> bit.next() + res12: Int = 2 + scala> bit.headOption + res13: Option[Int] = Some(3) + +Обратите внимание, что вызов метода `head` на буферизированном итераторе `bit` не продвигает его вперед. Поэтому последующий вызов `bit.next()` возвращает то же значение, что и `bit.head`. + +Как обычно, базовый итератор не должен в дальнейшем использоваться напрямую и должен быть исключен из операций. + +Буферизированный итератор буферизирует только следующий элемент, когда вызывается `head`. Другие производные итераторы, например произведенные от вызова `duplicate` и `partition`, могут буферизировать любое количество подпоследовательностей дочерних итераторов. Впрочем, итераторы могут быть эффективно объединены используя метод `++`: + + scala> def collapse(it: Iterator[Int]) = if (!it.hasNext) Iterator.empty else { + | var head = it.next + | val rest = if (head == 0) it.dropWhile(_ == 0) else it + | Iterator.single(head) ++ rest + | } + collapse: (it: Iterator[Int])Iterator[Int] + + scala> def collapse(it: Iterator[Int]) = { + | val (zeros, rest) = it.span(_ == 0) + | zeros.take(1) ++ rest + | } + collapse: (it: Iterator[Int])Iterator[Int] + + scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList + res14: List[Int] = List(0, 1, 2, 3, 4) + +В первой версии, любые встречаемые нули сбрасываются, а затем строится требуемый результат как объединенный итератор, который в свою очередь просто вызывает два входящих в него итератора. +Во втором варианте `collapse` неиспользованные нули буферизируются внутри. diff --git a/_ru/overviews/collections-2.13/maps.md b/_ru/overviews/collections-2.13/maps.md new file mode 100644 index 0000000000..1229e94527 --- /dev/null +++ b/_ru/overviews/collections-2.13/maps.md @@ -0,0 +1,117 @@ +--- +layout: multipage-overview +title: Мапы + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 7 +previous-page: sets +next-page: concrete-immutable-collection-classes + +language: ru + +--- + +[Map](http://www.scala-lang.org/api/current/scala/collection/Map.html) это [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html) состоящее из пар ключ значение (также называемых _связкой_ или _ассоциативным массивом_). +Scala Объект [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) предоставляет неявное преобразование, позволяющее записать пару `(ключ, значение)` используя альтернативный синтаксис вида `ключ -> значение` . Например, `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`. На мапах еще определен метод `apply`, которое напрямую возвращает связанное с заданным ключем значение, без оборачивания его в `Option`. В этом случае, когда ключ не определен, будет брошено исключение. +* **Добавление и обновления** `+`, `++`, `updated`, которые позволяют добавлять новые пары к мапам или изменять существующие. +* **Удаления** `-`, `--`, которые позволяют удалять пары из мап. +* **Создание подколлеций** `keys`, `keySet`, `keysIterator`, `values`, `valuesIterator`, которые возвращают ключи и значения мап отдельно в различных формах. +* **Трансформации** `filterKeys` и `mapValues`, которые создают новую мапу через фильтрацию и преобразования элементов существующей мапы. + +### Операции на Классе Map ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Запросы:** | | +| `ms get k` |Возвращает значение связанное с ключом `k` в мапе `ms` обернутое в опшен, `None` если значение не найдено.| +| `ms(k)` |(либо эквивалент `ms apply k`) Возвращает напрямую значение, связанное с ключом `k` на мапе `ms`, или исключение, если оно не найдено.| +| `ms getOrElse (k, d)` |Значение, связанное с ключом `k` на мапе `ms`, или значением по умолчанию `d`, если не найдено.| +| `ms contains k` |Проверяет, содержит ли `ms` значение для ключа `k`.| +| `ms isDefinedAt k` |Тоже самое что и `contains`. | +| **Подколлекции:** | | +| `ms.keys` |Итерируемая коллекция, содержащая каждый ключ из мапы `ms`. | +| `ms.keySet` |Множество, содержащее каждый ключ из `ms`. | +| `ms.keysIterator` |Итератор, выдающий каждый ключ из `ms`. | +| `ms.values` |Итерируемая коллекция, содержащая каждое значение, связанное с ключом из `ms`.| +| `ms.valuesIterator` |Итератор, выдающий каждое значение, связанное с ключом из `ms`.| +| **Преобразования:** | | +| `ms.view filterKeys p` |Отображение мапы, содержащее только те пары из `ms`, в которых ключ удовлетворяет предикату `p`.| +| `ms.view mapValues f` |Представление мапы `ms` к значениям которой применена функция `f`. | + +Неизменяемые мапы поддерживают операции добавления и удаления элементов через возврат новых `Мап`ов, как описано в следующей таблице. + +### Операции на Классе immutable.Map ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Добавления и обновления:**| | +| `ms.updated(k, v)`
или `ms + (k -> v)` |Мапа, содержащая все пары из `ms`, а также ассоциативную связь `k -> v` от ключа `k` к значению `v`.| +| **Удаления:** | | +| `ms remove k`
или `ms - k` |Мапа, содержащая все пары `ms` за исключением пары с ключом `k`.| +| `ms removeAll ks`
или `ms -- ks` | Мапа, содержащая все пары из `ms` за исключением пары с ключом из `ks`.| + +Изменяемые мапы поддерживают дополнительные операции, которые представленным в таблице ниже. + + +### Операции на Классе mutable.Map ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Добавления и обновления:** | | +| `ms(k) = v` |(либо эквивалент `ms.update(x, v)`). Добавляет связь от ключа `k` к значению `v` в мапе `ms` через побочный эффект, перезаписывая любую предыдущую связь с `k`.| +| `ms.addOne(k -> v)`
либо `ms += (k -> v)` |Добавляет связь от ключа `k` к значению `v` в мапе `ms` через побочный эффект и возвращает сам `ms`.| +| `ms addAll xvs`
либо `ms ++= kvs` |Добавляет все пары из `kvs` к `ms` через побочный эффект и возвращает сам `ms`.| +| `ms.put(k, v)` |Добавляет связь от ключа `k` к значению `v` в мапе `ms` и возвращает любое значение, которое было ранее связанно с `k` (опционально).| +| `ms getOrElseUpdate (k, d)`|Если ключ `k` определен на мапе `ms`, возвращает связанное с ним значение. В противном случае добавляет к `ms` связь вида `k -> d` и возвращает `d`.| +| **Удаления:**| | +| `ms subtractOne k`
либо `ms -= k` |Удаляет ассоциированную связь с ключом `k` из мапы `ms` побочным эффектом и возвращает сам `ms`.| +| `ms subtractAll ks`
либо `ms --= ks` |Удаляет все пары связанные с ключами `ks` из мапы `ms` побочным эффектом и возвращает сам `ms`.| +| `ms remove k` |Удаляет любую пару связанную с ключом `k` из `ms` и возвращает значение, которое ранее было связанное с `k` (опционально).| +| `ms filterInPlace p` |Оставляет только те пары в мапе `ms`, у которых ключ, удовлетворяет предикату `p`.| +| `ms.clear()` |Удаляет все пары из мапы `ms` | +| **Преобразования:** | | +| `ms mapValuesInPlace f` |Преобразует все значения в мапе `ms` используя функцию `f`. | +| **Клонирования:** | | +| `ms.clone` |Возвращает новую изменяемую мапу с теми же парами, что и у `ms`.| + +Операции добавления и удаления в мапах совпадают с операциями добавления и удаления у множеств. Изменяемая мапа `m` обычно обновляется через замену значений в самой себе, используя два варианта синтаксиса `m(key) = value` или `m += (key -> value)`. Существует также вариант `m.put(key, value)`, который возвращает `Option`, содержащее значение, ранее связанного с `key`, или `None`, если `key` не было в мапе. + +Функция `getOrElseUpdate` полезна для доступа к мапам, работающим в качестве кэша. Допустим, у вас есть дорогая для вычисления операция, вызываемая функцией `f`: + + scala> def f(x: String) = { + println("taking my time."); sleep(100) + x.reverse } + f: (x: String)String + +Допустим, что `f` без побочных эффектов, поэтому повторное обращение к функции с тем же аргументом всегда будет давать один и тот же результат. В этом случае можно сэкономить время, сохранив ранее вычисленное выражение связав аргумент с результатом `f` на мапе и вычислять `f` только в том случае, если результат для аргумента не находится в мапе. Можно сказать, что мапа представляет собой _кэш_ для вычислений функции `f`. + + scala> 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` вызывается "по имени", поэтому вычисление `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 + } diff --git a/_ru/overviews/collections-2.13/overview.md b/_ru/overviews/collections-2.13/overview.md new file mode 100644 index 0000000000..7cdefb71c3 --- /dev/null +++ b/_ru/overviews/collections-2.13/overview.md @@ -0,0 +1,108 @@ +--- +layout: multipage-overview +title: Изменяемые и Неизменяемые Коллекции + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 2 +previous-page: introduction +next-page: trait-iterable + +language: ru + +--- + +В коллекциях Scala постоянно проводят различие между неизменяемыми и изменяемыми коллекциями. _Изменяемые_ (mutable) коллекции могут быть изменены или дополнены. Это означает, что вы можете изменять, добавлять или удалять её элементы. _Неизменяемые_ (Immutable) коллекции, напротив, никогда не меняются. У них есть операции, имитирующие добавления, удаления или обновления, но эти операции каждый раз будут возвращать новую коллекцию и оставлять старую коллекцию без изменений. + +Все варианты коллекции находятся в пакете `scala.collection` либо в одном из его подпакетов `mutable` или `immutable`. Большинство коллекции, которые необходимы для работы с клиентским кодом, существуют в трех вариантах, +те которые находятся в пакетах `scala.collection`, `scala.collection.immutable` или `scala collection.mutable`. У каждого варианта свои особенности в работе, связанные с разным подходом к обработке изменений. + +Каждая коллекция в пакете `scala.collection.immutable` гарантированно будет неизменяемой. Такая коллекция никогда не изменится после ее создания. Поэтому, вы, можете положиться на факт того, что повторный доступ к значениям коллекции в любой момент времени приведет к одному и томуже результату. + +Известно, что у коллекции в пакете `scala.collection.mutable` есть операции, которые изменяют саму коллекцию. Поэтому, работая с изменяемой коллекцией вам нужно четко понимать, где и когда в нее вносятся изменения. + +Коллекция в пакете `scala.collection` может быть как изменяемой, так и неизменяемой. +Например, [collection.IndexedSeq\[T\]](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) +является _базовой_ для обоих коллекций [collection.immutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html) +и +[collection.mutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/IndexedSeq.html) +Как правило, базовые коллекции пакета `scala.collection` поддерживают операции преобразования, затрагивающие всю коллекцию, неизменяемые коллекции пакета `scala.collection.immutable` обычно добавляют операции добавления или удаления отдельных элементов, а изменяемые коллекции пакета `scala.collection.mutable` обычно добавляют к базовому интерфейсу, операции модификации элементов основанные на побочных эфектах. + +Еще одним отличием базовой коллекции от неизменяемой является то, что пользователи неизменяемой коллекции имеют гарантию, что никто не сможет изменить коллекцию, а пользователи базовой коллекции лишь обещают не менять ее самостоятельно. Даже если тип такой коллекции не предоставляет никаких операций для модификации коллекции, все равно возможно, что эта коллекция, может быть изменена какими-либо сторонними пользователями. + +По умолчанию Scala всегда выбирает неизменяемые коллекции. Например, если вы просто пишете `Set` без префикса или импортируете `Set` откуда-то, вы получаете неизменяемый Set, а если вы пишете `Iterable` - получите неизменяемую Iterable коллекцию, потому что такие связки прописаны по умолчанию, в пакете`scala`. Чтобы получить изменяемую версию необходимо явно написать `collection.mutable.Set` или `collection.mutable.Iterable`. + +Полезное соглашение, если вы хотите использовать как изменяемую, так и неизменяемую версию коллекций - импортируйте только пакет `collection.mutable`. + + import scala.collection.mutable + +Тогда указание типа `Set` без префикса по-прежнему будет относиться к неизменяемой коллекции, в то время как `mutable.Set` буде относиться к переменному аналогу. + +Последним пакетом в иерархии коллекций является `scala.collection.generic`. Этот пакет содержит строительные элементы для абстрагирования поверх конкретных коллекций. + +Для удобства и обратной совместимости некоторые важные типы имеют псевдонимы в `scala` пакете, поэтому вы можете использовать их указывая обычное имя без необходимости импорта пакета. Примером может служить тип `List`, к которому можно получить доступ следующим образом: + + scala.collection.immutable.List // Полное объявление + scala.List // объявление через псевдоним + List // тк scala._ всегда автоматически импортируется + // можно просто указать имя коллекции + +Другие псевдонимы для типов +[Iterable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html), [LazyList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/LazyList.html), [Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html), и [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html). + +На следующем рисунке показаны все коллекции из пакета `scala.collection`. Это все абстрактные классы или трейты, у которых обычно есть, как изменяемая, так и неизменяемая реализация. + +[![Иерархия базовых коллекций][1]][1] + +На следующем рисунке показаны все коллекции из пакета `scala.collection.immutable`. + +[![Иерархия неизменяемых коллекций][2]][2] + +И на конец все коллекции из пакета `scala.collection.mutable`. + +[![Иерархия изменяемых коллекций][3]][3] + +Описания: + +[![Граф описаний][4]][4] + +## Обзор API коллекций ## + +Наиболее важные классы коллекций представлены на рисунках выше. Существует довольно много общего между всеми этими классами. Например, любая коллекция может быть создана по одному общему синтаксису, написав имя класса коллекции, а затем ее элементы: + + 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) + +Этот же принцип применяется и к конкретным реализациям коллекций, таким как + + List(1, 2, 3) + HashMap("x" -> 24, "y" -> 25, "z" -> 26) + +Все эти коллекции выводятся с помощью `toString`. + +Все коллекции поддерживают API, предоставляемый `Iterable`, но с определенной спецификой на разных типах, где это имеет смысл. Например, метод `map` в классе `Iterable` возвращает в результате другой `Iterable`. Но в подклассах тип результата переопределяется. Например, вызов `map` в `List` снова дает `List`, вызов на`Set` снова дает `Set` и так далее. + + 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) + +Такое поведение, повсеместно реализованное для коллекций, называется _принцип единообразного типа возвращаемого значения_ (_uniform return type principles_). + +Большинство классов в иерархии коллекций существуют в трех вариантах: базовый, изменяемый и неизменяемый. Единственным исключением является трейт `Buffer`, который существует только в виде изменяемой коллекции. + +Далее мы рассмотрим все эти классы поподробнее. + + + [1]: /resources/images/tour/collections-diagram-213.svg + [2]: /resources/images/tour/collections-immutable-diagram-213.svg + [3]: /resources/images/tour/collections-mutable-diagram-213.svg + [4]: /resources/images/tour/collections-legend-diagram.svg diff --git a/_ru/overviews/collections-2.13/performance-characteristics.md b/_ru/overviews/collections-2.13/performance-characteristics.md new file mode 100644 index 0000000000..526a74a2b2 --- /dev/null +++ b/_ru/overviews/collections-2.13/performance-characteristics.md @@ -0,0 +1,90 @@ +--- +layout: multipage-overview +title: Показатели производительности + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 12 +previous-page: strings +next-page: equality + +language: ru + +--- + +Из предыдущих объяснений стало ясно, что разные типы коллекций имеют разные показатели производительности. Что зачастую является основной причиной, выбора одной коллекции вместо другой. Показатели для наиболее распространенных операций собраны в таблицах ниже. + +Показатели производительности на последовательностях: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| **Не изменяемые** | | | | | | | | +| `List` | C | C | L | L | C | L | - | +| `LazyList` | C | C | L | L | C | L | - | +| `ArraySeq` | C | L | C | L | L | L | - | +| `Vector` | eC | eC | eC | eC | eC | eC | - | +| `Queue` | aC | aC | L | L | C | C | - | +| `Range` | C | C | C | - | - | - | - | +| `String` | C | L | C | L | L | L | - | +| **Изменяемые** | | | | | | | | +| `ArrayBuffer` | C | L | C | C | L | aC | L | +| `ListBuffer` | C | L | L | L | C | C | L | +|`StringBuilder`| C | L | C | C | L | aC | L | +| `Queue` | C | L | L | L | C | C | L | +| `ArraySeq` | C | L | C | C | - | - | - | +| `Stack` | C | L | L | L | C | L | L | +| `Array` | C | L | C | C | - | - | - | +| `ArrayDeque` | C | L | C | C | aC | aC | L | + +Показатели производительности на множествах и мапах: + +| | lookup | add | remove | min | +| -------- | ---- | ---- | ---- | ---- | +| **Не изменяемые** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `TreeSet`/`TreeMap`| Log | Log | Log | Log | +| `BitSet` | C | L | L | eC1| +| `VectorMap` | eC | eC | aC | L | +| `ListMap` | L | L | L | L | +| **Изменяемые** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `WeakHashMap` | eC | eC | eC | L | +| `BitSet` | C | aC | C | eC1| +| `TreeSet` | Log | Log | Log | Log | + +Примечание: 1 Предполагаем, что биты плотно упакованы. + +Объяснение записей: + +| | | +| --- | ---- | +| **C** | Операция занимает (быстрое) [постоянное время](https://ru.wikipedia.org/wiki/%D0%92%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0). | +| **eC** | Операция занимает практически постоянное время, но может зависеть от некоторых допущений, таких как максимальная длина вектора или распределение ключей хэширования.| +| **aC** | Операция занимает постоянное амортизированное время. Некоторые вызовы операции могут занять больше времени, но если в среднем выполняется много операций, то на одну операцию приходится постоянное время. | +| **Log** | Операция занимает время, пропорциональное логарифму от размера коллекции.| +| **L** | Операция линейная, т.е. занимает время, пропорциональное размеру коллекции.| +| **-** | Операция не поддерживается. | + +В первой таблице рассматриваются типы последовательностей - как изменяемые, так и неизменяемые - со следующими операциями: + +| | | +| --- | ---- | +| **head** | Выбор первого элемента последовательности. | +| **tail** | Создание новой последовательности, состоящей из всех элементов, кроме первого. | +| **apply** | Работа с индексом. | +| **update** | Функционал обновления (с `updated`) для неизменяемых последовательностей и через сайд эффекты обновление (с `update`) для изменяемых последовательностей. +| **prepend**| Добавление элемента спереди последовательности. В неизменяемых последовательностях происходит создание новой последовательности. В изменяемых - модифицируется исходная. | +| **append** | Добавление элемента в конец последовательности. В неизменяемых последовательностях происходит создание новой последовательности. В изменяемых - модифицируется исходная. | +| **insert** | Вставка элемента в любом месте последовательности. Поддерживается только в изменяемых последовательностях. | + +Во второй таблице показаны изменяемые и неизменяемые множества и мапы со следующими операциями: + +| | | +| --- | ---- | +| **lookup** | Проверка наличия элемента во множестве или получение значения, связанного с ключом в мапе. | +| **add** | Добавление нового элемента во множество или пару ключ/значение в мапу . | +| **remove** | Удаление элемента из множества или ключа из мапы. | +| **min** | Наименьший элемент множества, или наименьший ключ в мапе. | diff --git a/_ru/overviews/collections-2.13/seqs.md b/_ru/overviews/collections-2.13/seqs.md new file mode 100644 index 0000000000..6e9412adda --- /dev/null +++ b/_ru/overviews/collections-2.13/seqs.md @@ -0,0 +1,125 @@ +--- +layout: multipage-overview +title: Последовательности. Трейт Seq, IndexedSeq и LinearSeq + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 5 +previous-page: trait-iterable +next-page: sets + +language: ru + +--- + +Трейт [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]`. Элементы последовательности индексируются от нуля до `length`(длинна последовательности) минус еденицу. Метод `length` на последовательностях является ссылкой на метод `size` общий коллекциях. Метод `lengthCompare` позволяет сравнивать длины последовательностей с `Int`, даже если длина последовательностей бесконечна. +* **Операции поиска индекса** `indexOf`, `lastIndexOf`, `indexOfSlice`, `lastIndexOfSlice`, `indexWhere`, `lastIndexWhere`, `segmentLength`, которые возвращают индекс элемента, равный заданному значению или совпадающий с каким-либо предикатом. +* **Операции сложения** `prepended`, `prependedAll`, `appended`, `appendedAll`, `padTo`, которые возвращают новые последовательности, полученные добавлением элементов в начале или в конце последовательности. +* **Операции обновления** `updated`, `patch`, которые возвращают новую последовательность полученную заменой некоторых элементов исходной последовательности +* **Операции сортировки** `sorted`, `sortWith`, `sortBy`, которые сортируют последовательность элементов в соответствии с различными критериями +* **Операции разворота** `reverse`, `reverseIterator`, которые выдают или обрабатывают элементы последовательности в обратном порядке. +* **Сравнения** `startsWith`, `endsWith`, `contains`, `containsSlice`, `corresponds`, `search`, которые сопоставляют две последовательности или осуществляют поиск элементов в последовательности. +* **Операции с множествами** `intersect`, `diff`, `distinct`, `distinctBy`, которые выполняют операции _как у множеств_ с элементами двух последовательностей либо удаляют дубликаты. + +Если последовательность изменяемая (мутабельная), то у нее есть операция `update` (обновления), которая обновляет элементы последовательности используя побочные эффекты. Как всегда в Scala синтаксис типа `seq(idx) = elem` - это просто сокращение от `seq.update(idx, elem)`, поэтому `update` - это просто более удобный вариант синтаксиса. Обратите внимание на разницу между `update` (обновить) и `updated` (обновленный). `updated` доступен для всех последовательностей и всегда возвращает новую последовательность вместо того, чтобы модифицировать исходную. + +### Операции на Классе Seq ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Размера и индексация:** | | +| `xs(i)` |(эквивалентно `xs apply i`). Выдает элемент `xs` на позиции `i`.| +| `xs isDefinedAt i` |Проверяет есть ли `i` в `xs.indices`.| +| `xs.length` |Длина последовательности (тоже самое что и `size`).| +| `xs lengthCompare n` |Возвращает `-1` если `x` короче `n`, `+1` если длиннее, и `0` такогоже размера что и `n`. Работает даже если последовательность бесконечна, например, `LazyList.from(1) lengthCompare 42` возвращает положительное значение.| +| `xs.indices` |Диапазон индексов `xs`, от `0` до `xs.length` - 1`.| +| **Поиск по индексу** | | +| `xs indexOf x` |Индекс первого элемента в `xs` равного `x`. | +| `xs lastIndexOf x` |Индекс последнего элемента в `xs` равного `x`. | +| `xs indexOfSlice ys` |Первый индекс элемента в `xs` начиная с которого можно сформировать последовательность эквивалентную `ys` (существует несколько вариантов). | +| `xs lastIndexOfSlice ys` |Последний индекс элемента в `xs` начиная с которого можно сформировать последовательность эквивалентную `ys` (существует несколько вариантов). | +| `xs indexWhere p` |Первый индекс элемента который удовлетворяет условию `p` (существует несколько вариантов). | +| `xs.segmentLength(p, i)`|Длина самого длинного непрерывного сегмента элементов в `xs`, начиная с которого удовлетворяется условие `p`.| +| **Сложения:** | | +| `xs.prepended(x)`
либо `x +: xs` |Новая последовательность, состоящая из `x` добавленный перед `xs`.| +| `xs.prependedAll(ys)`
либо `ys ++: xs` |Новая последовательность, состоящая из всех элементов `ys` добавленных перед `xs`.| +| `xs.appended(x)`
либо `xs :+ x` |Новая последовательность, состоящая из `x` добавленных после `xs`.| +| `xs.appendedAll(ys)`
либо `xs :++ ys` |Новая последовательность, состоящая из всех элементов `ys` добавленных после `xs`.| +| `xs.padTo(len, x)` |Последовательность, получаемая в результате добавления значения `x` к `xs` до тех пор пока не будет достигнута длина `len`.| +| **Обновления:** | | +| `xs.patch(i, ys, r)` |Последовательность, получаемая в результате замены `r` элементов `xs`, начиная с `i` заменяя их на `ys`.| +| `xs.updated(i, x)` |Создает копию `xs` в которой элемент с индексом `i` заменён на `x`.| +| `xs(i) = x` |(эквивалентно `xs.update(i, x)`, доступно только у `mutable.Seq`). Заменяет элемент `xs` с индексом `i` на `x`.| +| **Сортировка:** | | +| `xs.sorted` |Новая последовательность, полученная при сортировке элементов `xs` используя стардартную схему упорядочивания элементов типа `xs`.| +| `xs sortWith lt` |Новая последовательность, полученная при сортировке элементов `xs` при помощи операции сравнения `lt`.| +| `xs sortBy f` |Новая последовательность, полученная при сортировке элементов `xs`. Сравнение при сортировке происходит между двумя элементами полученных после выполнения функции `f` на исходных элементах.| +| **Развороты:** | | +| `xs.reverse` |Последовательность с элементами `xs` в обратном порядке.| +| `xs.reverseIterator` |Итератор, выдающий все элементы `xs` в обратном порядке.| +| **Сравнения:** | | +| `xs sameElements ys` |Проверка на то, содержат ли `xs` и `ys` одни и те же элементы в одном и том же порядке.| +| `xs startsWith ys` |Проверяет, начинается ли `xs` с последовательности `ys`. (существует несколько вариантов).| +| `xs endsWith ys` |Проверяет, заканчивается ли `xs` последовательностью `ys`. (существует несколько вариантов).| +| `xs contains x` |Проверяет, есть ли в `xs` элемент равный `x`.| +| `xs search x` |Проверяет, есть ли в отсортированной последовательности `xs` элемент, равный `x`, такой поиск может быть более эффективным чем `xs contains 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.distinct` |Подпоследовательность `xs`, которая не содержит дублирующих друг друга элементов.| +| `xs distinctBy f` |Подпоследовательность `xs`, которая не содержит дублирующего элемента после применения функции преобразования `f`. Например, `List("foo", "bar", "quux").distinctBy(_.length) == List("foo", "bar")`| + +У трейта [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) есть два дочерних трейта [LinearSeq](http://www.scala-lang.org/api/current/scala/collection/LinearSeq.html), и [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html). +Они не добавляют никаких новых операций, но у каждого из них разные характеристики производительности: у LinearSeq эффективные операции `head` и `tail`, в то время как у IndexedSeq эффективные операции `apply`, `length` и (если мутабельная) `update`. Часто используемые варианты LinearSeq - это `scala.collection.immutable.List` и `scala.collection.immutable.LazyList`. А наиболее часто используемые IndexedSeq - это `scala.Array` и `scala.collection.mutable.ArrayBuffer`. Класс `Vector` представляет собой компромисс между IndexedSeq и LinearSeq. У него эффективные, как обращение по индексу, так и последовательный обход элементов. Поэтому вектора хорошая основа для смешанных моделей доступа, где используются как индексированный, так и последовательный доступ. Позже мы расскажем больше о [векторах](concrete-immutable-collection-classes.html). + +В мутабельном варианте `IndexedSeq` добавляет операции преобразования ее элементов в самой коллекции (в отличие от таких операций как `map` и `sort`, доступных на базовом трейте `Seq`, для которых результат - это новая коллекция). + +#### Операции на Классе mutable.IndexedSeq #### + +| ПРИМЕР | ЧТО ДЕЛАЕТ| +| ------ | ------ | +| **Преобразования:** | | +| `xs.mapInPlace(f)` |Преобразует все элементы `xs`, применяя функцию `f` к каждому из них.| +| `xs.sortInPlace()` |Сортирует коллекцию `xs`.| +| `xs.sortInPlaceWith(c)` |Сортирует коллекцию `xs` в соответствии с заданной функцией сравнения `c`.| +| `xs.sortInPlaceBy(f)` |Сортирует коллекцию `xs` в соответствии с порядком, определяемым на результате после применения функции `f` к каждому элементу.| + +### Буферы ### + +Важной подкатегорией мутабельных последовательностей является `Buffer`ы. Они позволяют не только изменять существующие элементы, но и добавлять, вставлять и удалять элементы. Основными новыми методами, поддерживаемыми буфером, являются `append` и `appendAll` для добавления элементов в конце, `prepend` и `prependAll` для добавления спереди, `insert` и `insertAll` для вставок элементов, а также `remove`, `subtractOne` и `subtractAll` для удаления элементов. Краткая информация об этих операциях представлена в таблице ниже. + +Два часто используемых варианта реализации буферов - `ListBuffer` и `ArrayBuffer`. Как следует из названия, `ListBuffer` основан на `List` и поддерживает эффективное преобразование его элементов в `List` (список), тогда как `ArrayBuffer` - основан на `Array` (массиве), он также может быть быстро преобразован в массив. + +#### Операции на Классе Buffer #### + +| ПРИМЕР | ЧТО ДЕЛАЕТ| +| ------ | ------ | +| **Сложения:** | | +| `buf append x`
либо `buf += x` |Добавляет в конец буфера элемент `x` возвращая этот самый буфер `buf` в качестве результата.| +| `buf appendAll xs`
либо`buf ++= xs` |Добавляет все элементы `xs` в конец буфер.| +| `buf prepend x`
либо `x +=: buf` |Добавляет элемент `x` в начало буфера.| +| `buf prependAll xs`
либо `xs ++=: buf` |Добавляет все элементы `xs` в начало буфера.| +| `buf.insert(i, x)` |Вставляет элемент `x` на позицию `i` в буфер.| +| `buf.insertAll(i, xs)` |Вставляет все элементы в `xs` на позицию `i` в буфер.| +| `buf.padToInPlace(n, x)` |Добавляет элемент `x` в буфер до тех пор, пока там не будет `n` элементов.| +| **Удаления:** | | +| `buf subtractOne x`
либо `buf -= x` |Удаляет элемент `x` из буфера.| +| `buf subtractAll xs`
либо `buf --= xs` |Удаляет элементы `xs` из буфера.| +| `buf remove i` |Удаляет элемент на позиции `i` из буфера.| +| `buf.remove(i, n)` |Удаляет `n` элементов начиная с позиции `i` из буфера.| +| `buf trimStart n` |Удаляет первых `n` элементов из буфера.| +| `buf trimEnd n` |Удаляет последние `n` элементов из буфера.| +| `buf.clear()` |Удаляет все элементы из буфера.| +| **Замена:** | | +| `buf.patchInPlace(i, xs, n)` |Заменяет (не более чем) `n` элементов буфера элементами из `xs`, начиная с позиции `i` в буфере.| +| **Клонирование:** | | +| `buf.clone()` |Новый буфер с теми же элементами, что и `buf`.| diff --git a/_ru/overviews/collections-2.13/sets.md b/_ru/overviews/collections-2.13/sets.md new file mode 100644 index 0000000000..a8abc9bef2 --- /dev/null +++ b/_ru/overviews/collections-2.13/sets.md @@ -0,0 +1,155 @@ +--- +layout: multipage-overview +title: Множества + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 6 +previous-page: seqs +next-page: maps + +language: ru + +--- + +Множества (`Set`) - это итерируемые сущности, которые не содержат дублирующих друг друга элементов. Операции с множествами описаны в таблицах ниже. Описания включают операции для базовых, неизменяемых и изменяемых множеств. Все их операции поделены на следующие категории: + +* **Проверки** `contains`, `apply`, `subsetOf`. Метод `contains` спрашивает, содержит ли множество данный элемент. Метод `apply` для множества работает также как и `contains`, поэтому `set(elem)` является тем же самым, что и `set contains elem`. Это означает, что множества могут использоваться в качестве тестовых функций, которые возвращают `true` при проверке элементов, которые они содержат. + +Например: + + + scala> 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 + + +* **Сложения** `incl` и `concat` (либо `+` и `++`, соответственно), которые добавляют один или несколько элементов во множество, создавая новое множество. +* **Удаления** `excl` и `removedAll` (либо `-` и `--`, соответственно), которые удаляют один или несколько элементов из множества, образуя новое множество. +* **Операции с множествами** для объединения, пересечения и установления расхождений во множествах. Каждая из таких операций имеет две формы: словарную и символьную. Словарные версии - `intersect`, `union`, и `diff`, а символьные - `&`, `|` и `&~`. Фактически `++`, которые `Set` унаследовали от `Iterable`, можно рассматривать как еще один псевдоним `union` или `|`, за исключением того, что `++` принимает `IterableOnce` в качестве аргумента, а `union` и `|` принимают множества. + +### Операции на Классе Set ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Проверки:** | | +| `xs contains x` |Проверяет, является ли `x` элементом `xs`. | +| `xs(x)` |Тоже самое что и `xs contains x`. | +| `xs subsetOf ys` |Проверяет, является ли `xs` подмножеством `ys`. | +| **Добавления:** | | +| `xs concat ys`
или `xs ++ ys` |Множество, содержащее все элементы `xs`, а также все элементы `ys`. | +| **Удаления:** | | +| `xs.empty` |Пустое множество того же класса, что и `xs`. | +| **Двуместные Операции:** | | +| `xs intersect ys`
или `xs & ys` |Множество содержащее пересечение `xs` и `ys`. | +| `xs union ys`
или xs | ys |Множество содержащее объединение `xs` и `ys`. | +| `xs diff ys`
или `xs &~ ys` |Множество содержащее расхождение между `xs` и `ys`. | + +В неизменяемых множествах методы добавления или удаления элементов работают путем возврата новых множеств (`Set`), как описано ниже. + +### Операции на Классе immutable.Set ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Добавления:** | | +| `xs incl x`
или `xs + x` |Множество содержащее все элементы `xs` а также элемент `x`.| +| **Удаления:** | | +| `xs excl x`
или `xs - x` |Множество содержащее все элементы `xs` кроме `x`.| +| `xs removedAll ys`
или `xs -- ys` |Множество содержащее все элементы `xs` кроме элементов `ys`.| + +Изменяемые множества предоставляют в дополнение к перечисленным методам еще методы добавления, удаления или обновления элементов. + +### Операции на Классе mutable.Set ### + +| ПРИМЕР | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Добавления:** | | +| `xs addOne x`
или `xs += x` |Добавляет элемент `x` во множество `xs` побочным эффектом и возвращает сам `xs`. | +| `xs addAll ys`
или `xs ++= ys` |Добавляет все элементы `ys` во множество `xs` побочным эффектом и возвращает сам `xs`. | +| `xs add x` |Добавляет элемент `x` к `xs` и возвращает `true`, если `x` ранее не содержался в множестве, `false` если был.| +| **Удаления:** | | +| `xs subtractOne x`
или `xs -= x` |Удаляет элемент `x` из множества `xs` побочным эффектом и возвращает сам `xs`.| +| `xs subtractAll ys`
или `xs --= ys` |Удаляет все элементы `ys` из множества `xs` побочным эффектом и возвращает сам `xs`. | +| `xs remove x` |Удаляет элемент `x` из `xs` и возвращает `true`, если `x` ранее не содержался в множестве, `false` если был.| +| `xs filterInPlace 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`: + + 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) + +Мы использовали `+=` и `-=` на `var` типа `immutable.Set`. Выражение `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`, и _наоборот_. Это работает, по крайней мере, до тех пор, пока нет прямых отсылок на коллекцию, используя которую можно было бы определить была ли коллекция обновлена или создана заново. + +У изменяемых множеств также есть операции `add` и `remove` как эквиваленты для `+=` и `-=`. Разница лишь в том, что команды `add` и `remove` возвращают логический результат, показывающий, повлияла ли операция на само множество или нет. + +Текущая реализация изменяемого множества по умолчанию использует хэш-таблицу для хранения элементов множества. Реализация неизменяемого множества по умолчанию использует представление, которое адаптируется к количеству элементов множества. Пустое множество представлено объектом сингэлтоном. Множества размеров до четырех представлены одним объектом, в котором все элементы хранятся в виде полей. За пределами этого размера, неизменяемые множества реализованны в виде [Сжатого Отображенния Префиксного Дерева на Ассоциативном Массиве](concrete-immutable-collection-classes.html). + +Результатом такой схемы представления является то, что неизменяемые множества малых размеров (скажем, до 4), более компактны и более эффективны, чем изменяемые. Поэтому, если вы ожидаете, что размер множества будет небольшим, постарайтесь сделать его неизменяемым. + +Два дочерних трейта множеств `SortedSet` и `BitSet`. + +### Отсортированное Множество (SortedSet) ### +[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) - это упорядоченное двоичное дерево, которое поддерживает свойство того, что все элементы левого поддерева меньше, чем все элементы правого поддерева. Таким образом, простой упорядоченный обход может вернуть все элементы дерева в возрастающем порядке. Scala класс [immutable.TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) базируется на _красно-черном_ дереве, в котором сохраняется тоже свойство но при этом само дерево является _сбалансированным_ --, то есть все пути от корня дерева до листа имеют длину, которая может отличаться друг от друга максимум на еденицу. + +При создании пустого [TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html), можно сначала указать требуемый порядок: + + scala> val myOrdering = Ordering.fromLessThan[String](_ > _) + myOrdering: scala.math.Ordering[String] = ... + +Затем, чтоб создать пустой TreeSet с определенным порядком, используйте: + + 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 rangeFrom "three" + res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) + + +### Битовые Наборы (BitSet) ### + +Битовые Наборы - это множество неотрицательных целочисленных элементов, которые упаковываются в пакеты. Внутреннее представление [BitSet](http://www.scala-lang.org/api/current/scala/collection/BitSet.html) использует массив `Long`ов. Первый `Long` охватывает элементы от 0 до 63, второй от 64 до 127 и так далее (Неизменяемые наборы элементов в диапазоне от 0 до 127 оптимизированны таким образом что хранят биты непосредственно в одном или двух полях типа `Long` без использования массива). Для каждого `Long` 64 бита каждого из них устанавливается значение 1, если соответствующий элемент содержится в наборе, и сбрасывается в 0 в противном случае. Отсюда следует, что размер битового набора зависит от размера самого большого числа, которое в нем хранится. Если `N` является самым большим размером числа, то размер набора будет составлять `N/64` от размера `Long`, или `N/8` байта плюс небольшое количество дополнительных байт для информации о состоянии. + +Поэтому битовые наборы компактнее, чем другие множества, когда речь идет о хранении мелких элементов. Еще одним преимуществом битовых наборов является то, что такие операции, как проверка на наличие элемента `contains`, добавление либо удаление элементов с `+=` и `-=` черезвычайно эффективны. \ No newline at end of file diff --git a/_ru/overviews/collections-2.13/strings.md b/_ru/overviews/collections-2.13/strings.md new file mode 100644 index 0000000000..1527860415 --- /dev/null +++ b/_ru/overviews/collections-2.13/strings.md @@ -0,0 +1,33 @@ +--- +layout: multipage-overview +title: Строки + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 11 +previous-page: arrays +next-page: performance-characteristics + +language: ru + +--- + +Как и массивы, строки не являются непосредственно последовательностями, но могут быть преобразованы в них, а также поддерживают все операции которые есть у последовательностей. Ниже приведены некоторые примеры операций, которые можно вызывать на строках. + + 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] = hello + +Эти операции обеспечены двумя неявными преобразованиями. Первое, низкоприоритетное неявное преобразование отображает `String` в `WrappedString`, которая является подклассом `immutable.IndexedSeq`, это преобразование сделано в последней строке выше, в которой строка была преобразована в Seq. Второе, высокоприоритетное преобразование связывает строку и объект `StringOps`, который добавляет все методы на неизменяемых последовательностях. Это неявное преобразование используется при вызове методов `reverse`, `map`, `drop` и `slice` в примере выше. diff --git a/_ru/overviews/collections-2.13/trait-iterable.md b/_ru/overviews/collections-2.13/trait-iterable.md new file mode 100644 index 0000000000..80f7b5990f --- /dev/null +++ b/_ru/overviews/collections-2.13/trait-iterable.md @@ -0,0 +1,151 @@ +--- +layout: multipage-overview +title: Трейт Iterable + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 4 +previous-page: overview +next-page: seqs + +language: ru + +--- + +На самом верху иерархии коллекций находится трейт `Iterable`. Все методы в этого трейта описаны как абстрактные, `iterator` - это метод, который выдает элементы коллекции один за другим. + + def iterator: Iterator[A] + +Классы коллекций, которые базируются на `Iterable`, должны определять этот метод; все остальные методы могут быть просто унаследованы от `Iterable`. + +`Iterable` также определяет несколько конкретных методов, все они делятся на категории, которые опишем в следующей таблице: + +* **Сложения**, `concat`, которая объединяет две коллекции вместе либо добавляет все элементы итератора к коллекции. +* **Применения ко всем** операции `map`, `flatMap` и `collect`, которые создают новую коллекцию, применяя некую функцию к элементам коллекции. +* **Конверсии** `toArray`, `toList`, `toIterable`, `toSeq`, `toIndexedSeq`, `toStream`, `toSet`, `toMap`, которые превращают `Iterable` коллекцию во что-то более конкретное. Все эти преобразования возвращают потребляемые аргументы без изменений, если тип коллекции во время выполнения уже соответствует запрашиваемому типу коллекции. Например, применение команды `toList` к списку вернет в результате тотже самый список. +* **Копирования** `copyToArray` - как следует из названия, копирует элементы коллекции в массив. +* **Размерности** это операции `isEmpty`, `nonEmpty`, `size`, `knownSize`, `sizeIs`. Работают с количеством элементов коллекции, в некоторых случаях может потребовать обхода всех элементов (например для `List`). В других случаях коллекция может вообще иметь неограниченное количество элементов (например, `LazyList.from(1)`). +* **Выбора элементов** это операции `head`, `last`, `headOption`, `lastOption`, и `find`. Они выбирают первый или последний элемент коллекции, или же первый элемент, который соответствует условию. Обратите внимание, однако, что не все коллекции имеют четкое определенние того, что они подразумевают под "first"(первым) и "last"(последним) элементом. Например, хэши можгут хранить элементы в соответствии с их ключами, которые могут меняться от запуска к запуску. В таком случае "первый" элемент во множестве хэшей может быть разным, при каждом запуске программы. Однако если коллекция _упорядоченная_, то она всегда выдает свои элементы в одном и том же порядке. Большинство коллекций упорядоченные, но некоторые (_в том числе HashSet_) нет. Отсутствие упорядоченности дает им дополнительную эффективность работы. Упорядочивание часто необходимо для проведения воспроизводимых тестов и помощи в отладке. С этой целью коллекции Scala предоставляют упорядоченные альтернативы для всех типов коллекций. Например, упорядоченной альтернативой для `HashSet` является `LinkedHashSet`. +* **Выбора под-коллекций** `tail`, `init`, `slice`, `take`, `drop`, `takeWhile`, `dropWhile`, `filter`, `filterNot`, `withFilter`. Все они возвращают какую-то подколлекцию, определяемую индексным диапазоном или каким-либо предикатом. +* **Разделительных операций** `splitAt`, `span`, `partition`, `partitionMap`, `groupBy`, `groupMap`, `groupMapReduce`, которые разделяют элементы исходной коллекции на несколько подколлекций. +* **Проверки элементов** `exists`, `forall`, `count` которые проверяют элементы коллекции с определенным предикатом. +* **Свертки** `foldLeft`, `foldRight`, `reduceLeft`, `reduceRight` которые применяют двуместную операцию к последовательным элементам. +* **Определённых сверток** `sum`, `product`, `min`, `max`, которые работают над коллекциями конкретных типов (числовыми или сопоставимыми). +* **Строковая** операции `mkString`, `addString`, `className`, которые дают альтернативные способы преобразования коллекции в строку. +* **Отображения** - это такая коллекция, которая лениво вычисляется. Позже мы расмотрим отображения [подробнее](views.html). + +В `Iterable` есть два метода, которые возвращают итераторы: `grouped` и `sliding`. Правда эти итераторы возвращают не отдельные элементы, а целые подпоследовательности элементов исходной коллекции. Максимальный размер таких подпоследовательностей задается аргументом. Метод `grouped` возвращает свои элементы "нарезанные" на фиксированные части, тогда как `sliding` возвращает результат "прохода окна" (заданной длинны) над элементами. Разница между ними станет очевидной, если взглянуть на следующий результат в консоли: + + 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) + +### Операции в Классе Iterable ### + +| НАЗВАНИЕ | ЧТО ДЕЛАЕТ | +| ------ | ------ | +| **Абстрактный Метод:** | | +| `xs.iterator` |Возвращает итератор, который выдает каждый элемент из `xs`.| +| **Другие Итераторы:** | | +| `xs foreach f` |Выполняет функцию `f` на каждом элементе `xs`.| +| `xs grouped size` |Итератор, который нарезает коллекцию на кусочки фиксированного размера. | +| `xs sliding size` |Итератор, который выдает результат прохождения окна фиксированного размера над элементами исходной коллекции. | +| **Сложения:** | | +| `xs concat ys`
(либо `xs ++ ys`) |Результат состоит из элементов обоих коллекций `xs` и `ys`. `ys` - [IterableOnce](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IterableOnce.html) коллекция, т. е., либо [Iterable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) либо [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html).| +| **Применения ко всем:** | | +| `xs map f` |Коллекция, полученная применением функции `f` к каждому элементу в `xs`.| +| `xs flatMap f` |Коллекция, полученная применением функции производящей коллекции `f` к каждому элементу в`xs` с объединением результатов в единую коллекцию.| +| `xs collect f` |Коллекция, полученная применением частично определенной функции `f` к каждому элементу в `xs`, для которого она определена, с последующем сбором результатов. | +| **Конверсии:** | | +| `xs.toArray` |Преобразует коллекцию в массив. | +| `xs.toList` |Преобразует коллекцию в список. | +| `xs.toIterable` |Преобразует коллекцию в итерабельную коллекцию. | +| `xs.toSeq` |Преобразует коллекцию в последовательность. | +| `xs.toIndexedSeq` |Преобразует коллекцию в индексированную последовательность. | +| `xs.toSet` |Преобразует коллекцию в множество. | +| `xs.toMap` |Преобразует набор пар ключ/значение в Map. Если в исходной коллекции нет пар, то вызов этой операции приведёт к ошибке при статической проверки типов.| +| `xs.to(SortedSet)` | Общая операция преобразования, в которой в качестве параметра используется производящая коллекция. | +| **Копирования:** | | +| `xs copyToArray(arr, s, n)`|Копирует не более `n` элементов коллекции в массив `arr`, начиная с индекса `s`. Два последних аргумента являются необязательными.| +| **Размерности:** | | +| `xs.isEmpty` |Проверяет, пуста ли коллекция. | +| `xs.nonEmpty` |Проверяет, содержит ли коллекция элементы. | +| `xs.size` |Количество элементов в коллекции. | +| `xs.knownSize` |Количество элементов, вычисляется только если для вычисления количества требуется константное время (`О(1)`) , иначе `-1`. | +| `xs.sizeCompare(ys)` |Возвращает отрицательное значение, если `xs` короче коллекции `ys`, положительное значение, если она длиннее, и `0` если у них одинаковый размер. Работает даже если коллекция бесконечна, например, `LazyList.from(1) sizeCompare List(1, 2)` возвращает положительное значение. | +| `xs.sizeCompare(n)` |Возвращает отрицательное значение, если `xs` короче `n`, положительное значение, если больше, и `0` если размер равен `n`. Работает даже если коллекция бесконечна, например, `LazyList.from(1) sizeCompare 42` возвращает положительное значение. | +| `xs.sizeIs < 42`, `xs.sizeIs != 42`, и так далее |Обеспечивает более удобный синтаксис для `xs.sizeCompare(42) <0`, `xs.sizeCompare(42) !=0` и т.д., соответственно.| +| **Выбора элементов:** | | +| `xs.head` |Первый элемент коллекции (или какой-то элемент, если порядок не определен).| +| `xs.headOption` |Первый элемент `xs` в опциональном значении, или None, если `xs` пуст.| +| `xs.last` |Последний элемент коллекции (или какой-то элемент, если порядок не определен).| +| `xs.lastOption` |Последний элемент `xs` в опциональном значении, или None, если `xs` пуст.| +| `xs find p` |Опциональное значение, содержащее первый элемент из `xs`, которое удовлетворяет `p`, или `None` если ни один из элементов не удовлетворяет требованиям.| +| **Выбора под-коллекций:** | | +| `xs.tail` |Оставшаяся часть коллекции, без `xs.head`. | +| `xs.init` |Оставшаяся часть коллекции, без `xs.last`. | +| `xs.slice(from, to)` |Коллекция, состоящая из элементов в определенном диапазоне `xs` (от `from` до, но не включая `to`).| +| `xs take n` |Коллекция, состоящая из первых `n` элементов `xs` (или некоторых произвольных `n` элементов, если порядок не определен).| +| `xs drop n` |Оставшаяся часть коллекции, без `xs take n`.| +| `xs takeWhile p` |Самый длинный префикс элементов в коллекции, удовлетворяющий `p`.| +| `xs dropWhile p` |Коллекция без самого длинного префикса элементов, удовлетворяющего `p`.| +| `xs takeRight n` |Коллекция, состоящая из последних `n` элементов `xs` (или некоторых произвольных `n` элементов, если порядок не определен).| +| `xs dropRight n` |Оставшаяся часть коллекции, без `xs takeRight n`.| +| `xs filter p` |Коллекция, состоящая из тех элементов `xs`, которые удовлетворяют предикату `p`.| +| `xs withFilter p` |Нестрогий фильтр коллекции. Последовательность вызовов `map`, `flatMap`, `foreach` и `withFilter`, будет применятся только к тем элементам `xs`, для которых условие `p` верно.| +| `xs filterNot p` |Коллекция, состоящая из элементов `xs`, которые не удовлетворяют предикату `p`.| +| **Разделительных Операций:** | | +| `xs splitAt n` |Разделяет `xs` на две коллекции `(xs take n, xs drop n)` | +| `xs span p` |Разделяет `xs` в соответствии с предикатом, образуя пару коллекций `(xs takeWhile p, xs.dropWhile p)`.| +| `xs partition p` |Разделяет `xs` на пару коллекций; одна с элементами, удовлетворяющими предикату `p`, другая с элементами, которые не удовлетворяют, образуя пару коллекций `(xs filter p, xs.filterNot p)`.| +| `xs groupBy f` |Разделяет `xs` на пары ключ/значение в соответствии с разделительной функцией `f`.| +| `xs.groupMap(f)(g)`|Разделяет `xs` на пары ключ/значение в соответствии с разделительной функцией `f` и применяет функцию преобразования `g` к каждому элементу в группе.| +| `xs.groupMapReduce(f)(g)(h)`|Разделяет `xs` в соответствии с разделительной функцией `f`, применяет функцию `g` к каждому элементу в группе, а затем объединяет результаты при помощи функции `h`.| +| **Сведенья об элементах:** | | +| `xs forall p` |Логическое выражение того, соответствует ли предикат `p` всем элементам `xs`.| +| `xs exists p` |Логическое выражение того, соответствует ли предикат `p` какому-либо элементу в `xs`.| +| `xs count p` |Количество элементов в `xs`, удовлетворяющих предикату `p`.| +| **Свертки:** | | +| `xs.foldLeft(z)(op)` |Применяет двуместную операцию `op` между последовательными элементами `x`, слева направо и начиная с `z`.| +| `xs.foldRight(z)(op)` |Применяет двуместную операцию `op` между последовательными элементами `x`, переходя справа налево и заканчивая `z`.| +| `xs reduceLeft op` |Применяет двуместную операцию `op` между последовательными элементами непустой коллекции `xs`, идущей слева направо.| +| `xs reduceRight op` |Применяет двуместную операцию `op` между последовательными элементами непустой коллекции `xs`, идущей справа налево.| +| **Определённых Сверток:** | | +| `xs.sum` |Сумма числовых значений элементов коллекции `xs`.| +| `xs.product` |Произведение числовых значений элементов коллекции `xs`.| +| `xs.min` |Минимальное порядковое значение элемента коллекции `xs`.| +| `xs.max` |Максимальное порядковое значение элемента коллекции `xs`.| +| `xs.minOption` |Как `min` но возвращает `None` если `xs` пустой.| +| `xs.maxOption` |Как `max` но возвращает `None` если `xs` пустой.| +| **Строковые:** | | +| `xs.addString(b, start, sep, end)`|Добавляет строку `b` в `StringBuilder`, которая показывает все элементы `xs` разделенные `sep`, окруженные строками `start` и `end`. `end`. `start`, `sep` - не обязательные параметры.| +| `xs.mkString(start, sep, end)`|Преобразовывает коллекцию в строку, которая отображает все элементы `xs` разделенные `sep`, окруженные строками `start` и `end`. `end`. `start`, `sep` - не обязательные параметры.| +| `xs.stringPrefix` |Возвращает название коллекции `xs.toString'.| +| **Связывание:** | | +| `xs zip ys` |Коллекция пар соответствующих элементов из `xs` и `ys`.| +| `xs.zipAll(ys, x, y)` |Коллекция пар соответствующих элементов из `xs`и `ys`, где более короткая последовательность расширяется, чтобы соответствовать более длинной, добавляя элементы `x` или `y`.| +| `xs.zipWithIndex` |Коллекция пар элементов из `xs` с их индексами.| +| **Отображения:** | | +| `xs.view` |Выводит ленивое отображение для коллекции `xs`.| + +В иерархии наследования сразу под `Iterable` расположены три трейта: [Seq](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html), [Set](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Set.html), и [Map](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html).`Seq` и `Map` реализуют [PartialFunction](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/PartialFunction.html) трейт с его `apply` и `isDefinedAt` методами, но по-своему. `Set` получил свой `apply` метод от [SetOps](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/SetOps.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`. + +Далее мы более подробно рассмотрим каждую, из этих трех видов, коллекций. diff --git a/_ru/overviews/collections-2.13/views.md b/_ru/overviews/collections-2.13/views.md new file mode 100644 index 0000000000..8a095c96a1 --- /dev/null +++ b/_ru/overviews/collections-2.13/views.md @@ -0,0 +1,119 @@ +--- +layout: multipage-overview +title: Отображения + +discourse: true + +partof: collections-213 +overview-name: Collections + +num: 14 +previous-page: equality +next-page: iterators + +language: ru + +--- + +У коллекций довольно много вариантов создания новых коллекций. Ну например используя операции `map`, `filter` или `++`. Мы называем такие операции *трансформерами*, потому что они берут хотя бы одну коллекцию и трансформируют её в новую коллекцию. + +Существует два основных способа реализации трансформеров. Один из них _строгий_, то есть в результате трансформации строится новая коллекция со всеми ее элементами. Другой - _не строгий_ или _ленивый_, то есть трансформер создает только соглашение для получения результата, а дальше элементы создаются только когда будут запрошены. + +В качестве примера не строгого трансформера рассмотрим следующую реализацию операции создания ленивой мапы: + + def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { + def iterator = coll.iterator map f + } + +Обратите внимание, что `lazyMap` создает новую `Iterable` , не обходя все элементы коллекции `coll`. Функция `f` применяется к элементам новой коллекции `iterator` по мере запроса ее элементов. + +Коллекции Scala по умолчанию используют строгий способ во всех своих трансфмерах, за исключением `LazyList`, который реализует свои трансформеры не строгими (ленивыми). Однако существует практичный способ превратить любую коллекцию в ленивую и _наоборот_, основанный на отображении коллекции. _Отображение_ представляет собой особый вид коллекции, которое реализует все трансформеры лениво. + +Для перехода от коллекции к ее отображению можно использовать метод `view` (отобразить) у коллекции. Если `xs` - это какая-то коллекция, то `xs.view` - это та же самая коллекция, но со всеми трансформерами, реализованными лениво. Чтобы вернуться от такого отображения к строгой коллекции, можно использовать операцию преобразования `to` с указанием создаваемой коллекции в качестве параметра (например, `xs.view.to(List)`). + +Давайте рассмотрим пример. Допустим, у вас есть целочисленный вектор, на котором вы хотите последовательно применить две функции: + + 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) + +В последней строчке выражение `v map (_ + 1)` создает новый вектор, который при втором вызове в `map (_ * 2)` превращается в третий вектор. Как правило построение промежуточного результата от первого вызова мапы расточительно. В приведенном выше примере было бы быстрее составить единую мапу, состоящую из двух функций `(_ + 1)` и `(_ * 2)`. Если у вас обе функции доступны в одном и том же выражении, вы можете объеденить их вручную. Но довольно часто последовательные преобразования структуры данных выполняются в различных модулях программы. Слияние этих преобразований отрицательно скажется на модульности. Более универсальным способом избежать промежуточных результатов - это превратить вектор сначало в отображение, затем примененить все преобразования к отображению и, наконец, преобразовать отображение в вектор: + + scala> (v.view map (_ + 1) map (_ * 2)).to(Vector) + res12: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Давайте повторим последовательность этих операций, одну за другой: + + scala> val vv = v.view + vv: scala.collection.IndexedSeqView[Int] = IndexedSeqView() + +Применение `v.view` дает нам `IndexedSeqView[Int]`, тоесть лениво вычисляемым `IndexedSeq[Int]`. Также как и `LazyList`, +метод `toString` на отображении не принуждает выводить элементы, вот почему содержимое `vv` выводится как `View(?)`. + +Применение первого `map` к отображению дает: + + scala> vv map (_ + 1) + res13: scala.collection.IndexedSeqView[Int] = IndexedSeqView() + +Результатом работы `map` - еще один `IndexedSeqView[Int]`. По сути, это обёртка, которая *записывает* тот факт, что на вектор `v` необходимо наложить `map` с функцией `(_ + 1)`. Однако эта мапа не будет применяться до тех пор, пока не будет принудительно запрошена. Теперь применим вторую `map` к последнему результату. + + scala> res13 map (_ * 2) + res14: scala.collection.IndexedSeqView[Int] = IndexedSeqView() + +Наконец, принудительно запросим результат: + + scala> res14.to(Vector) + res15: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Обе сохраненные функции применяются в процессе выполнения операции `to` и строится новый вектор. Таким образом, промежуточная структура данных не требуется. + +В целом, операции преобразования, применяемые к отображениям, никогда не создают новую структуру данных, и имеют эффективный доступ к элементам отображения, обходя как можно меньше элементов базовой структуры данных. +Таким образом, отображения имеют следующие свойства: +1. Трансформеры имеют вычислительную сложность `O(1)`. +2. Операции доступа к элементам имеют ту же сложность что и у базовой структуры данных (например, доступ по индексу на `IndexedSeqView` имеет константную сложность). + + +Однако есть несколько исключений из этих правил. Например, операция `sorted` не может удовлетворять сразу оба свойства. +В действительности, необходимо обойти всю основную коллекцию, чтобы найти ее минимальный элемент. +С одной стороны, если этот обход произошел во время вызова `sorted`, то первое свойство будет нарушено (`sorted` не будет лениво отображен), +с другой стороны, если обход произошел во время доступа к полученным элементам отображения, то второе свойство будет нарушено. +Для таких операций мы решили нарушить первое свойство. +Такие операции задокументированны как *"всегда принуждающие к сбору всех элементов"*. + +Основной причиной использования отображений - производительность. Вы видели, что переключив коллекцию в режим отображения можно избежать создания промежуточных коллекций. Такая экономия может оказаться крайне важной. В качестве другого примера рассмотрим проблему нахождения первого палиндрома в списке слов. Палиндром - это слово, которое читается одинаково как слева направо, так и справа налево. Вот необходимые объявления: + + def isPalindrome(x: String) = x == x.reverse + def findPalindrome(s: Seq[String]) = s find isPalindrome + +Теперь предположим, что у вас очень длинная последовательность слов и вы хотите найти палиндром в первых миллионах слов этой последовательности. Можете ли вы повторно переиспользовать `findPalindrome`? Конечно, вы можете написать: + + findPalindrome(words take 1000000) + +Что прекрасно разделяет два аспекта: взятие первого миллиона слов последовательности и нахождение в ней палиндрома. Недостатком является то, что создается промежуточная последовательность, состоящая из одного миллиона слов, даже если первое слово из этой последовательности уже является палиндромным. Таким образом, возможно, 999'999 слов будут скопированы в промежуточный результат без последующей обработки. Многие программисты сдались бы здесь и написали бы свою собственную специализированную версию поиска палиндромов из заданного префикса последовательности аргументов. Но теперь с отображением, это не требуется делать. Достаточно написать: + + findPalindrome(words.view take 1000000) + +Такой подход имеет такое же хорошее разделение аспектов, но вместо последовательности в миллион элементов он строит только один легковесный объект отображения. Таким образом, вам не нужно выбирать между производительностью и модульностью. + +Увидев все эти изящные свособы отображения, вы можете задаться вопросом, зачем вообще нужен строгий способ создания коллекции? Одна из причин в том, что для производительности не всегда ленивость лучше строгости. При малых размерах коллекции дополнительные накладные расходы, связанные с формированием и применением замыканий в отображениях, часто превышают выгоду от отсутствия промежуточных структур данных. Вероятно, более важной причиной является то, что вычисления в отображениях могут оказаться очень запутанными для разработчика, если отложенные операции имеют побочные эффекты. + +Приведу пример, который вызывал боль у пользователей Scala до версии 2.8. В прежних версиях тип `Range` был ленивым, он вел себя также как отображение. Люди пытались создать ряд сущностей (`actors`) как в примере: + + val actors = for (i <- 1 to 10) yield actor { ... } + +Они были озадачены тем, что ни один из actors не исполнялся, хотя их метод должен был создавать и запускать `actor` из кода, заключенного в фигурные скобки идущие следом. Чтобы объяснить, почему ничего не происходило, напомним, что выражению выше равнозначно применению мапы: + + val actors = (1 to 10) map (i => actor { ... }) + +Так как диапазон, созданный с помощью `(1 to 10)`, вел себя как отображение, результат мапы снова был отображением. То есть элемент не вычислялся, а значит `actor` не создавался! Они могли бы быть созданы путем принудительного вычисления всего диапазона, но это было не таким уж и очевидным решением. + +Чтобы избежать подобных сюрпризов, в текущей библиотеке коллекций Scala действуют более привычные правила. Все коллекции, кроме ленивых списков и отображений, строгие. Единственный способ перейти от строгой коллекции к ленивой - метод `view`. Единственный способ вернуться обратно - использовать `to`. Таким образом, приведенное в примере объявление `actors` теперь будет вести себя так, как ожидалось, создавая и запуская 10 `actors`. Чтобы вернуть прежнее неочевидное поведение, необходимо добавить явный вызов метода `view`: + + val actors = for (i <- (1 to 10).view) yield actor { ... } + +Таким образом, отображения являются мощным инструментом для совмещения аспектов эффективности кода с необходимостью сохранения модульной разработки. Но чтобы не запутаться в аспектах отложенных вычислений, следует ограничить работу отображений только чистым функциональным кодом, в которых у преобразований коллекций нет побочных эффектов. Лучше всего избегать смешения отображений с операциями, которые создают новые коллекции и в то же время имеют побочные эффекты.