site.base_url }}/">The Bear's Den -->

The Bear's Den

Enter at your own risk

Counting Ones

Task 1: Maximum Ones

Submitted by: Mohammad Sajid Anwar


You are given a m x n binary matrix.

Write a script to return the row number containing maximum ones, in case of more than one rows then return smallest row number.

Example 1

Input: $matrix = [ [0, 1],
                   [1, 0],
                 ]
Output: 1

Row 1 and Row 2 have the same number of ones, so return row 1.

Example 2

Input: $matrix = [ [0, 0, 0],
                   [1, 0, 1],
                 ]
Output: 2

Row 2 has the maximum ones, so return row 2.

Example 3

Input: $matrix = [ [0, 0],
                   [1, 1],
                   [0, 0],
                 ]
Output: 2

Row 2 have the maximum ones, so return row 2.

Solution

As the matrix shall be binary, all we need to do is sum over the rows and find the one-based index of the maximum.

use strict;
use warnings;
use PDL;

sub maximum_ones {
    1 + maximum_ind sumover pdl @_;
}

See the full solution to task 1.

Task 2: Sort by 1 bits

Submitted by: Mohammad Sajid Anwar


You are give an array of integers, @ints.

Write a script to sort the integers in ascending order by the number of 1 bits in their binary representation. In case more than one integers have the same number of 1 bits then sort them in ascending order.

Example 1

Input: @ints = (0, 1, 2, 3, 4, 5, 6, 7, 8)
Output: (0, 1, 2, 4, 8, 3, 5, 6, 7)

0 = 0 one bits
1 = 1 one bits
2 = 1 one bits
4 = 1 one bits
8 = 1 one bits
3 = 2 one bits
5 = 2 one bits
6 = 2 one bits
7 = 3 one bits

Example 2

Input: @ints = (1024, 512, 256, 128, 64)
Output: (64, 128, 256, 512, 1024)

All integers in the given array have one 1-bits, so just sort them in ascending
order.

Solutions

This is a nice use case for a Schwartzian transform:

There is an issue with this task: Though negative numbers are permitted:

You are give an array of integers, @ints.

It is not specified how these are represented.

The most common representation of negative numbers is two’s-complement. If we are going to count bits, we need to fix the numbers’ bit size, as -1 is “all bits set”, which may be arbitrary large.

Fixing this issue by assuming a 32-bit signed representation. This means that -1 is represented as 0xFFFFFFFF and -2**31 = 0x80000000 has one bit set and thus comes between 0 and 1.

Furthermore, unpack will be used to count one-bits in all given implementations.

Straight Schwartzian transform

A straight implementation could be:

use strict;
use warnings;

sub sort_by_one_bits {
    map $_->[1],
    sort {$a->[0] <=> $b->[0] || $a->[1] <=> $b->[1]}
    map [unpack('%b*', pack 'l', $_), $_], @_;
}

There are two issues with this approach:

Index Sort

Addressing the second issue, we may calculate the one-bits-count for all numbers in a single call to pack/unpack and then perform an index sort. This might look like:

sub sort_by_one_bits_idx {
    my @bc = unpack '(%b32)*', pack 'l*', @_;

    @_[sort {$bc[$a] <=> $bc[$b] || $_[$a] <=> $_[$b]} 0 .. $#_];
}

Here we build an array @bc holding the one-bits-counts for all elements. The sorting criterion remains complex.

Composite Sorting Key

So let us consider the first issue, the complex sorting criterion. We need to construct a sorting key that fulfills both sorting criteria simultaneously: one-bits-count goes first, number order goes second.

Combining the the number of one-bits with the number itself becomes tricky, as negative numbers have the MSB set in two’s-complement representation. A “simple” sort would arrange negative values before positive values.

Thus we construct a composite of the one-bits-count, the sign and the number itself as the sorting key. The implementation becomes a bit obscure:

sub sort_by_one_bits_comp {
    my @bc = unpack '(%b32)*', pack 'l*', @_;

    unpack 'l*', pack 'L*',
    sort {$a <=> $b}
    map shift(@bc) << 40 | ($_ > 0) << 32 | (2**32 - 1) & $_, @_;
}

Assuming perl IVs having at least 48 bits, we transform each 32-bit signed long into a longer perl IV, where one byte holds the one-bits-count, the next byte represents the sign and the four trailing bytes hold the value.

These transformed values can be sorted numerically. Truncated to signed long, the the original values are revealed.

PDL!

Using PDL, things become easy.

As PDL ndarrays are stored in packed format, we just can unpack the one-bits-count - though this approach is discouraged. See get_dataref. And with qsortvec, there is no sign issue with the composite sort key.

use PDL;
use PDL::NiceSlice;
use feature 'postderef';

sub sort_by_one_bits_pdl {
    my $n = long @_;
    my $bc = long unpack '(%b32)*', $n->get_dataref->$*;

    $bc->dummy(0)->append($n->dummy(0))->qsortvec->((1));
}

Results

Here is a benchmark running these implementations on the list (-2**15 .. 2**15 - 1).

use Benchmark 'cmpthese';

my @n = (-2**15 .. 2**15-1);

cmpthese(0, {
        st => sub {sort_by_one_bits(@n)},
        idx => sub {sort_by_one_bits_idx(@n)},
        comp => sub {sort_by_one_bits_comp(@n)},
        pdl => sub {sort_by_one_bits_pdl(@n)},
    });

resulting in:

       Rate   st  idx comp  pdl
st   19.2/s   -- -46% -74% -76%
idx  35.6/s  85%   -- -51% -55%
comp 72.8/s 278% 105%   --  -8%
pdl  79.4/s 313% 123%   9%   --

Some differences are significant, but all implementations are within the same magnitude.

See the full solution to task 2.


If you have a question about this post or if you like to comment on it, feel free to open an issue in my github repository.