Last active
July 30, 2025 12:17
-
-
Save simonlindholm/e53652faf2eb59072d99f606dad4e314 to your computer and use it in GitHub Desktop.
Midnight Code Cup 2025 problem D (DSL Strikes Back), post-contest solutions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Helper library to give the DSL a nicer syntax. See e.g. sum-loop.py for example usage.""" | |
from collections import Counter | |
import inspect | |
import typing | |
ctr = 0 | |
def name(tp): | |
global ctr | |
ctr += 1 | |
return f"{tp}_{ctr}" | |
initial_rule = None | |
types = [] | |
rules = [] | |
new_nodes = [] | |
new_edges = [] | |
types_map = {} | |
class Tag: | |
def __init__(self, r): self.r = r | |
def __str__(self): return self.r | |
def __lt__(self, other): return cond(f"{self}<{other}") | |
def __gt__(self, other): return cond(f"{self}>{other}") | |
def __le__(self, other): return cond(f"{self}<={other}") | |
def __ge__(self, other): return cond(f"{self}>={other}") | |
def __eq__(self, other): return cond(f"{self}=={other}") | |
def __ne__(self, other): return cond(f"{self}!={other}") | |
def __lshift__(self, other): return f"{self}<<{other}" | |
def __rshift__(self, other): return f"{self}>>{other}" | |
def __mod__(self, other): return f"{self}%{other}" | |
def __mul__(self, other): return f"{self}*{other}" | |
def __add__(self, other): return f"{self}+{other}" | |
def __sub__(self, other): return f"{self}-{other}" | |
def __div__(self, other): return f"{self}/{other}" | |
def __or__(self, other): return f"{self}|{other}" | |
def __and__(self, other): return f"{self}&{other}" | |
def __xor__(self, other): return f"{self}^{other}" | |
def __rlshift__(self, other): return f"{other}<<{self}" | |
def __rrshift__(self, other): return f"{other}>>{self}" | |
def __rmod__(self, other): return f"{other}%{self}" | |
def __rmul__(self, other): return f"{other}*{self}" | |
def __radd__(self, other): return f"{other}+{self}" | |
def __rsub__(self, other): return f"{other}-{self}" | |
def __rdiv__(self, other): return f"{other}/{self}" | |
def __ror__(self, other): return f"{other}|{self}" | |
def __rand__(self, other): return f"{other}&{self}" | |
def __rxor__(self, other): return f"{other}^{self}" | |
def edge(a, b): | |
new_edges.append(f"{a}-{b}") | |
def add_type(type_name, active, *edges, prop=[]): | |
assert edges, f"{type_name} does not have any edges" | |
for n, c in Counter(edges).items(): | |
assert c == 1, f"duplicate edge name {n} in {type_name}" | |
class C: | |
_type_name = type_name | |
def __init__(self, tag, *args): | |
if isinstance(tag, (int, str, Tag)): | |
tag = str(tag) | |
else: | |
raise Exception(f"failed to pass tag to {type_name}") | |
self._name = name(type_name) | |
new_nodes.append(f"{self._name} {type_name} {tag}") | |
self._edges = edges | |
for i, e in enumerate(edges): | |
setattr(self, e, f"{self._name}.{i}") | |
if args: | |
assert len(args) >= len(edges), f"missing arguments when constructing {type_name}" | |
assert len(args) <= len(edges), f"too many arguments when constructing {type_name}" | |
for a, e in zip(args, edges): | |
if a is ...: | |
continue | |
n = getattr(self, e) | |
if not isinstance(a, str): | |
a = getattr(a, a._edges[0]) | |
edge(n, a) | |
@staticmethod | |
def _make_initial(side): | |
arg = C("tag", *([...] * len(edges))) | |
for i in range(len(edges)): | |
setattr(arg, edges[i], f"{side}{i}") | |
setattr(arg, "tag", Tag(f"{side}_tag")) | |
return arg | |
C.__name__ = type_name | |
types_map[type_name] = C | |
inspect.getmodule(inspect.stack()[1][0]).__dict__[type_name] = C | |
types.append((type_name, len(edges), active, prop)) | |
return C | |
def rule(fn): | |
global the_cond, cond_val | |
cond = "ALWAYS" | |
types = list(typing.get_type_hints(fn).values()) | |
lhs_t = types[0] | |
rhs_t = types[1] | |
lhs = lhs_t._make_initial("lhs") | |
rhs = rhs_t._make_initial("rhs") | |
del new_nodes[:] | |
del new_edges[:] | |
the_cond = None | |
cond_val = True | |
fn(lhs, rhs) | |
s = format_rule() | |
if the_cond is not None: | |
cond = the_cond | |
cond_val = False | |
fn(lhs, rhs) | |
s += "\n" + format_rule() | |
rules.append(f"{lhs_t._type_name} {rhs_t._type_name} {cond}\n" + s) | |
def initial(fn): | |
global initial_rule | |
assert initial_rule is None, "just one @initial allowed" | |
del new_nodes[:] | |
del new_edges[:] | |
fn() | |
initial_rule = format_rule() | |
def format_rule(): | |
s = f"{len(new_nodes)} {len(new_edges)}" | |
for n in new_nodes: | |
s += "\n" + n | |
for n in new_edges: | |
s += "\n" + n | |
del new_nodes[:] | |
del new_edges[:] | |
return s | |
def cond(expr): | |
global the_cond | |
the_cond = expr | |
return cond_val | |
add_type("INP", False, "out") | |
add_type("OUT", True, "out") | |
add_type("ERA", True, "out") | |
del types[:] | |
def finish(): | |
print(len(types)) | |
for tp in types: | |
print(tp[0], tp[1], str(tp[2]).lower(), *tp[3]) | |
print(len(rules)) | |
for rule in rules: | |
print(rule) | |
assert initial_rule is not None | |
print(initial_rule) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Bubble sort, using a circularly linked list. This roughly ties the best entry in code size. | |
from lib import * | |
add_type("START", True, "n") # [0] | |
add_type("SETUP", True, "inp", "right", "eof") # [ind] | |
add_type("EOF", True, "left", "right") # [ind] | |
add_type("BLUE", True, "right", "left") # [val] | |
add_type("RED", True, "left", "right") # [val] | |
@initial | |
def f(): | |
START(0, INP(0)) | |
@rule | |
def f(s: START, n: INP): | |
e = EOF(n.tag - 1) | |
SETUP(n.tag - 1, INP(n.tag), e.right, e) | |
@rule | |
def f(s: SETUP, i: INP): | |
if s.tag >= 0: | |
red = RED(i.tag) | |
SETUP(s.tag - 1, INP(s.tag), red, s.eof) | |
edge(red.right, s.right) | |
else: | |
BLUE(-2**63, s.right, s.eof) | |
@rule | |
def f(e: EOF, r: RED): | |
EOF(e.tag, e.right, BLUE(r.tag, r.right, ...).left) | |
@rule | |
def f(lhs: BLUE, rhs: RED): | |
low, high = lhs.tag, rhs.tag | |
if not (lhs.tag < rhs.tag): | |
low, high = high, low | |
RED(low, lhs.left, BLUE(high, rhs.right, ...).left) | |
@rule | |
def f(b: BLUE, e: EOF): | |
OUT(e.tag, START(b.tag)) | |
EOF(e.tag - 1, e.right, b.left) | |
finish() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Sorting in O(1) parallel time by abusing activation propagation along secondary edges. | |
# For each input we count how many other inputs are smaller than it to find its output position, | |
# using a network like the following for summation, where every node propagates activity either | |
# down-right to add +1 to the sum, or right to add +0. | |
# | |
# o - o - o - o | |
# \ \ \ | |
# o - o - o | |
# \ \ | |
# o - o | |
# \ | |
# o | |
from lib import * | |
maxn = 50 | |
add_type("READB", True, "n", "out") # [i+1] | |
add_type("READB2", True, "inp", "out") # [0] | |
add_type("READC", True, "n", "out") # [i+1] | |
add_type("READC2", True, "inp", "out") # [0] | |
add_type("READVAL", False, "out") # [val] | |
add_type("OUTPUT", False, "val", "source") # [i] | |
add_type("EMPTY", False, "out") # [0] | |
add_type("ADD0", False, "dummy", "fromlt", "fromeq", "togt", "toeq", prop=[4]) # [0] | |
add_type("ADD1", False, "dummy", "fromlt", "fromeq", "togt", "toeq", prop=[3]) # [0] | |
reada = { | |
cmp: [ | |
[ | |
add_type(f"READA{cmp}{i}_{j}", True, *(["n"] * (j < 2)), "b", | |
*[f"fromlt{k}" for k in range(1, i+1)], | |
*[f"fromeq{k}" for k in range(i)], | |
*[f"toeq{k}" for k in range(i+1)], | |
*[f"togt{k}" for k in range(i+1)], | |
) # [i+1] / [0] / [val] | |
for j in range(3) | |
] | |
for i in range(maxn) | |
] | |
for cmp in ("GT", "GE") | |
} | |
@initial | |
def f(): | |
for i in range(maxn): | |
li = [] | |
for j in range(maxn): | |
n1 = INP(0) | |
n2 = INP(0) | |
cmp = "GT" if i <= j else "GE" | |
ai = reada[cmp][j][0](i+1) | |
bi = READC(j+1, INP(0), ai.b) | |
INP(0, ai) | |
li.append(ai) | |
for j in range(maxn-1): | |
for k in range(j+1): | |
edge(getattr(li[j], f"toeq{k}"), getattr(li[j+1], f"fromeq{k}")) | |
edge(getattr(li[j], f"togt{k}"), getattr(li[j+1], f"fromlt{k+1}")) | |
outs = [[None, None] for _ in range(maxn+1)] | |
for j in range(maxn+1): | |
for k in range(2): | |
if (j == 0 and k == 1) or (j == maxn and k == 0): | |
continue | |
v = READB(i+1, INP(0), ...) | |
outs[j][k] = OUTPUT(j, v.out, ...) | |
for k in range(maxn): | |
edge(getattr(li[maxn-1], f"toeq{k}"), outs[k][0].source) | |
edge(getattr(li[maxn-1], f"togt{k}"), outs[k+1][1].source) | |
@rule | |
def f(n: INP, r: READB): | |
if r.tag <= n.tag: | |
READB2(0, INP(r.tag), r.out) | |
else: | |
EMPTY(0, r.out) | |
@rule | |
def f(n: INP, r: READB2): | |
READVAL(n.tag, r.out) | |
@rule | |
def f(n: INP, r: READC): | |
if r.tag <= n.tag: | |
READC2(0, INP(r.tag), r.out) | |
else: | |
# Pad input array with max possible value on the right. | |
# Since we do a stable sort, this value will not make a difference. | |
READVAL(2**63-1, r.out) | |
@rule | |
def f(n: INP, r: READC2): | |
READVAL(n.tag, r.out) | |
@rule | |
def f(o: OUTPUT, e: EMPTY): | |
ERA(0, o.source) | |
@rule | |
def f(o: OUTPUT, v: READVAL): | |
ERA(0, o.source) | |
OUT(o.tag, READVAL(v.tag)) | |
for cmp in ("GT", "GE"): | |
for i in range(maxn): | |
READA, READA2, READVALA = reada[cmp][i] | |
@rule | |
def f(n: INP, r: READA): | |
if r.tag <= n.tag: | |
index = r.tag | |
else: | |
# Don't care, we won't write the result anyway. | |
# Pick something valid to reduce special-casing. | |
index = 0 | |
val = READA2(0) | |
INP(index, val) | |
for e in r._edges[1:]: | |
edge(getattr(r, e), getattr(val, e)) | |
@rule | |
def f(n: INP, r: READA2): | |
val = READVALA(n.tag) | |
for e in r._edges[1:]: | |
edge(getattr(r, e), getattr(val, e)) | |
@rule | |
def f(a: READVALA, b: READVAL): | |
if (a.tag > b.tag if cmp == "GT" else a.tag >= b.tag): | |
tp = ADD1 | |
else: | |
tp = ADD0 | |
dummy1 = OUT(0) if i == 0 else ERA(0) | |
dummy2 = ERA(0) | |
for j in range(i+1): | |
fromlt = getattr(a, f"fromlt{j}") if j > 0 else dummy1 | |
fromeq = getattr(a, f"fromeq{j}") if j != i else dummy2 | |
togt = getattr(a, f"togt{j}") | |
toeq = getattr(a, f"toeq{j}") | |
tp(0, ERA(0), fromlt, fromeq, togt, toeq) | |
finish() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Sorting in O(n log n). This is competitive with the fastest solution in serial time. | |
from lib import * | |
add_type("INIT", True, "n") # [0] | |
add_type("COMPLETE", False, "par") # [0] | |
add_type("MAKELIST", True, "inp", "right") # [ind] | |
add_type("EOF", True, "left", "out") # [0] | |
add_type("START", True, "red") # [ind] | |
add_type("DONE", False, "par") # [ind] | |
add_type("STATE", False, "par", "lt_start", "gt_start") # [ind] | |
add_type("REC", True, "state", "lt_end", "gt_end", "out") # [midval] | |
add_type("RECMID", True, "done", "gt_start", "gt_end", "out") # [midval] | |
add_type("BLUE", True, "right", "lt", "gt", "state") # [val] | |
add_type("RED", True, "left", "right") # [val] | |
add_type("EPS", True, "left", "right") # [0] | |
# START <- RED <- RED <- ... <- RED <- EOF [ind] <- out | |
# processes to: | |
# DONE [newind] -> out | |
@initial | |
def f(): | |
INIT(0, INP(0)) | |
@rule | |
def f(s: INIT, n: INP): | |
MAKELIST(n.tag - 1, INP(n.tag), EOF(0, ..., COMPLETE(0))) | |
@rule | |
def f(m: MAKELIST, i: INP): | |
if m.tag >= 0: | |
red = RED(i.tag) | |
MAKELIST(m.tag - 1, INP(m.tag), red) | |
edge(red.right, m.right) | |
else: | |
START(0, m.right) | |
@rule | |
def f(s: START, e: EOF): | |
DONE(s.tag, e.out) | |
@rule | |
def f(e: START, r: RED): | |
state = STATE(e.tag) | |
lt_eps = EPS(0, state.lt_start, ...) | |
gt_eps = EPS(0, state.gt_start, ...) | |
BLUE(r.tag, r.right, lt_eps.right, gt_eps.right, state) | |
@rule | |
def f(lhs: BLUE, rhs: RED): | |
if lhs.tag < rhs.tag: | |
gt = RED(rhs.tag, lhs.gt, ...).right | |
lt = lhs.lt | |
else: | |
gt = lhs.gt | |
lt = RED(rhs.tag, lhs.lt, ...).right | |
BLUE(lhs.tag, rhs.right, lt, gt, lhs.state) | |
@rule | |
def f(b: BLUE, e: EOF): | |
REC(b.tag, b.state, b.lt, b.gt, e.out) | |
@rule | |
def f(b: START, e: EPS): | |
START(b.tag, e.right) | |
@rule | |
def f(r: REC, s: STATE): | |
START(s.tag, s.lt_start) | |
EOF(0, r.lt_end, RECMID(r.tag, ..., s.gt_start, r.gt_end, r.out)) | |
@rule | |
def f(r: RECMID, d: DONE): | |
OUT(d.tag, DONE(r.tag)) | |
START(d.tag + 1, r.gt_start) | |
EOF(0, r.gt_end, r.out) | |
finish() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Optimized quicksort, with some more state being encoded into node types. This beats the fastest solution in serial time. | |
from lib import * | |
add_type("INIT", True, "n") # [0] | |
add_type("COMPLETE", False, "par") # [0] | |
add_type("MAKELIST", True, "inp", "right") # [ind] | |
add_type("EOF", True, "left", "out") # [0] | |
add_type("START", True, "red") # [ind] | |
add_type("DONE", False, "par") # [ind] | |
add_type("STATE", False, "par", "lt_start", "gt_start") # [ind] | |
add_type("RED", True, "left", "right") # [val] | |
rec = [[None] * 3 for _ in range(3)] | |
blue = [[None] * 3 for _ in range(3)] | |
for i in range(3): | |
for j in range(3): | |
rec[i][j] = add_type(f"REC{i}{j}", True, "state", "lt_end", "gt_end", "out") # [midval] | |
blue[i][j] = add_type(f"BLUE{i}{j}", True, "right", "lt", "gt", "state") # [val] | |
add_type("RECMID0", True, "done", "out") # [midval] | |
add_type("RECMID1", True, "done", "gt", "out") # [midval] | |
add_type("RECMID2", True, "done", "gt_start", "gt_end", "out") # [midval] | |
# START <- RED <- RED <- ... <- RED <- EOF [ind] <- out | |
# processes to: | |
# DONE [newind] -> out | |
@initial | |
def f(): | |
INIT(0, INP(0)) | |
@rule | |
def f(s: INIT, n: INP): | |
MAKELIST(n.tag - 1, INP(n.tag), EOF(0, ..., COMPLETE(0))) | |
@rule | |
def f(m: MAKELIST, i: INP): | |
if m.tag >= 0: | |
red = RED(i.tag) | |
MAKELIST(m.tag - 1, INP(m.tag), red) | |
edge(red.right, m.right) | |
else: | |
START(0, m.right) | |
@rule | |
def f(s: START, e: EOF): | |
DONE(s.tag, e.out) | |
@rule | |
def f(e: START, r: RED): | |
state = STATE(e.tag) | |
BLUE00(r.tag, r.right, state.lt_start, state.gt_start, state) | |
for i in range(3): | |
for j in range(3): | |
BLUE = blue[i][j] | |
REC = rec[i][j] | |
@rule | |
def f(lhs: BLUE, rhs: RED): | |
lt = lhs.lt | |
gt = lhs.gt | |
ni = i | |
nj = j | |
if lhs.tag < rhs.tag: | |
gt = RED(rhs.tag, gt, ...).right | |
nj = min(j + 1, 2) | |
else: | |
lt = RED(rhs.tag, lt, ...).right | |
ni = min(i + 1, 2) | |
blue[ni][nj](lhs.tag, rhs.right, lt, gt, lhs.state) | |
@rule | |
def f(b: BLUE, e: EOF): | |
REC(b.tag, b.state, b.lt, b.gt, e.out) | |
@rule | |
def f(r: REC, s: STATE): | |
if i <= 1: | |
if i == 0: | |
edge(s.lt_start, r.lt_end) | |
else: | |
OUT(s.tag, s.lt_start) | |
if j == 1: | |
edge(r.lt_end, r.gt_end) | |
else: | |
ERA(0, r.lt_end) | |
OUT(s.tag + i, DONE(r.tag)) | |
if j <= 1: | |
if j == 0: | |
edge(s.gt_start, r.gt_end) | |
elif j == 1: | |
OUT(s.tag + (i+1), s.gt_start) | |
if i != 1: | |
ERA(0, r.gt_end) | |
DONE(s.tag + (i+j+1), r.out) | |
else: | |
START(s.tag + (i+1), s.gt_start) | |
EOF(0, r.gt_end, r.out) | |
else: | |
if j == 0: | |
edge(s.gt_start, r.gt_end) | |
mid = RECMID0(r.tag, ..., r.out) | |
elif j == 1: | |
ERA(0, r.gt_end) | |
mid = RECMID1(r.tag, ..., s.gt_start, r.out) | |
else: | |
mid = RECMID2(r.tag, ..., s.gt_start, r.gt_end, r.out) | |
START(s.tag, s.lt_start) | |
EOF(0, r.lt_end, mid) | |
@rule | |
def f(r: RECMID0, d: DONE): | |
OUT(d.tag, DONE(r.tag)) | |
DONE(d.tag + 1, r.out) | |
@rule | |
def f(r: RECMID1, d: DONE): | |
OUT(d.tag, DONE(r.tag)) | |
OUT(d.tag + 1, r.gt) | |
DONE(d.tag + 2, r.out) | |
@rule | |
def f(r: RECMID2, d: DONE): | |
OUT(d.tag, DONE(r.tag)) | |
START(d.tag + 1, r.gt_start) | |
EOF(0, r.gt_end, r.out) | |
finish() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Summation in O(1) parallel time by abusing activation propagation along secondary edges. | |
# For each output digit we figure out the input carry in O(1), by building a carry chain for the next digit sums where: | |
# - digit sum < 9 uses a node type that never propagates carry activity | |
# - digit sum > 9 uses a node type that starts active and propagates carry activity along a secondary edge | |
# - digit sum = 9 uses a node type that starts inactive and propagates carry activity along a secondary edge if it becomes active | |
from lib import * | |
maxn = 50 | |
add_type("READLT1", True, "a", "b", "eq_nocarry0_out", "out") # [0] | |
add_type("READLT2", True, "b", "eq_nocarry0_out", "out") # [9-a] | |
add_type("READGT1", True, "a", "b", "eq_carry0_out", "out") # [0] | |
add_type("READGT2", True, "b", "eq_carry0_out", "out") # [9-a] | |
add_type("READEQ1", True, "a", "b", "eq_nocarry0_in", "eq_nocarry1_in", "eq_carry0_in", "eq_carry1_in", "eq_nocarry1_out", "eq_carry1_out", "out0", "out1") # [0] | |
add_type("READEQ2", True, "b", "eq_nocarry0_in", "eq_nocarry1_in", "eq_carry0_in", "eq_carry1_in", "eq_nocarry1_out", "eq_carry1_out", "out0", "out1") # [9-a] | |
add_type("EMITFINAL", False, "emit") # [0] | |
add_type("VAL", False, "val") # [val] | |
add_type("CARRY0", False, "out", "source1", "source2") # [val] | |
add_type("CARRY1", False, "out", "source1", "source2") # [val] | |
add_type("ACTIVE1", True, "dummy", "next", "out", prop=[1,2]) # [0] | |
add_type("INACTIVE1", False, "dummy", "next", "out") # [0] | |
add_type("PROPAGATING2", False, "dummy", "in1", "in2", "next", "out", prop=[3,4]) # [0] | |
add_type("INACTIVE2", False, "dummy", "in1", "in2", "next", "out") # [0] | |
emitsums = [None] * maxn | |
for i in range(-1, maxn): | |
if i >= 0: | |
READ1 = add_type(f"READ1_{i}", True, "inp", "b", "out") # [0] | |
READ2 = add_type(f"READ2_{i}", True, "inp", "out") # [a] | |
EMIT = add_type(f"EMIT_{i}", False, "out") # [a+b] | |
emitsums[i] = READ1 | |
@rule | |
def f(a: INP, r: READ1): | |
READ2(a.tag, r.b, r.out) | |
@rule | |
def f(b: INP, r: READ2): | |
EMIT(b.tag + r.tag, r.out) | |
else: | |
EMIT = EMITFINAL | |
@rule | |
def f(s: EMIT, c: CARRY0): | |
OUT(i + 1, VAL(s.tag if s.tag < 10 else s.tag - 10)) | |
ERA(0, c.source1) | |
ERA(0, c.source2) | |
@rule | |
def f(s: EMIT, c: CARRY1): | |
OUT(i + 1, VAL(s.tag + 1 if s.tag < 9 else s.tag - 9)) | |
ERA(0, c.source1) | |
ERA(0, c.source2) | |
@rule | |
def f(a: INP, r: READLT1): | |
READLT2(9 - a.tag, r.b, r.eq_nocarry0_out, r.out) | |
@rule | |
def f(b: INP, r: READLT2): | |
tp = ACTIVE1 if b.tag < r.tag else INACTIVE1 | |
tp(0, ERA(0), r.eq_nocarry0_out, r.out) | |
@rule | |
def f(a: INP, r: READGT1): | |
READGT2(9 - a.tag, r.b, r.eq_carry0_out, r.out) | |
@rule | |
def f(b: INP, r: READGT2): | |
tp = ACTIVE1 if b.tag > r.tag else INACTIVE1 | |
tp(0, ERA(0), r.eq_carry0_out, r.out) | |
@rule | |
def f(a: INP, r: READEQ1): | |
READEQ2( | |
9 - a.tag, r.b, | |
r.eq_nocarry0_in, r.eq_nocarry1_in, r.eq_carry0_in, r.eq_carry1_in, | |
r.eq_nocarry1_out, r.eq_carry1_out, | |
r.out0, r.out1 | |
) | |
@rule | |
def f(b: INP, r: READEQ2): | |
tp = PROPAGATING2 if b.tag == r.tag else INACTIVE2 | |
tp(0, ERA(0), r.eq_nocarry0_in, r.eq_nocarry1_in, r.eq_nocarry1_out, r.out0) | |
tp(0, ERA(0), r.eq_carry0_in, r.eq_carry1_in, r.eq_carry1_out, r.out1) | |
@initial | |
def f(): | |
carry0 = CARRY0(0, ..., OUT(0), ERA(0)) | |
carry1 = CARRY1(0, ..., ERA(0), ERA(0)) | |
eq_nocarry0 = OUT(0) | |
eq_nocarry1 = ERA(0) | |
eq_carry0 = ERA(0) | |
eq_carry1 = ERA(0) | |
for i in range(maxn-1, -1, -1): | |
emitsums[i](0, INP(i), INP(maxn + i), carry0) | |
emitsums[i](0, INP(i), INP(maxn + i), carry1) | |
carry0 = CARRY0(0) | |
carry1 = CARRY1(0) | |
readeq = READEQ1(0, INP(i), INP(maxn + i), eq_nocarry0, eq_nocarry1, eq_carry0, eq_carry1, ..., ..., carry0.source1, carry1.source1) # [0] | |
readlt = READLT1(0, INP(i), INP(maxn + i), ..., carry0.source2) | |
readgt = READGT1(0, INP(i), INP(maxn + i), ..., carry1.source2) | |
eq_nocarry0 = readlt.eq_nocarry0_out | |
eq_carry0 = readgt.eq_carry0_out | |
eq_nocarry1 = readeq.eq_nocarry1_out | |
eq_carry1 = readeq.eq_carry1_out | |
EMITFINAL(0, carry0) | |
EMITFINAL(0, carry1) | |
ERA(0, eq_nocarry0) | |
ERA(0, eq_carry0) | |
ERA(0, eq_nocarry1) | |
ERA(0, eq_carry1) | |
finish() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Summation in O(n) using a loop. IIRC this ties the best solution in code size, but it's not terribly interesting. | |
from lib import * | |
size = 50 | |
add_type("POS", True, "carry") # [ind] | |
add_type("CARRY", True, "pos") # [val] | |
add_type("ADDCARRY", True, "a", "b", "out") # [val] | |
add_type("ADD", True, "b", "out") # [val] | |
add_type("CONT", False, "out") # [ind] | |
@initial | |
def f(): | |
CARRY(0, POS(size - 1)) | |
@rule | |
def f(p: POS, c: CARRY): | |
if p.tag >= 0: | |
ADDCARRY(c.tag, INP(p.tag), INP(p.tag + size), CONT(p.tag)) | |
else: | |
OUT(0, POS(c.tag)) | |
@rule | |
def f(a: ADDCARRY, i: INP): | |
ADD(a.tag + i.tag, a.b, a.out) | |
@rule | |
def f(a: ADD, i: INP): | |
CARRY(a.tag + i.tag, a.out) | |
@rule | |
def f(c: CARRY, o: CONT): | |
OUT(o.tag+1, POS(c.tag % 10)) | |
CARRY(c.tag / 10, POS(o.tag - 1)) | |
finish() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Summation in O(n) which beats the fastest solution in sequential time. | |
# The main trick here is to use a speculative INP read at the start of each loop iteration, | |
# which reduces the number of required interactions by one. We cannot perform the read of a[i] | |
# speculatively, because it might read index -1 and crash, but we can do it for b[i] since | |
# that just harmlessly references index 49. Carry is encoded in node type. | |
from lib import * | |
size = 50 | |
add_type("VAL", False, "other") # [val] | |
add_type("POS0", True, "b") # [ind] | |
add_type("POS1", True, "b") # [ind] | |
add_type("PROCESS", True, "ind") # [a + b + carry] | |
add_type("ADD", True, "i", "out") # [b + carry] | |
@initial | |
def f(): | |
POS0(size-1, INP(size*2 - 1)) | |
for carry in range(2): | |
POS = POS0 if carry == 0 else POS1 | |
@rule | |
def f(p: POS, i: INP): | |
if p.tag >= 0: | |
ADD(i.tag + carry, INP(p.tag), VAL(p.tag)) | |
else: | |
OUT(0, VAL(carry)) | |
@rule | |
def f(a: ADD, i: INP): | |
PROCESS(a.tag + i.tag, a.out) | |
@rule | |
def f(p: PROCESS, v: VAL): | |
OUT(v.tag+1, VAL(p.tag % 10)) | |
tp = POS1 if p.tag >= 10 else POS0 | |
tp(v.tag-1, INP(v.tag + (size-1))) | |
finish() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment