【问题标题】:Using awk to find a domain name containing the longest repeated word使用awk查找包含最长重复单词的域名
【发布时间】:2016-05-27 01:09:51
【问题描述】:

例如,假设有一个名为 domains.csv 的文件,其内容如下:

1,helloguys.ca
2,byegirls.com
3,hellohelloboys.ca
4,hellobyebyedad.com
5,letswelcomewelcomeyou.org

我正在尝试使用 linux awk 正则表达式来查找包含最长重复1 单词的行,因此在这种情况下,它将返回该行

5,letswelcomewelcomeyou.org

我该怎么做?

1 意思是“立即重复”,即abcabc,但不是abcXabc

【问题讨论】:

  • 如果有多个最长重复单词怎么办? (第一个,最后一个,全部?)
  • 我猜都是!
  • 第一个问题是如何找到一条记录的最长重复子串,假设你有“bbwelcomewelcome”之类的东西,因为正则表达式引擎从左到右工作,基本模式会找到“bb” .
  • 也许将第一个重复的子字符串存储到一个变量中,如果它在同一个字符串中找到另一个具有更长重复子字符串的子字符串,那么用新的子字符串覆盖第一个第一个子字符串?
  • “重复”是指“紧挨着”,还是重复之间可以有字符,如welcomeblahwelcome

标签: regex bash awk


【解决方案1】:

纯粹的 awk 实现会相当冗长,因为 awk 正则表达式没有反向引用,使用反向引用可以大大简化方法。

对于多个最长单词的情况,我在示例输入文件中添加了一行:

1,helloguys.ca
2,byegirls.com
3,hellohelloboys.ca
4,hellobyebyedad.com
5,letswelcomewelcomeyou.org
6,letscomewelcomewelyou.org

这会得到重复序列最长的行:

cut -d ',' -f 2 infile | grep -Eo '(.*)\1' |
awk '{ print length(), $0 }' | sort -k 1,1 -nr |
awk 'NR==1 {prev=$1;print $2;next} $1==prev {print $2;next} {exit}' | grep -f - infile

由于这很不明显,让我们分解它的作用并查看每个阶段的输出:

  1. 删除带有行号的第一列以避免匹配具有重复数字的行号:

    $ cut -d ',' -f 2 infile
    helloguys.ca
    byegirls.com
    hellohelloboys.ca
    hellobyebyedad.com
    letswelcomewelcomeyou.org
    letscomewelcomewelyou.org
    
  2. 获取所有具有重复序列的行,仅提取该重复序列:

    ... | grep -Eo '(.*)\1'
    ll
    hellohello
    ll
    byebye
    welcomewelcome
    comewelcomewel
    
  3. 获取每一行的长度:

    ... | awk '{ print length(), $0 }'
    2 ll
    10 hellohello
    2 ll
    6 byebye
    14 welcomewelcome
    14 comewelcomewel
    
  4. 按第一列数字降序排列:

    ...| sort -k 1,1 -nr
    14 welcomewelcome
    14 comewelcomewel
    10 hellohello
    6 byebye
    2 ll
    2 ll
    
  5. 为第一列(长度)与第一行具有相同值的所有行打印第二列:

    ... | awk 'NR==1{prev=$1;print $2;next} $1==prev{print $2;next} {exit}'
    welcomewelcome
    comewelcomewel
    
  6. 将其导入 grep,使用 -f - 参数将标准输入读取为文件:

    ... | grep -f - infile
    5,letswelcomewelcomeyou.org
    6,letscomewelcomewelyou.org
    

限制

虽然这可以处理 cmets 中提到的 bbwelcomewelcome 情况,但它会在重叠模式(例如 welwelcomewelcome)上跳闸,它只能找到 welwel,而不是 welcomewelcome

更多 awk 的替代解决方案,更少 sort

正如 cmets 中的 tripleee 所指出的,这可以简化为跳过 sort 步骤并将两个 awk 步骤和 sort 步骤合并为一个 awk 步骤,这可能会提高性能:

$ cut -d ',' -f 2 infile | grep -Eo '(.*)\1' |
awk '{if (length()>ml) {ml=length(); delete a; i=1} if (length()>=ml){a[i++]=$0}}
END{for (i in a){print a[i]}}' |
grep -f - infile

让我们更详细地看一下 awk 步骤,为了清楚起见,扩展了变量名称:

{
    # New longest match: throw away stored longest matches, reset index
    if (length() > max_len) {
        max_len = length()
        delete arr_longest
        idx = 1
    }

    # Add line to longest matches
    if (length() >= max_len)
        arr_longest[idx++] = $0
}

# Print all the longest matches
END {
    for (idx in arr_longest)
        print arr_longest[idx]
}

基准测试

我已经对 cme​​ts 中提到的top one million domains file 上的两个解决方案进行了计时:

  • 第一个解决方案(使用sort 和两个 awk 步骤):

    964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
    
    real    1m55.742s
    user    1m57.873s
    sys     0m0.045s
    
  • 第二种解决方案(只需一个 awk 步骤,没有 sort):

    964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
    
    real    1m55.603s
    user    1m56.514s
    sys     0m0.045s
    
  • 还有Perl solutionCasimir et Hippolyte

    964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
    
    real    0m5.249s
    user    0m5.234s
    sys     0m0.000s
    

我们从中学到的东西:下次请求 Perl 解决方案 ;)

有趣的是,如果我们知道只有一个最长匹配并相应地简化命令(只是head -1 而不是第一个解决方案的第二个 awk 命令,或者在第二个解决方案中没有使用 awk 跟踪多个最长匹配解决方案),所获得的时间仅在几秒钟的范围内。

便携备注

显然,BSD grep 不能通过 grep -f - 来读取标准输入。在这种情况下,管道的输出直到必须重定向到一个临时文件,然后这个临时文件与grep -f一起使用。

【讨论】:

    【解决方案2】:

    perl 的一种方式:

    perl -F, -ane 'if (@m=$F[1]=~/(?=(.+)\1)/g) {
        @m=sort { length $b <=> length $a} @m;
        $cl=length @m[0];
        if ($l<$cl) { @res=($_); $l=$cl; } elsif ($l==$cl) { push @res, ($_); }
    }
    END { print @res; }' file
    

    想法是在第二个字段的每个位置找到所有最长的重叠重复字符串,然后对匹配数组进行排序,最长的子字符串成为数组中的第一项(@m[0])。

    完成后,将当前重复子串 ($cl) 的长度与存储的(前一个最长子串的)长度进行比较。当当前重复子串的长度大于存储的长度时,结果数组被当前行覆盖,当长度相同时,当前行被压入结果数组中。

    详情:

    命令行选项:

    -F,设置字段分隔符为,
    -ane(e执行以下代码,n一次读取一行,并将其内容放入$_a自动拆分,使用定义的 FS,并将字段放入 @F 数组中)

    图案:

    /
    (?=         # open a lookahead assertion
        (.+)\1  # capture group 1 and backreference to the group 1
    )           # close the lookahead
    /g # all occurrences 
    

    这是一种在字符串中查找所有重叠结果的众所周知的模式。这个想法是利用前瞻不消耗字符的事实(前瞻仅意味着“检查此子模式是否跟随在当前位置”,但它不匹配任何字符)。要获得在前瞻中匹配的字符,您只需要一个捕获组。 由于前瞻不匹配,因此在每个位置都对模式进行测试(并且不关心之前是否已经在第 1 组中捕获了字符)。

    【讨论】:

    • 这是在期待某个地方的空间吗?真正的输入文件在逗号后没有空格。我想对此进行基准测试以与我的解决方案进行比较。
    • @BenjaminW.:确实,经过考古研究,我找到了文件(没有空格)。
    • 参见基准测试。这比我的快一个数量级;)
    • @BenjaminW。哇,差距太大了!但最重要的是你的电脑比我的快。
    • @CasimiretHippolyte 只是一个想法,如果您能解释每行(或每行中的部分)的作用,那将是很棒的学习体验!尤其是第一行: perl -F, -ane 'if (@m=$F[1]=~/(?=(.+)\1)/g)
    猜你喜欢
    • 1970-01-01
    • 2018-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    相关资源
    最近更新 更多