Ein Shell-Befehl zum Finden aller N-Gramm-Werke im Text

Ein Shell-Befehl zum Finden aller N-Gramm-Werke im Text

Ich habe einen Textstream oder eine Datei, die durch Leerzeichen getrennte Wörter enthält. Wie:

I have a toy. you may not like it.

Jedes durch Leerzeichen getrennte Wort kann aus zwei oder mehr kleinen Wörtern bestehen, die möglicherweise in Camel Case (getrennt durch unterschiedliche Groß- und Kleinschreibung), Snake Case (getrennt durch Unterstrich) oder durch einen Punkt getrennt sind, wie:

I_amAManTest you_haveAHouse FOO_BAR_test.model

Zum Beispiel:

I_amAManTest

kann unterteilt werden in:

I
am
A
Man
Test

aber ich möchte jeden druckenNWörter (jede Teilmenge zusammenhängender kleiner Wörter) im zusammengesetzten Wort, wie:

I_amAManTest

Ausgabe:

// 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

Zusammenfassend lässt sich also sagen, dass für Eingaben wie

I_amAManTest you_haveAHouse FOO_BAR_test

Die Ausgabe sollte

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

Antwort1

Eine (meistens-) sedLösung:

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'

Der Algorithmus ist

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

Einzelheiten:

  • cat "$@"ist ein UUOC. Normalerweise vermeide ich diese. Sie können dies tun , aber Sie können nicht mehrere Dateien  direkt an übergeben.tr args <filetr
  • tr -cs -- '._[:alpha:]' '[\n*]'teilt eine Zeile mit vielen zusammengesetzten Wörtern in einzelne Zeilen auf; z. B.
    I_amAManTest you_haveAHouse FOO_BAR_test
    
    wird
    I_amAManTest
    you_haveAHouse
    FOO_BAR_test
    
    sed kann also jeweils ein zusammengesetztes Wort verarbeiten.
  • sed -n– nichts automatisch drucken; nur auf Befehl drucken.
  • -elegt fest, dass die folgendentxpression ist Teil des Sed-Skripts.
  • h– Kopieren Sie den Musterbereich in den Haltebereich.
  • :ms— ein Label (Start der Hauptschleife)
  • p- drucken
  • :ss— ein Etikett (Start der Sekundärschleife)
  • Die folgenden Befehle entfernen ein kleines Wort vom Ende eines zusammengesetzten Wortes und drucken, wenn dies erfolgreich ist, das Ergebnis und springen zurück zum Anfang der sekundären Schleife.
    • s/\([[:lower:]]\)[[:upper:]][[:lower:]]*$/\1/p; t ss— ändert „nTest“ in „n“.
    • s/\([[:lower:]]\)[[:upper:]][[:upper:]]*$/\1/p; t ss— ändert „mOK“ in „m“.
    • s/\([[:upper:]]\)[[:upper:]][[:lower:]]\+$/\1/p; t ss— ändert „AMan“ in „A“.
    • s/[._][[:alpha:]][[:lower:]]*$//p; t ss– löscht „_am“ (ersetzt es durch nichts).
    • s/[._][[:upper:]]\+$//p; t ss– löscht „_BAR“ (ersetzt es durch nichts).
  • Dies ist das Ende der Sekundärschleife.
  • g– kopieren Sie den Haltebereich in den Musterbereich (gehen Sie zurück zu dem, was Sie am Anfang der obigen Schleife hatten).
  • Die folgenden Befehle entfernen ein kleines Wort vom Anfang eines zusammengesetzten Wortes und springen, wenn dies erfolgreich ist, zum Ende der Hauptschleife (mw = Main loop Wrap-up).
  • s/^[[:upper:]]\?[[:lower:]]\+\([[:upper:]]\)/\1/; t mw— ändert „amA“ in „A“ und „ManT“ in „T“.
  • s/^[[:upper:]]\+\([[:upper:]][[:lower:]]\)/\1/; t mw— ändert „AMa“ in „Ma“.
  • s/^[[:alpha:]][[:lower:]]*[._]//; t mw– löscht „Ich_“ und „Du_“ (ersetzt sie durch nichts).
  • s/^[[:upper:]]\+[._]//; t mw– löscht „FOO_“ (ersetzt es durch nichts).
  • Jeder der oben genannten Ersatzbefehle springt zum Abschluss der Hauptschleife (unten), wenn er erfolgreich ist (wenn er etwas findet/übereinstimmt). Wenn wir hier ankommen, enthält der Musterraum nur ein kleines Wort, also sind wir fertig.
  • b– Verzweigung (Sprung) zum Ende des Sed-Skripts, d. h. Beenden des Sed-Skripts.
  • :mw– Bezeichnung für die Zusammenfassung der Hauptschleife.
  • h– kopieren Sie den Musterbereich in den Haltebereich, um uns für die nächste Iteration der Hauptschleife vorzubereiten.
  • b ms– zum Anfang der Hauptschleife springen.

Es erzeugt die gewünschte Ausgabe. Leider wird sie in einer anderen Reihenfolge angezeigt. Ich kann das wahrscheinlich beheben, wenn es wichtig ist.

$ 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

Antwort2

Am besten finden Sie wahrscheinlich ein Tokenizer-Modul für Perl. Grep kann dies nicht ohne mehrere Durchläufe tun und benötigt wahrscheinlich -P(PCRE).

Hier ist eine Teillösung ohne Perl-Module:

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
  }
}

Dies liest Eingaben aus der Standardeingabe oder aus Dateien, Zeile für Zeile. $nist ein Wortzähler für den gedruckten Kommentar, dann iterieren wir durch Wörter (getrennt durch Leerzeichen, sodass der reguläre Ausdruck /(\S+)/gglobal aufeinanderfolgende Zeichen ohne Leerzeichen abgleicht). Innerhalb jedes Wortes iterieren wir über die Token-Teile mit([a-zA-Z0-9][a-z]*+), deren Übereinstimmungen alle mit Zahlen oder Buchstaben beginnen und von null oder mehr Kleinbuchstaben gefolgt werden ( *+ist wie *bei deaktiviertem Backtracking zum Schutz vorReDoS). Nachdem wir alle übereinstimmenden Token im Wort ausgedruckt haben, drucken wir das ganze Wort.

Sie führen dies wie perl solution.pl intput.txtfolgt aus oder inline wie:

$ 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

Beachten Sie, dass hier die mehrteiligen Untertoken von Wörtern fehlen.

Beachten Sie auch, dass Ihre Anforderung, als , , , I_AmAManzu analysieren , im Widerspruch zu Ihrer Anforderung steht , in , statt in , , , ... zu analysieren, wie es der obige Code tut. (Vielleicht wäre ein besseres Beispiel: Was soll werden? Drei Unigramme oder vier?)IAmAManFOO_BARFOOBARFOOBI_AmOK

Antwort3

Hier ist ein Anfang. Sie müssen es nur noch anpassen, sobald Sie Ihre Anforderungen für Zeichenfolgen herausgefunden haben, die eine Mischung aus Groß- und Kleinbuchstaben enthalten, und die Ausgabe in der Reihenfolge drucken möchten, die in Ihrer Frage angegeben ist:

$ 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
###########

verwandte Informationen