
Eu tenho 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
............
Como usar grep
ou sed
imprimir as linhas onde cada palavra da linha com letras maiúsculas começa com uma letra diferente?
Por exemplo:
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
Responder1
A primeira tarefa ao resolver um problema como este é escolher a ferramenta correta para o trabalho. Neste problema, precisamos contar o número de vezes que a letra inicial de cada palavra ocorre em uma linha. Ambos grep
e sed
são notoriamente ruins em contagem, pelo menos por si só, embora awk
sejam mais uma linguagem de programação de uso geral. Se quisermos usar uma única ferramenta para resolver a tarefa, awk
provavelmente seria mais adequada.
awk '{
delete count
for (i = 1; i <= NF; ++i) {
ch = substr($i,1,1)
if (ch == toupper(ch) && count[ch]++)
next
}
print
}' file
O código conta as ocorrências das letras maiúsculas iniciais de todas as palavras em cada linha (uma palavra sendo uma substring delimitada por espaços em branco). Mantemos as contagens no array associativo count
, indexado pelas letras dos dados.
Descartamos a linha assim que nos deparamos com uma das letras iniciais pela segunda vez. Imprimimos cada linha que não descartamos dessa forma.
Este código só se importa se a palavra forprimeirocaractere é maiúsculo. Para testar o primeiro caractere de palavras todas em maiúsculas, use o seguinte:
awk '{
delete count
for (i = 1; i <= NF; ++i)
if ($i != toupper($i) && count[substr($i,1,1)]++)
next
print
}' file
A próxima questão é entender o código. Você tempegouo código agora e funciona, mas talvez você não saiba por quê. Mais importante ainda, você pode não saber como modificá-lo para fazer algo um pouco diferente ou como corrigi-lo se ele falhar repentinamente em algum caso extremo que você descobrir.
Você pode conhecer melhor o código consultando cada bit no awk
manual para começar. Então, quando você não entender por que escrevi delete count
naquele lugar específico e não em outro lugar, você pode fazer outra pergunta sobre isso ou, melhor ainda, experimentar o código e observar de que maneira específica ele quebra.
Responder2
Você pode usar o regex para fazer a varredura da entrada e obter a saída desejada.
Estamos dizendo grep
para procurar uma palavra maiúscula cujo primeiro caractere seja encontrado abaixo da linha, mas apenas no início de outra palavra maiúscula. Como isso implica pelo menos uma dessas correspondências, mas não queremos tais correspondências, invertemos -v
o sentido da correspondência para obter a saída desejada.
Editado: com base nas observações de @they é modificado para procurar palavras em maiúsculas.
grep -v '\<\([A-Z]\)[A-Z]\{1,\}\>.*\<\1[A-Z]\{1,\}\>' file
Responder3
O script perl a seguir é excessivamente detalhado e pode ser consideravelmente reduzido, mas foi escrito para demonstrar o algoritmo de forma clara, em vez de sucinto e criptografado:
$ 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;
}
Nenhuma das suas linhas de entrada de amostra seria impressa com os critérios que você forneceu, então adicionei suas duas linhas de saída de amostra à entrada:
$ ./caps.pl input.txt
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGHT
Responder4
Usando Raku (anteriormente conhecido como Perl_6)
raku -ne '.put if .words.map(*.comb(/ ^<upper> /)).Bag.values.max == 1;'
Entrada de amostra:
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
Saída de amostra:
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
Este problema é resolvido facilmente usando uma linha em Raku, o novo nome da linguagem de programação anteriormente conhecida como Perl6 (renomeada em 2019).
Resumidamente, a entrada é lida em linha no Raku usando os -ne
sinalizadores de linha de comando. A entrada é dividida em espaços em branco separados words
, cada uma dessas palavras é examinada (usando map
) e filtrada (usando comb
) para palavras que começam com uma letra maiúscula (usando ^<upper>
regex). Essas letras são então Bag
-ged, que conta o número de ocorrências, e apenas as linhas onde max == 1
existem ocorrências (ou seja, sem letras duplicadas) são retornadas.
Parece haver alguns comentários sobre o que constitui uma “palavra” para este problema. Se você deseja contar palavras hifenizadas como palavras separadas, divida primeiro os hifens adicionando .split("-")
ao início da cadeia de métodos (antes de .words
).
Para lhe dar uma ideia de como o código Raku acima está funcionando, aqui está o núcleo do códigocoma split
rotina, massemo if
condicional e sem o 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/linguagem/regexes#Predefinido_character_classes
https://raku.org