The Bear's Den

Enter at your own risk

Second Last Word Standing

Task 1: Second Largest Digit

Submitted by: Mohammad Sajid Anwar


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

Solution

One obvious approach is to pick unique digits from the string, sort these and return the second last if it exists. Finding unique digits may be achieved in \(\mathcal{O}(n)\) time and sorting a fixed-sized list can be done in \(\mathcal{O}(1)\) with any sorting algorithm. Therefore there is no need to optimize the sort. Anyway, it’s fun!

Using a simplified bucket sort to sort unique items:
Each digit gets its own bucket with a capacity of one. After the scatter phase, each bucket contains either one digit or is empty. The second last nonempty bucket provides the requested result.

Perl

Picking digits with a regex and assigning to an array slice as the scatter process.

use strict;
use warnings;

sub second_largest {
    my @digits = shift =~ /\d/g;
    (\my @buckets)->@[@digits] = @digits;

    (grep defined, @buckets)[-2] // -1;
}

See the full solution to task 1.

J

The J implementation may be used to solve a generalized task:

Given a string seq containing valid characters and defining their order, find the n-th largest valid character in a given string and return its index in seq or -1 if none found.

Why and when do I provide an implementation that solves a generalization of the given task? It’s simple: When I come to a solution to the original task that may be generalized with no or little effort.

Sometimes this makes the solution even clearer, e.g. when “magic numbers” are replaced by variables or named constants.

Here it is the “Index Of” verb i. that I’d have used anyway to convert decimal digit characters to integers. The phrase x i. y in J may lead to something like
map index($x, $_), split //, $y
in Perl, which is much more complex than /\d/g for decimal digit characters.

In J the scattering is performed by a variant of “amend”, see set_self below.

NB. (conjunction) create a tacit verb that finds the index of the n-th largest
NB. valid character in y within the characters defined by m
nth_largest =: conjunction define
   NB. (monad) get indices of chars in y as found in m
   NB. provides "# m" for characters not in m
   char_indices =. m&i.

   NB. (noun) array of _1's, one item larger than m - leaving one bucket
   NB. for non-valid chars
   buckets =. _1 $~ >: # m

   NB. (dyad) set y at indices specified by x to x
   NB. This is comparable with Perl's
   NB. "do {my @tmp = @y; @tmp[@x] = @x; @tmp}"
   set_self =. [`[ }

   NB. (monad) drop the last item from y
   curtail =. }:

   NB. (monad) drop negative items from y
   non_neg =. #~ 0&<:

   NB. (monad) return the n-th last item from y or _1 if it doesn't exist
   nth_last =. (-@n { ])`_1:@.(n > #)

   NB. find indices of the chars in y within m, set these indices in the
   NB. bucket array, drop the non-valid bucket and all negative values
   NB. and finally return the n-th last element (which is an index into m)
   ([: nth_last [: non_neg [: curtail buckets set_self~ char_indices) f. : [:
)

Second largest decimal digit (example 3):

   second_largest_digit =: '0123456789' nth_largest 2
   second_largest_digit
([: (-@2 {  ])`(_1:)@.(2 > #) [: (#~ 0&<:) [: }: (11$_1) [`[} ~ '0123456789'&i.) :[:
   second_largest_digit '9zero8eight7seven9'
8

Third largest hex digit:

   '0123456789abcdef' nth_largest 3 'xy01cde23zt'
12

Largest vowel:

   'aeiou' nth_largest 1 'theweeklychallenge'
1

See the full solution.

Task 2: Sum of Words

Submitted by: Mohammad Sajid Anwar


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

Solution

Generalizing the task to an arbitrary base. This will augment or reduce the set of valid characters accordingly.

Furthermore, allow any number of summands. The sum of all but the last word is compared to the last.

Perl

use strict;
use warnings;
use Math::Prime::Util qw(fromdigits vecsum);

sub sum_of_words {
    my $base = shift;
    my @nums = map fromdigits([map ord() - 97, split //, $_], $base), @_;

    $nums[-1] == vecsum @nums[0 .. $#nums - 1];
}

See the full solution to task 2.

J

NB. (adverb) create a tacit verb that converts boxed strings to integers and
NB. checks if the sum of the leading items equals the last
sum_of_words =: adverb define
   NB. (adverb) apply left operand to leaves of y and unbox the result
   spread =. S:0

   NB. (monad) find character offsets from 'a'
   as_digits =. 97 -~ a. i. ]

   NB. (dyad) convert base x digits to an integer
   from_digits =. #.

   NB. (monad) sum over all items but the last
   curtailed_sum =. +/ @: }:

   NB. (monad) pick the last item
   tail =. {:

   NB. convert characters to digits, convert digits from base m to integer,
   NB. sum over all items but the last and compare it with the last
   (tail = curtailed_sum) @: ((m from_digits as_digits) spread) f. : [:
)

The final verb for m =: 10:

   sum_of_dec_words =: 10 sum_of_words
   sum_of_dec_words
({: = +/@:}:)@:((10 (#.) 97 -~ a. i. ])S:0) :[:

Example 1:

   sum_of_dec_words 'acb';'cba';'cdb'
1

Three summands 5 + 4 + 2 = 11:

   sum_of_dec_words 'f';'e';'c';'bb'
1

Hexadecimal 0xf + 0x1 = 0x10:

   16 sum_of_words 'p';'b';'ba'
1

See the full solution.