Haskell-将笛卡尔网格中的特定最近邻居分组

时间:2019-08-23 10:24:36

标签: algorithm haskell

我在这个谜题中找到了方向,需要将特定的最近邻居归为一类。

我的输入数据是:

myList :: Map (Int, Int) Int
myList =
  fromList
    [((-2,-2),0),((-2,-1),0),((-2,0),2),((-2,1),0),((-2,2),0)
    ,((-1,-2),1),((-1,-1),3),((-1,0),0),((-1,1),0),((-1,2),1)
    ,((0,-2),0),((0,-1),0),((0,0),0),((0,1),0),((0,2),0)
    ,((1,-2),0),((1,-1),0),((1,0),0),((1,1),2),((1,2),1)
    ,((2,-2),0),((2,-1),2),((2,0),0),((2,1),0),((2,2),0)]

以下是此5 x 5网格(棕色土地,蓝色水域)的数据表示形式:5 x 5 grid 我使用(Int,Int)作为XY坐标,因为列表的生成方式(因此顺序)是在作为原点的笛卡尔坐标网格(0,0)上的螺旋形。剩下的Int是人口0的人口,1..9是水,Map是土地。

由于我的4的排序,我一直在努力寻找一种遍历数据并返回[ [(-1 , 2)] , [(1, 2),(1,1)] , [(-2, -0),(-1,-1),(-1,-2)] , [(2, -1)]] 分组土地的方法,这些土地由于彼此之间的相互连接而被分组(包括对角线) ),因此我正在寻找类似波纹管的结果:

rlang

我已经研究并尝试了各种算法,例如BFS,Flood Fill,但是我的输入数据从不符合结构要求,或者我对主题的理解使我无法将其转换为使用坐标。

有没有一种方法可以直接在数据上运行算法,还是应该在寻找另一个方向?

很抱歉,到目前为止,我还没有代码示例,但是我什至无法创建任何对远程有用的东西。

2 个答案:

答案 0 :(得分:2)

我建议使用联合查找数据结构。循环所有位置;如果它是陆地,​​则将其标记为等同于它也是NE的NE,N,NW或W的任何位置。 (当您访问其他土地时,它会自动被标记为与该土地​​上存在E,SW,S或SE的土地等价。集合D = {NE,N,NW,W}的关键属性是镜像D中的所有方向以获得M,则M∪D包含每个方向;具有此属性的任何其他D集也可以正常工作。)在此过程结束时,数据结构返回的等效类将与您连接土地块。

如果n是职位总数,则此过程为O(n * log n); log n组件来自Map查找,以确定邻居是陆地还是水域。

如果可以的话,应该考虑使Map稀疏-仅存储对应于land的键-值对,并跳过水键-逐步到O(m * log m),其中m是土地总数,而不是阵地总数。如果您不能(例如,因为必须记住水位和不存在的位置之间的差异),则可以考虑切换到数组作为后备存储以逐步达到O(n * an),其中a是逆阿克曼函数,因此整个shebang基本上会尽可能地接近O(n),而实际上不必为O(n)。

当同时选择O(m * log m)或O(n * a n)时,O(m * log m)还是O(n * a n)是更可取的,这是对您认为代表典型用例的某些数据集进行经验探索的问题。

答案 1 :(得分:0)

我最终使用了Chris Penner通过FP松弛通道使用的此解决方案,它使用联合查找算法(我在代码中添加了注释以提供一些帮助):

-- | Take Map of land coordinates and return list of grouped land items forming islands
-- | Using Union find algorythm
findIslands ::  M.Map Coordinate Coordinate -> IO [[Coordinate]]
findIslands land = do
  -- create fresh point map
  pointMap <- traverse U.fresh land
  -- traverse each point checking for neighbours
  void . flip M.traverseWithKey pointMap $ \(x, y) point ->
      for_ (catMaybes (flip M.lookup pointMap <$> [(x + 1, y), (x, y + 1),(x +1, y +1), (x - 1, y + 1)]))
          $ \neighbourPoint ->
              U.union point neighbourPoint
  -- traverse ppintMap and representative and their descriptors
  withUnionKey :: (M.Map Coordinate Coordinate) <- for pointMap (U.repr >=> U.descriptor)
  -- swap cordinates arround
  let unionKeyToCoord :: [(Coordinate, Coordinate)] = (swap <$> M.toList withUnionKey)
      -- combine coordinates to create islands
      results :: M.Map Coordinate [Coordinate] = M.fromListWith (<>) (fmap (:[]) <$> unionKeyToCoord)
  -- return just the elements from the Map
  return (M.elems results)

convertTolandGrid :: [Coordinate] -> M.Map Coordinate Coordinate
convertTolandGrid = M.fromList . fmap (id &&& id)