从 Bash 数组中删除随机元素

Deleting random elements from a Bash array

我正在尝试遍历数组并动态选择一个随机元素来取消设置。我尝试了以下内容:

a=('red' 'green' 'black' 'yellow' 'white' 'orange' 'blue' 'purple')

while [ ${#a[@]} -ne 0 ]
do
    echo "Length of array:" ${#a[@]}
    randomnumber=$(( ( RANDOM % (${#a[@]}) ) ))
    echo "random number "$randomnumber" -> "${a[$randomnumber]}
    unset a[$randomnumber]
done

每个循环的长度似乎都是正确的,但是当我访问一个之前未设置索引的元素时,内容是空的。我读了一些关于 subshel​​l 的内容,但不知道这意味着什么。 unset 真的会重新排列数组吗?有人可以提示我如何解决这个问题吗?

摘自 bash 的联机帮助页。

The unset builtin is used to  destroy  arrays.   unset  name[subscript]
destroys  the  array element at index subscript.  Care must be taken to
avoid unwanted side effects caused by pathname expansion.  unset  name,
where  name is an array, or unset name[subscript], where subscript is *
or @, removes the entire array.

所以取消设置会破坏数组元素。在内存中这可能只是一个指针移动。

更新: 看看这个。由于不删除引用并以某种方式更新数组长度,它似乎缺少 $RANDOM returns 始终是 0 和当前长度之间的数字。

Length of array: 8
8 random number 7 -> purple
Length of array: 7
7 random number 6 -> blue
Length of array: 6
6 random number 2 -> black
Length of array: 5
5 random number 2 -> 
Length of array: 5
5 random number 0 -> red

一个可能的解决方案是覆盖数组。

更新: 如果您根据初始长度取消设置尚未取消设置的数组元素,则长度只会改变。所以你 运行 进入无限循环,如果你删除元素 1,那么长度是 7 但是你不能再产生随机数 8 了。所以长度至少为 1.

您需要始终生成 0 到 8 之间的随机数。此外,您可以存储已删除的索引。

length=${#a[@]} # ADD
while [ ${#a[@]} -ne 0 ] 
do
  echo "Length of array:" ${#a[@]}
  randomnumber=$(( $RANDOM % ${#a[@]}  )) # if using this arrange the index afterwards like Cyrus said, avoids the overhead mentioned below
  #randomnumber=$(( $RANDOM % $length  )) # regard initial length
  echo "random number "$randomnumber" -> "${a[$randomnumber]}
  unset a[$randomnumber]
  a=(${a[@]}) # rearranges the indexes
  sleep 2 
done

如果您不考虑已删除的索引,至少会有一些开销 selecting/creating 已取消设置的索引的随机数。如果 $RANDOM 生成不能正常运行并且不 return 一个范围内的数字,那么无限循环仍然是可能的。这种行为随着数组的长度而增加。

无论如何,这是一个很棒的问题:)

如果取消设置元素 2,则元素 3 不是新元素 2。

# define your array as example
a=('red' 'green' 'black' 'yellow' 'white' 'orange' 'blue' 'purple')

# show array with index in declare's style
declare -p a

输出:

declare -a a='([0]="red" [1]="green" [2]="black" [3]="yellow" [4]="white" [5]="orange" [6]="blue" [7]="purple")'
# remove element 2 (black)
unset a[2]
declare -p a

输出(无元素2):

declare -a a='([0]="red" [1]="green" [3]="yellow" [4]="white" [5]="orange" [6]="blue" [7]="purple")'

可能的解决方案:copy array 重新排列索引:

a=("${a[@]}")
declare -p a

输出:

declare -a a='([0]="red" [1]="green" [2]="yellow" [3]="white" [4]="orange" [5]="blue" [6]="purple")'