I am trying to implement an object that keeps multiple spans of consecutive integers.
In Python 2.x a simplified version of it looks like this:
class ValueSpanList (object):
def __init__(self, descr):
self.descr = descr
self.vsl = []
def addValue(self, v):
vsl = self.vsl
if vsl and vsl[-1][1] == v:
vsl[-1][1] = v+1
else:
vsl.append( [v, v+1] )
def allValues(self):
for v0, v1 in self.vsl:
for v in xrange(v0, v1):
yield v
if __name__ == "__main__":
from sys import stdout
testVsl = ValueSpanList("Some test values")
for v in xrange(3, 9): testVsl.addValue(v)
for v in xrange(13, 19): testVsl.addValue(v)
for v in xrange(21, 29): testVsl.addValue(v)
print testVsl.vsl
print "{:s}:".format(testVsl.descr)
for v in testVsl.allValues():
stdout.write("{:d} ".format(v))
So far I was able to come up with the following in nim:
type
ValueSpan = tuple[v0, v1: int] # a span of consecutive integers from v0 to v1-1
ValueSpanSeq = seq[ValueSpan] # a sequence of ascending ValueSpans
ValueSpanList* = ref object
descr: string
vsl: ValueSpanSeq
proc newValueSpanList*(descr: string): ValueSpanList =
new result
result.descr = descr
result.vsl = @[]
proc addValue*(self: ValueSpanList, v: int) =
if self.vsl.len > 0 and self.vsl[self.vsl.high].v1 == v:
self.vsl[self.vsl.high].v1 = v+1 # v is adjacent to the last span, extend it
else:
self.vsl.add( (v, v+1) ) # v is not adjacent to the last span, add a new span
iterator allValues*(self: ValueSpanList): int =
for span in self.vsl:
for v in span.v0..<span.v1:
yield v
when isMainModule:
var testVsl = newValueSpanList("Some test values")
for v in 3..8: testVsl.addValue(v)
for v in 13..18: testVsl.addValue(v)
for v in 21..28: testVsl.addValue(v)
echo repr(testVsl)
echo testVsl.descr, ":"
for v in testVsl.allValues():
stdout.write(v, " ")
This works, but the addValue proc is not as readable as I'd like it to be.
It would be nice to be able to write it like this (similar to python):
proc addValue*(self: ValueSpanList, v: int) =
var vsl = self.vsl
if vsl.len > 0 and vsl[vsl.high].v1 == v:
vsl[vsl.high].v1 = v+1
else:
vsl.add( (v, v+1) )
but this does not work, because var vsl = self.vsl assignment makes a copy of the self.vsl sequence, and the code that follows operates on the wrong sequence.
Any ideas on how the code for addValue proc can be made as readable as (or even more readable than) its python counterpart?
templates:
proc addValue*(self: ValueSpanList, v: int) =
template vsl: auto = self.vsl # var vsl = self.vsl
if vsl.len > 0 and vsl[vsl.high].v1 == v:
vsl[vsl.high].v1 = v+1
else:
vsl.add( (v, v+1) )
@Arrrrrrrrr Thanks! Being new to nim and trying to learn it by doing simple things, I tended to consider templates as a rather advanced feature to master "later", but you demonstrated that they can be pretty useful from the very beginning
@mratsim No, it's not a silly question. It did not occur to me that one can use slice object for something other than actually "slicing" something indexable with it.
With your help the code looks simpler and cleaner now:
type
ValueSpanList* = ref object
descr: string
vsl: seq[Slice[int]]
proc newValueSpanList*(descr: string): ValueSpanList =
new result
result.descr = descr
result.vsl = @[]
proc addValue*(self: ValueSpanList, v: int) =
template vsl: auto = self.vsl
if vsl.len > 0 and vsl[vsl.high].b == v-1:
vsl[vsl.high].b = v
else:
vsl.add(v..v)
iterator allValues*(self: ValueSpanList): int =
for slice in self.vsl:
for v in slice:
yield v
Try this:
type
ValueSpan = tuple[v0, v1: int]
ValueSpanSeq = seq[ValueSpan]
ValueSpanList* = ref object
descr: string
vsl: ValueSpanSeq
{.this:self.}
proc newValueSpanList*(descr: string): ValueSpanList =
ValueSpanList(descr: descr, vsl: @[])
proc addValue*(self: ValueSpanList, v: int) =
if vsl.len > 0 and vsl[^1].v1 == v:
vsl[^1].v1 = v+1
else:
vsl.add( (v, v+1) )
iterator allValues*(self: ValueSpanList): int =
for span in vsl:
for v in span.v0..<span.v1:
yield v
self.vsl[^1] is more idiomatic than self.vsl[self.vsl.high], and more readable too, I guess.
While there is {.this: self.} available in Nim, I don't consider it a good practice as it makes it unobvious whether a certain ident is a self's field or a variable (either local or global). It's even possible to silently override an ident with a local variable.
As for var vsl = self.vsl, it will never work in Nim due to implicit-copy policy, removal of which would imply breaking backwards-compatibility heavily. It has its own advantages and downsides.
I agree, this:self makes code less grep-able.
var vsl = self.vsl works because vsl is a ref type so the pointer is indeed copied but it points to the same (shared) data location.