打印大写字母行中每个单词都以不同字母开头的行

打印大写字母行中每个单词都以不同字母开头的行

我有这样的文字:

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 年重命名)的编程语言的新名称。

简而言之,使用命令行标志将输入逐行读入 Raku -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#Predefined_character_classes
https://raku.org

相关内容