Imprima líneas donde cada palabra de la línea con letras mayúsculas comience con una letra diferente

Imprima líneas donde cada palabra de la línea con letras mayúsculas comience con una letra diferente

Tengo este texto:

FOUR MILLION, EIGHT HUNDRED AND FIFTY-SEVEN THOUSAND, FIVE HUNDRED AND THIRTEEN innovating
FORTY-NINE MILLION, ONE HUNDRED AND EIGHTY THOUSAND, TWO HUNDRED AND FORTY-EIGHT championed
FORTY-SEVEN MILLION, NINE HUNDRED AND FIFTY-TWO THOUSAND, EIGHT HUNDRED AND SIX swashbuckling
NINE HUNDRED AND SIXTY-ONE THOUSAND, SIX HUNDRED AND THIRTY-ONE sprinklers
FORTY-TWO MILLION, TWO HUNDRED AND SIXTY-SIX THOUSAND, THREE HUNDRED AND SEVENTY-TWO furloughs
SEVEN MILLION, FOUR HUNDRED AND SEVENTEEN THOUSAND, FOUR HUNDRED AND FORTY-TWO panicky
THREE HUNDRED AND SEVENTY-NINE THOUSAND, FIVE HUNDRED AND TWENTY-EIGHT anchovies
FIVE MILLION, EIGHT HUNDRED AND FIFTY-NINE THOUSAND, FOUR HUNDRED AND SIXTY-FOUR excesses

 ............

¿Cómo usar grepo sedimprimir las líneas donde cada palabra de la línea con letras mayúsculas comienza con una letra diferente?

Por ejemplo:

FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH

Respuesta1

La primera tarea al resolver un problema como este es elegir la herramienta correcta para el trabajo. En este problema, necesitamos contar el número de veces que aparece la letra inicial de cada palabra en una línea. Ambos grepy sedson famosos por su mala capacidad de contar, al menos por sí mismos, mientras que awkes más bien un lenguaje de programación de propósito general. Si queremos utilizar una sola herramienta para resolver la tarea, awkprobablemente sea más adecuada.

awk '{
    delete count
    for (i = 1; i <= NF; ++i) {
        ch = substr($i,1,1)
        if (ch == toupper(ch) && count[ch]++)
            next
    }
    print
}' file

El código cuenta las apariciones de las letras mayúsculas iniciales de todas las palabras en cada línea (una palabra es una subcadena delimitada por espacios en blanco). Mantenemos los recuentos en la matriz asociativa count, indexados por las letras de los datos.

Descartamos la línea en cuanto nos topamos con una de las letras iniciales por segunda vez. Imprimimos cada línea que no descartamos de esta forma.

A este código sólo le importa si la palabraprimeroEl carácter está en mayúsculas. Para probar el primer carácter de palabras que están todas en mayúsculas, utilice lo siguiente:

awk '{
    delete count
    for (i = 1; i <= NF; ++i)
        if ($i != toupper($i) && count[substr($i,1,1)]++)
            next
    print
}' file

La siguiente cuestión es comprender el código. Tienesconsiguióel código ahora y funciona, pero es posible que no sepas por qué. Más importante aún, es posible que no sepa cómo modificarlo para que haga algo ligeramente diferente o cómo corregirlo si falla repentinamente en algún caso extremo que descubra.

awkPara empezar, podrías conocer mejor el código buscando cada bit en el manual. Luego, cuando no entiendas por qué escribí delete counten ese lugar en particular y no en otro, puedes hacer otra pregunta al respecto, o mejor aún, experimentar con el código y observar de qué maneras específicas se rompe.

Respuesta2

Puede utilizar la expresión regular para escanear la entrada y obtener el resultado deseado.

Le decimos grepque busque una palabra en mayúscula cuyo primer carácter se encuentre más adelante en la línea, pero solo al comienzo de otra palabra en mayúscula. Dado que esto implica al menos una de esas coincidencias, pero no queremos ninguna, invertimos -vel sentido de la coincidencia para obtener el resultado deseado.

Editado: en base a las observaciones de @they se modifica para buscar palabras en mayúsculas.

grep -v  '\<\([A-Z]\)[A-Z]\{1,\}\>.*\<\1[A-Z]\{1,\}\>'  file

Respuesta3

El siguiente script en Perl es demasiado detallado y podría acortarse considerablemente, pero fue escrito para demostrar el algoritmo de forma clara y no crípticamente concisa:

$ cat caps.pl
#!/usr/bin/perl
use strict;

MAIN: while(<>) {
  # skip lines without a capital letter
  next unless /[A-Z]/;

  # hash to hold the counts of the first letters of each word,
  # reset to empty for every input line
  my %letters = ();

  foreach my $w (split /[-\s]+/) {
    # ignore "words" not beginning with a letter
    next unless $w =~ m/^[[:alpha:]]/; 

    # get the first character of the word
    my $l = substr($w,0,1);

    # uncomment if you want upper- and lower-case to be treated
    # as the same letter:
    #$l = uc($l);

    $letters{$l}++;

    # If we've seen this letter before on this line, skip to the
    # next input line.
    next MAIN if $letters{$l} > 1;
  };

  # the input line has no first letters which appear more than once, so print it.
  print;
}

Ninguna de sus líneas de entrada de muestra se imprimiría con los criterios que proporcionó, así que agregué sus dos líneas de salida de muestra a la entrada:

$ ./caps.pl input.txt 
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGHT

Respuesta4

Usando Raku (anteriormente conocido como Perl_6)

raku -ne '.put if .words.map(*.comb(/ ^<upper> /)).Bag.values.max == 1;'  

Entrada de muestra:

FOUR MILLION, EIGHT HUNDRED AND FIFTY-SEVEN THOUSAND, FIVE HUNDRED AND THIRTEEN innovating
FORTY-NINE MILLION, ONE HUNDRED AND EIGHTY THOUSAND, TWO HUNDRED AND FORTY-EIGHT championed
FORTY-SEVEN MILLION, NINE HUNDRED AND FIFTY-TWO THOUSAND, EIGHT HUNDRED AND SIX swashbuckling
NINE HUNDRED AND SIXTY-ONE THOUSAND, SIX HUNDRED AND THIRTY-ONE sprinklers
FORTY-TWO MILLION, TWO HUNDRED AND SIXTY-SIX THOUSAND, THREE HUNDRED AND SEVENTY-TWO furloughs
SEVEN MILLION, FOUR HUNDRED AND SEVENTEEN THOUSAND, FOUR HUNDRED AND FORTY-TWO panicky
THREE HUNDRED AND SEVENTY-NINE THOUSAND, FIVE HUNDRED AND TWENTY-EIGHT anchovies
FIVE MILLION, EIGHT HUNDRED AND FIFTY-NINE THOUSAND, FOUR HUNDRED AND SIXTY-FOUR excesses
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH

Salida de muestra:

FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH

Este problema se resuelve fácilmente usando una sola línea en Raku, el nuevo nombre del lenguaje de programación anteriormente conocido como Perl6 (renombrado en 2019).

Brevemente, la entrada se lee en línea en Raku usando las -nebanderas de la línea de comando. La entrada se divide en espacios en blanco separados words, cada una de esas palabras se examina (usando map) y se filtra (usando comb) para palabras que comienzan con una letra mayúscula (usando ^<upper>expresiones regulares). Luego, esas letras se escriben Bagcon -ged, lo que cuenta el número de apariciones y sólo max == 1se devuelven las líneas donde existen apariciones (es decir, sin letras duplicadas).

Parece haber algunos comentarios sobre lo que constituye una "palabra" para este problema. Si desea contar las palabras con guiones como palabras separadas, primero divídalas en guiones agregándolas .split("-")al comienzo de la cadena de métodos (antes .words).

Para darle una idea de cómo funciona el código Raku anterior, aquí está el núcleo del código.conla splitrutina, perosinel ifcondicional, y sin el maxcondicional:

raku -ne '.split("-").words.map(*.comb(/ ^<upper> /)).Bag.put;' 

H(2) M A(2) T(2) E S F(3)
T(2) N E(2) H(2) O F(2) M A(2)
M S(2) T(2) N A(2) E H(2) F(2)
O(2) H(2) S(2) A(2) T(2) N
M H(2) A(2) S(3) F T(5)
S(2) F(3) A(2) H(2) T(2) M
T(3) H(2) S E F N A(2)
H(2) T S M N A(2) F(4) E
A E F H N T
E T F N H S A

https://docs.raku.org/language/regexes#Predefinido_character_classes
https://raku.org

información relacionada