RabbitFarm

2025-07-13

Let’s Count All of Our Nice Strings

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Counter Integers

You are given a string containing only lower case English letters and digits. Write a script to replace every non-digit character with a space and then return all the distinct integers left.

The code can be contained in a single file which has the following structure. The different code sections are explained in detail later.

"ch-1.pl" 1


use GD;
use JSON;
use OCR::OcrSpace;
write text to image 3
ocr image 4
main 5

We don’t really need to do the replacement with spaces since we could just use a regex to get the numbers or even just iterate over the string character by character. Still though, in the spirit of fun we’ll do it anyway.

replace all non-digit characters with a space 2 ⟩≡


$s =~ tr/a-z/ /;

Fragment referenced in 4.

Uses: $s 4.

Ok, sure, now we have a string with spaces and numbers. Now we have to use a regex (maybe with split, or maybe not) or loop over the string anyway to get the numbers. But we could have just done that from the beginning!Well, let’s force ourselves to do something which makes use of our converted string. We are going to write the new string with spaces and numbers to a PNG image file. Later we are going to OCR the results.

The image will be 500x500 and be black text on a white background for ease of character recognition. This fixed size is fine for the examples, more complex examples would require dynamic sizing of the image. The font choice is somewhat arbitrary, although intuitively a fixed width font like Courier should be easier to OCR.

The file paths used here are for my system, MacOS 15.4.

write text to image 3 ⟩≡


sub write_image{
my($s) = @_;
my $width = 500;
my $height = 500;
my $image_file = q#/tmp/output_image.png#;
my $image = GD::Image->new($width, $height);
my $white = $image->colorAllocate(255, 255, 255);
my $black = $image->colorAllocate(0, 0, 0);
$image->filledRectangle(0, 0, $width - 1, $height - 1, $white);
my $font_path = q#/System/Library/Fonts/Courier.ttc#;
my $font_size = 14;
$image->stringFT($black, $font_path, $font_size, 0, 10, 50, $s);
open TEMP, q/>/, qq/$image_file/;
binmode TEMP;
print TEMP $image->png;
close TEMP;
return $image_file;
}

Fragment referenced in 1.

Uses: $s 4.

This second subroutine will handle the OCRing of the image. It’ll also be the main subroutine we call which produces the final result.

After experimenting with tesseract and other open source OCR options it seemed far easier to make use of a hosted service. OCR::OcrSpace is a module ready made for interacting with OcrSpace, an OCR solution provider that offers a free tier of service suitable for our needs. Registration is required in order to obtain an API key.

ocr image 4 ⟩≡


sub counter_integers{
my($s) = @_;
my @numbers;
replace all non-digit characters with a space 2
my $image = write_image($s);
my $ocrspace = OCR::OcrSpace->new();
my $ocrspace_parameters = { file => qq/$image/,
apikey => q/XXXXXXX/,
filetype => q/PNG/,
scale => q/True/,
isOverlayRequired => q/True/,
OCREngine => 2};
my $result = $ocrspace->get_result($ocrspace_parameters);
$result = decode_json($result);
my $lines = $result->{ParsedResults}[0]
->{TextOverlay}
->{Lines};
for my $line (@{$lines}){
for my $word (@{$line->{Words}}){
push @numbers, $word->{WordText};
}
}
return join q/, /, @numbers;
}

Fragment referenced in 1.

Defines: $s 2, 3.

Just to make sure things work as expected we’ll define a few short tests.

main 5 ⟩≡


MAIN:{
print counter_integers q/the1weekly2challenge2/;
print qq/\n/;
print counter_integers q/go21od1lu5c7k/;
print qq/\n/;
print counter_integers q/4p3e2r1l/;
print qq/\n/;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
1, 2, 2 
21, 1, 5, 7 
4, 3, 2, 1
    

Part 2: Nice String

You are given a string made up of lower and upper case English letters only. Write a script to return the longest substring of the give string which is nice. A string is nice if, for every letter of the alphabet that the string contains, it appears both in uppercase and lowercase.

"ch-2.pl" 6


use v5.40;
is_nice 7
nice substring 8
main 9

We’ll do this in two subroutines: one for confirming if a substring is nice, and another for generating substrings.

This subroutine examines each letter and sets a hash value for both upper and lower case versions of the letter as they are seen. We return true if all letters have both an upper and lower case version.

is_nice 7 ⟩≡


sub is_nice{
my ($s) = @_;
my %seen;
for my $c (split //, $s){
if($c =~ m/[a-z]/) {
$seen{$c}{lower} = 1;
}
elsif($c =~ m/[A-Z]/) {
$seen{lc($c)}{upper} = 1;
}
}
for my $c (keys %seen){
return 0 unless exists $seen{$c}{lower} &&
exists $seen{$c}{upper};
}
return 1;
}

Fragment referenced in 6.

Here we just generate all substrings in a nested loop.

nice substring 8 ⟩≡


sub nice_substring{
my ($s) = @_;
my $n = length($s);
my $longest = q//;
for my $i (0 .. $n - 1) {
for my $j ($i + 1 .. $n) {
my $substring = substr($s, $i, $j - $i);
if (is_nice($substring) &&
length($substring) > length($longest)){
$longest = $substring;
}
}
}
return $longest;
}

Fragment referenced in 6.

The main section is just some basic tests.

main 9 ⟩≡


MAIN:{
say nice_substring q/YaaAho/;
say nice_substring q/cC/;
say nice_substring q/A/;
}

Fragment referenced in 6.

Sample Run
$ perl perl/ch-2.pl 
Weekly 
 
abc
    

References

OCR API Service
The Weekly Challenge 329
Generated Code

posted at: 16:54 by: Adam Russell | path: /perl | permanent link to this entry

2025-07-09

The Weekly Challenge 328 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Replace all ?

You are given a string containing only lower case English letters and ?. Write a script to replace all ? in the given string so that the string doesn’t contain consecutive repeating characters.

Our solution is short and will be contained in a single file that has the following structure.

"ch-1.p" 1


state of the replacement 2
replace all 3
replace all ?s 4

Let’s use a DCG! First we’ll define a couple of helper rules to track the state of the replacement as the string gets processed.

state of the replacement 2 ⟩≡


replacement(Replacement), [Replacement] --> [Replacement].
replacement(R, Replacement), [Replacement] --> [R].

Fragment referenced in 1.

Next let’s have the DCG rules themselves. This looks a little more complex then it could be just because I handle certain corner cases as separate rules. Basically all we’re doing is looking at the current character code of the string to see if it’s a question mark (63) and if it is then check the previous character and the next character and rely on backtracking to find a suitable random letter. The corner cases are for when we have no previous character and if we just have one character remaining to process. We also consider the case when we have a character other than ?.

replace all 3 ⟩≡


replace(Input) --> replacement(R, Replacement),
{\+ R == [],
Input = [C, CNext|T],
C == 63,
random(97, 123, C0),
last(R, CPrevious),
\+ C0 == CPrevious,
\+ C0 == CNext,
append(R, [C0], Replacement)
},
replace([CNext|T]).
replace(Input) --> replacement(R, Replacement),
{R == [],
Input = [C, CNext|T],
C == 63,
random(97, 123, C0),
\+ C0 == CNext,
append(R, [C0], Replacement)
},
replace([CNext|T]).
replace(Input) --> replacement(R, Replacement),
{Input = [C|T],
\+ C == 63,
append(R, [C], Replacement)
},
replace(T).
replace(Input) --> replacement(R, Replacement),
{Input = [C|T],
C == 63,
random(97, 123, C0),
last(R, CPrevious),
\+ C0 == CPrevious,
append(R, [C0], Replacement)
},
replace(T).
replace([]) --> [].

Fragment referenced in 1.

Finally we’ll have a small predicate for calling the DCG using phrase/3 and formatting the result.

replace all ?s 4 ⟩≡


replace_qs(S, Replaced):-
phrase(replace(S), [[]], [ReplacedCodes]),
atom_codes(Replaced, ReplacedCodes).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- replace_qs("a?z", Replaced). 
 
Replaced = adz ? 
 
(1 ms) yes 
| ?- replace_qs("pe?k", Replaced). 
 
Replaced = petk ? 
 
yes 
| ?- replace_qs("gra?te", Replaced). 
 
Replaced = gralte ? 
 
yes 
| ?- replace_qs("abcdefg", Replaced). 
 
Replaced = abcdefg ? 
 
yes 
| ?-
    

Part 2: Good String

You are given a string made up of lower and upper case English letters only. Write a script to return the good string of the given string. A string is called good string if it doesn’t have two adjacent same characters, one in upper case and other is lower case.

Let’s use DCGs again! This will proceed in a way very similar to Part 1.

"ch-2.p" 5


find a bad pair 7
track the state of the deletions 6
make good 8
remove all bad pairs 9

Some helper rules to track the state of the deletions of the ”bad pairs“. The state will be maintained as modifications of the original string. This may seem a little unusual at first, we aren’t iterating over the string, rather we are iterating over all possible bad pairs. Maybe this use of a DCG is a little clunky, but why let that stop us!

(I say this is “clunky” because here we’re mainly using DCG notation simply as an alternative to ordinary recursion. There’s nothing being parsed per se and there is nothing especially interesting about the list being processed.)

track the state of the deletions 6 ⟩≡


deletion(Deletion), [Deletion] --> [Deletion].
deletion(D, Deletion), [Deletion] --> [D].

Fragment referenced in 5.

find a bad pair 7 ⟩≡


bad_pair([], _):- false.
bad_pair(S, P):-
member(X, S),
member(Y, S),
\+ X == Y,
nth(I, S, X),
nth(J, S, Y),
succ(I, J),
(X is Y + 32; X is Y - 32), P = [I, J], !.

Fragment referenced in 5.

The main DCG rules.

make good 8 ⟩≡


make_good(S) --> deletion(D, Deletion),
{D == [], \+ S == [], Deletion = S},
make_good(S).
make_good(S) --> deletion(D, Deletion),
{\+ D == [],
bad_pair(S, P),
[I, _] = P,
I > 1,
prefix(Prefix, S),
length(Prefix, I0),
I0 is I - 1,
append(Prefix, [_, _|Rest], S),
append(Prefix, Rest, Deletion)
},
make_good(Deletion).
make_good(S) --> deletion(D, Deletion),
{\+ D == [],
bad_pair(S, P),
[I, _] = P,
I == 1,
append([_, _], Deletion, S)
},
make_good(Deletion).
make_good(S) --> deletion(_, Deletion),
{\+ bad_pair(S, _),
Deletion = S}.
make_good([]) --> [].

Fragment referenced in 5.

Finally we’ll have a small predicate for calling the DCG using phrase/3.

remove all bad pairs 9 ⟩≡


make_good_string(S, Good):-
phrase(make_good(S), [[]], [GoodCodes]),
atom_codes(Good, GoodCodes), !.

Fragment referenced in 5.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- make_good_string("WeEeekly", Good). 
 
Good = ’Weekly’ 
 
(1 ms) yes 
| ?- make_good_string("abBAdD", Good). 
 
Good = ’’ 
 
yes 
| ?- make_good_string("abc", Good). 
 
Good = abc 
 
yes 
| ?-
    

References

The Weekly Challenge 328
Generated Code

posted at: 19:32 by: Adam Russell | path: /prolog | permanent link to this entry

2025-07-06

A Good String Is Irreplaceable

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Replace all ?

You are given a string containing only lower case English letters and ?. Write a script to replace all ? in the given string so that the string doesn’t contain consecutive repeating characters.

The core of the solution is contained in a single subroutine. The resulting code can be contained in a single file.

"ch-1.pl" 1


use v5.40;
replace all ?s 2
main 4

The approach we take is to randomly select a new letter and test to make sure that it does not match the preceding or succeeding letter.

replace all ?s 2 ⟩≡


sub replace_all{
my($s) = @_;
my @s = split //, $s;
my @r = ();
{
my $c = shift @s;
my $before = pop @r;
my $after = shift @s;
my $replace;
if($c eq q/?/){
replace 3
push @r, $before, $replace if $before;
push @r, $replace if !$before;
}
else{
push @r, $before, $c if $before;
push @r, $c if !$before;
}
unshift @s, $after if $after;
redo if $after;
}
return join q//, @r;
}

Fragment referenced in 1.

Defines: $after 3, $before 3, $replace 3.

Finding the replacement is done in a loop that repeatedly tries to find a relacement that does not match the preceding or following character. Since the number of potential conflicts is so small this will not (most likely require many iterations.

replace 3 ⟩≡


do{
$replace = chr(int(97 + rand(123 - 97)));
$replace = undef if $before && $replace eq $before;
$replace = undef if $after && $replace eq $after;
} while(!$replace);

Fragment referenced in 2.

Uses: $after 2, $before 2, $replace 2.

Just to make sure things work as expected we’ll define a few short tests.

main 4 ⟩≡


MAIN:{
say replace_all q/a?z/;
say replace_all q/pe?k/;
say replace_all q/gra?te/;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
atz 
peck 
graqte
    

Part 2: Good String

You are given a string made up of lower and upper case English letters only. Write a script to return the good string of the given string. A string is called good string if it doesn’t have two adjacent same characters, one in upper case and other is lower case.

We’ll define a subroutine for detecting and returning non-good pairs of letters. We know we’re done when this subroutine returns undef.

"ch-2.pl" 5


use v5.40;
bad pairs 6
make good 7
main 8

We’ll call these pairs of letters bad pairs. To see if we have a matching pair we’ll just compare the ascii values.

bad pairs 6 ⟩≡


sub bad_pair{
my($s) = @_;
my @s = split q//, $s;
return undef if !@s;
{
my($x, $y) = (ord shift @s, ord shift @s);
if($x == $y + 32 || $x == $y - 32){
return chr($x) . chr($y);
}
unshift @s, chr($y);
redo unless @s == 1;
}
return undef;
}

Fragment referenced in 5.

We use that bad_pair subroutine repeatedly in a loop until all bad pairs are removed.

make good 7 ⟩≡


sub make_good{
my($s) = @_;
{
my $bad_pair = bad_pair $s;
$s =~ s/$bad_pair// if $bad_pair;
redo if bad_pair $s;
}
return $s;
}

Fragment referenced in 5.

The main section is just some basic tests.

main 8 ⟩≡


MAIN:{
say make_good q/WeEeekly/;
say make_good q/abBAdD/;
say make_good q/abc/;
}

Fragment referenced in 5.

Sample Run
$ perl perl/ch-2.pl 
Weekly 
 
abc
    

References

The Weekly Challenge 328
Generated Code

posted at: 17:39 by: Adam Russell | path: /perl | permanent link to this entry

2025-06-29

The Weekly Challenge 327 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Missing Integers

You are given an array of n integers. Write a script to find all the missing integers in the range 1..n in the given array.

Our solution is short and will be contained in a single file that has the following structure.

"ch-1.p" 1


missing integers 2

This problem is straightforward to solve using member/2.

missing integers 2 ⟩≡


missing_integers(L, Missing):-
length(L, Length),
findall(M, (
between(1, Length, M),
\+ member(M, L)
), Missing).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- missing_integers([1, 2, 1, 3, 2, 5], Missing). 
 
Missing = [4,6] 
 
yes 
| ?- missing_integers([1, 1, 1], Missing). 
 
Missing = [2,3] 
 
yes 
| ?- missing_integers([2, 2, 1], Missing). 
 
Missing = [3] 
 
yes 
| ?-
    

Part 2: MAD

You are given an array of distinct integers. Write a script to find all pairs of elements with minimum absolute difference (MAD) of any two elements.

The code required is fairly small, we’ll just need a single predicate and use GNU Prolog’s clp(fd) solver.

"ch-2.p" 3


compute MAD and find pairs 4

This is a good use of GNU Prolog’s clp(fd) solver. We set up the finite domain variables I and J to take values from the given list. We then find the minimum value of the differences and return all pairs having satisfied that constraint.

compute MAD and find pairs 4 ⟩≡


mad(L, Pairs):-
fd_max_integer(MAX_INT),
fd_domain([I, J], L),
fd_domain(D, 1, MAX_INT),
J #> I,
fd_minimize((D #= J - I, fd_labeling([D])), D),
findall(Pair, (fd_labeling([I, J]), Pair = [I, J]), Pairs).

Fragment referenced in 3.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- mad([4, 1, 2, 3], Pairs). 
 
Pairs = [[1,2],[2,3],[3,4]] 
 
yes 
| ?- mad([1, 3, 7, 11, 15], Pairs). 
 
Pairs = [[1,3]] 
 
yes 
| ?- mad([1, 5, 3, 8], Pairs). 
 
Pairs = [[1,3],[3,5]] 
 
yes 
| ?-
    

References

The Weekly Challenge 327
Generated Code

posted at: 15:28 by: Adam Russell | path: /prolog | permanent link to this entry

Missing Integers Don’t Make Me MAD, Just Disappointed

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Missing Integers

You are given an array of n integers. Write a script to find all the missing integers in the range 1..n in the given array.

The core of the solution is contained in a single subroutine. The resulting code can be contained in a single file.

"ch-1.pl" 1


use v5.40;
find missing 2
main 3

The approach we take is to use the given array as hash keys. Then we’ll iterate over the range 1..n and see which hash keys are missing.

find missing 2 ⟩≡


sub find_missing{
my %h = ();
my @missing = ();
do{ $h{$_} = -1 } for @_;
@missing = grep {!exists($h{$_})} 1 .. @_;
return @missing;
}

Fragment referenced in 1.

Just to make sure things work as expected we’ll define a few short tests.

main 3 ⟩≡


MAIN:{
say q/(/ . join(q/, /, find_missing 1, 2, 1, 3, 2, 5) . q/)/;
say q/(/ . join(q/, /, find_missing 1, 1, 1) . q/)/;
say q/(/ . join(q/, /, find_missing 2, 2, 1) . q/)/;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
(4, 6) 
(2, 3) 
(3)
    

Part 2: MAD

You are given an array of distinct integers. Write a script to find all pairs of elements with minimum absolute difference (MAD) of any two elements.

We’ll use a hash based approach like we did in Part 1. The amount of code is small, just a single subroutine.

"ch-2.pl" 4


use v5.40;
compute MAD and find pairs 5
main 6

Since we need to have a nested loop to access all pairs we’ll make an effort to only do it once. What we’ll do is store the pairs in a list keyed by the differences. We’ll also track the minimum difference in a variable to avoid sorting to find it later.

compute MAD and find pairs 5 ⟩≡


sub mad_pairs{
my %mad = ();
my $mad = ~0;
for my $i (0 .. @_ - 1){
for my $j ($i + 1 .. @_ - 1){
my $d = abs($_[$i] - $_[$j]);
$mad = $d if $d < $mad;
push @{$mad{$d}}, [$_[$i], $_[$j]];
}
}
return @{$mad{$mad}};
}

Fragment referenced in 4.

The main section is just some basic tests. Yeah, we’ll do lazy string formatting with chop!

main 6 ⟩≡


MAIN:{
my $s = q//;
do{
$s .= q/[/ . join(q/, /, @{$_}) . q/], /;
} for mad_pairs 4, 1, 2, 3;
chop $s;
chop $s;
say $s;
$s = q//;

do{
$s .= q/[/ . join(q/, /, @{$_}) . q/], /;
} for mad_pairs 1, 3, 7, 11, 15;
chop $s;
chop $s;
say $s;
$s = q//;

do{
$s .= q/[/ . join(q/, /, @{$_}) . q/], /;
} for mad_pairs 1, 5, 3, 8;
chop $s;
chop $s;
say $s;
$s = q//;
}

Fragment referenced in 4.

Sample Run
$ perl perl/ch-2.pl 
[4, 3], [1, 2], [2, 3] 
[1, 3] 
[1, 3], [5, 3]
                                                                  

                                                                  
    

References

The Weekly Challenge 327
Generated Code

posted at: 12:09 by: Adam Russell | path: /perl | permanent link to this entry

2025-06-22

The Weekly Challenge 326 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Day of the Year

You are given a date in the format YYYY-MM-DD. Write a script to find day number of the year that the given date represent.

Our solution is short, it involves just a couple of computations, and will be contained in a single file that has the following structure.

"ch-1.p" 1


day month year 4
leap year 2
february days 3
day of the year 5

We’ll put the determination of whether a year is a leap year or not into its own predicate.

leap year 2 ⟩≡


leap_year(Year):-
M1 is Year mod 4,
M2 is Year mod 100,
M3 is Year mod 400,
((M1 == 0, \+ M2 == 0);
(M1 == 0, M2 == 0, M3 == 0)).

Fragment referenced in 1.

Similarly, we’ll put the calculation of the number of February days in its own predicate.

february days 3 ⟩≡


february_days(Year, Days):-
leap_year(Year),
Days = 29.
february_days(_, Days):-
Days = 28.

Fragment referenced in 1.

One more utility predicate, which splits the input into day, month, and year values.

day month year 4 ⟩≡


day_month_year(S, Day, Month, Year):-
append(Y, [45|T], S),
append(M, [45|D], T),
number_codes(Day, D),
number_codes(Month, M),
number_codes(Year, Y).

Fragment referenced in 1.

Finally, let’s compute the day of the year.

day of the year 5 ⟩≡


day_of_year(Date, DayOfYear) :-
day_month_year(Date, Day, Month, Year),
february_days(Year, FebruaryDays),
DaysInMonth = [31, FebruaryDays, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
succ(M, Month),
length(Prefix, M),
prefix(Prefix, DaysInMonth),
sum_list(Prefix, MonthSum),
DayOfYear is MonthSum + Day.

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- day_of_year("2025-02-02", DayOfYear). 
 
DayOfYear = 33 ? 
 
yes 
| ?- day_of_year("2025-04-10", DayOfYear). 
 
DayOfYear = 100 ? 
 
yes 
| ?- day_of_year("2025-09-07", DayOfYear). 
 
DayOfYear = 250 ? 
 
yes 
| ?-
    

Part 2: Decompressed List

You are given an array of positive integers having even elements. Write a script to to return the decompress list. To decompress, pick adjacent pair (i, j) and replace it with j, i times.

The code required is fairly small, we’ll just need a couple of predicates.

"ch-2.p" 6


state of the decompression 7
decompress 8
decompress list 9

We’ll define a DCG to “decompress”the list. First, let’s have some predicates for maintaining the state of the decompression as it proceeds.

state of the decompression 7 ⟩≡


decompression(Decompression), [Decompression] --> [Decompression].
decompression(D, Decompression), [Decompression] --> [D].

Fragment referenced in 6.

The DCG for this is not so complex. Mainly we need to be concerned with maintaining the state of the decompression as we process the list.

decompress 8 ⟩≡


decompress(Input) --> decompression(D, Decompression),
{Input = [I, J|T],
length(L, I),
maplist(=(J), L),
append(D, L, Decompression)
},
decompress(T).
decompress([]) --> [].

Fragment referenced in 6.

Finally, let’s wrap the calls to the DCG in a small predicate using phrase/3.

decompress list 9 ⟩≡


decompress_list(L, Decompressed):-
phrase(decompress(L), [[]], [Decompressed]).

Fragment referenced in 6.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- decompress_list([1, 3, 2, 4], Decompressed). 
 
Decompressed = [3,4,4] 
 
yes 
| ?- decompress_list([1, 1, 2, 2], Decompressed). 
 
Decompressed = [1,2,2] 
 
yes 
| ?- decompress_list([3, 1, 3, 2], Decompressed). 
 
Decompressed = [1,1,1,2,2,2] 
 
yes 
| ?-
    

References

The Weekly Challenge 326
Generated Code

posted at: 18:45 by: Adam Russell | path: /prolog | permanent link to this entry

The Day We Decompress

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Day of the Year

You are given a date in the format YYYY-MM-DD. Write a script to find day number of the year that the given date represent.

The core of the solution is contained in a main loop. The resulting code can be contained in a single file.

"ch-1.pl" 1


use v5.40;
compute the day of the year 2
main 4

The answer is arrived at via a fairly straightforward calculation.

compute the day of the year 2 ⟩≡


sub day_of_year {
my ($date) = @_;
my $day_of_year = 0;
my ($year, $month, $day) = split /-/, $date;
determine if this is a leap year 3
my @days_in_month = (31, $february_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$day_of_year += $days_in_month[$_] for (0 .. $month - 2);
$day_of_year += $day;
return $day_of_year;
}

Fragment referenced in 1.

Defines: $year 3.

Uses: $february_days 3.

Let’s break the logic for computing a leap year into it’s own section. A leap year occurs every 4 years, except for years that are divisible by 100, unless they are also divisible by 400.

determine if this is a leap year 3 ⟩≡


my $is_leap_year = ($year % 400 == 0) || ($year % 4 == 0 && $year % 100 != 0);
my $february_days = $is_leap_year ? 29 : 28;

Fragment referenced in 2.

Defines: $february_days 2, $is_leap_year Never used.

Uses: $year 2.

Just to make sure things work as expected we’ll define a few short tests. The double chop is just a lazy way to make sure there aren’t any trailing commas in the output.

main 4 ⟩≡


MAIN:{
say day_of_year q/2025-02-02/;
say day_of_year q/2025-04-10/;
say day_of_year q/2025-09-07/;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
33 
100 
250
    

Part 2: Decompressed List

You are given an array of positive integers having even elements. Write a script to to return the decompress list. To decompress, pick adjacent pair (i, j) and replace it with j, i times.

For fun let’s use recursion!

"ch-2.pl" 5


use v5.40;
decompress list 6
main 7

Sometimes when I write a recursive subroutine in Perl I use a reference variable to set the return value. Other times I just use an ordinary return. In some cases, for convenience, I’ll do this with two subroutines. One of these is a wrapper which calls the main recursion.

For this problem I’ll do something a little different. I’ll have one subroutine and for each recursive call I’ll add in an array reference to hold the accumulating return value.

Note that we take advantage of Perl’s automatic list flattening when pushing to the array reference holding the new list we are building.

decompress list 6 ⟩≡


sub decompress_list{
my $r = shift @_;
if(!ref($r) || ref($r) ne q/ARRAY/){
unshift @_, $r;
$r = [];
}
unless(@_ == 0){
my $i = shift @_;
my $j = shift @_;
push @{$r}, ($j) x $i;
decompress_list($r, @_);
}
else{
return @{$r};
}
}

Fragment referenced in 5.

The main section is just some basic tests.

main 7 ⟩≡


MAIN:{
say join q/, /, decompress_list 1, 3, 2, 4;
say join q/, /, decompress_list 1, 1, 2, 2;
say join q/, /, decompress_list 3, 1, 3, 2;
}

Fragment referenced in 5.

Sample Run
$ perl perl/ch-2.pl 
3, 4, 4 
1, 2, 2 
1, 1, 1, 2, 2, 2
    

References

The Weekly Challenge 326
Generated Code

posted at: 16:45 by: Adam Russell | path: /perl | permanent link to this entry

2025-06-14

The Weekly Challenge 325 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Consecutive One

You are given a binary array containing only 0 or/and 1. Write a script to find out the maximum consecutive 1 in the given array.

Our solution is short and will be contained in a single file that has the following structure.

"ch-1.p" 1


state of the count 2
count consecutive ones 3
consecutive ones 4

We’ll define a DCG to count the ones in the list. First, let’s have some predicates for maintaining the state of the count of consecutive ones.

state of the count 2 ⟩≡


consecutive_ones(Consecutive), [Consecutive] --> [Consecutive].
consecutive_ones(C, Consecutive), [Consecutive] --> [C].

Fragment referenced in 1.

The DCG for this is not so complex. Mainly we need to be concerned with maintaining the state of the count as we see each list element.

count consecutive ones 3 ⟩≡


count_ones(Input) --> consecutive_ones(C, Consecutive),
{Input = [H|T],
H == 1,
[Count, Maximum] = C,
succ(Count, Count1),
((Count1 > Maximum, Consecutive = [Count1, Count1]);
(Consecutive = [Count1, Maximum]))
},
count_ones(T).
count_ones(Input) --> consecutive_ones(C, Consecutive),
{Input = [H|T],
H == 0,
[_, Maximum] = C,
Consecutive = [0, Maximum]},
count_ones(T).
count_ones([]) --> [].

Fragment referenced in 1.

Finally, let’s wrap the calls to the DCG in a small predicate using phrase/3.

consecutive ones 4 ⟩≡


consecutive_ones(L, MaximumConsecutive):-
phrase(count_ones(L), [[0, 0]], [Output]), !,
[_, MaximumConsecutive] = Output.

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- consecutive_ones([0, 1, 1, 0, 1, 1, 1], MaximumCount). 
 
MaximumCount = 3 
 
yes 
| ?- consecutive_ones([0, 0, 0, 0], MaximumCount). 
 
MaximumCount = 0 
 
yes 
| ?- consecutive_ones([1, 0, 1, 0, 1, 1], MaximumCount). 
 
MaximumCount = 2 
 
yes 
| ?-
    

Part 2: Final Price

You are given an array of item prices. Write a script to find out the final price of each items in the given array. There is a special discount scheme going on. If there’s an item with a lower or equal price later in the list, you get a discount equal to that later price (the first one you find in order).

The code required is fairly small, we’ll just need a couple of predicates.

"ch-2.p" 5


next smallest 6
compute lowest prices 7

Given a list and a price find the next smallest price in the list.

next smallest 6 ⟩≡


next_smallest([], _, 0).
next_smallest([H|_], Price, H):-
H =< Price, !.
next_smallest([H|T], Price, LowestPrice):-
H > Price,
next_smallest(T, Price, LowestPrice).

Fragment referenced in 5.

compute lowest prices 7 ⟩≡


compute_lowest([], []).
compute_lowest([H|T], [LowestPrice|LowestPrices1]):-
compute_lowest(T, LowestPrices1),
next_smallest(T, H, Discount),
LowestPrice is H - Discount.

Fragment referenced in 5.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- compute_lowest([8, 4, 6, 2, 3], FinalPrices). 
 
FinalPrices = [4,2,4,2,3] 
 
yes 
| ?- compute_lowest([1, 2, 3, 4, 5], FinalPrices). 
 
FinalPrices = [1,2,3,4,5] 
 
yes 
| ?- compute_lowest([7, 1, 1, 5], FinalPrices). 
 
FinalPrices = [6,0,1,5] 
 
yes 
| ?-
    

References

The Weekly Challenge 325
Generated Code

posted at: 15:33 by: Adam Russell | path: /prolog | permanent link to this entry

2025-06-12

Consecutive Search for Discount Prices

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Consecutive One

You are given a binary array containing only 0 or/and 1. Write a script to find out the maximum consecutive 1 in the given array.

The core of the solution is contained in a main loop. The resulting code can be contained in a single file.

"ch-1.pl" 1


use v5.40;
recursively count consecutive ones 3
find the longest consecutive sequence of ones 2
main 4

We’ll use a recursive procedure, which we’ll call from a subroutine which sets up some variables. We’ll pass scalar references to a recursive subroutine. When the recursion completes the $max_consecutive variable will hold the final answer.

find the longest consecutive sequence of ones 2 ⟩≡


sub consecutive_one{
my(@i) = @_;
my($consecutive, $max_consecutive) = (0, 0);
consecutive_one_r(\@i, \$consecutive, \$max_consecutive);
return $max_consecutive;
}

Fragment referenced in 1.

Defines: $consecutive 3, $max_consecutive 3.

Now, let’s define our recursion. We’ll terminate the recursion when we’ve exhausted the input array.

recursively count consecutive ones 3 ⟩≡


sub consecutive_one_r{
my($i, $consecutive, $max_consecutive) = @_;
my $x;
unless(@{$i} == 0){
$x = pop @{$i};
if($x == 0){
$$max_consecutive = $$consecutive if $$consecutive > $$max_consecutive;
$$consecutive = 0;
}
if($x == 1){
$$consecutive++;
}
consecutive_one_r($i, $consecutive, $max_consecutive);
}
elsif(@{$i} == 1){
$x = pop @{$i};
if($x == 0){
$$max_consecutive = $$consecutive if $$consecutive > $$max_consecutive;
}
if($x == 1){
$$consecutive++;
$$max_consecutive = $$consecutive if $$consecutive > $$max_consecutive;
}
consecutive_one_r($i, $consecutive, $max_consecutive);
}
}

Fragment referenced in 1.

Uses: $consecutive 2, $max_consecutive 2.

Just to make sure things work as expected we’ll define a few short tests. The double chop is just a lazy way to make sure there aren’t any trailing commas in the output.

main 4 ⟩≡


MAIN:{
say consecutive_one(0, 1, 1, 0, 1, 1, 1);
say consecutive_one(0, 0, 0, 0);
say consecutive_one(1, 0, 1, 0, 1, 1);
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
3 
0 
2
    

Part 2: Final Price

You are given an array of item prices. Write a script to find out the final price of each items in the given array. There is a special discount scheme going on. If there’s an item with a lower or equal price later in the list, you get a discount equal to that later price (the first one you find in order).

Hey, let’s use recursion again for this too!

"ch-2.pl" 5


use v5.40;
search for lower price 7
calculate lowest prices 8
main 6

The main section is just some basic tests.

main 6 ⟩≡


MAIN:{
say join q/, /, calculate_lowest_prices 8, 4, 6, 2, 3;
say join q/, /, calculate_lowest_prices 1, 2, 3, 4, 5;
say join q/, /, calculate_lowest_prices 7, 1, 1, 5;
}

Fragment referenced in 5.

First, let’s introduce a recursive subroutine that scans ahead and finds the next lowest price in the list. As in part one we’ll use a scalar reference.

search for lower price 7 ⟩≡


sub search_lower{
my($prices, $price, $lower) = @_;
if(@{$prices} > 0){
my $next_price = shift @{$prices};
search_lower($prices, $price, $lower) unless $next_price <= $price;
$$lower = $next_price if $next_price <= $price;
}
}

Fragment referenced in 5.

Uses: $lower 8.

With that subroutine defined we can use it to solve the main task at hand.

calculate lowest prices 8 ⟩≡


sub calculate_lowest_prices{
my @prices = @_;
my @lowest = ();
for my $i (0 .. @prices - 1){
my $lower = 0;
search_lower [@prices[$i + 1 .. @prices - 1]], $prices[$i], \$lower;
push @lowest, $prices[$i] - $lower;
}
return @lowest;
}

Fragment referenced in 5.

Defines: $lower 7.

Sample Run
$ perl perl/ch-2.pl 
4, 2, 4, 2, 3 
1, 2, 3, 4, 5 
6, 0, 1, 5
    

References

The Weekly Challenge 325
Generated Code

posted at: 22:13 by: Adam Russell | path: /perl | permanent link to this entry

2025-06-08

The Weekly Challenge 324 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: 2D Array

You are given an array of integers and two integers $r and $c. Write a script to create two dimension array having $r rows and $c columns using the given array.

Our solution is short and will be contained in a single file that has the following structure.

"ch-1.p" 1


create two dimensional array 2

We’ll use a straightforward recursive approach.

create two dimensional array 2 ⟩≡


create_array(_, 0, _, []).
create_array(L, Rows, Columns, [Row|T]) :-
create_row(L, Columns, Row, L1),
R is Rows - 1,
create_array(L1, R, Columns, T).

create_row(L, 0, [], L).
create_row([H|T], Columns, [H|Row], L) :-
C is Columns - 1,
create_row(T, C, Row, L).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- create_array([1, 2, 3, 4], 2, 2, TwoDArray). 
 
TwoDArray = [[1,2],[3,4]] ? 
 
yes 
| ?- create_array([1, 2, 3], 1, 3, TwoDArray). 
 
TwoDArray = [[1,2,3]] ? 
 
yes 
| ?- create_array([1, 2, 3, 4], 4, 1, TwoDArray). 
 
TwoDArray = [[1],[2],[3],[4]] ? 
 
yes 
| ?-
    

Part 2: Total XOR

You are given an array of integers. Write a script to return the sum of total XOR for every subset of given array.

GNU Prolog has a sublist/2 predicate which will generate all needed subsets on backtracking. We’ll use this inside of a findall/3. The code required is fairly small, although we’ll define a couple of small utility predicates.

"ch-2.p" 3


subtotal 6
compute total xor 4
combine xors 5

compute total xor 4 ⟩≡


total_xor(L, Total):-
findall(S, (
sublist(S, L),
\+ S = []
), SubLists),
maplist(combine, SubLists, Combined),
maplist(subtotal, Combined, SubTotals),
sum_list(SubTotals, Total).

Fragment referenced in 3.

combine xors 5 ⟩≡


combine([], 0).
combine([H|T], Combined):-
combine(T, Combined1),
Combined = xor(H, Combined1).

Fragment referenced in 3.

subtotal 6 ⟩≡


subtotal(Combined, X):-
X is Combined.

Fragment referenced in 3.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- total_xor([1, 3], Total). 
 
Total = 6 
 
yes 
| ?- total_xor([5, 1, 6], Total). 
 
Total = 28 
 
yes 
| ?- total_xor([3, 4, 5, 6, 7, 8], Total). 
 
Total = 480 
 
yes 
| ?-
    

References

The Weekly Challenge 324
Generated Code

posted at: 14:40 by: Adam Russell | path: /prolog | permanent link to this entry

Two Dimensional XOR Not?

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: 2D Array

You are given an array of integers and two integers $r and $c. Write a script to create two dimension array having $r rows and $c columns using the given array.

The core of the solution is contained in a main loop. The resulting code can be contained in a single file.

"ch-1.pl" 1


use v5.40;
create 2d array 2
main 3

create 2d array 2 ⟩≡


sub create_array{
my($i, $r, $c) = @_;
my @a = ();
for (0 .. $r - 1){
my $row = [];
for (0 .. $c - 1){
push @{$row}, shift @{$i};
}
push @a, $row;
}
return @a;
}

Fragment referenced in 1.

Just to make sure things work as expected we’ll define a few short tests. The double chop is just a lazy way to make sure there aren’t any trailing commas in the output.

main 3 ⟩≡


MAIN:{
my $s = q//;
$s .= q/(/;
do{
$s.= (q/[/ . join(q/, /, @{$_}) . q/], /);
} for create_array [1, 2, 3, 4], 2, 2;
chop $s;
chop $s;
$s .= q/)/;
say $s;

$s = q//;
$s .= q/(/;
do{
$s.= (q/[/ . join(q/, /, @{$_}) . q/], /);
} for create_array [1, 2, 3], 1, 3;
chop $s;
chop $s;
$s .= q/)/;
say $s;

$s = q//;
$s .= q/(/;
do{
$s.= (q/[/ . join(q/, /, @{$_}) . q/], /);
} for create_array [1, 2, 3, 4], 4, 1;
chop $s;
chop $s;
$s .= q/)/;
say $s;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
([1, 2], [3, 4]) 
([1, 2, 3]) 
([1], [2], [3], [4])
    

Part 2: Total XOR

You are given an array of integers. Write a script to return the sum of total XOR for every subset of given array.

This is another short one, but with a slightly more involved solution. We are going to compute the Power Set (set of all subsets) of the given array of integers and then for each of these sub-arrays compute and sum the XOR results.

"ch-2.pl" 4


use v5.40;
power set calculation 7
calculate the total XOR 6
main 5

The main section is just some basic tests.

main 5 ⟩≡


MAIN:{
say calculate_total_xor 1, 3;
say calculate_total_xor 5, 1, 6;
say calculate_total_xor 3, 4, 5, 6, 7, 8;
}

Fragment referenced in 4.

calculate the total XOR 6 ⟩≡


sub calculate_total_xor{
my $total = 0;
for my $a (power_set @_){
my $t = 0;
$t = eval join q/ ^ /, ($t, @{$a});
$total += $t;
}
return $total;
}

Fragment referenced in 4.

The Power Set can be computed by using a binary counter. Let’s say we have N elements of the set. We start at 0 x N and continue to 1 x N. At each iteration we compose a subarray by including the ith element from the original array if the ith bit is set. Actually, we arent going to start at 0 x N because we want to exclude the empty set for the purposes of the later XOR computation.

power set calculation 7 ⟩≡


sub power_set{
my @a = ();
for my $i (1 .. 2 ** @_- 1){
my @digits = ();
for my $j (0 .. @_ - 1){
push @digits, $_[$j] if 1 == ($i >> $j & 1);
}
push @a, \@digits;
}
return @a;
}

Fragment referenced in 4.

Sample Run
$ perl perl/ch-2.pl 
6 
28 
480
    

References

Power Set Defined
Power Set Calculcation (C++) from TWC 141
The Weekly Challenge 324
Generated Code

posted at: 12:36 by: Adam Russell | path: /perl | permanent link to this entry

2025-06-06

The Weekly Challenge 323 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Increment Decrement

You are given a list of operations. Write a script to return the final value after performing the given operations in order. The initial value is always 0.

Our solution will be contained in a single file that has the following structure.

"ch-1.p" 1


update input variables 4
state of the variables 2
process input 3
show final state of the variables 5
increment decrement 6

We’ll use a DCG approach to process the input and maintain the state of the variables.

First, let’s have some predicates for maintaining the state of the variables as the DCG processes the input.

state of the variables 2 ⟩≡


variables(VariableState), [VariableState] --> [VariableState].
variables(V, VariableState), [VariableState] --> [V].

Fragment referenced in 1.

Now we need to process the input, which we’ll treat as lists of character codes.

process input 3 ⟩≡


process(Input) --> variables(V, VariableState),
{Input = [Code1, Code2, Code3 | Codes],
Code1 == 43, Code2 == 43, Code3 >= 97,
Code3 =< 122,
increment_variable(Code3, V, VariableState)},
process(Codes).
process(Input) --> variables(V, VariableState),
{Input = [Code1, Code2, Code3 | Codes],
Code2 == 43, Code3 == 43, Code1 >= 97,
Code1 =< 122,
increment_variable(Code1, V, VariableState)},
process(Codes).
process(Input) --> variables(V, VariableState),
{Input = [Code1, Code2, Code3 | Codes],
Code1 == 45, Code2 == 45, Code3 >= 97,
Code3 =< 122,
decrement_variable(Code3, V, VariableState)},
process(Codes).
process(Input) --> variables(V, VariableState),
{Input = [Code1, Code2, Code3 | Codes],
Code2 == 45, Code3 == 45, Code1 >= 97,
Code1 =< 122,
decrement_variable(Code1, V, VariableState)},
process(Codes).
process(Input) --> variables(V, VariableState),
{Input = [Code | Codes],
Code >= 97, Code =< 122,
declare_variable(Code, V, VariableState)},
process(Codes).
process(Input) --> {Input = [Code | Codes],
Code == 32},
process(Codes).
process([]) --> [].

Fragment referenced in 1.

We’ll define some utility predicates for updating the state of the variables in our DCG input.

update input variables 4 ⟩≡


increment_variable(X, U, V):-
member(X-I, U),
delete(U, X-I, U1),
I1 is I + 1,
append([X-I1], U1, V).
increment_variable(X, U, V):-
\+ member(X-_, U),
append([X-1], U, V).
decrement_variable(X, U, V):-
member(X-I, U),
delete(U, X-I, U1),
I1 is I - 1,
append([X-I1], U1, V).
decrement_variable(X, U, V):-
\+ member(X-_, U),
append([X-(-1)], U, V).
declare_variable(X, U, V):-
delete(U, X-_, U1),
append([X-0], U1, V).

Fragment referenced in 1.

One more small utility predicate. This one is for displaying the final results. It’s intended to be called from maplist/2.

show final state of the variables 5 ⟩≡


show_variables(X-I):-
atom_codes(A, [X]),
write(A),
write(’:␣’),
write(I), nl.

Fragment referenced in 1.

Finally, let’s wrap the calls to the DCG in a small predicate using phrase/3.

increment decrement 6 ⟩≡


increment_decrement(Input):-
phrase(process(Input), [[]], [Output]), !,
maplist(show_variables, Output).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- increment_decrement("--xx++x++"). 
x: 1 
 
yes 
| ?- increment_decrement("x++++xx++"). 
x: 3 
 
(1 ms) yes 
| ?- increment_decrement("x++++x--xx--"). 
x: 0 
 
yes 
| ?- increment_decrement("abca++b++c++++a++b++c--a--b--ca--b--c--a++++bc++"). 
c: 1 
b: 1 
a: 1 
 
yes 
| ?-
    

Part 2: Tax Amount

You are given an income amount and tax brackets. Write a script to calculate the total tax amount.

While a DCG approach is also certainly possible for this second part we’ll go with a more plain recursive solution.

"ch-2.p" 7


compute taxes 8

The code is simple enough that it is pretty explainable in one single code section.

compute taxes 8 ⟩≡


compute_taxes(Income, TaxBrackets, Tax):-
compute_taxes(Income, TaxBrackets, 0, 0, Tax).
compute_taxes(0, _, 0, 0, 0).
compute_taxes(Income, [[Limit, Rate]|TaxBrackets], Taxable, Taxed, Tax):-
Limit =< Income,
Taxable1 is Limit - Taxable,
Taxed1 is Taxed + Taxable1,
compute_taxes(Income, TaxBrackets, Taxable1, Taxed1, Tax1),
Tax is Tax1 + (Taxable1 * (Rate/100)).
compute_taxes(Income, [[Limit, Rate]|_], _, Taxed, Tax):-
Limit > Income,
Tax is ((Income - Taxed) * (Rate/100)).

Fragment referenced in 7.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- compute_taxes(10, [[3, 50], [7, 10], [12,25]], Tax), format("$~2f", [Tax]). 
$2.65 
 
Tax = 2.6499999999999999 ? 
 
yes 
| ?- compute_taxes(2, [[1, 0], [4, 25], [5,50]], Tax), format("$~2f", [Tax]). 
$0.25 
 
Tax = 0.25 ? 
 
yes 
| ?- compute_taxes(0, [[2, 50]], Tax), format("$~2f", [Tax]). 
$0.00 
 
Tax = 0 ? 
 
yes 
| ?-
    

References

The Weekly Challenge 323
Generated Code

posted at: 17:05 by: Adam Russell | path: /prolog | permanent link to this entry

2025-06-05

Incremental Taxation

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Increment Decrement

You are given a list of operations. Write a script to return the final value after performing the given operations in order. The initial value is always 0.

Let’s entertain ourselves with an over engineered solution! We’ll use Parse::Yapp to handle incrementing and decrementing any single letter variable. Or, to put it another way, we’ll define a tiny language which consists of single letter variables that do not require declaration, are only of unsigned integer type, and are automatically initialized to zero. The only operations on these variables are the increment and decrement operations from the problem statement. At the completion of the parser’s execution we will print the final values of each variable.

The majority of the work will be done in the .yp yapp grammar definition file. We’ll focus on this file first.

"IncrementDecrement.yp" 1


declarations 2

%%

rules 5

%%

programs 6

The declarations section will have some token definitions and a global variable declaration.

declarations 2 ⟩≡


tokens 3
variables 4

Fragment referenced in 1.

For our simple language we’re just going to define a few tokens: the increment and decrement operators, our single letter variables.

tokens 3 ⟩≡


%token INCREMENT
%token DECREMENT
%token LETTER
%expect 2

Fragment referenced in 2.

We’re going to define a single global variable which will be used to track the state of each variable.

variables 4 ⟩≡


%{
my $variable_state = {};
%}

Fragment referenced in 2.

Defines: $variable_state 5, 10.

The rules section defines the actions of our increment and decrement operations in both prefix and postfix form. We’ll also allow for a completely optional variable declaration which is just placing a single letter variable by itself

rules 5 ⟩≡


program: statement {$variable_state}
| program statement
;

statement: variable_declaration
| increment_variable
| decrement_variable
;

variable_declaration: LETTER {$variable_state->{$_[1]} = 0}
;

increment_variable: INCREMENT LETTER {$variable_state->{$_[2]}++}
| LETTER INCREMENT {$variable_state->{$_[1]}++}
;

decrement_variable: DECREMENT LETTER {$variable_state->{$_[2]}--}
| LETTER DECREMENT {$variable_state->{$_[1]}--}
;

Fragment referenced in 1.

Uses: $variable_state 4.

The final section of the grammar definition file is, historically, called programs. This is where we have Perl code for the lexer, error handing, and a parse function which provides the main point of execution from code that wants to call the parser that has been generated from the grammar.

programs 6 ⟩≡


lexer 9
parse function 7
error handler 8
clear variables defined in the grammar definition file declarations 10

Fragment referenced in 1.

The parse function is for the convenience of calling the generated parser from other code. yapp will generate a module and this will be the module’s method used by other code to execute the parser against a given input.

Notice here that we are squashing white space, both tabs and newlines, using tr. This reduces all tabs and newlines to a single space. This eases further processing since extra whitespace is just ignored, according to the rules we’ve been given.

Also notice the return value from parsing. In the rules section we provide a return value, a hash reference, in the final action code block executed.

parse function 7 ⟩≡


sub parse{
my($self, $input) = @_;
$input =~ tr/\t/ /s;
$input =~ tr/\n/ /s;
$self->YYData->{INPUT} = $input;
my $result = $self->YYParse(yylex => \&lexer, yyerror => \&error);
return $result;
}

Fragment referenced in 6.

This is really just about the most minimal error handling function there can be! All this does is print “syntax error”when the parser encounters a problem.

error handler 8 ⟩≡


sub error{
exists $_[0]->YYData->{ERRMSG}
and do{
print $_[0]->YYData->{ERRMSG};
return;
};
print "syntax␣error\n";
}

Fragment referenced in 6.

The lexer function is called repeatedly for the entire input. Regular expressions are used to identify tokens (the ones declared at the top of the file) and pass them along for the rules processing.

lexer 9 ⟩≡


sub lexer{
my($parser) = @_;
$parser->YYData->{INPUT} or return(q//, undef);
$parser->YYData->{INPUT} =~ s/^[ \t]//g;
##
# send tokens to parser
##
for($parser->YYData->{INPUT}){
s/^(\s+)// and return (q/SPACE/, $1);
s/^([a-z]{1})// and return (q/LETTER/, $1);
s/^(\+\+)// and return (q/INCREMENT/, $1);
s/^(--)// and return (q/DECREMENT/, $1);
}
}

Fragment referenced in 6.

There’s one more function we should add. The reason for it is a little complex. Variables defined in the declarations section are considered static and are stored in the lexical pad of the package. So each new invocation of the parse() method will re-use the same variables. They are not cleared or reset. So, we’ll define a subroutine which will clear this for us manually.

clear variables defined in the grammar definition file declarations 10 ⟩≡


sub clear{
$variable_state = {};
}

Fragment referenced in 6.

Uses: $variable_state 4.

Let’s define a small file to drive some tests.

"ch-1.pl" 11


preamble 12
print final state of the variables 14
main 15

The preamble to the test driver sets the minimum perl version to be the most recent one, to take advantage of all recent changes. We also include the generated module file whihc yapp creates. For test purposes we’ll define some constants, taken from TWC’s examples.

preamble 12 ⟩≡


use v5.40;
use IncrementDecrement;
constant declarations 13

Fragment referenced in 11.

constant declarations 13 ⟩≡


use constant TEST0 => q/--x x++ x++/;
use constant TEST1 => q/x++ ++x x++/;
use constant TEST2 => q/x++ ++x --x x--/;
use constant COMPLEX_TEST => <<~END_TEST;
a b c
a++ b++ c++
++a ++b ++c
--a --b --c
a-- b-- c--
a++ ++b c++
END_TEST

Fragment referenced in 12.

For printing the results in a nice way we’ll define a small subroutine to display the return value from the parser.

print final state of the variables 14 ⟩≡


sub print_variables{
my($results) = @_;
for my $k (keys %{$results}){
print $k;
say qq/:\t$results->{$k}/;
}
}

Fragment referenced in 11.

main 15 ⟩≡


MAIN:{
my $parser = IncrementDecrement->new();
say TEST0;
say print_variables $parser->parse(TEST0);
say TEST1;
$parser->clear();
say print_variables $parser->parse(TEST1);
say TEST2;
$parser->clear();
say print_variables $parser->parse(TEST2);
say COMPLEX_TEST;
$parser->clear();
say print_variables $parser->parse(COMPLEX_TEST);
}

Fragment referenced in 11.

Sample Run
$ yapp -m IncrementDecrement perl/IncrementDecrement.yp; mv IncrementDecrement.pm perl; perl -Iperl perl/ch-1.pl 
--x x++ x++ 
x:      1 
 
x++ ++x x++ 
x:      3 
 
x++ ++x --x x-- 
x:      0 
 
a b c 
a++ b++ c++ 
++a ++b ++c 
--a --b --c 
a-- b-- c-- 
a++ ++b c++ 
 
b:      1 
a:      1 
c:      1
    

Part 2: Tax Amount

You are given an income amount and tax brackets. Write a script to calculate the total tax amount.

After over doing the complexity for the first part, we’ll make this one quite a bit shorter.

"ch-2.pl" 16


use v5.40;
calculate the total tax due 18
main 17

The main section is just some basic tests.

main 17 ⟩≡


MAIN:{
say calculate_tax 10, [[3, 50], [7, 10], [12,25]];
say calculate_tax 2, [[1, 0], [4, 25], [5,50]];
say calculate_tax 0, [[2, 50]];
}

Fragment referenced in 16.

calculate the total tax due 18 ⟩≡


sub calculate_tax{
my($income, $tax_brackets) = @_;
sort tax brackets by income 19
my $tax = 0;
my $taxed = 0;
my $taxable = 0;
iterate over the tax brackets and compute the tax 20
return $tax;
}

Fragment referenced in 16.

Defines: $income 20, $tax_brackets 19, 20.

Uses: $tax 20.

sort tax brackets by income 19 ⟩≡


$tax_brackets = [sort {$a->[0] <=> $b->[0]} @{$tax_brackets}];

Fragment referenced in 18.

Uses: $tax_brackets 18.

iterate over the tax brackets and compute the tax 20 ⟩≡


{
my $tax_bracket = shift @{$tax_brackets};
if($tax_bracket->[0] <= $income){
$taxable = $tax_bracket->[0] - $taxable;
$tax += ($taxable * ($tax_bracket->[1]/100));
$taxed += $taxable;
}
else{
$tax += (($income - $taxed) * ($tax_bracket->[1]/100));
$taxed = $income;
}
redo unless $taxed >= $income || @{$tax_brackets} == 0;
}

Fragment referenced in 18.

Defines: $tax 18.

Uses: $income 18, $tax_brackets 18.

Sample Run
$ perl perl/ch-2.pl 
2.65 
0.25 
0
    

References

The Weekly Challenge 323
Generated Code

posted at: 22:52 by: Adam Russell | path: /perl | permanent link to this entry

2025-05-26

The Weekly Challenge 322 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1:String Format

You are given a string and a positive integer. Write a script to format the string, removing any dashes, in groups of size given by the integer. The first group can be smaller than the integer but should have at least one character. Groups should be separated by dashes.

Our solution will be contained in a single file that has the following structure.

"ch-1.p" 1


state of the formatted string 2
process string 3
string format 4

We’ll use a DCG approach to process the string and maintain the result.

First, let’s have some predicates for maintaining the state of a character list as the DCG processes the string.

state of the formatted string 2 ⟩≡


format_(Format), [Format] --> [Format].
format_(F, Format), [Format] --> [F].

Fragment referenced in 1.

Now we need to process the strings, which we’ll treat as lists of character codes.

process string 3 ⟩≡


process(String, I, J) --> {String = [Code | Codes],
Code == 45},
process(Codes, I, J).
process(String, I, J) --> format_(F, Format),
{String = [Code | Codes],
\+ Code == 45,
succ(J, I),
char_code(C, Code),
length(Codes, L),
((L > 0, Format = [’-’, C|F]);
(Format = [C|F]))},
process(Codes, I, 0).
process(String, I, J) --> format_(F, Format),
{String = [Code | Codes],
\+ Code == 45,
succ(J, J1),
char_code(C, Code),
Format = [C|F]},
process(Codes, I, J1).
process([], _, _) --> [].

Fragment referenced in 1.

Finally, let’s wrap the calls to the DCG in a small predicate using phrase/3. We’re going to work from right to left so we’ll use reverse/2 to input into our DCG.

string format 4 ⟩≡


string_format(String, I, FormattedString):-
reverse(String, R),
phrase(process(R, I, 0), [[]], [F]), !,
atom_chars(FormattedString, F).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- string_format("ABC-D-E-F", 3, F). 
 
F = ’ABC-DEF’ 
 
yes 
| ?- string_format("A-BC-D-E", 2, F). 
 
F = ’A-BC-DE’ 
 
yes 
| ?- string_format("-A-B-CD-E", 4, F). 
 
F = ’A-BCDE’ 
 
yes 
| ?-
    

Part 2: Rank Array

You are given an array of integers. Write a script to return an array of the ranks of each element: the lowest value has rank 1, next lowest rank 2, etc. If two elements are the same then they share the same rank.

We’ll sort/2 the list of integers and then use the sroted list to look up the rank using nth/3. Remember, sort/2 removes duplicates! If it did not this approach would require extra work to first get the unique values.

"ch-2.p" 5


rank lookup  6
rank list 7

This is a predicate we’ll call via maplist.

rank lookup  6 ⟩≡


rank(SortedList, X, Rank):-
nth(Rank, SortedList, X).

Fragment referenced in 5.

We’ll define a predicate to do an initial sort and call rank/3.

rank list 7 ⟩≡


rank_list(L, Ranks):-
sort(L, Sorted),
maplist(rank(Sorted), L, Ranks).

Fragment referenced in 5.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- rank_list([55, 22, 44, 33], Ranks). 
 
Ranks = [4,1,3,2] ? 
 
yes 
| ?- rank_list([10, 10, 10], Ranks). 
 
Ranks = [1,1,1] ? 
 
yes 
| ?- rank_list([5, 1, 1, 4, 3], Ranks). 
 
Ranks = [4,1,1,3,2] ? 
 
yes 
| ?-
    

References

The Weekly Challenge 322
Generated Code

posted at: 00:31 by: Adam Russell | path: /prolog | permanent link to this entry

2025-05-25

Ordered Format Array

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: String Format

You are given a string and a positive integer. Write a script to format the string, removing any dashes, in groups of size given by the integer. The first group can be smaller than the integer but should have at least one character. Groups should be separated by dashes.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.pl" 1


preamble 2
process string as a list of characters 4
main 3

The preamble is just whatever we need to include. Here we aren’t using anything special, just specifying the latest Perl version.

preamble 2 ⟩≡


use v5.40;

Fragment referenced in 1, 5.

the main section is just some basic tests.

main 3 ⟩≡


MAIN:{
say string_format q/ABC-D-E-F/, 3;
say string_format q/A-BC-D-E/, 2;
say string_format q/-A-B-CD-E/, 4;
}

Fragment referenced in 1.

The approach is to maintain an array of arrays, with each sub-array being a new group of letters of the given size. We’ll process the string from right to left. This code seems to be well contained in a single subroutine. This sort of “stack processing” is straightforward enough to not require a lot of extra explanation.

process string as a list of characters 4 ⟩≡


sub string_format{
my($s, $i) = @_;
my @s = split //, $s;
my @t = ([]);
{
my $s_ = pop @s;
unless($s_ eq q/-/){
my $t_ = shift @t;
if(@{$t_} == $i){
unshift @t, $t_;
unshift @t, [$s_];
}
else{
unshift @{$t_}, $s_;
unshift @t, $t_;
}
}
redo if @s;
}
return join(q/-/, map {join q//, @{$_}} @t);
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
ABC-DEF 
A-BC-DE 
A-BCDE
    

Part 2: Rank Array

You are given an array of integers. Write a script to return an array of the ranks of each element:the lowest value has rank 1, next lowest rank 2, etc. If two elements are the same then they share the same rank.

Our solution will have the following structure.

"ch-2.pl" 5


preamble 2
number larger 9
rank the elements in a list 7
main 6

The main section is just some basic tests.

main 6 ⟩≡


MAIN:{
say q/(/ . join(q/, /, (rank_array 55, 22, 44, 33)) . q/)/;
say q/(/ . join(q/, /, (rank_array 10, 10, 10)) . q/)/;
say q/(/ . join(q/, /, (rank_array 5, 1, 1, 4, 3)) . q/)/;
}

Fragment referenced in 5.

Just for fun, no sort will be used to solve this problem! What we will do instead is define a subroutine to return the number of unique elements larger than a given number. The fun comes at a cost! This is an O(n2) method.

rank the elements in a list 7 ⟩≡


sub rank_array{
my(@i) = @_;
my %h;
my @unique = ();
determine unique values from the given array of integers 8
@unique = keys %h;
return map {number_larger $_, [@unique]} @i;
}

Fragment referenced in 5.

We use a hash to determine the unique values in the given array

determine unique values from the given array of integers 8 ⟩≡


do{$h{$_} = undef} for @i;

Fragment referenced in 7.

Here’s where we compute how many unique numbers are larger than any given one

number larger 9 ⟩≡


sub number_larger{
my($x, $unique) = @_;
return @{$unique} - grep {$_ > $x} @{$unique};
}

Fragment referenced in 5.

Sample Run
$ perl perl/ch-2.pl 
(4, 1, 3, 2) 
(1, 1, 1) 
(4, 1, 1, 3, 2)
    

References

The Weekly Challenge 322
Generated Code

posted at: 10:27 by: Adam Russell | path: /perl | permanent link to this entry

2025-05-18

The Weekly Challenge 321 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Distinct Average

You are given an array of numbers with even length. Write a script to return the count of distinct average. The average is calculate by removing the minimum and the maximum, then average of the two.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.p" 1


first last 2
distinct average 3

We’ll define a predicate for getting the minimum/maximum pairs. These will be the first/last pairs from a sorted list.

first last 2 ⟩≡


first_last([], []).
first_last(Numbers, FirstLastPairs):-
nth(1, Numbers, First),
last(Numbers, Last),
append([First|Rest], [Last], Numbers),
first_last(Rest, FirstLastPairs0),
append([[First, Last]], FirstLastPairs0, FirstLastPairs).

Fragment referenced in 1.

We just need a single predicate to sort the given list of numbers, call first_last/2, call maplist/2 with sum_list/2, sort/2 the results, and return the count of unique pairs. Since we only have pairs of numbers their averages will be the same if their sums are the same. (This also allows us to ignore potential floating point number annoyances). Also, remember that sort/2 will remove duplicates.

distinct average 3 ⟩≡


distinct_average(Numbers, DistinctAverage):-
sort(Numbers, NumbersSorted),
first_last(NumbersSorted, MinimumMaximumPairs),
maplist(sum_list, MinimumMaximumPairs, MinimumMaximumSums),
sort(MinimumMaximumSums, MinimumMaximumSumsSorted),
length(MinimumMaximumSumsSorted, DistinctAverage).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- distinct_average([1, 2, 4, 3, 5, 6], DistinctAverage). 
 
DistinctAverage = 1 ? 
 
yes 
| ?- distinct_average([0, 2, 4, 8, 3, 5], DistinctAverage). 
 
DistinctAverage = 2 ? 
 
yes 
| ?- distinct_average([7, 3, 1, 0, 5, 9], DistinctAverage). 
 
DistinctAverage = 2 ? 
 
yes 
| ?-
    

Part 2: Backspace Compare

You are given two strings containing zero or more #. Write a script to return true if the two given strings are same by treating # as backspace.

We’ll use a DCG approach to process the strings and maintain an list of characters.

"ch-2.p" 4


state of the character list 5
process string 6
backspace compare 7

Let’s have some predicates for maintaining the state of a character list as the DCG processes the string.

state of the character list 5 ⟩≡


characters(Characters), [Characters] --> [Characters].
characters(C, Characters), [Characters] --> [C].

Fragment referenced in 4.

Now we need to process the strings, which we’ll treat as lists of character codes.

process string 6 ⟩≡


process(String) --> characters(C, Characters),
{String = [Code | Codes],
last(C, PreviousCharacter),
((Code \== 35, char_code(C0, Code),
append(C, [C0], Characters));
(append(Characters, [PreviousCharacter], C))), !},
process(Codes).
process([]) --> [].

Fragment referenced in 4.

Finally, let’s wrap the calls to the DCG in a small predicate using phrase/3. This will process both strings and then compare the results.

backspace compare 7 ⟩≡


backspace_compare(String1, String2):-
phrase(process(String1), [[’’]], [R1]),
delete(R1, ’’, R2),
atom_chars(Result1, R2),
phrase(process(String2), [[’’]], [R3]),
delete(R3, ’’, R4),
atom_chars(Result2, R4),
Result1 == Result2.

Fragment referenced in 4.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- backspace_compare("ab#c", "ad#c"). 
 
yes 
| ?- backspace_compare("ab##", "a#b#"). 
 
yes 
| ?- backspace_compare("a#b", "c"). 
 
no 
| ?-
    

References

The Weekly Challenge 321
Generated Code

posted at: 13:02 by: Adam Russell | path: /prolog | permanent link to this entry

Back to a Unique Evaluation

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Distinct Average

You are given an array of numbers with even length. Write a script to return the count of distinct average. The average is calculate by removing the minimum and the maximum, then average of the two.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.pl" 1


preamble 2
distinct average calculation 4
main 3

The preamble is just whatever we need to include. Here we aren’t using anything special, just specifying the latest Perl version.

preamble 2 ⟩≡


use v5.40;

Fragment referenced in 1, 8.

the main section is just some basic tests.

main 3 ⟩≡


MAIN:{
say distinct_average 1, 2, 4, 3, 5, 6;
say distinct_average 0, 2, 4, 8, 3, 5;
say distinct_average 7, 3, 1, 0, 5, 9;
}

Fragment referenced in 1.

All the work is done in the following subroutine. This problem is straightforward enough to not require much more code than this.

To describe the details of this subroutine sections of it are separated out into their own code sections.

distinct average calculation 4 ⟩≡


sub distinct_average{
my @numbers = sort the given numbers in ascending order 5
my %averages;
loop over the sorted numbers, compute and track the averages 6
return 0 + keys %averages;
}

Fragment referenced in 1.

Defines: %averages Never used, @numbers 6, @_ 5.

sort the given numbers in ascending order 5 ⟩≡


sort {$a <=> $b} @_;

Fragment referenced in 4.

Uses: @_ 4.

loop over the sorted numbers, compute and track the averages 6 ⟩≡


for my $i (0 .. (@numbers / 2)){
my($x, $y) = ($numbers[$i], $numbers[@numbers - 1 - $i]);
$averages{average computed to 7 decimal place 7 } = undef;
}

Fragment referenced in 4.

Defines: $x 7, $y 7.

Uses: @numbers 4.

average computed to 7 decimal place 7 ⟩≡


sprintf(q/%0.7f/, (($x + $y)/2))

Fragment referenced in 6.

Uses: $x 6, $y 6.

Sample Run
$ perl perl/ch-1.pl 
1 
2 
2
    

Part 2: Backspace Compare

You are given two strings containing zero or more #. Write a script to return true if the two given strings are same by treating # as backspace.

Our solution will have the following structure.

"ch-2.pl" 8


preamble 2
process strings  10
main 9

The main section is just some basic tests.

main 9 ⟩≡


MAIN:{
say backspace_compare q/ab#c/, q/ad#c/;
say backspace_compare q/ab##/, q/a#b#/;
say backspace_compare q/a#b/, q/c/;
}

Fragment referenced in 8.

The approach is to maintain two arrays (think of them as stacks), one for each string. As we process each string we will push a character onto the stack as each non-# character is encountered. We’ll pop a character from the stack for every # encountered. When both strings have been processed we’ll compare the two resulting stacks. This code seems to be well contained in a single subroutine.

process strings  10 ⟩≡


sub backspace_compare{
my($s, $t) = @_;
my @s = split //, $s;
my @t = split //, $t;
my @u = ();
my @v = ();
{
my $s_ = shift @s || undef;
my $t_ = shift @t || undef;
push @u, $s_ if $s_ && $s_ ne q/#/;
push @v, $t_ if $t_ && $t_ ne q/#/;
pop @u if $s_ && $s_ eq q/#/;
pop @v if $t_ && $t_ eq q/#/;
redo if @s || @t;
}
return join(q//, @u) eq join(q//, @v)?q/true/:q/false/;
}

Fragment referenced in 8.

Sample Run
$ perl perl/ch-2.pl 
true 
true 
false
    

References

The Weekly Challenge 321
Generated Code

posted at: 13:01 by: Adam Russell | path: /perl | permanent link to this entry

2025-05-11

The Weekly Challenge 320 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Maximum Count

You are given an array of integers. Write a script to return the maximum between the number of positive and negative integers. Zero is neither positive nor negative.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.p" 1


identify negatives 2
identify positives 3
count negatives 4
count positives 5
maximum count 6

We’ll define two predicates for counting the number of negative and positive numbers. These will use small helper predicates to be called via maplist.

identify negatives 2 ⟩≡


identify_negatives(Number, 1):-
Number < 0.
identify_negatives(_, 0).

Fragment referenced in 1.

identify positives 3 ⟩≡


identify_positives(Number, 1):-
Number > 0.
identify_positives(_, 0).

Fragment referenced in 1.

count negatives 4 ⟩≡


count_negatives(Numbers, Count):-
maplist(identify_negatives, Numbers, Negatives),
sum_list(Negatives, Count).

Fragment referenced in 1.

count positives 5 ⟩≡


count_positives(Numbers, Count):-
maplist(identify_positives, Numbers, Positives),
sum_list(Positives, Count).

Fragment referenced in 1.

We’ll need a predicate to tie everything together, that’s what this next one does.

maximum count 6 ⟩≡


maximum_count(Numbers, MaximumCount):-
count_negatives(Numbers, NegativesCount),
count_positives(Numbers, PositivesCount),
max_list([NegativesCount, PositivesCount], MaximumCount).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- maximum_count([-3, -2, -1, 1, 2, 3], MaximumCount). 
 
MaximumCount = 3 ? 
 
yes 
| ?- maximum_count([-2, -1, 0, 0, 1], MaximumCount). 
 
MaximumCount = 2 ? 
 
yes 
| ?- maximum_count([1, 2, 3, 4], MaximumCount). 
 
MaximumCount = 4 ? 
 
yes 
| ?-
    

Part 2: Sum Differences

You are given an array of positive integers. Write a script to return the absolute difference between digit sum and element sum of the given array.

As in the first part, our solution will be pretty short, contained in just a single file.

"ch-2.p" 7


char_number 10
element sum 8
digit sum 9
sum differences 11

The element sum is a straightforward application of the builtin predicate sum_list/2.

element sum 8 ⟩≡


element_sum(Numbers, ElementSum):-
sum_list(Numbers, ElementSum).

Fragment referenced in 7.

To compute the digit sum we’ll first convert them to characters, via maplist, flatten the list, convert them back to numbers, and take the sum_list.

digit sum 9 ⟩≡


digit_sum(Numbers, DigitSum):-
maplist(number_chars, Numbers, Characters),
flatten(Characters, CharactersFlattened),
maplist(char_number, CharactersFlattened, Digits),
sum_list(Digits, DigitSum).

Fragment referenced in 7.

The above predicate, for convenience of the maplist, requires a small helper predicate to reverse the arguments of numbers_chars.

char_number 10 ⟩≡


char_number(C, N):-
number_chars(N, [C]).

Fragment referenced in 7.

sum_difference/2 is the main predicate, which calls the others we’ve defined so far.

sum differences 11 ⟩≡


sum_differences(Numbers, Differences):-
element_sum(Numbers, ElementSum),
digit_sum(Numbers, DigitSum),
Differences is abs(DigitSum - ElementSum).

Fragment referenced in 7.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- sum_differences([1, 23, 4, 5], SumDifferences). 
 
SumDifferences = 18 
 
yes 
| ?- sum_differences([1, 2, 3, 4, 5], SumDifferences). 
 
SumDifferences = 0 
 
yes 
| ?- sum_differences([1, 2, 34], SumDifferences). 
 
SumDifferences = 27 
 
yes 
| ?-
    

References

The Weekly Challenge 320
Generated Code

posted at: 13:04 by: Adam Russell | path: /prolog | permanent link to this entry

2025-05-10

Summit of Count Deviation

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Maximum Count

You are given an array of integers. Write a script to return the maximum between the number of positive and negative integers. Zero is neither positive nor negative.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.pl" 1


preamble 2
filter and count the positive/negative numbers, compute maximum 4
main 3

The preamble is just whatever we need to include. Here we aren’t using anything special, just specifying the latest Perl version.

preamble 2 ⟩≡


use v5.40;

Fragment referenced in 1, 7.

the main section is just some basic tests.

main 3 ⟩≡


MAIN:{
say maximum_count -3, -2, -1, 1, 2, 3;
say maximum_count -2, -1, 0, 0, 1;
say maximum_count 1, 2, 3, 4;
}

Fragment referenced in 1.

All the work is done in the following subroutine.

filter and count the positive/negative numbers, compute maximum 4 ⟩≡


sub maximum_count{
my @numbers = @_;
filter negatives 5
filter positives 6
return (sort {$b <=> $a} ($positives, $negatives))[0];
}

Fragment referenced in 1.

Defines: @numbers 5, 6.

Uses: $negatives 5, $positives 6.

We do the filtering with a grep.

filter negatives 5 ⟩≡


my $negatives = 0 + grep {$_ < 0} @numbers;

Fragment referenced in 4.

Defines: $negatives 4.

Uses: @numbers 4.

filter positives 6 ⟩≡


my $positives = 0 + grep {$_ > 0} @numbers;

Fragment referenced in 4.

Defines: $positives 4.

Uses: @numbers 4.

Sample Run
$ perl perl/ch-1.pl 
3 
2 
4
    

Part 2: Sum Difference

You are given an array of positive integers. Write a script to return the absolute difference between digit sum and element sum of the given array.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-2.pl" 7


preamble 2
compute the digit sum and element sum and then subtract 9
main 8

The main section is just some basic tests.

main 8 ⟩≡


MAIN:{
say sum_difference 1, 23, 4, 5;
say sum_difference 1, 2, 3, 4, 5;
say sum_difference 1, 2, 34;
}

Fragment referenced in 7.

All the work is done in the following subroutine.

compute the digit sum and element sum and then subtract 9 ⟩≡


sub sum_difference{
my @numbers = @_;
digit sum 10
element sum 11
return abs($digit_sum - $element_sum);
}

Fragment referenced in 7.

Defines: @numbers 10, 11.

Uses: $digit_sum 10, $element_sum 11.

We compute the digit sum by splitting each element as a string and then summing the list of digits.

digit sum 10 ⟩≡


my @digits;
do{
push @digits, split //, $_;
} for @numbers;
my $digit_sum = unpack(q/%32I*/, pack(q/I*/, @digits));

Fragment referenced in 9.

Defines: $digit_sum 9.

Uses: @numbers 9.

The element sum is a straightforward summing of the elements.

element sum 11 ⟩≡


my $element_sum = unpack(q/%32I*/, pack(q/I*/, @numbers));

Fragment referenced in 9.

Defines: $element_sum 9.

Uses: @numbers 9.

Sample Run
$ perl perl/ch-2.pl 
18 
0 
27
    

References

The Weekly Challenge 320
Generated Code

posted at: 19:15 by: Adam Russell | path: /perl | permanent link to this entry

2025-05-04

The Weekly Challenge 319 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Word Count

You are given a list of words containing alphabetic characters only. Write a script to return the count of words either starting with a vowel or ending with a vowel.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.p" 1


vowels 2
start_end_vowel 3
word count 4

We’re going to be using character codes for this. For convenience let’s declare are vowels this way.

vowels 2 ⟩≡


vowel(97). % a
vowel(101). % e
vowel(105). % i
vowel(111). % o
vowel(117). % u

Fragment referenced in 1.

We’ll use a small predicate, later to be called from maplist, to check if a word starts or ends with a vowel.

start_end_vowel 3 ⟩≡


start_end_vowel(Word, StartsEnds):-
((nth(1, Word, FirstLetter),
vowel(FirstLetter));
(last(Word, LastLetter),
vowel(LastLetter))),
StartsEnds = true.
start_end_vowel(_, -1).

Fragment referenced in 1.

We’ll need a predicate to tie everything together, that’s what this next one does.

word count 4 ⟩≡


word_count(Words, Count):-
maplist(start_end_vowel, Words, StartsEndsAll),
delete(StartsEndsAll, -1, StartsEnds),
length(StartsEnds, Count).

Fragment referenced in 1.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- word_count(["unicode", "xml", "raku", "perl"], Count). 
 
Count = 2 ? 
 
yes 
| ?- word_count(["the", "weekly", "challenge"], Count). 
 
Count = 2 ? 
 
yes 
| ?- word_count(["perl", "python", "postgres"], Count). 
 
Count = 0 
 
yes 
| ?-
    

Part 2: Minimum Common

You are given two arrays of integers. Write a script to return the minimum integer common to both arrays. If none found return -1.

As in the first part, our solution will be pretty short, contained in just a single file.

"ch-2.p" 5


minimum common 7

To check for common elements is easy in Prolog. First we subtract/3 all elements of one list from the other. That will give us the unique elements. Then we’ll delete the unique elements from one of the original lists to get all common elements. After that min_list/2 determines the result.

subtract lists to determine common elemets 6 ⟩≡


subtract(List1, List2, Difference1),
subtract(List2, List1, Difference2),
append(Difference1, Difference2, Differences),
subtract(List1, Differences, Common),

Fragment referenced in 7.

Defines: Common 7.

minimum_common/3 is the main (and only) predicate we define

minimum common 7 ⟩≡


minimum_common(List1, List2, MinimumCommon):-
subtract lists to determine common elemets 6
length(Common, L),
L >= 1,
min_list(Common, MinimumCommon).
minimum_common(_, _, -1).

Fragment referenced in 5.

Uses: Common 6.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- minimum_common([1, 2, 3, 4], [3, 4, 5, 6], MinimumCommon). 
 
MinimumCommon = 3 ? 
 
yes 
| ?- minimum_common([1, 2, 3], [2, 4], MinimumCommon). 
 
MinimumCommon = 2 ? 
 
yes 
| ?- minimum_common([1, 2, 3, 4], [5, 6, 7, 8], MinimumCommon). 
 
MinimumCommon = -1 
 
yes 
| ?-
    

References

The Weekly Challenge 319
Generated Code

posted at: 14:47 by: Adam Russell | path: /prolog | permanent link to this entry

In the Count of Common

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Word Count

You are given a list of words containing alphabetic characters only. Write a script to return the count of words either starting with a vowel or ending with a vowel.

Our solution will be pretty short, contained in just a single file that has the following structure.

"ch-1.pl" 1


preamble 2
count the words that begin or end with a vowel 4
main 3

The preamble is just whatever we need to include. Here we aren’t using anything special, just specifying the latest Perl version.

preamble 2 ⟩≡


use v5.40;

Fragment referenced in 1, 6.

the main section is just some basic tests.

main 3 ⟩≡


MAIN:{
say word_count qw/unicode xml raku perl/;
say word_count qw/the weekly challenge/;
say word_count qw/perl python postgres/;
}

Fragment referenced in 1.

All the work is done in the count section which contains a single small subroutine.

count the words that begin or end with a vowel 4 ⟩≡


sub word_count{
return 0 + grep { start/end vowel check 5 } @_;
}

Fragment referenced in 1.

For clarity we’ll break that vowel check into it’s own code section. It’s not too hard. We use the beginning and ending anchors (^, $) to see if there is a character class match at the beginning or end of the word.

start/end vowel check 5 ⟩≡


$_ =~ m/^[aeiou]/ || $_ =~ m/.*[aeiou]$/

Fragment referenced in 4.

Sample Run
$ perl perl/ch-1.pl 
2 
2 
0
    

Part 2: Minimum Common

You are given two arrays of integers. Write a script to return the minimum integer common to both arrays. If none found return -1.

As in the first part, our solution will be pretty short, contained in just a single file that has the following structure.

(The preamble is going to be the same as before, we don’t need anything extra for this problem either.)

The main section just drives a few tests.

The subroutine that gets the bulk of the solution started is in this section.

"ch-2.pl" 6


preamble 2
sub minimum_common{
my($u, $v) = @_;
find common elements 7
return $minimum;
}
main 8

Defines: $u 7, $v 7.

Uses: $minimum 7.

The real work is done in this section. We determine the unique elements by creating two separate hashes and then, using the keys to each hash, count the number of common elements. We then sort the common elements, if there are any, and set $minimum to be the smallest one.

find common elements 7 ⟩≡


my %h = ();
my %h_u = map {$_ => undef} @{$u};
my %h_v = map {$_ => undef} @{$v};
my $minimum = -1;
do{
$h{$_}++;
} for (keys %h_u, keys %h_v);
my @common = grep {$h{$_} > 1} keys %h;
if(0 < @common){
$minimum = (sort {$a <=> $b} @common)[0];
}

Fragment referenced in 6.

Defines: $minimum 6.

Uses: $u 6, $v 6.

main 8 ⟩≡


MAIN:{
say minimum_common [1, 2, 3, 4], [3, 4, 5, 6];
say minimum_common [1, 2, 3], [2, 4];
say minimum_common [1, 2, 3, 4], [5, 6, 7, 8];
}

Fragment referenced in 6.

Sample Run
$ perl perl/ch-2.pl 
3 
2 
-1
    

References

The Weekly Challenge 319
Generated Code

posted at: 12:25 by: Adam Russell | path: /perl | permanent link to this entry

2025-04-27

The Weekly Challenge 318 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Group Position

You are given a string of lowercase letters. Write a script to find the position of all groups in the given string. Three or more consecutive letters form a group. Return “” if none found.

We can do this in a single predicate which uses maplist to get the groupings with a small utility predicate.

utility predicate for finding groups 1 ⟩≡


group(Letters, Letter, Group):-
length(Letters, LengthLetters),
delete(Letters, Letter, Deleted),
length(Deleted, LengthDeleted),
Difference is LengthLetters - LengthDeleted,
Difference >= 3,
length(G1, Difference),
maplist(=(Letter), G1),
append(G1, _, G2),
append(_, G2, Letters),
atom_codes(Group, G1).
group(_, _, nil).

Fragment referenced in 3.

groupings 2 ⟩≡


groupings(Word, Groupings):-
sort(Word, UniqueLetters),
maplist(group(Word), UniqueLetters, Groups),
delete(Groups, nil, Groupings).

Fragment referenced in 3.

The rest of the code just wraps this single predicate into a file.

"ch-1.p" 3


utility predicate for finding groups 1
groupings 2

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- groupings("abccccd", Groupings). 
 
Groupings = [cccc] ? 
 
yes 
| ?- groupings("aaabcddddeefff", Groupings). 
 
Groupings = [aaa,dddd,fff] ? 
 
yes 
| ?- groupings("abcdd", Groupings). 
 
Groupings = [] 
 
yes 
| ?-
    

Part 2: Reverse Equals

You are given two arrays of integers, each containing the same elements as the other. Write a script to return true if one array can be made to equal the other by reversing exactly one contiguous subarray.

This is going to be a quick one, but there’s going to be a few pieces we need to take care of. First we will check that we can subtract/3 the two words (character code lists) and obtain an empty list. Then we’ll check in which places the words differ. If they differ in one place or more then we’re done. Otherwise we’ll test the reversal of the sublist.

test elements 4 ⟩≡


subtract(List1, List2, []),

Fragment referenced in 8.

Uses: List1 8, List2 8.

find differences 5 ⟩≡


length(List1, Length),
findall(I, (
between(1, Length, I),
nth(I, List1, C1),
nth(I, List2, C2),
\+ C1 = C2
), DifferenceIndices),

Fragment referenced in 8.

Defines: DifferenceIndices 6, 8.

Uses: List1 8, List2 8.

get sublists 6 ⟩≡


length(DifferenceIndices, NumberDifferences),
NumberDifferences > 0,
nth(1, DifferenceIndices, FirstIndex),
last(DifferenceIndices, LastIndex),
findall(E, (
between(FirstIndex, LastIndex, I),
nth(I, List1, E)
), SubList1),
findall(E, (
between(FirstIndex, LastIndex, I),
nth(I, List2, E)
), SubList2),

Fragment referenced in 8.

Defines: SubList1 7, SubList2 7.

Uses: DifferenceIndices 5, List1 8, List2 8.

test sublists and their reversals 7 ⟩≡


reverse(SubList1, Reverse1),
reverse(SubList2, Reverse2),
append(SubList1, Suffix1, S1),
append(SubList2, Suffix2, S2),
append(Reverse1, Suffix1, S3),
append(Reverse2, Suffix2, S4),
append(Prefix1, S1, List1),
append(Prefix2, S2, List2),
(append(Prefix1, S3, List2); append(Prefix2, S4, List1))

Fragment referenced in 8.

Uses: List1 8, List2 8, SubList1 6, SubList2 6.

All these pieces will be assembled into reverse_equals/2.

reverse equals 8 ⟩≡


reverse_equals(List1, List2):-
test elements 4
find differences 5
get sublists 6
test sublists and their reversals 7 .
reverse_equals(List1, List2):-
test elements 4
find differences 5
length(DifferenceIndices, NumberDifferences),
NumberDifferences = 0.

Fragment referenced in 9.

Defines: List1 4, 5, 6, 7, List2 4, 5, 6, 7.

Uses: DifferenceIndices 5.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 9


reverse equals 8

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- reverse_equals([3, 2, 1, 4], [1, 2, 3, 4]). 
 
true ? 
 
yes 
| ?- reverse_equals([1, 3, 4], [4, 1, 3]). 
 
no 
| ?- reverse_equals([2], [2]). 
 
yes 
| ?-
    

References

The Weekly Challenge 318
Generated Code

posted at: 23:10 by: Adam Russell | path: /prolog | permanent link to this entry

Group Position Reversals

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Group Position

You are given a string of lowercase letters. Write a script to find the position of all groups in the given string. Three or more consecutive letters form a group. Return “” if none found.

Here’s our one subroutine, this problem requires very little code.

groupings 1 ⟩≡


sub groupings{
my($s) = @_;
my @groups;
my @group;
my($current, $previous);
my @letters = split //, $s;
$previous = shift @letters;
@group = ($previous);
do {
$current = $_;
if($previous eq $current){
push @group, $current;
}
if($previous ne $current){
if(@group >= 3){
push @groups, [@group];
}
@group = ($current);
}
$previous = $current;
} for @letters;
if(@group >= 3){
push @groups, [@group];
}
my @r = map {q/"/␣.␣join(q//,␣@{$_}) . q/"/␣}␣@groups;
return join(q/, /, @r) || q/""/;
}

Fragment referenced in 2.

Putting it all together...

"ch-1.pl" 2


preamble 3
groupings 1
main 4

preamble 3 ⟩≡


use v5.40;

Fragment referenced in 2, 7.

The rest of the code just runs some basic tests.

main 4 ⟩≡


MAIN:{
say groupings q/abccccd/;
say groupings q/aaabcddddeefff/;
say groupings q/abcdd/;
}

Fragment referenced in 2.

Sample Run
$ perl perl/ch-1.pl 
"cccc" 
"aaa", "dddd", "fff" 
""
    

Part 2: Reverse Equals

You are given two arrays of integers, each containing the same elements as the other. Write a script to return true if one array can be made to equal the other by reversing exactly one contiguous subarray.

Here’s the process we’re going to follow.

  1. scan both arrays and check where and how often they differ
  2. if they differ in zero places return true!
  3. if they differ in one or more places check to see if the reversal makes the two arrays equal

scan both arrays 5 ⟩≡


my $indices_different = [];
for my $i (0 .. @{$u} - 1){
push @{$indices_different}, $i unless $u->[$i] eq $v->[$i];
}

Fragment referenced in 7.

Defines: $indices_different 6.

Uses: $u 7, $v 7.

Now let’s check and see how many differences were found.

review the differences found 6 ⟩≡


return 1 if @{$indices_different} == 0;
$indices_different = [sort {$a <=> $b} @{$indices_different}];
my $last_i = $indices_different->[@{$indices_different} - 1];
my $length = 1 + $last_i - $indices_different->[0];
my @u_ = reverse @{$u}[$indices_different->[0] .. $last_i];
my @v_ = reverse @{$v}[$indices_different->[0] .. $last_i];
splice @{$u}, $indices_different->[0], $length, @u_;
splice @{$v}, $indices_different->[0], $length, @v_;
return 1 if join(q/,/, @{$u}) eq join(q/,/, @{$t});
return 1 if join(q/,/, @{$v}) eq join(q/,/, @{$s});
return 0;

Fragment referenced in 7.

Uses: $indices_different 5, $s 7, $t 7, $u 7, $v 7.

The rest of the code combines the previous steps and drives some tests.

"ch-2.pl" 7


preamble 3
sub reverse_equals{
my($u, $v) = @_;
my($s, $t) = ([@{$u}], [@{$v}]);
scan both arrays 5
review the differences found 6
}
main 8

Defines: $s 6, $t 6, $u 5, 6, $v 5, 6.

main 8 ⟩≡


MAIN:{
say reverse_equals [3, 2, 1, 4], [1, 2, 3, 4];
say reverse_equals [1, 3, 4], [4, 1, 3];
say reverse_equals [2], [2];
}

Fragment referenced in 7.

Sample Run
$ perl perl/ch-2.pl 
1 
0 
1
    

References

The Weekly Challenge 318
Generated Code

posted at: 19:21 by: Adam Russell | path: /perl | permanent link to this entry

2025-04-19

The Weekly Challenge 317 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Acronyms

You are given an array of words and a word. Write a script to return true if concatenating the first letter of each word in the given array matches the given word, return false otherwise.

We can do this in a single predicate which uses maplist to get the first character from each word, which we’ll take as a list of character code lists.

acronym 1 ⟩≡


acronym(Words, Word):-
maplist(nth(1), Words, FirstLetters),
Word = FirstLetters.

Fragment referenced in 2.

The rest of the code just wraps this single predicate into a file.

"ch-1.p" 2


acronym 1

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- acronym(["Perl", "Weekly", "Challenge"], "PWC"). 
 
yes 
| ?- acronym(["Bob", "Charlie", "Joe"], "BCJ"). 
 
yes 
| ?- acronym(["Morning", "Good"], "MM"). 
 
no 
| ?-
    

Part 2: Friendly Strings

You are given two strings. Write a script to return true if swapping any two letters in one string match the other string, return false otherwise.

This is going to be a quick one. First we will check that we can subtract/3 the two words (character code lists) and obtain an empty list. Then we’ll check in which places the words differ. They must only differ in exactly two places.

friendly 3 ⟩≡


friendly(Word1, Word2):-
subtract(Word1, Word2, []),
length(Word1, Length),
findall(Difference, (
between(1, Length, I),
nth(I, Word1, C1),
nth(I, Word2, C2),
\+ C1 = C2,
Difference = [C1, C2]
), Differences),
length(Differences, NumberDifferences),
NumberDifferences == 2.

Fragment referenced in 4.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 4


friendly 3

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- friendly("desc", "dsec"). 
 
yes 
| ?- friendly("cat", "dog"). 
 
no 
| ?- friendly("stripe", "sprite"). 
 
yes 
| ?-
    

References

The Weekly Challenge 317
Generated Code

posted at: 21:39 by: Adam Russell | path: /prolog | permanent link to this entry

2025-04-17

Acronyms Among Friends

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Acronyms

You are given an array of words and a word. Write a script to return true if concatenating the first letter of each word in the given array matches the given word, return false otherwise.

Here’s our one subroutine, this problem requires very little code.

acronyms 1 ⟩≡


sub acronyms{
my($word_list, $word) = @_;
my @first_letters = map{
(split //, $_)[0]
} @{$word_list};
return 1 if $word eq join q//, @first_letters;
return 0;
}

Fragment referenced in 2.

Putting it all together...

"ch-1.pl" 2


preamble 3
acronyms 1
main 4

preamble 3 ⟩≡


use v5.40;

Fragment referenced in 2, 7.

The rest of the code just runs some simple tests.

main 4 ⟩≡


MAIN:{
say acronyms([qw/Perl Weekly Challenge/], q/PWC/);
say acronyms([qw/Bob Charlie Joe/], q/BCJ/);
say acronyms([qw/Morning Good/], q/MM/);
}

Fragment referenced in 2.

Sample Run
$ perl perl/ch-1.pl 
1 
1 
0
    

Part 2: Friendly Strings

You are given two strings. Write a script to return true if swapping any two letters in one string match the other string, return false otherwise.

Here’s the process we’re going to follow.

  1. scan both words and check where and how often they differ
  2. if they differ in zero places return true!
  3. if they differ in one place or more than two places return false
  4. if they differ in two places and the two pairs of letters are the same return true

scan both words 5 ⟩≡


my $differences = [];
my @u = split //, $u;
my @v = split //, $v;
{
my $u_ = pop @u;
my $v_ = pop @v;
push @{$differences}, [$u_, $v_] unless $u_ eq $v_;
redo unless !@u || !@v;
}

Fragment referenced in 7.

Defines: $differences 6.

Uses: $u 7, $v 7.

Now let’s check and see how many differences were found.

review the differences found 6 ⟩≡


return 1 if @{$differences} == 0;
return 0 if @{$differences} == 1 || @{$differences} > 2;
my @s = sort {$a cmp $b} @{$differences->[0]};
my @t = sort {$a cmp $b} @{$differences->[1]};
return 1 if $s[0] eq $t[0] && $s[1] eq $t[1];
return 0;

Fragment referenced in 7.

Uses: $differences 5.

The rest of the code combines the previous steps and drives some tests.

"ch-2.pl" 7


preamble 3
sub friendly{
my($u, $v) = @_;
scan both words 5
review the differences found 6
}
main 8

Defines: $u 5, $v 5.

main 8 ⟩≡


MAIN:{
say friendly q/desc/, q/dsec/;
say friendly q/cat/, q/dog/;
say friendly q/stripe/, q/sprite/;
}

Fragment referenced in 7.

Sample Run
$ perl perl/ch-2.pl 
1 
0 
1
    

References

The Weekly Challenge 317
Generated Code

posted at: 20:08 by: Adam Russell | path: /perl | permanent link to this entry

2025-04-12

The Weekly Challenge 316 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Circular

You are given a list of words. Write a script to find out whether the last character of each word is the first character of the following word.

We can do this in a single predicate which recursively examines the list of words, which we’ll take as a list of character code lists.

circular 1 ⟩≡


circular([]).
circular([_]).
circular([H0, H1|T]):-
last(H0, C0),
nth(1, H1, C1),
C0 = C1,
circular([H1|T]).

Fragment referenced in 2.

The rest of the code just wraps this predicate into a file.

"ch-1.p" 2


circular 1

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- circular(["perl", "loves", "scala"]). 
 
true ? 
 
(1 ms) yes 
| ?- circular(["love", "the", "programming"]). 
 
no 
| ?- circular(["java", "awk", "kotlin", "node.js"]). 
 
true ? 
 
yes 
| ?-
    

Part 2: Subsequence

You are given two strings. Write a script to find out if one string is a subsequence of another.

This is going to be a quick one, seeing as GNU Prolog has a sublist/2 predicate which does exactly this! As in the previous part we’ll take the strings as lists of character codes.

subsequence 3 ⟩≡


subsequence(S, T):-
sublist(S, T).

Fragment referenced in 4.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 4


subsequence 3

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- subsequence("uvw", "bcudvew"). 
 
true ? 
 
yes 
| ?- subsequence("aec", "abcde"). 
 
no 
| ?- subsequence("sip", "javascript"). 
 
true ? 
 
yes 
| ?-
    

References

The Weekly Challenge 316
Generated Code

posted at: 23:32 by: Adam Russell | path: /prolog | permanent link to this entry

Going Around in Sequential Circles

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Circular

You are given a list of words. Write a script to find out whether the last character of each word is the first character of the following word.

This seems straightforward enough. One question is whether we need to only consider the words in their given order. We’ll assume so.

Here’s our one subroutine, this problem requires very little code.

circular 1 ⟩≡


sub circular{
my $current = shift @_;
my $current_last = (split //, $current)[length($current) - 1];
do{
my $previous_last = $current_last;
$current = $_;
my $current_first = (split //, $current)[0];
$current_last = (split //, $current)[length($current) - 1];
return 0 if $previous_last ne $current_first;
} for @_;
return 1;
}

Fragment referenced in 2.

Putting it all together...

"ch-1.pl" 2


preamble 3
circular 1
main 4

preamble 3 ⟩≡


use v5.40;

Fragment referenced in 2, 8.

The rest of the code just runs some simple tests.

main 4 ⟩≡


MAIN:{
say circular(qw/perl loves scala/);
say circular(qw/love the programming/);
say circular(qw/java awk kotlin node.js/);
}

Fragment referenced in 2.

Sample Run
$ perl perl/ch-1.pl 
1 
0 
1
    

Part 2: Subsequence

You are given two strings. Write a script to find out if one string is a subsequence of another.

A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters.

We’re going to do this in teh shortest way possible, via a regular expression.

We’re going to construct the regular expression dynamically each time

construct regex 5 ⟩≡


my $pattern = join q/.*/, split //, $s;
my $regex = qr/^.*$pattern.*$/;

Fragment referenced in 7.

Defines: $regex 7.

Uses: $s 6.

The shorter of the two strings will be what we test as the potential subsequence of the other longer one.

determine shortest/longest string 6 ⟩≡


my($s, $t) = length $s0 > length $s1? ($s1, $s0): ($s0, $s1);

Fragment referenced in 7.

Defines: $s 5, $t 7.

We’re going to have the work done in a single subroutine which determines which string to test, builds the regex, and runs it.

subsequence 7 ⟩≡


sub subsequence{
my($s0, $s1) = @_;
determine shortest/longest string 6
construct regex 5
return 0 + $t =~ $regex;
}

Fragment referenced in 8.

Uses: $regex 5, $t 6.

The rest of the code drives some tests.

"ch-2.pl" 8


preamble 3
subsequence 7
main 9

main 9 ⟩≡


MAIN:{
say subsequence q/uvw/, q/bcudvew/;
say subsequence q/aec/, q/abcde/;
say subsequence q/sip/, q/javascript/;
}

Fragment referenced in 8.

Sample Run
$ perl perl/ch-2.pl 
1 
0 
1
    

References

The Weekly Challenge 316
Generated Code

posted at: 22:58 by: Adam Russell | path: /perl | permanent link to this entry

2025-04-06

The Weekly Challenge 315 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Find Words

You are given a list of words and a character. Write a script to return the index of word in the list where you find the given character.

This can be done with a basic predicate with findall. Or we can use maplist! maplist is more fun in my opinion, so we’ll go with that.

First off a small utility predicate to see if a word contains a given letter.

contains letter 1 ⟩≡


contains_letter(Letter, Index-Word, Index):-
atom_chars(Word, C),
member(Letter, C).
contains_letter(_, _, -1).

Fragment referenced in 4.

You can see that we are sending that previous predicate a pair. The first element in that pair is an index. Let’s build a word index, mainly for the sake of avoiding the use of between and findall. Those builtin predicates are fine, but aesthetically to me it seems nicer to not use them if we absolutely don’t have to.

build word index 2 ⟩≡


word_index(Words, Index):-
word_index(Words, 0, Index).
word_index([], _, []).
word_index([H|T], N, [I-H|Index]):-
succ(N, I),
word_index(T, I, Index).

Fragment referenced in 4.

Now we’ll use maplist to find the words.

find the indices of words that contain the letter 3 ⟩≡


find_words(Words, Letter, Indices):-
word_index(Words, Index),
maplist(contains_letter(Letter), Index, I),
delete(I, -1, Indices).

Fragment referenced in 4.

The rest of the code just wraps this predicate into a file.

"ch-1.p" 4


contains letter 1
build word index 2
find the indices of words that contain the letter 3

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- find_words([the, weekly, challenge], e, Indices). 
 
Indices = [1,2,3] ? 
 
yes 
| ?- find_words([perl, raku, python], p, Indices). 
 
Indices = [1,3] ? 
 
yes 
| ?- find_words([abc, def, bbb, bcd], b, Indices). 
 
Indices = [1,3,4] ? 
 
yes 
| ?-
    

Part 2: Find Third

You are given a sentence and two words. Write a script to return all words in the given sentence that appear in sequence to the given two words.

In the first part I mentioned the between and findall predicates. Here we will use them to find successive words.

find third 5 ⟩≡


find_third(Words, One, Two, Thirds):-
length(Words, WordLength),
N is WordLength - 2,
findall(Third, (
between(1, N, I),
succ(I, J),
nth(I, Words, One),
nth(J, Words, Two),
succ(J, K),
nth(K, Words, Third)
), Thirds).

Fragment referenced in 6.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 6


find third 5

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- find_third([’Perl’, is, a, my, favourite, language, but, ’Python’, is, my, favourite, too], my, favourite, Thirds). 
 
Thirds = [language,too] 
 
yes 
| ?- find_third([’Barbie’, is, a, beautiful, doll, also, also, a, beautiful, princess], a, beautiful, Thirds). 
 
Thirds = [doll,princess] 
 
yes 
| ?- find_third([we, will, we, will, rock, you, rock, you], we, will, Thirds). 
 
Thirds = [we,rock] 
 
yes 
| ?-
    

References

The Weekly Challenge 315
Generated Code

posted at: 17:41 by: Adam Russell | path: /prolog | permanent link to this entry

Finding a Third of the Words

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Find Words

You are given a list of words and a character. Write a script to return the index of word in the list where you find the given character.

This can be done in essentially one line. Rather than write a true Perl one-liner for the command line though, we’ll package this into a single subroutine.

Here’s our one subroutine.

find words 1 ⟩≡


sub find_words{
my($s, $c) = @_;
return grep {$s->[$_] =~ m/$c/} 0 .. @{$s} - 1;
}

Fragment referenced in 2.

Putting it all together...

"ch-1.pl" 2


preamble 3
find words 1
main 4

preamble 3 ⟩≡


use v5.40;

Fragment referenced in 2, 6.

The rest of the code just runs some simple tests.

main 4 ⟩≡


MAIN:{
say q/(/ . join(q/, /, find_words([q/the/, q/weekly/, q/challenge/], q/e/)). q/)/;
say q/(/ . join(q/, /, find_words([q/perl/, q/raku/, q/python/], q/p/)) . q/)/;
say q/(/ . join(q/, /, find_words([q/abc/, q/def/, q/bbb/, q/bcd/], q/b/)) . q/)/;
}

Fragment referenced in 2.

Sample Run
$ perl perl/ch-1.pl 
(0, 1, 2) 
(0, 2) 
(0, 2, 3)
    

Part 2: Find Third

You are given a sentence and two words. Write a script to return all words in the given sentence that appear in sequence to the given two words.

Similar to the first part this will be a single short subroutine. We’re just going to loop over the words and match as we go. There are two small things to note here: we strip out any punctuation from our sentence and the empty string q// is considered by Perl to be a false value. The latter is only important in that is how we initialize $next.

find third 5 ⟩≡


sub find_third{
my ($s, $first, $second) = @_;
$s =~ s/[[:punct:]]//g;
my @thirds = ();
my($previous, $current, $next) = (q//, q//, q//);
do{
push @thirds, $_ if $next;
$current = $_;
$next = 1 if $previous eq $first && $current eq $second;
$next = 0 unless $previous eq $first && $current eq $second;
$previous = $current;
} for split q/\s+/, $s;
return @thirds;
}

Fragment referenced in 6.

The rest of the code drives some tests.

"ch-2.pl" 6


preamble 3
find third 5
main 7

main 7 ⟩≡


MAIN:{
say q/(/ . join(q/, /, find_third(q/Perl is a my favourite language but Python is my favourite too./, q/my/, q/favourite/)). q/)/;
say q/(/ . join(q/, /, find_third(q/Barbie is a beautiful doll also also a beautiful princess./, q/a/, q/beautiful/)) . q/)/;
say q/(/ . join(q/, /, find_third(q/we will we will rock you rock you./, q/we/, q/will/)) . q/)/;
}

Fragment referenced in 6.

Sample Run
$ perl perl/ch-2.pl 
(language, too) 
(doll, princess) 
(we, rock)
    

References

The Weekly Challenge 315
Generated Code

posted at: 17:29 by: Adam Russell | path: /perl | permanent link to this entry

2025-03-29

The Weekly Challenge 314 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Equal Strings

You are given three strings. You are allowed to remove the rightmost character of a string to make all equals. Write a script to return the number of operations to make it equal otherwise -1.

The approach we’ll take is to pop off the last letter of each and compare the remainders. If they are equal then we are done. Otherwise we’ll continue popping off letter until we’re done.

A special case to consider is when the strings are of unequal length. In that case we make sure to only pop off letters from equal length strings, although the untouched strings will still be used when checking to see if we are done.

We’re going to define some convenience predicates, some with the intention that they’re going to be called from a maplist.

remove last letter 1 ⟩≡


remove_last(S, S1):-
length(Last, 1),
append(S1, Last, S).

Fragment referenced in 7.

strings all of the same size 2 ⟩≡


match_length(Length, S, S):-
length(S, Length).
match_length(_, _, nil).

Fragment referenced in 7.

all strings are equal 3 ⟩≡


all_equal([H|T]):-
maplist(==(H), T).

Fragment referenced in 7.

all strings are empty 4 ⟩≡


all_empty(L):-
maplist(==([]), L).

Fragment referenced in 7.

max size of all strings 5 ⟩≡


max_string_size(Strings, MaxLength):-
maplist(length, Strings, Lengths),
max_list(Lengths, MaxLength).

Fragment referenced in 7.

We’ll define a predicate for doing all the co-ordination and overall logic.

equal strings 6 ⟩≡


equal_strings(Strings, Operations):-
equal_strings(Strings, 0, Operations), !.
equal_strings(Strings, Operations, Operations):-
last(Strings, S),
\+ S = [],
all_equal(Strings).
equal_strings(Strings, _, -1):-
last(Strings, S),
S = [].
equal_strings(Strings, OperationsAccum, Operations):-
max_string_size(Strings, MaxLength),
maplist(match_length(MaxLength), Strings, S1),
delete(S1, nil, S2),
subtract(Strings, S2, S4),
maplist(remove_last, S2, S3),
append(S4, S3, S5),
all_equal(S5),
\+ all_empty(S5),
length(S2, Removals),
Operations is OperationsAccum + Removals.
equal_strings(Strings, OperationsAccum, Operations):-
max_string_size(Strings, MaxLength),
maplist(match_length(MaxLength), Strings, S1),
delete(S1, nil, S2),
subtract(Strings, S2, S4),
maplist(remove_last, S2, S3),
append(S4, S3, S5),
\+ all_equal(S5),
length(S2, Removals),
O is OperationsAccum + Removals,
equal_strings(S5, O, Operations).

Fragment referenced in 7.

The rest of the code just wraps this predicate into a file.

"ch-1.p" 7


remove last letter 1
strings all of the same size 2
all strings are equal 3
all strings are empty 4
max size of all strings 5
equal strings 6

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- equal_strings(["abb", "ab", "abc"], Operations). 
 
Operations = 2 
 
yes 
| ?- equal_strings(["ayz", "cyz", "xyz"], Operations). 
 
Operations = -1 
 
yes 
| ?- equal_strings(["yza", "yzb", "yzc"], Operations). 
 
Operations = 3 
 
yes 
| ?-
    

Part 2: Sort Column

You are given a list of strings of same length. Write a script to make each column sorted lexicographically by deleting any non sorted columns. Return the total columns deleted.

We’ll start with a short predicate for convenience.

column 8 ⟩≡


column(I, L, Column):-
maplist(nth(I), L, Column).

Fragment referenced in 10.

sort column 9 ⟩≡


sort_column(Strings, Removals):-
last(Strings, S),
length(S, L),
findall(R, (
between(1, L, I),
column(I, Strings, Column),
msort(Column, ColumnSorted),
\+ Column == ColumnSorted,
R = Column
), Rs),
length(Rs, Removals).

Fragment referenced in 10.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 10


column 8
sort column 9

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- sort_column(["swpc", "tyad", "azbe"], Removals). 
 
Removals = 2 
 
yes 
| ?- sort_column(["cba", "daf", "ghi"], Removals). 
 
Removals = 1 
 
yes 
| ?- sort_column(["a", "b", "c"], Removals). 
 
Removals = 0 
 
yes 
| ?-
    

References

The Weekly Challenge 314
Generated Code

posted at: 19:05 by: Adam Russell | path: /prolog | permanent link to this entry

2025-03-27

Equally Sorted

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Equal Strings

You are given three strings. You are allowed to remove the rightmost character of a string to make all equals. Write a script to return the number of operations to make it equal otherwise -1.

The fact that we’re give exactly three strings makes things slightly easier. The approach we’ll take is to pop off the last letter of each and compare the remainders. If they are equal then we are done. Otherwise we’ll continue popping off letter until we’re done.

A special case to consider is when the strings are of unequal length. In that case we make sure to only pop off letters from equal length strings, although the untouched strings will still be used when checking to see if we are done.

Everything can be easily contained in one subroutine. I know that the do blocks with postfix if are not common, but to me they are the most aesthetic way to conditionally perform two short statements.

loop, pop, and compare 1 ⟩≡


sub loop_pop_compare{
my($s, $t, $u) = @_;
my @s = split //, $s;
my @t = split //, $t;
my @u = split //, $u;
my $counter = 0;
{
my $max_size = (sort {$b <=> $a} (0 + @s, 0 + @t, 0 + @u))[0];
unless(join(q//, @s) eq join(q//, @t) &&
join(q//, @t) eq join(q//, @u)){
do{$counter++; pop @s} if @s == $max_size;
do{$counter++; pop @t} if @t == $max_size;
do{$counter++; pop @u} if @u == $max_size;
}
else{
return $counter;
}
redo unless @s == 0 || @t == 0 || @u == 0;
}
return -1;
}

Fragment referenced in 2.

Putting it all together...

"ch-1.pl" 2


preamble 3
loop, pop, and compare 1
main 4

preamble 3 ⟩≡


use v5.40;

Fragment referenced in 2, 9.

The rest of the code just runs some simple tests.

main 4 ⟩≡


MAIN:{
say loop_pop_compare q/abc/, q/abb/, q/ab/;
say loop_pop_compare q/ayz/, q/cyz/, q/xyz/;
say loop_pop_compare q/yza/, q/yzb/, q/yzc/;
}

Fragment referenced in 2.

Sample Run
$ perl perl/ch-1.pl 
2 
-1 
3
    

Part 2: Sort Column

You are given a list of strings of same length. Write a script to make each column sorted lexicographically by deleting any non sorted columns. Return the total columns deleted.

Unlike the first part, the strings here are guaranteed to be all of the same length and we do not know how many we will need to consider.

get a column 5 ⟩≡


my $column = [map {my @w = split //, $_; $w[$i]} @{$s}];

Fragment referenced in 8.

Defines: $column 6.

Uses: $i 8, $s 8.

determine if the column is sorted 6 ⟩≡


my @sorted = sort {$a cmp $b} @{$column};
my @check = grep {$sorted[$_] eq $column->[$_]} 0 .. @{$column} - 1;
my $sorted = 0 + @check == 0 + @sorted;

Fragment referenced in 8.

Defines: $sorted 8.

Uses: $column 5.

get every other column 7 ⟩≡


my $remaining = [grep {$string->[$_] if $_ != $i} 0 .. @{$string} - 1];

Fragment never referenced.

Defines: $remaining Never used.

Uses: $i 8.

We’ll put everything together in a single subroutine.

sort columns 8 ⟩≡


sub sort_columns{
my $s = [@_];
my $i = 0;
my $removals = 0;
do{
my $i = $_;
get a column 5
determine if the column is sorted 6
$removals++ unless $sorted;
} for 0 .. length($s->[0]) - 1;
return $removals;
}

Fragment referenced in 9.

Defines: $i 5, 7, $s 5.

Uses: $sorted 6.

The rest of the code drives some tests.

"ch-2.pl" 9


preamble 3
sort columns 8
main 10

main 10 ⟩≡


MAIN:{
say sort_columns qw/swpc tyad azbe/;
say sort_columns qw/cba daf ghi/;
say sort_columns qw/a b c/;
}

Fragment referenced in 9.

Sample Run
$ perl perl/ch-2.pl 
2 
1 
0
    

References

The Weekly Challenge 314
Generated Code

posted at: 18:14 by: Adam Russell | path: /perl | permanent link to this entry

2025-03-23

Reverse Broken Keys for Letters

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Broken Keys

You have a broken keyboard which sometimes type a character more than once. You are given a string and actual typed string. Write a script to find out if the actual typed string is meant for the given string.

What we’re faced with here is a problem of removing consecutive duplicated letters. The trick is that some letters may be correctly duplicated in succession! For example “coffeescript”has two f’s and two e’s which are not in error. We’re given the correct version so wee need to track the current correct letter and find deviations.

Another special case to consider is when the deviations occur at the end of the string and we get all of the same repeated letters as in the “rrakuuuu”example. To address this we check if the repeated letters remaining match the last known good letter.

Our solution is to loop over the letters in the candidate string guided by the original word. We do this all in one subroutine, the code is not so unwieldy to need to be broken into smaller pieces.

loop and compare 1 ⟩≡


sub loop_compare{
my($n, $s) = @_;
my @n = split //, $n;
my @s = split //, $s;
my $current_n = q//;
my $current_s = q//;
{
my $previous_n = $current_n;
$current_n = shift @n;
$current_s = shift @s;
if($current_s ne $current_n && $current_s eq $previous_n){
unshift @n, $current_n;
{
$current_s = shift @s;
redo if $current_s eq $previous_n && @s > 0;
unshift @s, $current_s;
}
}
return 0 if $current_s ne $current_n && $current_s ne $previous_n;
redo if @n > 0 && @s > 0;
}
return 1 if (@n == 0 && @s ==0) || (@s == grep {$_ eq $current_s} @s);
return 0;
}

Fragment referenced in 2.

We really just need one subroutine to co-ordinate the inputs and run the main loop that’s required.

Putting it all together...

"ch-1.pl" 2


preamble 3
loop and compare 1
main 4

preamble 3 ⟩≡


use v5.40;

Fragment referenced in 2, 9.

The rest of the code just runs some simple tests.

main 4 ⟩≡


MAIN:{
say loop_compare q/perl/, q/perrrl/;
say loop_compare q/raku/, q/rrakuuuu/;
say loop_compare q/python/, q/perl/;
say loop_compare q/coffeescript/, q/cofffeescccript/;
}

Fragment referenced in 2.

Sample Run
$ perl perl/ch-1.pl 
1 
1 
0 
1
    

Part 2: Reverse Letters

You are given a string. Write a script to reverse only the alphabetic characters in the string.

First we separate the alphabetic characters from the string, then we reverse them, then we finish by recombining the reversed alphabetic characters with the non-alphabetic characters.

remove alphabetic characters 5 ⟩≡


my $a = [grep {$_ =~ m/[[:alpha:]]/} @{$s}];

Fragment referenced in 8.

Defines: $a 6, 7.

Uses: $s 8.

reverse the alphabetic characters 6 ⟩≡


$a = [reverse @{$a}];

Fragment referenced in 8.

Uses: $a 5.

combine the sorted alphabetic characters with the non-alphabetic characters 7 ⟩≡


{
my $c = shift @{$s};
push @{$reverse_letters}, $c if $c !~ m/[[:alpha:]]/;
push @{$reverse_letters}, shift @{$a} if $c =~ m/[[:alpha:]]/;
redo if @{$s} > 0;
}

Fragment referenced in 8.

Uses: $a 5, $reverse_letters 8, $s 8.

We’ll put everything together in a single subroutine.

reverse_letters: co-ordinates all the swaps and checks 8 ⟩≡


sub reverse_letters{
my($s) = (@_);
$s = [split //, $s];
my $reverse_letters = [];
remove alphabetic characters 5
reverse the alphabetic characters 6
combine the sorted alphabetic characters with the non-alphabetic characters 7
return join q//, @{$reverse_letters};
}

Fragment referenced in 9.

Defines: $reverse_letters 7, $s 5, 7.

The rest of the code drives some tests.

"ch-2.pl" 9


preamble 3
reverse_letters: co-ordinates all the swaps and checks 8
main 10

main 10 ⟩≡


MAIN:{
say reverse_letters q/p-er?l/;
say reverse_letters q/wee-k!L-y/;
say reverse_letters q/_c-!h_all-en!g_e/;
}

Fragment referenced in 9.

Sample Run
$ perl perl/ch-2.pl 
l-re?p 
yLk-e!e-w 
_e-!g_nel-la!h_c
    

References

The Weekly Challenge 313
Generated Code

posted at: 14:18 by: Adam Russell | path: /perl | permanent link to this entry

2025-03-16

The Weekly Challenge 312 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Minimum Time

You are given a typewriter with lowercase english letters a to z arranged in a circle. Typing a character takes 1 sec. You can move pointer one character clockwise or anti-clockwise. The pointer initially points at a. Write a script to return minimum time it takes to print the given string.

All the logic for computing the shortest move us contained in a single predicate.

compute minumum number of moves to get to the next letter 1 ⟩≡


compute_moves(Current, Next, Moves):-
X is Current - 96,
Y is Next - 96,
XH is X + 13,
YH is Y + 13,
((Y >= XH, Moves is X + 26 - Y);
(Y >= X, Y =< XH, Moves is Y - X);
(X >= YH, Moves is Y + 26 - X);
(X >= Y, X =< YH, Moves is X - Y)).

Fragment referenced in 3.

We’ll use a DCG to process the list of letters (we’re using ASCII codes).

process letters 2 ⟩≡


minimum_time([Current, Next|Letters]) --> [Time], {
compute_moves(Current, Next, Moves),
succ(Moves, Time)}, minimum_time([Next|Letters]).
minimum_time([_]) --> [].

Fragment referenced in 3.

Finally, let’s combine the previous predicates into a file along with a predicate minimum_time/2 that ties everything together.

"ch-1.p" 3


compute minumum number of moves to get to the next letter 1
process letters 2
minimum_time(S, MinimumTime):-
append([97], S, S0),
phrase(minimum_time(S0), Times),
sum_list(Times, MinimumTime), !.

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- minimum_time("abc", MinimumTime). 
 
MinimumTime = 5 
 
yes 
| ?- minimum_time("bza", MinimumTime). 
 
MinimumTime = 7 
 
yes 
| ?- minimum_time("zjpc", MinimumTime). 
 
MinimumTime = 34 
 
yes 
| ?-
    

Part 2: Balls and Boxes

There are $n balls of mixed colors: red, blue or green. They are all distributed in 10 boxes labelled 0-9. You are given a string describing the location of balls. Write a script to find the number of boxes containing all three colors. Return 0 if none found.

We’ll use a DCG here in Part 2, althought it’ll be just a little bit more intricate.

First off, we’ll be passing the state around via some helper predicates. At the end of processing we’ll have a set of pairs that hold the contents of each box.

state of the content of the boxes 4 ⟩≡


boxes(Boxes), [Boxes] --> [Boxes].
boxes(Box, Boxes), [Boxes] --> [Box].

Fragment referenced in 7.

Now the DCG. For each record given we’ll process each Ball/Box pair.

process the record of box contents as a list 5 ⟩≡


box_record([]) --> [].
box_record(Record) --> boxes(B, Boxes), {[Color, Box|T] = Record,
var(B), B = Box-[Color], append([], [B], Boxes)},
box_record(T).
box_record(Record) --> boxes(B, Boxes), {[Color, Box|T] = Record,
nonvar(B), member(Box-Colors, B), delete(B, Box-Colors, B0),
append([Color], Colors, Colors0), append([Box-Colors0], B0, Boxes)},
box_record(T).
box_record(Record) --> boxes(B, Boxes), {[Color, Box|T] = Record,
nonvar(B), \+ member(Box-_, B), append([Box-[Color]], B, Boxes)},
box_record(T).

Fragment referenced in 7.

helper predicate for checking if a box has all three colors 6 ⟩≡


full_box(_-Colors, Full):-
sort(Colors, ColorsSorted),
length(ColorsSorted, NumberColors),
((NumberColors == 3, Full = true);
(Full = false)).

Fragment referenced in 7.

Finally, let’s assemble our completed code into a single file. We’ll also add a predicate to co-orindate calling the DCG and processing the final result.

"ch-2.p" 7


state of the content of the boxes 4
process the record of box contents as a list 5
helper predicate for checking if a box has all three colors 6
full_boxes(BallsBoxes, CountFullBoxes):-
phrase(box_record(BallsBoxes), [_], [Boxes]),
maplist(full_box, Boxes, Full),
delete(Full, false, FullBoxes),
length(FullBoxes, CountFullBoxes), !.

Sample Run
$ gprolog --consult-file prolog/ch-2.p 
| ?- full_boxes("G0B1R2R0B0", FullBoxes). 
 
FullBoxes = 1 
 
yes 
| ?- full_boxes("G1R3R6B3G6B1B6R1G3", FullBoxes). 
 
FullBoxes = 3 
 
yes 
| ?- full_boxes("B3B2G1B3", FullBoxes). 
 
FullBoxes = 0 
 
yes 
| ?-
    

References

The Weekly Challenge 312
Generated Code

posted at: 15:35 by: Adam Russell | path: /prolog | permanent link to this entry

Minimum Time in the Box

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Minimum Time

You are given a typewriter with lowercase english letters a to z arranged in a circle. Typing a character takes 1 sec. You can move pointer one character clockwise or anti-clockwise. The pointer initially points at a. Write a script to return minimum time it takes to print the given string.

The complete solution is contained in one file that has a simple structure.

"ch-1.pl" 1


preamble 2
minimum time to type 3
main 4

For this problem we do not need to include very much. We’re just specifying to use the current version of Perl, for all the latest features in the language. This fragment is also used in Part 2.

preamble 2 ⟩≡


use v5.40;

Fragment referenced in 1, 6.

All the work is in one subroutine. We use the ASCII values of each character to compute the new value.

minimum time to type 3 ⟩≡


sub minimum_time{
my($s) = @_;
my @c = split //, lc($s);
my $time = 0;
my $moves;
my $current = q/a/;
{
my $next = shift @c;
my($x, $y) = (ord($current) - 96, ord($next) - 96);
$moves = ($x + 26) - $y if $y >= ($x + 13);
$moves = $y - $x if $y <= ($x + 13) && $y >= $x;
$moves = ($y + 26) - $x if $x >= ($y + 13);
$moves = $x - $y if $x <= ($y + 13) && $x >= $y;
$time += $moves;
$time++;
$current = $next;
redo if @c > 0;
}
return $time;
}

Fragment referenced in 1.

Now all we need are a few lines of code for running some tests.

main 4 ⟩≡


MAIN:{
say minimum_time q/abc/;
say minimum_time q/bza/;
say minimum_time q/zjpc/;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
5 
7 
34
    

Part 2: Balls and Boxes

There are $n balls of mixed colors: red, blue or green. They are all distributed in 10 boxes labelled 0-9. You are given a string describing the location of balls. Write a script to find the number of boxes containing all three colors. Return 0 if none found.

We’re going to use Parse::Yapp for this problem. Writing parsers is fun! This problem is providing an excuse to write one. This approach has been used in past weeks, for example TWC 259 from this time last year. For simplicity, to start with, here is all the code that Parse::Yapp will use as it’s input.

"ch-2.yp" 5


%token LETTER
%token NUMBER
%{
my %boxes = ();
%}

%%

records: record {\%boxes}
| records record
;

record: LETTER NUMBER {push @{$boxes{qq/$_[2]/}}, $_[1]}
;

%%

sub lexer{
my($parser) = @_;
defined($parser->YYData->{INPUT}) or return(’’, undef);
##
# send tokens to parser
##
for($parser->YYData->{INPUT}){
s/^([0-9])// and return (q/NUMBER/, $1);
s/^([A-Z])// and return (q/LETTER/, $1);
}
}

sub error{
exists $_[0]->YYData->{ERRMSG}
and do{
print $_[0]->YYData->{ERRMSG};
return;
};
print "syntax␣error\n";
}

sub parse{
my($self, $input) = @_;
$input =~ tr/\t/ /s;
$input =~ tr/ //s;
$self->YYData->{INPUT} = $input;
my $result = $self->YYParse(yylex => \&lexer, yyerror => \&error);
return $result;
}

"ch-2.pl" 6


preamble 2
use Ch2;
parse the input and check the results 7
main 8

To solve this problem we are going to pass the input string to the parser. The parser is going to return a hash reference which we’ll check to see which boxes contain all the balls, as described in the problem statement.

parse the input and check the results 7 ⟩≡


sub parse_boxes{
my($record) = @_;
my $parser = Ch2->new();
my $boxes = $parser->parse($record);
my $full = 0;
for my $box (keys %{$boxes}){
$full++ if 1 <= (grep { $_ eq q/R/ } @{$boxes->{$box}}) &&
1 <= (grep { $_ eq q/G/ } @{$boxes->{$box}}) &&
1 <= (grep { $_ eq q/B/ } @{$boxes->{$box}});
}
return $full;
}

Fragment referenced in 6.

Finally, we need to confirm everything is working right.

main 8 ⟩≡


MAIN:{
say parse_boxes $ARGV[0];
}

Fragment referenced in 6.

Sample Run
$ yapp -m Ch2 perl/ch-2.yp; mv Ch2.pm perl 
$ perl -I perl perl/ch-2.pl G0B1R2R0B0 
1 
$ perl -I perl perl/ch-2.pl G1R3R6B3G6B1B6R1G3 
3 
$ perl -I perl perl/ch-2.pl B3B2G1B3 
0
    

References

The Weekly Challenge 312
Generated Code

posted at: 01:29 by: Adam Russell | path: /perl | permanent link to this entry

2025-03-09

The Weekly Challenge 311 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Upper Lower

You are given a string consists of english letters only. Write a script to convert lower case to upper and upper case to lower in the given string.

GNU Prolog defines a predicate lower_upper/2 which does pretty much what this problem is asking for. We could also just use the character code values instead of chars, but using chars seems a little more elegant as it avoids ASCII math.

upper lower 1 ⟩≡


upper_lower(S, UpperLower):-
atom_chars(S, C),
upper_lower(C, UL, _),
atom_chars(UpperLower, UL).
upper_lower([], [], _).
upper_lower([C|T], [L|UL], _):-
lower_upper(C, C0),
C == C0,
lower_upper(L, C),
upper_lower(T, UL, _).
upper_lower([C|T], [U|UL], _):-
lower_upper(C, U),
\+ C == U,
upper_lower(T, UL, _).

Fragment referenced in 2.

The rest of the code just wraps this predicate into a file.

"ch-1.p" 2


upper lower 1

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- upper_lower(’pERl’, UpperLower). 
 
UpperLower = ’PerL’ ? 
 
yes 
| ?- upper_lower(’rakU’, UpperLower). 
 
UpperLower = ’RAKu’ ? 
 
yes 
| ?- upper_lower(’PyThOn’, UpperLower). 
 
UpperLower = pYtHoN ? 
 
yes 
| ?-
    

Part 2: Group Digit Sum

You are given a string, $str, made up of digits, and an integer, $int, which is less than the length of the given string. Write a script to divide the given string into consecutive groups of size $int (plus one for leftovers if any). Then sum the digits of each group, and concatenate all group sums to create a new string. If the length of the new string is less than or equal to the given integer then return the new string, otherwise continue the process.

To solve this problem we need to do the following

  1. divide the list into groups of the given size
  2. compute the sums
  3. recombine
  4. repeat as needed

Let’s look at each of those pieces individually and then combine them together into one predicate.

divide the list into groups of the given size 3 ⟩≡


group_list(S, Size, GroupedList):-
atom_chars(S, C),
maplist(char_number, C, N),
group_list(N, Size, [], [], GroupedList).
group_list([], _, Group, GroupedListAccum, GroupedList):-
length(Group, L),
((L > 0, append(GroupedListAccum, [Group], GroupedList));
(GroupedList = GroupedListAccum)).
group_list([H|T], Size, Group, GroupedListAccum, GroupedList):-
length(Group, L),
L < Size,
append(Group, [H], G),
GLA = GroupedListAccum,
group_list(T, Size, G, GLA, GroupedList).
group_list([H|T], Size, Group, GroupedListAccum, GroupedList):-
length(Group, L),
L == Size,
append(GroupedListAccum, [Group], GLA),
group_list([H|T], Size, [], GLA, GroupedList).

Fragment referenced in 9.

Uses: GroupedList 8, Size 8.

turn a numeral into a number 4 ⟩≡


char_number(C, N):-
number_chars(N, [C]).

Fragment referenced in 9.

We can compute the sums using the GNU Prolog builtin predicate sum_list/2.

compute the sums 5 ⟩≡


maplist(sum_list, GroupedList, Sums),

Fragment referenced in 8.

Defines: Sums 7.

Uses: GroupedList 8.

join a list of numbers into a single atom 6 ⟩≡


join([], ’’).
join([H|T], A):-
join(T, A0),
number_atom(H, A1),
atom_concat(A1, A0, A).

Fragment referenced in 9.

recombine and check if done 7 ⟩≡


flatten(Sums, SumsFlatted),
join(SumsFlatted, A),
atom_property(A, length(Length)),
((Length =< Size, GroupDigitSum = A, !);
(group_digit_sum(A, Size, GroupDigitSum), !)).

Fragment referenced in 8.

Uses: GroupDigitSum 8, Size 8, Sums 5.

the main predicate 8 ⟩≡


group_digit_sum(S, Size, GroupDigitSum):-
group_list(S, Size, GroupedList),
compute the sums 5
recombine and check if done 7

Fragment referenced in 9.

Defines: GroupDigitSum 7, GroupedList 3, 5, Size 3, 7.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 9


the main predicate 8
divide the list into groups of the given size 3
turn a numeral into a number 4
join a list of numbers into a single atom 6

Sample Run
$ gprolog prolog/ch-2.p 
| ?- group_digit_sum(’111122333’, 3, GroupDigitSum). 
 
GroupDigitSum = ’359’ 
 
yes 
| ?- group_digit_sum(’1222312’, 2, GroupDigitSum). 
 
GroupDigitSum = ’76’ 
 
yes 
| ?- group_digit_sum(’100012121001’, 4, GroupDigitSum). 
 
GroupDigitSum = ’162’ 
 
yes 
| ?-
    

References

The Weekly Challenge 311
Generated Code

posted at: 00:39 by: Adam Russell | path: /prolog | permanent link to this entry

2025-03-08

Lower the Upper Sums!

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Upper Lower

You are given a string consists of english letters only. Write a script to convert lower case to upper and upper case to lower in the given string.

The complete solution is contained in one file that has a simple structure.

"ch-1.pl" 1


preamble 2
upper lower calculations 3
main 4

For this problem we do not need to include very much. We’re just specifying to use the current version of Perl, for all the latest features in the language. This fragment is also used in Part 2.

preamble 2 ⟩≡


use v5.38;

Fragment referenced in 1, 5.

All the work is in one subroutine. We use the ASCII values of each character to compute the new value.

upper lower calculations 3 ⟩≡


sub upper_lower{
my($s) = @_;
my @c = split //, $s;
return join q//, map{
my $x = ord($_);
if($x >= 65 && $x <= 90){
chr($x + 32);
}
elsif($x >= 97 && $x <= 122){
chr($x - 32);
}
} @c;
}

Fragment referenced in 1.

Now all we need are a few lines of code for running some tests.

main 4 ⟩≡


MAIN:{
say upper_lower q/pERl/;
say upper_lower q/rakU/;
say upper_lower q/PyThOn/;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
PerL 
RAKu 
pYtHoN
    

Part 2: Group Digit Sum

You are given a string, $str, made up of digits, and an integer, $int, which is less than the length of the given string. Write a script to divide the given string into consecutive groups of size $int (plus one for leftovers if any). Then sum the digits of each group, and concatenate all group sums to create a new string. If the length of the new string is less than or equal to the given integer then return the new string, otherwise continue the process.

"ch-2.pl" 5


preamble 2
group digit sum 9
main 10

To solve this problem we need to do the following

1.
divide the list into groups of the given size
2.
compute the sums
3.
recombine
4.
repeat as needed

Let’s look at each of those pieces individually and then combine them together into one subroutine.

divide the list into groups of the given size 6 ⟩≡


my $g = [];
my $groups;
for my $i (0 .. @{$c} - 1){
my $n = $i % $size;

if($n == 0){
$g = [];
push @{$g}, $c->[$i];
}
elsif($n == $size - 1){
push @{$g}, $c->[$i];
push @{$groups}, $g;
$g = [];
}
else{
push @{$g}, $c->[$i];
}
}
push @{$groups}, $g if @{$g} > 0;

Fragment referenced in 9.

Defines: $groups 7.

Uses: $c 9, $size 9.

compute the sums 7 ⟩≡


my $sums = [];
do{
my $sum = unpack(q/%32I*/, pack(q/I*/, @{$_}));
push @{$sums}, $sum;
} for @{$groups};

Fragment referenced in 9.

Defines: $sums 8.

Uses: $groups 6.

recombine 8 ⟩≡


$s = join q//, @{$sums};
return $s if length $s <= $size;
group_digit_sum($s, $size);

Fragment referenced in 9.

Uses: $size 9, $sums 7.

With that work take care of, let’s combine all these pieces into one subroutine.

group digit sum 9 ⟩≡


sub group_digit_sum{
my($s, $size) = @_;
my $c = [split //, $s];
divide the list into groups of the given size 6
compute the sums 7
recombine 8
}

Fragment referenced in 5.

Defines: $c 6, $size 6, 8.

Finally, here’s a few tests to confirm everything is working right.

main 10 ⟩≡


MAIN:{
say group_digit_sum q/111122333/, 3;
say group_digit_sum q/1222312/, 2;
say group_digit_sum q/100012121001/, 4;
}

Fragment referenced in 5.

Sample Run
$ perl perl/ch-2.pl 
359 
76 
162
    

References

The Weekly Challenge 311
Generated Code

posted at: 22:30 by: Adam Russell | path: /perl | permanent link to this entry

2025-03-02

The Weekly Challenge 310 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Arrays Intersection

You are given a list of array of integers. Write a script to return the common elements in all the arrays.

Let’s define a predicate that can be called via maplist. This will just wrap member and be used to return a list of common elements. We’ll also remove duplicates (via sort).

Note that because the predicate used with maplist must be true for all list elements we’ll return a nil value if there is no match. We just need to make sure to delete all the nils before further using the list.

common element finder 1 ⟩≡


common_elements(L0, L1, Common):-
maplist(common_element(L0), L1, C0),
sort(C0, C1),
delete(C1, nil, Common).
common_element(L, X, X):-
member(X, L).
common_element(L, X, nil):-
\+ member(X, L).

Fragment referenced in 3.

The plan is to find the common elements in the first two lists and then compare those common elements against the rest of the lists, only keeping elements that continue to be in common. We’ll use tail recursion and an accumulator here.

process lists 2 ⟩≡


intersections(Lists, Intersections):-
intersections(Lists, [], Intersections).
intersections([H0, H1|[]], [], Intersections):-
common_elements(H0, H1, Intersections).
intersections([H0, H1|[]], IntersectionsAccum, Intersections):-
common_elements(H0, H1, Common),
common_elements(Common, IntersectionsAccum, Intersections).
intersections([H0, H1|T], IntersectionsAccum, Intersections):-
common_elements(H0, H1, Common),
append(IntersectionsAccum, Common, I),
intersections([Common|T], I, Intersections).

Fragment referenced in 3.

The rest of the code just wraps this predicate into a file.

"ch-1.p" 3


common element finder 1
process lists 2

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- intersections([[1, 2, 3, 4], [4, 5, 6, 1], [4, 2, 1, 3]], Intersections). 
 
Intersections = [1,4] ? 
 
yes 
| ?- intersections([[1, 0, 2, 3], [2, 4, 5]], Intersections). 
 
Intersections = [2] ? 
 
yes 
| ?- intersections([[1, 2, 3], [4, 5], [6]], Intersections). 
 
Intersections = [] ? 
 
yes 
| ?-
    

Part 2: Sort Odd Even

You are given an array of integers. Write a script to sort odd index elements in decreasing order and even index elements in increasing order in the given array.

To solve this problem we need to do the following

  1. seperate the odd and even indexed numbers
  2. sort the two lists as directed
  3. combine the results

This problem was written with 0-based indices in mind so we will keep that in mind when calculating whether an index is odd or even.

seperate the odd and even indexed numbers 4 ⟩≡


odd_even_indexed(List, Odds, Evens):-
length(List, L),
findall(Odd,(
between(1, L, Index),
succ(I, Index),
M is I rem 2,
\+ M == 0,
nth(Index, List, Odd)
), Odds),
findall(Even,(
between(1, L, Index),
succ(I, Index),
M is I rem 2,
M == 0,
nth(Index, List, Even)
), Evens).

Fragment referenced in 8.

We’ll need a few more predicates to contain most of the rest of the work needed. To do the descending sort we’ll take the negatives of the numbers. We also need to combine the final result.

negate numbers 5 ⟩≡


negate(X, Y):-
Y is -1 * X.

Fragment referenced in 8.

combine 6 ⟩≡


combine(OddsSorted, EvensSorted, Combined):-
combine(OddsSorted, EvensSorted, 0, [], Combined).
combine([], [], _, Combined, Combined).
combine([O|OT], EvensSorted, Index, CombinedAccum, Combined):-
M is Index rem 2,
\+ M == 0,
append(CombinedAccum, [O], C),
succ(Index, I),
combine(OT, EvensSorted, I, C, Combined).
combine(OddsSorted, [E|ET], Index, CombinedAccum, Combined):-
M is Index rem 2,
M == 0,
append(CombinedAccum, [E], C),
succ(Index, I),
combine(OddsSorted, ET, I, C, Combined).

Fragment referenced in 8.

sort odd evens 7 ⟩≡


sort_odd_evens(List, Sorted):-
odd_even_indexed(List, Odds, Evens),
maplist(negate, Odds, OddsNegated),
sort(OddsNegated, OddsNegatedSorted),
maplist(negate, OddsNegatedSorted, OddsSorted),
sort(Evens, EvensSorted),
combine(OddsSorted, EvensSorted, Sorted).

Fragment referenced in 8.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 8


seperate the odd and even indexed numbers 4
negate numbers 5
combine 6
sort odd evens 7

Sample Run
$ gprolog prolog/ch-2.p 
| ?- sort_odd_evens([4, 1, 2, 3], Sorted). 
 
Sorted = [2,3,4,1] ? 
 
yes 
| ?- sort_odd_evens([3, 1], Sorted). 
 
Sorted = [3,1] ? 
 
yes 
| ?- sort_odd_evens([5, 3, 2, 1, 4], Sorted). 
 
Sorted = [2,3,4,1,5] ? 
 
yes 
| ?-
    

References

The Weekly Challenge 310
Generated Code

posted at: 23:09 by: Adam Russell | path: /prolog | permanent link to this entry

2025-03-01

Arrays Intersect in Odd Ways

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Arrays Intersection

You are given a list of array of integers. Write a script to return the common elements in all the arrays.

The complete solution is contained in one file that has a simple structure.

"ch-1.pl" 1


preamble 2
compute array intersections. 3
main 4

For this problem we do not need to include very much. We’re just specifying to use the current version of Perl, for all the latest features in the language. This fragment is also used in Part 2.

preamble 2 ⟩≡


use v5.38;

Fragment referenced in 1, 5.

We’ll take the arrays in pairs and build up a list of common elements. First we compute the common elements of the first two arrays and then proceed to check these against the remaining arrays. If any of these initial common elements are not found in subsequent arrays they are not included in future checks. Finally the remaining common elements are returned. If there are no common elements we return n/a.

compute array intersections. 3 ⟩≡


sub array_intersections{
my @common_elements;
my $x = shift @_;
my $y = shift @_;
if($x && $y){
my @common = map {
my $x = $_;
grep {$x == $_} @{$y}
} @{$x};
push @common_elements, @common;
}
{
$x = shift @_;
my @common = map {
my $x = $_;
grep {$x == $_} @{$y}
} @common_elements;
@common_elements = @common;
redo if @_ > 1;
}
return (join q/, /, @common_elements) || q#n/a#;
}

Fragment referenced in 1.

Now all we need are a few lines of code for running some tests.

main 4 ⟩≡


MAIN:{
say array_intersections [1, 2, 3, 4], [4, 5, 6, 1], [4, 2, 1, 3];
say array_intersections [1, 0, 2, 3], [2, 4, 5];
say array_intersections [1, 2, 3], [4, 5], [6];
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
1, 4 
2 
n/a
    

Part 2: Sort Odd Even

You are given an array of integers. Write a script to sort odd index elements in decreasing order and even index elements in increasing order in the given array.

"ch-2.pl" 5


preamble 2
sort odd/even. All the code for solving the problem is here. 6
main 7

To solve this problem we need to do the following

1.
seperate the odd and even numbers
2.
sort the two lists as directed
3.
combine the results

Much of this work can be written concisely using map and grep.

sort odd/even. All the code for solving the problem is here. 6 ⟩≡


sub sort_odd_even{
my @i = @_;
my @odds = map { $i[$_] } grep {$_ % 2 != 0} 0 .. @_ - 1;
my @evens = map { $i[$_] } grep {$_ % 2 == 0} 0 .. @_ - 1;
my @odds_sorted = sort {$b <=> $a} @odds;
my @evens_sorted = sort {$a <=> $b} @evens;
my @common_elements;
do {
$common_elements[$_] = shift @odds_sorted if $_ % 2 != 0;
$common_elements[$_] = shift @evens_sorted if $_ % 2 == 0;
} for 0 .. @_ - 1;
return @common_elements;
}

Fragment referenced in 5.

Finally, here’s a few tests to confirm everything is working right.

main 7 ⟩≡


MAIN:{
say join q/, /, sort_odd_even 4, 1, 2, 3;
say join q/, /, sort_odd_even 3, 1;
say join q/, /, sort_odd_even 5, 3, 2, 1, 4;
}

Fragment referenced in 5.

Sample Run
$ perl perl/ch-2.pl 
2, 3, 4, 1 
3, 1 
2, 3, 4, 1, 5
    

References

The Weekly Challenge 310
Generated Code

posted at: 22:17 by: Adam Russell | path: /perl | permanent link to this entry

2025-02-23

The Weekly Challenge 309 (Prolog Solutions)

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Min Gap

You are given an array of integers, @ints, increasing order. Write a script to return the element before which you find the smallest gap.

There are probably a few good approaches to this problem. Here we’ll use a DCG approach. Ultimately this problem is at its core is to process a list of integers, and processing a list is something a DCG is well suited to handle.

We will be passing the state of the minimal gap found through the list processing predicates. The plan is that every time we find a new smallest gap the element where we found it will be appended to the list, along with the size of the gap. At the end of processing the final element will contain where the smallest gap was found.

gap state 1 ⟩≡


gap(Gap), [Gap] --> [Gap].
gap(G, Gap), [Gap] --> [G].

Fragment referenced in 4, 7.

find all gaps/locations 2 ⟩≡


min_gap([]) --> [].
min_gap(Integers) --> {[_] = Integers}.
min_gap(Integers) -->
gap(G, Gap),
{[X,Y|T] = Integers, D is Y - X, var(G), G = D, append([], [G-Y], Gap)},
min_gap([Y|T]).
min_gap(Integers) -->
gap(G, Gap),
{[X,Y|T] = Integers, D is Y - X, last(G, GCurrent-_), D < GCurrent, append(G, [D-Y], Gap)},
min_gap([Y|T]).
min_gap(Integers) -->
gap(G, Gap),
{[X,Y|T] = Integers, D is Y - X, last(G, GCurrent-_), D >= GCurrent, Gap = G},
min_gap([Y|T]).

Fragment referenced in 4.

Let’s give this DCG a simple interface. We’ll write a utility predicate that calls the DCG and sets the location of the smallest gap found.

gap location finder 3 ⟩≡


min_gap(Integers, MinGapLocation):-
phrase(min_gap(Integers), [_], [Gaps]),
last(Gaps, _-MinGapLocation), !.

Fragment referenced in 4.

The rest of the code just wraps this predicate into a file.

"ch-1.p" 4


gap state 1
find all gaps/locations 2
gap location finder 3

Sample Run
$ gprolog --consult-file prolog/ch-1.p 
| ?- min_gap([2, 8, 10, 11, 15], MinGapLocation). 
 
MinGapLocation = 11 
 
(1 ms) yes 
| ?- min_gap([1, 5, 6, 7, 14], MinGapLocation). 
 
MinGapLocation = 6 
 
yes 
| ?- min_gap([8, 20, 25, 28], MinGapLocation). 
 
MinGapLocation = 28 
 
yes 
| ?-
    

Part 2: Min Diff

You are given an array of integers, @ints. Write a script to find the minimum difference between any two elements.

From Part 1 we know that if we sort the list we know we need to only check adjacent elements to find the minimum difference.

Much of the code is going to be the same. In fact there’s going to be less code since all we need to do is track the gap sizes and not the locations.

find all gaps 5 ⟩≡


min_gap([]) --> [].
min_gap(Integers) --> {[_] = Integers}.
min_gap(Integers) -->
gap(G, Gap),
{[X,Y|T] = Integers, D is Y - X, var(G), G = D, append([], [G], Gap)},
min_gap([Y|T]).
min_gap(Integers) -->
gap(G, Gap),
{[X,Y|T] = Integers, D is Y - X, last(G, GCurrent), D < GCurrent, append(G, [D], Gap)},
min_gap([Y|T]).
min_gap(Integers) -->
gap(G, Gap),
{[X,Y|T] = Integers, D is Y - X, last(G, GCurrent), D >= GCurrent, Gap = G},
min_gap([Y|T]).

Fragment referenced in 7.

As before let’s give this DCG a simple interface. We’ll write a utility predicate that calls the DCG and sets the smallest gap found.

gap finder 6 ⟩≡


min_gap(Integers, MinGap):-
msort(Integers, SortedIntegers),
phrase(min_gap(SortedIntegers), [_], [Gaps]),
last(Gaps, MinGap), !.

Fragment referenced in 7.

Finally, let’s assemble our completed code into a single file.

"ch-2.p" 7


gap state 1
find all gaps 5
gap finder 6

Sample Run
$ gprolog prolog/ch-2.p 
| ?- min_gap([1, 5, 8, 9], MinGap). 
 
MinGap = 1 
 
yes 
| ?- min_gap([9, 4, 1, 7], MinGap). 
 
MinGap = 2 
 
yes 
| ?-
    

References

The Weekly Challenge 309
Generated Code

posted at: 20:01 by: Adam Russell | path: /prolog | permanent link to this entry

Gap Minimizations

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1: Min Gap

You are given an array of integers, @ints, increasing order. Write a script to return the element before which you find the smallest gap.

The complete solution is contained in one file that has a simple structure.

"ch-1.pl" 1


preamble 2
Min Gap subroutine. Contains all the important code for this problem. 3
main 4

For this problem we do not need to include very much. We’re just specifying to use the current version of Perl, for all the latest features in the language. This fragment is also used in Part 2.

preamble 2 ⟩≡


use v5.38;

Fragment referenced in 1, 5.

Let’s not even store anything. Instead go down the list and update two variables: one to store the current minimum gap, and the other to store the element before the current smallest gap found.

I use a small trick here. A way of saying The Maximum Integer is 0 + q/inf/.

Min Gap subroutine. Contains all the important code for this problem. 3 ⟩≡


sub min_gap{
my($min_gap, $element_min_gap) = (0 + q/inf/, 0 + q/inf/);
{
my $x = shift @_;
my $y = shift @_;
if($x && $y){
my $gap = $y - $x;
if($gap < $min_gap){
$min_gap = $gap;
$element_min_gap = $y;
}
}
unshift @_, $y;
redo if @_ > 1;
}
return $element_min_gap;
}

Fragment referenced in 1.

Now all we need are a few lines of code for running some tests.

main 4 ⟩≡


MAIN:{
say min_gap 2, 8, 10, 11, 15;
say min_gap 1, 5, 6, 7, 14;
say min_gap 8, 20, 25, 28;
}

Fragment referenced in 1.

Sample Run
$ perl perl/ch-1.pl 
11 
6 
28
    

Part 2: Min Diff

You are given an array of integers, @ints. Write a script to find the minimum difference between any two elements.

"ch-2.pl" 5


preamble 2
Min Diff subroutine. All the code for solving the problem is here. 6
main 7

From Part 1 we know that if we sort the list we know we need to only check adjacent elements to find the minimum difference.

Min Diff subroutine. All the code for solving the problem is here. 6 ⟩≡


sub min_diff{
my $min_gap = 0 + q/inf/;
my @i = sort {$a <=> $b} @_;
{
my $x = shift @i;
my $y = shift @i;
if($x && $y){
my $gap = $y - $x;
$min_gap = $gap if $gap < $min_gap;
}
unshift @i, $y;
redo if @i > 1;
}
return $min_gap;
}

Fragment referenced in 5.

Finally, here’s a few tests to confirm everything is working right.

main 7 ⟩≡


MAIN:{
say min_diff 1, 5, 8, 9;
say min_diff 9, 4, 1, 7;
}

Fragment referenced in 5.

Sample Run
$ perl ch-2.pl 
1 
2
    

References

The Weekly Challenge 309
Generated Code

posted at: 19:58 by: Adam Russell | path: /perl | permanent link to this entry