100 * $i / $n
already considered to be non-trivial);init_progress ;
#
do_first_half ;
update_progress 50 ;
#
do_last_half ;
update_progress 100 ;
Now suppose that each half is a challenge to a long function that can output its information about progress. However, she does not know in what context she was called and what range of the overall progress indicator was set aside for its implementation. A natural implementation would be something like this:
sub do_first_half ( ) {
#
update_progress 33 ;
#
update_progress 66 ;
#
update_progress 100 ;
}
That is, we report information about our progress, and let someone else map it to the desired range (in our case, 0-50%). Here I came up with an analogy with the OpenGL matrix stack, where any affine transformations of three-dimensional coordinates are described by a 4 Ă— 4 matrix and the sequence of transformations is placed on the stack, and when it comes to specifying the vertices of a particular object, we specify specific numbers without any calculations. OpenGL itself converts the coordinates, multiplying by a specific matrix. Here, in fact, we also have coordinates on the progress indicator, only one-dimensional. Affine transformation is described by two numbers: transfer and scaling. We will add the transformations to the stack, and the update_progress
function update_progress
perform the necessary conversion and pass the already converted coordinates to the renderer:
# [, ]
my @stack = ( [ 1 , 0 ] ) ;
sub update_progress ( $ ) {
my $percent = shift ;
$percent = $stack [ - 1 ] [ 0 ] * $percent + $stack [ - 1 ] [ 1 ] ;
renderer ( $percent ) ;
}
Now add the functions push_progress
and pop_progress
. For ease of use, we will not push_progress
and transfer to push_progress
, but the range to which subsequent percentages should be mapped. Of course, if some transformation is already in effect, then the parameters of push_progress
also need to be converted:
sub push_progress ( $$ ) {
#
my ( $s , $e ) = @_ ;
#
( $s , $e ) = map { $stack [ - 1 ] [ 0 ] * $_ + $stack [ - 1 ] [ 1 ] } ( $s , $e ) ;
#
push @stack , [ ( $e - $s ) / 100 , $s ] ;
}
sub pop_progress ( ) {
pop @stack ;
}
Now it remains to wrap the calls of the do_first_half
and do_last_half
in brackets of push_progress/pop_progress
:
push_progress 0 , 50 ;
do_first_half ;
pop_progress ;
push_progress 50 , 100 ;
do_last_half ;
pop_progress ;
Already not bad. Unfortunately, you have to make sure that each push_progress
matching pop_progress
. However, we can wrap the code fragment between push_progress
and pop_progress
into a block and transfer it to the sub_progress
function of the sub_progress
form:
sub sub_progress ( & $$ ) {
my ( $code , $s , $e ) = @_ ;
push_progress $s , $e ;
my @retval = & { $code } ( ) ;
update_progress 100 ;
pop_progress ;
return @retval ;
}
The main code is then simplified:
sub_progress { do_first_half } 0 , 50 ;
sub_progress { do_last_half } 50 , 100 ;
Notice that before pop_progress
I called update_progress(100)
just in case the unit forgot to do it. Now it becomes clear that the $s
parameter is not needed: you can use the last displayed value of the progress indicator instead.
for ( $i = 1 ; $i < = 1024 ; $i*= 2 )
, but it will work with any cycles like foreach
(by the way, the given cycle is easily converted to foreach
: for ( map { 2 **$_ } 0. .10 )
). Our for_progress
will perform such a chain of actions for each iteration: put the range [ $i / $n * 100 , ( $i + 1 ) / $n * 100 ]
for_progress
stack, where $ i is the iteration number and $ n is the number of elements list, load the current element in $ _, execute a code block, call update_progress(100)
, retrieve the last element from the stack. Then in the existing cycles, it is enough to replace for
with for_progress
, drag the list to the end (as in the map
) and assign $ _ to your variable if you used another variable. I will note that next
and last
continue to work (albeit with the varning), since inside for_progress
normal for
. The simplest test looks like this:
init_progress ;
for_progress { sleep ( 1 ) } 1. .10 ;
Since update_progress
is called at the end of the block by an automaton, it is possible not to call it at all in a loop. However, if each iteration is long, you can use it, indicating the percentages of the current iteration. Of course, nested loops work, use sub_progress
inside for_progress
and vice versa. Here is a simple example:
sub A {
for_progress {
sleep ( 1 ) ;
} 1. .4 ;
}
sub B {
sleep ( 1 ) ;
update_progress 10 ;
sub_progress { A } 50 ;
sleep ( 1 ) ;
update_progress 60 ;
sleep ( 2 ) ;
update_progress 80 ;
sleep ( 2 ) ;
}
init_progress ;
sub_progress { A } 25 ;
sub_progress { A } 50 ;
sub_progress { B } 100 ;
Modern programming is difficult to imagine without the words map
and reduce
. For them, the map_progress
and reduce_progress
wrappers are also written:
init_progress ;
print " \n Sum of cubes from 1 to 1000000 = " .
reduce_progress { $a + $b * $b * $b } 1. .1000000 ;
Here, of course, there is a question of productivity: the iteration is too short, and the call to update the progress indicator will slow down the process by several orders of magnitude each time. update_progress
takes this into account and causes the renderer not every time, but only when it deems it necessary: ​​if the percentages have reached 100, they have changed sufficiently or enough time has passed since the last update (everything is set up with the init_progress
parameters). In addition, additional optimizations have been made, as a result of which my example with reduce_progress
is performed “only” 4.5 times slower than with List::Util::reduce
. For very short iterations, use caution.
Progress::Stack
I put in the CPAN. So far the application for the namespace has not been approved, but the package can be downloaded from the CPAN site . In addition to the features described here, there is something else, including the object interface (although it is not really needed) and the file_progress
function for processing a text file by analogy with while ( <FH> ) { }
. The documentation has a detailed description and examples.
Source: https://habr.com/ru/post/81765/