Edward Edward - 10 months ago 38
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.


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);