Search code examples
scalayieldspiral

Generate lazy "spiral" in Scala


Task: For a given position in 2D array generate list of surrounding positions located in radius.

For example:

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

I wrote something like

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

In this code getPositions returns not a sequance of points, but a sequance of Tuple4 of sequances of points. How can I "concatenate" 4 generators listed in code? Or is there more concise solution for my task? (I'm pretty new to scala).

P.S. It's actually for my starcraft bot.


Solution

  • You need to flatten the List (twice), so this would do:

    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
    

    It’s not a ‘lazy’ spiral, though.

    Edit

    That one is lazy:

    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’.
    

    I’ve used the def P method to show the real laziness. Everytime, you’d create a Pair, it gets called. In a lazy solution, you’d only want this on demand.