~kb/sparse

602e043a5ffbd4e47b925ab4058502e36e8322c5 — Kim Burgess 3 years ago 0514239
Initial Monoid, Semiring, Vector types
4 files changed, 171 insertions(+), 0 deletions(-)

M src/sparse.cr
A src/sparse/monoid.cr
A src/sparse/semiring.cr
A src/sparse/vector.cr
M src/sparse.cr => src/sparse.cr +2 -0
@@ 1,3 1,5 @@
require "./sparse/*"

module Sparse
  VERSION = `shards version`
end

A src/sparse/monoid.cr => src/sparse/monoid.cr +23 -0
@@ 0,0 1,23 @@
module Sparse
  module Monoid(T)
    # The binary operator that provides an assocatiate combination.
    #
    # ```
    # add(a, b) == add(b, a)
    # ```
    #
    # The above most hold in the mathematical sense as ordering of this may be
    # changed. Non associatively due to floating point precessicion erros may be
    # ignored in this context.
    @[AlwaysInline]
    abstract def add(a : T, b : T) : T

    # Identity element.
    #
    # ```
    # add(a, id) == add(id, a) == a
    # ```
    @[AlwaysInline]
    abstract def id : T
  end
end

A src/sparse/semiring.cr => src/sparse/semiring.cr +13 -0
@@ 0,0 1,13 @@
require "./monoid"

module Sparse
  module Semiring(T, U, V)
    macro extended
      extend Monoid(T)
    end

    # Multiplicative binary operator.
    @[AlwaysInline]
    abstract def mul(a : U,  b : V) : T
  end
end

A src/sparse/vector.cr => src/sparse/vector.cr +133 -0
@@ 0,0 1,133 @@
require "set"

module Sparse
  struct Vector(T, N)
    @idx : Array(UInt64)
    @val : Array(T)

    # Creates a new `Vector` with *initial_capacity* pre-allocated for elements.
    def initialize(initial_capacity : Int = 0)
      @idx = typeof(@idx).new initial_capacity
      @val = typeof(@val).new initial_capacity
    end

    private def inititialize(@idx, @val)
    end

    # Creates a new `Vector` with the same domain, size and contents.
    def dup : Vector(T, N)
      idx = @idx.dup
      val = @val.dup
      Vector(T, N).new idx, val
    end

    # Change the size of an existing vector.
    #
    # NOTE: this will mutate the internal state of the passed *vec*, but return
    # this encased in a new `Vector` with the new dimensions encoded within the
    # type. Any references to the passed source vector should be discarded.
    macro resize!(vec, size)
      begin
        %idx, %val = {{vec}}.extract_tuples
        %top = %idx[-1]?
        if %top && %top >= {{size}}
          %del = %idx.bsearch_index { |i| i >= {{size}} }
          %idx.delete_at %del, %idx.size
          %val.delete_at %val, %cal.size
        end
        Vector(typeof({{vec}}[0]), {{size}}).new %idx, %val
      end
    end

    # Removes all elements.
    def clear : self
      @idx.clear
      @val.clear
      self
    end

    # The dimensionality this vector represents.
    def size : UInt64
      N
    end

    # The number of stored elements.
    def nvals : Int
      @idx.size
    end

    # Store the passed elements in *self*.
    #
    # If duplicate values exists for the same index, the result of callig *dup*
    # with the previes and new values will be stored.
    def build(indicies : Indexable(UInt64), 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
        if idx.in? seen
          old = unsafe_fetch(idx).not_nil!
          val = dup.call old, val
          set_element idx, val if old != val
        else
          set_element idx, val
        end
        seen << idx
      end
    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 @idx[pos] == idx
        @val[pos] = value
      else
        @idx.insert pos index
        @val.insert pos value
      end
    end

    # Remove one stored element.
    def remove_element(index : Int) : T?
      check_index_out_of_bounds index
      pos = @idx.bsearch_index &.==(index)
      if del
        @idx.delete_at pos
        @val.delete_at pos
      end
    end

    # Extract one element of *self* into a scalar.
    def extract_element(index : Int) : T?
      check_index_out_of_bounds index
      pos = @idx.bsearch_index &.==(index)
      if pos
        @val.unsafe_fetch pos if pos
      end
    end

    # Extract all elements with values assigned.
    def extract_tuples : { Indexable(Idx), Indexable(T) }
      { @idx, @val }
    end

    @[AlwaysInline]
    private def unsafe_fetch(index : Int) : T?
      pos = @idx.bsearch_index &.==(index)
      @val.unsafe_fetch pos if pos
    end

    private def check_index_out_of_bounds(index)
      check_index_out_of_bound { raise IndexError.new "Invalid index (#{index})" }
    end

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