Repository: scalekit (difficulty: hard)

Issue:
count_no_adjacent_equal does a full scan and cannot reach the bound

count_no_adjacent_equal(100) == 90 (only 11, 22, .., 99 and 100 break
the adjacency rule) and count_no_adjacent_equal(200000) == 125478, both
matching a brute scan. The documented bound is N up to 10**17, though,
and the current implementation literally enumerates 1 .. N, so
count_no_adjacent_equal(10**16 - 1) is never going to return; I stopped
it after fifteen minutes. Expected: 2084647712458320, which is the sum
of 9**l for l = 1 .. 16, because for each decimal length l there are
exactly 9**l valid numbers (nine choices for the leading digit and nine
for every later digit). A digit-by-digit walk over the decimal form of
N answers the general case in microseconds.

The test suite wraps each large call in a watchdog worker process with
an 8 second per-call budget, so the O(N) scan now fails
deterministically. Bounds: 1 <= N <= 10**17. Semantics stay exactly as
documented: count the integers x in [1, N] whose decimal digits
(written without leading zeros) never repeat consecutively.

Source files:
core.py: """Digit pattern counters for scalekit.

This module exposes count_no_adjacent_equal(N), which returns how many
integers x in [1, N] have no two adjacent equal digits in their
decimal representation (written without leading zeros). For example
121 and 90 qualify while 112, 100 and 33 do not.

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


def count_no_adjacent_equal(N):
    """Return the count by scanning every candidate up to N."""
    count = 0
    for x in range(1, N + 1):
        s = str(x)
        if all(a != b for a, b in zip(s, s[1:])):
            count += 1
    return count


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

Large calls run in a watchdog worker process with a hard per-call time
budget: within the documented bounds (N up to 10**17) only a digit-DP
style algorithm can answer, so a correct full scan of 1 .. N fails
these tests by running out of budget.
"""

import multiprocessing as mp

from core import count_no_adjacent_equal

LARGE_BUDGET_SECONDS = 8.0


def _worker(args, q):
    q.put(count_no_adjacent_equal(*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(
            "count_no_adjacent_equal{} did not finish within {}s; the stated "
            "bounds need a digit DP, not a full scan".format(tuple(args), timeout)
        )
    if p.exitcode != 0:
        raise AssertionError(
            "count_no_adjacent_equal{} worker exited with code {}".format(
                tuple(args), p.exitcode
            )
        )
    return q.get(timeout=5.0)


def test_small_matches_brute():
    checkpoints = {
        1, 9, 10, 11, 99, 100, 101, 1000, 12345, 54321,
        99999, 100000, 150000, 199999, 200000,
    }
    running = 0
    for x in range(1, 200001):
        s = str(x)
        if all(a != b for a, b in zip(s, s[1:])):
            running += 1
        if x in checkpoints:
            assert count_no_adjacent_equal(x) == running


def test_matches_all_nines_closed_form():
    # for N = 10**L - 1 the count covers full lengths 1 .. L, and each
    # length l holds exactly 9**l valid numbers (nine choices for the
    # leading digit, nine for each later digit), so the expected value
    # is computed here independently of the module under test
    expected = sum(9**l for l in range(1, 17))
    assert _call_with_timeout((10**16 - 1,)) == expected


def test_large_pinned_values():
    assert _call_with_timeout((31415926535897932,)) == 6069141374923794
    assert _call_with_timeout((10**17,)) == 18761829412124889


Patch:
--- a/core.py
+++ b/core.py
@@ -13,10 +13,30 @@
 
 
 def count_no_adjacent_equal(N):
-    """Return the count by scanning every candidate up to N."""
-    count = 0
-    for x in range(1, N + 1):
-        s = str(x)
-        if all(a != b for a, b in zip(s, s[1:])):
-            count += 1
-    return count
+    """Return the count with a digit walk over the decimal form of N.
+
+    Numbers shorter than N contribute 9**l for each length l: the
+    leading digit has nine choices (1 - 9) and every later digit has
+    nine choices distinct from its predecessor. For numbers of the same
+    length as N, walk the digits of N from the left: keeping a valid
+    prefix of N and placing a smaller digit at position i leaves
+    9**(L - 1 - i) free completions; stop once the prefix of N itself
+    violates the rule. Finally count N itself when it is valid. Total
+    work is O(len(str(N))**2).
+    """
+    s = str(N)
+    L = len(s)
+    total = sum(9**l for l in range(1, L))
+    prefix_ok = True
+    for i, ch in enumerate(s):
+        d = int(ch)
+        lo = 1 if i == 0 else 0
+        prev = int(s[i - 1]) if i > 0 else -1
+        choices = sum(1 for x in range(lo, d) if x != prev)
+        total += choices * 9 ** (L - 1 - i)
+        if i > 0 and d == prev:
+            prefix_ok = False
+            break
+    if prefix_ok:
+        total += 1
+    return total