diff options
| author | Mohammad Sajid Anwar <Mohammad.Anwar@yahoo.com> | 2023-10-08 19:21:43 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-08 19:21:43 +0100 |
| commit | 50ef96e1ce46454072633abc9f11d6a7ec8ea2db (patch) | |
| tree | a8eef15137b9b0f29db196192d49688e12c36fec | |
| parent | fc8ad5c4919e34026ab18433df42b53770b9bf9d (diff) | |
| parent | 8d53d886dc18ad9a528eb01361f6f241f2d0e257 (diff) | |
| download | perlweeklychallenge-club-50ef96e1ce46454072633abc9f11d6a7ec8ea2db.tar.gz perlweeklychallenge-club-50ef96e1ce46454072633abc9f11d6a7ec8ea2db.tar.bz2 perlweeklychallenge-club-50ef96e1ce46454072633abc9f11d6a7ec8ea2db.zip | |
Merge pull request #8827 from demerphq/master
Challenge #237 - Seize The Day and Maximum Greatness
| -rw-r--r-- | challenge-237/demerphq/README.md | 139 | ||||
| -rw-r--r-- | challenge-237/demerphq/blog.txt | 1 | ||||
| -rw-r--r-- | challenge-237/demerphq/js/.gitignore | 2 | ||||
| -rw-r--r-- | challenge-237/demerphq/js/ch-1.js | 53 | ||||
| -rw-r--r-- | challenge-237/demerphq/js/ch-2.js | 51 | ||||
| -rw-r--r-- | challenge-237/demerphq/js/package.json | 5 | ||||
| -rw-r--r-- | challenge-237/demerphq/perl/ch-1.pl | 48 | ||||
| -rw-r--r-- | challenge-237/demerphq/perl/ch-2.pl | 70 |
8 files changed, 369 insertions, 0 deletions
diff --git a/challenge-237/demerphq/README.md b/challenge-237/demerphq/README.md new file mode 100644 index 0000000000..66d2bdc1ca --- /dev/null +++ b/challenge-237/demerphq/README.md @@ -0,0 +1,139 @@ +# Challenge 237 - Seize The Day and Maximise Greatness + +## Seize The Day - Simple Rules The Day + +My solution was based on using Time::Local to compute the epoch for noon +on the first day of the month, and then stepping forward by day until +the required day of week has been found, and then stepping forward by +week until the required repetition of that day has been found, or we ran +off the end of the calender month. Using noon instead of midnight is IMO +a prudent guard against issues related leap seconds, but probably isn't +really necessary but doesn't hurt either. + +## Maximise Greatness - Permutation Misdirection + +Task #2 is more difficult and IMO more interesting. The problem is +stated as the following: "Maximise Greatness: You are given an array of +integers. Write a script to permute the give[n] array such that you get +the maximum possible greatness. To determine greatness, [count pairs +that satisfy] nums[i] < perm[i] where 0 <= i < nums.length", and then +gives the example of + + Input: @nums = (1, 3, 5, 2, 1, 3, 1) + Output: 4 + + One possible permutation: (2, 5, 1, 3, 3, 1, 1) + which returns 4 greatness as below: + nums[0] < perm[0] + nums[1] < perm[1] + nums[3] < perm[3] + nums[4] < perm[4] + +I think this is a fun little problem and a good example of the kind of +problem that gets posed in the perl-weekly challenge. Simple input, +simple output, simple description, but a deeper problem than it might +seem at first blush. + +The reason I say deeper is that way the problem is stated it makes it +seem like we need to compute a permutation, and that the solution to the +problem might be `O(N!)` or `O(N**2)`. But on closer inspection we can +see that we need not compute a permutation at all, and we only need to +do so if we want to visually debug that our answer is correct. It +actually turns out that all we need to do is find the count of the most +frequently occuring number in the array. The maximum greatness will be +the number of elements in the array minus the count of the most +frequently occuring element in the array. + +A complete proof of this is a bit beyond this document. But we can get +the general idea by considering a sorted array. Lets say we have an +array with duplicates, such that `A[0] < A[1] < A[2] ... < A[n-1]`. It +is obvious that the maximum element in the array is the only one that +cannot be paired with something larger than it. We can view this as a +rotation of the array: + +``` + | 1, 2, 3 | + | 2, 3, 1 | <- rotate left by 1. + | X X | <- Which columns satisfy the constraint +``` + +In this case it is obvious that the maximum greatness is 1 less than +number of elements in the array, and 1 is the count of the most +frequently occuring element. If we then duplicate one of the elements +and we rotate the array by different numbers of elements we can see +maximum is the same regardless of which element is duplicated, and that +the rotating any more than the number of duplicates causes the number of +pairs that satisify the constraint to reduce. + +``` + | 1, 1, 2, 3 | 1, 2, 2, 3 | 1, 2, 3, 3 | + | 1, 2, 3, 1 | 2, 2, 3, 1 | 2, 3, 3, 1 | <- rotate 1 + | X X | X X | X X | + + | 1, 1, 2, 3 | 1, 2, 2, 3 | 1, 2, 3, 3 | + | 2, 3, 1, 1 | 2, 3, 1, 2 | 3, 3, 1, 2 | <- rotate 2 + | X X | X X | X X | + + | 1, 1, 2, 3 | 1, 2, 2, 3 | 1, 2, 3, 3 | + | 3, 1, 1, 2 | 3, 1, 2, 2 | 3, 1, 2, 3 | <- rotate 3 + | X | X | X | +``` + +The same pattern holds if the array contains two sets of duplicated +numbers, the maximum number of pairs is found when the rotation matches +the count of the most frequently repeated element: + +``` + | 1, 1, 2, 2, 3 | 1, 2, 2, 3, 3 | 1, 1, 2, 3, 3 | + | 1, 2, 2, 3, 1 | 2, 2, 3, 3, 1 | 1, 2, 3, 3, 1 | <- rotate left by 1 + X X | X X | X X | + + | 1, 1, 2, 2, 3 | 1, 2, 2, 3, 3 | 1, 1, 2, 3, 3 | + | 2, 2, 3, 1, 1 | 2, 3, 3, 1, 2 | 2, 3, 3, 1, 1 | <- rotate left by 2 + | X X X | X X X | X X X | + + | 1, 1, 2, 2, 3 | 1, 2, 2, 3, 3 | 1, 1, 2, 3, 3 | + | 2, 3, 1, 1, 2 | 3, 3, 1, 2, 2 | 3, 3, 1, 1, 2 | <- rotate left by 3 + | X X | X X | X X | +``` + +And lastly consider what happens when we have 1 unique element, 1 pair, +and 1 triplicate: + +``` + | 1, 2, 2, 3, 3, 3 | 1, 1, 2, 2, 2, 3 | 1, 1, 1, 2, 3, 3 | + | 2, 2, 3, 3, 3, 1 | 1, 2, 2, 2, 3, 1 | 1, 1, 2, 3, 3, 1 | <- rotate 1 + | X X | X X | X X | + + | 1, 2, 2, 3, 3, 3 | 1, 1, 2, 2, 2, 3 | 1, 1, 1, 2, 3, 3 | + | 2, 3, 3, 3, 1, 2 | 2, 2, 2, 3, 1, 1 | 1, 2, 3, 3, 1, 1 | <- rotate 2 + | X X X | X X X | X X X | + + | 1, 2, 2, 3, 3, 3 | 1, 1, 2, 2, 2, 3 | 1, 1, 1, 2, 3, 3 | + | 3, 3, 3, 1, 2, 2 | 2, 2, 3, 1, 1, 2 | 2, 3, 3, 1, 1, 1 | <- rotate 3 + | X X X | X X X | X X X | + + | 1, 2, 2, 3, 3, 3 | 1, 1, 2, 2, 2, 3 | 1, 1, 1, 2, 3, 3 | + | 3, 3, 1, 2, 2, 3 | 2, 3, 1, 1, 2, 2 | 3, 3, 1, 1, 1, 2 | <- rotate 4 + | X X | X X | X X | +``` + +In each case we can see that the maximum greatness is determined only +by the number of elements in the array and the count of the most +frequent element. Note, that there may actually be two rotations of a +sorted array that produce the maximum greatness, that of the count of +the most frequent element, and that of the count of the second most +frequent element. If the counts differ then there will be two possible +rotations that give the maximum greatness. + +The last point to observe is that if we **must** produce a permutation +that would produce the maximum greatness then we can produce an array +`S` of the indexes of the input array such that `A[S[i]] <= A[S[i+1]]`. +We can then solve for the sorted array by rotating it left by the count +of the most frequent element, and then use `S` to map the solution into +the appropriate order for the actual input array. + +Thus if we are to compute the maximum greatness alone we can do so +in `O(N)` steps. If we are to produce a permutation of the input +array would produce that maximum greatness we can do so in +`O(N log2 N)` steps. diff --git a/challenge-237/demerphq/blog.txt b/challenge-237/demerphq/blog.txt new file mode 100644 index 0000000000..69ae9e6708 --- /dev/null +++ b/challenge-237/demerphq/blog.txt @@ -0,0 +1 @@ +https://github.com/demerphq/perlweeklychallenge-club/tree/master/challenge-237/demerphq#readme diff --git a/challenge-237/demerphq/js/.gitignore b/challenge-237/demerphq/js/.gitignore new file mode 100644 index 0000000000..d5f19d89b3 --- /dev/null +++ b/challenge-237/demerphq/js/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/challenge-237/demerphq/js/ch-1.js b/challenge-237/demerphq/js/ch-1.js new file mode 100644 index 0000000000..9698ae8546 --- /dev/null +++ b/challenge-237/demerphq/js/ch-1.js @@ -0,0 +1,53 @@ +"use strict"; + +function mdayForWeekDow(yearNum, month, week, dow) { + const initialTime = new Date(Date.UTC(yearNum, month, 1, 12, 0, 0, 0)); + let time = Math.floor( initialTime.getTime() / 1000 ); + let date; + + while (true) { + date = new Date(time * 1000); + if (date.getUTCDay() === dow) { + break; + } + time += 24 * 60 * 60; + } + + for (; week > 1; week--) { + time += 7 * 24 * 60 * 60; + date = new Date(time * 1000); + if (date.getUTCMonth() !== month) { + return 0; + } + } + + return date.getUTCDate(); +} + +const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const monthNames = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +]; + +function numSuffix(n) { + return n + (n === 1 ? "st" : n === 2 ? "nd" : n === 3 ? "rd" : "th"); +} + +const testCases = [ + [2024, 4, 1, 2], + [2025, 10, 2, 4], + [2026, 8, 5, 3], +]; + +testCases.forEach(([year, month, week, dow]) => { + const mday = mdayForWeekDow(year, month - 1, week, dow); + const dayName = dayNames[dow]; + const monthName = monthNames[month - 1]; + if (mday) { + console.log(`The ${numSuffix(week)} ${dayName} of ${year} ${monthName} is the ${numSuffix(mday)}`); + } else { + console.log(`The ${numSuffix(week)} ${dayName} of ${year} ${monthName} does not exist`); + } +}); + diff --git a/challenge-237/demerphq/js/ch-2.js b/challenge-237/demerphq/js/ch-2.js new file mode 100644 index 0000000000..bbe196b695 --- /dev/null +++ b/challenge-237/demerphq/js/ch-2.js @@ -0,0 +1,51 @@ +"use strict"; +"use strict"; +const assert = require("assert"); + +function maximumGreatness(array, permute) { + const seen = new Map(); + let max = 0; + + for (const value of array) { + if (!seen.has(value)) { + seen.set(value, 0); + } + const count = seen.get(value) + 1; + seen.set(value, count); + if (count > max) { + max = count; + } + } + + if (permute) { + const sortedIdx = Array.from(array.keys()).sort((a, b) => { + return array[a] - array[b] || a - b; + }); + + for (let i = 0; i < array.length; i++) { + const next = (i + max) % array.length; + permute[sortedIdx[i]] = array[sortedIdx[next]]; + } + } + + return array.length - max; +} + +const testCases = [ + [4, [1, 3, 5, 2, 1, 3, 1], [2, 1, 1, 5, 3, 1, 3]], + [5, [1, 2, 2, 3, 3, 4, 4], [2, 3, 3, 4, 4, 1, 2]], + [4, [1, 2, 3, 4, 5], [2, 3, 4, 5, 1]], + [3, [55, 22, -33, 10], [-33, 55, 10, 22]] +]; + +testCases.forEach(([want, array, want_permute]) => { + const permute = new Array(array.length); + const count = maximumGreatness(array, permute); + + assert.strictEqual(count, want, `Array [${array}] count should be ${want}`); + if (permute) { + assert.deepStrictEqual(permute, want_permute, `Permute [${want_permute}]`); + } +}); + +console.log("All tests passed!"); diff --git a/challenge-237/demerphq/js/package.json b/challenge-237/demerphq/js/package.json new file mode 100644 index 0000000000..c83455d553 --- /dev/null +++ b/challenge-237/demerphq/js/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "assert": "^2.1.0" + } +} diff --git a/challenge-237/demerphq/perl/ch-1.pl b/challenge-237/demerphq/perl/ch-1.pl new file mode 100644 index 0000000000..dcd85c3ffa --- /dev/null +++ b/challenge-237/demerphq/perl/ch-1.pl @@ -0,0 +1,48 @@ +use strict; +use warnings; +use Time::Local qw(timegm_posix); + +# Seize The Day +# +sub mday_for_week_dow { + my ($year_num, $month, $week, $dow)= @_; + + my $time = timegm_posix( 0, 0, 12, 1, $month, $year_num ); + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst); + + while (1) { + ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)= gmtime($time); + last if $wday == $dow; + $time += (24 * 60 * 60); + } + for (;$week;$week--) { + ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)= gmtime($time); + return 0 if $mon != $month; + $time += (24 * 60 * 60) * 7; + } + return $mday+1; +} + +my @day_name= qw(Sun Mon Tue Wed Thu Fri Sat); +my @month_name= qw(Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec); + +sub num_suffix { + my ($n)= @_; + return $n . ($n == 1 ? "st" : $n == 2 ? "nd" : $n == 3 ? "rd" : "th"); +} + +for my $tuple ( + [2024, 4, 1, 2], + [2025, 10, 2, 4], + [2026, 8, 5, 3], + +) { + my ($year, $month, $week, $dow)= @$tuple; + my $mday = mday_for_week_dow($year, $month-1, $week, $dow); + printf "The %s %s of %s %s %s\n", + num_suffix($week), + $day_name[$dow], + $year, + $month_name[$month-1], + $mday ? "is the ".num_suffix($mday) : "does not exist"; +} diff --git a/challenge-237/demerphq/perl/ch-2.pl b/challenge-237/demerphq/perl/ch-2.pl new file mode 100644 index 0000000000..6bbd7877ee --- /dev/null +++ b/challenge-237/demerphq/perl/ch-2.pl @@ -0,0 +1,70 @@ +use strict; +use warnings; + +# Maximum greatness. Given an array of integers compute +# the maximum number of pairs that satisfy $A[$i] < $A[$j] +# where each possible value of $i and $j is used only once. +# If $permute is passed in then treat it as an array ref +# which is to be populated with the permutation of $array +# that would produce that maximum number of pairs. + +sub maximum_greatness { + my ($array, $permute) = @_; + + # Compute the number of occurrences of each unique value, keeping + # track of the maximum number of occurrences. If the array was large + # and the number of duplicates high we might get better performance + # by doing two loops, first over the array itself, and then over the + # values in the result. But for arrays with few or no duplicated + # values this will be faster. + + my %seen; + my $max = 0; + $seen{$value}++ for @$array; + foreach my $value (@$array) { + my $count = ++$seen{$value}; + $max = $count if $max < $count; + } + + if ($permute) { + # they have asked us to compute the permutation + # as the count, so we have to sort the array. + + # sort the indexes into the original array, not + # the array itself so we can use it to populate + # $permute as well as read from $array. + my @sorted_idx = sort { + $array->[$a] <=> $array->[$b] || + $a <=> $b + } 0 .. $#$array; + + # now compute the permutation + for my $i (0 .. $#$array) { + my $next = ($i + $max) % @$array; + $permute->[$sorted_idx[$i]] = $array->[$sorted_idx[$next]]; + } + } + + return @$array - $max; +} + + +use Test::More; +foreach my $tuple ( + [ 4, [ 1, 3, 5, 2, 1, 3, 1 ], + [ 2, 1, 1, 5, 3, 1, 3 ] ], + [ 5, [ 1, 2, 2, 3, 3, 4, 4 ], + [ 2, 3, 3, 4, 4, 1, 2 ]], + [ 4, [ 1, 2, 3, 4, 5 ], + [ 2, 3, 4, 5, 1 ] ], + [ 3, [ 55, 22, -33, 10 ], + [ -33, 55, 10, 22 ] ], +) { + my ($want, $array, $want_permute)= @$tuple; + my $permute = []; + my $count = maximum_greatness($array, $permute); + is($count, $want, "Array [@$array] count should be $want"); + is("@$permute", "@$want_permute", "Permute [@$want_permute]") + if $permute; +} +done_testing(); |
