aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--challenge-058/duncan-c-white/README93
-rwxr-xr-xchallenge-058/duncan-c-white/perl/ch-1.pl156
-rwxr-xr-xchallenge-058/duncan-c-white/perl/ch-2.pl136
3 files changed, 344 insertions, 41 deletions
diff --git a/challenge-058/duncan-c-white/README b/challenge-058/duncan-c-white/README
index 19927bb4b1..ec66b1a712 100644
--- a/challenge-058/duncan-c-white/README
+++ b/challenge-058/duncan-c-white/README
@@ -1,58 +1,69 @@
-Task 1: "Invert Tree
+Task 1: "Compare Version
-You are given a full binary tree of any height, similar to the one below:
+Compare two given version number strings v1 and v2 such that:
- 1
- / \
- 2 3
- / \ / \
- 4 5 6 7
+ If v1 > v2 return 1
+ If v1 < v2 return -1
+ Otherwise, return 0
-Write a script to invert the tree, by mirroring the children of every node, from left to right. The expected output from the tree above would be:
+The version numbers are non-empty strings containing only digits, the dot
+('.') and underscore ('_') characters. ('_' denotes an alpha/development
+version, and has a lower precedence than a dot, '.'). Here are some examples:
- 1
- / \
- 3 2
- / \ / \
- 7 6 5 4
+ v1 v2 Result
+------ ------ ------
+ 0.1 < 1.1 -1
+ 2.0 > 1.2 1
+ 1.2 < 1.2_5 -1
+1.2.1 > 1.2_1 1
+1.2.1 = 1.2.1 0
-The input can be any sensible machine-readable binary tree format of your choosing, and the output should be the same format.
-
-BONUS
-
-In addition to the above, you may wish to pretty-print your binary tree in a human readable text-based format similar to the above."
-
-
-My notes: Let's reuse the same binary tree representation and input format as last time. That's the bulk of the problem.
+Version numbers may also contain leading zeros. You may handle these
+how you wish, as long as it's consistent.
+"
-The "flip-left-right" algorithm looks very simple. Not sure why "full"
-is stressed, AFAIK any obvious algorithm for this would deal with any
-binary tree. In Haskell, it's as simple as:
+My notes: I hate it already:-) but it doesn't sound too hard.
-fliplr e = e
-fliplr leaf(n) = leaf(n)
-fliplr node(n,l,r) = node(n, fliplr(r), fliplr(l))
-The bonus is MUCH the most difficult part, as best layout may be slightly
-subjective, and it wasn't immediately clear exactly what spacing is needed
-at each level. An obvious alternative would be to use GraphViz to
-draw the directed graph (our tree), I did that first and it worked fine,
-but in this version I attempt the fullblown ASCII art layout.. new:
-decided to generalise it to deal properly with non-full binary trees too,
-ie. missing elements.
+Task 2: "Ordered Lineup
+Write a script to arrange people in a lineup according to how many
+taller people are in front of each person in line. You are given two
+arrays. @H is a list of unique heights, in any order. @T is a list of
+how many taller people are to be put in front of the corresponding
+person in @H. The output is the final ordering of people's heights,
+or an error if there is no solution.
-Task 2: "Shortest Unique Prefix
+Here is a small example:
-Write a script to find the shortest unique prefix for each each word in the given list. The prefixes will not necessarily be of the same length.
+ @H = (2, 6, 4, 5, 1, 3) # Heights
+ @T = (1, 0, 2, 0, 1, 2) # Number of taller people wanted in front
-Sample Input
+The ordering of both arrays lines up, so H[i] and T[i] refer to the same
+person. For example, there are 2 taller people in front of the person
+with height 4, and there is 1 person in front of the person with height 1.
- [ "alphabet", "book", "carpet", "cadmium", "cadeau", "alpine" ]
+here is one possible solution that satisfies @H and @T:
-Expected Output
+(5, 1, 2, 6, 3, 4)
- [ "alph", "b", "car", "cadm", "cade", "alpi" ]
+Note that the leftmost element is the 'front' of the array.)
"
-My notes: sounds entirely straightforward.
+My notes: an unfamiliar problem, sounds quite interesting. No algorithm
+immediately comes to mind.. Generate-and-test was my first thought - i.e.
+have a function to test "is-this-combination-a-valid-answer" and invoke it
+for every possible answer. But the question also suggested that our
+algorithm should be able to work at a scale of 1000 people, and the number
+of combinations of 1000 people is insane (1000!). so scratch that.
+
+After trying a few examples manually, I realised that the first person in
+the queue must be someone who wants 0 people taller than them in front of
+them, and quickly generalised this into what I think it a valid and efficient
+algorithm. See function "solve".
+
+However, it doesn't (yet) scale to the 64-person solution, much less the
+1000-person one. I've tried some Perl profiling on an intermediate scale
+problem, and managed to make it run 4 times faster, but it still can't
+scale up to 64-person scale, would need a fundamentally faster algorithm,
+which I just can't see.
diff --git a/challenge-058/duncan-c-white/perl/ch-1.pl b/challenge-058/duncan-c-white/perl/ch-1.pl
new file mode 100755
index 0000000000..596bb09bd0
--- /dev/null
+++ b/challenge-058/duncan-c-white/perl/ch-1.pl
@@ -0,0 +1,156 @@
+#!/usr/bin/perl
+#
+# Task 1: "Compare Version
+#
+# Compare two given version number strings v1 and v2 such that:
+#
+# If v1 > v2 return 1
+# If v1 < v2 return -1
+# Otherwise, return 0
+#
+# The version numbers are non-empty strings containing only digits, the dot
+# ('.') and underscore ('_') characters. ('_' denotes an alpha/development
+# version, and has a lower precedence than a dot, '.'). Here are some examples:
+#
+# v1 v2 Result
+# ------ ------ ------
+# 0.1 < 1.1 -1
+# 2.0 > 1.2 1
+# 1.2 < 1.2_5 -1
+# 1.2.1 > 1.2_1 1
+# 1.2.1 = 1.2.1 0
+#
+# Version numbers may also contain leading zeros. You may handle these
+# how you wish, as long as it's consistent.
+# "
+#
+# My notes: I hate version numbers already:-) but it doesn't sound too hard.
+# I am assuming there can be any number of '.'s, but one zero or one '_'
+# inside each '.'-separated component. eg 1.2_2_2 isn't valid.
+#
+
+use strict;
+use warnings;
+use feature 'say';
+use Function::Parameters;
+use List::Util qw(max);
+use Data::Dumper;
+
+
+
+die "Usage: compare-versions v1 v2\n" unless @ARGV==2;
+my( $v1, $v2 ) = @ARGV;
+
+
+#
+# my $result = validate_version( $v );
+# Validate a version string $v according to
+# the rules described at the top. Return 1 if $v is valid,
+# -1 otherwise.
+#
+fun validate_version( $v )
+{
+ my $origv = $v;
+
+ $v =~ s/^0+//; # remove any leading zeroes
+
+ while( $v )
+ {
+ $v =~ s/^\d+// || return 0;
+ return 1 unless $v;
+
+ # deal with optional underscore
+ if( $v =~ s/^_// )
+ {
+ $v =~ s/^\d+// || return 0;
+ }
+
+ return 1 unless $v;
+
+ # deal with compulsory '.'
+ return 0 unless $v =~ s/^\.//;
+ }
+
+ return 1;
+}
+
+
+#
+# my $result = compare_versions( $v1, $v2 );
+# Compare two VALID version strings $v1 and $v2, according to
+# the rules described at the top. Return -1 if $v1 < $v2,
+# 1 if $v1 > $v2 and 0 if $v1 == $v2. Don't bother to check validity
+# as you go, told both versions ARE valid.
+#
+fun compare_versions( $v1, $v2 )
+{
+ my $origv1 = $v1;
+ my $origv2 = $v2;
+
+ $v1 =~ s/^0+//; # remove any leading zeroes
+ $v2 =~ s/^0+//;
+
+ while( 1 )
+ {
+ # both must have leading digit sequence
+ $v1 =~ s/^(\d+)//; my $c = $1;
+ $v2 =~ s/^(\d+)//; my $d = $1;
+
+ return -1 if $c < $d; # leading numeric part of v1 smaller
+ return 1 if $c > $d; # leading numeric part of v1 larger
+
+ # same leading number:-)
+
+ # deal with optional underscores
+ if( $v1 =~ /^_/ && $v2 =~ /^_/ )
+ {
+ # both have _.. check and compare
+ $v1 =~ s/^_//;
+ $v2 =~ s/^_//;
+
+ # both must have leading digit sequence: compare them
+
+ $v1 =~ s/^(\d+)//; my $c = $1;
+ $v2 =~ s/^(\d+)//; my $d = $1;
+
+ return -1 if $c < $d; # _N part of v1 smaller
+ return 1 if $c > $d; # _N part of v1 larger
+ } elsif( $v1 =~ /^_/ )
+ {
+ return 1;
+ } elsif( $v2 =~ /^_/ )
+ {
+ return -1;
+ }
+
+ # might be end, or could have '.'
+ return 0 if $v2 eq '' && $v1 eq '';
+
+ # deal with optional '.'
+ if( $v1 =~ /^\./ && $v2 =~ /^\./ )
+ {
+ # both have '.', check and compare
+ $v1 =~ s/^\.//;
+ $v2 =~ s/^\.//;
+
+ # continue at top of while loop
+
+ } elsif( $v1 =~ /^\./ )
+ {
+ return 1; # v2 ended
+ } elsif( $v2 =~ /^\./ )
+ {
+ return -1; # v1 ended
+ }
+ }
+
+ # never reached
+}
+
+die "v1 $v1 is invalid\n" unless validate_version($v1);
+die "v2 $v2 is invalid\n" unless validate_version($v2);
+
+my $result = compare_versions( $v1, $v2 );
+print "$v1 is less than $v2\n" if $result == -1;
+print "$v1 is greater than $v2\n" if $result == 1;
+print "$v1 is same as $v2\n" if $result == 0;
diff --git a/challenge-058/duncan-c-white/perl/ch-2.pl b/challenge-058/duncan-c-white/perl/ch-2.pl
new file mode 100755
index 0000000000..2d3d77b742
--- /dev/null
+++ b/challenge-058/duncan-c-white/perl/ch-2.pl
@@ -0,0 +1,136 @@
+#!/usr/bin/perl
+#
+# Task 2: "Ordered Lineup
+#
+# Write a script to arrange people in a lineup according to how many
+# taller people are in front of each person in line. You are given two
+# arrays. @H is a list of unique heights, in any order. @T is a list of
+# how many taller people are to be put in front of the corresponding
+# person in @H. The output is the final ordering of people's heights,
+# or an error if there is no solution.
+#
+# Here is a small example:
+#
+# @H = (2, 6, 4, 5, 1, 3) # Heights
+# @T = (1, 0, 2, 0, 1, 2) # Number of taller people wanted in front
+#
+# The ordering of both arrays lines up, so H[i] and T[i] refer to the same
+# person. For example, there are 2 taller people in front of the person
+# with height 4, and there is 1 person in front of the person with height 1.
+#
+# here is one possible solution that satisfies @H and @T:
+#
+# (5, 1, 2, 6, 3, 4)
+#
+# Note that the leftmost element is the 'front' of the array.)
+# "
+#
+# My notes: an unfamiliar problem, sounds quite interesting. No algorithm
+# immediately comes to mind.. Generate-and-test was my first thought - i.e.
+# have a function to test "is-this-combination-a-valid-answer" and invoke it
+# for every possible answer. But the question also suggested that our
+# algorithm should be able to work at a scale of 1000 people, and the number
+# of combinations of 1000 people is insane (1000!). so scratch that.
+#
+# After trying a few examples manually, I realised that the first person in
+# the queue must be someone who wants 0 people taller than them in front of
+# them, and quickly generalised this into what I think it a valid and efficient
+# algorithm. See function "solve".
+#
+# Run the program on the example as:
+# ./ch-2.pl 2,6,4,5,1,3 1,0,2,0,1,2
+# bigger examples can be got (in a simple way) by adding ",MAXH+1" to the
+# heights, and ",0" to the taller. eg
+# ./ch-2.pl 2,6,4,5,1,3,7 1,0,2,0,1,2,0
+# ./ch-2.pl 2,6,4,5,1,3,7,8 1,0,2,0,1,2,0,0
+# ...
+# ./ch-2.pl 2,6,4,5,1,3,7,8,9,10,11,12,13,14,15,16,17,18 1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0
+#
+
+use strict;
+use warnings;
+use feature 'say';
+use Function::Parameters;
+#use Data::Dumper;
+
+
+die "Usage: ordered-lineup heights nfronttaller\n" unless @ARGV==2;
+my( $heightstr, $nfronttallerstr ) = @ARGV;
+
+my @h = split(/,/,$heightstr);
+my @t = split(/,/,$nfronttallerstr);
+
+my $npeople = @h;
+my $nt = @t;
+
+die "ordered-lineup: $npeople heights, $nt nfronttallers\n" unless
+ $npeople == $nt;
+
+my $nfound = 0;
+
+
+#
+# my $ng = ngreater( $x, @v );
+# Determine the number of values in @v that are greater than $x.
+# Return that number.
+#
+fun ngreater( $x, @v )
+{
+ my $ng = grep { $_ > $x } @v;
+ #say "there are $ng elements in ", join(',',@v), " bigger than $x";
+ return $ng;
+}
+
+
+#
+# solve( $hl, $pl );
+# Solve the ordered-lineup problem, given that some
+# progress has already been made: we know the front scalar(@$hl)
+# people of the lineup: they have people numbers @$pl and
+# heights @$hl. Find all possible one-step extensions of this
+# partial solution that are consistent with the rules, and
+# recurse until you find all solutions. Set global $nfound to
+# the number of solutions found; uses globals @h and @t for
+# the data.
+#
+fun solve( $hl, $pl )
+{
+ my %used = map { $_ => 1 } @$pl;
+
+ #say "solving hl ", join(',',@$hl); # , " and pl ", join(',',@$pl);
+
+ my @possp =
+ grep { ! $used{$_} && ngreater( $h[$_], @$hl ) == $t[$_] }
+ 0..$npeople-1;
+ unless( @possp )
+ {
+ #say " no possible next people";
+ return;
+ }
+ #say " possible next heights are ", join(',',map { $h[$_] } @possp);
+
+ foreach my $p (@possp)
+ {
+ # found unused person p, height h[p] > t[p]
+ # elements in $hl. Add it to copies of hl and pl
+ #say "trying unused person $p, height $h[$p] as next";
+
+ my @p2 = @$pl;
+ push @p2, $p;
+ my @h2 = @$hl;
+ push @h2, $h[$p];
+ if( @p2 == $npeople )
+ {
+ say "found solution ", join(',',@h2);
+ $nfound++;
+ } else
+ {
+ solve( \@h2, \@p2 );
+ }
+ }
+}
+
+
+$nfound = 0;
+
+solve( [], [] );