
У меня есть такой текст:
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
............
Как использовать grep
или sed
печатать строки, в которых каждое слово строки с заглавными буквами начинается с другой буквы?
Например:
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
решение1
Первая задача при решении такой задачи — выбрать правильный инструмент для работы. В этой задаче нам нужно подсчитать, сколько раз в строке встречается начальная буква каждого слова. Оба grep
и sed
известны тем, что плохо умеют считать, по крайней мере сами по себе, в то время как awk
— это скорее язык программирования общего назначения. Если мы хотим использовать какой-либо один инструмент для решения задачи, то, awk
вероятно, лучше подойдет.
awk '{
delete count
for (i = 1; i <= NF; ++i) {
ch = substr($i,1,1)
if (ch == toupper(ch) && count[ch]++)
next
}
print
}' file
Код подсчитывает вхождения начальных заглавных букв всех слов в каждой строке (слово является подстрокой, разделенной пробелом). Мы сохраняем подсчеты в ассоциативном массиве count
, индексированном буквами из данных.
Мы отбрасываем строку, как только встречаем одну из начальных букв во второй раз. Мы печатаем каждую строку, которую не отбрасываем таким образом.
Этот код заботится только о том,первыйсимвол заглавный. Чтобы проверить первый символ слов, которые все заглавные, используйте следующее:
awk '{
delete count
for (i = 1; i <= NF; ++i)
if ($i != toupper($i) && count[substr($i,1,1)]++)
next
print
}' file
Следующий вопрос — понять код. Выполучилкод сейчас, и он работает, но вы можете не знать почему. Что еще важнее, вы можете не знать, как изменить его, чтобы он делал что-то немного другое, или как исправить его, если он внезапно даст сбой в каком-то пограничном случае, который вы обнаружите.
Вы могли бы лучше узнать код, просматривая каждый бит в awk
руководстве для начала. Затем, когда вы не понимаете, почему я написал delete count
в этом конкретном месте, а не в другом, вы могли бы задать другой вопрос об этом, или, что еще лучше, поэкспериментировать с кодом и отметить, какими именно способами он ломается.
решение2
Вы можете использовать регулярное выражение для сканирования входных данных и получения желаемого результата.
Мы говорим grep
искать заглавное слово, первый символ которого находится ниже по строке, но только в начале другого заглавного слова. Поскольку это подразумевает по крайней мере одно такое совпадение, а мы не хотим таких совпадений, мы инвертируем -v
смысл совпадения, чтобы получить желаемый вывод.
Отредактировано: на основе наблюдений @they он изменен для поиска слов с заглавными буквами.
grep -v '\<\([A-Z]\)[A-Z]\{1,\}\>.*\<\1[A-Z]\{1,\}\>' file
решение3
Следующий скрипт Perl слишком многословен и может быть значительно сокращен, но он был написан для наглядной демонстрации алгоритма, а не для его скрытной краткости:
$ 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;
}
Ни один из ваших примеров входных строк не будет напечатан с заданными вами критериями, поэтому я добавил ваши два примера выходных строк к входным данным:
$ ./caps.pl input.txt
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGHT
решение4
Использование Raku (ранее известного как Perl_6)
raku -ne '.put if .words.map(*.comb(/ ^<upper> /)).Bag.values.max == 1;'
Пример ввода:
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
Пример вывода:
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
Эту проблему легко решить с помощью однострочного кода на языке Raku — новом названии языка программирования, ранее известного как Perl6 (переименованного в 2019 году).
Вкратце, ввод считывается построчно в Raku с использованием -ne
флагов командной строки. Ввод разбивается на разделенные пробелами words
, каждое из этих слов проверяется (с помощью map
) и фильтруется (с помощью comb
) на наличие слов, начинающихся с заглавной буквы (с помощью ^<upper>
regex). Затем эти буквы подвергаются Bag
-ged, что подсчитывает количество вхождений, и max == 1
возвращаются только строки, в которых есть вхождения (т. е. нет повторяющихся букв).
Кажется, есть некоторые комментарии о том, что составляет "слово" для этой проблемы. Если вы хотите считать слова с дефисами как отдельные слова, сначала разделите их по дефисам, добавив .split("-")
в начало цепочки методов (перед .words
).
Чтобы дать вам представление о том, как работает код Raku, приведенный выше, вот его ядро:срутина split
, нобезусловное if
и без max
условного:
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#Предопределенные_классы_символов
https://raku.org