Task: Intersecting number wheels
C++: #include <initializer_list>
#include <iostream>
#include <map>
#include <vector>

struct Wheel {
private:
    std::vector<char> values;
    size_t index;

public:
    Wheel() : index(0) {
        
    }

    Wheel(std::initializer_list<char> data) : values(data), index(0) {
        
        if (values.size() < 1) {
            throw new std::runtime_error("Not enough elements");
        }
    }

    char front() {
        return values[index];
    }

    void popFront() {
        index = (index + 1) % values.size();
    }
};

struct NamedWheel {
private:
    std::map<char, Wheel> wheels;

public:
    void put(char c, Wheel w) {
        wheels[c] = w;
    }

    char front(char c) {
        char v = wheels[c].front();
        while ('A' <= v && v <= 'Z') {
            v = wheels[v].front();
        }
        return v;
    }

    void popFront(char c) {
        auto v = wheels[c].front();
        wheels[c].popFront();

        while ('A' <= v && v <= 'Z') {
            auto d = wheels[v].front();
            wheels[v].popFront();
            v = d;
        }
    }
};

void group1() {
    Wheel w({ '1', '2', '3' });
    for (size_t i = 0; i < 20; i++) {
        std::cout << ' ' << w.front();
        w.popFront();
    }
    std::cout << '\n';
}

void group2() {
    Wheel a({ '1', 'B', '2' });
    Wheel b({ '3', '4' });

    NamedWheel n;
    n.put('A', a);
    n.put('B', b);

    for (size_t i = 0; i < 20; i++) {
        std::cout << ' ' << n.front('A');
        n.popFront('A');
    }
    std::cout << '\n';
}

void group3() {
    Wheel a({ '1', 'D', 'D' });
    Wheel d({ '6', '7', '8' });

    NamedWheel n;
    n.put('A', a);
    n.put('D', d);

    for (size_t i = 0; i < 20; i++) {
        std::cout << ' ' << n.front('A');
        n.popFront('A');
    }
    std::cout << '\n';
}

void group4() {
    Wheel a({ '1', 'B', 'C' });
    Wheel b({ '3', '4' });
    Wheel c({ '5', 'B' });

    NamedWheel n;
    n.put('A', a);
    n.put('B', b);
    n.put('C', c);

    for (size_t i = 0; i < 20; i++) {
        std::cout << ' ' << n.front('A');
        n.popFront('A');
    }
    std::cout << '\n';
}

int main() {
    group1();
    group2();
    group3();
    group4();

    return 0;
}

Python: from itertools import islice

class INW():
    

    def __init__(self, **wheels):
        self._wheels = wheels
        self.isect = {name: self._wstate(name, wheel) 
                      for name, wheel in wheels.items()}
    
    def _wstate(self, name, wheel):
        "Wheel state holder"
        assert all(val in self._wheels for val in wheel if type(val) == str), \
               f"ERROR: Interconnected wheel not found in {name}: {wheel}"
        pos = 0
        ln = len(wheel)
        while True:
            nxt, pos = wheel[pos % ln], pos + 1
            yield next(self.isect[nxt]) if type(nxt) == str else nxt
                
    def __iter__(self):
        base_wheel_name = next(self.isect.__iter__())
        yield from self.isect[base_wheel_name]
        
    def __repr__(self):
        return f"{self.__class__.__name__}({self._wheels})"
    
    def __str__(self):
        txt = "Intersecting Number Wheel group:"
        for name, wheel in self._wheels.items():
            txt += f"\n  {name+':':4}" + ' '.join(str(v) for v in wheel)
        return txt

def first(iter, n):
    "Pretty print first few terms"
    return ' '.join(f"{nxt}" for nxt in islice(iter, n))

if __name__ == '__main__':
    for group in[
      {'A': (1, 2, 3)},
      {'A': (1, 'B', 2),
       'B': (3, 4)},
      {'A': (1, 'D', 'D'),
       'D': (6, 7, 8)},
      {'A': (1, 'B', 'C'),
       'B': (3, 4),
       'C': (5, 'B')}, 
     ]:
        w = INW(**group)
        print(f"{w}\n  Generates:\n    {first(w, 20)} ...\n")