2010-09-14 3 views
12

Есть ли прямая комбинация стандартных функций более высокого порядка для подсчета уникальных элементов в списке?Подсчет уникальных элементов в списке

Например результат для

[1, 1, 4, 0, 4, 4] 

будет что-то вроде

[(1,2), (4,3), (0,1)] 
+2

ли заказ важен? Если да, то какой порядок? Порядок первого появления? – sepp2k

ответ

10

Если порядок не важен это работает:

map (\[email protected](x:_) -> (x, length xs)) . group . sort 

group . sort даст вам список списков где все элементы, которые равны друг другу, сгруппированы в один и тот же подсписк (без sor t, только последовательные равные элементы будут сгруппированы вместе). Затем map превращает каждый подсвечник в набор (element, lengthOfSublist).

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

+0

Сортировка может быть очень дорогой в больших списках. Возможно, лучше использовать решения KennyTM или sdcwc для повышения производительности. – GeneralBecos

+0

@GeneralBecos Почему сортировка будет медленнее, чем создание карты? Оба являются «O (n log n)». – sepp2k

+0

Поскольку предполагается, что вы выполняете частотное распределение, количество элементов только в худшем случае будет таким же, как и количество элементов в списке. В более распространенном сценарии количество элементов в распределении будет намного меньше. Поэтому в среднем карта будет превосходить сортировку. – GeneralBecos

6

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

map (\xs -> (head xs, length xs)) . group . sort 
+4

Кстати, вы можете написать '\ xs -> (head xs, length xs)' as 'head &&& length', используя модуль Control.Arrow. – sdcvvc

6

Если список содержит только целые числа, вы можете также использовать

import qualified Data.IntMap as I 

countElems1 :: [Int] -> [(Int, Int)] 
countElems1 = I.toList . foldr (\k -> I.insertWith (+) k 1) I.empty 

(Не забудьте компилировать с оптимизацией, хотя, в противном случае это будет 2x медленнее, чем метод group . sort. С -O2 это немного быстрее на 14%.)

Вы также можете использовать один из multisetpackages, что делает функцию так просто, как

import qualified Math.Combinatorics.Multiset as S 
countElems4 = S.toCounts . S.fromList 

но менее эффективный.

Все вышеуказанные решения игнорируют первоначальный заказ.

+0

И это без недавних улучшений скорости в библиотеке контейнеров, я готов поспорить. –

1

Что вы говорите, это просто run length encoding на отсортированных данных: в бесплатной онлайн-книге Real World Haskell есть great example of this. Вы захотите отсортировать список, прежде чем вводить его через runLengthEncoder.

+0

Это * не * RLE. RLE даст '[(1,2), (4,1), (0,1), (4,2)] '. – kennytm

+0

@KennyTM Обратите внимание, что я сказал« на отсортированные данные ». Таким образом, не совсем RLE, но почти с отсортированным входом, я думаю, это так, не так ли? –

13

Использование Data.Map и кортежей разделы:

count = Map.fromListWith (+) . map (, 1) 

(Добавить Map.toList если вам нужен список.)

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