Как подсчитать частоту элемента в APL или J без петель

Предположим, у меня есть два списка, один - текст t, один - список символов c. Я хочу подсчитать, сколько раз каждый символ появляется в тексте.

Это можно сделать легко с помощью следующего кода APL.

+⌿t∘.=c

Однако он медленный. Он берет внешний продукт, затем суммирует каждый столбец.

Это алгоритм O (nm), где n и m - размер t и c.

Конечно, я могу написать процедурные программы в APL, которые читают символ t по символу и решают эту проблему в O (n + m) (предполагают идеальное хэширование).

Есть ли способы сделать это быстрее в APL без циклов (или условных)? Я также принимаю решения в J.

Edit: Фактически, я делаю это, когда текст намного короче, чем список символов (символы не являются ascii). Я рассматриваю, где текст имеет длину 20 и список символов имеют длину в тысячах.

Существует простая оптимизация, для которой n меньше m.

w  ← (∪t)∩c
f ←  +⌿t∘.=w
r ← (⍴c)⍴0
r[c⍳w] ← f
r

w содержит только символы из t, поэтому размер таблицы зависит только от t, а не от c. Этот алгоритм выполняется в O (n ^ 2 + m log m). Где m log m - время выполнения операции пересечения.

Однако субквадратичный алгоритм по-прежнему предпочтителен, если кто-то дал огромный текстовый файл.

Ответ 1

NB. Использование слов "ключ" (/.) Наречия w/tally (#)

   #/.~ 'abdaaa'
4 1 1

NB. подсчитанные элементы - это нуль строки.

   ~. 'abdaaa'
abd

NB. Итак, если мы подсчитаем цель вместе со строкой

   #/.~ 'abc','abdaaa'
5 2 1 1

NB. Мы получаем дополнительный для каждого из целевых элементов.

   countKey2=: 4 : '<:(#x){.#/.~ x,y'

NB. Это вычитает 1 (<:) из каждого отсчета xs.

   6!:2 '''1'' countKey2 10000000$''1234567890'''
0.0451088
   6!:2 '''1'' countKey2 1e7$''1234567890'''
0.0441849
   6!:2 '''1'' countKey2 1e8$''1234567890'''
0.466857

NB. Негласная версия

   countKey=. [: <: ([: # [) {. [: #/.~ ,

NB. кажется сначала немного быстрее

   6!:2 '''1'' countKey 1e8$''1234567890'''
0.432938

NB. Но повторение времени 10 раз показывает, что они одинаковы.

   (10) 6!:2 '''1'' countKey 1e8$''1234567890'''
0.43914
   (10) 6!:2 '''1'' countKey2 1e8$''1234567890'''
0.43964

Ответ 2

Я думаю, что этот пример, написанный на J, соответствует вашему запросу. Список символов длиннее, чем текст (но оба они остаются короткими для удобства во время разработки.) Я не изучал время, но моя интуиция заключается в том, что он будет быстрым. Сводка выполняется только со ссылкой на символы, которые действительно встречаются в тексте, а длинный набор символов просматривается только для корреляции символов, которые встречаются в тексте.

   c=: 80{.43}.a.
   t=: 'some {text} to examine'

   RawIndicies=: c i. ~.t
   Mask=: RawIndicies ~: #c
   Indicies=: Mask # RawIndicies
   Tallies=: Mask # #/.~ t
   Result=: Tallies Indicies} (#c)$0

   4 20 $ Result
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 4 0
0 0 1 0 0 0 2 1 2 0 0 0 1 3 0 0 0 2 0 0
   4 20 $ c
+,-./0123456789:;<=>
[email protected]
STUVWXYZ[\]^_`abcdef
ghijklmnopqrstuvwxyz

Ответ 3

Dyalog v14 ввел ключевой оператор ():

      {⍺,⍴⍵}⌸'abcracadabra'
a 5
b 2
c 2
r 2
d 1

Функция операнда принимает букву как и вхождения этой буквы (вектор индексов) как .

Ответ 4

"Грубая сила" в J:

count =: (i.~~.) ({,&0) (]+/"[email protected]:=)

Использование:

   'abc' count 'abdaaa'
4 1 0

Не знаете, как это реализовано внутренне, но вот тайминги для разных размеров ввода:

   6!:2 '''abcdefg'' count 100000$''abdaaaerbfqeiurbouebjkvwek''' NB: run time for #t = 100000 
0.00803909
   6!:2 '''abcdefg'' count 1000000$''abdaaaerbfqeiurbouebjkvwek'''
0.0845451
   6!:2 '''abcdefg'' count 10000000$''abdaaaerbfqeiurbouebjkvwek''' NB: and for #t = 10^7
0.862423

Мы не фильтруем дату ввода до "self-classify", поэтому:

   6!:2 '''1'' count 10000000$''1'''
0.244975
   6!:2 '''1'' count 10000000$''1234567890'''
0.673034
   6!:2 '''1234567890'' count 10000000$''1234567890'''
0.673864

Ответ 5

Моя реализация в APL (NARS2000):

(∪w),[0.5]∪⍦w←t∩c

Пример:

      c←'abcdefg'
      t←'abdaaaerbfqeiurbouebjkvwek'
      (∪w),[0.5]∪⍦w←t∩c
a b d e f
4 4 1 4 1

Примечание: показаны только те символы из c, которые существуют в t

Ответ 6

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

Классическим решением является "сортировка, сдвиг и сравнение":

      c←'missippi'
      t←'abcdefghijklmnopqrstuvwxyz'
      g←⍋c
      g
1 4 7 0 5 6 2 3
      s←c[g]
      s
iiimppss
      b←s≠¯1⌽s
      b
1 0 0 1 1 0 1 0
      n←b/⍳⍴b
      n
0 3 4 6
      k←(1↓n,⍴b)-n
      k
3 1 2 2
      u←b/s
      u
imps

И для окончательного ответа:

       z←(⍴t)⍴0
       z
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
       z[t⍳u]←k
       z
0 0 0 0 0 0 0 0 3 0 0 0 1 0 0 2 0 0 2 0 0 0 0 0 0 0

Этот код не работает, но не готов к выпуску. Нужно искать пустые случаи - булевский сдвиг, вероятно, не подходит для всех случаев....

Ответ 7

Моя первоначальная мысль заключалась в том, что это был случай для оператора Find:

T←'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
C←'MISSISSIPPI'
X←+/¨T⍷¨⊂C

Используемые символы:

       (×X)/T
IMPS

Их соответствующие частоты:

       X~0
4 1 2 4

Я только запускаю корпуса для игрушек, поэтому я понятия не имею, что такое производительность, но моя интуиция говорит мне, что это должно быть дешевле, чем внешний продукт. Любые мысли?