Edward Edward - 11 months ago 45
C# Question

Two lists of coords returning most distinct minimal for one list based on distance function

I have a list of coords using Tuple, since I don't have access to the Drawing library to use 'Point'.

List<Tuple<int,int>> coords = new List<Tuple<int,int>>();
string[] movement = new string[temp.Count];
for(int i=1000; i<=8000; i=i+2300)
for(int j=1000; j<=15000; j=j+2000)
coords.RemoveAll(x=> 3500>= getDist(0,0,x.Item1,x.Item2) );
coords.RemoveAll(x=> 3500>= getDist(16000,9000,x.Item1,x.Item2) );

I have player pieces in a list, two examples below.

List<int[]> player = new List<int[]>() {new int[]{0,726,1084,0,0,5},new int[]{2,1481,2208,0,0,-1} };
//piece numb, loc_X, loc_Y, teamID, state, value

Turn-based movements must be figured, and when the state of a piece indicates scouting, I want to find the most minimal coord-point-set to each scouting piece, without two pieces going to the same set of coords. If I try to use a foreach loop like:

foreach(var myBust in temp) {
int minDist = coords.Select(x => getDist( x.Item1,x.Item2,myBust[1],myBust[2]) ).OrderBy(x => x).Distinct().First();
coords.RemoveAll(x => getDist(16000,9000,x.Item1,x.Item2) == minDist); }

then I fall into an issue that the first piece configured may not be the closest to a point-set as compared to another scouting piece, which is why the foreach loop doesn't work or me. Therefore I want some type of linq/lambda statement that can return the said "Distinct" minimal coord-point in the coord list in comparison (by getDist and Min) to all the player pieces.

//Not sure why this doesn't give me what I am looking for
int minDist= coords.Zip(player, (x,y) => getDist(x.Item1,x.Item2,y[1],y[2])).Min();

My dist method.

static int getDist(int x1, int y1, int x2, int y2)
{ return Convert.ToInt32( Math.Sqrt(Math.Pow(x1-x2,2) + Math.Pow(y1-y2,2) );}

So for an answer I am looking for a way to run a function on two lists, which runs a function (but doesn't aggregate anything together), and can return whatever I want from one or both of those lists.

Based on the answer from @Jacob I came up with the below so far:

string[] movement = new string[player.Count];
List<int[]> temp = player;
HashSet<int> dists = new HashSet<int>();
foreach(var myBust in temp)
{ dists.UnionWith(coords.Select(x => getDist(x.Item1,x.Item2,myBust[1],myBust[2]) )); }
foreach(var myBust in player)
{ if(coords.Exists(x => getDist( x.Item1,x.Item2,myBust[1],myBust[2]) == dists.Min() ) )
Tuple<int,int> result = coords.FindAll(x => getDist( x.Item1,x.Item2,myBust[1],myBust[2]) == dists.Min() ).First();
movement[player.IndexOf(myBust)] = "Move " + result.Item1 + " " + result.Item2;
Console.WriteLine("Player Number "+myBust[0]+" going a dist of "+dists.Min()+" to coords "+result.Item1+","+result.Item2);

This gives the correct output of "Player Number 2 going a dist of 1871 to coords 3000,3300".
That is at least the first iteration through but then throws the error "Collection was modified; enumeration operation may not execute."
Any suggestions or modifications would be appreciated.

Answer Source

To find shortest paths without duplicating and starting with most minimal one first the code below worked.

List<Tuple<int,int,int,int,int>> coordSets = new List<Tuple<int,int,int,int,int>>();
List<int[]> temp = player.Where(x => x[4]==0 ).Where(x=> !needHelp.Any(y=> y[1]==x[0])).Where(x=> movement[player.IndexOf(x)]==null).ToList();
coords.ForEach(x => temp.ForEach(y => coordSets.Add(Tuple.Create(y[1],y[2],x[0],x[1],getDist(x[0],x[1],y[1],y[2])))));
for(int i =0; i<temp.Count; i++)
   Tuple<int,int,int,int,int> coordSet = coordSets.OrderBy(x=>x.Item5).First();
   movement[player.FindIndex(x=> x[1]==coordSet.Item1 && x[2]==coordSet.Item2)] = "Move " + coordSet.Item3 + " " + coordSet.Item4;
   coordSets.RemoveAll(x=> x.Item1==coordSet.Item1 && x.Item2==coordSet.Item2);
   coordSets.RemoveAll(x=> x.Item3==coordSet.Item3 && x.Item4==coordSet.Item4);