aboutsummaryrefslogtreecommitdiff
path: root/challenge-178
diff options
context:
space:
mode:
authorFlavio Poletti <flavio@polettix.it>2022-08-17 00:01:36 +0200
committerFlavio Poletti <flavio@polettix.it>2022-08-17 00:01:36 +0200
commit876ca5efc8ad875b1bb4d543073cd5d67caa8337 (patch)
tree0b77e7724820843d81ec5deb6f73be3fb9bd3912 /challenge-178
parent4f806bc5039c3ceee8f45a056110e880bdf0db1e (diff)
downloadperlweeklychallenge-club-876ca5efc8ad875b1bb4d543073cd5d67caa8337.tar.gz
perlweeklychallenge-club-876ca5efc8ad875b1bb4d543073cd5d67caa8337.tar.bz2
perlweeklychallenge-club-876ca5efc8ad875b1bb4d543073cd5d67caa8337.zip
Add polettix's solution to challenge-178
Diffstat (limited to 'challenge-178')
-rw-r--r--challenge-178/polettix/blog.txt1
-rw-r--r--challenge-178/polettix/blog1.txt1
-rw-r--r--challenge-178/polettix/perl/ch-1.pl45
-rw-r--r--challenge-178/polettix/perl/ch-2.pl75
-rw-r--r--challenge-178/polettix/raku/ch-1.raku63
-rw-r--r--challenge-178/polettix/raku/ch-2.raku68
6 files changed, 253 insertions, 0 deletions
diff --git a/challenge-178/polettix/blog.txt b/challenge-178/polettix/blog.txt
new file mode 100644
index 0000000000..66cb445e10
--- /dev/null
+++ b/challenge-178/polettix/blog.txt
@@ -0,0 +1 @@
+https://etoobusy.polettix.it/2022/08/17/pwc178-quater-imaginary-base/
diff --git a/challenge-178/polettix/blog1.txt b/challenge-178/polettix/blog1.txt
new file mode 100644
index 0000000000..9de560a0c8
--- /dev/null
+++ b/challenge-178/polettix/blog1.txt
@@ -0,0 +1 @@
+https://etoobusy.polettix.it/2022/08/18/pwc178-business-date/
diff --git a/challenge-178/polettix/perl/ch-1.pl b/challenge-178/polettix/perl/ch-1.pl
new file mode 100644
index 0000000000..965330c0fa
--- /dev/null
+++ b/challenge-178/polettix/perl/ch-1.pl
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl
+use v5.24;
+use warnings;
+use experimental 'signatures';
+no warnings 'experimental::signatures';
+
+say to_m4i(shift // 4);
+
+sub to_m4i ($x) {
+ my ($real, $img) = parse_complex($x);
+ $real = join('0', split m{}mxs, b10_to_bm($real, -4)) || 0;
+ if ($img) {
+ $img = join('0', split m{}mxs, b10_to_bm(-2 * $img, -4));
+ my $after = substr $img, -1, 1, '';
+ $real += $img if $img;
+ $real .= '.' . $after if $after;
+ }
+ return $real;
+}
+
+sub b10_to_bm ($x, $m) {
+ my @digits;
+ while ($x) {
+ my $rem = $x % $m;
+ $x = (($x - $rem) / $m);
+ ($rem, $x) = ($rem - $m, $x + 1) if $rem < 0;
+ unshift @digits, $rem;
+ }
+ return join '', @digits;
+}
+
+sub parse_complex ($x) {
+ $x =~ m{
+ \A\s*(?:
+ (?<real> 0 | -?[1-9]\d*)
+ | (?<real> 0 | -?[1-9]\d*) \s* (?<img> [-+] (?:[1-9]\d*|))i
+ | (?<img> [-+]? (?:[1-9]\d*|))i
+ )\s*\z
+ }mxs;
+ my $real = $+{real} // 0;
+ my $img = $+{img} // 0;
+ $img = 1 if $img eq '' || $img eq '+';
+ $img = -1 if $img eq '-';
+ return ($real, $img);
+}
diff --git a/challenge-178/polettix/perl/ch-2.pl b/challenge-178/polettix/perl/ch-2.pl
new file mode 100644
index 0000000000..b4a2b45064
--- /dev/null
+++ b/challenge-178/polettix/perl/ch-2.pl
@@ -0,0 +1,75 @@
+#!/usr/bin/env perl
+use v5.24;
+use warnings;
+use experimental 'signatures';
+no warnings 'experimental::signatures';
+use Time::Local 'timegm';
+
+my $ts = shift // '2022-08-01 10:30';
+my $duration = shift // 4;
+say add_bh($ts, $duration);
+
+sub parse_datetime ($dt) {
+ my $error = "invalid input timestamp <$dt>\n";
+ my ($y, $m, $d, $H, $M) = $dt =~ m{
+ \A (\d+) - (\d\d) - (\d\d) \ (\d\d):(\d\d) \z
+ }mxs or die $error;
+ die $error unless eval {
+ timegm(0, $M, $H, $d, $m - 1, $y);
+ 1;
+ };
+ return [ $y, $m, $d, $H, $M ];
+}
+
+sub add_bh ($timestamp, $duration) {
+ state $sod_min = 9 * 60;
+ state $eod_min = 18 * 60;
+ my $duration_min = int($duration * 60); # in minutes, rounded down
+
+ my $dt = parse_datetime($timestamp);
+
+ # cope with the possibility that the provided timestamp is
+ # *outside* the allowed range, move to the beginning of the
+ # next business day
+ $dt = next_business_day_start($dt) unless is_in_business($dt);
+ my $start_min = $dt->[3] * 60 + $dt->[4];
+
+ while ($duration_min > 0) {
+ my $available_min = $eod_min - $start_min;
+ if ($duration_min >= $available_min) {
+ $dt = next_business_day_start($dt);
+ $duration_min -= $available_min;
+ $start_min = $sod_min;
+ }
+ else { # we're in the very day!
+ my $stop_min = $start_min + $duration_min;
+ $dt->[4] = my $M = $stop_min % 60;
+ $dt->[3] = ($stop_min - $M) / 60;
+ $duration_min = 0;
+ }
+ }
+
+ return sprintf '%04d-%02d-%02d %02d:%02d', $dt->@*;
+}
+
+sub is_in_business ($dt) {
+ my ($y, $m, $d, $H, $M) = $dt->@*;
+ return if $H < 9 || $H > 17; # 18:00 is out ;)
+ my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
+ gmtime(timegm(0, $M, $H, $d, $m - 1, $y));
+ return (0 < $wday && $wday < 6);
+} ## end sub is_in_business ($dt)
+
+sub next_business_day_start ($dt) {
+ state $day_s = 24 * 60 * 60;
+ my ($y, $m, $d) = $dt->@*;
+ while ('necessary') {
+ my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
+ gmtime($day_s + timegm(0, 30, 12, $d, $m - 1, $y));
+ $year += 1900;
+ ++$mon;
+ return [$year, $mon, $mday, 9, 0]
+ if 0 < $wday && $wday < 6;
+ ($y, $m, $d) = ($year, $mon, $mday);
+ }
+}
diff --git a/challenge-178/polettix/raku/ch-1.raku b/challenge-178/polettix/raku/ch-1.raku
new file mode 100644
index 0000000000..a9968c0e0a
--- /dev/null
+++ b/challenge-178/polettix/raku/ch-1.raku
@@ -0,0 +1,63 @@
+#!/usr/bin/env raku
+use v6;
+
+sub MAIN (Str:D() $x) { put to-m4i($x) }
+
+sub to-m4i (Complex:D() $cx) {
+ my $real = b10-to-bm($cx.re.Int, -4).comb.join('0') || 0;
+ my $img = b10-to-bm(-2 * $cx.im.Int, -4).comb.join('0');
+ if $img.chars {
+ my $after = $img.substr(*-1, 1);
+ $img.substr-rw(*-1, 1) = '';
+ $real += $img if $img.elems;
+ $real ~= '.' ~ $after if $after > 0;
+ }
+ return $real;
+}
+
+multi sub b10-to-bm (Int:D $x is copy, Int:D $m where * < 0 --> Str) {
+ my @digits;
+ while $x {
+ my $rem = $x % $m;
+ $x = (($x - $rem) / $m).Int;
+ ($rem, $x) = $rem - $m, $x + 1 if $rem < 0;
+ @digits.unshift: $rem;
+ }
+ return @digits.join('');
+}
+
+=finish
+
+multi sub b10-to-bm (Int:D $x is copy, Int:D $m where * > 0 --> Str) {
+ my @digits;
+ my $sign = '';
+ ($sign, $x) = ('-', $x.abs) if $x < 0;
+ while $x {
+ @digits.unshift: my $rem = $x % $m;
+ $x = (($x - $rem) / $m).Int;
+ }
+ @digits.unshift: $sign;
+ return @digits.join('');
+}
+
+sub bm-to-b10 (Str:D $x is copy, Int:D $m --> Int) {
+ my $sign = 1;
+ my @digits = $x.comb;
+ if ($m > 0 && @digits[0] eq '-') {
+ @digits.shift;
+ $sign = -1;
+ }
+
+ state %weight_for;
+ if ! %weight_for.elems {
+ %weight_for{$_} = .Int for 0 .. 9;
+ %weight_for{$_} = 10 + .ord - 'a'.ord for 'a' .. 'z';
+ %weight_for{$_} = 10 + .ord - 'A'.ord for 'A' .. 'Z';
+ }
+
+ my $retval = 0;
+ for @digits -> $digit {
+ $retval = $retval * $m + %weight_for{$digit};
+ }
+ return $sign * $retval;
+}
diff --git a/challenge-178/polettix/raku/ch-2.raku b/challenge-178/polettix/raku/ch-2.raku
new file mode 100644
index 0000000000..7d21386572
--- /dev/null
+++ b/challenge-178/polettix/raku/ch-2.raku
@@ -0,0 +1,68 @@
+#!/usr/bin/env raku
+use v6;
+
+sub MAIN ($ts = '2022-08-01 10:30', $duration = 4) {
+ put add-bh($ts, $duration);
+}
+
+sub parse_datetime ($dt) {
+ my $error = "invalid input timestamp <$dt>\n";
+ my $match = $dt ~~ /
+ ^ (\d+) '-' (\d\d) '-' (\d\d) ' ' (\d\d) ':' (\d\d) $
+ / or die $error;
+ my ($y, $m, $d, $H, $M) = $match[0..4].map({0 + $_});
+ try {
+ CATCH {
+ default { die $error }
+ }
+ return DateTime.new(year => $y, month => $m, day => $d,
+ hour => $H, minute => $M);
+ }
+}
+
+sub is-in-business ($dt) {
+ return (
+ (9 <= $dt.hour < 18) # 18:00 is out ;)
+ && (1 <= $dt.day-of-week <= 5)
+ );
+} ## end sub is_in_business ($dt)
+
+sub next-business-day-start ($dt is copy) {
+ loop {
+ $dt = $dt.clone(hour => 9, minute => 0).later(day => 1);
+ return $dt if (1 <= $dt.day-of-week <= 5);
+ }
+}
+
+sub add-bh ($timestamp, $duration) {
+ state $sod_min = 9 * 60;
+ state $eod_min = 18 * 60;
+ my $duration_min = ($duration * 60).Int; # in minutes, rounded down
+
+ my $dt = parse_datetime($timestamp);
+
+ # cope with the possibility that the provided timestamp is
+ # *outside* the allowed range, move to the beginning of the
+ # next business day
+ $dt = next-business-day-start($dt) unless is-in-business($dt);
+ my $start_min = $dt.hour * 60 + $dt.minute;
+
+ while $duration_min > 0 {
+ my $available_min = $eod_min - $start_min;
+ if ($duration_min >= $available_min) {
+ $dt = next-business-day-start($dt);
+ $duration_min -= $available_min;
+ $start_min = $sod_min;
+ }
+ else { # we're in the very day!
+ my $stop_min = $start_min + $duration_min;
+ my $M = $stop_min % 60;
+ my $H = ($stop_min - $M) / 60;
+ $dt = $dt.clone(hour => $H, minute => $M);
+ $duration_min = 0;
+ }
+ }
+
+ return '%04d-%02d-%02d %02d:%02d'.sprintf(
+ $dt.year, $dt.month, $dt.day, $dt.hour, $dt.minute);
+}