Task: Railway circuit





Java: package railwaycircuit;

import static java.util.Arrays.stream;
import java.util.*;
import static java.util.stream.IntStream.range;

public class RailwayCircuit {
    final static int RIGHT = 1, LEFT = -1, STRAIGHT = 0;

    static String normalize(int[] tracks) {
        char[] a = new char[tracks.length];
        for (int i = 0; i < a.length; i++)
            a[i] = "abc".charAt(tracks[i] + 1);

        
        String norm = new String(a);
        for (int i = 0, len = a.length; i < len; i++) {

            String s = new String(a);
            if (s.compareTo(norm) < 0)
                norm = s;

            char tmp = a[0];
            for (int j = 1; j < a.length; j++)
                a[j - 1] = a[j];
            a[len - 1] = tmp;
        }
        return norm;
    }

    static boolean fullCircleStraight(int[] tracks, int nStraight) {
        if (nStraight == 0)
            return true;

        
        if (stream(tracks).filter(i -> i == STRAIGHT).count() != nStraight)
            return false;

        
        int[] straight = new int[12];
        for (int i = 0, idx = 0; i < tracks.length && idx >= 0; i++) {
            if (tracks[i] == STRAIGHT)
                straight[idx % 12]++;
            idx += tracks[i];
        }

        return !(range(0, 6).anyMatch(i -> straight[i] != straight[i + 6])
                && range(0, 8).anyMatch(i -> straight[i] != straight[i + 4]));
    }

    static boolean fullCircleRight(int[] tracks) {

        
        if (stream(tracks).map(i -> i * 30).sum() % 360 != 0)
            return false;

        
        int[] rTurns = new int[12];
        for (int i = 0, idx = 0; i < tracks.length && idx >= 0; i++) {
            if (tracks[i] == RIGHT)
                rTurns[idx % 12]++;
            idx += tracks[i];
        }

        return !(range(0, 6).anyMatch(i -> rTurns[i] != rTurns[i + 6])
                && range(0, 8).anyMatch(i -> rTurns[i] != rTurns[i + 4]));
    }

    static void circuits(int nCurved, int nStraight) {
        Map<String, int[]> solutions = new HashMap<>();

        PermutationsGen gen = getPermutationsGen(nCurved, nStraight);
        while (gen.hasNext()) {

            int[] tracks = gen.next();

            if (!fullCircleStraight(tracks, nStraight))
                continue;

            if (!fullCircleRight(tracks))
                continue;

            solutions.put(normalize(tracks), tracks.clone());
        }
        report(solutions, nCurved, nStraight);
    }

    static PermutationsGen getPermutationsGen(int nCurved, int nStraight) {
        assert (nCurved + nStraight - 12) % 4 == 0 : "input must be 12 + k * 4";

        int[] trackTypes = new int[]{RIGHT, LEFT};

        if (nStraight != 0) {
            if (nCurved == 12)
                trackTypes = new int[]{RIGHT, STRAIGHT};
            else
                trackTypes = new int[]{RIGHT, LEFT, STRAIGHT};
        }

        return new PermutationsGen(nCurved + nStraight, trackTypes);
    }

    static void report(Map<String, int[]> sol, int numC, int numS) {

        int size = sol.size();
        System.out.printf("%n%d solution(s) for C%d,%d %n", size, numC, numS);

        if (size < 10)
            sol.values().stream().forEach(tracks -> {
                stream(tracks).forEach(i -> System.out.printf("%2d ", i));
                System.out.println();
            });
    }

    public static void main(String[] args) {
        circuits(12, 0);
        circuits(16, 0);
        circuits(20, 0);
        circuits(24, 0);
        circuits(12, 4);
    }
}

class PermutationsGen {
    
    private int[] indices;
    private int[] choices;
    private int[] sequence;
    private int carry;

    PermutationsGen(int numPositions, int[] choices) {
        indices = new int[numPositions];
        sequence = new int[numPositions];
        this.choices = choices;
    }

    int[] next() {
        carry = 1;
        
        for (int i = 1; i < indices.length && carry > 0; i++) {
            indices[i] += carry;
            carry = 0;

            if (indices[i] == choices.length) {
                carry = 1;
                indices[i] = 0;
            }
        }

        for (int i = 0; i < indices.length; i++)
            sequence[i] = choices[indices[i]];

        return sequence;
    }

    boolean hasNext() {
        return carry != 1;
    }
}



Python: from itertools import count, islice
import numpy as np
from numpy import sin, cos, pi


ANGDIV = 12
ANG = 2*pi/ANGDIV

def draw_all(sols):
    import matplotlib.pyplot as plt

    def draw_track(ax, s):
        turn, xend, yend = 0, [0], [0]

        for d in s:
            x0, y0 = xend[-1], yend[-1]
            a = turn*ANG
            cs, sn = cos(a), sin(a)
            ang = a + d*pi/2
            cx, cy = x0 + cos(ang), y0 + sin(ang)

            da = np.linspace(ang, ang + d*ANG, 10)
            xs = cx - cos(da)
            ys = cy - sin(da)
            ax.plot(xs, ys, 'green' if d == -1 else 'orange')

            xend.append(xs[-1])
            yend.append(ys[-1])
            turn += d

        ax.plot(xend, yend, 'k.', markersize=1)
        ax.set_aspect(1)

    ls = len(sols)
    if ls == 0: return

    w, h = min((abs(w*2 - h*3) + w*h - ls, w, h)
        for w, h in ((w, (ls + w - 1)//w)
            for w in range(1, ls + 1)))[1:]

    fig, ax = plt.subplots(h, w, squeeze=False)
    for a in ax.ravel(): a.set_axis_off()

    for i, s in enumerate(sols):
        draw_track(ax[i//w, i%w], s)

    plt.show()


def match_up(this, that, equal_lr, seen):
    if not this or not that: return

    n = len(this[0][-1])
    n2 = n*2

    l_lo, l_hi, r_lo, r_hi = 0, 0, 0, 0

    def record(m):
        for _ in range(n2):
            seen[m] = True
            m = (m&1) << (n2 - 1) | (m >> 1)

        if equal_lr:
            m ^= (1<<n2) - 1
            for _ in range(n2):
                seen[m] = True
                m = (m&1) << (n2 - 1) | (m >> 1)

    l_n, r_n = len(this), len(that)
    tol = 1e-3

    while l_lo < l_n:
        while l_hi < l_n and this[l_hi][0] - this[l_lo][0] <= tol:
            l_hi += 1

        while r_lo < r_n and that[r_lo][0] < this[l_lo][0] - tol:
            r_lo += 1

        r_hi = r_lo
        while r_hi < r_n and that[r_hi][0] < this[l_lo][0] + tol:
            r_hi += 1

        for a in this[l_lo:l_hi]:
            m_left = a[-2]<<n
            for b in that[r_lo:r_hi]:
                if (m := m_left | b[-2]) not in seen:
                    if np.abs(a[1] + b[2]) < tol:
                        record(m)
                        record(int(f'{m:b}'[::-1], base=2))
                        yield(a[-1] + b[-1])

        l_lo, r_lo = l_hi, r_hi

def track_combo(left, right):
    n = (left + right)//2
    n1 = left + right - n

    alphas = np.exp(1j*ANG*np.arange(ANGDIV))
    def half_track(m, n):
        turns = tuple(1 - 2*(m>>i & 1) for i in range(n))
        rcnt = np.cumsum(turns)%ANGDIV
        asum = np.sum(alphas[rcnt])
        want = asum/alphas[rcnt[-1]]
        return np.abs(asum), asum, want, m, turns

    res = [[] for _ in range(right + 1)]
    for i in range(1<<n):
        b = i.bit_count()
        if b <= right:
            res[b].append(half_track(i, n))

    for v in res: v.sort()
    if n1 == n:
        return res, res

    res1 = [[] for _ in range(right + 1)]
    for i in range(1<<n1):
        b = i.bit_count()
        if b <= right:
            res1[b].append(half_track(i, n1))

    for v in res: v.sort()
    return res, res1

def railway(n):
    seen = {}

    for l in range(n//2, n + 1):
        r = n - l
        if not l >= r: continue

        if (l - r)%ANGDIV == 0:
            res_l, res_r = track_combo(l, r)

            for i, this in enumerate(res_l):
                if 2*i < r: continue
                that = res_r[r - i]
                for s in match_up(this, that, l == r, seen):
                    yield s

sols = []
for i, s in enumerate(railway(30)):
    
    print(i + 1, s)
    sols.append(s)

draw_all(sols[:40])