Task: Intersecting number wheels
C#: using System;
using System.Collections.Generic;
using System.Linq;

public static class IntersectingNumberWheels
{
    public static void Main() {
        TurnWheels(('A', "123")).Take(20).Print();
        TurnWheels(('A', "1B2"), ('B', "34")).Take(20).Print();
        TurnWheels(('A', "1DD"), ('D', "678")).Take(20).Print();
        TurnWheels(('A', "1BC"), ('B', "34"), ('C', "5B")).Take(20).Print();
    }

    static IEnumerable<char> TurnWheels(params (char name, string values)[] wheels) {
        var data = wheels.ToDictionary(wheel => wheel.name, wheel => wheel.values.Loop().GetEnumerator());
        var primary = data[wheels[0].name];
        while (true) {
            yield return Turn(primary);
        }

        char Turn(IEnumerator<char> sequence) {
            sequence.MoveNext();
            char c = sequence.Current;
            return char.IsDigit(c) ? c : Turn(data[c]);
        }
    }

    static IEnumerable<T> Loop<T>(this IEnumerable<T> seq) {
        while (true) {
            foreach (T element in seq) yield return element;
        }
    }

    static void Print(this IEnumerable<char> sequence) => Console.WriteLine(string.Join(" ", sequence));
}

Java: package intersectingNumberWheels;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

public class WheelController {
	private static final String IS_NUMBER = "[0-9]";
	private static final int TWENTY = 20;
	private static Map<String, WheelModel> wheelMap;

	public static void advance(String wheel) {
		WheelModel w = wheelMap.get(wheel);
		if (w.list.get(w.position).matches(IS_NUMBER)) {
			w.printThePosition();
			w.advanceThePosition();
		} else {
			String wheelName = w.list.get(w.position);
			advance(wheelName);
			w.advanceThePosition();
		}
	}

	public static void run() {
		System.out.println(wheelMap);
		IntStream.rangeClosed(1, TWENTY).forEach(i -> advance("A"));
		System.out.println();
		wheelMap.clear();
	}

	public static void main(String[] args) {
		wheelMap = new HashMap<>();
		wheelMap.put("A", new WheelModel("A", "1", "2", "3"));
		run();
		wheelMap.put("A", new WheelModel("A", "1", "B", "2"));
		wheelMap.put("B", new WheelModel("B", "3", "4"));
		run();
		wheelMap.put("A", new WheelModel("A", "1", "D", "D"));
		wheelMap.put("D", new WheelModel("D", "6", "7", "8"));
		run();
		wheelMap.put("A", new WheelModel("A", "1", "B", "C"));
		wheelMap.put("B", new WheelModel("B", "3", "4"));
		wheelMap.put("C", new WheelModel("C", "5", "B"));
		run();
	}

}

class WheelModel {
	String name;
	List<String> list;
	int position;
	int endPosition;
	private static final int INITIAL = 0;

	public WheelModel(String name, String... values) {
		super();

		this.name = name.toUpperCase();
		this.list = new ArrayList<>();
		for (String value : values) {
			list.add(value);
		}
		this.position = INITIAL;
		this.endPosition = this.list.size() - 1;
	}

	@Override
	public String toString() {
		return list.toString();
	}

	public void advanceThePosition() {
		if (this.position == this.endPosition) {
			this.position = INITIAL;
		} else {
			this.position++;
		}
	}

	public void printThePosition() {
		System.out.print(" " + this.list.get(position));
	}
}