我正在从设置文件中读取练习,其中每行指定两个单词和一个数字。数字表示指定的两个单词之间的单词数。另一个文件 - input.txt
- 有一个文本块,程序尝试计算输入文件中出现的次数,该次数遵循设置文件中每行中的约束(即两个特定的单词 a 和 b 应由 n 字隔开,其中 a , b 和 n 在安装文件中指定。
所以我尝试将其作为shell脚本执行,但我的实现可能非常低效。我使用数组来存储设置文件中的单词,然后对文本文件进行线性搜索以找出单词和作品。如果它有帮助,这里有一些代码:
#!/bin/sh
j=0
count=0;
m=0;
flag=0;
error=0;
while read line; do
line=($line);
a[j]=${line[0]}
b[j]=${line[1]}
num=${line[2]}
c[j]=`expr $num + 0`
j=`expr $j + 1`
done <input2.txt
while read line2; do
line2=($line2)
for (( i=0; $i<=50; i++ )); do
for (( m=0; $m<j; m++)); do
g=`expr $i + ${c[m]}`
g=`expr $g + 1`
if [ "${line2[i]}" == "${a[m]}" ] ; then
for (( k=$i; $k<$g; k++)); do
if [[ "${line2[k]}" == *.* ]]; then
flag=1
break
fi
done
if [ "${b[m]}" == "${line2[g]}" ] ; then
if [ "$flag" == 1 ] ; then
error=`expr $error + 1`
fi
count=`expr $count + 1`
fi
flag=0
fi
if [ "${line2[i]}" == "${b[m]}" ] ; then
for (( k=$i; $k<$g; k++)); do
if [[ "${line2[k]}" == *.* ]]; then
flag=1
break
fi
done
if [ "${a[m]}" == "${line2[g]}" ] ; then
if [ "$flag" == 1 ] ; then
error=`expr $error + 1`
fi
count=`expr $count + 1`
fi
flag=0
fi
done
done
done <input.txt
count=`expr $count - $error`
echo "| Count = $count |"
如您所见,这需要花费很多时间。
这次,我想到了一种更有效的方法来实现这一点,在C或C ++中。什么可能是一个可能的替代实现,效率考虑?我想到了哈希表,但是有没有更好的方法呢?
我想听听大家对此的看法。
答案 0 :(得分:1)
如果:
然后我会按照以下方式进行:
作为准备我会创建:
您可以阅读文本文件,同时填充这两个结构。
处理:
对于设置文件的每一行,我将获得INDEXES [firstword]中的索引,并检查WORDS [index + wordsin between + 1]是否等于secondword。如果确实如此,这就是一个打击。
注意:
准备:您只阅读一次文本文件。对于文本文件中的每个单词,您正在进行快速操作,其性能不受已处理单词数量的影响。
处理:您只读取一次安装文件。对于每一行,您在这里的操作也仅受文本文件中第一个字出现次数的影响。
答案 1 :(得分:1)
这是一个完全有效的可能性。它不是100%纯bash
,因为它使用(GNU)sed
:我使用sed
来小写所有内容并去除标点符号。也许你不需要这个。适应您的需求。
#!/bin/bash
input=input.txt
setup=setup.txt
# The Check function
Check() {
# $1 is word1
# $2 is word2
# $3 is number of words between word1 and word2
nb=0
# Get all positions of w1
IFS=, read -a q <<< "${positions[$1]}"
# Check, for each position, if word2 is at distance $3 from word1
for i in "${q[@]}"; do
[[ ${words[$i+$3+1]} = $2 ]] && ((++nb))
done
echo "$nb"
}
# Slurp input file in an array
words=( $(sed 's/[,.:!?]//g;s/\(.*\)/\L\1/' -- "$input") )
# For each word, specify its positions in file
declare -A positions
pos=0
for i in "${words[@]}"; do
positions[$i]+=$((pos++)),
done
# Do it!
while read w1 w2 p; do
# Check that w1 w2 are not empty
[[ -n $w2 ]] || continue
# Check that p is a number
[[ $p =~ ^[[:digit:]]+$ ]] || continue
n=$(Check "$w1" "$w2" "$p")
[[ $w1 != $w2 ]] && (( n += $(Check "$w2" "$w1" "$p") ))
echo "$w1 $w2 $p: $n"
done < <(sed 's/\(.*\)/\L\1/' -- "$setup")
它是如何运作的:
words
中的整个文件 input.txt :每个字段一个字。请注意,我在此处使用sed
删除所有标点符号(仅限,
,.
,:
,!
,?
,出于测试目的,如果您愿意,可以添加更多内容,并将每个字母小写。循环遍历数组words
,对于每个单词,将其位置放在关联数组positions
中:
w => "position1,position2,...,positionk,"
sed
过滤到小写一切 - 可选参见下文)。快速检查一行是否有效(2个单词和一个数字),然后调用Check
函数(两次,对于给定单词的每个排列,除非两个单词相等)。Check
函数在文件中找到word1的所有位置,这要归功于关联数组positions
然后使用数组words
,检查word2是否与word1的给定“距离” 第二个sed
是可选的。我已经通过sed
过滤了 setup.txt 文件以小写所有内容。这个sed
只会留下很少的开销,因此,效率方面,这并不是什么大问题。您将能够在以后添加更多过滤以确保数据与脚本将如何使用它(例如,摆脱标点符号)一致。否则你可以:
完全摆脱它:用
替换相应的行(最后一行)done < "$setup"
在这种情况下,您必须信任将编写 setup.txt 文件的人/人。
如上所述摆脱它,但仍希望将所有内容转换为小写。在这种情况下,
下面while read w1 w2 p; do
行,只需添加以下行:
w1=${w1,,}
w2=${w2,,}
这是小写字符串的bash方式。
警告。如果出现以下情况,脚本将会中断:
0
开头,包含8
或9
。这是因为bash会将其视为八进制数,其中8
和9
无效。有解决方法。input.txt 中的文字没有遵循正确的印刷习惯:标点符号后面跟着一个空格。例如,如果输入文件包含
The quick,brown,dog jumps over the lazy fox
然后在sed
处理之后,文本看起来像
The quickbrowndog jumps over the lazy fox
并且 quick , brown 和 dog 等字词将无法正常处理。您可以将sed
替换s/[,:!?]//g
替换为s/[,:!?]/ /g
,以使用空格转换这些符号。这取决于你,但在这种情况下,缩写为例如例如和即可能不会被认为是正确的...它现在真的取决于你需要做什么。
:)
。) 关于效率。我认为算法效率很高。 bash
可能不是最合适的语言,但它很有趣,而且如果我们看一下它就不那么困难(少于20行相关代码,甚至不到那么多!)。如果您只有50个包含50000个单词的文件,那么您可能不会注意到bash
和perl/python/awk/C/you-name-it
之间存在太大差异:bash
对此类文件的执行速度非常快。现在,如果你有100000个文件,每个文件包含数百万个单词,那么应该采用不同的方法,并且应该使用不同的语言(但我不知道哪一个)。