Task 1: Straight Line
You are given a list of co-ordinates.
Write a script to find out if the
given points make a straight line.
-
Example 1:
-
Input:
@list = ([2, 1], [2, 3], [2, 5])
-
Output:
true
-
Input:
-
Example 2:
-
Input:
@list = ([1, 4], [3, 4], [10, 4])
-
Output:
true
- Example 3:
-
Input:
@list = ([0, 0], [1, 1], [2, 3])
-
Output:
false
-
Input:
-
Example 4:
-
Input:
@list = ([1, 1], [1, 1], [1, 1])
-
Output:
true
-
Input:
-
Example 5:
-
Input:
@list = ([1000000, 1000000], [2000000, 2000000], [3000000, 3000000])
-
Output:
true
-
Input:
This is not so much a programming problem as it as an algebra review problem. There's a straight-forward calculation that answers the question. I knew that, but didn't remember the details, so I looked it up. Once again, the 8th grade cynics who said we would never need this in real life are proven wrong, 53 years later.
sub isLine(@point)
{
my @x = map { $_->[0] } @point;
my @y = map { $_->[1] } @point;
return ($y[1] - $y[0]) * ( $x[2] - $x[0] ) == ( $y[2] - $y[0] ) * ( $x[1] - $x[0] )
}
The more interesting programming problem to me was not the geometric algebra, but the data representation of the points and how to reference them in some readable way. In the function, I chose to pass an array of references to pairs, as the examples show. Then I extract the x and y coordinates from each pair as lists to create the readable implementation of the formula.
To pass data into the script from the command line, I chose to pass comma-separated pairs. For example: perl ch-1.pl 1,1 2,2 3,3
Each of the pairs needs to be turned into a reference to a pair of numbers, which can be done by putting the results of a split
inside a pair of square brackets, and applying that to every argument with map
.
my @point = map { [split ',', $_] } @ARGV;
die "Need three points" unless @point == 3;
say isLine(@point) ? "true" : "false";
Task 2: Duplicate Zeroes
You are given an array of integers.
Write a script to duplicate each occurrence
of zero, shifting the remaining elements to
the right. The elements beyond the length of
the original array are not written.
-
Example 1:
-
Input:
@ints = (1, 0, 2, 3, 0, 4, 5, 0)
-
Output:
(1, 0, 0, 2, 3, 0, 0, 4)
- Each zero is duplicated. Elements beyond the original length (like 5 and last 0) are discarded.
-
Input:
-
Example 2:
-
Input:
@ints = (1, 2, 3)
-
Output:
(1, 2, 3)
- No zeros exist, so the array remains unchanged.
-
Input:
-
Example 3:
-
Input:
@ints = (1, 2, 3, 0)
-
Output:
(1, 2, 3, 0)
-
Input:
-
Example 4:
-
Input:
@ints = (0, 0, 1, 2)
-
Output:
(0, 0, 0, 0)
-
Input:
-
Example 5:
-
Input:
@ints = (1, 2, 0, 3, 4)
-
Output:
(1, 2, 0, 0, 3)
-
Input:
This is a little tour of how easy it is to manipulate arrays in Perl. I have three possible solutions in mind:
- Copy to a new array, adding an extra zero when needed.
- Map each zero to a pair of zeroes, then cut to size. This also effectively makes a copy of the list.
- Splice an extra zero into the original list Let's develop them and run a benchmark.
Copy with insertion
In this solution, we'll push each element of the original list to a new list, and add an extra 0 every time we see a 0.
sub duplicateZero(@int)
{
my @out;
my $length = @int;
while ( defined(my $n = shift @int) && @out < $length)
{
push @out, $n;
push @out, 0 if $n == 0 && @out < $length;
}
return \@out;
}
Pretty straight-forward. I'm annoyed by having to check the length twice, but I can live with it.
Mapping to pairs and truncating
In this solution, we'll transform every 0 to a pair, and then reduce to the correct size.
sub dupCut(@int)
{
my @out = map { $_ == 0 ? (0,0) : ($_) } @int;
splice(@out, scalar(@int) );
return \@out;
}
The cuteness here is that a single element can be mapped to a list, and then the list is flattened. Perl is doing its famous "do what I mean" trick here.
Truncating the list could be done either with an array slice, or by using splice
. Using a slice would create a copy, so I prefer to modify in place. On my first attempt, I tried to return the result of splice
, but that's wrong, because splice
returns the part that was removed, not the remainder. I could have spliced the complement (splice(@out, 0, @int-1)
) to make it a single line, but this way seems more readable.
My one reservation about this solution is that it might do a lot of unnecessary work. Imagine a very long list (a million, maybe) where there are a lot of zeroes. After getting about halfway through the list, we might have already exceeded the length, but we'll continue to convert the other half-million elements, only to throw them away.
Splice in place
The third solution tries to avoid the overhead of creating a second list, and instead inserts zeroes into the original list.
sub dupSplice(@int)
{
my $length = @int;
for ( reverse 0 .. $#int )
{
splice(@int, $_, 1, 0,0) if $int[$_] == 0;
}
splice(@int, $length);
return \@int;
}
Some notes here:
-
reverse 0 .. $#int
-- I'm working backwards because I need to use indexes into the list, and when I insert new elements the indexes will change. -
splice
doesn't only removes elements from a list. Given extra arguments, it can also replace. Once again I'm replacing a single element with a short list. - This is not very different from the map-and-truncate solution, except that the loop is explicit and there's no second array.
- Like the previous solution, this might do unnecessary work, especially since we're starting from the back and must work through the entire list.
Benchmark
Let's see if there's any performance advantage among the solutions. My intuition tells me that modification in place without creating a second array not only wins in memory, but should be faster.
sub runBenchmark($repeat)
{
use Benchmark qw/cmpthese/;
my @int = map { int(rand(20)) + 1 } 1 .. 100;
$int[$_] = 0 for map { int(rand(100)) } 1..10;
cmpthese($repeat, {
copy => sub { duplicateZero(@int) },
cut => sub { dupCut(@int) },
splice => sub { dupSplice(@int) },
});
}
I set up a longer array with random non-zero numbers. They don't need to be random, but it makes me feel better to emulate the examples. Then I randomly set some percentage of them to 0. Here's a result:
$ perl ch-2.pl -b 80000
Rate copy splice cut
copy 76923/s -- -54% -54%
splice 166667/s 117% -- 0%
cut 166667/s 117% 0% --
The map-and-truncate solution is just as fast as splicing in place. As predicted, both are better than building a copy of the list.
I played a bit with making the list contain no zeroes, or all zeroes, or other proportions of zeroes, but the result is the same.
Top comments (0)