[英]Finding elements in common between two ksh or bash arrays efficiently
我正在編寫 Korn shell 腳本。 我有兩個數組(比如arr1
和arr2
),都包含字符串,我需要檢查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 秒……因此重復循環遍歷數組會產生影響。
顯然(?)需要減少重復動作的數量......
comm
解決方案是有意義的(它在大約 0.5 秒內針對相同的 3300/500 測試集運行)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.