
我有這樣的文字:
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
匹配的含義以獲得所需的輸出。
編輯:根據@他們的觀察,對其進行修改以查找大寫單字。
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