Эффективный способ преобразования массива Scala в уникальный отсортированный список - PullRequest
10 голосов
/ 17 ноября 2011

Может ли кто-нибудь оптимизировать следующее утверждение в Scala:

// maybe large
val someArray = Array(9, 1, 6, 2, 1, 9, 4, 5, 1, 6, 5, 0, 6) 

// output a sorted list which contains unique element from the array without 0
val newList=(someArray filter (_>0)).toList.distinct.sort((e1, e2) => (e1 > e2))

Поскольку производительность критична, есть ли лучший способ?

Спасибо.

Ответы [ 7 ]

20 голосов
/ 17 ноября 2011

Эта простая строка является одним из самых быстрых на данный момент кодов:

someArray.toList.filter (_ > 0).sortWith (_ > _).distinct

но явный победитель пока - из-за моих измерений - Джед Уэсли-Смит. Возможно, если код Рекса исправлен, он выглядит иначе.

bench diagram

Типичный отказ от ответственности 1 + 2:

  1. Я изменил коды, чтобы принять массив и вернуть список.
  2. Типичные ориентиры:
    • Это были случайные данные, распределенные поровну. Для 1 миллиона элементов я создал массив из 1 миллиона целых чисел от 0 до 1 миллиона. Так что с большим или меньшим количеством нулей и большим или меньшим количеством дубликатов, это может измениться.
    • Это может зависеть от компьютера и т. Д. Я использовал одноядерный процессор, Intel-Linux-32bit, jdk-1.6, scala 2.9.0.1

Вот базовый код скамейки и конкретный код для создания графика (gnuplot). Ось Y: время в секундах. Ось X: от 100 000 до 1 000 000 элементов в массиве.

Обновление:

Обнаружив проблему с кодом Рекса, его код работает так же быстро, как и код Джеда, но последняя операция - это преобразование его массива в список (чтобы заполнить мой тестовый интерфейс). Использование var result = List [Int] и result = someArray (i) :: result ускоряет его код, так что он примерно в два раза быстрее Jed-кода.

Еще один, возможно, интересный вывод: если я переставлю свой код в порядке фильтра / сортировки / отчетливого (fsd) => (dsf, dfs, fsd, ...), все 6 возможностей существенно не различаются ,

7 голосов
/ 17 ноября 2011

Я не измерял, но я с Дунканом, сортируй на месте, затем используй что-то вроде:

util.Sorting.quickSort(array)
array.foldRight(List.empty[Int]){ 
  case (a, b) => 
    if (!b.isEmpty && b(0) == a) 
      b 
    else 
      a :: b 
}

Теоретически это должно быть довольно эффективно.

4 голосов
/ 17 ноября 2011

Без бенчмаркинга я не могу быть уверен, но я думаю, что следующее довольно эффективно:

val list = collection.SortedSet(someArray.filter(_>0) :_*).toList

Также попробуйте добавить .par после некоторого массива в вашей версии. Это не гарантируется, что будет быстрее, но это может быть. Вы должны запустить тест и поэкспериментировать.

sort устарело. Вместо этого используйте .sortWith(_ > _).

3 голосов
/ 17 ноября 2011

Примитивы бокса принесут вам 10-30-кратное снижение производительности. Поэтому, если вы действительно ограничены в производительности, вам захочется работать с необработанными примитивными массивами:

def arrayDistinctInts(someArray: Array[Int]) = {    
  java.util.Arrays.sort(someArray)
  var overzero = 0
  var ndiff = 0
  var last = 0
  var i = 0
  while (i < someArray.length) {
    if (someArray(i)<=0) overzero = i+1
    else if (someArray(i)>last) {
      last = someArray(i)
      ndiff += 1
    }
    i += 1
  }
  val result = new Array[Int](ndiff)
  var j = 0
  i = overzero
  last = 0
  while (i < someArray.length) {
    if (someArray(i) > last) {
      result(j) = someArray(i)
      last = someArray(i)
      j += 1
    }
    i += 1
  }
  result
}

Вы можете стать немного лучше, чем это, если вы будете осторожны (и будьте осторожны, я набрал это на макушке; возможно, я что-то опечатал, но это стиль, который нужно использовать), но если вы найдете существующая версия слишком медленная, она должна быть как минимум в 5 раз быстрее и, возможно, намного больше.


Редактировать (в дополнение к исправлению предыдущего кода, чтобы он действительно работал):

Если вы настаиваете на том, чтобы заканчивать списком, то вы можете строить список по мере продвижения. Вы можете сделать это рекурсивно, но я не думаю, что в этом случае это будет яснее, чем итеративная версия, поэтому:

def listDistinctInts(someArray: Array[Int]): List[Int] = {
  if (someArray.length == 0 || someArray(someArray.length-1) <= 0) List[Int]()
  else {
    java.util.Arrays.sort(someArray)
    var last = someArray(someArray.length-1)
    var list = last :: Nil
    var i = someArray.length-2
    while (i >= 0) {
      if (someArray(i) < last) {
        last = someArray(i)
        if (last <= 0) return list;
        list = last :: list
      }
      i -= 1
    }
    list
  }
}

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

И имейте в виду, что существуют специальные решения, которые намного быстрее, но в зависимости от характера данных. Например, если вы знаете, что у вас длинный массив, но числа будут в небольшом диапазоне (например, от -100 до 100), то вы можете использовать набор битов, чтобы отслеживать, с какими вы столкнулись.

2 голосов
/ 17 ноября 2011

Для эффективности, в зависимости от вашего значения large:

val a = someArray.toSet.filter(_>0).toArray
java.util.Arrays.sort(a) // quicksort, mutable data structures bad :-)
res15: Array[Int] = Array(1, 2, 4, 5, 6, 9)

Обратите внимание, что это выполняет сортировку с использованием qsort для распакованного массива.

1 голос
/ 17 ноября 2011

Как насчет добавления всего в отсортированный набор?

val a = scala.collection.immutable.SortedSet(someArray filter (0 !=): _*)

Конечно, вы должны тестировать код, чтобы проверить, что быстрее, и, что более важно, что это действительно горячая точка.

1 голос
/ 17 ноября 2011

Я не в состоянии измерить, но есть еще несколько предложений ...

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...