Perl Weekly Challenge: Digitally Replace Broken Keys

I’m sorry for the lack of music this time around, but when I read “broken keys”, there’s only one broken key I can think of.

Let’s proceed with caution and approach Perl Weekly Challenge 275!

Task 1: Broken Keys

You are given a sentence, $sentence and list of broken keys @keys.

Write a script to find out how many words can be typed fully.

Example 1

Input: $sentence = "Perl Weekly Challenge", @keys = ('l', 'a')
Output: 0

Example 2

Input: $sentence = "Perl and Raku", @keys = ('a')
Output: 1

Only Perl since the other word two words contain 'a' and can't be typed fully.

Example 3

Input: $sentence = "Well done Team PWC", @keys = ('l', 'o')
Output: 2

Example 4

Input: $sentence = "The joys of polyglottism", @keys = ('T')
Output: 2

Approach

This screams regular expression to me: build a regular expression of the letters for the broken keys, split the sentence into words on whitespace, then match the words that don’t contain the letters, and count them up. Easy-peasy.

Raku

sub brokenKeys($sentence, @keys) {
  my @words = $sentence.comb(/\S+/);
  my $regex = '<[' ~ @keys.join ~ ']>';
  return ( ( @words.grep({! /:i <$regex>/ }) ).elems )
}
$ raku/ch-1.raku
Example 1:
Input: $sentence = "Perl Weekly Challenge", @keys = ('l', 'a')
Output: 0

Example 2:
Input: $sentence = "Perl and Raku", @keys = ('a')
Output: 1

Example 3:
Input: $sentence = "Well done Team PWC", @keys = ('l', 'o')
Output: 2

Example 4:
Input: $sentence = "The joys of polyglottism", @keys = ('T')
Output: 2


View the entire Raku script for this task on GitHub
.

Perl

sub brokenKeys($sentence, @keys) {
  my @words = split /\s+/, $sentence;
  my $regex = '[' . join('', @keys) . ']';
  return ( scalar( grep {! /$regex/i } @words) );
}

View the entire Perl script for this task on GitHub.

Python

import re

def brokenKeys(sentence, keys):
  words = sentence.split()
  regex = re.compile('[' + ''.join(keys) + ']', re.I)
  return( len([
    word for word in words if not re.search(regex, word)
  ]) )

View the entire Python script for this task on GitHub.

Elixir

  def brokenKeys(sentence, keys) do
    {:ok, reg} = Regex.compile("[" <> Enum.join(keys) <> "]", [:caseless])
    String.split(sentence)
    |> Enum.filter(fn word -> not Regex.match?(reg, word) end)
    |> Enum.count
    |> to_string
  end

View the entire Elixir script for this task on GitHub.


Task 2: Replace Digits

You are given an alphanumeric string, $str, where each character is either a letter or a digit.

Write a script to replace each digit in the given string with the value of the previous letter plus (digit) places.

Example 1

Input: $str = 'a1c1e1'
Ouput: 'abcdef'

shift('a', 1) => 'b'
shift('c', 1) => 'd'
shift('e', 1) => 'f'

Example 2

Input: $str = 'a1b2c3d4'
Output: 'abbdcfdh'

shift('a', 1) => 'b'
shift('b', 2) => 'd'
shift('c', 3) => 'f'
shift('d', 4) => 'h'

Example 3

Input: $str = 'b2b'
Output: 'bdb'

Example 4

Input: $str = 'a16z'
Output: 'abgz'

Approach

The fourth example helped me figure out how to interpret multiple numeric digits in a row. The description didn’t really make clear to me what 'a16z' should produce. The 16th letter after a? Then the output would be 'aqz'. But the output is 'abgz', so obviously, we’re interpreting each numeric digit separately. But what should the previous letter be for each digit? The 1 is obvious: it’s adding 1 to the 'a' to produce 'b'. But should 6 be added to the 'a' or the 'b' we just produced? Well, if we count back six places from 'g', we find that we get 'a', so we know that we’re supposed to add the digit to the previous letter in the original string, not the previous letter in the output string.

One thing that’s implied by the definition, and none of the examples provide an alternate explanation: the first character of the string has to be a letter, because if it was a numeric digit, we wouldn’t have a previous letter to add it to.

Raku

sub replaceDigits($str) {
  my @chars = $str.comb;
  my $last_letter = @chars[0];
  my $out;
  for @chars -> $c {
    if ($c ~~ /<[0..9]>/) {
      $out ~= ( $last_letter.ord + $c.Int ).chr;
    }
    else {
      $out ~= $c;
      $last_letter = $c;
    }
  }
  return $out;
}
$ raku/ch-2.raku
Example 1:
Input: $str = 'a1c1e1'
Output: 'abcdef'

Example 2:
Input: $str = 'a1b2c3d4'
Output: 'abbdcfdh'

Example 3:
Input: $str = 'b2b'
Output: 'bdb'

Example 4:
Input: $str = 'a16z'
Output: 'abgz'


View the entire Raku script for this task on GitHub
.

Perl

sub replaceDigits($str) {
  my @chars = split //, $str;
  my $last_letter = $chars[0];
  my $out;
  foreach my $c ( @chars ) {
    if ($c =~ /[0-9]/) {
      $out .= chr( ord($last_letter) + $c );
    }
    else {
      $out .= $c;
      $last_letter = $c;
    }
  }
  return $out;
}

View the entire Perl script for this task on GitHub.

Python

Python has the handy str.isnumeric() function to tell us if a character is numeric.

def replaceDigits(str):
  last_letter = str[0:1]
  out = ''
  for c in str:
    if c.isnumeric():
      out += chr( ord(last_letter) + int(c) )
    else:
      out += c
      last_letter = c
  return out

View the entire Python script for this task on GitHub.

Elixir

With Elixir I’m using the pattern I’ve fallen into of having a version of the function that unpacks the data as it’s passed in that recursively calls a version of the same function with more parameters (one of which is a list we’re processing) to track the things we need to track, then a stopping case where the list is empty where we just return the final output.

Converting to ASCII values and back to characters was a bit different: I chose to use the utf8 modifier on the binary syntax << >> to extract the ASCII value, and then use List.to_string/1 to convert it back.

  def replaceDigits([], _, out), do: out

  def replaceDigits(, last, out) do
    if Regex.match?(~r/[0-9]/, c) do
      <<chrval::utf8>> = last
      c = List.to_string([ chrval + String.to_integer(c) ])
      replaceDigits(rest, last, out <> c)
    else
      replaceDigits(rest, c, out <> c)
    end
  end

  def replaceDigits(str) do
    replaceDigits(String.graphemes(str), nil, "")
  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/master/challenge-275/packy-anderson