The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Tie::Quicksort::Lazy;
@Tie::Quicksort::Lazy::Stable::ISA = qw/ Tie::Quicksort::Lazy /;

use Carp;

use 5.006001;
use strict;
use warnings;

our $VERSION = '0.04';
sub DEBUG() { 0 };

# object field names:
BEGIN {
   my $i = 0;
   for (qw/comparator size ready parts/){  # a coderef, then an arrayref, then an arrayref of arrayrefs.
      eval "sub $_ () {".$i++.'}'
   }
}

our $trivial = 2 ;  # if you want to call sort you have to ask for it

sub import {
	shift; # lose package name
        my %args = @_;
        $trivial = $args{TRIVIAL} || $trivial;
};

sub TIEARRAY{
   my $obj = bless [];
   shift; # lose package name
   if ( ( ref $_[0] ) eq 'CODE' ) {
      $obj->[comparator] = shift
   }else{
      $obj->[comparator] = sub {
 DEBUG and ((defined $_[0] and defined $_[1] ) or Carp::confess "undefined arg to comparator");
 $_[0] cmp $_[1] };
   };

   $obj->[size] = @_;
   $obj->[ready] = [];
   $obj->[parts] = [ [ @_ ] ];  # the stack of unsorted partitions

   return $obj;
};


sub _sort {
   my $obj = shift;
   my $comp_func = $obj->[comparator];
   for(;;){
    my $arr = pop @{$obj->[parts]};
    DEBUG and warn "arr is [ @$arr ]";

    if (@$arr == 1 ) {
      $obj->[ready] = $arr ;
      return
    } elsif (@$arr == 2 ) {
      $obj->[ready] = ( $comp_func->(@$arr) > 0 ? [@$arr[1,0]] : $arr ) ;
      return
    } elsif (@$arr <= $trivial ) {
      $obj->[ready] = [ sort { $comp_func->($a,$b) } @$arr ];
      return
    };
    my (@HighSide, @LowSide) = ();

    # by choosing a random pivot and treating equality differently
    # when examining the before and after parts of the partition,
    # we get stability without scrambling and without any
    # degenerate cases, even contrived ones. (choosing the midpoint
    # gives n*log(n) performance for sorted input, but it would be
    # possible to contrive a quadratic case)
 
    my $pivot_index = int rand @$arr;
 
    my $pivot = $arr->[$pivot_index];
 
    # BEFORE THE PIVOT ELT:
    for ( splice @$arr, 0, $pivot_index ) {
       if ($comp_func->($pivot, $_) < 0 ){
          # we are looking at an elt that belongs after the pivot
          push @HighSide, $_
       }else{
          push @LowSide, $_
       };
    };
 
    shift @$arr;  # shift off the pivot elt
 
    # AFTER THE PIVOT ELT:
    for ( @$arr ) {
       if ($comp_func->($pivot, $_) > 0 ){
          # we are looking at an elt that belongs before the pivot
          push @LowSide, $_
       }else{
          push @HighSide, $_
       };
    };
 
    @HighSide and push @{$obj->[parts]}, \@HighSide; # defer the high side
    push @{$obj->[parts]}, [$pivot]; # this pivot,
    @LowSide and push @{$obj->[parts]}, \@LowSide; # do the low side, if any, next
   } # for (;;)

}


sub FETCHSIZE { 
	 $_[0]->[size] 
}

sub SHIFT {
    my $obj = shift;
    $obj->[size] or return undef; 
    my $rarr = $obj->[ready];
         
    unless (@$rarr){
        $obj->_sort;
        $rarr = $obj->[ready];
    };
 
    $obj->[size]-- ; 
    shift @$rarr;
}

*STORE = *PUSH = *UNSHIFT = *FETCH =
*STORESIZE = *POP = *EXISTS = *DELETE =
*CLEAR = sub {
   require Carp;
   Carp::croak ('"SHIFT"  and "FETCHSIZE" are the only methods defined for a '.
               __PACKAGE__ . " array");
};

1;
__END__

=head1 NAME

Tie::Quicksort::Lazy - a lazy quicksort with tiearray interface

=head1 SYNOPSIS

  use Tie::Quicksort::Lazy TRIVIAL => 1023;
  tie my @producer, Tie::Quicksort::Lazy, @input;
  while (@producer){
    my $first_remaining = shift @producer;
    ...
  };
  
  use sort 'stable';
  tie my @StableProducer, Tie::Quicksort::Lazy, \&comparator,  @input;
  ...

=head1 DESCRIPTION

A pure-perl lazy, stable, quicksort.  The only defined way to
access the resulting tied array is with C<shift>.

Sorting is deferred until an item is required.

Stability is maintained by choosing a pivot element randomly
and treating equal elements differently in the before and
after sections.

=head2 memory use

This module operates on a copy of the input array, which
becomes the initial partition.  As the partitions are divided,
the old partitions are let go. 

=head2 trivial partitions

For a stable variant, tie to Tie::Quicksort::Lazy::Stable instead
and use a stable perl sort for the trivial sort or set 
"TRIVIAL" to 1 on the use line.

=head2 BYO (Bring Your Own) comparator

when the first parameter is an unblessed coderef,
that coderef will be used as the sort
comparison function. The default is

   sub { $_[0] cmp $_[1] }

Ergo, if you want to use this module to sort a list of coderefs,
you will need to bless the first one.

=head2 trivial partition

A variable C<$trivial> is defined which declares the size of a partition
that we simply hand off to Perl's sort for sorting. by default, this is
no longer used, but it is still available if you want it.

=head1 INSPIRATION

this module was inspired by an employment interview question
concerning the quicksort-like method of selecting the first k
from n items ( see L<http://en.wikipedia.org/wiki/Quicksort#Selection-based_pivoting> )

=head1 HISTORY

=over 8

=item 0.01

Original version; created by h2xs 1.23 with options

  -ACX
	-b
	5.6.1
	-n
	Tie::Quicksort::Lazy

=item 0.02

revised to use perl arrays for partitioning operations instead of a
confusing profusion of temporary index variables

=item 0.04 

revised internal data structure, no longer using perl's sort for
anything by default, no longer scrambling input due to random pivot
element selection.

=back



=head1 SEE ALSO

L<Tie::Array::Sorted::Lazy> is vaguely similar

=head1 AUTHOR

David L. Nicol davidnico@cpan.org

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2009 by the author

This library is free software; you may redistribute and/or modify
it under the same terms as Perl.


=cut