Class: MultiMatrix

Inherits:
Object
  • Object
show all
Defined in:
lib/multimatrix.rb

Overview

An n-dimensonal matrix, where dimensions are labels

Imagine: your urgent need is to store a dataset more than 1 or 2 dimensions large. The default way to do this is tu use multi dimensional arrays, like “array[][]” for example. Inserting or retrieveing a value from this dataset would be kind of an easy task, but what happens when You have to summarize all the elements of a dimension. For example sum all the elements where the second level array’s index is 2. You have to loop through n*n*n elements, just to find some of them. Tedious.

MultiMatrix comes to the rescue.

MultiMatrix makes it easy to create multi-level data structures, and makes finding and looping through them nice and easy. And fast.

To easily show an example, and concept of this class let’s see a 2 dimensional array

m = MultiMatrix.new(:x, :y) m.insert(:x => 1, :y = 3) = 10 m.insert(:x => 2, :y = 1) = 20 m.insert(:x => 2, :y =>2) = 30

could be imagined as

  x| 1 | 2 | 3 |
 y |___|___|___|
 1 | ? | 20| ? |
   |___|___|___|
 2 | ? | 30| ? |
   |___|___|___|
 3 | 10| ? | ? |
   |___|___|___|

>> m.sum(x: 2)
=> 50
>> m.sum(x: 3)
=> 0
>> m.to_a(y: 2)
=> [30]
>> m[:x => 1, :y => 1]
=> nil
>> m[:x => 3] == MultiMatrix.new(:y)
=> true

Usage:

>> x = MultiMatrix.new(:x, :y, :z)
>> x[:x => 1, :y => 1, :z => 1] = 1
>> x[:x => 1, :y => 1, :z => 2] = 1
>> x[:x => 2, :y => 1, :z => 2] = 1

>> x[:x => 1, :y => 1, :z => 1]
=> 1

>> x[:x => 1, :y => 1]
=> MultiMatrix({:z=>2}=>2)

>> x.sum(:x => 1)
=> 2
>> x.sum(:z => 1)
=> 1
>> x.to_a(:z => 1)
=> [1]

Wait! There is more:

>> x[:z => [1,2]]
=> [1,1,1]
>> x.sum(:z => [1,2])
=> 3

Author:

Instance Method Summary collapse

Constructor Details

#initialize(*labelskeys) ⇒ MultiMatrix

Creates a new MultiMatrix object, where labels will be the dimensions of the dataset use labels appropriate for Hash keys, because they will be stored as such internally. labels could be Symbols, Integers, even ActiveModel objects, the only requirement for it that it have to have a meaningful == method.

Parameters:

  • labels

    an [Array] of values to be used as dimension labels

Raises:



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/multimatrix.rb', line 82

def initialize(*labelskeys)
  raise MultiMatrixException, "no dimensions" if labelskeys.length == 0
  @labels = labelskeys
  @dim = @labels.length
  @values = Hash.new
  @values_by_id = Hash.new
  @labels_by_id = Hash.new
  @sumcache = {}
  @seq = 0
  @finderhash = {}
end

Instance Method Details

#==(other) ⇒ Object

NMatrices are equal if they have the same labels, label values, and all their respective values are equal

Parameters:



223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/multimatrix.rb', line 223

def ==(other)
  [[self, other],[other,self]].each do |x|
    x.first.keys do |k|
      if !x.last.key?(k)
        return false
      end
      if x.last[k] != x.first[k]
        return false
      end
    end
  end
  return true
end

#each(labels = {}) ⇒ Object

Executes block on all value at position given by labels

Parameters:

  • labels (defaults to: {})

    finder hash



121
122
123
124
125
126
127
128
129
130
# File 'lib/multimatrix.rb', line 121

def each(labels = {})
  if check_labels(labels) and labels.keys.length <= @dim
    ids = find_ids(labels)
    ids.each do |id|
      if block_given?
        yield @values_by_id[id]
      end
    end
  end
end

#inject(labels = {}, default = 0) ⇒ Object

Same as Enumerable#inject, but filter hash is accepted

Parameters:

  • labels (defaults to: {})

    finder hash

  • default (defaults to: 0)

    beginning value of the memo



135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/multimatrix.rb', line 135

def inject(labels = {}, default = 0)
  memo = default
  if check_labels(labels) and labels.keys.length <= @dim
    ids = find_ids(labels)
    ids.each do |id|
      if block_given?
        memo = yield memo, @values_by_id[id]
      end
    end
  end
  return memo
end

#insert(labels, value) ⇒ true Also known as: []=

Inserts a value to the data set.

Parameters:

  • labels (Hash)

    the position where to insert the value. beware, at insert all dimension of the positin must be given

  • value

    The value to be inserted

Returns:

  • (true)

    on success, raises MultiMatrixException otherwise



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/multimatrix.rb', line 100

def insert(labels, value)
  if check_labels(labels) and labels.keys.length == @dim
    if @values.key?(labels)
      id = @values[labels]
      if block_given?
        save_value(labels, yield(get_value(labels), value), :override => id)
      else
        save_value(labels, get_value(labels) + value, :override => id)
      end
    else
      save_value(labels, value)
    end
  else
    raise MultiMatrixException, 'not enough key supplied'
  end
  @sumcache = {}
  true
end

#keysObject

Returns labels in MultiMatrix.

Returns:

  • labels in MultiMatrix



238
239
240
# File 'lib/multimatrix.rb', line 238

def keys
  @values.keys
end

#retrieve(labels = {}) ⇒ Object Also known as: []

Retrieves a value If every dimension is given, retrieve just a value at the specified position, if not, retrieves an MultiMatrix of the matching elements, leaving the fixed dimensions behind

Parameters:

  • labels (defaults to: {})

    finder hash

Returns:

  • value/MultiMatrix



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/multimatrix.rb', line 185

def retrieve(labels = {})
  if check_labels(labels)
    if labels.keys.length < @dim or labels.values.find_all {|val| val.class == Array}.length > 0
      tmp = MultiMatrix.new(*(@labels - labels.keys.find_all {|val| labels[val].class != Array}))
      ids = find_ids(labels)
      ids.each do |id|
        hash = @labels_by_id[id]
        labels.keys.find_all {|val| labels[val].class != Array}.each do |key|
          hash.delete(key)
        end
        tmp[hash] = @values_by_id[id]
      end
      return tmp
    elsif labels.keys.length == @dim
      return get_value(labels)
    end
  end
end

#sum(labels = {}, default = 0) ⇒ Object

Summarizes all elements matching labels a little faster than using MultiMatrix#inject(labels) {|a,b| a+b} because of two factors:

  • if every dimension is given, sum return only one element without looping through the data structure

  • sum is cached so if sum is called twice with the same selector hash, the result is given back instantly

Parameters:

  • labels (defaults to: {})

    finder hash

  • default (defaults to: 0)

    default value of sum



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/multimatrix.rb', line 164

def sum(labels = {}, default = 0)
  if check_labels(labels)
    if labels.keys.length == @dim and labels.values.find_all {|val| val.class.to_s == 'Array'}.empty?
      return get_value(labels)
    end
    if @sumcache.key?(labels)
      return @sumcache[labels]
    end
    vals = find_ids(labels).map {|id| @values_by_id[id]}
    ret = default
    vals.each do |v| ret +=v end
    @sumcache[labels] = ret
    ret
  end
end

#to_a(labels = {}) ⇒ Array

Returns an array of values matching @param labels

Parameters:

  • labels (defaults to: {})

    finder hash

Returns:

  • (Array)

    values found



151
152
153
154
155
156
# File 'lib/multimatrix.rb', line 151

def to_a(labels = {})
  if check_labels(labels)
    ids=find_ids(labels)
    return ids.map{|id| @values_by_id[id]}
  end
end

#to_sObject

Returns String representation of an MultiMatrix - shown as a Hash of label => value pairs.

Returns:

  • String representation of an MultiMatrix - shown as a Hash of label => value pairs



243
244
245
246
247
248
249
# File 'lib/multimatrix.rb', line 243

def to_s
  hash = {}
  @values.each_pair do |k,v|
    hash[k] = @values_by_id[v]
  end
  hash.to_s
end

#values_for_label(needle, rest = {}) ⇒ Object

return the possible values of a dimension where values match the labels given in [rest]

Parameters:

  • needle

    a key of the labels hash

  • rest (defaults to: {})

    a selector hash

Raises:



208
209
210
211
212
213
# File 'lib/multimatrix.rb', line 208

def values_for_label(needle, rest = {})
  raise MultiMatrixException, 'labels don\'t match: '+rest.keys.join(",") unless check_labels(rest)
  raise MultiMatrixException, 'label does not exist' unless check_labels({needle => nil})
  ids = find_ids(rest)
  ids.map{|id| @labels_by_id[id][needle]}
end