
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 grep
o sed
imprimir 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 grep
y sed
son famosos por su mala capacidad de contar, al menos por sí mismos, mientras que awk
es más bien un lenguaje de programación de propósito general. Si queremos utilizar una sola herramienta para resolver la tarea, awk
probablemente 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.
awk
Para empezar, podrías conocer mejor el código buscando cada bit en el manual. Luego, cuando no entiendas por qué escribí delete count
en 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 grep
que 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 -v
el 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 -ne
banderas 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 Bag
con -ged, lo que cuenta el número de apariciones y sólo max == 1
se 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 split
rutina, perosinel if
condicional, y sin el max
condicional:
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