<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P132-fair-share/ | updated: 2026-10-17 -->

# P132 Fair share

One worker and one-slot jobs from a heavy tenant and light ones, first come first served or tenants in turn: the light tenants' worst wait changes a lot, the mean per job not at all.

EML-P project `projects/fair-share` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/fair-share` runs it and `eml project verify projects/fair-share` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: fair-share-vs-fifo (https://efficientnewlanguage.org/cases/278-fair-share-vs-fifo/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
3

3
0
3
31
3
x
3
5

3
5
9
3
5
2

3
5
2
31
3
30
8
0
2
4
```

Screen:

```text
== Fair share ==
One worker, jobs of one slot each, from one heavy tenant and several light ones.

1) the tenants  2) compare  3) settings  4) quit
choice> 0
Pick a number from 1 to 4.

1) the tenants  2) compare  3) settings  4) quit
choice> x
Pick a number from 1 to 4.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 
Cancelled.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 0
Type a whole number from 1 to 30.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 31
Type a whole number from 1 to 30.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> x
Type a whole number from 1 to 30.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 5
light tenants (1 to 8)> 
Cancelled.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 5
light tenants (1 to 8)> 9
Type a whole number from 1 to 8.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 5
light tenants (1 to 8)> 2
slot the light tenants send at (0 to 30)> 
Cancelled.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 5
light tenants (1 to 8)> 2
slot the light tenants send at (0 to 30)> 31
Type a whole number from 0 to 30.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 30
light tenants (1 to 8)> 8
slot the light tenants send at (0 to 30)> 0
Tenant H sends 30 jobs at slot 0; tenants a to h send one each at slot 0. Every job takes one slot.

1) the tenants  2) compare  3) settings  4) quit
choice> 2
Slot by slot (H the heavy tenant, a letter each light one):
  first come HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHabcdefgh
  in turn    HabcdefghHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
  tenant   jobs   first come: worst  mean   in turn: worst  mean
  H          30                 30  15.5              38  23.2
  a           1                 31  31.0               2   2.0
  b           1                 32  32.0               3   3.0
  c           1                 33  33.0               4   4.0
  d           1                 34  34.0               5   5.0
  e           1                 35  35.0               6   6.0
  f           1                 36  36.0               7   7.0
  g           1                 37  37.0               8   8.0
  h           1                 38  38.0               9   9.0
Worst completion among the light tenants: 38 first come, 9 in turn; tenant H's: 30 and 38.
Mean completion per job: 19.5 first come, 19.5 in turn; mean wait per job: 18.5 and 18.5.
Per job the two are the same: the worker is busy in the same slots either way, and only who goes first changes.

1) the tenants  2) compare  3) settings  4) quit
choice> 4
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
2
3
20
5
25
2
3
6
3
0
2
4
```

Screen:

```text
== Fair share ==
One worker, jobs of one slot each, from one heavy tenant and several light ones.

1) the tenants  2) compare  3) settings  4) quit
choice> 1
Tenant H sends 20 jobs at slot 0; tenants a to e send one each at slot 1. Every job takes one slot.

1) the tenants  2) compare  3) settings  4) quit
choice> 2
Slot by slot (H the heavy tenant, a letter each light one):
  first come HHHHHHHHHHHHHHHHHHHHabcde
  in turn    HabcdeHHHHHHHHHHHHHHHHHHH
  tenant   jobs   first come: worst  mean   in turn: worst  mean
  H          20                 20  10.5              25  15.3
  a           1                 21  21.0               2   2.0
  b           1                 22  22.0               3   3.0
  c           1                 23  23.0               4   4.0
  d           1                 24  24.0               5   5.0
  e           1                 25  25.0               6   6.0
Worst completion among the light tenants: 25 first come, 6 in turn; tenant H's: 20 and 25.
Mean completion per job: 13.0 first come, 13.0 in turn; mean wait per job: 11.8 and 11.8.
Per job the two are the same: the worker is busy in the same slots either way, and only who goes first changes.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 20
light tenants (1 to 8)> 5
slot the light tenants send at (0 to 30)> 25
Tenant H sends 20 jobs at slot 0; tenants a to e send one each at slot 25. Every job takes one slot.

1) the tenants  2) compare  3) settings  4) quit
choice> 2
Slot by slot (H the heavy tenant, a letter each light one):
  first come HHHHHHHHHHHHHHHHHHHHabcde
  in turn    HHHHHHHHHHHHHHHHHHHHabcde
  tenant   jobs   first come: worst  mean   in turn: worst  mean
  H          20                 20  10.5              20  10.5
  a           1                 26  26.0              26  26.0
  b           1                 27  27.0              27  27.0
  c           1                 28  28.0              28  28.0
  d           1                 29  29.0              29  29.0
  e           1                 30  30.0              30  30.0
Worst completion among the light tenants: 30 first come, 30 in turn; tenant H's: 20 and 20.
Mean completion per job: 14.0 first come, 14.0 in turn; mean wait per job: 8.0 and 8.0.
Per job the two are the same: the worker is busy in the same slots either way, and only who goes first changes.

1) the tenants  2) compare  3) settings  4) quit
choice> 3
tenant H's jobs (1 to 30)> 6
light tenants (1 to 8)> 3
slot the light tenants send at (0 to 30)> 0
Tenant H sends 6 jobs at slot 0; tenants a to c send one each at slot 0. Every job takes one slot.

1) the tenants  2) compare  3) settings  4) quit
choice> 2
Slot by slot (H the heavy tenant, a letter each light one):
  first come HHHHHHabc
  in turn    HabcHHHHH
  tenant   jobs   first come: worst  mean   in turn: worst  mean
  H           6                  6   3.5               9   6.0
  a           1                  7   7.0               2   2.0
  b           1                  8   8.0               3   3.0
  c           1                  9   9.0               4   4.0
Worst completion among the light tenants: 9 first come, 4 in turn; tenant H's: 6 and 9.
Mean completion per job: 5.0 first come, 5.0 in turn; mean wait per job: 4.0 and 4.0.
Per job the two are the same: the worker is busy in the same slots either way, and only who goes first changes.

1) the tenants  2) compare  3) settings  4) quit
choice> 4
Bye.
```

## Modules

### main.eml

```eml
# P132 fair share: one heavy tenant and several light ones through first
# come first served and through taking tenants in turn, slot by slot and
# per tenant.
import fair

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def pad(s, n):
    s => out
    while len(out) < n:
        out + " " => out
    return out

def lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def letter(t):
    "Habcdefgh" => ls
    return ls[t]

def tenths(p, q):
    # p / q to one decimal, rounded half up
    int((20 * p + q) / (2 * q)) => t
    while t * 2 * q > 20 * p + q:
        t - 1 => t
    while (t + 1) * 2 * q <= 20 * p + q:
        t + 1 => t
    return str(int(t / 10)) + "." + str(t % 10)

def show(heavy, light, late):
    ("Tenant H sends " + str(heavy) + " jobs at slot 0; tenants a to " + letter(light) + " send one each at slot " + str(late) + ". Every job takes one slot.") ^0

def compare(heavy, light, late):
    fair.jobs(heavy, light, late) => js
    light + 1 => tenants
    "Slot by slot (H the heavy tenant, a letter each light one):" ^0
    [] => orders
    for plan in ["fifo", "fair"]:
        fair.schedule(js, tenants, plan) => o
        orders + [o] => orders
        "" => line
        for e in o:
            line + letter(js[e[0]][0]) => line
        "first come" => label
        if plan == "fair":
            "in turn" => label
        ("  " + pad(label, 11) + line) ^0
    "  tenant   jobs   first come: worst  mean   in turn: worst  mean" ^0
    fair.per_tenant(js, tenants, orders[0]) => a
    fair.per_tenant(js, tenants, orders[1]) => b
    0 => worst_a
    0 => worst_b
    for t in [0:tenants - 1]:
        ("  " + pad(letter(t), 7) + lpad(str(a[t][0]), 6) + lpad(str(a[t][1]), 19) + lpad(tenths(a[t][2], a[t][0]), 6) + lpad(str(b[t][1]), 16) + lpad(tenths(b[t][2], b[t][0]), 6)) ^0
        if t > 0:
            if a[t][1] > worst_a:
                a[t][1] => worst_a
            if b[t][1] > worst_b:
                b[t][1] => worst_b
    fair.per_job(js, orders[0]) => ja
    fair.per_job(js, orders[1]) => jb
    ("Worst completion among the light tenants: " + str(worst_a) + " first come, " + str(worst_b) + " in turn; tenant H's: " + str(a[0][1]) + " and " + str(b[0][1]) + ".") ^0
    ("Mean completion per job: " + tenths(ja[0], len(js)) + " first come, " + tenths(jb[0], len(js)) + " in turn; mean wait per job: " + tenths(ja[1], len(js)) + " and " + tenths(jb[1], len(js)) + ".") ^0
    if ja[0] == jb[0]:
        "Per job the two are the same: the worker is busy in the same slots either way, and only who goes first changes." ^0

def ask_int(prompt, low, high):
    plain(input(prompt)) => t
    if t == "":
        "Cancelled." ^0
        return 0 - 1
    if len(t) > 2:
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
        return 0 - 1
    for c in t:
        if c < "0" or c > "9":
            ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
            return 0 - 1
    if int(t) < low or int(t) > high:
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
        return 0 - 1
    return int(t)

def settings(heavy, light, late):
    ask_int("tenant H's jobs (1 to 30)> ", 1, 30) => h
    if h < 0:
        return [heavy, light, late]
    ask_int("light tenants (1 to 8)> ", 1, 8) => l
    if l < 0:
        return [heavy, light, late]
    ask_int("slot the light tenants send at (0 to 30)> ", 0, 30) => s
    if s < 0:
        return [heavy, light, late]
    show(h, l, s)
    return [h, l, s]

20 => heavy
5 => light
1 => late

"== Fair share ==" ^0
"One worker, jobs of one slot each, from one heavy tenant and several light ones." ^0
True => running
while running:
    "" ^0
    "1) the tenants  2) compare  3) settings  4) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show(heavy, light, late)
    elif choice == "2":
        compare(heavy, light, late)
    elif choice == "3":
        settings(heavy, light, late) => s
        s[0] => heavy
        s[1] => light
        s[2] => late
    elif choice == "4":
        False => running
    else:
        "Pick a number from 1 to 4." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import fair

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def pad(s, n):
    out = s
    while len(out) < n:
        out = out + " "
    return out

def lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def letter(t):
    ls = "Habcdefgh"
    return ls[t]

def tenths(p, q):
    t = int((20 * p + q) / (2 * q))
    while t * 2 * q > 20 * p + q:
        t = t - 1
    while (t + 1) * 2 * q <= 20 * p + q:
        t = t + 1
    return str(int(t / 10)) + "." + str(t % 10)

def show(heavy, light, late):
    print("Tenant H sends " + str(heavy) + " jobs at slot 0; tenants a to " + letter(light) + " send one each at slot " + str(late) + ". Every job takes one slot.")

def compare(heavy, light, late):
    js = fair.jobs(heavy, light, late)
    tenants = light + 1
    print("Slot by slot (H the heavy tenant, a letter each light one):")
    orders = []
    for plan in ["fifo", "fair"]:
        o = fair.schedule(js, tenants, plan)
        orders = orders + [o]
        line = ""
        for e in o:
            line = line + letter(js[e[0]][0])
        label = "first come"
        if plan == "fair":
            label = "in turn"
        print("  " + pad(label, 11) + line)
    print("  tenant   jobs   first come: worst  mean   in turn: worst  mean")
    a = fair.per_tenant(js, tenants, orders[0])
    b = fair.per_tenant(js, tenants, orders[1])
    worst_a = 0
    worst_b = 0
    for t in range(0, tenants):
        print("  " + pad(letter(t), 7) + lpad(str(a[t][0]), 6) + lpad(str(a[t][1]), 19) + lpad(tenths(a[t][2], a[t][0]), 6) + lpad(str(b[t][1]), 16) + lpad(tenths(b[t][2], b[t][0]), 6))
        if t > 0:
            if a[t][1] > worst_a:
                worst_a = a[t][1]
            if b[t][1] > worst_b:
                worst_b = b[t][1]
    ja = fair.per_job(js, orders[0])
    jb = fair.per_job(js, orders[1])
    print("Worst completion among the light tenants: " + str(worst_a) + " first come, " + str(worst_b) + " in turn; tenant H's: " + str(a[0][1]) + " and " + str(b[0][1]) + ".")
    print("Mean completion per job: " + tenths(ja[0], len(js)) + " first come, " + tenths(jb[0], len(js)) + " in turn; mean wait per job: " + tenths(ja[1], len(js)) + " and " + tenths(jb[1], len(js)) + ".")
    if ja[0] == jb[0]:
        print("Per job the two are the same: the worker is busy in the same slots either way, and only who goes first changes.")

def ask_int(prompt, low, high):
    t = plain(input(prompt))
    if t == "":
        print("Cancelled.")
        return 0 - 1
    if len(t) > 2:
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")
        return 0 - 1
    for c in t:
        if c < "0" or c > "9":
            print("Type a whole number from " + str(low) + " to " + str(high) + ".")
            return 0 - 1
    if int(t) < low or int(t) > high:
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")
        return 0 - 1
    return int(t)

def settings(heavy, light, late):
    h = ask_int("tenant H's jobs (1 to 30)> ", 1, 30)
    if h < 0:
        return [heavy, light, late]
    l = ask_int("light tenants (1 to 8)> ", 1, 8)
    if l < 0:
        return [heavy, light, late]
    s = ask_int("slot the light tenants send at (0 to 30)> ", 0, 30)
    if s < 0:
        return [heavy, light, late]
    show(h, l, s)
    return [h, l, s]

heavy = 20
light = 5
late = 1
print("== Fair share ==")
print("One worker, jobs of one slot each, from one heavy tenant and several light ones.")
running = True
while running:
    print("")
    print("1) the tenants  2) compare  3) settings  4) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show(heavy, light, late)
    elif choice == "2":
        compare(heavy, light, late)
    elif choice == "3":
        s = settings(heavy, light, late)
        heavy = s[0]
        light = s[1]
        late = s[2]
    elif choice == "4":
        running = False
    else:
        print("Pick a number from 1 to 4.")
print("Bye.")
```

### fair.eml

```eml
# P132 fair share - one worker, jobs of one slot each from several tenants.
# As in the corpus case fair-share-vs-fifo, first come first served is fair
# to jobs, and taking the tenants in turn is fair to tenants. Each job is
# [tenant, arrival slot]; the worker runs one job a slot, never idle while a
# job is waiting.

def jobs(heavy, light, late):
    # tenant 0 sends heavy jobs at slot 0; tenants 1 to light one each at
    # slot late
    [] => out
    for k in [1:heavy]:
        out + [[0, 0]] => out
    for t in [1:light]:
        out + [[t, late]] => out
    return out

def schedule(js, tenants, plan):
    # the order the jobs run in, as [job index, start slot]
    [] => done
    for j in js:
        done + [False] => done
    [] => out
    0 => slot
    0 => turn
    while len(out) < len(js):
        0 - 1 => pick
        if plan == "fifo":
            for i in [0:len(js) - 1]:
                if pick < 0 and not done[i] and js[i][1] <= slot:
                    i => pick
        else:
            for step in [0:tenants - 1]:
                (turn + step) % tenants => t
                if pick < 0:
                    for i in [0:len(js) - 1]:
                        if pick < 0 and not done[i] and js[i][0] == t and js[i][1] <= slot:
                            i => pick
                    if pick >= 0:
                        (t + 1) % tenants => turn
        if pick >= 0:
            True => done[pick]
            out + [[pick, slot]] => out
        slot + 1 => slot
    return out

def per_tenant(js, tenants, order):
    # per tenant [jobs, worst completion, total completion]; completion is the
    # slot after the job's slot
    [] => out
    for t in [0:tenants - 1]:
        out + [[0, 0, 0]] => out
    for e in order:
        js[e[0]][0] => t
        e[1] + 1 => c
        out[t][0] + 1 => out[t][0]
        if c > out[t][1]:
            c => out[t][1]
        out[t][2] + c => out[t][2]
    return out

def per_job(js, order):
    # [total completion, total wait] over all jobs
    0 => c
    0 => w
    for e in order:
        c + e[1] + 1 => c
        w + e[1] - js[e[0]][1] => w
    return [c, w]
```

Python projection of fair.eml:

```python
def jobs(heavy, light, late):
    out = []
    for k in range(1, heavy+1):
        out = out + [[0, 0]]
    for t in range(1, light+1):
        out = out + [[t, late]]
    return out

def schedule(js, tenants, plan):
    done = []
    for j in js:
        done = done + [False]
    out = []
    slot = 0
    turn = 0
    while len(out) < len(js):
        pick = 0 - 1
        if plan == "fifo":
            for i in range(0, len(js)):
                if pick < 0 and not done[i] and js[i][1] <= slot:
                    pick = i
        else:
            for step in range(0, tenants):
                t = (turn + step) % tenants
                if pick < 0:
                    for i in range(0, len(js)):
                        if pick < 0 and not done[i] and js[i][0] == t and js[i][1] <= slot:
                            pick = i
                    if pick >= 0:
                        turn = (t + 1) % tenants
        if pick >= 0:
            done[pick] = True
            out = out + [[pick, slot]]
        slot = slot + 1
    return out

def per_tenant(js, tenants, order):
    out = []
    for t in range(0, tenants):
        out = out + [[0, 0, 0]]
    for e in order:
        t = js[e[0]][0]
        c = e[1] + 1
        out[t][0] = out[t][0] + 1
        if c > out[t][1]:
            out[t][1] = c
        out[t][2] = out[t][2] + c
    return out

def per_job(js, order):
    c = 0
    w = 0
    for e in order:
        c = c + e[1] + 1
        w = w + e[1] - js[e[0]][1]
    return [c, w]
```

## README

# P132 - Fair share

One worker, jobs of one slot each, from one heavy tenant and several light
ones, run first come first served or taking the tenants in turn.

- `main.eml` - the menu, the tenants, the comparison slot by slot and per
  tenant, and the settings
- `fair.eml` - the jobs, the two schedules, and the counts per tenant and
  per job

How each part works:

- Tenant H sends its jobs at slot 0; each light tenant sends one at a
  later slot. The worker runs one job a slot and is never idle while a
  job waits. In turn, it takes the next waiting job of the next tenant
  that has one.
- As in the corpus case `fair-share-vs-fifo`, first come is fair to jobs
  and in turn is fair to tenants. With 20 jobs from H and one each from a
  to e at slot 1, the light tenants' worst completion is 25 first come and
  6 in turn; H's last job moves from 20 to 25.
- Per job the two are the same: the worker is busy in the same slots
  either way, so the mean completion and the mean wait per job do not
  move at all. Taking turns never makes a light tenant's worst completion
  later.

What is checked: menu choices 1 to 4; 1 to 30 jobs for H, 1 to 8 light
tenants, and a slot from 0 to 30 for their jobs. An empty answer cancels.

Sessions: `sessions/basic.in` gives:
- the tenants and the comparison;
- the light jobs at slot 25, after H's are done: the two run alike, the
  worker idle in between;
- 6 jobs from H and 3 light tenants at slot 0.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- settings: a cancel, 0, 31 and x for H, then 5 with a cancel and 9
  light tenants, and 5 and 2 with a cancel and slot 31;
- the most of everything: 30 jobs from H and 8 light tenants at slot 0.

Built on the verified corpus case `fair-share-vs-fifo` (the same jobs in
two orders, fair to requests or fair to tenants).
