2016-11-20 1 views
1

Я ищу для сортировки ListBuffer(Int, Int, Int) по третьему значению наиболее эффективно. Это то, что я сейчас имею. Я использую sortBy. Примечание y и z оба являются ListBuffer[(Int, Int, Int)], которые я принимаю во внимание первым. Моя цель - оптимизировать это и выполнить эту операцию (используя разницу между двумя списками и сортировкой по третьему элементу) наиболее эффективно. Я предполагаю, что часть diff не может быть оптимизирована, но sortBy может, поэтому я ищу эффективный способ сделать для части сортировки. Я нашел сообщения по сортировке массивов, но я работаю с ListBuffer, и преобразование в Array добавляет накладные расходы, поэтому я не хочу конвертировать ListBuffer.Scala: Сортировка ListBuffer of (Int, Int, Int) по третьему значению наиболее эффективно

val x = (y diff z).sortBy(i => i._3) 

ответ

5

1) Если вы хотите использовать библиотеки Scala, то вы не можете сделать гораздо лучше, чем это. Scala уже пытается сортировать вашу коллекцию наиболее эффективным способом. SeqLike определяет def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr = sorted(ord on f), который вызывает эту реализацию:

def sorted[B >: A](implicit ord: Ordering[B]): Repr = { 
    val len = this.length 
    val arr = new ArraySeq[A](len) 
    var i = 0 
    for (x <- this.seq) { 
     arr(i) = x 
     i += 1 
    } 
    java.util.Arrays.sort(arr.array, ord.asInstanceOf[Ordering[Object]]) 
    val b = newBuilder 
    b.sizeHint(len) 
    for (x <- arr) b += x 
    b.result 
    } 

Это то, что ваш код будет звонить. Как вы можете видеть, он уже использует массивы для сортировки данных на месте. Согласно javadoc из public static void sort(Object[] a) в:

Примечание реализации: Эта реализация является устойчивой, адаптивной, итеративным слиянием, что требует гораздо меньше, чем п LG (N) сравнений , когда массив ввода частично сортируются, в то время предлагая Производительность традиционного объединения, когда входной массив случайным образом упорядочен. Если входной массив почти отсортирован, реализация требует приблизительно n сравнений.

2) Если вы пытаетесь оптимизировать вставляя результаты вашего дифф непосредственно в отсортированный структуру как бинарное дерево, как вы производите их поэлементно, вы все равно будете платить ту же цену: средняя стоимость вставки log(n)n Элементы = n log(n) - то же, что и любой алгоритм быстрой сортировки, такой как сортировка слияния.

3) Таким образом, вы не можете оптимизировать этот случай в общем случае, если вы не оптимизируете его в своем конкретном прецеденте.

3a) Например, ListBuffer может быть заменен Set и diff должны быть гораздо быстрее. На самом деле он реализован как:

def diff(that: GenSet[A]): This = this -- that 

который использует -, который в свою очередь должен быть быстрее, чем diff на Seq, который должен сначала построить карту:

def diff[B >: A](that: GenSeq[B]): Repr = { 
    val occ = occCounts(that.seq) 
    val b = newBuilder 
    for (x <- this) 
     if (occ(x) == 0) b += x 
     else occ(x) -= 1 
    b.result 
    } 

3b) Вы также можете избежать сортировки используя _3 как индекс в массиве. Если вы вставляете этот индекс, ваш массив будет отсортирован. Это будет работать, только если ваши данные достаточно плотные, или вы счастливы иметь дело с разреженным массивом впоследствии. Один индекс может также иметь несколько значений, сопоставляющих его, вам также придется иметь дело с этим. Эффективно вы строите отсортированную карту.Вы можете использовать Map для этого, но HashMap не будет сортироваться, а TreeMap потребует log(n) для дополнительной операции добавления.

Проконсультируйтесь с Scala Collections Performance Characteristics, чтобы понять, что вы можете получить на основе вашего дела.

4) Как бы то ни было, на современных компьютерах действительно очень быстро сортировать. Сделайте некоторые бенчмаркинга, чтобы убедиться, что вы преждевременно не оптимизируете его.

Резюмируя сложности для различных сценариев ...

Ваш текущий случай:

  • дифференциал для SeqLike: n создать карту из that + n перебрать this (карта поиска фактически постоянная время (C)) = 2n или O(n)
  • sort - O(n log(n))
  • всего = O(n) + O(n log(n)) = O(n log(n)), точнее: 2n + nlog(n)

Если вы используете Set вместо SeqLike:

  • дифференциал для Set: n итерировать (поиск является C) = O(n)
  • рода - такой же
  • итого - такой же: O(n) + O(n log(n)) = O(n log(n)), более точно: n + nlog(n)

Если вы используете Set и массив для вставки:

  • диф - такой же, как для набора
  • сортировки - 0 - массив отсортирован по построению
  • всего: O(n) + O(0) = O(n), а точнее: n. Не может быть очень практичным для разреженных данных.

Похоже, что в великой схеме вещей это не имеет значения, если у вас нет уникального случая, который приносит пользу из последней опции (массива).

Если у вас есть ListBuffer[Int], а не ListBuffer[(Int, Int, Int)], я бы предложил сначала отсортировать обе коллекции, а затем выполнить diff, выполнив один проход через оба из них в одно и то же время. Это будет O(nlog(n)). В вашем случае сортировка по _3 не является достаточной, чтобы гарантировать точный порядок в обеих коллекциях. Вы можете сортировать по всем трем полям кортежа, но это изменит первоначальный порядок. Если вы в порядке с этим и написав свой собственный diff, то это может быть самый быстрый вариант.

Смежные вопросы