Added: Version #2 of this question
Minor Update/Additonal information: This question is based on a Codility question similar to this one. The input (an int array), the outpout (an integer) and the method signature (public int solution(int[] A)
) are given.
Situation:
A critter starts at (0, 0) on a Cartesian graph. We have a non-empty zero-indexed "moves" list that contains numbers. Each number represents the distance moved. (Similar to this question) The first number is the distance north, the second is the distance east, the third is the distance south, the fourth is the distance west, and repeats like this forever. Therefore the directions cycle every four moves.
Goal:
Find an algorithm that gives the move number that makes the critter cross a point that it has already visited before. The move number is the index of the "moves" list.
Example:
If given this move list: [1, 3, 2, 5, 4, 4, 6, 3, 2]
The answer is then 6. (It's the 7th move).
Draw it on a graph, the turtle will go:
(0,0) -> (0,1) -> (3,1) -> (3,-1) -> (-2,-1) -> (-2,3) -> (2,3) -> (2,-3)
At the 6th index (move number 7th) it will meet (2,1) which is a point that the turtle has already crossed.
Notes:
Algorithm should preferably be O(n).
algorithm space Complexity should be __?
n (Number of moves) is an integer between 1 and 100,000
m (distance per move) is a positive integers between 1 and 1,000
"No collision" should return -1
I'm mainly concerned about accuracy (correct answer), speed (big O) and space. (not that I won't accept criticism of other parts).
In my SemiShort
and my SemiLong
tests, this takes a bit of time to run as things get bigger (SemiShort
takes 3ms. SemiLong
takes 18.). It's obviously because I'm creating a new Dictionary for each X, and new list entry for each Y of each X.
What options do I have to reduce the space complexity while also keeping the run time at \$O(n)\$? I have an itching feeling that Dict<int, List<int>>
isn't the most effective way to build this, but I can't place my finger on what else I could use.
I've considered trying to do a bit-manipulation via something like some "magic" code that uses Bit Vector to achieve "greatness" - but I think it would ruin readability (especially once you have to start working with more than 32 "bits").
So I have something I feel is readable and accurate... but slow with bigger numbers/longer runs - that I could make more efficient at the cost of readability and complexity.
If the moves per direction was increased from 1k to 100k - it would blow the space used out of orbit and times would skyrocket accordingly.
using System;
using System.Collections.Generic;
using System.Drawing;
using Xunit;
namespace Critter_Crossing
{
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine((new Classy()).Solution(new[] {1, 1, 1, 1}));
Console.WriteLine((new Classy()).Solution(new[] {1, 3, 2, 5, 4, 4, 6, 3, 2}));
Console.WriteLine((new Classy()).Solution(new[]
{
1000, 1000, 1001, 1001, 1002, 1002, 1003, 1003, 1004, 1004, 1005, 1005, 1006,
1006, 1007, 1007, 1008, 1008, 1009, 1009, 1010, 1010, 1010, 1010
}));
//Console.WriteLine((new Classy()).Solution(new[] { 100000, 100000, 100000, 100000 }));
//Console.WriteLine((new Classy()).Solution(new[]
// {
// 100000, 100000, 100001, 100001, 100002, 100002, 100003, 100003, 100004, 100004, 100005, 100005, 100006,
// 100006, 100007, 100007, 100008, 100008, 100009, 100009, 100010, 100010, 1
// }));
Console.ReadLine();
}
}
public class Testy
{
[Fact] public void Test_Null() { Assert.Equal(-1, (new Classy()).Solution(null)); }
[Fact] public void Test_0() { Assert.Equal(-1, (new Classy()).Solution(new int[0])); }
[Fact] public void Test_1() { Assert.Equal(-1, (new Classy()).Solution(new []{1})); }
[Fact] public void Test_2() { Assert.Equal(-1, (new Classy()).Solution(new []{1,1})); }
[Fact] public void Test_3() { Assert.Equal(-1, (new Classy()).Solution(new []{1,1,1})); }
[Fact] public void Test_4() { Assert.Equal( 3, (new Classy()).Solution(new []{1,1,1,1})); }
[Fact] public void Test_TestExample() { Assert.Equal(6, (new Classy()).Solution(new []{1, 3, 2, 5, 4, 4, 6, 3, 2})); }
[Fact] public void Test_LongExample() { Assert.Equal(41, (new Classy()).Solution(new []{1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,20,1})); }
[Fact] public void Test_SemiShort() { Assert.Equal(3, (new Classy()).Solution(new[] {1000, 1000, 1000, 1000})); }
[Fact] public void Test_SemiLong() { Assert.Equal(23, (new Classy()).Solution(new[]
{
1000, 1000, 1001, 1001, 1002, 1002, 1003, 1003, 1004, 1004, 1005, 1005,
1006, 1006, 1007, 1007, 1008, 1008, 1009, 1009, 1010, 1010, 1010, 1010
})); }
[Fact(Skip="Runs Long")] public void Test_ShortLong() { Assert.Equal(3, (new Classy()).Solution(new[] {100000, 100000, 100000, 100000})); }
[Fact(Skip="Runs Long")] public void Test_LongLong() { Assert.Equal(22, (new Classy()).Solution(new[]
{
100000, 100000, 100001, 100001, 100002, 100002, 100003, 100003, 100004, 100004, 100005, 100005, 100006,
100006, 100007, 100007, 100008, 100008, 100009, 100009, 100010, 100010, 1
}));}
}
public class Classy
{
public static Point L; /* Tracks current location on cortesian plane */
public static Dictionary<int, List<int>> P; /* Tracks path taken for collision detection */
public int Solution(int[] A)
{
if (A == null) return -1; /* Invalid Input */
if (A.Length < 4) return -1; /* Can't cross itself without going at least 4 steps */
P = new Dictionary<int, List<int>>();
L = new Point(0, 0);
var crossed = false;
var result = -1;
P[0] = new List<int>();
P[0].Add(0);
for (var i = 0; i < A.Length; i++)
{
if (A[i] <= 0) return -1; /* Bad input */
switch (i%4) /* Rotate through directions NSEW */
{
case 0: /* North */ crossed = Travel(1, 0, A[i]); break;
case 1: /* East */ crossed = Travel(0, 1, A[i]); break;
case 2: /* South */ crossed = Travel(-1, 0, A[i]); break;
case 3: /* West */ crossed = Travel(0, -1, A[i]); break;
}
if (!crossed) continue;
/* Found a crossing point, get result and break */
result = i;
break;
}
return result;
}
private bool Travel(int x, int y, int distance)
{
/* move one space, check location and...
* ... return if already ticked
* ... add otherwise
*/
int X; /* used for brevity */
int Y; /* used for brevity */
for (var j = 1; j <= distance; j++)
{
X = L.X += x*1;
Y = L.Y += y*1;
if (!P.ContainsKey(L.X))
{
/* First time on this X plane */
P[X] = new List<int>();
P[X].Add(Y);
}
else if (!P[X].Contains(Y))
{
/* First time seeing this Y coordinate on an existing X plane */
P[X].Add(Y);
}
else
{
/* Existing Y on an existing X */
return true;
}
}
return false;
}
}
}
Dictionary<Tuple<int,int>, int>
it looks like obvious solution? \$\endgroup\$