Perl Weekly Challenge: A pretty average progression…

Since Perl Weekly Challenge 351‘s tasks are “Special Average” and “Arithmetic Progression”, I decided that this week’s musical theme should be Average Person by Paul McCartney.

Task 1: Special Average

You are given an array of integers.

Write a script to return the average excluding the minimum and maximum of the given array.

Example 1

Input: @ints = (8000, 5000, 6000, 2000, 3000, 7000)
Output: 5250

Min: 2000
Max: 8000
Avg: (3000+5000+6000+7000)/4 = 21000/4 = 5250

Example 2

Input: @ints = (100_000, 80_000, 110_000, 90_000)
Output: 95_000

Min: 80_000
Max: 110_000
Avg: (100_000 + 90_000)/2 = 190_000/2 = 95_000

Example 3

Input: @ints = (2500, 2500, 2500, 2500)
Output: 0

Min: 2500
Max: 2500
Avg: 0

Example 4

Input: @ints = (2000)
Output: 0

Min: 2000
Max: 2000
Avg: 0

Example 5

Input: @ints = (1000, 2000, 3000, 4000, 5000, 6000)
Output: 3500

Min: 1000
Max: 6000
Avg: (2000 + 3000 + 4000 + 5000)/4 = 14000/4 = 3500

Approach

This task is pretty straightforward: one pass though the list to find the min and max values, then a second pass through to remove all values that match those min and max values, and then we count and sum them to generate an average.

Raku

Of course, once I started writing, I just naturally fell into using min and max to find the minimum and maximum values, and those are probably implemented in the compiler with loops, so I guess I’m doing three passes through the list.

sub specialAverage(@ints) {
  my $min = @ints.min; # ok, using built-in functions to
  my $max = @ints.max; # find min/max rather than a loop
  @ints = @ints.grep({ $_ != $min && $_ != $max });
  return 0 if @ints.elems == 0;
  return @ints.sum / @ints.elems;
}

View the entire Raku script for this task on GitHub.

$ raku/ch-1.raku
Example 1:
Input: @ints = (8000, 5000, 6000, 2000, 3000, 7000)
Output: 5250

Example 2:
Input: @ints = (100000, 80000, 110000, 90000)
Output: 95000

Example 3:
Input: @ints = (2500, 2500, 2500, 2500)
Output: 0

Example 4:
Input: @ints = (2000)
Output: 0

Example 5:
Input: @ints = (1000, 2000, 3000, 4000, 5000, 6000)
Output: 3500

Perl

And because Perl has a plethora of CPAN modules descended from List::Util that provide min, max, and sum, I just used those from my favorite, List::AllUtils.

use List::AllUtils qw( min max sum );

sub specialAverage(@ints) {
  my $min = min @ints; # ok, using List::Util functions to
  my $max = max @ints; # find min/max rather than a loop
  @ints = grep { $_ != $min && $_ != $max } @ints;
  return 0 if @ints == 0;
  return sum(@ints) / scalar(@ints);
}

View the entire Perl script for this task on GitHub.

Python

Python, just like Raku, has the min, max, and sum functions built in.

def special_average(ints):
  minv = min(ints) # ok, using built-in functions to
  maxv = max(ints) # find min/max rather than a loop
  ints = [ i for i in ints if i != minv and i != maxv ]
  if len(ints) == 0: return 0
  return int(sum(ints) / len(ints))

View the entire Python script for this task on GitHub.

Elixir

And because the algorithm I’m implementing doesn’t have any early bailing out of these loops, I’m able to write it all up in a single, non-recursive Elixir function:

  def special_average(ints) do
    min  = Enum.min(ints) # ok, using built-in functions to
    max  = Enum.max(ints) # find min/max rather than a loop
    ints = Enum.filter(ints, fn i -> i != min and i != max end)
    if length(ints) == 0 do
      0
    else
      trunc(Enum.sum(ints) / length(ints))
    end
  end

View the entire Elixir script for this task on GitHub.


Task 2: Arithmetic Progression

You are given an array of numbers.

Write a script to return true if the given array can be re-arranged to form an arithmetic progression, otherwise return false.

A sequence of numbers is called an arithmetic progression if the difference between any two consecutive elements is the same.

Example 1

Input: @num = (1, 3, 5, 7, 9)
Output: true

Already AP with common difference 2.

Example 2

Input: @num = (9, 1, 7, 5, 3)
Output: true

The given array re-arranged like (1, 3, 5, 7, 9) with common difference 2.

Example 3

Input: @num = (1, 2, 4, 8, 16)
Output: false

This is geometric progression and not arithmetic progression.

Example 4

Input: @num = (5, -1, 3, 1, -3)
Output: true

The given array re-arranged like (-3, -1, 1, 3, 5) with common difference 2.

Example 5

Input: @num = (1.5, 3, 0, 4.5, 6)
Output: true

The given array re-arranged like (0, 1.5, 3, 4.5, 6) with common difference 1.5.

Approach

We’re going to sort the elements from lowest to highest, then calculate the difference between the first two elements and make sure the differences between each subsequent pair matches.

Raku

At first, I pulled off the first two elements to calculate the diff, but then I realized I could save a line by just leaving the first value of $cur in the @ints list and just re-calculate the difference again the first time through the loop.

sub arithmeticProgression(@ints) {
  @ints = @ints.sort;
  my $prev = @ints.shift;
  my $diff = @ints[0] - $prev; # calc first difference
  for @ints -> $cur {
    return False if $diff != $cur - $prev;
    $prev = $cur;
  }
  return True;
}

View the entire Raku script for this task on GitHub.

$ raku/ch-2.raku
Example 1:
Input: @ints = (1, 3, 5, 7, 9)
Output: True

Example 2:
Input: @ints = (9, 1, 7, 5, 3)
Output: True

Example 3:
Input: @ints = (1, 2, 4, 8, 16)
Output: False

Example 4:
Input: @ints = (5, -1, 3, 1, -3)
Output: True

Example 5:
Input: @ints = (1.5, 3, 0, 4.5, 6)
Output: True

Perl

The big difference between Raku and Perl is that we need to explicitly say we’re sorting @ints numerically.

sub arithmeticProgression(@ints) {
  @ints = sort { $a <=> $b } @ints;
  my $prev = shift @ints;
  my $diff = $ints[0] - $prev; # calc first difference
  foreach my $cur (@ints) {
    return 'False' if $diff != $cur - $prev;
    $prev = $cur;
  }
  return 'True';
}

View the entire Perl script for this task on GitHub.

Python

There’s not much to say about the Python version.

def arithmetic_progression(ints):
  ints = sorted(ints)
  prev = ints.pop(0)
  diff = ints[0] - prev # calc first difference
  for cur in ints:
    if diff != cur - prev: return False
    prev = cur
  return True

View the entire Python script for this task on GitHub.

Elixir

However, because this algorithm does involve bailing out of the loop early if we fail a condition, I’ve implemented it as a recursive function. The assignment of prev = cur winds up happening on line 9 when we recursively call arithmetic_progression/3 with cur instead of prev.

  def arithmetic_progression([], _, _), do: true

  def arithmetic_progression([cur | ints], prev, diff) do
    cond do
      diff != cur - prev -> false
      true -> arithmetic_progression(ints, cur, diff)
    end
  end

  def arithmetic_progression(ints) do
    ints = Enum.sort(ints)
    {prev, ints} = {hd(ints), tl(ints)}
    diff = hd(ints) - prev # calc first difference
    arithmetic_progression(ints, prev, diff)
  end

View the entire Elixir script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-351/packy-anderson

Leave a Reply