aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoger Bell_West <roger@firedrake.org>2023-06-05 10:31:25 +0100
committerRoger Bell_West <roger@firedrake.org>2023-06-05 10:31:25 +0100
commit3b7fa3819a985c034f0d1ad497b488a19bcdbea7 (patch)
treec4af8079af47a5092c850f082e063c2e087046fe
parent401be1861472af6d62bbdeb0fe65f6ced1ca8f31 (diff)
downloadperlweeklychallenge-club-3b7fa3819a985c034f0d1ad497b488a19bcdbea7.tar.gz
perlweeklychallenge-club-3b7fa3819a985c034f0d1ad497b488a19bcdbea7.tar.bz2
perlweeklychallenge-club-3b7fa3819a985c034f0d1ad497b488a19bcdbea7.zip
RogerBW solutions for challenge no. 220
-rwxr-xr-xchallenge-220/roger-bell-west/javascript/ch-1.js68
-rwxr-xr-xchallenge-220/roger-bell-west/javascript/ch-2.js134
-rw-r--r--challenge-220/roger-bell-west/kotlin/ch-1.kt36
-rw-r--r--challenge-220/roger-bell-west/kotlin/ch-2.kt82
-rwxr-xr-xchallenge-220/roger-bell-west/lua/ch-1.lua76
-rwxr-xr-xchallenge-220/roger-bell-west/lua/ch-2.lua155
-rwxr-xr-xchallenge-220/roger-bell-west/perl/ch-1.pl27
-rwxr-xr-xchallenge-220/roger-bell-west/perl/ch-2.pl63
-rw-r--r--challenge-220/roger-bell-west/postscript/ch-1.ps253
-rw-r--r--challenge-220/roger-bell-west/postscript/ch-2.ps323
-rwxr-xr-xchallenge-220/roger-bell-west/python/ch-1.py26
-rwxr-xr-xchallenge-220/roger-bell-west/python/ch-2.py51
-rwxr-xr-xchallenge-220/roger-bell-west/raku/ch-1.p620
-rwxr-xr-xchallenge-220/roger-bell-west/raku/ch-2.p656
-rwxr-xr-xchallenge-220/roger-bell-west/ruby/ch-1.rb29
-rwxr-xr-xchallenge-220/roger-bell-west/ruby/ch-2.rb45
-rwxr-xr-xchallenge-220/roger-bell-west/rust/ch-1.rs35
-rwxr-xr-xchallenge-220/roger-bell-west/rust/ch-2.rs48
-rw-r--r--challenge-220/roger-bell-west/tests.yaml39
19 files changed, 1566 insertions, 0 deletions
diff --git a/challenge-220/roger-bell-west/javascript/ch-1.js b/challenge-220/roger-bell-west/javascript/ch-1.js
new file mode 100755
index 0000000000..fa14df573c
--- /dev/null
+++ b/challenge-220/roger-bell-west/javascript/ch-1.js
@@ -0,0 +1,68 @@
+#! /usr/bin/node
+
+"use strict"
+
+// by Frank Tan
+// https://stackoverflow.com/questions/38400594/javascript-deep-comparison
+function deepEqual(a,b)
+{
+ if( (typeof a == 'object' && a != null) &&
+ (typeof b == 'object' && b != null) )
+ {
+ var count = [0,0];
+ for( var key in a) count[0]++;
+ for( var key in b) count[1]++;
+ if( count[0]-count[1] != 0) {return false;}
+ for( var key in a)
+ {
+ if(!(key in b) || !deepEqual(a[key],b[key])) {return false;}
+ }
+ for( var key in b)
+ {
+ if(!(key in a) || !deepEqual(b[key],a[key])) {return false;}
+ }
+ return true;
+ }
+ else
+ {
+ return a === b;
+ }
+}
+
+function word2set(word) {
+ let r = new Set();
+ for (let c of word.toLowerCase()) {
+ if (c >= 'a' && c <= 'z') {
+ r.add(c);
+ }
+ }
+ return r
+}
+
+function commoncharacters(lst) {
+ let c = word2set(lst[0]);
+ for (let w of lst.slice(1)) {
+ const d = word2set(w);
+ for (let l of c) {
+ if (!d.has(l)) {
+ c.delete(l);
+ }
+ }
+ }
+ let s = Array.from(c);
+ s.sort();
+ return s;
+}
+
+if (deepEqual(commoncharacters(['Perl', 'Rust', 'Raku']), ['r'])) {
+ process.stdout.write("Pass");
+} else {
+ process.stdout.write("FAIL");
+}
+process.stdout.write(" ");
+if (deepEqual(commoncharacters(['love', 'live', 'leave']), ['e', 'l', 'v'])) {
+ process.stdout.write("Pass");
+} else {
+ process.stdout.write("FAIL");
+}
+process.stdout.write("\n");
diff --git a/challenge-220/roger-bell-west/javascript/ch-2.js b/challenge-220/roger-bell-west/javascript/ch-2.js
new file mode 100755
index 0000000000..8937d6f67a
--- /dev/null
+++ b/challenge-220/roger-bell-west/javascript/ch-2.js
@@ -0,0 +1,134 @@
+#! /usr/bin/node
+
+"use strict"
+
+// by Frank Tan
+// https://stackoverflow.com/questions/38400594/javascript-deep-comparison
+function deepEqual(a,b)
+{
+ if( (typeof a == 'object' && a != null) &&
+ (typeof b == 'object' && b != null) )
+ {
+ var count = [0,0];
+ for( var key in a) count[0]++;
+ for( var key in b) count[1]++;
+ if( count[0]-count[1] != 0) {return false;}
+ for( var key in a)
+ {
+ if(!(key in b) || !deepEqual(a[key],b[key])) {return false;}
+ }
+ for( var key in b)
+ {
+ if(!(key in a) || !deepEqual(b[key],a[key])) {return false;}
+ }
+ return true;
+ }
+ else
+ {
+ return a === b;
+ }
+}
+
+function permute(a) {
+ let out = [];
+ let n=a.length;
+ let c=[];
+ for (let i = 0; i < n; i++) {
+ c.push(0);
+ }
+ out.push([...a]);
+ let i=0;
+ while (true) {
+ if (i >= n) {
+ break;
+ }
+ if (c[i] < i) {
+ if (i % 2 == 0) {
+ [a[0],a[i]] = [a[i],a[0]];
+ } else {
+ [a[c[i]],a[i]] = [a[i],a[c[i]]];
+ }
+ out.push([...a]);
+ c[i]++;
+ i=0;
+ } else {
+ c[i]=0;
+ i++;
+ }
+ }
+ return out;
+}
+
+// by VLAZ
+// https://stackoverflow.com/a/59322890
+function toWindows(inputArray, size) {
+ return Array.from(
+ {length: inputArray.length - (size - 1)}, //get the appropriate length
+ (_, index) => inputArray.slice(index, index+size) //create the windows
+ )
+}
+
+function squared(a) {
+ return a * a;
+}
+
+function decode(a0, base) {
+ let eq = [];
+ let a = a0;
+ while (a > 0) {
+ eq.unshift(a % base);
+ a = Math.trunc(a / base);
+ }
+ return eq;
+}
+
+function encode(sq, base) {
+ let a = 0;
+ for (let v of sq) {
+ a *= base;
+ a += v;
+ }
+ return a;
+}
+
+function squareful(lst) {
+ let results = new Set();
+ let squares = new Set();
+ const base = Math.max(...lst) + 1;
+ for (let la of permute(lst)) {
+ let squareful = true;
+ for (let a of toWindows(la, 2)) {
+ const cs = a[0] + a[1];
+ let mx = squared(squares.size);
+ while (cs > mx) {
+ mx = squared(squares.size + 1);
+ squares.add(mx);
+ }
+ if (!squares.has(cs)) {
+ squareful = false;
+ break;
+ }
+ }
+ if (squareful) {
+ results.add(encode(la, base));
+ }
+ }
+ let s = Array.from(results);
+ s.sort(function(a,b) {
+ return a-b;
+ });
+ return s.map(x => decode(x, base));
+}
+
+if (deepEqual(squareful([1, 17, 8]), [[1, 8, 17], [17, 8, 1]])) {
+ process.stdout.write("Pass");
+} else {
+ process.stdout.write("FAIL");
+}
+process.stdout.write(" ");
+if (deepEqual(squareful([2, 2, 2]), [[2, 2, 2]])) {
+ process.stdout.write("Pass");
+} else {
+ process.stdout.write("FAIL");
+}
+process.stdout.write("\n");
diff --git a/challenge-220/roger-bell-west/kotlin/ch-1.kt b/challenge-220/roger-bell-west/kotlin/ch-1.kt
new file mode 100644
index 0000000000..dcddd560c5
--- /dev/null
+++ b/challenge-220/roger-bell-west/kotlin/ch-1.kt
@@ -0,0 +1,36 @@
+fun word2set(word: String): MutableSet<Char> {
+ var r = mutableSetOf<Char>()
+ for (c in word.lowercase()) {
+ if (c >= 'a' && c <= 'z') {
+ r.add(c)
+ }
+ }
+ return r
+}
+
+fun commoncharacters(lst: List<String>): List<Char> {
+ var c = word2set(lst[0])
+ for (w in lst.drop(1)) {
+ c = c.intersect(word2set(w)).toMutableSet()
+ }
+ var s = ArrayList(c)
+ s.sort()
+ return s.toList()
+}
+
+fun main() {
+
+ if (commoncharacters(listOf("Perl", "Rust", "Raku")) == listOf('r')) {
+ print("Pass")
+ } else {
+ print("Fail")
+ }
+ print(" ")
+ if (commoncharacters(listOf("love", "live", "leave")) == listOf('e', 'l', 'v')) {
+ print("Pass")
+ } else {
+ print("Fail")
+ }
+ println("")
+
+}
diff --git a/challenge-220/roger-bell-west/kotlin/ch-2.kt b/challenge-220/roger-bell-west/kotlin/ch-2.kt
new file mode 100644
index 0000000000..02832eb560
--- /dev/null
+++ b/challenge-220/roger-bell-west/kotlin/ch-2.kt
@@ -0,0 +1,82 @@
+fun permute(aa: List<Int>): ArrayList<List<Int>> {
+ var a = ArrayList<Int>()
+ for (i in aa) {
+ a.add(i)
+ }
+ var out = ArrayList<List<Int>>()
+ val n = a.size
+ var c = ArrayList<Int>();
+ for (i in 0..n-1) {
+ c.add(0)
+ }
+ out.add(a.toList())
+ var i = 0
+ while (true) {
+ if (i >= n) {
+ break
+ }
+ if (c[i] < i) {
+ if (i % 2 == 0) {
+ val tmp = a[0]
+ a[0] = a[i]
+ a[i] = tmp
+ } else {
+ val tmp = a[c[i]]
+ a[c[i]] = a[i]
+ a[i] = tmp
+ }
+ out.add(a.toList())
+ c[i] += 1
+ i = 0
+ } else {
+ c[i] = 0
+ i += 1
+ }
+ }
+ return out
+}
+
+fun squared(a: Int): Int {
+ return a * a
+}
+
+fun squareful(lst: List<Int>): List<List<Int>> {
+ var results = mutableSetOf<List<Int>>()
+ var squares = mutableSetOf<Int>()
+ for (la in permute(lst)) {
+ var squareful = true
+ for (a in la.windowed(size = 2)) {
+ val cs = a[0] + a[1]
+ var mx = squared(squares.size)
+ while (cs > mx) {
+ mx = squared(squares.size + 1)
+ squares.add(mx)
+ }
+ if (!squares.contains(cs)) {
+ squareful = false
+ break
+ }
+ }
+ if (squareful) {
+ results.add(la)
+ }
+ }
+ return results.toList()
+}
+
+fun main() {
+
+ if (squareful(listOf(1, 17, 8)) == listOf(listOf(1, 8, 17), listOf(17, 8, 1))) {
+ print("Pass")
+ } else {
+ print("Fail")
+ }
+ print(" ")
+ if (squareful(listOf(2, 2, 2)) == listOf(listOf(2, 2, 2))) {
+ print("Pass")
+ } else {
+ print("Fail")
+ }
+ println("")
+
+}
diff --git a/challenge-220/roger-bell-west/lua/ch-1.lua b/challenge-220/roger-bell-west/lua/ch-1.lua
new file mode 100755
index 0000000000..74624b0e7e
--- /dev/null
+++ b/challenge-220/roger-bell-west/lua/ch-1.lua
@@ -0,0 +1,76 @@
+#! /usr/bin/lua
+
+-- by Michael Anderson at
+-- https://stackoverflow.com/questions/8722620/comparing-two-index-tables-by-index-value-in-lua
+-- modified by Roger
+function recursive_compare(t1,t2)
+ -- Use usual comparison first.
+ if t1==t2 then return true end
+ -- We only support non-default behavior for tables
+ if (type(t1)~="table") then return false end
+ -- They better have the same metatables
+ local mt1 = getmetatable(t1)
+ local mt2 = getmetatable(t2)
+ if( not recursive_compare(mt1,mt2) ) then return false end
+ -- Build list of all keys
+ local kk = {}
+ for k1, _ in pairs(t1) do
+ kk[k1] = true
+ end
+ for k2, _ in pairs(t2) do
+ kk[k2] = true
+ end
+ -- Check each key that exists in at least one table
+ for _, k in ipairs(kk) do
+ if (not recursive_compare(t1[k], t2[k])) then
+ return false
+ end
+ end
+ return true
+end
+
+function keys(t)
+ local a = {}
+ for k, v in pairs(t) do
+ table.insert(a, k)
+ end
+ return a
+end
+
+function word2set(word)
+ local s = {}
+ for c in string.gmatch(string.lower(word), "%a") do
+ s[c] = true
+ end
+ return s
+end
+
+function commoncharacters(lst)
+ local c = word2set(lst[1])
+ for i = 2, #lst do
+ local d = word2set(lst[i])
+ for k, _ in pairs(c) do
+ if d[k] == nil then
+ c[k] = nil
+ end
+ end
+ end
+ local o = keys(c)
+ table.sort(o)
+ return o
+end
+
+if recursive_compare(commoncharacters({"Perl", "Rust", "Raku"}), {"r"}) then
+ io.write("Pass")
+else
+ io.write("FAIL")
+end
+io.write(" ")
+
+if recursive_compare(commoncharacters({"love", "live", "leave"}), {"e", "l", "v"}) then
+ io.write("Pass")
+else
+ io.write("FAIL")
+end
+print("")
+
diff --git a/challenge-220/roger-bell-west/lua/ch-2.lua b/challenge-220/roger-bell-west/lua/ch-2.lua
new file mode 100755
index 0000000000..57254c3a40
--- /dev/null
+++ b/challenge-220/roger-bell-west/lua/ch-2.lua
@@ -0,0 +1,155 @@
+#! /usr/bin/lua
+
+-- by Michael Anderson at
+-- https://stackoverflow.com/questions/8722620/comparing-two-index-tables-by-index-value-in-lua
+-- modified by Roger
+function recursive_compare(t1,t2)
+ -- Use usual comparison first.
+ if t1==t2 then return true end
+ -- We only support non-default behavior for tables
+ if (type(t1)~="table") then return false end
+ -- They better have the same metatables
+ local mt1 = getmetatable(t1)
+ local mt2 = getmetatable(t2)
+ if( not recursive_compare(mt1,mt2) ) then return false end
+ -- Build list of all keys
+ local kk = {}
+ for k1, _ in pairs(t1) do
+ kk[k1] = true
+ end
+ for k2, _ in pairs(t2) do
+ kk[k2] = true
+ end
+ -- Check each key that exists in at least one table
+ for k, _ in pairs(kk) do
+ if (not recursive_compare(t1[k], t2[k])) then
+ return false
+ end
+ end
+ return true
+end
+
+function keys(t)
+ local a = {}
+ for k, v in pairs(t) do
+ table.insert(a, k)
+ end
+ return a
+end
+
+function deepcopy(src)
+ local dst = {}
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ v = deepcopy(v)
+ end
+ dst[k] = v
+ end
+ return dst
+end
+
+function permute(a)
+ local out = {}
+ local n = #a
+ local c = {}
+ for i = 1,n do
+ table.insert(c, 1)
+ end
+ table.insert(out, deepcopy(a))
+ local i=1
+ while true do
+ if i > n then
+ break
+ end
+ if c[i] < i then
+ if i % 2 == 1 then
+ a[1],a[i] = a[i],a[1]
+ else
+ a[c[i]],a[i] = a[i],a[c[i]]
+ end
+ table.insert(out, deepcopy(a))
+ c[i] = c[i]+1
+ i = 1
+ else
+ c[i] = 1
+ i = i+1
+ end
+ end
+ return out
+end
+
+function propersize(t)
+ local l=0
+ for k,v in pairs(t) do
+ l = l + 1
+ end
+ return l
+end
+
+function squared(a)
+ return a * a
+end
+
+function encode(sq, base)
+ local a = 0
+ for _, v in ipairs(sq) do
+ a = a * base
+ a = a + v
+ end
+ return a
+end
+
+function decode(a, base)
+ local eq = {}
+ while a > 0 do
+ table.insert(eq, 1, a % base)
+ a = a // base
+ end
+ return eq
+end
+
+function squareful(lst)
+ local results = {}
+ local squares = {}
+ local base = math.max(table.unpack(lst)) + 1
+ for _, la in ipairs(permute(lst)) do
+ local squareful = true
+ for i = 1, #la-1 do
+ local cs = la[i] + la[i + 1]
+ local mx = squared(propersize(squares))
+ while cs > mx do
+ mx = squared(propersize(squares) + 1)
+ squares[mx] = true
+ end
+ if squares[cs] == nil then
+ squareful = false
+ break
+ end
+ end
+ if squareful then
+ results[encode(la, base)] = true
+ end
+ end
+ local o = keys(results)
+ table.sort(o)
+ local out = {}
+ for _, v in ipairs(o) do
+ table.insert(out, decode(v, base))
+ end
+ return out
+end
+
+if recursive_compare(squareful({1, 17, 8}), {{1, 8, 17}, {17, 8, 1}}) then
+ io.write("Pass")
+else
+ io.write("FAIL")
+end
+io.write(" ")
+
+if recursive_compare(squareful({2, 2, 2}), {{2, 2, 2}}) then
+ io.write("Pass")
+else
+ io.write("FAIL")
+end
+print("")
+
diff --git a/challenge-220/roger-bell-west/perl/ch-1.pl b/challenge-220/roger-bell-west/perl/ch-1.pl
new file mode 100755
index 0000000000..9370a80063
--- /dev/null
+++ b/challenge-220/roger-bell-west/perl/ch-1.pl
@@ -0,0 +1,27 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use experimental 'signatures';
+
+use Test::More tests => 2;
+
+is_deeply(commoncharacters(['Perl', 'Rust', 'Raku']), ['r'], 'example 1');
+is_deeply(commoncharacters(['love', 'live', 'leave']), ['e', 'l', 'v'], 'example 2');
+
+sub word2set($word) {
+ return {map {$_ => 1} grep /[a-z]/, split '', lc($word)};
+}
+
+sub commoncharacters($lst) {
+ my $c = word2set($lst->[0]);
+ foreach my $si (1 .. $#{$lst}) {
+ my $d = word2set($lst->[$si]);
+ foreach my $l (keys %{$c}) {
+ unless (exists $d->{$l}) {
+ delete $c->{$l};
+ }
+ }
+ }
+ return [sort keys %{$c}];
+}
diff --git a/challenge-220/roger-bell-west/perl/ch-2.pl b/challenge-220/roger-bell-west/perl/ch-2.pl
new file mode 100755
index 0000000000..4785f189c4
--- /dev/null
+++ b/challenge-220/roger-bell-west/perl/ch-2.pl
@@ -0,0 +1,63 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use experimental 'signatures';
+
+use Algorithm::Permute;
+use List::Util qw(max);
+
+use Test::More tests => 2;
+
+is_deeply(squareful([1, 17, 8]), [[1, 8, 17], [17, 8, 1]], 'example 1');
+is_deeply(squareful([2, 2, 2]), [[2, 2, 2]], 'example 2');
+
+
+sub squared($a) {
+ return $a * $a;
+}
+
+sub decode($a0, $base) {
+ my @eq;
+ my $a = $a0;
+ while ($a > 0) {
+ unshift @eq, $a % $base;
+ $a = int($a / $base);
+ }
+ return \@eq;
+}
+
+sub encode($sq, $base) {
+ my $a = 0;
+ foreach my $v (@{$sq}) {
+ $a *= $base;
+ $a += $v;
+ }
+ return $a;
+}
+
+sub squareful($lst) {
+ my %results;
+ my %squares;
+ my $base = max(@{$lst}) + 1;
+ my $p = Algorithm::Permute->new($lst);
+ while (my @la = $p->next) {
+ my $squareful = 1;
+ foreach my $i (0 .. $#la - 1) {
+ my $cs = $la[$i] + $la[$i + 1];
+ my $mx = squared(scalar keys %squares);
+ while ($cs > $mx) {
+ $mx = squared((scalar keys %squares) + 1);
+ $squares{$mx} = 1;
+ }
+ unless (exists $squares{$cs}) {
+ $squareful = 0;
+ last;
+ }
+ }
+ if ($squareful) {
+ $results{encode(\@la, $base)} = 1;
+ }
+ }
+ return [map {decode($_, $base)} sort {$a <=> $b} keys %results];
+}
diff --git a/challenge-220/roger-bell-west/postscript/ch-1.ps b/challenge-220/roger-bell-west/postscript/ch-1.ps
new file mode 100644
index 0000000000..d46e25cd53
--- /dev/null
+++ b/challenge-220/roger-bell-west/postscript/ch-1.ps
@@ -0,0 +1,253 @@
+%!PS
+
+% begin included library code
+% see https://codeberg.org/Firedrake/postscript-libraries/
+/map { % array proc -> array
+ 2 dict begin
+ /p exch def
+ [ exch
+ {
+ p
+ } forall
+ ]
+ end
+} bind def
+
+/s2a {
+ [ exch { } forall ]
+} bind def
+
+/quicksort.main { % lo hi -> (null)
+ 3 dict begin
+ /hi exch def
+ /lo exch def
+ /xit false def
+ lo 0 lt {
+ /xit true def
+ } if
+ hi 0 lt {
+ /xit true def
+ } if
+ lo hi ge {
+ /xit true def
+ } if
+ xit not {
+ /p quicksort.partition def
+ lo p quicksort.main
+ p 1 add hi quicksort.main
+ } if
+ end
+} bind def
+
+/quicksort.with_comparator { % [ a c b ] { comparator } -> [ a b c ]
+ 2 dict begin
+ /cmp exch def
+ /arr exch def
+ arr length 0 gt {
+ 0 arr length 1 sub quicksort.main
+ } if
+ arr
+ end
+} bind def
+
+/quicksort.swap {
+ 2 dict begin
+ /bi exch def
+ /ai exch def
+ arr ai get
+ arr bi get
+ arr exch ai exch put
+ arr exch bi exch put
+ end
+} bind def
+
+/quicksort.partition {
+ 3 dict begin
+ /pivot arr hi lo add 2 idiv get def
+ /i lo 1 sub def
+ /j hi 1 add def
+ {
+ {
+ /i i 1 add def
+ arr i get pivot cmp 0 ge {
+ exit
+ } if
+ } loop
+ {
+ /j j 1 sub def
+ arr j get pivot cmp 0 le {
+ exit
+ } if
+ } loop
+ i j ge {
+ j
+ exit
+ } if
+ i j quicksort.swap
+ } loop
+ end
+} bind def
+
+/deepeq {
+ 2 dict begin
+ /a exch def
+ /b exch def
+ a type b type eq {
+ a type /dicttype eq {
+ a length b length eq {
+ <<
+ a {
+ pop
+ true
+ } forall
+ b {
+ pop
+ true
+ } forall
+ >>
+ true exch
+ {
+ pop
+ dup a exch known {
+ dup b exch known {
+ dup a exch get exch b exch get deepeq not {
+ pop false
+ } if
+ } {
+ false
+ } ifelse
+ } {
+ false
+ } ifelse
+ } forall
+ } {
+ false
+ } ifelse
+ } {
+ a type dup /arraytype eq exch /stringtype eq or {
+ a length b length eq {
+ true
+ 0 1 a length 1 sub {
+ dup a exch get exch b exch get deepeq not {
+ pop false
+ exit
+ } if
+ } for
+ } {
+ false
+ } ifelse
+ } {
+ a b eq
+ } ifelse
+ } ifelse
+ } {
+ false
+ } ifelse
+ end
+} bind def
+
+/toset { % array -> dict of (value, true)
+ << exch
+ {
+ true
+ } forall
+ >>
+} bind def
+
+/quicksort.cmp {
+ 2 copy
+ lt {
+ pop pop -1
+ } {
+ gt {
+ 1
+ } {
+ 0
+ } ifelse
+ } ifelse
+} bind def
+
+/test.start {
+ print (:) print
+ /test.pass 0 def
+ /test.count 0 def
+} bind def
+
+/test.end {
+ ( ) print
+ test.count 0 gt {
+ (Passed ) print
+ test.pass (...) cvs print
+ (/) print
+ test.count (...) cvs print
+ ( \() print
+ test.pass 100 mul test.count idiv (...) cvs print
+ (%\)) print
+ (\r\n) print
+ } if
+} bind def
+
+/quicksort {
+ { quicksort.cmp } quicksort.with_comparator
+} bind def
+
+/keys { % dict -> array of dict keys
+ [ exch
+ {
+ pop
+ } forall
+ ]
+} bind def
+
+/test {
+ /test.count test.count 1 add def
+ {
+ /test.pass test.pass 1 add def
+ } {
+ ( ) print
+ test.count (....) cvs print
+ (-fail) print
+ } ifelse
+} bind def
+
+
+% end included library code
+
+/word2set {
+ 1 dict begin
+ [ exch
+ s2a {
+ /ch exch def
+ ch 65 ge ch 90 le and {
+ ch 32 add
+ } {
+ ch 97 ge ch 122 le and {
+ ch
+ } if
+ } ifelse
+ } forall
+ ] toset
+ end
+} bind def
+
+/commoncharacters {
+ 4 dict begin
+ /lst exch def
+ /c lst 0 get word2set def
+ 1 1 lst length 1 sub {
+ lst exch get word2set /d exch def
+ c keys {
+ /k exch def
+ d k known not {
+ c k undef
+ } if
+ } forall
+ } for
+ c keys quicksort { 1 string dup 0 4 -1 roll put } map
+ end
+} bind def
+
+(commoncharacters) test.start
+[(Perl) (Rust) (Raku)] commoncharacters [(r)] deepeq test
+[(love) (live) (leave)] commoncharacters [(e) (l) (v)] deepeq test
+test.end
diff --git a/challenge-220/roger-bell-west/postscript/ch-2.ps b/challenge-220/roger-bell-west/postscript/ch-2.ps
new file mode 100644
index 0000000000..b47b145c36
--- /dev/null
+++ b/challenge-220/roger-bell-west/postscript/ch-2.ps
@@ -0,0 +1,323 @@
+%!PS
+
+% begin included library code
+% see https://codeberg.org/Firedrake/postscript-libraries/
+/test.end {
+ ( ) print
+ test.count 0 gt {
+ (Passed ) print
+ test.pass (...) cvs print
+ (/) print
+ test.count (...) cvs print
+ ( \() print
+ test.pass 100 mul test.count idiv (...) cvs print
+ (%\)) print
+ (\r\n) print
+ } if
+} bind def
+
+/reverse {
+ 1 dict begin
+ dup length /l exch def
+ [ exch
+ aload pop
+ 2 1 l {
+ -1 roll
+ } for
+ ]
+ end
+} bind def
+
+/listmax {
+ { max } reduce
+} bind def
+
+/keys { % dict -> array of dict keys
+ [ exch
+ {
+ pop
+ } forall
+ ]
+} bind def
+
+/quicksort.with_comparator { % [ a c b ] { comparator } -> [ a b c ]
+ 2 dict begin
+ /cmp exch def
+ /arr exch def
+ arr length 0 gt {
+ 0 arr length 1 sub quicksort.main
+ } if
+ arr
+ end
+} bind def
+
+/permute { % [array] {proc} permute runs proc on each permutation of array
+ 7 dict begin
+ /permute.subproc exch def
+ /permute.a exch def
+ /permute.n permute.a length def
+ /permute.c [ permute.n { 0 } repeat ] def
+ permute.a permute.subproc
+ /permute.i 0 def
+ {
+ permute.i permute.n ge {
+ exit
+ } if
+ permute.c permute.i get permute.i lt {
+ permute.i 2 mod 0 eq {
+ 0 permute.i permute.swap
+ } {
+ permute.c permute.i get permute.i permute.swap
+ } ifelse
+ permute.a permute.subproc
+ permute.c permute.i get 1 add permute.c exch permute.i exch put
+ /permute.i 0 def
+ } {
+ permute.c permute.i 0 put
+ /permute.i permute.i 1 add def
+ } ifelse
+ } loop
+ end
+} bind def
+
+/permute.swap {
+ /permute.bi exch def
+ /permute.ai exch def
+ permute.a permute.ai get
+ permute.a permute.bi get
+ permute.a exch permute.ai exch put
+ permute.a exch permute.bi exch put
+} bind def
+
+/quicksort.cmp {
+ 2 copy
+ lt {
+ pop pop -1
+ } {
+ gt {
+ 1
+ } {
+ 0
+ } ifelse
+ } ifelse
+} bind def
+
+/quicksort {
+ { quicksort.cmp } quicksort.with_comparator
+} bind def
+
+/quicksort.swap {
+ 2 dict begin
+ /bi exch def
+ /ai exch def
+ arr ai get
+ arr bi get
+ arr exch ai exch put
+ arr exch bi exch put
+ end
+} bind def
+
+/toset { % array -> dict of (value, true)
+ << exch
+ {
+ true
+ } forall
+ >>
+} bind def
+
+/quicksort.partition {
+ 3 dict begin
+ /pivot arr hi lo add 2 idiv get def
+ /i lo 1 sub def
+ /j hi 1 add def
+ {
+ {
+ /i i 1 add def
+ arr i get pivot cmp 0 ge {
+ exit
+ } if
+ } loop
+ {
+ /j j 1 sub def