Editorial for Lyndon's Golf Contest 1 P4 - Symbolic Ruby


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.
Submitting an official solution before solving the problem yourself is a bannable offence.

Author: Dingledooper

23 bytes

Let's for a second ignore the constraint that only symbolic characters are allowed. We can read the first line using gets, and find its length with .size:

p gets.size

Now, let's slowly try to convert our program into one that is fully symbolic, starting with the printing. One familiar with Ruby may be aware of Ruby's built-in $stdout object, as well as its convenient alias, $>. This object represents the STDOUT stream, and we can write to it via the << operator. However, to pass the identical checker, we also need to append a \n to the stream. Luckily, the input record separator $/ happens to already have a default value of "\n". This becomes:

$><<gets.size<<$/

Next, let's look at gets. Recall from earlier that the alias for STDOUT is $>. As you might have guessed, there is a corresponding STDIN object, whose alias is $<. In general, we could read its input by calling the .read method, but this won't do for our symbolic-only program. Instead, we can destructure the stream via [*$<], which converts it to a list of lines. Since there is only one line of input, we just have to grab the first element. There are several ways to do this, but one approach is to use the * operator to "join" the array like so: [*$<]*''. An important thing to note is that [*$<] preserves the trailing newline in the input, meaning that the string is actually one longer than it should be. We can temporarily fix this by subtracting 1 from the size, which leads to the following solution:

$><<([*$<]*'').size-1<<$/

Finally, we must somehow get rid of .size. This is where regex comes in handy. The =~ operator matches a string with a regular expression, returning the index of the first match. To get the length of a string, all we need to do is construct a regular expression that matches the very end of our string. It happens that the $ (end-of-line) token perfectly fits our needs. If we have a string, say "abc\n", then "abc\n"=~/$/ will return 3, since it returns the position right before the newline. With this final piece of information, we can solve the problem, symbolic-only, in 23 bytes:

$><<([*$<]*''=~/$/)<<$/

20 bytes

In the 23-byte solution above, we read the input by destructuring $< into a list, and join it to get the string inside. Let's explore another method to read the input, one that may look familiar to Python programmers:

_,=*$<;$><<(_=~/$/)<<$/

The right side of the assignment, *$<, destructures the input stream into a list like before, while the left side unpacks the list by storing its first element in _. Unfortunately, this solution reaches the same byte count as before. However, there is one final trick to this approach that allows us to get 20 bytes: $_. $_ is a magic variable that commonly appears in string operations allowing for shorter code constructs. For example, the ~ operator, when applied to a regular expression, matches it to the string stored in $_. In other words, $_=~/REGEX/ is equivalent to ~/REGEX/. This happens to be exactly what we need to save the last 3 bytes in our solution:

$_,=*$<;$><<~/$/<<$/

19 bytes [*]

Although not necessary to score full points, a 19-byte solution was found during the contest by JoKing:

$><</$/.=~(*$<)<<$/

Ruby's dot (.) notation allows an operator to act like a function. At first, this may seem useless, but it allows us to pass $< as an argument via splatting, which also elegantly handles the list-to-string conversion.


Comments


  • 0
    jklm  commented on Nov. 23, 2022, 7:35 a.m.

    Don't gets preserve the trailing newline in ruby ? Is that different on dmoj ? Does not change the result but you can also use $' after the regex to reuse \n on the input