5
\$\begingroup\$

I want to break a larger list into $num-lists smaller lists. I thought that there might be an option for rotor that would already do it, but I haven't found one.

#!/bin/env perl6

my @a = 1 .. 13;

my @lists = fair-lists(@a, 3);

say @$_ for @lists;

sub fair-lists ( @array, $num-lists )
{
    my $num-elements = @array.elems;
    my $num-large    = $num-elements % $num-lists; # remainder
    my $num-small    = $num-lists - $num-large;

    my $small-size = floor($num-elements/$num-lists);
    my $large-size = $small-size + 1; # same as ceiling($num-element/$num-lists);

    my $first-large = ($num-small * $small-size);
    my $last-small  = $first-large - 1;

    my @first-sublist  = @array[ 0 .. $last-small];
    my @second-sublist = @array[ $first-large .. $num-elements - 1 ];

    my @small-lists = @first-sublist.rotor($small-size);
    my @large-lists = @second-sublist.rotor($large-size);
    my @all-lists   = (@small-lists, @large-lists).flat;
    return @all-lists;
}

Is there a more concise and more clear way to do this in Perl 6?

\$\endgroup\$
  • 1
    \$\begingroup\$ floor( $ / $ ) is the same as $ div $ \$\endgroup\$ – Brad Gilbert Jan 8 '17 at 14:45
5
\$\begingroup\$

Using roundrobin on a rotored array works

sub fair-lists ( @array, Int $num-lists ) {

  # "invert" the following "matrix"
  roundrobin

    # make it at least $num-lists long
    |@array[{ 0 ..^ ( $_ max $num-lists ) }]\

    # split it into chunks that are as big as the number of $num-lists
    .rotor: $num-lists, :partial

}
say fair-lists( 1..1, 3 ).perl;
# ((1,), (Any,), (Any,)).Seq

say fair-lists( 1..4, 3 ).perl;
# ((1, 4), (2,), (3,)).Seq

test it

\$\endgroup\$
  • \$\begingroup\$ Awesome!! When running your code, I do see a difference from your results when my list is shorter than $num-lists. What version of Rakudo are you using? I'm using Rakudo Star 2016.11 on CentOS 7.2.1511 (Core). Instead of ((1,), (Any,), (Any,)).Seq, I'm getting ((1,), (Failure.new(exception => X::OutOfRange.new(what => "Effective index", got => 1, range => 0..0, comment => Any), backtrace => Backtrace.new),), (Failure.new(exception => X::OutOfRange.new(what => "Effective index", got => 2, range => 0..0, comment => Any), backtrace => Backtrace.new),)).Seq. \$\endgroup\$ – Christopher Bottoms Jan 10 '17 at 17:40
  • \$\begingroup\$ @ChristopherBottoms Notice that in the test it link there is a .map  … to work around that issue. I'm fairly sure that is a bug that was noticed and fixed sometime after that version. I always run a version that is less than a week old, and usually less than a few hours old. \$\endgroup\$ – Brad Gilbert Jan 11 '17 at 17:11
4
\$\begingroup\$

Very interesting problem. Rotor is so painfully close to doing what is desired that I fooled around with it for quite some time before giving up on it. Also, I was seeing some weirdness with rotor that I will have to play with separately.

One tricky thing was trying to use [].push instead of [].append from here.

I learned that that push will take all the arrays and mash them together so the last element would end up being an array of arrays (of arrays...).

I've also learned that you need to surround the array with $() so that it will not be treated as a list of numbers but as a single element to be appended to the array of arrays.

This question would make a good introduction to Arrays and Lists in Perl 6 or Perl 6 in general, first time I did any 'real' dabbling in it.

This is what I came up with:

sub fair-lists (@array, $num-lists)
{
    return $(@array) if $num-lists === 1;
    my $element-count = ceiling(@array.elems / $num-lists);
    return [$(@array[0..$element-count-1])]
           .append(fair-lists(@array[$element-count..*], $num-lists-1));
}

#  ^^^ that is all the code, the rest is just to demo it.

my $debug = 0;

quick-check(1,1,1);
quick-check(1,2,1);
quick-check(1,3,1);

quick-check(1,1,3);
quick-check(1,2,3);
quick-check(1,3,3);
quick-check(1,4,3);
quick-check(1,5,3);
quick-check(1,6,3);
quick-check(1,7,3);
quick-check(1,13,3);
quick-check(1,23,5);

$debug = 1;

quick-check(1,13,3);

sub quick-check ($start, $end, $count) {
    my @array = ($start..$end);

    say "\n$(@array.elems) elements in list, desire $count lists" if $debug;
    say @array if $debug;

    my @lists = fair-lists(@array, $count);

    { say @$_ for @lists } if $debug;

    if @lists.elems === $count {
        say "Pass - $(@lists.elems) is the same as desired count of $count";
    }  else {
        say "FAIL!!!! - got $(@lists.elems) but wanted $count";
    }
}

Here are the results of running it:

Pass - 1 is the same as desired count of 1
Pass - 1 is the same as desired count of 1
Pass - 1 is the same as desired count of 1
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 3 is the same as desired count of 3
Pass - 5 is the same as desired count of 5

13 elements in list, desire 3 lists
[1 2 3 4 5 6 7 8 9 10 11 12 13]
(1 2 3 4 5)
(6 7 8 9)
(10 11 12 13)
Pass - 3 is the same as desired count of 3
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.