有效地查找两个ksh或bash数组之间的共同元素

时间:2017-11-28 16:48:36

标签: arrays bash shell grep ksh

我正在写一个Korn shell脚本。我有两个数组(比如,arr1arr2),两个都包含字符串,我需要检查arr1中哪些元素(作为整个字符串或子字符串){{1 }}。最直观的解决方案是嵌套for循环,并检查arr2中的每个元素是否都可以在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 有大约3000个元素,因此运行嵌套循环需要很长时间。我想知道在Bash中是否有更好的方法可以做到这一点。

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

有什么建议吗?

4 个答案:

答案 0 :(得分:3)

从版本4.0开始,Bash具有关联数组:

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

您可以像使用Java Map一样使用它。

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中尝试

答案 1 :(得分:2)

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

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

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

如果您的数组已预先排序,您可以删除sort(这将使大型数组的内存和时间效率极高,超过基于grep的方法)。

答案 2 :(得分:0)

这是一个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] ):查看[i]是否是文件#2中当前行($ 0)的子字符串,如果是这样的话......
  • print "array1...:输出有关匹配的信息

使用以下数据进行测试:

arr1 == 3300 elements
arr2 ==  500 elements

当所有arr2个元素在arr1中都有子串/模式匹配(即500个匹配)时,总运行时间约为27秒......所以重复循环遍历数组需要通行费。

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

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

答案 3 :(得分:0)

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

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

针对样本数据集运行时:

arr1 == 3300 elements
arr2 ==  500 elements

...有500场比赛,总运行时间约为5秒; egrep仍然有一些重复的处理,但没有我的其他答案(bash/awk)那么糟糕......当然没有那么快comm解决方案消除了重复处理。