одна команда оболочки для поиска всех n-грамм работает в тексте

одна команда оболочки для поиска всех n-грамм работает в тексте

У меня есть текстовый поток или файл, содержащий слова, разделенные пробелами. Например:

I have a toy. you may not like it.

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

I_amAManTest you_haveAHouse FOO_BAR_test.model

например:

I_amAManTest

можно разделить на:

I
am
A
Man
Test

но я хочу распечатать всенслова (каждое подмножество смежных коротких слов) в сложном слове, например:

I_amAManTest

выход:

// from first word on
I
I_am
I_amA
I_amAMan
I_amAManTest
// from second word on 
am
amA
amAMan
amAManTest
// from third word on 
A
AMan
AManTest
// from fourth word on
Man
ManTest
// from fifth word on
Test

Итак, в заключение, для ввода типа

I_amAManTest you_haveAHouse FOO_BAR_test

выход должен быть

I
I_am
I_amA
I_amAMan
I_amAManTest
am
amA
amAMan
amAManTest
A
AMan
AManTest
Man
ManTest
Test
you
you_have
you_haveA
you_haveAHouse
have
haveA
haveAHouse
A
AHouse
House
FOO
FOO_BAR
FOO_BAR_test
BAR
BAR_test
test

решение1

(В основном) sedрешение:

cat "$@" |
    tr -cs -- '._[:alpha:]' '[\n*]' |
    sed -n  -e 'h; :ms' \
            -e 'p; :ss' \
                -e 's/\([[:lower:]]\)[[:upper:]][[:lower:]]*$/\1/p; t ss' \
                -e 's/\([[:lower:]]\)[[:upper:]][[:upper:]]*$/\1/p; t ss' \
                -e 's/\([[:upper:]]\)[[:upper:]][[:lower:]]\+$/\1/p; t ss' \
                -e 's/[._][[:alpha:]][[:lower:]]*$//p; t ss' \
                -e 's/[._][[:upper:]]\+$//p; t ss' \
            -e 'g' \
            -e 's/^[[:upper:]]\?[[:lower:]]\+\([[:upper:]]\)/\1/; t mw' \
            -e 's/^[[:upper:]]\+\([[:upper:]][[:lower:]]\)/\1/; t mw' \
            -e 's/^[[:alpha:]][[:lower:]]*[._]//; t mw' \
            -e 's/^[[:upper:]]\+[._]//; t mw' \
            -e 'b' \
            -e ':mw; h; b ms'

Алгоритм такой:

for each compound word (e.g., “FOO_BAR_test”) in the input
do
    repeat
        print what you’ve got
        repeat
            remove a small word from the end (e.g., “FOO_BAR_test” → “FOO_BAR”) and print what’s left
        until you’re down to the last one (e.g., “FOO_BAR_test” → “FOO”)
        go back to what you had at the beginning of the above loop
          and remove a small word from the beginning
          (e.g., “FOO_BAR_test” → “BAR_test”) ... but don’t print anything
    until you’re down to the last one (e.g., “FOO_BAR_test” → “test”)
end for loop

Подробности:

  • cat "$@"это UUOC. Я обычно избегаю этого; вы можете сделать , но вы не можете передать несколько файлов  напрямую.tr args <filetr
  • tr -cs -- '._[:alpha:]' '[\n*]'разбивает строку, состоящую из множества сложных слов, на отдельные строки; например,
    I_amAManTest you_haveAHouse FOO_BAR_test
    
    становится
    I_amAManTest
    you_haveAHouse
    FOO_BAR_test
    
    поэтому sed может обрабатывать одно составное слово за раз.
  • sed -n— ничего не печатать автоматически; печатать только по команде.
  • -eуказывает, что следующеееxpression является частью скрипта sed.
  • h— скопировать пространство шаблона в пространство удержания.
  • :ms— метка (Начало основного цикла)
  • p- Распечатать
  • :ss— метка (Вторичный цикл Начало)
  • Следующие команды удаляют короткое слово из конца составного слова и, в случае успеха, выводят результат и возвращаются к началу вторичного цикла.
    • s/\([[:lower:]]\)[[:upper:]][[:lower:]]*$/\1/p; t ss— изменяет «nTest» на «n».
    • s/\([[:lower:]]\)[[:upper:]][[:upper:]]*$/\1/p; t ss— меняет «мОК» на «м».
    • s/\([[:upper:]]\)[[:upper:]][[:lower:]]\+$/\1/p; t ss— меняет «АМан» на «А».
    • s/[._][[:alpha:]][[:lower:]]*$//p; t ss— удаляет «_am» (заменяет его ничем).
    • s/[._][[:upper:]]\+$//p; t ss— удаляет «_BAR» (заменяет его ничем).
  • Это конец вторичного цикла.
  • g— скопируйте пространство удержания в пространство шаблона (вернитесь к тому, что было в начале цикла выше).
  • Следующие команды удаляют короткое слово из начала сложного слова и, в случае успеха, переходят к концу основного цикла (mw = Main loop Wrap-up).
  • s/^[[:upper:]]\?[[:lower:]]\+\([[:upper:]]\)/\1/; t mw— меняет «amA» на «A» и «ManT» на «T».
  • s/^[[:upper:]]\+\([[:upper:]][[:lower:]]\)/\1/; t mw— меняет «АМа» на «Ма».
  • s/^[[:alpha:]][[:lower:]]*[._]//; t mw— удаляет «I_» и «you_» (заменяет их ничем).
  • s/^[[:upper:]]\+[._]//; t mw— удаляет «FOO_» (заменяет его ничем).
  • Каждая из приведенных выше команд замены переходит к основному циклу Wrap-up (ниже), если она успешна (если она находит / соответствует чему-то). Если мы попадаем сюда, пространство шаблона содержит только короткое слово, так что мы закончили.
  • b— переход (переход) в конец скрипта sed; т. е. выход из скрипта sed.
  • :mw— метка для основного цикла.
  • h— копируем пространство шаблона в пространство удержания, чтобы подготовиться к следующей итерации основного цикла.
  • b ms— перейти к началу основного цикла.

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

$ echo "I_amAManTest you_haveAHouse FOO_BAR_test" | ./myscript
I_amAManTest
I_amAMan
I_amA
I_am
I
amAManTest
amAMan
amA
am
AManTest
AMan
A
ManTest
Man
Test
you_haveAHouse
you_haveA
you_have
you
haveAHouse
haveA
have
AHouse
A
House
FOO_BAR_test
FOO_BAR
FOO
BAR_test
BAR
Test

решение2

Лучшим вариантом, скорее всего, будет найти модуль токенизатора для perl. Grep не может сделать это без нескольких запусков, вероятно, требуя -P(PCRE).

Вот частичное решение без каких-либо модулей Perl:

while (<>) {
  my $n = 1;
  while (/(\S+)/g) {
    printf "// outputting whitespace-separated word %d\n", $n++;
    my $whole = $1;
    while ($whole =~ /([a-zA-Z0-9][a-z]*+)/g) {
      print "$1\n";
    }
    print "$whole\n";    # whole space-delimited tokens
  }
}

Это считывает входные данные из стандартного ввода или файлов, по одной строке за раз. $nявляется счетчиком слов для напечатанного комментария, затем мы итерируем по словам (как обозначено пробелами, таким образом, регулярное выражение /(\S+)/gглобально соответствует последовательным непробельным символам). Внутри каждого слова мы итерируем по частям токена, используя([a-zA-Z0-9][a-z]*+), все совпадения которого начинаются с цифр или букв, за которыми следует ноль или более строчных букв ( *+похоже *на случай, если откат отключен для защиты отReDoS). После того, как мы выведем все совпавшие токены в слове, мы выведем все слово целиком.

Вы запускаете это как perl solution.pl intput.txtили встраиваете так:

$ echo "I_amAManTest you_haveAHouse FOO_BAR_test.model" |perl solution.pl
// outputting whitespace-separated word 1
I
am
A
Man
Test
I_amAManTest
// outputting whitespace-separated word 2
you
have
A
House
you_haveAHouse
// outputting whitespace-separated word 3
F
O
O
B
A
R
test
model
FOO_BAR_test.model

Обратите внимание, что здесь отсутствуют составные части слов.

Также обратите внимание, что ваш запрос на I_AmAManразбор как I, Am, A, Manконфликтует с вашим запросом на FOO_BARразбор в FOO, BARа не F, O, O, B... как это делает приведенный выше код. (Возможно, лучшим примером будет: что должно I_AmOKстать? Три униграммы или четыре?)

решение3

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

$ cat tst.awk
{
    for (wordNr=1; wordNr<=NF; wordNr++) {
        delete ngrams
        word = $wordNr
        ngrams[word]
        print "word", word
        numUndSeps = split(word,undSeps,/_/)
        for (undSepNr=1; undSepNr<=numUndSeps; undSepNr++) {
            undSep = undSeps[undSepNr]
            ngrams[undSep]
            print "undSep", undSep
            numDotSeps = split(undSep,dotSeps,/[.]/)
            for (dotSepNr=1; dotSepNr<=numDotSeps; dotSepNr++) {
                dotSep = dotSeps[dotSepNr]
                ngrams[dotSep]
                print "dotSep", dotSep
                while ( match(dotSep,/[[:upper:]]+[^[:upper:]]+/) ) {
                    camel = substr(dotSep,RSTART,RLENGTH)
                    dotSep = substr(dotSep,RSTART+RLENGTH)
                    ngrams[camel]
                    print "camel", camel
                }
            }
        }
        print "-----------"
        for (ngram in ngrams) {
            print ngram
        }
        print "###########"
    }
}

.

$ awk -f tst.awk file
word I_amAManTest
undSep I
dotSep I
undSep amAManTest
dotSep amAManTest
camel AMan
camel Test
-----------
Test
amAManTest
I_amAManTest
I
AMan
###########
word you_haveAHouse
undSep you
dotSep you
undSep haveAHouse
dotSep haveAHouse
camel AHouse
-----------
you
you_haveAHouse
haveAHouse
AHouse
###########
word FOO_BAR_test.model
undSep FOO
dotSep FOO
undSep BAR
dotSep BAR
undSep test.model
dotSep test
dotSep model
-----------
model
FOO
FOO_BAR_test.model
test.model
BAR
test
###########

Связанный контент