🎄 20/25. Using command-line options in Perl 6 one-liners

Welcome to Day 20 of the Perl 6 One-Liner Advent Calendar! So far, we created about 25 different one-liners, but never talked about the command-line options that the Rakudo Perl 6 compiler offers to us.

-e

The first option to know when working with (Rakudo) Perl 6 is -e. It takes a string with your Perl 6 one-liner and executes it immediately.

For example, print the version of the current Perl 6 specification:

$ perl6 -e'$*PERL.version.say'
v6.c

Be careful not to use the Perl 5.10+ styled capital -E, which does the same as -e but also activates features such as say. In Perl 6, the option is always lowercase.

-n

This option repeats the code for each line of input data. This is quite handy when you want to process a file. For example, here’s a one-liner that adds up the values in a row and prints the sum:

$ perl6 -ne'say [+] .split(" ")' data.txt 

If the data.txt file contains the following:

10 20 30 40
1 2 3 4
5 6 7 8

then the result of the one-liner is:

100
10
26

There’s no difference whether you use shell’s input redirection or not; the following line also works:

$ perl6 -ne'say [+] .split(" ")' < data.txt 

Make sure you place the e option the last in the list (so, not perl6 -en'...') or split the options: perl6 -n -e'...'.

-p

This option is similar to -n, but prints the topic variable after each iteration.

The following one-liner reverses the lines in the file and prints them to the console:

$ perl6 -npe'.=flip' data.txt

For the same input file, the result will look like this:

04 03 02 01
4 3 2 1
8 7 6 5

Notice that you have to update the $_ variable, so you type .=flip. If you only have .flip, you will reverse the string, but the result will not be used and the original line will be printed.

An equivalent program with .flip and with no -p will look like this:

$ perl6 -ne'.flip.say' data.txt

After-party

Let’s go through a few one-liners from the Perl One-Liners book and create one-liners in Perl 6.


Double-space a file

$ perl6 -npe's/$/\n/' text.txt 


Remove all blank lines

$ perl6 -ne'.say if .chars' text.txt 

Depending on how you define ‘blank’, you may want another one-liner that skips the lines containing whitespaces:

$ perl6 -ne'.say if /\S/' text.txt 


Number all lines in a file

$ perl6 -ne'say ++$ ~ ". " ~ $_' text.txt

This code, probably, requires a comment. The $ variable is a state variable and it can be used without declaration.


Convert all text to uppercase

$ perl6 -npe'.=uc' text.txt


Strip whitespace from the beginning and end of each line

$ perl6 -npe'.=trim' text.txt


Print the first line of a file

$ perl6 -ne'.say ; exit' text.txt 


Print the first 10 lines of a file

$ perl6 -npe'exit if $++ == 10' text.txt 

This time, a postfix ++ was applied to $.

I hope that was a useful journey today. See you tomorrow!

🎄 19/25. Using map and Seq to compute the value of π in Perl 6

Welcome to Day 19 of the Perl 6 One-Liner Advent Calendar! Today, we will be computing the value of π using two different methods. The goal of this blog post is to play with different approaches to generate numeric sequences.

Pre-party

Of course, in Perl 6 you don’t need to calculate the value of π yourself, as the language gives us a few predefined constants in the shape of both π and pi, as well as doubled values τ and tau.

But to demonstrate the usage of maps and sequences, let’s implement one of the simplest algorithms to calculate π:

Here’s a draft code to check the answer:

my $pi = 1;
my $sign = 1;

for 1..10000 -> $k {
$sign *= -1;
$pi += $sign / ($k * 2 + 1);
}

say 4 * $pi;

Part 1

Now, let us employ map to make the solution compact. It is better to make the formula more generic, too:

And here’s our first one-liner:

say 4 * [+] (^1000).map({(-1) ** $_ / (2 * $_ + 1)})

I hope you understand everything here. We covered different parts of this solution in the previous days of this year’s Advent Calendar, for example, in the post on Day 11.

But still, I want to emphasise that you need parentheses around -1. If you type -1 ** $_, then you always get −1, as the minus prefix is applied to the result of taking power. So the correct code is (-1) ** $_.

Part 2

It is also interesting to try using a sequence operator ... to generate the row according to the formula mentioned above. Also, we’ll use rational numbers (see Day 12) to create the fractions ⅓, ⅕, etc.

say 4 * [+] <1/1>, 
{-Rat.new($^n.numerator, $^n.denominator + 2)} ...
*.abs < 1E-5;

This sequence starts with a rational number <1/1>, which is handy as we can immediately take its numerator and denominator. The generator block of the sequence uses this number to create a new rational number, whose denominator becomes greater by two on each iteration.

You may ask why I am referring $^n.numerator, which is always 1. This is needed, because to alter the sign, we need to know the sign of the current value, and the sign is kept in the numerator part of the rational value.

The placeholder variable $^n automatically takes the first (and the only) argument of the generator code block.

The sequence is generated until the absolute value of the current value becomes small enough. It may be tempting to replace the final condition with * ≅ 0, but that program will run too long to see the result, as the default tolerance of the approximately-equal operator is 10−15, while the row does not converge that fast.

Also, you cannot use the < ... / ...> syntax to create a Rat number in the generator:

{ <$^n.numerator / $^n.denominator + 2> }

In this case, Perl 6 will treat this as a quoting construction such as <red green blue>, and instead of the code block you get a list of strings.

And that’s all for today. See you tomorrow!

🎄 18/25. Renaming files with Perl 6

Welcome to Day 18 of the Perl 6 One-Liner Advent Calendar! Today, there will be a true one-liner, in the sense that you run it from the terminal as a devops.

Our task is to rename all the files passed in the command-line arguments and give the files sequential numbers in the preferred format. Here is an example of the command line:

$ perl6 rename.pl *.jpg img_0000.jpg

In this example, all image files in the current directory will be renamed to img_0001.jpg, img_0002.jpg, etc.

And here’s the possible solution in Perl 6:

@*ARGS[0..*-2].sort.map: *.Str.IO.rename(++@*ARGS[*-1])

The pre-defined dynamic variable @*ARGS contains the arguments from the command line. In the above example, the shell unrolls the *.jpg mask to a list of files, so the array contains them all. The last element is the renaming sample img_0000.jpg.

Notice that unlike Perl 5, the variable is called ARGS, not ARGV.

To loop over all the files (and skipping the last file item with the file mask), we are taking the slice of @*ARGS. The 0..*-2 construct creates a range of indices to take all elements except the last one.

Then the list is sorted (the original @*ARGS array stays unchanged), and we iterate over the file names using the map method.

The body of map contains a WhateveCode block; it takes the string representation of the current value, makes an IO::Path object out of it, and calls the rename method. Notice that the IO method creates an object of the IO::Path class; while a bare IO is a role in the hierarchy of the Perl 6 object system.

Finally, the increment operator ++ changes the renaming sample (which is held in the last, *-1st, element of @*ARGS). When the operator is applied to a string, it increments the number part of it, so we get img_0001.jpg, img_0002.jpg, etc.

I hope that Perl 6 will not be ever called a star-noise language 🙂 Nevertheless, tomorrow there will be another short story about Perl 6!

🎄 17/25. Playing with prime numbers in Perl 6

Welcome to Day 17 of the Perl 6 One-Liner Advent Calendar! Today, we’ll have two one-liners, both generating some prime numbers.

Part 1

First, let us solve Problem 7 of Project Euler, where you need to print the 10001st number (having the first being 2).

Perl 6 is good at prime numbers, as it has a built-in method of the Int class, is-prime.

There are a few ways of generating prime numbers. For one-liners, the best is the simplest, but the least efficient, method that tests every number.

say ((1..*).grep: *.is-prime)[10000]

It takes about half-a-minute to compute the result, but the code is quite short. Someday, we’ll solve the task using the so-called sieve of Eratosthenes, which should be much faster, but will probably require more lines of code.

Part 2

In the second part of this advent post, let us play golf and solve the corresponding problem on the code-golf.io site. We need to print all prime numbers below 100.

My solution, which needs 22 characters, is the following:

.is-prime&&.say for ^Ⅽ

There is no shorter solution in Perl 6, while in J, they managed to have only 11 characters. In Perl 6, eight characters are consumed by the method name already. I believe, to win all golf contests, you need a special language with very short names (which J is) and a set of built-in routines to generate lists of prime, or Fibonacci, or any other numeric sequence. It should also strongly utilise Unicode character space.

In our Perl 6 example, there is also a Unicode character, . This not a simple C, the third letter from the Latin alphabet, but a Unicode character ROMAN NUMERAL ONE HUNDRED (which is originally the third letter of the Latin alphabet, of course). Using this symbol let us save two characters in the solution.

The && trick is possible because Perl does not execute the second part of the Boolean expression if the first operand is False. Notice that you cannot use a single & here. The full non-optimised version of the code would need additional spaces and would look like this:

.say if .is-prime for ^100

And that’s the end of today’s Perl 6 journey, see you tomorrow!

🎄 16/25. Distance between two points in Perl 6

Welcome to Day 16 of the Perl 6 One-Liner Advent Calendar! Today, we’ll solve a simple problem and will find the distance between two points on a surface.

Here’s an illustration to help to formulate the task. Our goal is to find the distance between the points A and B.


To make the answer more transparent and easy to check, I chose the line AB so that it is a hypotenuse of a right triangle with sides 3 and 4. The length of the third side will be 5 in this case.

So, here’s the solution:

say abs(5.5+2i - (1.5+5i))

The code uses complex numbers, and as soon as you move to a complex plane, you gain from the fact that the distance between two points on the surface equals to the absolute result of subtraction of these two numbers from one another.

One of the points, in this case, is the point 5.5+2i on a complex plane, and the second point is 1.5+5i. In Perl 6, you write complex numbers as you do in mathematics.

Without the built-in support of complex numbers, you would have to use Pythagorean theorem explicitly:

say sqrt((5.5 - 1.5)² + (2 - 5)²)

Homework. Modify Rakudo’s grammar to allow the following code:

say √((5.5 - 1.5)² + (2 - 5)²)

And that’s all for today. Come again tomorrow to read about another Perl 6 one-liner or two!

🎄 15/25. Playing with Fibonacci numbers in Perl 6

Welcome to Day 15 of the Perl 6 One-Liner Advent Calendar! Today, there will be two one-liners, and they both generate Fibonacci numbers.

Yes, most likely, you never used such numbers in real code, and, again, most likely, you solved many educating problems with them. Nevertheless, today, let’s solve the Problem 25 of the Project Euler and try to approach the shortest solution for a Fibonacci problem on the Code-Golf.io site.

Pre-party

How do we form a Fibonacci sequence in Perl 6? With a sequential operator ...:

0, 1, * + * ... *

If you want some exotic flavour in the code, you can replace the last star with either Inf or . In any case, the result will be a lazy sequence of the Seq type. Perl 6 will not compute it immediately (and it can’t, as the right edge is infinite).

Part 1

The first task is to find the index of the first Fibonacci number, which has 1000 digits. Of course, you can loop over the above-created sequence and trace the index yourself. But in Perl 6, there is an option to modify the grep routine family, and ask it to return the index of the matched item instead of the item itself.

Also, instead of grep, we’ll use a more appropriate method, first. It will return the first matching item or its index if we call the method with the k key. It is enough just to mention the key, no value is really needed.

say (0, 1, * + * ... *).first(*.chars >= 1000, :k)

This program prints a single integer number, which is the correct answer to the given problem.

Part 2

Now let’s solve a Golf task and print the first 30 Fibonacci numbers, one on a line. This time, we have to use as few characters in the code as possible.

The first approach is rather wordy (even with ^31 instead of 0..30 it needs 33 characters):

.say for (0, 1, * + * ... *)[^31]

There is some room that allows compression. Of course, the first and the most obvious thing is to remove spaces (remaining 28 characters):

.say for (0,1,*+*...*)[^31]

Another interesting trick is to use the >> meta-operator to call the say method on each element of the sequence. It compresses the code further to 24 characters:

(0,1,*+*...*)[^31]>>.say

At this moment, we can employ some Unicode, and gain three more characters (21 left):

(0,1,*+*…*)[^31]».say

Looks compact already, but there are still some options to try. Let us get rid of the explicit slicing, and try to stop the sequence at the last element:

(0,1,*+*…*>514229)».say

The code became longer (23 characters), but we do not need an exact number 514229 here. It is enough to give some number, which is bigger then the 29th and smaller then the 30th element of the sequence. For example, it can be 823543, which is 7 powered 7. Write it down using superscripts (19 characters):

(0,1,*+*…*>7⁷)».say

Finally, it is possible to make it one character less by using another Unicode character representing 800000 in a single character. Not every (if any) font can display something visually attractive, but Perl 6 takes the character with no complains:

(0,1,*+*…*>𐧴)».say

These 18 characters is one character longer than the top result at Gode-Golf. I have a feeling that you could gain another character by replacing the first two elements of the sequence with ^2, but that does not work in current Rakudo, and you have to return a character to flatten the list: |^2, which makes the solution 18 characters long again.

The desire is to remove the *> part in the condition to stop the sequence and replace it with a fixed number. Unfortunately, there’s no way to express 832040 with powers of the numbers between 1 and 90. Would that be possible, we could use a superscript to calculate the number.

Another idea is to use a regex, but we need at least four characters, which does not help:

(0,1,*+*…/20/)».say

And let me stop here for today. Would you happen to create a shorter solution, please leave a comment. See you tomorrow!

🎄 14/25. Another solution of yesterday’s problem

Welcome to Day 14 of the Perl 6 One-Liner Advent Calendar! Today, we are presenting another solution of the problem we were solving yesterday. The task was to count all Sundays that fall on the first of the month in the XX century.

Yesterday, we just scanned through all the days in the whole century, selecting the ones that are Sundays (.day-of-week == 7) and are the first of the month (.day == 1).

It is possible to make a more efficient algorithm. As we are only interested in the first days of the month, there is no need to scan all 36525 days during 100 years, but only 1200 days that are the first day of each month between 1901 and 2000.

So, we need two nested loops: over the years and over the months. Do we need two fors? Not necessarily. Let’s use the X operator; we are familiar with it from the previous advent posts.

And here is our today’s one-liner:

(gather for 1901..2000 X 1..12 {
take Date.new(|@_, 1)
}).grep(*.day-of-week == 7).elems.say;

The for 1901..2000 X 1..12 loop goes over each month in the XX century. For each, we create a Date object by calling a constructor with three arguments.

Update. Based on the very relevant comments from the readers, here’s a better and simpler way of the solution:

say +(1901..2000 X 1..12).map(
{Date.new(|@_, 1)}
).grep(*.day-of-week == 7);

Notice that inside the loop, you can use both $_[0] and $_[1], and @_[0] and @_[1]. In the first case, the $_ variable contains a list of two elements, while in the second it is an array @_. The shortest code is achieved if you are just using a dot to call a method on the topic (default) variable: .[0] and .[1].

Instead of Date.new(|@_, 1), you can type Date.new(.[0], .[1], 1). The |@_ syntax is used to unroll the array, as otherwise Perl 6 will think you are passing an array as a first argument.

All first days of the months are collected to a sequence with the help of the gathertake pair.

The final step is a grep as yesterday, but this time we only need to select Sundays, so a single *.day-of-week == 7 condition is enough.

The call of the elems method returns the number of the elements in the list, which is the number of Sundays that we are looking for. Thus, print it with say.

A new idea and a new solution should make you happy for at least one day until tomorrow’s post in this One-Liner Calendar!