【问题标题】:Finding elements in common between two ksh or bash arrays efficiently有效地查找两个 ksh 或 bash 数组之间的共同元素
【发布时间】:2018-05-12 05:15:54
【问题描述】:

我正在编写一个 Korn shell 脚本。我有两个数组(比如arr1arr2),都包含字符串,我需要检查arr2 中存在arr1 中的哪些元素(作为整个字符串或子字符串)。最直观的解决方案是嵌套for循环,并检查arr1中的每个元素是否可以在arr2(通过grep)中找到,如下所示:

for arr1Element in ${arr1[*]}; do
    for arr2Element in ${arr2[*]}; do
        # using grep to check if arr1Element is present in arr2Element
        echo $arr2Element | grep $arr1Element
    done
done

问题是arr2 有大约 3000 个元素,因此运行嵌套循环需要很长时间。我想知道在 Bash 中是否有更好的方法来做到这一点。

如果我在 Java 中执行此操作,我可以计算其中一个数组中元素的哈希值,然后在另一个数组中查找这些哈希值,但我认为 Bash 没有任何功能可以执行此类操作(除非我愿意在 Bash 中编写哈希计算函数)。

有什么建议吗?

【问题讨论】:

  • 我不确定我是否理解您使用哈希的 Java 解决方案将如何处理子字符串。也许你应该添加一些伪代码来澄清一下?
  • @slim 你是对的 - 它不会处理子字符串,只处理完全匹配。我添加该部分只是为了看看是否有人可以在 bash 中提出等效的解决方案(用于精确匹配)。
  • 顺便说一句,使用"${arr1[@]}",而不是${arr1[*]}。后者拆分单词、扩展 glob 并做各种其他不受欢迎的事情。
  • which ksh 很重要。例如,并非所有广泛使用的 ksh 版本都支持关联数组,因此基于它们的解决方案可能无法转换(除非您使用的是正确的 David Korn ksh93 ——如果您是,那就太好了)。
  • 顺便说一句,echo foo | grep bar[[ foo = *bar* ]]大大。在这里,您根本不需要这样做。

标签: arrays bash awk grep ksh


【解决方案1】:

自 4.0 版以来,Bash 具有关联数组:

$ declare -A elements
$ elements[hello]=world
$ echo ${elements[hello]}
world

您可以像使用 Java 地图一样使用它。

declare -A map
for el in "${arr1[@]}"; do 
    map[$el]="x"
done

for el in "${arr2[@]}"; do 
    if [ -n "${map[$el]}" ] ; then 
       echo "${el}"
    fi
done

处理子字符串是一个更重要的问题,并且在任何语言中都是一个挑战,除了您已经使用的蛮力算法。你可以建立一个字符序列的二叉树索引,但我不会在 Bash 中尝试 that

【讨论】:

  • 但是,如果可以的话,我仍然会说“使用现代脚本语言”。 Python等
  • 谢谢,但这对比较两个数组中的元素有什么帮助,这是我的主要目标?
  • 添加了一个演示。可能有比使用虚拟值"x" 更简洁的方法
【解决方案2】:

由于您可以使用grep,并且您想匹配子字符串以及完整字符串,因此一种方法是编写:

printf '%s\n' "${arr2[@]}" \
  | grep -o -F "$(printf '%s\n' "${arr1[@]}")

并让grep 对其进行优化。

【讨论】:

    【解决方案3】:

    BashFAQ #36 描述了在 bash 中使用 comm 进行集合算术(联合、不相交集合等)。

    假设您的值不能包含文字换行符,以下将在 arr1 和 arr2 中为每个项目发出一行:

    comm -12 <(printf '%s\n' "${arr1[@]}" | sort -u) \
             <(printf '%s\n' "${arr2[@]}" | sort -u)
    

    如果您的数组是预先排序的,您可以删除 sorts(这将使大型数组的内存和时间效率非常高,比基于 grep 的方法更有效)。

    【讨论】:

    • 如何处理子字符串?
    • 我没有。 OP 在 cmets 中明确指出,匹配子字符串是其原始实现提案中的一个错误,而不是所需的功能。
    • 啊,好的,在问题中提到了子字符串...错过了(并且没有完全理解)埋在 cmets 中的那一点
    【解决方案4】:

    这是一个bash/awk 的想法:

    # some sample arrays
    
    $ arr1=( my first string "hello wolrd")
    $ arr2=( my last  stringbean strings "well, hello world!)
    
    # break array elements into separate lines
    
    $ printf '%s\n' "${arr1[@]}"
    my
    first
    string
    hello world
    
    $ printf '%s\n' "${arr2[@]}"
    my
    last
    stringbean
    strings
    well, hello world!
    
    # use the 'printf' command output as input to our awk command
    
    $ awk '
    NR==FNR { a[NR]=$0 ; next }
    { for (i in a)
          if ($0 ~ a[i]) print "array1 string {"a[i]"} is a substring of array2 string {"$0"}" }
    ' <( printf '%s\n' "${arr1[@]}" ) \
      <( printf '%s\n' "${arr2[@]}" )
    
    array1 string {my} is a substring of array2 string {my}
    array1 string {string} is a substring of array2 string {stringbean}
    array1 string {string} is a substring of array2 string {strings}
    array1 string {hello world} is a substring of array2 string {well, hello world!}
    
    • NR==FNR :仅适用于文件 #1:将元素存储到名为 'a' 的 awk 数组中
    • next : 处理文件#1 中的下一行;此时,文件#1 的 awk 脚本的其余部分被忽略;文件 #2 中的每一行 ...
    • for (i in a) : 对于数组 'a' 中的每个索引 'i' ...
    • if ($0 ~ a[i] ) : 查看 a[i] 是否是文件 #2 中当前行 ($0) 的子字符串,如果是...
    • print "array1... : 输出比赛信息

    使用以下数据进行测试:

    arr1 == 3300 elements
    arr2 ==  500 elements
    

    当所有arr2 元素在arr1 中都有一个子字符串/模式匹配项(即500 个匹配项)时,总运行时间约为27 秒......因此重复循环遍历数组会造成损失。

    显然(?)需要减少重复动作的数量...

    • 对于完全匹配的字符串,Charles Duffy 的 comm 解决方案是有意义的(它在大约 0.5 秒内针对相同的 3300/500 测试集运行)
    • 对于子字符串/模式匹配,我能够在大约 5 秒内运行 egrep 解决方案(请参阅我的其他答案/帖子)

    【讨论】:

      【解决方案5】:

      子字符串/模式匹配的egrep 解决方案...

      egrep -f <(printf '.*%s.*\n' "${arr1[@]}") \
               <(printf '%s\n'     "${arr2[@]}")
      
      • egrep -f :从-f 指定的文件中搜索模式,在这种情况下是...
      • &lt;(printf '.*%s.*\n' "${arr1[@]}") :将 arr1 元素转换为每行 1 个模式,为前缀和后缀附加正则表达式通配符 (.*)
      • &lt;(printf '%s\n' "${arr2[@]}") :将 arr2 元素转换为每行 1 个字符串

      当针对样本数据集运行时:

      arr1 == 3300 elements
      arr2 ==  500 elements
      

      ... 有 500 个匹配项,总运行时间约为 5 秒; egrep 仍然有很多重复处理,但没有我的其他答案 (bash/awk) 看到的那么糟糕......当然没有消除重复处理的comm 解决方案那么快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-07-20
        • 1970-01-01
        • 2020-03-26
        • 1970-01-01
        • 1970-01-01
        • 2019-09-12
        • 1970-01-01
        • 2021-01-01
        相关资源
        最近更新 更多