Collz
Custom Scala Collections
TODO english docs in few months
Библиотека коллекций для Scala
Библиотека коллекций, которых может недоставать в стандартной библиотеке Scala. Пока находится в разработке и будет готова в течении нескольких месяцев. В данный момент содержит следующие коллекции:
-
Мутабельные:
- VList
- BoundedQueue
- IntMap
- IntSet
- PrefixMap
- PrefixSet
-
Иммутабельные:
- BoundedQueue
- BoundedBucketQueue
- Router
Использование
Библиотека собрана для версий 2.11.8 и 2.12.1 Для подключения библиотки добавьте себе в .sbt файл строку
libraryDependencies += "com.github.a14e" %% "collz" % "0.2.2"
Описание реализаций
Все мутабельные коллекции не являются потокобезопасными
VList
Класс как возможная альтернатива ListBuffer или ArrayBuffer. В отличие от ListBuffer имеет быстрый доступ к элементам по индексу и более эффективное использование памяти, а в отличие от ArrayBuffer позволяет добавлять элементы в конец без перестроения всего массива.
Реализован как VList c одним изменением: список из подмассивов заменен на массив с буффером, что позволило несколько упростить реализацию и добавить эффективно реализовать итерации в прямом и обратном направлениях. Сложность добавления в конец O(log(log(n))), что примерно ~O(1). Скорость обращения по индексу в худшем случае (для индекса 0) - O(log(n)), но в большинстве случаев - O(1), так как 50% ... 75% индексов содержатся в 2х последних подмассивах.
import a14e.collz.mut.VList
val list = VList[Int](1, 2, 3)
list += 1 // list == VList(1, 2, 3, 1)
list ++= List(2, 3) // list == VList(1, 2, 3, 1, 2, 3)
val x = list(1) // x == 2
var sum = 0
list.foreach(sum += _) // sum == 12
BoundedQueue
Представляет собой очередь типа FIFO с фиксированным размером. Новые элементы добавляются в конец методами push, pushAll, :+ и тд. Элементы из очереди извлекаются и удаляются из начала вызовами методов pull. При превышении размера удаляются элементы из начала. Реализована с быстрым добавлением в конец и извлечением из начала без перестроения всей очереди. Также реализовано с очень быстрым доступом по индексу и быстрыми итерациями по очереди.
Внутри реализовано с помощью двух массивов с размером, равным размеру очереди. В первом массиве есть указатели на начало и конец очереди, а во втором массиве только указатель на конец. (тут указателем называется номер индекса). При перепонии первого моссива начинает заполнятся второй. При превышении размера или извлечении элементов из учереди методом pull смещается указатель на начало очереди и место извдеченного элемента заполняется null, чтобы помочь сборщику мусора. Когда место во втором массиве заканчивается или указатель на начало во втором массиве доходит до конца, тогда второй массив заменяет собой первый.
import a14e.collz.mut.BoundedQueue
val queue = BoundedQueue[Int](2)
queue += 1 // queue == BoundedQueue(1)
queue += 2 // queue == BoundedQueue(1, 2)
queue += 3 // queue == BoundedQueue(2, 3)
val x1 = queue.pull() // x1 == 2, queue == BoundedQueue(3)
val x2 = queue.pull() // x2 == 3, queue == BoundedQueue()
queue ++= List(1, 2, 3) // queue == BoundedQueue(2, 3)
val x3 = queue(0) // x3 == 2
val x4 = queue(1) // x4 == 3
var sum = 0
queue.foreach(sum += _) // sum === 5
иммутабельные реализованы схожим образом. Только вместо массивов используется класс Vector
import a14e.collz.immut.BoundedQueue
val queue0 = BoundedQueue[Int](2)
val queue1 = queue0 :+ 1 // queue1 == BoundedQueue(1)
val queue2 = queue1 :+ 2 // queue2 == BoundedQueue(1, 2)
val queue3 = queue1 :+ 3 // queue3 == BoundedQueue(2, 3)
val (queue4, x1) = queue3.pull() // x1 == 2, queue4 == BoundedQueue(3)
val (queue5, x2) = queue4.pull() // x2 == 3, queue5 == BoundedQueue()
val queue6 = queue5 :++ List(1, 2, 3) // BoundedQueue(2, 3)
val x3 = queue6(0) // x3 == 2
val x4 = queue6(1) // x4 == 3
var sum = 0
queue6.foreach(sum += _) // sum === 5
В иммутабельной версии часть элементов после pull() может скрыто оставаться в коллекции, стоит помнить об этом во избежание утечек памяти.
IntMap
В стандартной библиотеке Scala есть эффективная неизменяемая реализация IntMap. Тут же преставлена эффективная изменяемая реализация.
Выполнена как префиксное дерево, в каждом узле которого до 16 ветвей, номер ветви определяется как key & F, при увеличении глубины ключ сдвигается на 4 бита вправо. Очень близка к иммутабельной реазации HashMap, но имеет сильно более простую реализацию и несколько лучшую производительность при поиске и добавлении элементов.
При поиске по элементу бенчмарки большой разницы по сравнению из мутабельными AnyRefMap, HashMap и тд. Но скорость добавления и удаления элементов может быть в 4-8 раз быстрее. Сложность операций добавления, удаления, поиска по ключу имеет вид O(log16(n))
Недостатком данной реализации из-за использование массивов по 16 является более высокий расход памяти и более медленные итерации по коллеции.
Ключи или значения null недопустимы
import a14e.collz.mut.IntMap
val map = IntMap[Int](1 -> 3, 2 -> 4) // IntMap(1 -> 1, 2 -> 2)
val x1 = map.get(1) // x1 == Some(3)
val x2 = map.get(2) // x2 == Some(4)
val x3 = map.get(0) // x3 == None
val x4 = map.get(3) // x4 == None
map(1) = 5 // map == IntMap(1 -> 5, 2 -> 2)
map(6) = 7 // map == IntMap(1 -> 5, 2 -> 2, 6 -> 7)
val x5 = map.contains(2) // x5 == true
map -= 2 // map == IntMap(1 -> 5, 6 -> 7)
val x6 = map.contains(2) // x6 == false
var sum = 0
map.foreach{ case (key, value) => sum += value } // sum == 12
map.clear() // map = IntMap()
IntSet
Реализация mutable.Set[_], оптимизированная для работы с Int. Сделана на базе префиксных деревьев. Представляет собой легкую обертку поверх IntMap.
import a14e.collz.mut.IntSet
val set = IntSet(1, 2, 3, 4) // IntSet(1, 2, 3, 4)
val res1 = set.contains(1) //res1 == true
val res2 = set.contains(2) //res2 == true
val res3 = set.contains(6) //res3 == false
set += 6 // set == IntSet(1, 2, 3, 4, 6)
val res4 = set.contains(6) //res4 == true
set -= 1 // set == IntSet(2, 3, 4, 6)
val res5 = set.contains(1) //res5 == false
var sum = 0
set.foreach(sum += _) // sum == 15
PrefixMap
Реализация mutable.Map[_, _], оптимизированная для работы со строками. Сделана в виде префиксных деревьев. Для быстрого поиска элемента в узле, каждый узел содержит IntMap, что позволяет получить худшее возможное время поиска мало зависящим от колличества элементов в коллекции, а зависящим только от длинны ключа. Время поиска в худшем случае примерно равно времени поиска в лучшем случае в хэш таблице и разница будет тем больше, чем длиннее ключ. Для длинных строк типа URL с большими общими частями можно получить значительный прирост в скорости по сравнению с хэш таблицей. Также есть возможность эффективно находить все строки, начинающиеся с некоторого префикса и определять существуют ли такие строки.
Ключи или значения null недопустимы
import a14e.collz.mut.PrefixMap
val map = PrefixMap("string" -> 1, "stringWithSuffix" -> 2, "string2" -> 3, "anotherString" -> 4)
val x1 = map.get("string") // s1 == Some(1)
val x2 = map.get("string2") // s1 == Some(3)
val x3 = map.get("string3") // s1 == None
val x4 = map.hasPrefix("stri") // x4 == true
val x5 = map.hasPrefix("unknown") // x5 == false
val x6 = map.findForPrefix("stri").toList
// x6 == List("string" -> 1, "stringWithSuffix" -> 2, "string2" -> 3)
map -= "string"
// map == PrefixMap("stringWithSuffix" -> 2, "string2" -> 3, "anotherString" -> 4)
map("string3") = 5
// map == PrefixMap("stringWithSuffix" -> 2, "string2" -> 3, "anotherString" -> 4, "string3" -> 5)
PrefixSet
Реализация mutable.Set[_], оптимизированная для работы со строками. Реализована в виде тонкой обертки вокруг PrefixMap.
значения null недопустимы
import a14e.collz.mut.PrefixSet
val set = PrefixSet("string", "stringWithSuffix", "string2", "anotherString" )
val x1 = set.contains("string") // s1 == true
val x2 = set.contains("string2") // s1 == true
val x3 = set.contains("string3") // s1 == false
val x4 = set.hasPrefix("stri") // x4 == true
val x5 = set.hasPrefix("unknown") // x5 == false
val x6 = set.findForPrefix("stri").toList
// x6 == List("string", "stringWithSuffix", "string2")
set -= "string"
// set == PrefixSet("stringWithSuffix", "string2", "anotherString")
set += "string3"
// set == PrefixSet("stringWithSuffix", "string2", "anotherString", "string3")
BoundedBucketQueue
В целом очень похож на BoundedQueue, но не удаляет прошлые значения, а сохраняет их в корзине (bucket), которая заполняется по принцыпу стека
так как коллекция состояит из 2 подколлекций, то не имеет смысла ей наследовать разного рода Iterable[_] и тд. Поэтому для доступа к итерациям нужно сначала вызвать методы queue или bucket
import a14e.collz.immut.BoundedBucketQueue
val queue0 = BoundedBucketQueue[Int](2)
val queue1 = queue0 :+ 1 // queue1.queue == BoundedQueue(1) и queue1.bucket == Nil
val queue2 = queue1 :+ 2 // queue2.queue == BoundedQueue(1, 2) и queue1.bucket == Nil
val queue3 = queue1 :+ 3 // queue3.queue == BoundedQueue(2, 3) и queue1.bucket == List(1)
val (queue4, x1) = queue3.pull() // x1 == 2, queue4 == BoundedQueue(3) и queue1.bucket == List(1)
val (queue5, x2) = queue4.pull() // x2 == 3, queue5 == BoundedQueue() и queue1.bucket == List(1)
val queue6 = queue5 :++ List(1, 2, 3) // queue == BoundedQueue(2, 3) и queue1.bucket == List(1, 1)
val x3 = queue6(0) // x3 == 2
val x4 = queue6(1) // x4 == 3
Router
Простой класс для роутинга. Нужен для случайного выбора из входящих элементов по некоторому ключу. Ключем может быть любой наследник Any. Элементы будут выбиратся примерно случайно, но однозначно для каждого ключа. Независимо от порядка добавления роутинг будет однозначным. Но при изменении даже на 1 элемент распределение по ключам может сильно изменится.
import a14e.collz.immut.Router
val router = Router("127.0.0.1:2222", "127.0.0.1:2223", "127.0.0.1:2224")
val id1 = 1
val id2 = 2
val id3 = 2
router.route(id1) // 127.0.0.1:2223
router.route(id2) // 127.0.0.1:2224
router.route(id3) // 127.0.0.1:2222
Сборка из исходных кодов
Проект собирается для scala 2.11.8 и 2.12.1
скопировать исходники:
$ git clone https://github.com/a14e/collz.git
$ cd collz
собрать:
$ sbt update
$ sbt +test
$ sbt +package
взять .jar из папки traget
Roadmap
- Сделать нормальную документацию на англ языке (English docs)
- Добавить новые коллекции
- иммутабельные:
a. IntervalMap
- иммутабельные:
- Добавить бенчмарки в репозиторий