When a Wolfram expert provides a solution to one of my questions, it is often an elegant bit of programming that makes use of advanced features of the Wolfram Language... at least advanced from my relative novice perspective. I pull apart such a solution in an effort to learn from it. I'll deconstruct this repeating decimal solution here not only so that I will learn new techniques, but so that other novices may benefit as well.
I asked how a number could be output as a repeating decimal with an line over the repeating part. The solution (by Gianluca Gorni with a slight tweak by Sander Huisman) was this:
normalizeDigitSequence =
{{{beforeRecurring___, {recurring__}}, c_?NonPositive} :>
{{0, beforeRecurring, {recurring}}, c + 1},
{{beforeRecurring___, {recurring__}}, c_?Positive} /;
Length[{beforeRecurring}] < c :>
{{beforeRecurring, First[{recurring}],
RotateLeft[{recurring}]}, c}};
addOverlineToRepeating =
{{beforeRecurring___, {recurring___}}, c_?Positive} /;
Length[{beforeRecurring}] >= c :>
Row[Append[Insert[{beforeRecurring}, ".", c + 1],
OverBar[Row[{recurring}]]]];
RealDigits[13/7] //. normalizeDigitSequence /. addOverlineToRepeating
Here is an explanation of this gem:
First, the input into this whole thing is a number wrapped in RealDigits[], which outputs a list of two items: {list of digits, a decimal offset}. RealDigits[2] outputs {{2}, 1}, and RealDigits[.2] outputs {{2}, 0}. The list of digits can also contain a list of repeating digits, so RealDigits[8/3] outputs {{2, {6}}, 1}, and RealDigits[13/7] outputs {{1, {8, 5, 7, 1, 4, 2}}, 1}.
The solution has three parts.
normalizeDigitSequence turns the RealDigits list into a list suitable for the output number.
This is accomplished by assigning a delayed replacement rule, :>, to the variable normalizeDigitSequence. A delayed replacement rule happens whenever the left side is used later in code.
{{beforeRecurring___, {recurring__}}, c_} :>
{{0, beforeRecurring, {recurring}}, c + 1}
This means put a 0 at the front of the list of non-repeating digits and add 1 to the decimal point offset. But that should only happen when c is less than 1, so c_ is written as c_?NonPositive.
A second delayed replacement rule takes care situations where c is positive:
{{beforeRecurring___, {recurring__}}, c_?Positive} :>
{{beforeRecurring, First[{recurring}], RotateLeft[{recurring}]}, c}
This means copy the first digit from the list of repeating digits to the end of the list of non-repeating digits and then rotate the order of the list of repeating digits so that {1, 2, 3} becomes {2, 3, 1}. But this should only happen when length of the list of non-repeating digits is less than c, the decimal offset. That's why there is a condition on this rule:
/; Length[{beforeRecurring}] < c
addOverlineToRepeating is the second variable assigned to a delayed replacement rule. The rule only applies when c is positive and also has a conditional that restricts the replacement to cases when the length of the non-repeating digit list is greater than or equal to c:
{{beforeRecurring___, {recurring___}}, c_?Positive} /;
Length[{beforeRecurring}] >= c :>
Row[Append[Insert[{beforeRecurring}, ".", c + 1],
OverBar[Row[{recurring}]]]]
This says make a list of the non-repeating digits, put a decimal point at location c in this list, put an overbar on the list of repeating digits, and then join the two lists. Row[] turns this list of items (digits, a decimal, and overlined digits) into a continuous number.
RealDigits[13/7] //. normalizeDigitSequence /. addOverlineToRepeating
This applies all the replacement rules to some number. But there is something subtle going on here, too. Notice that the first replacement is //. instead of /.? The first one means replace repeatedly. It will keep applying the replacement rules over and over until the output stops changing. That is important here because there may be more than one digit that needs to be copied from the repeating digit list to the non-repeating list, or more than one zero that needs to precede the repeating decimals.
In the end, we have a perfect solution to the problem (as far as my needs are concerned, anyway) that is both elegant and compact.
Here are a few things I learned from this solution:
- a = b :> c is a way to save replacement rules for later use, very handy.
- a_?Positive is a way to restrict a pattern, “Positive” possibly being any conditional
- a_ /; b > c :> d is a way to selectively apply a replacement rule
- Row[{1, 2, 3}] is a way to reconstitute a number (for display purposes)
- a //. _ ? b is a way to apply the same replacement over and over
Wow! I love how depth and complexity comes from such simple Wolfram syntax!
I also learned that I should check Stack Overflow and Stack Exchange before asking a question in the Wolfram Community. If I had, though, I wouldn't have seen Gianluca's marvelous solution. But in the future I will check SO and SE first.
I hope this helps someone. It helped me. : )
Mark