テキスト内のあらゆるn-gram作品を見つけるための1つのシェルコマンド

テキスト内のあらゆるn-gram作品を見つけるための1つのシェルコマンド

空白で区切られた単語を含むテキスト ストリームまたはファイルがあります。次のようになります。

I have a toy. you may not like it.

空白で区切られた各単語は、2 つ以上の小さな単語で構成され、キャメル ケース (大文字と小文字で区切る)、スネーク ケース (下線で区切る)、またはドットで区切られる場合があります。

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 は一度に 1 つの複合語を処理できます。
  • sed -n— 何も自動的に印刷せず、指示されたときのみ印刷します。
  • -e以下を指定しますexpression は 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 は複数回実行しなければこれを実行できないため、おそらく-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
  }
}

これは、標準入力またはファイルからの入力を1行ずつ読み取ります。は$n、印刷されたコメントの単語カウンタです。次に、単語を反復処理します(空白で区切られているため、正規表現は/(\S+)/g連続する空白以外の文字にグローバルに一致します)。各単語内で、トークン部分を反復処理します。([a-zA-Z0-9][a-z]*+)、すべての一致が数字または文字で始まり、0個以上の小文字が続く(バックトラックを無効にして*+*再DoS)。単語内の一致したトークンをすべて出力した後、単語全体を出力します。

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するという要求は、を 、、、 ...ではなくに解析するという要求と競合することに注意してください。 (おそらく、より良い例は、何になるでしょうか? 3 つのユニグラムか、4 つのユニグラムか? です。)IAmAManFOO_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
###########

関連情報