~kb/sparse

8d9abe91ec3a6b77cf0ea64d17bce5170e0fbd71 — Kim Burgess 3 years ago 6446592
Continued Vector impl (still WIP)
2 files changed, 70 insertions(+), 21 deletions(-)

M spec/sparse/vector_spec.cr
M src/sparse/vector.cr
M spec/sparse/vector_spec.cr => spec/sparse/vector_spec.cr +52 -0
@@ 40,4 40,56 @@ describe Sparse::Vector do
      v2.nvals.should eq(1)
    end
  end

  describe "#clear" do
    it "removes all elements" do
      v = Sparse::Vector(Int32, 10000).new
      v.set_element 0, 42
      v.nvals.should eq(1)
      v.clear
      v.nvals.should eq(0)
    end

    it "keeps the underlying arrays in place" do
      v = Sparse::Vector(UInt8, 10000).new
      idx, val = v.extract_tuples
      v.clear
      idx2, val2 = v.extract_tuples
      idx2.object_id.should eq(idx.object_id)
      val2.object_id.should eq(val.object_id)
    end
  end

  describe "#size" do
    it "provides the dimensionality" do
      v = Sparse::Vector(UInt8, 10000).new
      v.size.should eq(10000)
    end
  end

  describe "#nvals" do
    it "provides the number of elements set" do
      v = Sparse::Vector(UInt8, 10000).new
      v.nvals.should eq(0)
      v.set_element 0, 42
      v.nvals.should eq(1)
      v.set_element 5048, 0
      v.nvals.should eq(2)
      v.clear
      v.nvals.should eq(0)
    end
  end

  describe "#build" do
    it "populates internal state" do
      v = Sparse::Vector(Int32, 10000).new
      idx = {0, 42, 100, 9999}
      val = {1, 23, 456, 7890}
      add = Proc(Int32, Int32, Int32).new { |a, b| a + b }
      v.build idx, val, add
      v.extract_element(42).should eq(23)
      v.extract_element(41).should be_nil
      v.extract_element(43).should be_nil
    end
  end
end

M src/sparse/vector.cr => src/sparse/vector.cr +18 -21
@@ 2,6 2,8 @@ require "set"

module Sparse
  struct Vector(T, N)
    include Indexable(T)

    @idx : Array(UInt64)
    @val : Array(T)



@@ 64,11 66,12 @@ module Sparse
    # with the previes and new values will be stored.
    #
    # Passed elements do _not_ need to be sorted.
    def build(indicies : Indexable(UInt64), values : Indexable(T), dup : T, T -> T) : self
    def build(indicies : Indexable(Int), values : Indexable(T), dup : T, T -> T) : self
      raise "cannot build into a populated Vector" unless nvals.zero?
      seen = Set(UInt64).new
      indicies.zip values do |idx, val|
        next if idx >= size
        idx = idx.to_u64
        if seen.add? idx
          set_element idx, val
        else


@@ 77,20 80,24 @@ module Sparse
          set_element idx, val if old != val
        end
      end
      self
    end

    # Set one element to a given *value*.
    def set_element(index : Int, value : T) : T
      check_index_out_of_bounds index
      pos = @idx.bsearch_index &.>=(index)
      if pos.nil?
      if @idx.empty? || index > @idx.last
        @idx.push index.to_u64
        @val.push value
      elsif @idx.unsafe_fetch(pos) == index
        @val.to_unsafe[pos] = value
      else
        @idx.insert pos, index.to_u64
        @val.insert pos, value
        pos = @idx.bsearch_index { |x, i| x >= index }
        pos = pos.not_nil!
        if @idx.unsafe_fetch(pos) == index
          @val.to_unsafe[pos] = value
        else
          @idx.insert pos, index.to_u64
          @val.insert pos, value
        end
      end
      value
    end


@@ 106,9 113,9 @@ module Sparse
    end

    # Extract one element of *self* into a scalar.
    @[AlwaysInline]
    def extract_element(index : Int) : T?
      check_index_out_of_bounds index
      unsafe_fetch index
      fetch(index) { nil }
    end

    # Extract all elements with values assigned.


@@ 118,6 125,8 @@ module Sparse

    # Perform a binary search across the tracked element indicies to locate the
    # internal offset of *index* in the vector.
    #
    # If an element for *index* has not been set `nil` is returned.
    private def unsafe_pos(index)
      l = 0
      r = @idx.size


@@ 138,17 147,5 @@ module Sparse
      pos = unsafe_pos index
      @val.unsafe_fetch pos if pos
    end

    private def check_index_out_of_bounds(index)
      check_index_out_of_bounds index { raise IndexError.new }
    end

    private def check_index_out_of_bounds(index, &)
      if index < 0 || index >= size
        yield index
      else
        index
      end
    end
  end
end