Let’s make those ideas more concrete by implementing them. First we
need to be able to ask a tag system what characters it uses:
class
TagRule
def
alphabet
(
[
first_character
]
+
append_characters
.
chars
.
entries
)
.
uniq
end
end
class
TagRulebook
def
alphabet
rules
.
flat_map
(
&
:alphabet
)
.
uniq
end
end
class
TagSystem
def
alphabet
(
rulebook
.
alphabet
+
current_string
.
chars
.
entries
)
.
uniq
.
sort
end
end
We can test this on the number-incrementing tag system from
Tag Systems
.TagSystem#alphabet
tells us that this system
uses the charactersa
,b
,c
, andd
:
>>
rulebook
=
TagRulebook
.
new
(
2
,
[
TagRule
.
new
(
'a'
,
'ccdd'
),
TagRule
.
new
(
'b'
,
'dd'
)
]
)
=> #
>>
system
=
TagSystem
.
new
(
'aabbbb'
,
rulebook
)
=> #
>>
system
.
alphabet
=> ["a", "b", "c", "d"]
Next we need a way of encoding each character as a string that the
cyclic tag system can use. There’s a specific encoding scheme that makes
the simulation work: each character is represented as a string of0
s with the same length as the alphabet, with a
single1
character in a position that
reflects that character’s position in the alphabet.
[
62
]
Our tag system has a four-character alphabet, so each letter gets
encoded as a four-character string with a1
in a different place:
Tag system character | Position in alphabet | Encoded representation |
a | 0 | 1000 |
b | 1 | 0100 |
c | 2 | 0010 |
d | 3 | 0001 |
To implement this encoding scheme, we’ll introduce aCyclicTagEncoder
that gets constructed with a specific alphabet and then asked to
encode strings of characters from that alphabet:
class
CyclicTagEncoder
<
Struct
.
new
(
:alphabet
)
def
encode_string
(
string
)
string
.
chars
.
map
{
|
character
|
encode_character
(
character
)
}
.
join
end
def
encode_character
(
character
)
character_position
=
alphabet
.
index
(
character
)
(
0
.
.
.
alphabet
.
length
)
.
map
{
|
n
|
n
==
character_position
?
'1'
:
'0'
}
.
join
end
end
class
TagSystem
def
encoder
CyclicTagEncoder
.
new
(
alphabet
)
end
end
Now we can use our tag system’sCyclicTagEncoder
to encode any strings made up
ofa
,b
,c
, andd
:
>>
encoder
=
system
.
encoder
=> #
>>
encoder
.
encode_character
(
'c'
)
=> "0010"
>>
encoder
.
encode_string
(
'cab'
)
=> "001010000100"
The encoder gives us a way to convert each tag system rule into a
cyclic tag system rule. We just encode theappend_characters
of aTagRule
and use the resulting string to build aCyclicTagRule
:
class
TagRule
def
to_cyclic
(
encoder
)
CyclicTagRule
.
new
(
encoder
.
encode_string
(
append_characters
))
end
end
Let’s try that on a singleTagRule
:
>>
rule
=
system
.
rulebook
.
rules
.
first
=> #
>>
rule
.
to_cyclic
(
encoder
)
=> #
Alright, so theappend_characters
have been converted,
but now we’ve lost the information about whichfirst_character
is supposed to trigger the rule—everyCyclic
TagRule
is triggered by the character1
regardless of whichTagRule
it was converted from.
Instead, that information is communicated by the
order
of the rules in the cyclic tag system: the
first rule is for the first character in the alphabet, the second rule is
for the second character, and so on. Any character without a corresponding
rule in the tag system gets a blank rule in the cyclic tag system
rulebook.
We can implement aTagRulebook#cyclic_rules
method to return the
converted rules in the right order:
class
TagRulebook
def
cyclic_rules
(
encoder
)
encoder
.
alphabet
.
map
{
|
character
|
cyclic_rule_for
(
character
,
encoder
)
}
end
def
cyclic_rule_for
(
character
,
encoder
)
rule
=
rule_for
(
character
)
if
rule
.
nil?
CyclicTagRule
.
new
(
''
)
else
rule
.
to_cyclic
(
encoder
)
end
end
end
Here’s what#cyclic_rules
produces for our tag system:
>>
system
.
rulebook
.
cyclic_rules
(
encoder
)
=> [
#
, #
, #
, #
]
As expected, the converteda
andb
rules appear first, followed by two
blank rules in thec
andd
positions.
This arrangement dovetails with the character encoding scheme to
make the whole simulation work. If the simulated tag system’s input string
is the single characterb
, for
instance, it will appear as0100
in the
input string of the cyclic tag system. Watch what happens when the system
runs with that input:
Current string | Current rule | Rule applies? |
0100 | append the characters0010001000010001 (a rule) | no |
100 | append the characters00010001 (b rule) | yes |
00 00010001 | append nothing (c rule) | no |
000010001 | append nothing (d rule) | no |
⋮ | ⋮ | ⋮ |
On the first step of computation, the converteda
rule
is current, and doesn’t get used because the current string begins with a0
. But on the second step, theb
rule becomes current just as the leading0
is deleted from the current string, revealing a leading1
that triggers the rule. The next two characters are both0
,
so thec
andd
rules
don’t get used either.
So, by carefully timing the appearances of the character1
in the input string to coincide with the
rotating appearances of rules in the cyclic tag system, we can trigger the
right rules at the right times, perfectly simulating the
character-matching behavior of conventional tag system rules.
Finally, we need to simulate the deletion number of the original tag system, but that’s
easily done by inserting extra empty rules into the cyclic tag system’s rulebook so that the
right number of characters get deleted after a single encoded character has been successfully
processed. If the original tag system hasn
characters in its alphabet,
then each character of the original system’s string is represented asn
characters in the cyclic tag system’s string, so we needn
blank rules for
every additional simulated character that we want to delete:
class
TagRulebook
def
cyclic_padding_rules
(
encoder
)
Array
.
new
(
encoder
.
alphabet
.
length
,
CyclicTagRule
.
new
(
''
))
*
(
deletion_number
-
1
)
end
end
Our tag system has a four-character alphabet and a deletion number
of 2, so we need an extra four empty rules to delete one simulated
character in addition to the one that already gets deleted by the
converted rules:
>>
system
.
rulebook
.
cyclic_padding_rules
(
encoder
)
=> [
#
, #
, #
, #
]