在Scala中生成懒惰的“螺旋”

时间:2010-09-01 15:19:45

标签: scala yield spiral

任务: 对于2D阵列中的给定位置,生成位于半径内的周围位置列表。

例如:

input: (1, 1)
radius: 1
output: ( (0, 0), (1, 0), (2, 0), 
          (0, 1),         (2, 1),
          (0, 2), (1, 2), (2, 2) ).

我写了类似

的内容
def getPositions(x:Int, y:Int, r:Int) = {
  for(radius <- 1 to r) yield {
    List(
      for (dx <- -radius to radius) yield Pair(x + dx, y - radius),
      for (dx <- -radius to radius) yield Pair(x + dx, y + radius),
      for (dy <- -radius to radius) yield Pair(x + radius, y + dy),
      for (dy <- -radius to radius) yield Pair(x - radius, y + dy)
    )
  }
}

在这段代码中,getPositions不返回一系列点,而是返回点序列的元组4的序列。 如何“连接”代码中列出的4个生成器?或者我的任务有更简洁的解决方案吗? (我对scala很新。)

P.S。 这实际上是我的星际机器人。

5 个答案:

答案 0 :(得分:4)

你需要压扁List(两次),所以这样做:

def getPositions(x:Int, y:Int, r:Int) = {
  for(radius <- 1 to r) yield {
    List(
      for (dx <- -radius to radius) yield Pair(x + dx, y - radius),
      for (dx <- -radius to radius) yield Pair(x + dx, y + radius),
      for (dy <- -radius to radius) yield Pair(x + radius, y + dy),
      for (dy <- -radius to radius) yield Pair(x - radius, y + dy)
    ).flatten
  }
}.flatten

但这不是一个“懒惰”的螺旋。

修改

那个人很懒:

def P(i:Int, j:Int) = { print("eval"); Pair(i,j) }

def lazyPositions(x:Int, y:Int, r:Int) = {
  (1 to r).toStream.flatMap{ radius =>

    (-radius to radius).toStream.map(dx => P(x + dx, y - radius)) #:::
    (-radius to radius).toStream.map(dx => P(x + dx, y + radius)) #:::
    (-radius to radius).toStream.map(dy => P(x + radius, y + dy)) #:::
    (-radius to radius).toStream.map(dy => P(x - radius, y + dy))
  }
}


print(lazyPositions(1,1,1).take(3).toList) # prints exactly three times ‘eval’.

我使用def P方法来显示真正的懒惰。每次,你都会创建一个Pair,它会被调用。在懒惰的解决方案中,您只需按需提供此功能。

答案 1 :(得分:1)

试试这个:

object Spiral
{
    def
    getPositions(x: Int, y: Int, r: Int): Seq[(Int, Int)] = {
      for { radius <- 1 to r
            dx <- -radius to radius
            dy <- -radius to radius
            if dx != 0 || dy != 0
      } yield
          (x + dx, y + dy)
    }


    def
    main(args: Array[String]): Unit = {
        printf("getPositions(1, 1, 1): %s%n", getPositions(0, 0, 1).mkString("{ ", ", ", " }"))
    }
}

输出:

getPositions(1, 1, 1): { (-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1) }

答案 2 :(得分:1)

您可以直接形成您的范围,并使用flatMap++将这些列表组合在一起,您也可能希望以循环方向进行:

def getPositions(x: Int, y: Int, r: Int) = {
  (1 to r) flatMap (radius => {
    val dx = -radius to radius
    val dy = -(radius-1) to (radius-1)
    dx.map(i => (x+i, y+radius)) ++ dy.map(i => (x+radius, y-i)) ++
    dx.map(i => (x-i, y-radius)) ++ dy.map(i => (x-radius, y+i))
  })
}

如果你真的想让结果变得懒惰,你就必须对懒惰的组件做同样的事情:

def getPositions(x: Int, y: Int, r: Int) = {
  Stream.range(1,r+1) flatMap (radius => {
    val dx = Stream.range(-radius,radius+1)
    val dy = Stream.range(-(radius+1),radius)
    dx.map(i => (x+i, y+radius)) ++ dy.map(i => (x+radius, y-i)) ++
    dx.map(i => (x-i, y-radius)) ++ dy.map(i => (x-radius, y+i))
  })
}

编辑:修正了dx与dy拼写错误。

答案 3 :(得分:0)

以下是此问题的一些解决方案。首先,如果您不关心订单,只关注头寸,这样做:

def getPositions(x:Int, y:Int, r:Int) = for {
  yr <- y - r to y + r
  xr <- x - r to x + r
  if xr != x || yr != y
} yield (xr, yr)

这将给出您指定的完全相同的输出。但是,您需要一个Python风格的生成器,因此这更合适:

def getPositions(x:Int, y:Int, r:Int) = Iterator.range(y - r, y + r + 1) flatMap {
  yr => Iterator.range(x - r, x + r + 1) map { 
    xr => (xr, yr)
  }
} filter (_ != (x, y))

这将返回Iterator,您可以使用next进行迭代。使用hasNext检查结尾。

您可以将Iterator替换为ListStream或类似的内容,并获得完全生成的集合。

现在,让我们假设您需要从中心开始螺旋并一次移动一个位置。我们可以这样做:

def getPositions(x:Int, y:Int, r:Int) = new Iterator[(Int, Int)] {
  private var currentX = x
  private var currentY = y
  private var currentR = 1
  private var incX = 0
  private var incY = 1
  def next = {
    currentX += incX
    currentY += incY
    val UpperLeft = (x - currentR, y + currentR)
    val UpperRight = (x + currentR, y + currentR)
    val LowerLeft = (x - currentR, y - currentR)
    val LowerRight = (x + currentR, y - currentR)
    val PrevSpiral = (x, y + currentR)
    val NextSpiral = (x - 1, y + currentR)
    (currentX, currentY) match {
      case NextSpiral => incX = 1; incY = 1; currentR += 1
      case PrevSpiral => incX = 1; incY = 0
      case UpperLeft => incX = 1; incY = 0
      case UpperRight => incX = 0; incY = -1
      case LowerRight => incX = -1; incY = 0
      case LowerLeft => incX = 0; incY = 1
      case _ =>
    }
    if (currentR > r)
      throw new NoSuchElementException("next on empty iterator")
    (currentX, currentY)
  }
  def hasNext = currentR <= r
}

答案 4 :(得分:0)

这是一条走在边缘的小溪。

假设输入(3,3),2给出

{(1,1), (2,1), (3,1), (4,1), (5,1),
 (1,2),                      (5,2),
 (1,3),                      (5,3),
 (1,4),                      (5,4),
 (1,5), (2,5), (3,5), (4,5), (5,5)}

然后您可以使用以下内容:

def border(p: (Int,Int), r: Int) = {
  val X1 = p._1 - r
  val X2 = p._1 + r
  val Y1 = p._2 - r
  val Y2 = p._2 + r
  def stream(currentPoint: (Int,Int)): Stream[(Int,Int)] = {
    val nextPoint = currentPoint match {
      case (X1, Y1) => (X1+1, Y1)
      case (X2, Y2) => (X2-1, Y2)
      case (X1, Y2) => (X1, Y2-1)
      case (X2, Y1) => (X2, Y1+1)
      case (x, Y1) => (x+1, Y1)
      case (x, Y2) => (x-1, Y2)
      case (X1, y) => (X1, y-1)
      case (X2, y) => (X2, y+1)
    }
    Stream.cons(nextPoint, if (nextPoint == (X1,Y1)) Stream.empty else stream(nextPoint))
  }
  stream((X1,Y1))
}

用法:

scala> val b = border((3,3),2)
b: Stream[(Int, Int)] = Stream((2,1), ?)

scala> b.toList
res24: List[(Int, Int)] = List((2,1), (3,1), (4,1), (5,1), (5,2), (5,3), (5,4), (5,5), (4,5), (3,5), (2,5), (1,5), (1,4), (1,3), (1,2), (1,1))