如何对匹配数求和

如何对匹配数求和

我是脚本编写新手,需要一些帮助。非常感谢您的回答。

我得到了这个作业,即找到包含以下数字组中的两个的所有五位数字(在 10000 - 99999 范围内)的总和:{ 4, 5, 6 }。这些可能会在相同的数量内重复,如果是这样,则每次出现都会计数一次。

匹配数字的一些示例是 42057、74638 和 89515。我只有这一小段代码。

#! /bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
    do
        ## UNKNOWN COMMANDS
    done

答案1

以下是计算数字中出现了多少个 4、5 或 6 并bash根据结果是否为 2 执行语句的一种方法:

$ con1=1457
$ a=${con1//[^456]/}; [ ${#a} -eq 2 ] && echo Yes
Yes

答案2

入门

每当我有这样的项目时,我喜欢分阶段进行。我喜欢做的第一件事就是echo在循环内部添加 an ,然后运行它,以确保循环给我我想要的东西。

#! /bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
do
  echo $CON1
done

现在,当我运行它时,我将head -5仅显示它输出的前 5 行。

$ ./cmd.bash | head -5
10000
10001
10002
10003
10004

好的,看起来不错,检查结尾如下:

$ ./cmd.bash | tail -5
99995
99996
99997
99998
99999

看起来也不错。现在让我们找出一些方法来完成下一步,从集合 {4,5,6} 中识别 2 位数字。我的第一直觉是去寻找grep。也有纯粹在 Bash 中执行此操作的方法,但我喜欢使用各种工具 、grepawksed来执行此类操作,主要是因为这就是我的思维方式。

一种方法

那么我们如何才能grep从集合 {4,5,6} 中找到包含 2 位数字的行呢?为此,您可以使用集合符号,在正则表达式中写成这样,[456]。您还可以指定要从该集合中匹配多少位数字。写成这样:

[456]{#}

其中#是数字或数字范围。如果我们想要 3,我们会写[456]{3}.如果我们想要 2-5 位数字,我们会写成[456]{2,5}.如果您想要 3 个或更多,[456]{3,}`。

所以对于你的场景来说是[456]{2}.要在 中使用正则表达式grep,您的特定版本grep需要支持该-E开关。这通常在大多数标准中都可用grep

$ echo "45123" | grep -E "[456]{2}"
45123

似乎可行,但如果我们给它数字 3,我们就会开始看到一个问题:

$ echo "45423" | grep -E "[456]{2}"
45423

这也很配啊这是因为grep没有概念这些是字符串中的数字。这是愚蠢的。我们告诉它告诉我们字符串中的一系列字符是否来自一个集合,并且有 2 个字符并且字符串中有 2 个数字45423

对于这些字符串它也失败:

$ echo "41412" | grep -E "[456]{2}"
$

那么这个方法到底好用吗?如果我们稍微改变一下策略,那就是这样,但我们必须重新调整正则表达式。

例子

$ echo -e "41123\n44123\n44423\n41423" | grep -E "[^456]*([456][^456]*){2}"
44123
44423
41423

以上介绍了4种类型的字符串。它只echo -e "41123\n44123\n44423\n41423"打印我们范围内的 4 个数字。

$ echo -e "41123\n44123\n44423\n41423"
41123
44123
44423
41423

这个正则表达式如何工作?它设置零个或多个“not [456]”字符的正则表达式模式,后跟 1 个或多个 [456] 或零个或多个“not [456]”字符,查找后者的 2 次出现。

现在我们在您的脚本中进行一些汇编。

for (( CON1=10000; CON1<=99999; CON1++ )) ;
do
  if echo $CON1 | grep -q -E "[^456]*([456][^456]*){2}"; then
      echo $CON1
    fi
done

使用上面的head&tail技巧,我们可以看到它正在工作:

$ ./cmd.bash | head -5
10044
10045
10046
10054
10055

$ ./cmd.bash | tail -5
99955
99956
99964
99965
99966

但事实证明这种方法非常慢。问题是grep。它很昂贵,而且我们在循环中每次迭代都运行 `grep 1 次,所以大约是 80k 次!

为了改进这一点,我们可以将grep命令移出循环并在生成列表后运行它一次,就像这样,使用我们刚刚回显数字的脚本的原始版本:

$ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}"

笔记:我们可以完全放弃 for 循环并使用命令行工具seq.这将生成相同的数字序列seq 10000 99999

一艘班轮?

执行此操作的一种奇特方法是使用上述命令中的数字序列,然后将其通过管道传输到在每个数字之间paste插入 a 的命令+,然后将该输出运行到命令行计算器中bc

$ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" | paste -s -d"+"
10044+10045+10046+10054+10055+10056+10064+10065+10066+10144+10145+...

$ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" | paste -s -d"+" | bc
2409327540

但这是解决这个问题的完全不同的方法,所以让我们回到循环for

使用纯 Bash

因此,我们需要某种方法来测试 Bash 中的一个数字是否恰好是 2 位数字,但又不像调用grep80k 次那么昂贵。现代版本的 Bash 包括使用运算符进行匹配的功能=~,它可以进行与grep.接下来我们就来看看吧。

#!/bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
  if [[ $CON1 =~ [^456]*([456][^456]*){2} ]]; then
    echo $CON1
  fi
done

运行这个看起来正是我们想要的。

$ ./cmd1.bash  | head -5
10044
10045
10046
10054
10055

$ ./cmd1.bash  | tail -5
99955
99956
99964
99965
99966

检查显示它现在可以与 41511 配合使用:

$ ./cmd1.bash | grep 41511
41511

参考

答案3

我想你必须在纯 Bash 脚本中执行此操作,但是将 John1024 的算法转换为 awk 会给出大量加速:

awk 'BEGIN{k=0;for(i=10000;i<100000;i++){j=i;if(gsub(/[456]/,"",j)==2)k+=i};print k}'

其运行时间不到 bash 版本的 1/20;它也比使用 Python 内置str.count()方法的 Python 版本快一点。

相关内容