Algorithm-LineSegments

 view release on metacpan or  search on metacpan

lib/Algorithm/LineSegments.pm  view on Meta::CPAN

  my @d = @{ $o{points} };

  my @q;

  for (my $ix = 0; $ix < @d - 1; $ix += 2) {
    push @q, [$d[$ix], $d[$ix+1]];
  }

  my $min = min @d;
  my $max = max @d;

  ###################################################################
  # This function projects values from $min to $max to 0 to 1. This
  # is useful in order to keep the cost values similar.
  ###################################################################
  my $scale_to_unit = sub {
    my $v = shift;
    return ($v - $min) / ($max - $min);
  };

  $o{cost} //= sub {
    my ($left, $right) = @_;
    _normalised_euclidean($q[$left], $q[$right], $scale_to_unit);
  };

  $o{continue} //= sub {
    my ($count, $cost) = @_;
    return 0 if $count <= 3; 
    return 1;
  };
  
  my $heap = Heap::Priority->new;
  $heap->lowest_first;
  $heap->add($_, $o{cost}->($_, $_+1)) for 0 .. $#q - 1;

  ###################################################################
  # I haven't found a good solution to maintain the heap and modify
  # the list, so as a workaround the heap identifies a mergable pair
  # with the key and when merging elements of a pair, the second
  # element is replaced by `undef` to maintain the size of the list,
  # so the heap keys, indices into the list, remain valid. This has
  # the consequence of producing gaps in the list, and the variables
  # below maintain how the gaps can be skipped.
  ###################################################################
  my %next = map { $_ => $_ + 1 } 0 .. $#q - 1;
  my %prev = map { $_ => $_ - 1 } 1 .. $#q - 1;

  for (my $count = @q;;) {
    my $ix = $heap->pop;
    last unless defined $ix;

    #################################################################
    # Ordinarily it should be possible to obtain the priority of the
    # element on top of the heap, but the chosen module can report
    # only the priorities of all elements, which is a bit costly, so
    # instead the cost is re-computed here for now.
    #################################################################
    my $cost = $o{cost}->($ix, $next{$ix});
    
    #################################################################
    # The callback can be by calling code to stop the merging process 
    #################################################################
    last unless $o{continue}->($count, $cost);

    my $k = $ix;
    my $j = $next{$k};
    
    next unless defined $j;
    
    my @merged = map { @{ $q[$_] } } $k, $j;
    $q[$k] = undef;
    $q[$j] = undef;
    splice @q, $k, 2, [@merged], undef;
    $count--;
    
    #################################################################
    # Now that $k has changed, merging $k with the element before or
    # after has a different cost, so those elements are removed from
    # the heap and added again with the newly calculated cost factor.
    #################################################################
    $heap->delete_item($next{$k}) if defined $next{$k};
    $heap->delete_item($prev{$k}) if defined $prev{$k};

    $next{$k} = $next{$j};

    $heap->add($prev{$k}, $o{cost}->($prev{$k}, $k)) if defined $prev{$k};
    $heap->add($k, $o{cost}->($k, $next{$k})) if defined $next{$k};

    $prev{$next{$j}} = $k if defined $next{$j};
    
    delete $next{$j};
    delete $prev{$j};
  }

  my @temp = grep { defined } @q;
  my @result;
  my $pos = 0;
  for (my $ix = 0; $ix < @temp; ++$ix) {
    push @result, [
      [ $pos, $temp[$ix][0] ],
      [ $pos + scalar(@{$temp[$ix]}) - 1, $temp[$ix][-1] ]
    ];
    $pos += scalar(@{$temp[$ix]})
  }
  
  return @result;
}

1;

__END__

=head1 NAME

Algorithm::LineSegments - Piecewise linear function approximation

=head1 SYNOPSIS

  use Algorithm::LineSegments;
  my @points = line_segment_points(
    points => \@numbers,



( run in 1.018 second using v1.01-cache-2.11-cpan-02777c243ea )