🎄 22/25. Reversing a file with Perl 6

Welcome to Day 22 of the Perl 6 One-Liner Advent Calendar! Today, we will continue working with files, and the goal for today is to create a one-liner to print the lines of a text file in reversed order (as tail -r does it).

The first one-liner does the job with the STDIN stream:

.say for $*IN.lines.reverse

Run the program as:

$ perl6 reverse.pl < text.txt

Update. Thanks to the reader comment, we can gain from the fact that $*IN can be omitted in this case, which makes the one-liner even shorter:

.say for lines.reverse

If you want to read the files directly from Perl 6, modify the program a bit to create a file handle out of the command-line argument:

.say for @*ARGS[0].IO.open.lines.reverse

Now you run it as follows:

$ perl6 reverse.pl text.txt

It is important to remember that the default behaviour of the lines method is to exclude the newline characters from the final sequence of lines (the method returns a Seq object, not an array or a list). It may be opposite to what you are used to when working with Perl 5. Using chomp is quite a common practice there.

In Perl 6, the lines method splits the lines based on the value stored in the .nl-in attribute of the IO::Handle object.

You can look at the current value of the line separators with the following tiny script:

dd $_ for @*ARGS[0].IO.open.nl-in

This is what you find there by default:

$["\n", "\r\n"]

The interesting thing is that you can control the behaviour of lines and tell Perl not to exclude the newline characters:

@*ARGS[0].IO.open(chomp => False).lines.reverse.put

The chomp attribute is set to True by default. You can also change the default separator:

@*ARGS[0].IO.open(
  nl-in => "\r", chomp => False
).lines.reverse.put

Notice that without chomping, you do not need an explicit for loop over the lines: in the last two one-liners, the .put method is called directly on the sequence object. In the earlier versions, the strings did not contain the newline characters, and thus they would be printed as a single long line.

I will leave you today with some small homework: Tell the difference between put and say.

Till tomorrow!

🎄 21/25. Merging files horizontally in Perl 6

Welcome to Day 21 of the Perl 6 One-Liner Advent Calendar! Only a few days left until the end of this season of advent calendars, so let’s pack as many things as possible in the remaining days, and today we’ll merge a few files into a single file 🙂

Our today’s goal is to take two (or three, or more) files and copy their contents line by line. For example, we want to merge two log files, knowing that all their lines correspond to each other.

File a.txt:

2018/12/20 11:16:13
2018/12/20 11:17:58
2018/12/20 11:19:18
2018/12/20 11:24:30

File b.txt:

"/favicon.ico" failed (No such file)
"/favicon.ico" failed (No such file)
"/robots.txt" failed (No such file)
"/robots.txt" failed (No such file)

Our first one-liner illustrates the idea:

.say for [Z~] @*ARGS.map: *.IO.lines;

It is assumed that the program is run as follows:

$ perl6 merge.pl a.txt b.txt

For each filename (@*.ARGS.map) in the command line, an IO::Path object is created (.IO), and the lines from the files are read (.lines).

In the case of two files, we have two sequences, which are then concatenated line by line using the zip meta-operator Z applied to a concatenation infix ~.

After that step, we get another sequence, which we can print line by line (.say for).

2018/12/20 11:16:13"/favicon.ico" failed (No such file)
2018/12/20 11:17:58"/favicon.ico" failed (No such file)
2018/12/20 11:19:18"/robots.txt" failed (No such file)
2018/12/20 11:24:30"/robots.txt" failed (No such file)

The result is formally correct, but let’s add a space between the original lines. Here is an updated version of the one-liner:

.trim.say for [Z~] @*ARGS.map: *.IO.lines.map: *~ ' '

Here, a space character is appended to the end of each line (.map: *~ ' '), and as there will be one extra space at the end of the combined line, it is removed by the trim method. Its sibling, trim-trailing, could be used instead (or a regex if you care about original trailing spaces happened to be in the second file).

With the above change, the files are perfectly merged now:

2018/12/20 11:16:13 "/favicon.ico" failed (No such file)
2018/12/20 11:17:58 "/favicon.ico" failed (No such file)
2018/12/20 11:19:18 "/robots.txt" failed (No such file)
2018/12/20 11:24:30 "/robots.txt" failed (No such file)

There’s no problem to merge the same file to itself, or to provide more than two files, for example:

$ perl6 merge.pl a.txt a.txt a.txt

That was it for today, come again tomorrow!

🎄 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!