Вывести строки, в которых каждое слово с заглавными буквами начинается с другой буквы.

Вывести строки, в которых каждое слово с заглавными буквами начинается с другой буквы.

У меня есть такой текст:

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

Связанный контент