![如何对匹配数求和](https://linux22.com/image/57881/%E5%A6%82%E4%BD%95%E5%AF%B9%E5%8C%B9%E9%85%8D%E6%95%B0%E6%B1%82%E5%92%8C.png)
我是脚本编写新手,需要一些帮助。非常感谢您的回答。
我得到了这个作业,即找到包含以下数字组中的两个的所有五位数字(在 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 中执行此操作的方法,但我喜欢使用各种工具 、grep
、awk
和sed
来执行此类操作,主要是因为这就是我的思维方式。
一种方法
那么我们如何才能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 位数字,但又不像调用grep
80k 次那么昂贵。现代版本的 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 版本快一点。