Подсчет вхождений строк с помощью ArangoDB AQL

Чтобы подсчитать количество объектов, содержащих определенное значение атрибута, я могу сделать что-то вроде:

FOR t IN thing
  COLLECT other = t.name = "Other" WITH COUNT INTO otherCount
  FILTER other != false
  RETURN otherCount

Но как я могу подсчитать три других вхождения в одном запросе, чтобы подзапросы не выполнялись по одному и тому же набору данных несколько раз?

Я пробовал что-то вроде:

FOR t IN thing
  COLLECT 
    other = t.name = "Other",
    some = t.name = "Some",
    thing = t.name = "Thing"
  WITH COUNT INTO count
  RETURN {
   other, some, thing,
   count
  }

Но я не могу разобраться в результатах: должно быть, я неправильно подхожу к этому?


person Bjorn Thor Jonsson    schedule 14.01.2020    source источник
comment
Вы действительно хотите подсчитать, как часто определенные фразы встречаются в более крупном строковом значении одного атрибута? Ваш запрос не выглядит так, как будто он будет делать что-то подобное, я действительно удивлен, что он недействителен (т.е. вызывает синтаксическую ошибку из-за x = y = z)   -  person CodeManX    schedule 15.01.2020
comment
Да, я хотел бы посчитать, как часто разные фразы встречаются как подстроки одного атрибута. Если бы было достаточно точных значений, то достаточно было бы сделать что-нибудь вроде: FOR t IN thing COLLECT name = t.name WITH COUNT INTO count RETURN { name, count }.   -  person Bjorn Thor Jonsson    schedule 15.01.2020


Ответы (1)


Разделить и подсчитать

Вы можете разделить строку по фразе и вычесть 1 из счета. Это работает для любой подстроки, что, с другой стороны, означает, что она не соблюдает границы слов.

LET things = [
    {name: "Here are SomeSome and Some Other Things, brOther!"},
    {name: "There are no such substrings in here."},
    {name: "some-Other-here-though!"}
]

FOR t IN things
  LET Some = LENGTH(SPLIT(t.name, "Some"))-1
  LET Other = LENGTH(SPLIT(t.name, "Other"))-1
  LET Thing = LENGTH(SPLIT(t.name, "Thing"))-1
  RETURN {
   Some, Other, Thing
}

Результат:

[
  {
    "Some": 3,
    "Other": 2,
    "Thing": 1
  },
  {
    "Some": 0,
    "Other": 0,
    "Thing": 0
  },
  {
    "Some": 0,
    "Other": 1,
    "Thing": 0
  }
]

Вы можете использовать SPLIT(LOWER(t.name), LOWER("...")), чтобы сделать регистр нечувствительным.

СОБИРАЙ слова

Функцию TOKENS() можно использовать для разделения ввода на массивы слов, которые затем можно сгруппировать и подсчитать. Обратите внимание, что я немного изменил ввод. Вход "SomeSome" не будет засчитан, потому что "somesome" != "some" (этот вариант основан на словах, а не на подстроках).

LET things = [
    {name: "Here are SOME some and Some Other Things. More Other!"},
    {name: "There are no such substrings in here."},
    {name: "some-Other-here-though!"}
]
LET whitelist = TOKENS("Some Other Things", "text_en")

FOR t IN things
  LET whitelisted = (FOR w IN TOKENS(t.name, "text_en") FILTER w IN whitelist RETURN w)
  LET counts = MERGE(FOR w IN whitelisted
    COLLECT word = w WITH COUNT INTO count
    RETURN { [word]: count }
  )
  RETURN {
    name: t.name,
    some: counts.some || 0,
    other: counts.other || 0,
    things: counts.things ||0
  }

Результат:

[
  {
    "name": "Here are SOME some and Some Other Things. More Other!",
    "some": 3,
    "other": 2,
    "things": 0
  },
  {
    "name": "There are no such substrings in here.",
    "some": 0,
    "other": 0,
    "things": 0
  },
  {
    "name": "some-Other-here-though!",
    "some": 1,
    "other": 1,
    "things": 0
  }
]

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

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

Возможно, вы захотите создать отдельный Анализатор с отключенным стеммингом для языка, если вы хотите точно сопоставить слова. Вы также можете отключить нормализацию ("accent": true, "case": "none"). Альтернативой может быть использование REGEX_SPLIT() для типичных пробелов и знаков препинания для упрощения токенизации, но это зависит от вашего варианта использования.

Другие решения

Я не думаю, что можно подсчитывать каждый входной объект независимо с помощью COLLECT без подзапроса, если вам не нужен общий счет.

Разделение - это своего рода хитрость, но вы можете заменить SPLIT () на REGEX_SPLIT () и заключить поисковые фразы в \b, чтобы они соответствовали только тем, что границы слова находятся с обеих сторон. Тогда он должен соответствовать только словам (более или менее):

LET things = [
    {name: "Here are SomeSome and Some Other Things, brOther!"},
    {name: "There are no such substrings in here."},
    {name: "some-Other-here-though!"}
]

FOR t IN things
  LET Some = LENGTH(REGEX_SPLIT(t.name, "\\bSome\\b"))-1
  LET Other = LENGTH(REGEX_SPLIT(t.name, "\\bOther\\b"))-1
  LET Thing = LENGTH(REGEX_SPLIT(t.name, "\\bThings\\b"))-1
  RETURN {
   Some, Other, Thing
}

Результат:

[
  {
    "Some": 1,
    "Other": 1,
    "Thing": 1
  },
  {
    "Some": 0,
    "Other": 0,
    "Thing": 0
  },
  {
    "Some": 0,
    "Other": 1,
    "Thing": 0
  }
]

Более элегантным решением было бы использовать ArangoSearch для подсчета слов, но у него нет функции, позволяющей узнать, как часто встречается слово. Он может отслеживать это уже внутренне (функция анализатора "частота"), но определенно не отображается в данный момент.

person CodeManX    schedule 16.01.2020
comment
Спасибо за идеи и примеры! - person Bjorn Thor Jonsson; 20.01.2020