Randomizing Workplace Meetups

Many companies are experimenting with randomly assigning employees into small groups for virtual coffee breaks or video hangouts. The goal is to recreate the chance encounters that naturally happen around shared spaces. If you're implementing this yourself, the underlying programming problem is straightforward: divide n people into groups of at least size g, while minimizing the size difference between groups. For example, 15 employees split into groups of at least four becomes 5, 5, 5; with 16 you get 4, 4, 4, 4; with 17 you get 4, 4, 4, 5.

A straightforward Python implementation might start by creating n//g groups of size g and then distributing the remainder across those groups.

    groups = [g] * (n//g)

    for e in range(0, n % g):
        groups[e % len(groups)] += 1

Here, the first line creates n//g groups of size g (using integer division). The for loop handles the leftover elements. For instance, with g == 4 and n == 17, you start with four groups of four and add one element to a group, yielding [5, 4, 4, 4].

The expression e % len(groups) matters when the number of leftovers exceeds the number of groups. If g == 4 and n == 11, you have [4, 4] with three elements to distribute across just two groups, so the modulo wraps around.

This works, but it feels like there should be a cleaner formula. A more naive approach helps visualize the distribution process.

    groups = [0] * (n//g)

    for i in range(n):
        groups[i % len(groups)] += 1

While this implementation loops n times, it clarifies the mechanics. With g == 4 and n == 17, each element is assigned to a group sequentially, filling up the list like this:

Creating serendipity with Python Embedded Image - 4QD7fp

The result is [5, 4, 4, 4]. Observing this, you can calculate the increment count for each group position without iterating. The number of times groups[i] gets incremented equals how many full passes the naive loop makes over that position.

That insight leads to a formula that builds the entire list in one pass.

    groups = [1+max(0,n-(i+1))//(n//g) for i in range(n//g)]

In this version, n//g tells you the number of groups, and each entry is calculated as 1 + max(0, n-(i+1))//(n//g). The logic breaks down as follows:

  1. The 1 accounts for the first element assigned to each group position.
  2. max(0, n-(i+1)) counts the elements remaining after placing that initial 1 in each group up to position i. Dividing by n//g gives the number of additional full passes those leftovers contribute to position i.

To see this in action, consider computing groups[0] with n == 17 and g == 4. You place 1 in that position, leaving 16 elements. Sharing the remainder across four groups means four full passes, so you add 16/4 = 4 elements, making groups[0] equal to 5. For groups[1], you place 1, leaving 15 elements; integer division gives 15/4 = 3, so that group ends up with 4.

This one-liner is concise and efficient, but it's worth noting that the simpler looping solutions are perfectly readable. The formula trades explicit steps for compactness, and for many developers the clarity of the naive version may be a better maintenance trade-off.

    4 [4]
    5 [5]
    6 [6]
    7 [7]
    8 [4, 4]
    9 [5, 4]
    10 [5, 5]
    11 [6, 5]
    12 [4, 4, 4]
    13 [5, 4, 4]
    14 [5, 5, 4]
    15 [5, 5, 5]
    16 [4, 4, 4, 4]
    17 [5, 4, 4, 4]