Browse | Submit A New Snippet | Create A Package

 

extendible multidimensional array with compact storage

Type:
Full Script
Category:
Math Functions
License:
GNU General Public License
Language:
Ruby
 
Description:
This is an early version of a class for multidimensional arrays. New element-types and coercions can be added later. Elementwise operations (such as "+" and "-") are very slow at the moment.

Versions Of This Snippet::

Jan Wedekind
Snippet ID Download Version Date Posted Author Delete
2290.12007-06-14 19:35Jan Wedekind

Download a raw-text version of this code by clicking on "Download Version"

 


Latest Snippet Version: :0.1

require 'complex'

class Sequence

  @@types = []
  # @@conversions = Hash.new( proc { |x| x } )
  @@coercions = Hash.new

  Type = Struct.new( :name, :type, :size, :default, :pack, :unpack )
  class Type
    def inspect
      name
    end
  end

  def Sequence.register_type( symbol, type, size, default, pack, unpack )
    type = Type.new( symbol.to_s, type, size, default, pack, unpack )
    eval "#{symbol.to_s} = type"
    @@coercions[ [ type, type ] ] = type
  end

  register_type( :BYTE, Fixnum, 1, 0, proc { |o| [o].pack("c") },
                proc { |s| s.unpack("c")[0] } )
  register_type( :UBYTE, Fixnum, 1, 0, proc { |o| [o].pack("C") },
                proc { |s| s.unpack("C")[0] } )
  register_type( :USINT, Fixnum, 2, 0, proc { |o| [o].pack("S") },
                proc { |s| s.unpack("S")[0] } )
  register_type( :ULINT, Fixnum, 4, 0, proc { |o| [o].pack("L") },
                proc { |s| s.unpack("L")[0] } )
  register_type( :SINT, Fixnum, 2, 0, proc { |o| [o].pack("s") },
                proc { |s| s.unpack("s")[0] } )
  register_type( :LINT, Fixnum, 4, 0, proc { |o| [o].pack("l") },
                proc { |s| s.unpack("l")[0] } )
  register_type( :SFLOAT, Float, 4, 0.0, proc { |o| [o].pack("f") },
                proc { |s| s.unpack("f")[0] } )
  register_type( :DFLOAT, Float, 8, 0.0, proc { |o| [o].pack("d") },
                proc { |s| s.unpack("d")[0] } )
  register_type( :SCOMPLEX, Complex, 8, Complex( 0.0, 0.0 ),
                proc { |o| [ o.real, o.imag ].pack( "ff" ) },
                proc { |s| Complex( *s.unpack( "ff" ) ) } )
  register_type( :DCOMPLEX, Complex, 16, Complex( 0.0, 0.0 ),
                proc { |o| [ o.real, o.imag ].pack( "dd" ) },
                proc { |s| Complex( *s.unpack( "dd" ) ) } )
  register_type( :OBJECT, Object, 1, nil, proc { |o| [o] },
                proc { |s| s[0] } )

  # def Sequence.register_conversion( from, to, operation )
  #   @@conversions[ [ from, to ] ] = operation
  # end

  def Sequence.register_coercion( result, type1, *type2 )
    type2.each { |t2|
      @@coercions[ [ type1, t2 ] ] = result
      @@coercions[ [ t2, type1 ] ] = result
    }
  end

  register_coercion( USINT, USINT, BYTE, UBYTE )
  register_coercion( ULINT, ULINT, USINT, BYTE, UBYTE )
  register_coercion( SINT, SINT, USINT, BYTE, UBYTE )
  register_coercion( LINT, LINT, ULINT, SINT, USINT, BYTE, UBYTE )
  register_coercion( SFLOAT, SFLOAT, LINT, ULINT, SINT, USINT, BYTE, UBYTE )
  register_coercion( DFLOAT, DFLOAT, SFLOAT, LINT, ULINT, SINT, USINT, BYTE,
                    UBYTE )
  register_coercion( SCOMPLEX, SCOMPLEX, SFLOAT, LINT, ULINT, SINT, USINT,
                    BYTE, UBYTE )
  register_coercion( DCOMPLEX, SCOMPLEX, DFLOAT )
  register_coercion( DCOMPLEX, DCOMPLEX, SCOMPLEX, DFLOAT, SFLOAT, LINT,
                    ULINT, SINT, USINT, BYTE, UBYTE )
  register_coercion( OBJECT, OBJECT, DCOMPLEX, SCOMPLEX, DFLOAT, SFLOAT, LINT,
                    ULINT, SINT, USINT, BYTE, UBYTE )

  def Sequence.import( type, data )
    retval = Sequence.new( type )
    retval.import( data )
  end

  def initialize( type = OBJECT, n = 0, value = nil )
    @type = type
    @data = @type.pack.call( value == nil ? @type.default : value ) * n
  end

  def import( data )
    @data = data
    self
  end

  def inspect
    retval = "["
    i = 0
    while i < size
      v = self[i].inspect
      i += 1
      v += ", " if i < size
      if retval.size + v.size >= 74 - "...]".size
        retval += "..."
        break
      else
        retval += v
      end
    end
    retval = "Sequence." + @type.inspect.downcase + "(" + size.inspect +
      "):\n" + retval + "]"
  end

  def to_s
    @data.to_s
  end

  def dup
    retval = Sequence.new( @type )
    retval.data = @data.dup
    retval
  end

  def coerce( other )
    if other.kind_of?( Sequence )
      target = @@coercions[ [ other.typecode, typecode ] ]
      if target == nil
        raise "Don't now how to coerce Sequence of type " +
          typecode.inspect + " and type " + other.typecode.inspect
      else
        return other.to_type( target ), to_type( target )
      end
    elsif other.kind_of?( Array )
      return other, to_a
    else
      raise "Don't now how to coerce Sequence and " + other.class.inspect
    end
  end

  def each
    for i in 0...size
      yield self[i]
    end
    self
  end

  def collect( type = OBJECT )
    retval = Sequence.new( type, size )
    for i in 0...size
      retval[i] = yield self[i]
    end
    retval
  end

  def to_a
    retval = [ nil ] * size
    for i in 0...size
      retval[i] = self[i]
    end
    retval
  end

  def zip( *args )
    arrs = [ self, *args ]
    n = ( arrs.collect { |arr| arr.size } ).max
    retval = Sequence.new( OBJECT, n )
    ( 0...n ).each { |i| retval[i] = arrs.collect { |arr| arr[i] } }
    retval
  end

  def +( other )
    if other.kind_of?( Array ) or other.typecode != typecode
      y, x = coerce( other )
      retval = x + y
    else
      retval = Sequence.new( @type )
      retval.data = @data + other.data
    end
    retval
  end

  def *( n )
    if n.kind_of?( String )
      join( n )
    else
      retval = Sequence.new( @type )
      retval.data = @data * n
      retval
    end
  end

  def join( sep )
    to_a.join( sep )
  end

  def ==( other )
    if other.kind_of?( Sequence )
      if other.typecode == typecode
        other.data == data
      else
        false
      end
    else
      false
    end
  end

  def ===( other )
    self == other
  end

  def eql?( other )
    if other.kind_of?( Sequence )
      other.data.eql?( @data )
    else
      false
    end
  end

  def hash
    @data.hash
  end

  def clear
    @data = @type.default * 0
  end

  def to_type( type )
    if type == @type
      self
    else
      retval = Sequence.new( type, size )
      # conversion = @@conversions[ [ @type.type, type.type ] ]
      for i in 0...size
        # retval[i] = conversion.call( self[i] )
        retval[i] =self[i]
      end
      retval
    end
  end

  def element_size
    @type.size
  end

  def size
    @data.size / element_size
  end

  def typecode
    @type
  end

  def fill( value )
    @data = @type.pack.call( value ) * size
    self
  end

  def <<( *arr )
    push( *arr )
  end

  def push( *arr )
    arr.each { |o| @data += @type.pack.call( o ) }
    self
  end

  def at( i )
    pos = i * @type.size
    @type.unpack.call( @data[ pos...( pos + @type.size ) ] )
  end

  def slice( *i )
    if i.size > 1
      i.collect { |idx| at( idx ) }
    else
      if i[0].kind_of?( Range )
        i[0].collect { |idx| at( idx ) }
      else
        at( i[0] )
      end
    end
  end

  def []( *i )
    slice( *i )
  end

  def []=( i, o )
    pos = i * @type.size
    @data[ pos...( pos + @type.size ) ] = @type.pack.call( o )
    o
  end

  protected

  def data=( s )
    @data = s
  end

  def data
    @data
  end

end



class MultiArray

  BYTE      = Sequence::BYTE
  UBYTE     = Sequence::UBYTE
  USINT     = Sequence::USINT
  ULINT     = Sequence::ULINT
  SINT      = Sequence::SINT
  LINT      = Sequence::LINT
  SFLOAT    = Sequence::SFLOAT
  DFLOAT    = Sequence::DFLOAT
  SCOMPLEX  = Sequence::SCOMPLEX
  DCOMPLEX  = Sequence::DCOMPLEX
  OBJECT    = Sequence::OBJECT

  def MultiArray.import( type, data, *shape )
    retval = MultiArray.new( type )
    retval.import( data, *shape )
  end

  def initialize( type = OBJECT, *shape )
    @shape = shape
    @strides, size = compute_strides
    @data = Sequence.new( type, size )
  end

  def compute_strides
    strides = []
    size = @shape.inject( 1 ) { |stride,dim|
      if dim <= 0
        raise "All dimensions must be greater than zero (was #{dim})"
      end
      strides.push( stride ); stride * dim
    }
    return strides, size
  end

  def import( data, *shape )
    @shape = *shape
    @strides, size = compute_strides
    @data.import( data )
    self
  end

  def inspect
    "MultiArray." + @data.typecode.inspect.downcase + "(" +
      @shape.join(",") + "):\n" + print( @shape.size - 1, 0, 10 )[1]
  end

  def dup
    retval = MultiArray.new
    retval.shape = @shape.dup
    retval.strides = @strides.dup
    retval.data = @data.dup
    retval
  end

  def to_s
    @data.to_s
  end

  def +@
    self
  end

  def -@
    collect( typecode ) { |x| -x }
  end

  def +( other )
    if other.kind_of?( MultiArray )
      if other.typecode != typecode
        y, x = coerce( other )
      else
        y, x = other, self
      end
      retval = MultiArray.new( typecode, *shape )
      ( 0...size ).each { |i| retval.data[i] = data[i] + other.data[i] }
    else
      retval = collect( typecode ) { |x| x + other }
    end
    retval
  end

  def -( other )
    if other.kind_of?( MultiArray )
      if other.typecode != typecode
        y, x = coerce( other )
      else
        y, x = other, self
      end
      retval = MultiArray.new( typecode, *shape )
      ( 0...size ).each { |i| retval.data[i] = data[i] - other.data[i] }
    else
      retval = collect( typecode ) { |x| x - other }
    end
    retval
  end

  def /( other )
    if other.kind_of?( MultiArray )
      if other.typecode != typecode
        y, x = coerce( other )
      else
        y, x = other, self
      end
      retval = MultiArray.new( typecode, *shape )
      ( 0...size ).each { |i| retval.data[i] = data[i] / other.data[i] }
    else
      retval = collect( typecode ) { |x| x / other }
    end
    retval
  end

  def *( other )
    if other.kind_of?( MultiArray )
      if other.typecode != typecode
        y, x = coerce( other )
      else
        y, x = other, self
      end
      retval = MultiArray.new( typecode, *shape )
      ( 0...size ).each { |i| retval.data[i] = data[i] * other.data[i] }
    else
      retval = collect( typecode ) { |x| x * other }
    end
    retval
  end

  def []=( *parameters )
    @data[ pos( *(parameters[0...-1] ) ) ] = parameters.last
  end

  def []( *indices )
    @data[ pos( *indices ) ]
  end

  def collect( type = OBJECT, &action )
    retval = MultiArray.new
    retval.shape = @shape.dup
    retval.strides = @strides.dup
    retval.data = @data.collect( type, &action )
    retval
  end

  def zip( *args )
    arrs = [ self, *args ]
    # resizing !!!
    retval = MultiArray.new( OBJECT, *arrs[0].shape )
    ( 0...size ).each { |i|
      retval.data[i] = arrs.collect { |arr| arr.data[i] }
    }
    retval
  end

  def to_type( type )
    retval = MultiArray.new
    retval.shape = @shape.dup
    retval.strides = @strides.dup
    retval.data = @data.to_type( type )
    retval
  end

  def fill( value )
    @data.fill( value )
    self
  end

  def rank
    @shape.size
  end

  def size
    @data.size
  end

  def shape
    @shape
  end

  def typecode
    @data.typecode
  end

  protected

  def shape=( shape )
    @shape = shape
  end

  def strides
    @strides
  end

  def strides=( strides )
    @strides = strides
  end

  def data
    @data
  end

  def data=( data )
    @data = data
  end

  def pos( *indices )
    if indices.size != @shape.size
      raise "MultiArray has #{dimensions.size} indices (not #{indices.size})"
    end
    stride = @data.size
    retval = 0
    for i in 0...indices.size
      if not (0...@shape[i]).member?( indices[i] )
        raise "Index #{i} must be in #{0...@shape[i]} (was #{indices[i]})" 
      end
      retval += indices[i] * @strides[i]
    end
    retval
  end

  def print( level, skip, lines )
    if @shape.size == 0
      s = @data[0].inspect
    else
      stride = @strides[ level ]
      if level == 0
        s = "[ "
        for i in 0...@shape[ level ]
          s += ", " if i > 0
          v = @data[ skip + i ].inspect
          if s.size + v.size >= 74 - "...".size - ", ".size - "[  ]".size * ( @shape.size - level - 1 )
            s += "..."
            break
          else
            s += v
          end
        end
        lines -= 1
        s += " ]"
      else
        s = "[ "
        for i in 0...@shape[ level ]
          if lines > 0
            p = print( level - 1, skip + i * stride, lines )
            s += ",\n  " + "  " * ( @shape.size - level - 1 ) if i > 0
            s += p[1]
            lines = p[0]
          else
            if lines == 0
              if i > 0
                s += ",\n..."
              else
                s += "\n..."
              end
              lines = -1
            end
          end
        end
        s += " ]" if lines >= 0
      end
      [ lines, s ]
    end
  end

end

		

Submit a new version

You can submit a new version of this snippet if you have modified it and you feel it is appropriate to share with others..