哪种数据结构可能是更有效的实现?

时间:2013-06-19 06:22:06

标签: c bash data-structures hashtable

我正在从设置文件中读取练习,其中每行指定两个单词和一个数字。数字表示指定的两个单词之间的单词数。另一个文件 - 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 ++中。什么可能是一个可能的替代实现,效率考虑?我想到了哈希表,但是有没有更好的方法呢?

我想听听大家对此的看法。

2 个答案:

答案 0 :(得分:1)

如果:

  • 为了提高效率,它会变得复杂
  • 文本文件可以很大
  • 安装文件可以包含多行

然后我会按照以下方式进行:

作为准备我会创建:

  1. 哈希映射,其中单词的索引为键,单词为值(名为-say- WORDS)。所以WORDS [1]将成为第一个单词,WORDS [2]成为第二个单词,依此类推。
  2. 一个散列映射,其中的单词为键,索引列表为值(名为-say-INDEXES)。因此,如果WORDS [2]和WORDS [5]是“dog”而不是其他,则INDEXES [“dog”]将产生数字2和5.该值可以是动态索引数组或链表。如果有多次出现的话,链接列表会更好。
  3. 您可以阅读文本文件,同时填充这两个结构。

    处理:

    对于设置文件的每一行,我将获得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,"
    
  • 最后,我们阅读 setup.txt 文件(再次通过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方式。

警告。如果出现以下情况,脚本将会中断:

  • setup.txt 文件中的数字以0开头,包含89。这是因为bash会将其视为八进制数,其中89无效。有解决方法。
  • 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个单词的文件,那么您可能不会注意到bashperl/python/awk/C/you-name-it之间存在太大差异:bash对此类文件的执行速度非常快。现在,如果你有100000个文件,每个文件包含数百万个单词,那么应该采用不同的方法,并且应该使用不同的语言(但我不知道哪一个)。