~kb/sparse

e6876df498a7e76afc26a38af4f479baa5f0cd2e — Kim Burgess 3 years ago 3b44fbb
Complete niave Vector implementation

Provides initial structure and test coverage to enable refactoring and
perf updates.
3 files changed, 97 insertions(+), 5 deletions(-)

M spec/sparse/vector_spec.cr
M spec/spec_helper.cr
M src/sparse/vector.cr
M spec/sparse/vector_spec.cr => spec/sparse/vector_spec.cr +82 -0
@@ 91,4 91,86 @@ describe Sparse::Vector do
      v.extract_element(43).should be_nil
    end
  end

  describe "#set_element" do
    it "inserts at the provided index" do
      context "populated" do
        v = Sparse::Vector(Int32, 10000).random 1000
        v.set_element 42, 1729
        v.extract_element(42).should eq(1729)
      end

      context "empty" do
        v = Sparse::Vector(Int32, 10000).new
        v.set_element 42, 1729
        v.extract_element(42).should eq(1729)
      end
    end

    it "overwrites an existing value if present" do
      v = Sparse::Vector(Int32, 10000).new
      v.set_element 42, 1729
      v.set_element 42, 1
      v.extract_element(42).should eq(1)
    end

    it "raises when out of bounds" do
      v = Sparse::Vector(Int32, 100).new
      expect_raises(IndexError) do
        v.set_element 1000, 42
      end
    end
  end

  describe "#remove_element" do
    it "drops the element at the specified index" do
      v = Sparse::Vector(Int32, 100).new
      v.set_element 42, 1729
      v.remove_element(42).should eq(1729)
      v.extract_element(0).should be_nil
    end

    it "ignore unset element indicies" do
      v = Sparse::Vector(Int32, 100).new
      v.remove_element(42).should be_nil
    end

    it "raises when out of bounds" do
      v = Sparse::Vector(Int32, 100).new
      expect_raises(IndexError) do
        v.remove_element 1000
      end
    end
  end

  describe "#extract_element" do
    it "returns to the element at the specified index" do
      v = Sparse::Vector(Int32, 100).new
      v.set_element 42, 1729
      v.extract_element(42).should eq(1729)
    end

    it "returns nil if the element does not exist" do
      v = Sparse::Vector(Int32, 100).new
      v.extract_element(42).should be_nil
    end

    it "raises when out of bounds" do
      v = Sparse::Vector(Int32, 100).new
      expect_raises(IndexError) do
        v.extract_element 1000
      end
    end
  end

  describe "#extract_tuples" do
    it "provides the underlying idx, val arrays" do
      idx = Sparse::Spec::Random.rand(Array(UInt64), 1000, max: 10000, sorted: true)
      val = Sparse::Spec::Random.rand(Array(UInt8), 1000)
      vec = Sparse::Vector(UInt8, 10000).new idx, val
      idx2, val2 = vec.extract_tuples
      idx2.should be(idx)
      val2.should be(val)
    end
  end
end

M spec/spec_helper.cr => spec/spec_helper.cr +13 -3
@@ 11,6 11,17 @@ module Sparse::Spec::Random
    SOURCE.rand type
  end

  def self.rand(max)
    SOURCE.rand max
  end

  # OPTIMIZE: build in O(n) when specs get slow. This is simple for now.
  def self.rand(klass : Array(T).class, size, max = T::MAX, sorted = false) : Array(T) forall T
    arr = Array.new(size) { rand T.new(max) }
    arr.sort! if sorted
    arr
  end

  ::Spec.before_suite do
    puts "Running with random seed: #{SEED}"
  end


@@ 18,9 29,8 @@ end

struct Sparse::Vector(T, N)
  def self.random(nvals = 1000)
    # OPTIMIZE: build in O(n) specs get slow. This is simple for now.
    idx = Array.new(nvals) { Sparse::Spec::Random.rand UInt64 }.sort!
    val = Array.new(nvals) { Sparse::Spec::Random.rand T }
    idx = Spec::Random.rand(Array(UInt64), nvals, max: N, sorted: true)
    val = Spec::Random.rand(Array(T), nvals)
    new idx, val
  end
end

M src/sparse/vector.cr => src/sparse/vector.cr +2 -2
@@ 113,9 113,9 @@ module Sparse
    end

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

    # Extract all elements with values assigned.