0

Using a given range of values, I'm trying to generate random instances of three contiguous numbers.

For example, given the range 1-100, and desiring three (non-overlapping) random instances of three contiguous numbers, the output would be something like:

4 5 6
50 51 52
32 33 34

I've been trying to use the command shuf, for example,

shuf -i 1-100 -n 3

but this doesn't seem to permit the generation of random contiguous number sequences. Any ideas?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
aaron
  • 3
  • 2

2 Answers2

1

The basic answer is to generate three separate random values from the range 1 .. (100 - 3 + 1) or 1..98, and then produce n,n+1,n+2 from each value n.

That just leaves you with the non-overlapping requirement. You need to test that the absolute value of the gap between any two of the three numbers is at least 3; if not, generate a new number. You need to decide whether it is OK to produce (assuming that the 'random generation' produced a permutation of {1, 4, 7}):

1 2 3
4 5 6
7 8 9

or whether there must be a gap between the sets of numbers. If there must be a gap, then you check that the distance between pairs of generated values is at least 4, instead of 3.

Choosing to require a gap of at least one between sets of 3 values, I ended up with this script:

#!/bin/bash

min=1           # Minimum value
max=100         # Maximum value
adj=3           # Number of adjacent values per set
num=3           # Number of sets

r_min=$min
r_max=$(($max - $adj + 1))

base=()
while [ ${#base[*]} -lt $num ]
do
    next=$(($RANDOM % ($r_max - $r_min + 1) + $r_min))
    ok=yes
    for n in ${base[@]}
    do
        gap=$(($next - $n))
        [ $gap -lt 0 ] && gap=$((- $gap))
        if [ $gap -le $adj ]
        then ok=no; break
        fi
    done
    if [ $ok = yes ]
    then base+=( $next )
    fi
done

for n in ${base[@]}
do
    for ((i = 0; i < $adj; i++))
    do printf "%4d" $(($n + $i))
    done
    echo
done

Note that if the number of sets of points and the number of points in a set ($num and $adj in the code) get too large, you might end up with an infinite loop as there aren't enough possibilities. For example, with $adj at 3, setting $num to 25 or more guarantees infinite loops; you can easily run into trouble a long time before that, though.

Example runs:

$ bash randcont.sh               
  16  17  18
  92  93  94
   6   7   8
$ bash randcont.sh
  81  82  83
  40  41  42
  13  14  15
$ bash randcont.sh
  61  62  63
  71  72  73
  23  24  25
$ bash randcont.sh
  54  55  56
   7   8   9
  46  47  48
$

There is a bias in the mechanism used to generate the random numbers — a bias towards the lower numbers. If that's a problem, you can work out how to fix it, too.

I'm not convinced this is the best method; there are probably some methods that manage to use less brute force and ignorance. But it does work 'OK' on the sample requirements.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

It is possible to generate the triples uniformly and without the need for testing and rejecting overlaps.

The problem reduces to finding a random sequence of size k {a1ak} from {1…N−2} such that the minimum difference between any two values of the subsequence is at least 3. (N−2 because the values selected are the first values of each triple, so the largest cannot be greater than N−2.)

This can be done by starting with a random ordered subsequence {a'1a'k} from {1…N−((k−1)×2+2)} and then setting each ai to a'i+2(i−1). Finally, the sequence can be randomly shuffled.

This can be easily be generalized to find tuples of size m.

In bash:

# tuples n k m
tuples () { 
    local -i n=${1:-100} k=${2:-3} m=$((${3:-3}-1))
    if ((n < k*m + k)); then return 1; fi
    local -i i=0 a
    for a in $(shuf -i 1-$((n - k * m)) -n $k | sort -n); do
        # -s'  ' to work around a bug in coreutils 8.20 and 8.21
        seq -s'  ' $((a+i)) $((a+i+m))
        i+=m
    done | shuf
}
rici
  • 234,347
  • 28
  • 237
  • 341