Shunting yards are depots where freight trains can be split into parts and reassembled, usually near railway junctions, industrial areas and ports.

shunting yard
Godorf Station shunting yard (Cologne, Germany).

Rail freight transport has undergone a dramatic scale-up since the 1960s. Where only one or just a few freight wagons used to be sufficient for a single delivery, block trains nowadays run in their entirety from origin to destination. This means shunting along the way is no longer necessary. Together with a general decline in rail freight transport, this has led to a worldwide closure of many shunting yards.

Assignment

We model a shunting yard based on the drawing below. There is a shunting zone on the left side of the yard, with a maximum capacity of $$c$$ carriages. There is always a locomotive on the far left of the shunting zone track, behind which there is space for up to $$c - 1$$ wagons. There are two or more wagon stands on the right side of the yard, numbered from top to bottom starting from zero. The track of each stand has a maximum capacity of $$c$$ carriages. Wagons are always parked against each other on the far right side of a stand. The shunting zone is connected to each stand via a system of switches, which can be used to change the composition of the wagons behind the locomotive.

shunting yard
Model of a shunting yard. There is a shunting zone on the left side of the yard, with a maximum capacity of $$c$$ carriages. There is always a locomotive on the far left of the shunting zone track, behind which there is space for up to $$c - 1$$ wagons. There are two or more wagon stands on the right side of the yard. The tracks of those stands are numbered from top to bottom, starting from zero. The track of each stand has a maximum capacity of $$c$$ carriages. Wagons are always parked against each other on the right side of a stand. The shunting zone is connected to each stand via a system of switches, which can be used to change the composition of the wagons behind the locomotive.

Using the switches it is possible to pick up $$n$$ wagons from a stand $$i$$. In doing so, the first $$n$$ wagons (to the left) of the stand are moved to the rear (to the right) of the shunting zone, without changing the order of the wagons. Naturally, the number of wagons that are picked up cannot exceed the number of wagons parked at stand $$i$$. The maximum capacity $$c$$ of the shunting zone also cannot be exceeded, taking into account it already contained the locomotive and possibly some wagons as well.

Using the switches it is also possible to shelve $$n$$ wagons at a stand $$i$$. In doing so, the last $$n$$ wagons (to the right) of the shunting zone are moved to the front (to the left) of stand $$i$$, without changing the order of the wagons. Naturally, the number of wagons that are shelved cannot exceed the number of wagons on the shunting zone. The maximum capacity $$c$$ of stand $$i$$ also cannot be exceeded, taking into account it may already contain some wagons.

Define a class ShuntingYard that allows to change the composition of the wagons behind a locomotive, on a shunting yard designed according to the above model. When creating a new shunting yard (ShuntingYard), a list (list) must be passed containing the wagons parked on the stands. The stands are listed from top to bottom, and the length of the list corresponds to the number of stands on the shunting yard. The number of wagons parked on a stand — and their order — is represented as a string (str) of uppercase letters, where each letter represents one wagon. It is possible that multiple wagons on the shunting yard are represented by the same uppercase letter. There is also an optional parameter locomotive that takes a similar string (str) to indicate how many wagons — and in what order — are behind the locomotive on the shunting zone. If no value is explicitly passed to this parameter, there are no wagons behind the locomotive. There is also an optional parameter capacity that sets the maximum capacity $$c$$ (int; default value: 10) of the shunting zone and the stands. If the newly created shunting yard would have less than two stands, or if the capacity of the shunting zone or one of the stands would be exceeded, an AssertionError must be raised with the message invalid configuration.

If a shunting yard $$s$$ (ShuntingYard) is passed to the built-in function repr, it must return a string (str) that reads as expression to create a new shunting yard (ShuntingYard) with the same configuration as the current configuration of shunting yard $$s$$. Values must explicitly be passed to the named parameters locomotive and capacity.

If a shunting yard $$s$$ (ShuntingYard) is passed to the built-in function str, it must return a string representation (str) of shunting yard $$r$$. A black square (■) indicates the track segment containing the locomotive, an uppercase letter a track segment containing a wagon, and a horizontal line (━) an empty track segment (with the length of one carriage). Specific representations are used for the switch at the top stand (┳), the switch at the bottom stand (┗) and switches at the intermediate stands (┣). The empty zone to the left of stands 1, 2, … is filled with spaces.

If a shunting yard $$s$$ (ShuntingYard) is passed to the built-in function len, it must return the total number of wagons (int) parked on the stands of the shunting yard. The wagons that are behind the locomotive in the shunting zone should not be included in the total number.

In addition, it must be possible to call at least the following methods on a shunting yard $$s$$ (ShuntingYard):

Example

>>> yard = ShuntingYard(['OACHF', 'HJN', 'XGWCU', 'DF'], 'CMP')
>>> yard
ShuntingYard(['OACHF', 'HJN', 'XGWCU', 'DF'], locomotive='CMP', capacity=10)
>>> print(yard)
■CMP━━━━━━┳━━━━━OACHF
          ┣━━━━━━━HJN
          ┣━━━━━XGWCU
          ┗━━━━━━━━DF
>>> len(yard)
15
>>> yard.pick(3, 6)
Traceback (most recent call last):
AssertionError: invalid action
>>> yard.pick(3, 2)
ShuntingYard(['OACHF', 'HJN', 'CU', 'DF'], locomotive='CMPXGW', capacity=10)
>>> print(yard)
■CMPXGW━━━┳━━━━━OACHF
          ┣━━━━━━━HJN
          ┣━━━━━━━━CU
          ┗━━━━━━━━DF
>>> len(yard)
12
>>> yard.pick(4, 0)
Traceback (most recent call last):
AssertionError: invalid action
>>> yard.pick(3, 0)
ShuntingYard(['HF', 'HJN', 'CU', 'DF'], locomotive='CMPXGWOAC', capacity=10)
>>> print(yard)
■CMPXGWOAC┳━━━━━━━━HF
          ┣━━━━━━━HJN
          ┣━━━━━━━━CU
          ┗━━━━━━━━DF
>>> len(yard)
9
>>> yard.shelve(8, 1)
Traceback (most recent call last):
AssertionError: invalid action
>>> yard.shelve(7, 1)
ShuntingYard(['HF', 'PXGWOACHJN', 'CU', 'DF'], locomotive='CM', capacity=10)
>>> print(yard)
■CM━━━━━━━┳━━━━━━━━HF
          ┣PXGWOACHJN
          ┣━━━━━━━━CU
          ┗━━━━━━━━DF
>>> len(yard)
16
>>> yard.shelve(3, 3)
Traceback (most recent call last):
AssertionError: invalid action
>>> yard.pick(4, 1).shelve(2, 3).shelve(3, 2).pick(4, 1)
ShuntingYard(['HF', 'JN', 'MPXCU', 'GWDF'], locomotive='COACH', capacity=10)
>>> print(yard)
■COACH━━━━┳━━━━━━━━HF
          ┣━━━━━━━━JN
          ┣━━━━━MPXCU
          ┗━━━━━━GWDF
>>> len(yard)
13

>>> ShuntingYard(['ABC', 'DEF'])
ShuntingYard(['ABC', 'DEF'], locomotive='', capacity=10)
>>> ShuntingYard(['ABC'])
Traceback (most recent call last):
AssertionError: invalid configuration
>>> ShuntingYard(['ABC', 'DEFGHI'], capacity=4)
Traceback (most recent call last):
AssertionError: invalid configuration
>>> ShuntingYard(['ABC', 'DEF'], locomotive='GHIJ', capacity=4)
Traceback (most recent call last):
AssertionError: invalid configuration