# ETOOBUSY đźš€ minimal blogging for the impatient

# PWC222 - Last Member

**TL;DR**

On with TASK #2 from The Weekly Challenge #222. Enjoy!

# The challenge

You are given an array of positive integers, @ints.

Write a script to find the last member if found otherwise return 0. Each turn pick 2 biggest members (x, y) then decide based on the following conditions, continue this until you are left with 1 member or none.

a) if x == y then remove both members

b) if x != y then remove both members and add new member (y-x)

Example 1:`Input: @ints = (2, 7, 4, 1, 8, 1) Output: 1 Step 1: pick 7 and 8, we remove both and add new member 1 => (2, 4, 1, 1, 1). Step 2: pick 2 and 4, we remove both and add new member 2 => (2, 1, 1, 1). Step 3: pick 2 and 1, we remove both and add new member 1 => (1, 1, 1). Step 4: pick 1 and 1, we remove both => (1).`

Example 2:`Input: @ints = (1) Output: 1`

Example 3:`Input: @ints = (1, 1) Output: 0 Step 1: pick 1 and 1, we remove both and we left with none.`

# The questions

Uh this was so underspecified.

After the correction we are still left with extracting the two top numbers,
*but* itâ€™s not clear from the text whether `x`

should be the bigger or the
other one. Turns out from the examples that itâ€™s the lower of the two,
stillâ€¦

# The solution

As we are required to extract the top two values from the bunch and
*possibly* add a new element to it, this seems a good candidate for using an
optimized data structure like a Priority Queue based on a *Binary Heap*, which has the following complexity:

- insert a new value: $O(log(n))$, which is better than a linear complexity
that we would have by managing a sorted array, so
*yay*! - Build from a list: $O(n \cdot log(n))$, as an extension of inserting a new value above. This is the same as sorting the list so no gain but no loss too.
- Extract the top value: $O(log(n))$, which is
*worse*than the $O(1)$ that we would have with a sorted array.

All in all we might gain or lose depending on the inputs.

In the worst case, all values will always be different from one another, which means that every pair yields a new value and we will have $n - 1$ insertions, that means $O(n \cdot log(n))$ for a priority queue and $O(n^2)$ for a sorted array. Assuming a single pair of different values, we are left with $O(log(n))$ for the priority queue and $O(n)$ for the sorted array. If we define the average case as having $\frac{n}{2}$ insertions, we still end up with the same complexities as the worst case because the insertions are linear with the inputs.

Extractions go the other way around; there will be at least $n$ of them,
with a worst case that is *about* $2n$; anyway, linear with the input. So
the complexity is $O(n \cdot log(n))$ for a priority queue and $O(n)$ for a
sorted array. The average case, as defined above, has the same complexity.

So if we anticipate inputs with very few insertionsâ€¦ a sorted array wins
because the number of insertions will be low. This is what happens if the
values are low and/or mostly similar. On the other hand, the average case
with *generic* values seems to favor the use of a priority queue, so we will
go for it.

Good for us that we have an implementation in cglib-perl and cglib-raku, so letâ€™s go!

Perl first:

```
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
say last_member(@ARGV);
sub last_member (@list) {
my $pq = BasicPriorityQueue->new(
items => \@list,
before => sub { $_[0] > $_[1] }
);
while ($pq->size > 1) {
my $x = $pq->dequeue;
my $delta = $x - $pq->dequeue;
$pq->enqueue($delta) if $delta;
}
return $pq->is_empty ? 0 : $pq->dequeue;
} ## end sub last_member
package BasicPriorityQueue;
use strict; # Adapted from https://algs4.cs.princeton.edu/24pq/
sub dequeue; # see below
sub enqueue; # see below
sub is_empty { return !$#{$_[0]{items}} }
sub top { return $#{$_[0]{items}} ? $_[0]{items}[1] : () }
sub new; # see below
sub size { return $#{$_[0]{items}} }
sub dequeue { # includes "sink"
my ($is, $before, $k) = (@{$_[0]}{qw< items before >}, 1);
return unless $#$is;
my $r = ($#$is > 1) ? (splice @$is, 1, 1, pop @$is) : pop @$is;
while ((my $j = $k * 2) <= $#$is) {
++$j if ($j < $#$is) && $before->($is->[$j + 1], $is->[$j]);
last if $before->($is->[$k], $is->[$j]);
(@{$is}[$j, $k], $k) = (@{$is}[$k, $j], $j);
}
return $r;
} ## end sub dequeue
sub enqueue { # includes "swim"
my ($is, $before) = (@{$_[0]}{qw< items before >});
push @$is, $_[1];
my $k = $#$is;
(@{$is}[$k / 2, $k], $k) = (@{$is}[$k, $k / 2], int($k / 2))
while ($k > 1) && $before->($is->[$k], $is->[$k / 2]);
} ## end sub enqueue
sub new {
my $package = shift;
my $self = bless {((@_ && ref($_[0])) ? %{$_[0]} : @_)}, $package;
$self->{before} ||= sub { $_[0] < $_[1] };
(my $is, $self->{items}) = ($self->{items} || [], ['-']);
$self->enqueue($_) for @$is;
return $self;
} ## end sub new
1;
```

The Raku version is an almost direct translation:

```
#!/usr/bin/env raku
use v6;
sub MAIN (*@list) { put last-member(@list) }
sub last-member (Array(Int()) $list) {
class BasicPriorityQueue { ... }
my $pq = BasicPriorityQueue.new(
items => $list,
before => {$^a > $^b},
);
while ($pq.size() > 1) {
my $x = $pq.dequeue;
my $delta = $x - $pq.dequeue;
$pq.enqueue($delta) if $delta;
}
return $pq.is-empty ?? 0 !! $pq.dequeue();
}
class BasicPriorityQueue {
has @!items;
has &!before;
submethod BUILD (:&!before = {$^a < $^b}, :@items) {
@!items = '-';
self.enqueue($_) for @items;
}
#method dequeue ($obj) <-- see below
method elems { @!items.end }
# method enqueue ($obj) <-- see below
method is-empty { @!items.elems == 1 }
method size { @!items.end }
method top { @!items.end ?? @!items[1] !! Any }
method dequeue () { # includes "sink"
return unless @!items.end;
my $r = @!items.pop;
($r, @!items[1]) = (@!items[1], $r) if @!items.end >= 1;
my $k = 1;
while (my $j = $k * 2) <= @!items.end {
++$j if $j < @!items.end && &!before(@!items[$j + 1], @!items[$j]);
last if &!before(@!items[$k], @!items[$j]);
(@!items[$j, $k], $k) = (|@!items[$k, $j], $j);
}
return $r;
}
method enqueue ($obj) { # includes "swim"
@!items.push: $obj;
my $k = @!items.end;
(@!items[$k/2, $k], $k) = (|@!items[$k, $k/2], ($k/2).Int)
while $k > 1 && &!before(@!items[$k], @!items[$k/2]);
return self;
}
}
```

With all thatâ€¦ stay safe and *partially ordered*!

*Comments? Octodon, , GitHub, Reddit, or drop me a line!*