下面的代码片段生成输入文本中所有大写单词的列表:
grep -o '[^ ]*[[:upper:]][^ ]*' book_text.txt > Capitalized_words.txt
现在我想提取并计算任意长度的唯一大写短语的出现次数。
也就是说,我想要一个共享首字母大写的空格分隔单词的唯一字符串的计数。给定的唯一短语不包含标点符号或非大写单词,因此University of British Columbia
将是两个短语,University
和British Columbia
。
输入示例:
Harvard archaeologists in Mexico also participated in the International
School of American Archaeology and Ethnology in Mexico City with scholars from
Mexico, Prussia and the United States.
预期输出:
1 - Harvard
1 - International School
1 - American Archaeology
1 - Ethnology
1 - Mexico City
2 - Mexico
1 - Prussia
1 - United States
请注意,在示例中,Mexico
和Mexico City
是共享一个单词的两个不同的独特短语。
答案1
如果使用 GNUgrep
构建 PCRE 支持:
$ grep -Pow '(\p{Lu}\w*)(\s+(?1))*' input | sort | uniq -c
1 American Archaeology
1 Ethnology
1 Example Input
1 Harvard
1 International School
2 Mexico
1 Mexico City
1 Prussia
1 United States
或者:
<input tr -s '[:space:]' '[ *]' |
grep -Pow '(\p{Lu}\w*)(\s+(?1))*' |
sort |
uniq -c
首先将所有空白字符序列(包括换行符)转换为单个空格,例如Example Input
、Example Input
或Example\nInput
被认为是相同的。
请注意,这-w
不是为了空格分隔单词,单词边界位于单词和非单词字符之间(单词字符是数字和下划线)。你确实说你想要 以空格分隔的单词,但这与您的输入中的期待United States
代替United States.
或Mexico
代替相矛盾。Mexico, Prussia
另请注意,[^ ]*[[:upper:]][^ ]*
将匹配空白分隔的单词包含至少有一个大写字母,但这不一定是在开头。例如,它将匹配fooBar
或0xAB+12
。您需要(?<!\S)\p{Lu}\S*
一个以大写字母开头的空格分隔的单词。
$ grep -Po '(?<!\S)(\p{Lu}\S*)(\s+(?1))*' input | sort | uniq -c
1 American Archaeology
1 Ethnology
1 Example Input:
1 International School
1 Mexico
1 Mexico City
1 Mexico, Prussia
1 United States."
(Harvard
丢失是因为我有一整行Example Input: "Havard ..."
,input
所以空格分隔的单词"Harvard
不是以大写字母开头)。
您还可以在中间添加一些in
s 和s:of
$ grep -Pow '(\p{Lu}\w*)((\s+(in|of))?\s+(?1))*' input | sort | uniq -c
1 Ethnology in Mexico City
1 Example Input
1 Harvard
1 International School of American Archaeology
2 Mexico
1 Prussia
1 United States
如果处理非英文文本,您可能还想替换\w
为((?=\w)\X)
,即代替单词字符,匹配一个以单词字符开头的字素簇。
$ echo $'Universidad Nacional Auto\u0301noma de Me\u0301xico' |
grep -Pow '(\p{Lu}\w*)((\s+(in|of|de))?\s+(?1))*' | sort | uniq -c
1 Me
1 Universidad Nacional Auto
$ echo $'Universidad Nacional Auto\u0301noma de Me\u0301xico' |
grep -Pow '((?=\p{Lu})\X((?=\w)\X)*)((\s+(in|of|de))?\s+(?1))*' |
sort | uniq -c
1 Universidad Nacional Autónoma de México
那仍然会匹配Bar
in $'foo\u0301Bar'
。
您可能还需要细化单词字符/字素的构成和/或分隔符要涵盖的名称,例如苏西的厨房,奥布莱恩小学,让·保罗·萨特中学, ETC。
将所有这些放在一起,我们最终可以得到:
first_grapheme='(?: (?= \p{Lu} ) \X )'
word_character="[\w'-]"
word_grapheme="(?: (?= $word_character ) \X )"
word="$first_grapheme $word_grapheme *"
separator='(?: [ ] (?: in | on | of | de | en ) )? [ ]'
<input tr -s '[:space:]' '[ *]' |
grep -Po "(?x) (?<! \pM | $word_character ) $word (?: $separator $word ) *" |
sort |
uniq -c
答案2
使用 Raku(以前称为 Perl_6)
raku -e '.subst(",", " and ", :g).subst(".", " ", :g).comb(/ <( [ <:Lu> <:Ll>+ \h+ ]+ )> <:Ll>* /).map(*.trim-trailing).Bag.antipairs.join("\n").say for lines();'
输入示例:
Harvard archaeologists in Mexico also participated in the International School of American Archaeology and Ethnology in Mexico City with scholars from Mexico, Prussia and the United States.
示例输出(最终):
1 Ethnology
1 Prussia
2 Mexico
1 American Archaeology
1 Harvard
1 Mexico City
1 International School
1 United States
有趣的问题,我决定用 Raku 来解决它,因为它被认为有一个相当先进的正则表达式引擎(根据底部的参考文献,比 PCRE 更先进)。
我们可以首先将 Raku 代码分为三个主要部分。该comb
部分使用正则表达式匹配器将文本输入分解为所需的元素。您可能已经熟悉许多符号(或概念上熟悉)。例如,Raku 中的捕获标记是<(…)>
。仅此comb
部分就返回 8/9 的预期值,尽管仅部分返回了 1 个。
raku -e '.comb(/ <( [ <:Lu> <:Ll>+ \h+ ]+ )> <:Ll>* /).join("\n").say for lines();'
示例输出(尝试#1):
Harvard
Mexico
International School
American Archaeology
Ethnology
Mexico City
Prussia
United
我们立即看到必须对标点符号采取一些措施,因为值Mexico,
和部分值States.
都被留下了。第二次尝试:
raku -e '.subst(",", " and ", :g).subst(".", " ", :g).comb(/ <( [ <:Lu> <:Ll>+ \h+ ]+ )> <:Ll>* /).join("\n").say for lines();'
示例输出(尝试#2):
Harvard
Mexico
International School
American Archaeology
Ethnology
Mexico City
Mexico
Prussia
United States
以上返回 9/9 的预期值。最后我决定,
用 , 替换逗号and
,用空格替换.
句号。 (您必须为您的文本决定最佳的操作方案)。
为了获得顶部的最终结果,.map(*.trim-trailing).Bag.antipairs
在代码中插入了调用,从而产生了所需的结果。
https://slides.yowconference.com/yowwest2015/Conway-EverythingYouKnowAboutRegexesIsWrong.pdf
https://youtu.be/ubvSjW6Nyqk
https://raku.org