列印大寫字母行中每個單字都以不同字母開頭的行

列印大寫字母行中每個單字都以不同字母開頭的行

我有這樣的文字:

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

 ............

如何使用grepsed列印大寫字母行中每個單字都以不同字母開頭的行?

例如:

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匹配的含義以獲得所需的輸出。

編輯:根據@他們的觀察,對其進行修改以查找大寫單字。

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 中的一行程式碼可以輕鬆解決這個問題,Raku 是以前稱為 Perl6(於 2019 年重新命名)的程式語言的新名稱。

-ne簡而言之,使用命令列標誌將輸入按行讀取到 Raku 中。輸入被分成以空格分隔的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#Predefined_character_classes
https://raku.org

相關內容