The Bulgarian Solitaire
Imagine you are the director of a unit in a large company. You have 5 teams, with various numbers of people in each team. An important new project has come up, and you would like to set up a new team to tackle it. The problem is: you don’t have any budget to hire new people!
What can you do? Well, you could take one person from each existing team, and build a new team with these 5 people.
It works well for a while, but of course soon enough, a new project comes up, and you still don’t have any additional budget! It worked the first time, so you reshuffle the teams once more in the same way. One of the existing team had only one person left, so they go to the newly created team and their existing team just disappears.
Now the CEO is getting concerned with your unconventional management practices. She summons you to her office and asks you: What would happen if you do this repeatedly for every new project? How many teams will you end up with, with how many people in each one of them?
The Bulgarian Solitaire
Instead of this (admittedly far-fetched) management scenario, you can
do the same process with playing cards. You have \(n\) cards divided
arbitrarily into several piles. At each step, you remove one card from
each pile and collect them in a new pile next to the existing ones. If
you repeat this operation, how many piles of cards will you get? With
how many cards in each of them? This problem is called the Bulgarian
Solitaire. It is described in this paperWhich also describes the
history of the problem, including why it is called “Bulgarian”.
.
With \(n\) cards, the Bulgarian Solitaire is an example of a discrete dynamical system on partitions of \(n\). A partition is an unordered sequence of numbers that sum to \(n\). For instance, \((4,3,2,1)\) and \((8,3,2,2)\) are partitions of 15. At each step, the Bulgarian Solitaire will transform a partition like \((8,3,2,2)\) into a new one, in this case \((7,2,1,1,4)\) (the new pile is at the end, but we don’t care about the order so we can also write it as \((7,4,2,1,1)\)).
What we are trying to understand is in fact a standard question in dynamical systems: where is the system going? Are there stable states, cycles? What are they?
Visualizing the Bulgarian Solitaire process
As a first example, let’s take \(n=10\) cards, in piles of 4, 3, and 3 cards. We take one card from the top of each pile (in orange), and we make a new pile of size 3:
Visualizing cards like this is not very practical, but as it turns out, mathematicians have already devised a nice way to visualize partitions: Young diagrams. They represent a partition as rows of boxes. For example, the partition \((6,5,3,1)\) of the number 15 can be visualized as:

In our cases, we can rotate the Young diagram by 90° so that it looks more like piles of cards:

A step in the Bulgarian Solitaire will look like this:

We take one card from each pile (highlighted in orange in the diagram above), and create a new pile from them. Then we reorder the piles so that they are sorted by decreasing size.
Convergence of the Bulgarian Solitaire
Now, if you try the Bulgarian Solitaire with 15 cards, for every possible starting position, you will end up with 4 piles of size 1, 2, 3, and 4. It turns out that this pattern can be observed consistently:
When the number of cards \(n = k(k+1)/2\) is triangularTriangular numbers are the sums of consecutive
integers: 1, 1+2, 1+2+3, etc. They get their names from the fact that
they can be arranged in a triangle. (OEIS: A000217) (Image from
Wikimedia, CC-BY-SA.)
,
the Bulgarian Solitaire will converge to piles of size 1, 2, …, \(k\).
How can we prove this?
There is a nice trick: we rotate (again!) our Young diagram by 45°:

When we do a step in the Bulgarian Solitaire, we move around the boxes:

Sometimes we can see that a box (in grey here) is above an empty space, and therefore it falls down. This corresponds to cases when there is a need to reorder the piles so that they remain in order of decreasing size.
We can see that at each step, the total potential energy of the boxes either stays the same (when all boxes are still resting on other boxes or on the edges) or decreases (when a box is above an empty space and falls down). And the lowest possible potential energy is reached when the configuration is fully triangular, when boxes are spread evenly across all piles!
So now we just need to prove that all configurations will end up at this minimum of potential energy, i.e. that there is no local minimum that prevents the system from reaching this global minimum.
Imagine we are not at this triangular position. Since the total number of boxes is triangular, there is necessarily one layer with an empty space for a box, and one layer above with an additional box. At each step, the boxes will “rotate”, and the empty space and the additional box will move one space to the right. If the layer with the empty space is of size \(k\), the layer above it will be of size \(k+1\), and these numbers are relatively prime. After a certain number of steps, the additional box will be directly above the empty space and will fall down.
So no configuration that is not fully triangular is stable: after a certain number of steps, the potential energy will decrease. The detailed proof can be found in section 2 of the paper.
Simulating the Bulgarian Solitaire
Let’s simulate the process (in Python) to visualize the convergence. The script will generate all partitions of a given integer \(n\), and generate the transition graph of the Bulgarian Solitaire. If a step of the Bulgarian Solitaire transforms partition \(p_1\) into partition \(p_2\), we will draw an edge between \(p_1\) and \(p_2\).
We’ll start by defining helpful type aliases: a partition is a tuple of ints, and the graph we will generate is a dictionary from partitions to partitions.
type Partition = tuple[int, ...]
type Graph = dict[Partition, Partition]Next, we need a function that enumerates all possible partitions of a given integer \(n\). It is built recursively: for all possible prefixes \(0 < k \leq n\), the partitions we are looking for consist of \(k\) followed by all partitions of \(n-k\).
from collections.abc import Generator
def integer_partition(n: int) -> Generator[Partition]:
if n <= 0:
return
def find_partitions_recursive(target: int, current_partition: list[int]):
if target == 0:
yield current_partition
return
max_val = current_partition[-1] if current_partition else target
for i in range(min(target, max_val), 0, -1):
current_partition.append(i)
yield from find_partitions_recursive(target - i, current_partition)
# Backtrack
current_partition.pop()
yield from map(tuple, find_partitions_recursive(n, []))The step function is the Bulgarian Solitaire itself:
def step(partition: Partition) -> Partition:
next = [len(partition)] + [k - 1 for k in partition if k > 1]
return tuple(sorted(next, reverse=True))We can now compute the transition graph:
def compute_graph(n: int) -> Graph:
return {partition: step(partition) for partition in integer_partition(n)}Now for display, we generate some Graphviz code. The colors are adapted for viewing directly in the terminal.
def print_graphviz(graph: Graph) -> None:
print("""digraph {
bgcolor="transparent";
node [shape="rect", fontcolor="white", color="white"];
edge [fontcolor="white", color="white"];
""")
for k, v in graph.items():
print(f' "{k}" -> "{v}";')
print("}")To finish, let’s add a command-line interface:
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("n", type=int)
args = parser.parse_args()
graph = compute_graph(args.n)
print_graphviz(graph)
if __name__ == "__main__":
main()The script can then be called directly:
# Save the graphviz file
python bulgarian_solitaire.py 10 > graph10.dot
# Generate a PNG graph
python bulgarian_solitaire.py 10 | dot -Tpng > graph_10.png
# Display the image directly in the terminal (if it supports image display)
python bulgarian_solitaire.py 12 | dot -Tpng | viu -Here is the output for \(n=6\):
And for \(n=10\):
As we can see, all partitions indeed converge to the triangular configurations \((3,2,1)\) and \((4,3,2,1)\).
Now what happens when the number \(n\) is not triangular?
For \(n=7\):
The dynamical system converges to a cycle between \((4,2,1)\), \((3,3,1)\), \((3,2,2)\), and \((3,2,1,1)\). When we look at this cycle, we realize that it is the triangular configuration \((3,2,1)\), plus an additional box cycling above.
For \(n=8\):
Here we get two connected components in the graph. And here again, we get the triangular partition but with two additional boxes cycling above.
This property can be proved (Theorem 2 in the paper), and the number of connected components can be computed exactly for all \(n\).
Conclusion
The Bulgarian Solitaire is a nice example of a discrete dynamical system. The proof is particularly interesting because it relies on visualization and physical intuition about the “potential energy” of the system, which translates directly into the formal proof.
The paper also mentions generalizations. I am interested in particular in the stochastic Bulgarian Solitaire. Instead of taking one card from each pile at each step, we take one card from each pile independently with probability \(0<p<1\). The dynamical system becomes a Markov chain, and it would be interesting to study (even experimentally) its convergence properties.
Bonus: BQN implementation of the simulator
First, the Step function computes one step of the Bulgarian
Solitaire. It removes 1 from each pile (¯1⊸+), removes the piles
that became empty (0⊸≠), adds a pile of the original size (≠∾), and
sorts the piles for consistency (∨).
Step←∨≠∾(0⊸≠⊸/¯1⊸+)The Partitions function generates all partitions of a given
integer. Conveniently, this was already available in BQNcrate!
Partitions←{∾⊢´∾⟜(<-⟜↕∘≠{𝕨∾¨∾(-𝕨⌊≠𝕩)↑𝕩}¨⊢)⍟𝕩⋈⋈⋈↕0}We can now compute the graph, by first computing all partitions of the
input, then building the edges by calling Step on each partition.
ComputeGraph←{>(⊢⋈Step)¨Partitions𝕩}We now generate and print the graphviz code:
EdgeToGraphviz←{∾⟜(""";"∾@+10)5↓∾""" -> """⊸∾¨•Fmt¨𝕩}
graphvizheader←"bgcolor=""transparent"";
node [shape=""rect"",fontcolor=""white"",color=""white""];
edge [fontcolor=""white"",color=""white""];"
ToGraphviz←{"digraph {"∾graphvizheader∾(∾EdgeToGraphviz¨<˘𝕩)∾"}"}
n←•ParseFloat⊑•args
•Out ToGraphviz ComputeGraph nSimilarly to the Python version, we can run the script with:
bqn bulgarian_solitaire.bqn 6 | dot -Tpng > graph_6.png