7

I have various scenarios in loops where I would 'peek' or 'skip' ahead while iterating through items for processing.

One scenario is I'm enumerating through lines of a file, and there is a 'continuation' character at the end of a line indicating to combine the next line with the current line. If I'm simply looping that isn't too hard, I can just read the next line, and bump my counter/index.

It isn't as obvious is the pattern for doing this with my iterator. I effectively want to consume the next line(s) without exiting my closure. But I'm not even sure if that is possible. Are there any good design pattern for this iteration pattern using a closure, so I don't have to resort to a less groovy loop? Is it perhaps a form of iterator with some stack for pushing/popping items for processing?

MarkE
  • 123
  • 8
  • 3
    I generally use a decorator around the iterator like this: https://gist.github.com/ataylor284/5389604 – ataylor Apr 15 '13 at 17:07

3 Answers3

1

I'd make an iterator that would be in charge of combining lines. For the line continuation example, the iterator can take the lines read in from the file in its constructor, then have its next method read from the lines, reading ahead when it finds a continuation character, so that the continuation characters are resolved before the next step in the pipeline. So whatever state machine you need would be contained within the iterator.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
1

I had to implement something similar sometime ago. I had a large file with pipe-separated data on each line and the data could continue in the next line, but i could only know if i "peeked" the next line. I ended using ArrayList as a stack combined with a peek method:

def list = [1, 2, 3, 4, 5, 6, 7]

list.metaClass.peek = { delegate[-1] }

assert list.pop() == 7
assert list.pop() == 6
assert list.peek() == 5
assert list.peek() == 5
assert list.pop() == 5
assert list.pop() == 4
assert list.peek() == 3
Will
  • 14,348
  • 1
  • 42
  • 44
0

Interesting question.

The key problem here is that you need to carry some state through iterations.

One way to do this could be to use an external variable (here i'm using an array of strings and List#each instead of a file and File#eachLine, but they should be analogous):

def lines = [
  "Single line.",
  "Long \\",
  "line \\",
  "continuation.",
  "Single line."
]

def processLine(line) { println "Processing \"$line\""}

def continuation = ''
lines.each { line ->
  line = continuation + line
  if (line.endsWith('\\')) {
    continuation = line.take(line.size() - 1)
  }
  else {
    processLine(line)
    continuation = ''
  }
}

Another way is to use an iterator specially designed to carry state though interations, like Collection#inject:

lines = lines.inject([]) { list, line ->
  if (list && list[-1].endsWith('\\'))
    list[-1] = list[-1][0..-2] + line
  else 
    list << line
  list
}

lines.each { processLine(it) }

In this case we're first joining the continued lines and then processing them.

In both cases the output is:

Processing "Single line."
Processing "Long line continuation."
Processing "Single line."
epidemian
  • 18,817
  • 3
  • 62
  • 71