Repository: scalekit (difficulty: hard)

Issue:
josephus cannot reach the documented n

For small circles everything checks out: josephus(5, 2) == 2 and
josephus(41, 3) == 30, matching a direct simulation of the elimination
process (people 0 .. n - 1, counting starts at position 0, every k-th
person leaves, and the 0-indexed survivor position is returned). But
the module documents n up to 10**12 with 1 <= k <= 40, and the
one-step recurrence does 10**12 iterations to get there:
josephus(10**12, 2) did not finish in twenty minutes. Expected:
900488372224, which the k == 2 closed form
2 * (n - 2**floor(log2(n))) gives instantly; general k should be
similarly fast because many consecutive recurrence steps can be
applied in a single jump while the circle is much larger than k.

The test suite now runs each large call in a watchdog worker process
with an 8 second per-call budget, so the O(n) loop fails the suite
rather than stalling it. Bounds: 1 <= n <= 10**12, 1 <= k <= 40. The
survivor semantics above must not change; they are pinned by a list
simulation for small n and by the plain recurrence at n = 10**6.

Source files:
core.py: """Josephus elimination helpers for scalekit.

This module exposes josephus(n, k): n people stand in a circle at
positions 0 .. n - 1, counting starts at position 0, and every k-th
person is eliminated (with k == 2 the first to leave is position 1;
with k == 1 it is position 0). Counting resumes with the neighbour
that follows each eliminated person. The function returns the
0-indexed position of the survivor.

Contract and bounds:
- n is an integer with 1 <= n <= 10**12
- k is an integer with 1 <= k <= 40
- any single call in the stated range must complete within a few
  seconds on commodity hardware
"""


def josephus(n, k):
    """Return the survivor with the standard recurrence.

    J(1) = 0 and J(m) = (J(m-1) + k) % m for m = 2 .. n, evaluated with
    one loop iteration per circle size.
    """
    res = 0
    for m in range(2, n + 1):
        res = (res + k) % m
    return res


Test files:
test_core.py: """Tests for core.josephus.

Large calls run in a watchdog worker process with a hard per-call time
budget: within the documented bounds (n up to 10**12, k up to 40) only
a jump-style O(k log n) method can answer, so the correct O(n)
recurrence fails these tests by running out of budget.
"""

import multiprocessing as mp
import random

from core import josephus

LARGE_BUDGET_SECONDS = 8.0


def _worker(args, q):
    q.put(josephus(*args))


def _call_with_timeout(args, timeout=LARGE_BUDGET_SECONDS):
    ctx = mp.get_context("spawn")
    q = ctx.Queue()
    p = ctx.Process(target=_worker, args=(args, q))
    p.start()
    p.join(timeout)
    if p.is_alive():
        p.terminate()
        p.join()
        raise AssertionError(
            "josephus{} did not finish within {}s; the stated bounds need a "
            "jump method, not the O(n) recurrence".format(tuple(args), timeout)
        )
    if p.exitcode != 0:
        raise AssertionError(
            "josephus{} worker exited with code {}".format(tuple(args), p.exitcode)
        )
    return q.get(timeout=5.0)


def _simulate(n, k):
    # direct elimination on a list; people 0 .. n - 1, counting starts at 0
    people = list(range(n))
    idx = 0
    while len(people) > 1:
        idx = (idx + k - 1) % len(people)
        people.pop(idx)
    return people[0]


def _reference_recurrence(n, k):
    # J(1) = 0, J(m) = (J(m-1) + k) % m
    res = 0
    for m in range(2, n + 1):
        res = (res + k) % m
    return res


def test_small_matches_simulation():
    for n in range(1, 81):
        for k in range(1, 11):
            assert josephus(n, k) == _simulate(n, k)
    rng = random.Random(1012)
    for _ in range(50):
        n = rng.randrange(1, 201)
        k = rng.randrange(1, 41)
        assert josephus(n, k) == _simulate(n, k)


def test_midsize_matches_recurrence():
    for k in (2, 3, 7, 40):
        assert josephus(10**6, k) == _reference_recurrence(10**6, k)


def test_large_k2_matches_closed_form():
    # for k == 2 the survivor has the closed form 2 * (n - 2**floor(log2 n)),
    # computed here independently of the module under test
    n = 10**12
    expected = 2 * (n - 2 ** (n.bit_length() - 1))
    assert _call_with_timeout((n, 2)) == expected


def test_large_k3_within_budget():
    assert _call_with_timeout((10**11, 3)) == 98686642455


Patch:
--- a/core.py
+++ b/core.py
@@ -16,12 +16,33 @@
 
 
 def josephus(n, k):
-    """Return the survivor with the standard recurrence.
+    """Return the survivor by jumping across many recurrence steps.
 
-    J(1) = 0 and J(m) = (J(m-1) + k) % m for m = 2 .. n, evaluated with
-    one loop iteration per circle size.
+    With k == 1 the survivor is simply n - 1. Otherwise descend from n:
+    while m >= k, the m // k recurrence steps that grow the circle from
+    size m - m // k to size m have a combined closed form, and while
+    m < k single steps are used. Only O(k * log n) intermediate sizes
+    are visited on the way down; the jumps are then replayed in reverse
+    starting from J(1) = 0.
     """
+    if k == 1:
+        return n - 1
+    sizes = []
+    m = n
+    while m > 1:
+        sizes.append(m)
+        if m < k:
+            m -= 1
+        else:
+            m -= m // k
     res = 0
-    for m in range(2, n + 1):
-        res = (res + k) % m
+    for m in reversed(sizes):
+        if m < k:
+            res = (res + k) % m
+        else:
+            res -= m % k
+            if res < 0:
+                res += m
+            else:
+                res += res // (k - 1)
     return res