簡體   English   中英

有效地查找兩個 ksh 或 bash 數組之間的共同元素

[英]Finding elements in common between two ksh or bash arrays efficiently

我正在編寫 Korn shell 腳本。 我有兩個數組(比如arr1arr2 ),都包含字符串,我需要檢查arr1中的哪些元素(作為整個字符串或子字符串)存在於arr2 最直觀的解決方案是嵌套 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 中編寫哈希計算函數)。

有什么建議么?

由於版本 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 中嘗試這樣做!

由於您可以使用grep ,並且您想匹配子字符串以及完整字符串,因此一種方法是編寫:

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

並讓grep優化,因為它認為合適。

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

假設您的值不能包含文字換行符,以下內容將在 arr1 和 arr2 中為每個項目發出一行:

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

如果您的數組是預先排序的,您可以刪除sort s(這將使大數組的內存和時間效率極高,比基於grep的方法更高效)。

這是一個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解決方案(請參閱我的其他答案/帖子)

用於子字符串/模式匹配的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解決方案也沒有那么快。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM