—###############################################################################
## ----------------------------------------------------------------------------
## Sequence helper class.
##
###############################################################################
package
MCE::Shared::Sequence;
use
strict;
use
warnings;
use
5.010001;
no
warnings
qw( threads recursion uninitialized numeric )
;
our
$VERSION
=
'1.848'
;
use
MCE::Shared::Base ();
use
constant {
_BEGV
=> 0,
# sequence begin value
_ENDV
=> 1,
# sequence end value
_STEP
=> 2,
# sequence step size
_FMT
=> 3,
# sequence format
_CKSZ
=> 4,
# chunk_size option, default 1
_ONLY
=> 5,
# bounds_only option, default 0
_ITER
=> 6,
# iterator count
};
use
overload (
q("")
=> \
&MCE::Shared::Base::_stringify
,
q(0+)
=> \
&MCE::Shared::Base::_numify
,
fallback
=> 1
);
sub
_croak {
goto
&MCE::Shared::Base::_croak
;
}
sub
_reset {
my
$self
=
shift
;
my
$opts
=
ref
(
$_
[0]) eq
'HASH'
?
shift
() : {};
@{
$self
} =
@_
;
_croak(
'invalid begin'
)
unless
looks_like_number(
$self
->[_BEGV] );
_croak(
'invalid end'
)
unless
looks_like_number(
$self
->[_ENDV] );
$self
->[_STEP] = (
$self
->[_BEGV] <=
$self
->[_ENDV] ) ? 1 : -1
unless
(
defined
$self
->[_STEP] );
$self
->[_FMT] =~ s/%//
if
(
defined
$self
->[_FMT] );
_croak(
'invalid step'
)
unless
looks_like_number(
$self
->[_STEP] );
for
my
$_k
(_BEGV, _ENDV, _STEP) {
$self
->[
$_k
] =
int
(
$self
->[
$_k
])
unless
(
$self
->[
$_k
] =~ /\./ );
}
$self
->[_CKSZ] =
$opts
->{
'chunk_size'
} || 1;
$self
->[_ONLY] =
$opts
->{
'bounds_only'
} // 0;
_croak(
'invalid chunk_size'
)
unless
(
$self
->[_CKSZ] =~ /^[0-9e\+]+$/ );
_croak(
'invalid bounds_only'
)
unless
(
$self
->[_ONLY] =~ /^[01]$/ );
$self
->[_CKSZ] =
int
(
$self
->[_CKSZ]);
$self
->[_ITER] =
undef
;
return
;
}
sub
_sprintf {
my
(
$fmt
,
$arg
) =
@_
;
# remove tainted'ness
(
$fmt
) =
$fmt
=~ /(.*)/;
return
sprintf
(
"$fmt"
,
$arg
);
}
###############################################################################
## ----------------------------------------------------------------------------
## Public methods.
##
###############################################################################
# new ( begin, end [, step, format ] )
# new ( )
sub
new {
my
(
$class
,
$self
) = (
shift
, [] );
if
( !
@_
) {
@{
$self
} = ( 0, 0, 1,
'__NOOP__'
);
}
else
{
_reset(
$self
,
@_
);
}
bless
$self
,
$class
;
}
# next ( )
sub
next
{
my
(
$self
) =
@_
;
my
$iter
=
$self
->[_ITER];
if
(
defined
$iter
) {
my
(
$begv
,
$endv
,
$step
,
$fmt
,
$chunk_size
,
$bounds_only
) = @{
$self
};
my
(
$begn
,
$seqn
);
# computes from *begv* value to not lose precision during iteration
if
(
$begv
<=
$endv
) {
$begn
=
$seqn
=
$begv
+ (
$iter
++ *
$chunk_size
*
$step
);
return
if
(
$begv
==
$endv
&&
$begn
!=
$begv
);
return
if
(
$seqn
>
$endv
);
}
else
{
$begn
=
$seqn
=
$begv
- -(
$iter
++ *
$chunk_size
*
$step
);
return
if
(
$seqn
<
$endv
);
}
$self
->[_ITER] =
$iter
;
if
(
$chunk_size
== 1 ||
$begv
==
$endv
) {
$seqn
= _sprintf(
"%$fmt"
,
$seqn
)
if
(
defined
$fmt
);
return
(
$bounds_only
) ? (
$seqn
,
$seqn
,
$iter
) :
$seqn
;
}
if
(
$bounds_only
) {
my
(
$seqb
,
$seqe
) = (
$seqn
);
if
(
$begv
<=
$endv
) {
if
(
$step
* (
$chunk_size
- 1 ) +
$seqn
<=
$endv
) {
$seqe
=
$step
* (
$chunk_size
- 1 ) +
$seqn
;
}
elsif
(
$step
== 1 ) {
$seqe
=
$endv
;
}
else
{
for
my
$i
( 1 ..
$chunk_size
) {
last
if
(
$seqn
>
$endv
);
$seqe
=
$seqn
;
$seqn
=
$step
*
$i
+
$begn
;
}
}
}
else
{
if
(
$step
* (
$chunk_size
- 1 ) +
$seqn
>=
$endv
) {
$seqe
=
$step
* (
$chunk_size
- 1 ) +
$seqn
;
}
elsif
(
$step
== -1 ) {
$seqe
=
$endv
;
}
else
{
for
my
$i
( 1 ..
$chunk_size
) {
last
if
(
$seqn
<
$endv
);
$seqe
=
$seqn
;
$seqn
=
$step
*
$i
+
$begn
;
}
}
}
return
(
defined
$fmt
)
? ( _sprintf(
"%$fmt"
,
$seqb
), _sprintf(
"%$fmt"
,
$seqe
) )
: (
$seqb
,
$seqe
,
$iter
);
}
my
@n
;
if
(
$begv
<=
$endv
) {
if
( !
defined
$fmt
&&
$step
== 1 &&
abs
(
$endv
) < ~1 &&
abs
(
$begv
) < ~1 ) {
return
(
$seqn
+
$chunk_size
<=
$endv
)
? (
$seqn
..
$seqn
+
$chunk_size
- 1 )
: (
$seqn
..
$endv
);
}
for
my
$i
( 1 ..
$chunk_size
) {
last
if
(
$seqn
>
$endv
);
push
@n
,
defined
$fmt
? _sprintf(
"%$fmt"
,
$seqn
) :
$seqn
;
$seqn
=
$step
*
$i
+
$begn
;
}
}
else
{
for
my
$i
( 1 ..
$chunk_size
) {
last
if
(
$seqn
<
$endv
);
push
@n
,
defined
$fmt
? _sprintf(
"%$fmt"
,
$seqn
) :
$seqn
;
$seqn
=
$step
*
$i
+
$begn
;
}
}
return
@n
;
}
else
{
$self
->[_ITER] = 0;
$self
->
next
();
}
}
# rewind ( begin, end [, step, format ] )
# rewind ( )
sub
rewind {
my
$self
=
shift
;
if
( !
@_
) {
$self
->[_ITER] =
undef
unless
(
$self
->[_FMT] eq
'__NOOP__'
);
}
else
{
_reset(
$self
,
@_
);
}
return
;
}
1;
__END__
###############################################################################
## ----------------------------------------------------------------------------
## Module usage.
##
###############################################################################
=head1 NAME
MCE::Shared::Sequence - Sequence helper class
=head1 VERSION
This document describes MCE::Shared::Sequence version 1.848
=head1 DESCRIPTION
A number sequence class for use as a standalone or managed by L<MCE::Shared>.
=head1 SYNOPSIS
# non-shared or local construction for use by a single process
use MCE::Shared::Sequence;
my $seq_a = MCE::Shared::Sequence->new( $begin, $end, $step, $fmt );
my $seq_b = MCE::Shared::Sequence->new(
{ chunk_size => 10, bounds_only => 1 },
$begin, $end, $step, $fmt
);
# construction for sharing with other threads and processes
use MCE::Shared;
my $seq_a = MCE::Shared->sequence( 1, 100 );
my $seq_b = MCE::Shared->sequence(
{ chunk_size => 10, bounds_only => 1 },
1, 100
);
# example
use MCE::Hobo;
sub parallel_a {
my ( $id ) = @_;
while ( defined ( my $num = $seq_a->next ) ) {
print "$id: $num\n";
}
}
sub parallel_b {
my ( $id ) = @_;
while ( my ( $beg, $end ) = $seq_b->next ) {
for my $num ( $beg .. $end ) {
print "$id: $num\n";
}
}
}
MCE::Hobo->new( \¶llel_a, $_ ) for 1 .. 2;
MCE::Hobo->new( \¶llel_b, $_ ) for 3 .. 4;
# ... do other work ...
MCE::Hobo->waitall();
=head1 API DOCUMENTATION
=head2 MCE::Shared::Sequence->new ( { options }, begin, end [, step, format ] )
=head2 MCE::Shared::Sequence->new ( begin, end [, step, format ] )
=head2 MCE::Shared->sequence ( { options }, begin, end [, step, format ] )
=head2 MCE::Shared->sequence ( begin, end [, step, format ] )
Constructs a new object. C<step>, if omitted, defaults to C<1> if C<begin> is
smaller than C<end> or C<-1> if C<begin> is greater than C<end>. The C<format>
string is passed to C<sprintf> behind the scene (% may be omitted).
$seq_n_formatted = sprintf( "%4.1f", $seq_n );
Two options C<chunk_size> and C<bounds_only> are supported, which default to
1 and 0 respectively. Chunking reduces the number of IPC calls to and from the
shared-manager process for large sequences.
If C<< bounds_only => 1 >> is specified, the C<next> method computes the
C<begin> and C<end> values only for the chunk and not the numbers in between
(hence boundaries only).
use MCE::Shared;
# demo 1
$seq1 = MCE::Shared->sequence(
{ chunk_size => 10, bounds_only => 0 }, 1, 20
);
# @chunk = $seq1->next; # ( qw/ 1 2 3 4 5 6 7 8 9 10 / )
# @chunk = $seq1->next; # ( qw/ 11 12 13 14 15 16 17 18 19 20 / )
while ( my @chunk = $seq1->next ) {
...
}
# demo 2
$seq2 = MCE::Shared->sequence(
{ chunk_size => 10, bounds_only => 1 }, 1, 100
);
# ( $beg, $end ) = $seq2->next; # ( 1, 10 )
# ( $beg, $end ) = $seq2->next; # ( 11, 20 )
# ( $beg, $end ) = $seq2->next; # ( 21, 30 )
# ...
# ( $beg, $end ) = $seq2->next; # ( 81, 90 )
# ( $beg, $end ) = $seq2->next; # ( 91, 100 )
# The optional chunk_id value, starting at 1, applies to sequence
# objects configured with the bounds_only option set to a true
# value. API available since 1.834.
while ( my ( $beg, $end, $chunk_id ) = $seq2->next ) {
for my $i ( $beg .. $end ) {
...
}
}
Parameters may be given later with C<rewind> before calling C<next>.
# non-shared or local construction for use by a single process
use MCE::Shared::Sequence;
$seq = MCE::Shared::Sequence->new;
$seq->rewind( -1, 1, 0.1, "%4.1f" );
$seq = MCE::Shared::Sequence->new(
{ chunk_size => 10, bounds_only => 1 }, 1, 100
);
# construction for sharing with other threads and processes
use MCE::Shared;
$seq = MCE::Shared->sequence;
$seq->rewind( 1, 100 );
$seq = MCE::Shared->sequence(
{ chunk_size => 10, bounds_only => 1 }, 1, 100
);
=head2 next
Returns the next computed sequence(s). An undefined value is returned when
the computed C<begin> value exceeds the value held by C<end>.
# default: { chunk_size => 1, bounds_only => 0 }
$seq = MCE::Shared->sequence( 1, 100 );
while ( defined ( my $num = $seq->next ) ) {
...
}
# chunking
$seq = MCE::Shared->sequence(
{ chunk_size => 10 }, 1, 100
);
while ( my @chunk = $seq->next ) {
...
}
# chunking, boundaries only
$seq = MCE::Shared->sequence(
{ chunk_size => 10, bounds_only => 1 }, 1, 100
);
while ( my ( $beg, $end, $chunk_id ) = $seq->next ) {
for my $i ( $beg .. $end ) {
...
}
}
=head2 rewind ( { options }, begin, end [, step, format ] )
=head2 rewind ( begin, end [, step, format ] )
Sets the initial value back to the value held by C<begin> when no arguments
are given. Otherwise, resets the sequence with given criteria.
$seq->rewind;
$seq->rewind( { chunk_size => 10, bounds_only => 1 }, 1, 100 );
while ( my ( $beg, $end ) = $seq->next ) {
for my $i ( $beg .. $end ) {
...
}
}
$seq->rewind( 1, 100 );
while ( defined ( my $num = $seq->next ) ) {
...
}
=head1 INDEX
L<MCE|MCE>, L<MCE::Hobo>, L<MCE::Shared>
=head1 AUTHOR
Mario E. Roy, S<E<lt>marioeroy AT gmail DOT comE<gt>>
=cut