텍스트의 모든 n-gram을 찾는 하나의 셸 명령

텍스트의 모든 n-gram을 찾는 하나의 셸 명령

공백으로 구분된 단어가 포함된 텍스트 스트림이나 파일이 있습니다. 좋다:

I have a toy. you may not like it.

공백으로 구분된 각 단어는 두 개 이상의 작은 단어로 구성될 수 있으며, 낙타 표기(다른 대소문자로 구분), 뱀 표기(밑줄로 구분) 또는 점으로 구분됩니다. 예를 들면 다음과 같습니다.

I_amAManTest you_haveAHouse FOO_BAR_test.model

예를 들어:

I_amAManTest

그것은 나눌 수 있습니다 :

I
am
A
Man
Test

하지만 나는 매일 인쇄하고 싶어요.N복합어의 단어(연속되는 작은 단어의 각 하위 집합). 예:

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— "mOK"를 "m"으로 변경합니다.
    • s/\([[:upper:]]\)[[:upper:]][[:lower:]]\+$/\1/p; t ss— "AMan"을 "A"로 변경합니다.
    • s/[._][[:alpha:]][[:lower:]]*$//p; t ss— "_am"을 제거합니다(아무 것도 대체하지 않음).
    • s/[._][[:upper:]]\+$//p; t ss— "_BAR"을 제거합니다(비어 있는 것으로 교체).
  • 이것이 보조 루프의 끝입니다.
  • g- 홀드 공간을 패턴 공간으로 복사합니다(위 루프의 시작 부분으로 돌아감).
  • 다음 명령은 복합어의 시작 부분에서 작은 단어를 제거하고, 성공하면 메인 루프의 끝으로 점프합니다(mw = 메인 루프 요약).
  • s/^[[:upper:]]\?[[:lower:]]\+\([[:upper:]]\)/\1/; t mw— "amA"를 "A"로, "ManT"를 "T"로 변경합니다.
  • s/^[[:upper:]]\+\([[:upper:]][[:lower:]]\)/\1/; t mw— "AMa"를 "Ma"로 변경합니다.
  • s/^[[:alpha:]][[:lower:]]*[._]//; t mw— "I_" 및 "you_"를 제거합니다(아무 것도 대체하지 않음).
  • s/^[[:upper:]]\+[._]//; t mw— "FOO_"를 제거합니다(아무 것도 대체하지 않음).
  • 위의 각 대체 명령은 성공하면(뭔가 발견/일치하는 경우) 기본 루프 요약(아래)으로 이동합니다. 여기까지 오면 패턴 공간에는 작은 단어 하나만 포함되므로 작업은 끝난 것입니다.
  • 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은 여러 번 실행해야 하는 -PPCRE(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]*+), 일치 항목은 모두 숫자나 문자로 시작하고 그 뒤에 0개 이상의 소문자가 옵니다( *+예: *역추적을 비활성화하여재실행 서비스). 단어에서 일치하는 모든 토큰을 인쇄한 후 전체 단어를 인쇄합니다.

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_AmAManI또한 , Am, 로 확인되는 요청은 위 코드에서와 같이 , , , ...가 아닌 A, 로 확인되는 요청과 충돌합니다 . Man(아마도 더 나은 예는 다음과 같습니다. 어떻게 되어야 합니까? 3개의 단항 단어입니까 아니면 4개의 단어입니까?)FOO_BARFOOBARFOOBI_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
###########

관련 정보