Task: Railway circuit




Go: package main

import "fmt"

const (
    right    = 1
    left     = -1
    straight = 0
)

func normalize(tracks []int) string {
    size := len(tracks)
    a := make([]byte, size)
    for i := 0; i < size; i++ {
        a[i] = "abc"[tracks[i]+1]
    }

    

    norm := string(a)
    for i := 0; i < size; i++ {
        s := string(a)
        if s < norm {
            norm = s
        }
        tmp := a[0]
        copy(a, a[1:])
        a[size-1] = tmp
    }
    return norm
}

func fullCircleStraight(tracks []int, nStraight int) bool {
    if nStraight == 0 {
        return true
    }

    
    count := 0
    for _, track := range tracks {
        if track == straight {
            count++
        }
    }
    if count != nStraight {
        return false
    }

    
    var straightTracks [12]int
    for i, idx := 0, 0; i < len(tracks) && idx >= 0; i++ {
        if tracks[i] == straight {
            straightTracks[idx%12]++
        }
        idx += tracks[i]
    }
    any1, any2 := false, false
    for i := 0; i <= 5; i++ {
        if straightTracks[i] != straightTracks[i+6] {
            any1 = true
            break
        }
    }
    for i := 0; i <= 7; i++ {
        if straightTracks[i] != straightTracks[i+4] {
            any2 = true
            break
        }
    }
    return !any1 || !any2
}

func fullCircleRight(tracks []int) bool {
    
    sum := 0
    for _, track := range tracks {
        sum += track * 30
    }
    if sum%360 != 0 {
        return false
    }

    
    var rTurns [12]int
    for i, idx := 0, 0; i < len(tracks) && idx >= 0; i++ {
        if tracks[i] == right {
            rTurns[idx%12]++
        }
        idx += tracks[i]
    }
    any1, any2 := false, false
    for i := 0; i <= 5; i++ {
        if rTurns[i] != rTurns[i+6] {
            any1 = true
            break
        }
    }
    for i := 0; i <= 7; i++ {
        if rTurns[i] != rTurns[i+4] {
            any2 = true
            break
        }
    }
    return !any1 || !any2
}

func circuits(nCurved, nStraight int) {
    solutions := make(map[string][]int)
    gen := getPermutationsGen(nCurved, nStraight)
    for gen.hasNext() {
        tracks := gen.next()
        if !fullCircleStraight(tracks, nStraight) {
            continue
        }
        if !fullCircleRight(tracks) {
            continue
        }
        tracks2 := make([]int, len(tracks))
        copy(tracks2, tracks)
        solutions[normalize(tracks)] = tracks2
    }
    report(solutions, nCurved, nStraight)
}

func getPermutationsGen(nCurved, nStraight int) PermutationsGen {
    if (nCurved+nStraight-12)%4 != 0 {
        panic("input must be 12 + k * 4")
    }
    var trackTypes []int
    switch nStraight {
    case 0:
        trackTypes = []int{right, left}
    case 12:
        trackTypes = []int{right, straight}
    default:
        trackTypes = []int{right, left, straight}
    }
    return NewPermutationsGen(nCurved+nStraight, trackTypes)
}

func report(sol map[string][]int, numC, numS int) {
    size := len(sol)
    fmt.Printf("\n%d solution(s) for C%d,%d \n", size, numC, numS)
    if numC <= 20 {
        for _, tracks := range sol {
            for _, track := range tracks {
                fmt.Printf("%2d ", track)
            }
            fmt.Println()
        }
    }
}


type PermutationsGen struct {
    NumPositions int
    choices      []int
    indices      []int
    sequence     []int
    carry        int
}

func NewPermutationsGen(numPositions int, choices []int) PermutationsGen {
    indices := make([]int, numPositions)
    sequence := make([]int, numPositions)
    carry := 0
    return PermutationsGen{numPositions, choices, indices, sequence, carry}
}

func (p *PermutationsGen) next() []int {
    p.carry = 1

    
    for i := 1; i < len(p.indices) && p.carry > 0; i++ {
        p.indices[i] += p.carry
        p.carry = 0
        if p.indices[i] == len(p.choices) {
            p.carry = 1
            p.indices[i] = 0
        }
    }
    for j := 0; j < len(p.indices); j++ {
        p.sequence[j] = p.choices[p.indices[j]]
    }
    return p.sequence
}

func (p *PermutationsGen) hasNext() bool {
    return p.carry != 1
}

func main() {
    for n := 12; n <= 28; n += 4 {
        circuits(n, 0)
    }
    circuits(12, 4)
}




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])