Perl Weekly Challenge 122.

My solutions (task 1 and task 2) to the The Weekly Challenge - 122.

Task 1: Average of Stream

Submitted by: Mohammad S Anwar
You are given a stream of numbers, @N.

Write a script to print the average of the stream at every point.

Example
Input: @N = (10, 20, 30, 40, 50, 60, 70, 80, 90, ...)
Output:      10, 15, 20, 25, 30, 35, 40, 45, 50, ...

Average of first number is 10.
Average of first 2 numbers (10+20)/2 = 15
Average of first 3 numbers (10+20+30)/3 = 20
Average of first 4 numbers (10+20+30+40)/4 = 25 and so on.

By keeping the current total and the number of data items the problem becomes a one-liner. I take input from STDIN, assuming that in a real application the data might not be known before the program is launched, so I can pipe in the data.

perl -E '$i=1; say 10 * $i++ while $i<10;' | # data source \
perl -E 'while(<>){chomp; say "Input: ", $_, " Output:", ($T+=$_)/(++$N);}' # running average

Results:

Input: 10 Output:10
Input: 20 Output:15
Input: 30 Output:20
Input: 40 Output:25
Input: 50 Output:30
Input: 60 Output:35
Input: 70 Output:40
Input: 80 Output:45
Input: 90 Output:50

The full program would be:

# Perl weekly challenge 122
# Task 1: Average of stream
#
# See https://wlmb.github.io/2021/07/19/PWC122/#task-1-average-of-stream
use strict;
use warnings;
use v5.12;
my $counter=0; # item counter
my $total=0; # running total
while(<>){
    chomp;
    ++$counter;
    $total += $_;
    my $average=$total/$counter;
    say "Input: $_ Output: $average";
}

Example:

perl -E '$i=1; say 10 * $i++ while $i<10;' | ./ch-1.pl

Results:

Input: 10 Output: 10
Input: 20 Output: 15
Input: 30 Output: 20
Input: 40 Output: 25
Input: 50 Output: 30
Input: 60 Output: 35
Input: 70 Output: 40
Input: 80 Output: 45
Input: 90 Output: 50

Other examples

  1. Average of triangular numbers

    perl -E '$i=1; say $i++ while $i<10;' |./ch-1.pl
    

    Results:

    Input: 1 Output: 1
    Input: 2 Output: 1.5
    Input: 3 Output: 2
    Input: 4 Output: 2.5
    Input: 5 Output: 3
    Input: 6 Output: 3.5
    Input: 7 Output: 4
    Input: 8 Output: 4.5
    Input: 9 Output: 5
    
  2. Average of first squares:

    perl -E '$i=1; say +($i++)**2 while $i<10;' |./ch-1.pl
    

    Results:

    Input: 1 Output: 1
    Input: 4 Output: 2.5
    Input: 9 Output: 4.66666666666667
    Input: 16 Output: 7.5
    Input: 25 Output: 11
    Input: 36 Output: 15.1666666666667
    Input: 49 Output: 20
    Input: 64 Output: 25.5
    Input: 81 Output: 31.6666666666667
    
  3. Average of first cubes:

    perl -E '$i=1; say +($i++)**3 while $i<10;' |./ch-1.pl
    

    Results:

    Input: 1 Output: 1
    Input: 8 Output: 4.5
    Input: 27 Output: 12
    Input: 64 Output: 25
    Input: 125 Output: 45
    Input: 216 Output: 73.5
    Input: 343 Output: 112
    Input: 512 Output: 162
    Input: 729 Output: 225
    

Task 2: Basketball Points

Submitted by: Mohammad S Anwar
You are given a score $S.

You can win basketball points e.g. 1 point, 2 points and 3
points.

Write a script to find out the different ways you can score
$S.

Example
Input: $S = 4
Output: 1 1 1 1
        1 1 2
        1 2 1
        1 3
        2 1 1
        2 2
        3 1

Input: $S = 5
Output: 1 1 1 1 1
        1 1 1 2
        1 1 2 1
        1 1 3
        1 2 1 1
        1 2 2
        1 3 1
        2 1 1 1
        2 1 2
        2 2 1
        2 3
        3 1 1
        3 2

I solve the problem recursively. The possible sets of points for a given score S are those for a lower score S-1, S-2 or S-3 followed by a single, double or triple point. I just have to be careful with the initial conditions, either a single, a double or a triple score.

# Perl weekly challenge 122
# Task 2:
#  Basketball Points
# See https://wlmb.github.io/2021/07/19/PWC122/#task-2-basketball-points
use strict;
use warnings;
use v5.12;
use Memoize;
memoize("points");

foreach (@ARGV){
    say "Input: $_\nOutput:\n\t", join "\n\t", map {join " ", @$_} points($_);
}
sub points{
    my $s=shift;
    return ()  if $s<=0;
    # Append a 1, 2 or 3 point throw to the previous points
    my @result=((map {my @x=@$_;push @x,1; [@x]}points($s-1)),
		(map {my @x=@$_;push @x,2; [@x]}points($s-2)),
		(map {my @x=@$_;push @x,3; [@x]}points($s-3)));
    push @result, [$s] if $s<=3;
    return @result;
}

Examples:

./ch-2.pl 4 5

Results:

Input: 4
Output:
	1 1 1 1
	2 1 1
	1 2 1
	3 1
	1 1 2
	2 2
	1 3
Input: 5
Output:
	1 1 1 1 1
	2 1 1 1
	1 2 1 1
	3 1 1
	1 1 2 1
	2 2 1
	1 3 1
	1 1 1 2
	2 1 2
	1 2 2
	3 2
	1 1 3
	2 3

Memoization makes a difference for large scores. For example, for S=24 it took about 5 times longer to obtain the result without memoization compared with the result with memoization. However, memoization can produce surprises. For example, a line such as

@result=((map {push @$_,1; $_}points($s-1),...);

(which I wrote in my first attempt) would work without memoization but it fails with memoization, as the variable $_ from previous results would be inadvertently modified. Thus, the need for the temporal variable @x.

Larger example:

time (./ch-2.pl 25 | wc) 2>&1

Results:

2555759 40058424 82672617

real	0m15.589s
user	0m13.761s
sys	0m1.867s

which means the program found 2555759-2=2555757 different sets of points for a total score of 25 in 15s in my laptop.

Written on July 19, 2021