4

This question is about generating random numbers between a range, which is fine, but it doesn't fit my case.

I'll explain in SQL terms because it seems to me that's easier to understand, though the question is about bash. My idea is with the results of the bash code build a SQL script.

I have two MySQL tables, one of people and one of places. Each record has an unique integer id which goes from 1 to 139 (places) and 1 to 1519 (people). They are linked to each other by a foreign key, meaning: a place can have many people, but a person can have only one place.

# 1-139  # 1-1519
place1 → person1
       → person2
       → person3
       ... and so on

The data I have right now is that in one place all the people are linked, and the rest of places without any.

The places are 139 and the people are 1519, so I have one place with 1519 people.

My goal is to distribute the people randomly to the places, and that each place has at least one person.

My code so far is this:

$ c=1519
$ while [[ $c -ne 0 ]]; do 
    x=$((shuf -i 1-139 -n 1))
    [[ $x -gt 139 ]] && continue
    echo $x
    (( c-- ))
  done

This code generates 1519 random numbers between 1-139, so now I can have each person linked to a random place.

My questions are:

  • Is there a more efficient way to accomplish this?
  • How can I control that each place has at least one person?

I prefer to do this in bash, but I'm open to other solutions not involving it.

3
  • 2
    Why not do it in SQL? Nov 11, 2021 at 11:53
  • @StephenKitt I handle basic SQL, but I'd be a little lost on how to do it. Nov 11, 2021 at 11:55
  • Is it really worth to worry that each place has at least one person, given that you have about 10 persons per place? I mean: It possible, but rather unlikely. Also decide whether you want to distribute the persons randomly or evenly to the places.
    – U. Windl
    Nov 12, 2021 at 12:29

3 Answers 3

9

If you want to do this only using commonly-available tools (at least on Linux distributions), the most efficient way is probably to ask shuf:

shuf -i 1-139 -n 1519 -r

This produces 1519 numbers randomly chosen between 1 and 139.

To ensure that each place gets one person, shuffle 139 numbers first without repeating:

shuf -i 1-139
shuf -i 1-139 -n 1380 -r

To reduce the “first 139” effect (the first 139 people would all end up in different places), shuffle all this again:

(shuf -i 1-139; shuf -i 1-139 -n 1380 -r) | shuf
1
  • Thanks, I'll mark this as accepted because the question is about bash, although I will apply @they's answer because I won't have to convert bash to SQL. But I learnt something in the process, and it may be the case that it's useful for future users with similar question not related to SQL. Nov 11, 2021 at 14:07
5

Assuming the persons are stored in the person table and that each person has a place_id that needs to be an integer between 1 and 139. Using SQL, updating the person table directly:

UPDATE person SET place_id = FLOOR(RAND()*139 + 1);

This should update each entry in the table, randomizing the place_id key. It is totally untested though.

After updating, you could test whether each place is represented by using

SELECT COUNT(DISTINCT place_id) = 139 FROM person;

This should return 1 if all places are represented, otherwise it returns 0.

3
  • Thanks, this is the best answer for my situation since I don't have to convert from bash to SQL, but I'm doubting to mark it as accepted because it will break the point of asking about bash... Nov 11, 2021 at 14:03
  • @schrodigerscatcuriosity Well, you could do this from bash... But the command would be a bit boring from a shell programming point of view: mysql ...connection parameters... 'UPDATE person SET ...as above...'
    – Kusalananda
    Nov 11, 2021 at 16:15
  • @they I have built a 3-place, 5-person simulation of this at sqlfiddle.com/#!9/6e570a/1 and you can now remove "totally untested" from your Answer. :) Nov 11, 2021 at 21:28
1

If we want the values to be as uniformly distributed as we can (allowing for the fact that 1519 is 10 short of being an exact multiple of 139), rather than merely avoiding empty places, then we should generate a sufficiently-large repeating sequence of 1, ..., 139, 1, ..., 139, 1, ..., and then shuffle the first 1519 members of that:

while seq 139; do :; done | head -n 1519 | shuf

If we need mappings of people to places, then we can simply number the output lines:

while seq 139; do :; done | head -n 1519 | shuf | nl

N.B. this is all standard shell; no need for any Bash extensions.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .