It tends to come up somewhat regularly and I had the need myself. So here it is 
a "StableSeq", that is a seq that does not reallocate. The cost is that it's 
not convertible to an `openArray`.
    
    
    type
      StableChunk[T] = object
        next: ptr StableChunk[T]
        len, cap: int
        data: UncheckedArray[T]
      
      StableSeq*[T] = object
        head, tail: ptr StableChunk[T]
        len: int
    
    proc createChunk[T](cap: int): ptr StableChunk[T] =
      result = cast[ptr StableChunk[T]](alloc0(sizeof(StableChunk[T]) + 
sizeof(T) * cap))
      result.cap = cap
    
    proc createStableSeq*[T](cap = 10): StableSeq[T] =
      var c = createChunk[T](cap)
      StableSeq[T](head: c, tail: c, len: 0)
    
    proc destroy*[T](s: StableSeq[T]) =
      var it = s.head
      while it != nil:
        let next = it.next
        dealloc it
        it = next
    
    proc append*[T](s: var StableSeq[T]; data: sink T): ptr T =
      if s.tail.len >= s.tail.cap:
        let oldTail = s.tail
        let newCap = oldTail.cap * 3 div 2
        assert newCap > 2
        s.tail = createChunk[T](newCap)
        oldTail.next = s.tail
      
      result = addr s.tail.data[s.tail.len]
      result[] = ensureMove data
      inc s.tail.len
      inc s.len
    
    proc add*[T](s: var StableSeq[T]; data: sink T) =
      discard append(s, data)
    
    iterator items*[T](s: StableSeq[T]): lent T =
      var it = s.head
      while it != nil:
        for i in 0 ..< it.len:
          yield it.data[i]
        it = it.next
    
    proc nav[T](s: StableSeq[T]; i: var int): ptr StableChunk[T] =
      var it = s.head
      while it != nil:
        if i < it.len:
          return it
        dec i, it.len
        it = it.next
      return nil
    
    proc `[]`*[T](s: var StableSeq[T]; index: int): var T =
      var i = index
      let it = nav(s, i)
      if it != nil:
        return it.data[i]
      else:
        raise newException(IndexDefect, "index out of bounds")
    
    proc `[]`*[T](s: StableSeq[T]; index: int): lent T =
      var i = index
      let it = nav(s, i)
      if it != nil:
        return it.data[i]
      else:
        raise newException(IndexDefect, "index out of bounds")
    
    proc `[]=`*[T](s: var StableSeq[T]; index: int; elem: sink T) =
      var i = index
      let it = nav(s, i)
      if it != nil:
        it.data[i] = elem
      else:
        raise newException(IndexDefect, "index out of bounds")
    
    
    when isMainModule:
      var s = createStableSeq[int]()
      for i in 0 ..< 1000:
        discard s.append i
      
      s[998] = 13
      
      for elem in s:
        echo elem
      
      destroy s
    
    
    Run

Here are my questions/remarks:

  1. Should this really be named `StableSeq`?
  2. Should `=copy` be an `.error` or follow the existing containers and 
perform an expensive copy?
  3. Should it be named `append`?
  4. `nav` should probably use some better algorithm.


Reply via email to