Perl Weekly Challenge: ‘Cause I’d Already Know

Perl Weekly Challenge 378‘s tasks are “Second Largest Digit” and “Sum of Words”.

I blame thank my wife for this music selection…

Second largest digit
Is not the words in this week’s second task
It’s not that I’m counting
Characters, but if you only knew
How easy… it would be to sum. up. all. these. things!
Sum of words
Is all you have to do to make it real

Task 1: Second Largest Digit

You are given an alphanumeric string.

Write a script to find the second largest distinct digit in the given string. Return -1 if none found.

Example 1

Input: $str = "aaaaa77777"
Output: -1

The only digit in the given string is 7 and there is no second digit.

Example 2

Input: $str = "abcde"
Output: -1

No numerical digits in the given string.

Example 3

Input: $str = "9zero8eight7seven9"
Output: 8

Example 4

Input: $str = "xyz9876543210"
Output: 8

Example 5

Input: $str = "4abc4def2ghi8jkl2"
Output: 4

Approach

Ok, this feels to me like a single pass through the string, maintaining two variables: $one and $two, both initialized to -1. We process each character, skipping characters that aren’t numeric. We then compare any numeric digit to $one. If it’s greater than the current value of $one, we demote the current value of $one to $two, replace $one with this digit, and move on to the next character. If the digit is less than $one, we compare it to $two, and if it’s greater than the current value of $two, we replace the value and move on. Once we’ve gone through the entire string, we output the value of $two.

Of course, I could get a few efficiencies by removing non-numeric characters when I’m breaking the string up into characters.

Raku

Which is exactly what I’m doing here with .comb(/d/). Really, for PWC tasks, .comb is endlessly useful.

sub secondLargestDigit($str) {
  my ($one, $two) = (-1, -1);
  for $str.comb(/\d/) -> $digit { # this gives us only digits
    if ($digit > $one) {
      ($one, $two) = ($digit, $one);
    }
    elsif ($digit < $one && $digit > $two) {
      $two = $digit;
    }
  }
  $two;
}

View the entire Raku script for this task on GitHub.

$ raku/ch-1.raku
Example 1:
Input: $str = "aaaaa77777"
Output: -1

Example 2:
Input: $str = "abcde"
Output: -1

Example 3:
Input: $str = "9zero8eight7seven9"
Output: 8

Example 4:
Input: $str = "xyz9876543210"
Output: 8

Example 5:
Input: $str = "4abc4def2ghi8jkl2"
Output: 4

Perl

And in Perl I’m able to accomplish the same thing by feeing split // into grep { /\d/ }.

sub secondLargestDigit($str) {
  my ($one, $two) = (-1, -1);
  for my $digit ( grep { /\d/ } split //, $str) { # only digits
    if ($digit > $one) {
      ($one, $two) = ($digit, $one);
    }
    elsif ($digit < $one && $digit > $two) {
      $two = $digit;
    }
  }
  $two;
}

View the entire Perl script for this task on GitHub.

Python

In Python, however, we can easily do it using a list comprehension and the str.isnumeric() method.

def second_largest_digit(string):
  (one, two) = (-1, -1)
  digits = [ int(c) for c in string if c.isnumeric() ]
  for digit in digits:
    if digit > one:
      one, two = digit, one
    elif digit < one and digit > two:
      two = digit
  return two

View the entire Python script for this task on GitHub.

Elixir

For Elixir, I needed to do a little more work to filter out the non-numeric characters, but the Enum.reduce/3 from lines 7-11 do that nicely by appending the integer representation of the character to a list if it’s numeric, and appending nothing to the list if it’s not.

The big difference in the main logic of the second Enum.reduce/3 from lines 12-18 is that because Elixir statements need to return values, we need line 16 to not modify the values in one or two if neither of the previous conditionals are true.

def second_largest_digit(str) do
  {_, two} = str
  |> String.codepoints
  |> Enum.reduce([], fn c, list ->
    list ++ if Regex.match?(~r/\d/, c) do
      [ String.to_integer(c)]
    else [] end
  end)
  |> Enum.reduce({-1, -1}, fn digit, {one, two} ->
    cond do
      digit > one                 -> {digit, one}
      digit < one and digit > two -> {one, digit}
      true                        -> {one, two}
    end
  end)
  two
end

View the entire Elixir script for this task on GitHub.


Task 2: Sum of Words

You are given three strings consisting of lower case English letters ‘a’ to ‘j’ only. The letter value of a = 0, b = 1, c = 3, etc.

Write a script to find if sum of first two strings return the third string.

Example 1

Input: $str1 = "acb", $str2 = "cba", $str3 = "cdb"
Output: true

$str1 = "acb" = 021
$str2 = "cba" = 210
$str3 = "cdb" = 231
$str1 + $str2 = $str3

Example 2

Input: $str1 = "aab", $str2 = "aac", $str3 = "ad"
Output: true

$str1 = "aab" = 001
$str2 = "aac" = 002
$str3 = "ad"  = 03

Example 3

Input: $str1 = "bc", $str2 = "je", $str3 = "jg"
Output: false

$str1 = "bc" = 12
$str2 = "je" = 94
$str3 = "jg" = 96

Example 4

Input: $str1 = "a", $str2 = "aaaa", $str3 = "aa"
Output: true

$str1 = "a"    = 0
$str2 = "aaaa" = 0000
$str3 = "aa"   = 00

Example 5

Input: $str1 = "c", $str2 = "d", $str3 = "h"
Output: false

$str1 = "c" = 2
$str2 = "d" = 3
$str3 = "h" = 7

Example 6

Input: $str1 = "gfi", $str2 = "hbf", $str3 = "bdhd"
Output: true

$str1 =  "gfi" =  658
$str2 =  "hbf" =  715
$str3 = "bdhd" = 1373

Approach

This is a job for transliteration. In Perl, it’s the tr/// or y/// operators, and most of the languages I’m writing in have operators/functions to do it.

Raku

In Raku, we’re using the String method .trans.

sub sumOfWords($str1, $str2, $str3) {
  my $width = max($str1.chars, $str2.chars, $str3.chars);
  my $num1 = $str1.trans(['a'..'j'] => [0..9]);
  my $num2 = $str2.trans(['a'..'j'] => [0..9]);
  my $num3 = $str3.trans(['a'..'j'] => [0..9]);
  return(
    $num1 + $num2 == $num3,
    sprintf(
      "\$str1 = %*s = %*s\n" ~
      "\$str2 = %*s = %*s\n" ~
      "\$str3 = %*s = %*s",
      $width+2, qq/"$str1"/, $width, $num1,
      $width+2, qq/"$str2"/, $width, $num2,
      $width+2, qq/"$str3"/, $width, $num3,
    )
  );
}

View the entire Raku script for this task on GitHub.

$ raku/ch-2.raku
Example 1:
Input: $str1 = "acb", $str2 = "cba", $str3 = "cdb"
Output: True

$str1 = "acb" = 021
$str2 = "cba" = 210
$str3 = "cdb" = 231

Example 2:
Input: $str1 = "aab", $str2 = "aac", $str3 = "ad"
Output: True

$str1 = "aab" = 001
$str2 = "aac" = 002
$str3 =  "ad" =  03

Example 3:
Input: $str1 = "bc", $str2 = "je", $str3 = "jg"
Output: False

$str1 = "bc" = 12
$str2 = "je" = 94
$str3 = "jg" = 96

Example 4:
Input: $str1 = "a", $str2 = "aaaa", $str3 = "aa"
Output: True

$str1 =    "a" =    0
$str2 = "aaaa" = 0000
$str3 =   "aa" =   00

Example 5:
Input: $str1 = "c", $str2 = "d", $str3 = "h"
Output: False

$str1 = "c" = 2
$str2 = "d" = 3
$str3 = "h" = 7

Example 6:
Input: $str1 = "gfi", $str2 = "hbf", $str3 = "bdhd"
Output: True

$str1 =  "gfi" =  658
$str2 =  "hbf" =  715
$str3 = "bdhd" = 1373

Perl

In Perl, of course, I’m using the tr/// operator.

use List::AllUtils qw( max );

sub sumOfWords($str1, $str2, $str3) {
  my $width = max (length($str1), length($str2), length($str3));
  (my $num1 = $str1) =~ tr/a-j/0-9/;
  (my $num2 = $str2) =~ tr/a-j/0-9/;
  (my $num3 = $str3) =~ tr/a-j/0-9/;
  return(
    $num1 + $num2 == $num3 ? 'True' : 'False',
    sprintf(
      "\$str1 = %*s = %*s\n" .
      "\$str2 = %*s = %*s\n" .
      "\$str3 = %*s = %*s",
      $width+2, qq/"$str1"/, $width, $num1,
      $width+2, qq/"$str2"/, $width, $num2,
      $width+2, qq/"$str3"/, $width, $num3,
    )
  );
}

View the entire Perl script for this task on GitHub.

Python

In Python, it’s the str.translate() method. Also, because I’m interpolating the strings in an f-string, I added a helper function to wrap the strings in quotes and right-justify them.

def pquote(string, width):
  return f'"{string}"'.rjust(width)

def sum_of_words(str1, str2, str3):
  width = max(len(str1), len(str2), len(str3))
  table = str.maketrans("abcdefghij", "0123456789")
  num1 = str1.translate(table)
  num2 = str2.translate(table)
  num3 = str3.translate(table)
  return (
    "True" if int(num1) + int(num2) == int(num3) else "False",
    f'$str1 = {pquote(str1, width+2)} = ' +
    f'{num1.rjust(width)}\n' +
    f'$str2 = {pquote(str2, width+2)} = ' +
    f'{num2.rjust(width)}\n' +
    f'$str3 = {pquote(str3, width+2)} = ' +
    f'{num3.rjust(width)}'
  )

View the entire Python script for this task on GitHub.

Elixir

Elixir, however, doesn’t have a function to transliterate strings. So I rolled my own, translate.

defp table(), do: Map.new(~c[abcdefghij], fn c ->
  { to_string([c]), to_string([c - 49]) }
end)

defp translate(str) do
  str
  |> String.codepoints
  |> Enum.reduce([], fn c, list ->
    list ++ [ Map.get(table(), c) ]
  end)
  |> Enum.join
end

defp pquote(str, width) do
  String.pad_leading("\"#{str}\"", width)
end

def sum_of_words(str1, str2, str3) do
  width = Enum.max([
    String.length(str1),
    String.length(str2),
    String.length(str3)
  ])
  num1 = translate(str1)
  num2 = translate(str2)
  num3 = translate(str3)
  result = if String.to_integer(num1) +
              String.to_integer(num2) ==
              String.to_integer(num3), do: "True",
                                     else: "False"
  {
    result,
    "$str1 = #{pquote(str1, width+2)} = " <>
    "#{num1 |> String.pad_leading(width)}\n" <>
    "$str2 = #{pquote(str2, width+2)} = " <>
    "#{num2 |> String.pad_leading(width)}\n" <>
    "$str3 = #{pquote(str3, width+2)} = " <>
    "#{num3 |> String.pad_leading(width)}"
  }
end

View the entire Elixir script for this task on GitHub.


Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/challenge-378-packy-anderson/challenge-378/packy-anderson

Leave a Reply