【问题标题】:Two lists of coords returning most distinct minimal for one list based on distance function基于距离函数的一个列表的两个坐标列表返回最明显的最小值
【发布时间】:2016-08-24 22:49:14
【问题描述】:

我有一个使用元组的坐标列表,因为我无权访问绘图库来使用“点”。

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.Add(Tuple.Create(j,i));
coords.RemoveAll(x=> 3500>= getDist(0,0,x.Item1,x.Item2) );
coords.RemoveAll(x=> 3500>= getDist(16000,9000,x.Item1,x.Item2) );

我的列表中有玩家棋子,下面有两个例子。

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

必须计算基于回合的动作,并且当棋子的状态指示侦察时,我想为每个侦察棋子找到最小的坐标点集,而不是两个棋子进入同一组坐标。如果我尝试使用这样的 foreach 循环:

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

然后我遇到了一个问题,即与另一个侦察片相比,配置的第一片可能不是最接近点集的,这就是为什么 foreach 循环不起作用或我。因此,我想要某种类型的 linq/lambda 语句,它可以将坐标列表中的所述“不同”最小坐标点返回给所有玩家片段(通过 getDist 和 Min)。

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

我的 dist 方法。

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

因此,对于答案,我正在寻找一种在两个列表上运行函数的方法,该函数运行一个函数(但不会将任何内容聚合在一起),并且可以从其中一个或两个列表中返回我想要的任何内容。

根据@Jacob 的回答,到目前为止,我得出了以下结论:

string[] movement = new string[player.Count];
List<int[]> temp = player;
while(temp.Any()){
  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);
        coords.Remove(result);
        temp.Remove(myBust);
    }    
  }
}

这给出了“玩家 2 号从 1871 到坐标 3000,3300 的距离”的正确输出。 这至少是第一次迭代,但随后抛出错误“集合已修改;枚举操作可能无法执行”。 任何建议或修改将不胜感激。

【问题讨论】:

  • 你应该重新表述你的问题,因为它并不完全清楚:)
  • 旁注:感觉int[] 应该是某种具有属性的类...
  • 您需要做的第一件事是停止使用数组来存储聚合数据。使用适当的属性声明一个用户定义的类型(即class)以存储您想要的值(例如XY)。您需要做的第二件事是改进问题,以便我们能够理解您的意思。首先提供一个好的minimal reproducible example,它清楚地显示您正在尝试做什么,并准确解释该代码的作用以及您希望它做什么。
  • 详细说明用户定义的类型:您可能(可能)会发现为 X 和 @987654334 创建或使用内置的 Point 或类似类型是有意义的@,然后在您的 Player 类型中包含该类型,以及您想要的任何其他玩家数据。
  • 不确定有什么不清楚的,但至于创建一个类来存储玩家信息是可能的,但即便如此,您也会在网格地图上拥有一个具有当前坐标的玩家列表。我正在尝试获取基于回合的运动坐标列表,如果没有坐标在玩家(或侦察员)之间重复。我有一个简单的解决方案,但我发现第一个 myBust 可以劫持另一个侦察员已经在运行的 minDist,当它的工作功能更改为侦察员时;即使后面的侦察兵离得更近。

标签: c# linq list lambda


【解决方案1】:

据我了解,您希望每个玩家棋子从一组坐标中获取最接近的坐标,并且您还希望每个玩家棋子不共享坐标。在不避免可怕的算法复杂性的情况下,可能没有简单的单行代码。您有两个主要担忧;找到最近的坐标并消除以前使用的坐标或玩家。

所以让我们使用这个算法:

  1. 创建按距离排序的玩家/坐标对列表。
  2. 创建一组空的已使用的玩家和坐标。
  3. 循环播放玩家/坐标对。如果该对用于尚未分配的玩家和坐标,请使用该点,因为它应该是最短的,然后将玩家和坐标标记为已分配。

正如其他人所说,如果您使用更具描述性的对象而不是数组,这段代码会更清晰,但让我们坚持现有的。

这是一个尝试:

var players = new List<int[]>() { new int[] { 0, 726, 1084, 0, 0, 5 }, new int[] { 2, 1481, 2208, 0, 0, -1 } };
List<int[]> coords = new List<int[]>();
for (int i = 1000; i <= 8000; i = i + 2300)
    for (int j = 1000; j <= 15000; j = j + 2000)
        coords.Add(new int[2] { j, i });

coords.RemoveAll(x => 3500 >= getDist(0, 0, x[0], x[1]));
coords.RemoveAll(x => 3500 >= getDist(16000, 9000, x[0], x[1]));

var closestPoints =
    from playerIdx in Enumerable.Range(0, players.Count())
    let player = players[playerIdx]
    from coordIdx in Enumerable.Range(0, coords.Count())
    let theseCoords = coords[coordIdx]
    orderby getDist(theseCoords[0], theseCoords[1], player[1], player[2])
    select new { playerIdx, coordIdx };

var playerCoords = new Dictionary<int, int>();
var usedPlayers = new HashSet<int>();
var usedCoords = new HashSet<int>();
foreach (var pairing in closestPoints)
{
    if (!usedPlayers.Contains(pairing.playerIdx)
        && !usedCoords.Contains(pairing.coordIdx))
    {
        playerCoords[pairing.playerIdx] = pairing.coordIdx;
        usedPlayers.Add(pairing.playerIdx);
        usedCoords.Add(pairing.coordIdx);
    }
}

// Result is in playerCoords as { 0: 0, 1: 6 }

如果您有大量玩家或坐标,这仍然不是非常有效,因为您正在计算每个玩家/坐标配对的距离。您可能想找到一个快捷方式来避免计算其中的一些。请参阅Closest Pair: A Divide-and-Conquer approach 那里的想法。

【讨论】:

  • 据我了解,根据我的阅读,Enumerable.Range 是过程密集型的,所以我以前远离这种编码。我确实喜欢 HashSet,因为它具有更快的迭代时间,并且永远不会让您添加重复项。但是根据您所说的,我想为所有片段/坐标集创建一个临时 HashSet 分布,取 Min() 然后删除等于该值的片段/坐标集并再次运行该过程,直到没有更多片段需要球探指导。
  • Enumerable.Range 不是进程密集型 AFAIK;会对您从哪里获得该信息感兴趣。当然,它肯定不如常规循环高效。
  • 好吧,我仍然是个业余爱好者,如果我在这一点上错了,那是另一件事,我会在其他时间研究。
  • 好的,在 csharppad.com 上尝试了这段代码,但不确定结果真正给了我什么。
  • Keys = 玩家索引,Values = 坐标索引。
猜你喜欢
  • 2020-06-11
  • 1970-01-01
  • 2018-10-09
  • 2020-05-12
  • 2015-03-14
  • 2021-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多