大文字の行の各単語が異なる文字で始まる行を印刷します。

大文字の行の各単語が異なる文字で始まる行を印刷します。

次のようなテキストがあります:

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データの文字でインデックス付けされた連想配列に保存されます。

最初の文字の 1 つに 2 回目に遭遇するとすぐに、その行を破棄します。破棄しない各行をこのように印刷します。

このコードは単語の初め文字がすべて大文字である単語の最初の文字をテストするには、代わりに次を使用します。

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最初の文字が行の下にあるが、別の大文字の単語の先頭にのみ見つかる大文字の単語を検索するように指示しています。これは少なくとも 1 つのそのような一致を意味しますが、そのような一致は望ましくないため、-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;
}

指定した基準ではサンプル入力行はどれも印刷されなかったため、2 つのサンプル出力行を入力に追加しました。

$ ./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

この問題は、以前は Perl6 と呼ばれていたプログラミング言語の新しい名前である Raku (2019 年に名前が変更されました) のワンライナーを使用すると簡単に解決できます。

簡単に言うと、入力はコマンドラインフラグを使用して行単位で Ra​​ku に読み込まれます-ne。入力は空白で区切られた に分割されwords、各単語が検査され ( を使用map)、comb大文字で始まる単語 (^<upper>正規表現を使用) がフィルタリングされます ( を使用)。その後、これらの文字は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

関連情報