2

I am trying to random choose a number of items of an array and then update it to do it again till the last set:

#! /bin/bash

A=({1..27})
T=${#A[@]} #number of items in the array
N=3 #number of items to be chosen
V=$(($T/$N)) #number of times to loop
echo ${A[@]} " >> ${#A[@]}"
for ((n=0;n<$V;n++)); do 
    A1=()
    for I in `shuf --input-range=0-$(( ${#A[*]} - 1 )) | head -${N}`; do #random chooses N items random
        S=`echo ${A[$I]}` #the chosen items
        #echo $S
        A1+=("$S|") #creates an array with the chosen items 
        A=("${A[@]/$S}") #deletes the the chosen items from array 
    done
    echo ${A[@]} " >> ${#A[@]}"
    echo ${A1[@]} " >> ${#A1[@]}"
done

The type of output I am getting with this code is:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  >> 27
1 4 5 6 7 8 9 10 11 1 1 14 15 16 17 18 19 1 2 4 5 6 7  >> 27
20| 2| 3|  >> 3
4 6 7 8 9 0 1 4 6 7 8 9 2 4 6 7  >> 27
1| | 5|  >> 3
6 7 8 9 0 1 6 7 8 9 2 6 7  >> 27
| | 4|  >> 3
7 8 9 0 1 7 8 9 2 7  >> 27
6| | |  >> 3
7 8 9 0 1 7 8 9 7  >> 27
| | 2|  >> 3
7 9 0 1 7 9 7  >> 27
8| | |  >> 3
7 9 1 7 9 7  >> 27
| | 0|  >> 3
7 9 1 7 9 7  >> 27
| | |  >> 3
1  >> 27
9| 7| |  >> 3

Any ideas why it works fine in the start and fails in the end??

2
  • 1
    In future, use set -x after the #!/bin//bash line to run the script in debugger mode. I pretty much root-caused it to improper deletion from array A using it by maintaining a minimal input as A=({1..5}). Commented Dec 30, 2016 at 16:32
  • 1
    Also do not use for-loop for processing command output, use IFS appropriately and use a while-loop with process-substitution Commented Dec 30, 2016 at 16:35

2 Answers 2

1

There are some improvements that could be made to your script:

  1. Use lowercase letters
  2. Quote your expansion ("${a[@]}").
  3. Remove items by doing unset a[j].
  4. Re-build array with a=("${a[@]}").
  5. No need for head as shuf could produce a counted result with -n.
  6. Avoid the use of backquotes, use $(…) instead.

That will reduce the script to:

#!/bin/bash

t=27                #number of items in the array
n=3                 #number of items to be chosen
a=( $(seq 1 "$t") )
a1=()

while (( ${#a[@]} >= n )); do 
    for j in $(shuf -n "$n" --input-range=0-$((${#a[@]}-1)) ); do # choose $n random items.
        a1+=("${a[j]}")     # append to an array the chosen items.
        unset "a[j]"           # deletes the the chosen items from array.
    done
    a=("${a[@]}")               # re-build the array.
    echo "a  ${a[@]}  >> ${#a[@]}"
    echo "a1 ${a1[@]} >> ${#a1[@]}"
done

This will produce this output:

a  1 2 3 5 6 7 8 9 10 11 12 14 15 16 18 19 20 21 22 23 24 25 26 27  >> 24
a1 17 13 4 >> 3
a  1 2 5 6 7 8 10 11 12 14 15 16 18 19 20 21 22 23 24 26 27  >> 21
a1 17 13 4 25 3 9 >> 6
a  1 2 5 6 7 8 10 11 14 15 16 18 21 22 23 24 26 27  >> 18
a1 17 13 4 25 3 9 12 20 19 >> 9
a  1 2 5 7 8 10 11 14 15 16 21 22 24 26 27  >> 15
a1 17 13 4 25 3 9 12 20 19 23 6 18 >> 12
a  2 7 8 10 11 14 15 16 22 24 26 27  >> 12
a1 17 13 4 25 3 9 12 20 19 23 6 18 1 5 21 >> 15
a  2 8 10 14 15 22 24 26 27  >> 9
a1 17 13 4 25 3 9 12 20 19 23 6 18 1 5 21 7 11 16 >> 18
a  2 10 14 24 26 27  >> 6
a1 17 13 4 25 3 9 12 20 19 23 6 18 1 5 21 7 11 16 22 8 15 >> 21
a  14 26 27  >> 3
a1 17 13 4 25 3 9 12 20 19 23 6 18 1 5 21 7 11 16 22 8 15 2 24 10 >> 24
a    >> 0
a1 17 13 4 25 3 9 12 20 19 23 6 18 1 5 21 7 11 16 22 8 15 2 24 10 14 26 27 >> 27

But the same array a1 could be built just in one step with shuf:

#!/bin/bash

t=27                                # number of items in the array
n=3                                 # number of items to be chosen
a1=( $(shuf --input-range=1-"$t") )
printf '%3s ' "${a1[@]}"; echo

Which will print:

$ ./script.sh 
 15 23  1  9 24  2 21 11 12 10 19 25 27 13  5 26  4  7 14  3 22 20 17 18 16  6  8

Or, if the result must be in $n elements per line:

#!/bin/bash

t=27                                    # number of items in the array
n=3                                     # number of items to be chosen
a1=( $(shuf --input-range=1-"$t") )

while ((i+n<=t)); do
    printf '%3s ' "${a1[@]:i:n}"; echo
    ((i+=n))
done

Printing:

$ ./script.sh 
  7  19  16 
  4  20  26 
 11  23   2 
 13   6  15 
 22  12  25 
 18  14  10 
 21   8   9 
  5  24  27 
  1   3  17 

Looks like a simpler solution.

Sign up to request clarification or add additional context in comments.

Comments

1

There are a few wrong things in your script, especially the way you (think you) delete the elements from the array. You're not deleting any elements, you're only replacing their values by the empty string in every field!. That's why your array has 27 elements all the way through, and after the first iteration, all the 2's and 3's are removed from each field. Here's a more idiomatic script:

#! /bin/bash

a=( {1..27} )
n=3 #number of items to be chosen
while ((${#a[@]}>=n)); do
   a1=()
   for ((i=0;i<n;++i)); do
      # pick an index
      x=$((RANDOM%${#a[@]}))
      # append its value to array a1
      a1+=( "${a[x]}" )
      # unset it
      unset 'a[x]'
      # reconstruct array a (make it non-sparse again)
      a=( "${a[@]}" )
   done
   # print array
   printf 'a: %s >> %s\n' "${a[*]}" "${#a[@]}"
   # print chosen elements
   printf 'a1: %s >> %s\n' "${a1[*]}" "${#a1[@]}"
done

4 Comments

There are a couple of wrong ideas on this script. One: the random number x is not uniformly distributed. The variable RANDOM generates integers in the range 0-32767. For each 32768 draws, in average, RANDOM%100 will produce 328 results in the range 0-67 and 327 in the range 68-99. That is clearly a non-uniform random distribution.
The value of x could be made uniform with a simple loop: range=27;a=$RANDOM; until ((a< (32767/range)*range )); do a=$RANDOM; done; echo "a=$a x=$((a%range))". But: why do that is shuf could do the same in a simpler call: x=$(shuf -i 0-27 -n 1).
Of course both RANDOM and shuf are for simple random requirements. If a CSPRNG is required, maybe a simple printf '%d\n' "0x$(openssl rand -hex 4)" (32 bit decimal number) should be enough, if not, a simple call to urandom sure will: printf '%d\n' "0x$(dd if=/dev/urandom bs=1 count=4 2>/dev/null|xxd -p)" (again, a 32 bit decimal number).
@sorontar: you're a bit off-topic here. OP is struggling with Bash code, not with PRNG. By the way, I'm not too sure this community welcomes people who edit a post only to remove an upvote.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.