aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMohammad Sajid Anwar <Mohammad.Anwar@yahoo.com>2023-12-31 21:40:21 +0000
committerGitHub <noreply@github.com>2023-12-31 21:40:21 +0000
commit596cfc2075123c84437ea3a6b1c5258c7cc244e3 (patch)
treea13c9c0b8cb1366f9aaded13ae12d5f4a0fef8c7
parent1961bfe3aa533d48c23f3fdbedb7bb29d0af18d5 (diff)
parentbc04dc4faa68d042e12646783670d7af859b6b01 (diff)
downloadperlweeklychallenge-club-596cfc2075123c84437ea3a6b1c5258c7cc244e3.tar.gz
perlweeklychallenge-club-596cfc2075123c84437ea3a6b1c5258c7cc244e3.tar.bz2
perlweeklychallenge-club-596cfc2075123c84437ea3a6b1c5258c7cc244e3.zip
Merge pull request #9320 from Util/c249
Add TWC 249 solutions by Bruce Gray (in Raku and F#).
-rw-r--r--challenge-249/bruce-gray/fsharp/ch-1.fsx37
-rw-r--r--challenge-249/bruce-gray/fsharp/ch-2.fsx41
-rw-r--r--challenge-249/bruce-gray/raku/ch-1.raku40
-rw-r--r--challenge-249/bruce-gray/raku/ch-2.raku107
4 files changed, 225 insertions, 0 deletions
diff --git a/challenge-249/bruce-gray/fsharp/ch-1.fsx b/challenge-249/bruce-gray/fsharp/ch-1.fsx
new file mode 100644
index 0000000000..cd56f410ab
--- /dev/null
+++ b/challenge-249/bruce-gray/fsharp/ch-1.fsx
@@ -0,0 +1,37 @@
+// To execute from the command line: `dotnet fsi fsharp/ch-1.fsx`
+
+type tII = int * int // Tuple of two `int`s
+let make_tII (ns: list<int>):tII = ns.[0], ns.[1]
+let tII_same (x: tII): bool = fst x = snd x
+
+let list_to_sorted_tuples_of_two (xs) =
+ xs |> List.sort
+ |> List.chunkBySize 2
+ |> List.map (make_tII)
+
+let task1 (ns: list<int>) =
+
+ let n_n: list<tII> = ns |> list_to_sorted_tuples_of_two
+
+ let all_equal: bool = n_n |> List.forall tII_same
+
+ if all_equal then n_n else []
+
+
+// Below this point is test harness and data
+let mutable test_number = 0
+let is (got: 'a) (expected: 'a) (test_name: string) =
+ test_number <- test_number + 1
+ let ok_msg = if (got = expected) then "ok" else "not ok"
+ printfn $"{ok_msg} {test_number} {test_name}"
+ ()
+
+type TestRecord = { ns: list<int>; expected: list<tII>; }
+// Note: first task example changed here to sorted order, to ease testing.
+let tests : list<TestRecord> = [
+ { ns = [3; 2; 3; 2; 2; 2]; expected = [(2, 2); (2, 2); (3, 3)] };
+ { ns = [1; 2; 3; 4]; expected = [] }
+]
+for test : TestRecord in tests do
+ let got : list<tII> = task1 test.ns
+ is got test.expected "Task1 example"
diff --git a/challenge-249/bruce-gray/fsharp/ch-2.fsx b/challenge-249/bruce-gray/fsharp/ch-2.fsx
new file mode 100644
index 0000000000..1357510d31
--- /dev/null
+++ b/challenge-249/bruce-gray/fsharp/ch-2.fsx
@@ -0,0 +1,41 @@
+// To execute from the command line: `dotnet fsi fsharp/ch-2.fsx`
+
+let task2 (d_and_i_s: string) =
+ // lo,hi will be modified via side-effects within .map
+ let mutable lo = -1
+ let mutable hi = 1 + d_and_i_s.Length
+
+ let squeeze_from_ends (di: char): int =
+ if di = 'D'
+ then hi <- hi - 1; hi
+ else lo <- lo + 1; lo
+
+ (d_and_i_s + "I")
+ |> Seq.map(squeeze_from_ends)
+ |> Seq.toList
+
+// This is only used for extra round-trip testing.
+let ints_to_DI (ns: list<int>) =
+ ns |> List.pairwise
+ |> List.map (fun (a, b) -> if a > b then "D" else "I")
+ |> List.reduce (+)
+
+// Below this point is test harness and data
+let mutable test_number = 0
+let is (got: 'a) (expected: 'a) (test_name: string) =
+ test_number <- test_number + 1
+ let ok_msg = if (got = expected) then "ok" else "not ok"
+ printfn $"{ok_msg} {test_number} {test_name}"
+ ()
+
+type TestRecord = { di: string; expected: list<int> }
+let tests : list<TestRecord> = [
+ { di = "IDID" ; expected = [0; 4; 1; 3; 2] };
+ { di = "III" ; expected = [0; 1; 2; 3] };
+ { di = "DDI" ; expected = [3; 2; 0; 1] };
+]
+
+for test : TestRecord in tests do
+ let got: list<int> = task2 test.di
+ is got test.expected ("Task2 example: " + test.di)
+ is (ints_to_DI got) test.di ("Task2 example: " + test.di + " round-trip")
diff --git a/challenge-249/bruce-gray/raku/ch-1.raku b/challenge-249/bruce-gray/raku/ch-1.raku
new file mode 100644
index 0000000000..b31cc902ed
--- /dev/null
+++ b/challenge-249/bruce-gray/raku/ch-1.raku
@@ -0,0 +1,40 @@
+# By storing @a in a Bag, we have all we need to both
+# check for an even count of each input number,
+# and dish them back out in pairs.
+sub task1_bag_unbag ( @a where * %% 2 ) {
+ my $b = @a.Bag;
+ return $b.values.map(* %% 2).all ?? $b.kxxv.batch(2) !! Empty;
+}
+
+# By sorting @a and pairing up neighbors,
+# we have exactly the pairs that need to be returned,
+# but only after checking that the pairs are all (.[0] == .[1]).
+sub task1_check_pairs_equal ( @a where * %% 2 ) {
+ my @ret = @a.sort.batch(2);
+ return @ret.map({ [eqv] .list }).all ?? @ret !! [];
+}
+
+# The two approaches can be combined, yielding a single-expression solution.
+sub task1_concise ( @a where * %% 2 ) {
+ return @a.Bag.values.all %% 2 ?? @a.sort.batch(2) !! Empty;
+}
+
+
+constant @tests =
+ ( (3, 2, 3, 2, 2, 2), [(2, 2), (3, 3), (2, 2)] ),
+ ( (1, 2, 3, 4), [] ),
+
+ ( (3, 3, 3, 2, 2, 2), [] ),
+;
+constant @subs =
+ :&task1_bag_unbag,
+ :&task1_check_pairs_equal,
+ :&task1_concise,
+;
+use Test; plan @subs * @tests;
+for @subs -> ( :key($sub_name), :value(&task1) ) {
+ for @tests -> ( @in, @expected ) {
+ is-deeply task1(@in).sort.Array,
+ @expected.sort.Array, "$sub_name: @in[]";
+ }
+}
diff --git a/challenge-249/bruce-gray/raku/ch-2.raku b/challenge-249/bruce-gray/raku/ch-2.raku
new file mode 100644
index 0000000000..3fdbb3c787
--- /dev/null
+++ b/challenge-249/bruce-gray/raku/ch-2.raku
@@ -0,0 +1,107 @@
+# Here you can see the evolution of my thinking of the problem,
+# and the resulting changes in my solutions.
+
+# Used by one solution, and to test for validity.
+sub perm_to_DI ( @perm --> Str ) {
+ my Bool @up = @perm Z< @perm.skip;
+ return <D I>[@up].join;
+}
+
+# There are many solutions to most inputs; DID could be any of <1032 2031 2130 3021 3120>.
+# Choice of D/I will produce 2ⁿ possibilities, but permutations will be N!.
+# This can easily generate all of them for analysis; it just returns the first one found.
+# Either way, it will fail to deliver the particular solution given in the task.
+sub task2_many ( Str $s ) {
+ my @a = 0 .. $s.chars;
+ return @a.permutations.first({ .&perm_to_DI eq $s });
+
+ # my @answers = @a.permutations.grep({ .&perm_to_DI eq $s });
+ # return @answers.tail;
+}
+
+# Popping and shifting from ends works well.
+sub task2b ( Str $s ) {
+ my @a = 0 .. $s.chars;
+ my @ret;
+ for $s.comb -> $D_I {
+ push @ret, ($D_I eq 'D') ?? (@a.pop)
+ !! ($D_I eq 'I') ?? (@a.shift)
+ !! die
+ ;
+ }
+ warn if @a != 1; # Only one should be left.
+ push @ret, @a.shift;
+ return @ret.List;
+}
+
+# If we add `D` or `I` to the end, we don't need the final shift/pop!
+sub task2c ( Str $s ) {
+ my @a = 0 .. $s.chars;
+ return "{$s}I".comb.map: {
+ $_ eq 'D' ?? @a.pop
+ !! $_ eq 'I' ?? @a.shift
+ !! die;
+ }
+}
+
+# Hmmm. Could I sweep once and deposit numbers forward for every I, then reverse and deposit for D?
+# Could I classify the index based on D/I? Yes! Works!
+sub task2d ( Str $s ) {
+ my @aa = 0 .. $s.chars;
+ my %DI = "{$s}I".comb.pairs.classify( *.value, :as{.key} );
+
+ my @ret;
+ @ret[ %DI<I>.list ] = @aa.splice(0, %DI<I>.elems) if %DI<I>;
+ @ret[ %DI<D>.list ] = @aa.splice(0, %DI<D>.elems).reverse if %DI<D>;
+ return @ret;
+}
+
+# Tighter.
+sub task2e ( Str $s ) {
+ my ( $D, $I ) = "{$s}I".comb.pairs.classify( *.value eq 'D', :as{.key} ){True, False};
+ my @ret;
+ @ret[ $I.list ] = 0 ..^ $I.elems if $I;
+ @ret[ $D.list ] = $s.chars … ($s.chars - $D.elems + 1) if $D;
+ return @ret;
+}
+
+# Different.
+sub task2f ( Str $s ) {
+ my ( $D, $I ) = "{$s}I".comb.pairs.classify( *.value eq 'D', :as{.key} ){True, False}.map({ $_ // [] });
+ my @ret;
+ @ret[ $I.list ] = 0 ..^ $I.elems;
+ @ret[ $D.list ] = $s.chars … ($s.chars - $D.elems + 1);
+ return @ret;
+}
+
+# Going back to task2c, and realizing that we don't need the whole array when we are only using the endpoints.
+# Best solution!
+sub task2g ( Str $s ) {
+ my ($lo, $hi) = 0, $s.chars;
+
+ return "{$s}I".comb.map({ $_ eq 'D' ?? $hi-- !! $lo++ });
+}
+
+
+my @tests =
+ ( 'IDID' , (0, 4, 1, 3, 2) ),
+ ( 'III' , (0, 1, 2, 3) ),
+ ( 'DDI' , (3, 2, 0, 1) ),
+;
+constant @subs =
+ :&task2_many
+ :&task2b,
+ :&task2c,
+ :&task2d,
+ :&task2e,
+ :&task2f,
+ :&task2g,
+;
+use Test; plan @subs * @tests * 2;
+for @subs -> ( :key($sub_name), :value(&task2) ) {
+ for @tests -> ( $in, @expected ) {
+ is-deeply task2($in).List, @expected, "$sub_name: $in";
+
+ is-deeply perm_to_DI(@expected), $in, "$sub_name: $in round-trip";
+ }
+}