Perl Weekly Challenge 179: Ordinal Numbers and Unicode Sparkline

These are some answers to the Week 179 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few of days from now (on Aug. 28, 2022 at 23:59). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 1: Ordinal Number Spelling

You are given a positive number, $n.

Write a script to spell the ordinal number.

For example,

11 => eleventh
62 => sixty-second
99 => ninety-ninth

Hum, this task is not very interesting, since it has more to do with English than with computer science. I’m not going to enumerate dozens of numeral or ordinal English names. So, contrary to what I usually do, I’ll use an off-the-shelf module to complete this task.

Ordinal Number Spelling in Raku

Here, we use the Lingua::EN::Numbers Raku module:

use Lingua::EN::Numbers;

for 11, 62, 99 -> $num {
    say "$num => ", ordinal($num);
}

This program displays the following output:

$ raku ./ordinal_numbers.raku
11 => eleventh
62 => sixty-second
99 => ninety-ninth

Ordinal Number Spelling in Perl

The Perl module we’re going to use is also called Lingua::EN::Numbers, but it’s not the same package as the Raku module used above.

use strict;
use warnings;
use feature qw/say/;

use Lingua::EN::Numbers qw/num2en_ordinal/;

for my $num (11, 62, 99) {
    say "$num => ", ordinal($num);
}

This program displays the following output:

$ perl ./ordinal_numbers.pl
11 => eleventh
62 => sixty-second
99 => ninety-ninth

Task 2: Unicode Sparkline

You are given a list of positive numbers, @n.

Write a script to print sparkline in Unicode for the given list of numbers.

Here, I regret to have to say that my friend Mohammad has been a bit sloppy, since he did not explain anything about sparklines (I had never heard about sparklines before). A sparkline is a very small graph of successive values laid out horizontally where the height of the line is proportional to the values in succession. See this Wikipedia Page for additional information.

The Rosetta Code Web site provides additional information about Unicode sparklines:

Use the following series of Unicode characters to create a program that takes a series of numbers separated by one or more whitespace or comma characters and generates a sparkline-type bar graph of the values on a single line of output.

The eight characters: ‘▁▂▃▄▅▆▇█’ (Unicode values U+2581 through U+2588).

We have eight Unicode characters of growing size, so the problem essentially boils down to scaling the input sequence to eight steps.

Unicode Sparkline in Raku

my @bars = map {.chr}, 0x2581 .. 0x2588;
for < 2 4 6 8 10 12 10 8 6 4 2>, <0 1 19 20>, 
    <0 999 4000 4999 7000 7999> -> @test {
    my ($min, $max) = @test.minmax[0,*-1];
    say "@test[]; min: $min; max: $max.";
    say join '', @bars[ map { @bars * ($_ - $min) / ($max - $min) min @bars.end }, @test], "\n";
}

This program displays the following output:

$ raku ./sparkline.raku
2 4 6 8 10 12 10 8 6 4 2; min: 2; max: 12.
▁▂▄▅▇█▇▅▄▂▁

0 1 19 20; min: 0; max: 20.
▁▁██

0 999 4000 4999 7000 7999; min: 0; max: 7999.
▁▁▅▅██

Unicode Sparkline in Perl

use strict;
use warnings;
use feature qw/say/;

binmode(STDOUT, ":utf8");
my @bars = map chr, 0x2581 .. 0x2588;

for my $test ([< 2 4 6 8 10 12 10 8 6 4 2>], 
    [<0 1 19 20>], [<0 999 4000 4999 7000 7999>]) {
    my @test = @$test;
    my ($min, $max) = (sort {$a <=> $b} @$test)[0, $#test];
    my $out = "";
    for my $item (@test) {
        my $h = @bars * ($item - $min) / ($max - $min);
        $h = $#bars if $h > $#bars;
        $out .= $bars[int($h)];
    }
    say "@test; min: $min; max: $max.";
    say $out, "\n";
}

This program displays the following output:

$ perl sparkline.pl
2 4 6 8 10 12 10 8 6 4 2; min: 2; max: 12.
▁▂▄▅▇█▇▅▄▂▁

0 1 19 20; min: 0; max: 20.
▁▁██

0 999 4000 4999 7000 7999; min: 0; max: 7999.
▁▁▅▅██

Unicode Sparkline in Julia

function sparkline(test)
    bars = '\u2581':'\u2588'
    bar_count = length(bars)
    min, max = extrema(test)
    out = ""
    for item in test
        h = 1 + bar_count * (item - min) / (max - min)
        h > bar_count && (h = bar_count)
        out = out * string(bars[Int(floor(h))])
    end
    return out * "\n"
end

tests = [ [2, 4, 6, 8, 10, 12, 10, 8, 6, 4, 2],
    [0, 1, 19, 20], [0, 999, 4000, 4999, 7000, 7999] ]
for test in tests
    println(test, "\n")
    println( sparkline(test))
end

Output:

$ julia ./sparkline.jl
[2, 4, 6, 8, 10, 12, 10, 8, 6, 4, 2]

▁▂▄▅▇█▇▅▄▂▁

[0, 1, 19, 20]

▁▁██

[0, 999, 4000, 4999, 7000, 7999]

▁▁▅▅██

Unicode Sparkline in Python

def sparkline(test):
  bars = [chr(bar) for bar in range(9601, 9608+1)]
  minim = min(test)
  maxim = max(test)
  scale = maxim - minim
  length = len(bars)
  line = ""
  for item in test:
    line += bars[min(int((item-minim) / scale * length), length - 1)]
  return line

tests = [ [2, 4, 6, 8, 10, 12, 10, 8, 6, 4, 2], \
  [0, 1, 19, 20], [0, 999, 4000, 4999, 7000, 7999] ]

for test in tests:
  print(test)
  print(sparkline(test), "\n")

Output:

$ python3 sparkline.py
[2, 4, 6, 8, 10, 12, 10, 8, 6, 4, 2]
▁▂▄▅▇█▇▅▄▂▁

[0, 1, 19, 20]
▁▁██

[0, 999, 4000, 4999, 7000, 7999]
▁▁▅▅██

Unicode Sparkline in Ruby

bars = ('▁'..'█').to_a 
tests = [ [1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1],
    [0, 1, 19, 20], [0, 999, 4000, 4999, 7000, 7999] ]
for test in tests
    min, max = test.minmax
    puts test.join(" ")
    puts "min: %.2f; max: %.2f"% [min, max]
    scale = (max - min) / (bars.size - 1)
    line = ""
    for item in test
        h = bars.size * (item - min) / (max - min)
        if h >= bars.size
            h = bars.size - 1
        end
        line += bars[h]
    end
    puts line
    puts " "
end

Output:

1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1.00; max: 8.00
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

0 1 19 20
min: 0.00; max: 20.00
▁▁██

0 999 4000 4999 7000 7999
min: 0.00; max: 7999.00
▁▁▅▅██

Unicode Sparkline in Scala

object sparkLine extends App {

  def sparkline(test: Array[Int]): String = {
    val bars = ('\u2581' to '\u2588')
    var outl = ""
    for (item <- test) {
      var h = bars.length * (item - test.min) / (test.max - test.min)
      if (h >= bars.length) { h = bars.length - 1 }
      outl = outl + bars(h)
    }
    return outl
  }
  val tests = Array(
    Array(1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1),
    Array(0, 1, 19, 20),
    Array(0, 999, 4000, 4999, 7000, 7999)
  )
  for (test <- tests) {
    println(test.mkString(" "))
    println("")
    println(sparkline(test))
    println("")
  }
}

Output:

1 2 3 4 5 6 7 8 7 6 5 4 3 2 1

▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

0 1 19 20

▁▁██

0 999 4000 4999 7000 7999

▁▁▅▅██

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on September 4, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.