un comando de shell para encontrar cada n-grama que funciona en el texto

un comando de shell para encontrar cada n-grama que funciona en el texto

Tengo una secuencia de texto o un archivo que contiene palabras separadas por espacios en blanco. Como:

I have a toy. you may not like it.

Cada palabra separada por espacios en blanco puede estar compuesta por dos o más palabras pequeñas, tal vez organizadas por mayúsculas y minúsculas (separadas por mayúsculas y minúsculas), mayúsculas y minúsculas (separadas por subrayado) o separadas por puntos, como:

I_amAManTest you_haveAHouse FOO_BAR_test.model

Por ejemplo:

I_amAManTest

se puede dividir en:

I
am
A
Man
Test

pero quiero imprimir cadanortepalabras (cada subconjunto de palabras pequeñas contiguas) en la palabra compuesta, como:

I_amAManTest

producción:

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

En conclusión, para entradas como

I_amAManTest you_haveAHouse FOO_BAR_test

la salida debe ser

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

Respuesta1

Una solución (en su mayoría) 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'

El algoritmo es

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

Detalles:

  • cat "$@"es un UUOC. Normalmente los evito; Puedes hacerlo , pero no puedes pasar varios archivos  directamente.tr args <filetr
  • tr -cs -- '._[:alpha:]' '[\n*]'divide una línea de muchas palabras compuestas en líneas separadas; p.ej,
    I_amAManTest you_haveAHouse FOO_BAR_test
    
    se convierte
    I_amAManTest
    you_haveAHouse
    FOO_BAR_test
    
    entonces sed puede procesar una palabra compuesta a la vez.
  • sed -n— no imprimir nada automáticamente; imprimir sólo cuando se le ordene.
  • -eespecifica que lo siguientemixpression es parte del script sed.
  • h— copie el espacio del patrón en el espacio de espera.
  • :ms— una etiqueta (Inicio del bucle principal)
  • p- imprimir
  • :ss— una etiqueta (inicio del bucle secundario)
  • Los siguientes comandos eliminan una palabra pequeña del final de una palabra compuesta y, si tienen éxito, imprimen el resultado y regresan al principio del ciclo secundario.
    • s/\([[:lower:]]\)[[:upper:]][[:lower:]]*$/\1/p; t ss— cambia “nTest” a “n”.
    • s/\([[:lower:]]\)[[:upper:]][[:upper:]]*$/\1/p; t ss— cambia “mOK” a “m”.
    • s/\([[:upper:]]\)[[:upper:]][[:lower:]]\+$/\1/p; t ss— cambia “AMan” a “A”.
    • s/[._][[:alpha:]][[:lower:]]*$//p; t ss— elimina “_am” (lo reemplaza por nada).
    • s/[._][[:upper:]]\+$//p; t ss— elimina “_BAR” (lo reemplaza por nada).
  • Este es el final del ciclo secundario.
  • g— copia el espacio de espera al espacio del patrón (vuelve a lo que tenías al principio del bucle anterior).
  • Los siguientes comandos eliminan una palabra pequeña del principio de una palabra compuesta y, si tienen éxito, saltan hasta el final del bucle principal (mw = Resumen del bucle principal).
  • s/^[[:upper:]]\?[[:lower:]]\+\([[:upper:]]\)/\1/; t mw— cambia “amA” a “A” y “ManT” a “T”.
  • s/^[[:upper:]]\+\([[:upper:]][[:lower:]]\)/\1/; t mw— cambia “AMa” a “Ma”.
  • s/^[[:alpha:]][[:lower:]]*[._]//; t mw— elimina “I_” y “you_” (los reemplaza por nada).
  • s/^[[:upper:]]\+[._]//; t mw— elimina “FOO_” (lo reemplaza por nada).
  • Cada uno de los comandos sustitutos anteriores salta al resumen del bucle principal (a continuación) si tiene éxito (si encuentra/coincide con algo). Si llegamos aquí, el espacio del patrón contiene solo una palabra pequeña, así que terminamos.
  • b— saltar (saltar) hasta el final del script sed; es decir, salir del script sed.
  • :mw— etiqueta para resumen del bucle principal.
  • h— copie el espacio del patrón en el espacio de retención, para prepararnos para la siguiente iteración del bucle principal.
  • b ms— salta al principio del bucle principal.

Produce la salida solicitada. Desafortunadamente, lo pone en un orden diferente. Probablemente pueda arreglar eso si es importante.

$ 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

Respuesta2

Es probable que su mejor opción sea encontrar un módulo tokenizador para Perl. Grep no puede hacer esto sin varias ejecuciones, probablemente necesite -P(PCRE).

Aquí hay una solución parcial sin ningún módulo 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
  }
}

Esto lee entradas de archivos o entradas estándar, una línea a la vez. $nes un contador de palabras para el comentario impreso, luego iteramos a través de las palabras (según lo delineado por espacios en blanco, por lo tanto, la expresión regular /(\S+)/gcoincide globalmente con caracteres consecutivos que no son espacios en blanco). Dentro de cada palabra, iteramos sobre las partes del token usando([a-zA-Z0-9][a-z]*+), cuyas coincidencias comienzan todas con números o letras y van seguidas de cero o más letras minúsculas ( *+es como *tener el retroceso desactivado para protegerse contra unRehacer). Después de imprimir todas las fichas coincidentes en la palabra, imprimimos la palabra completa.

Ejecute esto como perl solution.pl intput.txto en línea como:

$ 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

Tenga en cuenta que a esto le faltan los subtokens de palabras de varias partes.

También tenga en cuenta que su solicitud de I_AmAMananalizar como I, Am, entra Aen Manconflicto con su solicitud de FOO_BARanalizar en FOO, BARen lugar de F, O, O, B... como lo hace el código anterior. (Quizás un mejor ejemplo sería: ¿qué debería I_AmOKser? ¿Tres unigramos o cuatro?)

Respuesta3

Este es un comienzo, solo tendrá que modificarlo una vez que determine sus requisitos para las cadenas que contienen mezclas de letras mayúsculas y minúsculas e imprimir el resultado en cualquier orden que esté mostrando en su pregunta:

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

información relacionada