从Bash数组中删除随机元素

3

我正在尝试遍历一个数组,并动态地选择一个随机元素将其取消。我尝试了以下方法:

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

每个循环的长度似乎都正确,但是当我访问一个之前未设置索引的元素时,内容为空。我查阅了一些有关子shell的信息,但不知道这是什么意思。unset是否真的会重新排列数组?有人可以给我提示如何解决吗?

2个回答

4
如果取消元素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

输出:

声明 -a a='([0]="红色" [1]="绿色" [2]="黑色" [3]="黄色" [4]="白色" [5]="橙色" [6]="蓝色" [7]="紫色")'

这段代码是一个关于数组的声明,其中包含了8个元素,分别对应不同的颜色。
# remove element 2 (black)
unset a[2]
declare -p a

输出(不包括第二个元素):

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]="红色" [1]="绿色" [2]="黄色" [3]="白色" [4]="橙色" [5]="蓝色" [6]="紫色")'

+1 给我们称之为“非重排提示” :) - Marc Bredt

1

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.

所以,unset会销毁数组元素。在内存中,这可能只是指针移动。

更新: 看看这个。由于未删除引用并以一种方式更新数组长度,因此似乎缺少$RANDOM始终返回介于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

至少在不考虑已经被删除的索引的情况下,选择/创建已经被取消的索引的随机数会增加一些开销。如果$RANDOM生成功能不正常并且没有返回范围内的数字,则仍然可能出现无限循环。此行为随着数组长度的增加而增加。

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


a=(${a[@]}) 中需要在括号内使用双引号以防止单词分割。 - codeforester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接