files / problems / one-third-two-thirds / compute / q2 / collect.py

3.9 KB · on GitHub

#!/usr/bin/env python3
"""Turn C enumerator stdout into the JSON certificates verify_winners reads."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

HERE = Path(__file__).resolve().parent


def parse_broken(cell: str) -> list[int]:
    if cell in ("-", "", "None"):
        return []
    return [int(x) for x in cell.split(",") if x != ""]


def parse_frac(cell: str) -> list[int]:
    a, b = cell.split("/")
    return [int(a), int(b)]


def parse_down(cell: str) -> list[int] | None:
    if cell in ("-", ""):
        return None
    return [int(x) for x in cell.split(",")]


def collect_ladder(path: Path) -> dict:
    rows = []
    for line in path.read_text().splitlines():
        if not line or line.startswith("n\t"):
            continue
        n, mn, broken, e, n_non, n_below = line.split("\t")
        rows.append(
            {
                "n": int(n),
                "min_delta": parse_frac(mn),
                "broken": parse_broken(broken),
                "e": int(e),
                "n_non_sum": int(n_non),
                "n_below_1_3": int(n_below),
                "source": "C exhaustive; Python replay of the witness",
            }
        )
    return {
        "census": rows,
        "note": "Non-sum broken-rung ladder minima. Stamp-based C enumerator.",
    }


def collect_rail(path: Path) -> dict:
    rows = []
    for line in path.read_text().splitlines():
        if not line or line.startswith("n\t"):
            continue
        n, mn, b4, b5, e, n_non, n617, n13 = line.split("\t")
        rows.append(
            {
                "n": int(n),
                "min_delta": parse_frac(mn),
                "broken4": parse_broken(b4),
                "broken5": parse_broken(b5),
                "e": int(e),
                "n_non_sum": int(n_non),
                "n_below_6_17": int(n617),
                "n_below_1_3": int(n13),
                "complete": True,
            }
        )
    return {
        "exhaustive": rows,
        "note": "Three-rail non-sum minima. C exhaustive.",
    }


def collect_interval(path: Path) -> dict:
    rows = []
    for line in path.read_text().splitlines():
        if not line or line.startswith("n\t"):
            continue
        parts = line.split("\t")
        (
            n,
            n_all,
            n_non,
            n_ns,
            mn,
            min_e,
            min_ns,
            min_ns_e,
            n_below,
            min_down,
            min_ns_down,
        ) = parts
        row = {
            "n": int(n),
            "n_natural_interval": int(n_all),
            "n_nonchain": int(n_non),
            "n_not_semiorder": int(n_ns),
            "n_below_1_3": int(n_below),
            "min_delta": parse_frac(mn),
            "min_e": int(min_e),
            "min_down": parse_down(min_down),
            "complete": True,
        }
        if min_ns != "-":
            row["min_not_semi"] = parse_frac(min_ns)
            row["min_ns_e"] = int(min_ns_e)
            row["min_not_semi_down"] = parse_down(min_ns_down)
        rows.append(row)
    return {"census": rows, "oeis": "A367494"}


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("kind", choices=("ladder", "rail", "interval"))
    ap.add_argument("log")
    ap.add_argument("-o", "--out")
    args = ap.parse_args()
    src = Path(args.log)
    if args.kind == "ladder":
        blob = collect_ladder(src)
        dest = Path(args.out) if args.out else HERE / "ladder_census.json"
    elif args.kind == "rail":
        blob = collect_rail(src)
        dest = Path(args.out) if args.out else HERE / "three_rail.json"
    else:
        blob = collect_interval(src)
        dest = Path(args.out) if args.out else HERE / "interval_orders.json"
    dest.write_text(json.dumps(blob, indent=2) + "\n")
    print(f"wrote {dest} ({len(blob.get('census') or blob.get('exhaustive'))} rows)")


if __name__ == "__main__":
    main()