Sunday, August 19, 2018

Random chose N items from array and update it in BASH SCRIPT

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??

Solved

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.


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> %s\n' "${a[*]}" "${#a[@]}"
   # print chosen elements
   printf 'a1: %s >> %s\n' "${a1[*]}" "${#a1[@]}"
done

No comments:

Post a Comment