From: Dan Fitzpatrick Date: 2009-08-21T12:36:17+09:00 Subject: [ANN] Natrix - The Named Matrix Natrix is a class for making named matrix structures (more for text than math). It was designed to take rows of data and put the value of each row in a cell in the matrix. In other words, the row contains an x, y, and a value. It also supports very basic import and export of delimited text and output of JSON (array of arrays) and (array of hashes). It has a strict mode so it can raise an error if you try to set a cell that does not exist. Feedback is welcome. # Natrix is a named matrix # t = Natrix.new( # :xs => %w(a b c d), # :ys => %w(m n o p) # ) # t.set('a','p',20) # t.get('a','p') => 20 # t.get(0,3) => 20 class Natrix include Enumerable # X Axis attr_accessor :xs # Y Axis attr_accessor :ys # Data attr_accessor :d # Mode (open|strict) # Strict ensures that you can not set() or get() if the keys do not exist. attr_accessor :mode def initialize(a={},&b) @mode = 'open' @d = Hash.new @xs = Array.new @ys = Array.new a.each{|k,v| send("#{k}=",v)} yield self if b end # Make the keys strings def xs=(*args) @xs = args.flatten.map{|a| a.to_s} end def ys=(*args) @ys = args.flatten.map{|a| a.to_s} end # The x key def xk(key) return key.to_s if String === key or Symbol === key xs[key] # integer index end # The y key def yk(key) return key.to_s if String === key or Symbol === key ys[key] # integer index end def validate_input(x,y) # Validate input if strict mode if mode.to_s == 'strict' raise ArgumentError, "No X axis with a value of #{x.inspect}" unless xs.include?(xk(x)) raise ArgumentError, "No Y axis with a value of #{y.inspect}" unless ys.include?(yk(y)) end end # Set a value def set(x,y,val) # Validate input if strict mode if mode.to_s == 'strict' validate_input(x,y) end d[xk(x)] ||= Hash.new d[xk(x)][yk(y)] = val end # Get a value def get(x,y) # Validate input if strict mode if mode.to_s == 'strict' validate_input(x,y) end d[xk(x)][yk(y)] rescue nil end # Each returns all the values x0y0, x1y0, x2y0, x0y1, x1y1, x2y1... def each(&b) ys.each{|y| xs.each{|x| begin yield d[x][y] rescue yield nil end } } end # Each X yields an array of values for an x column def each_x(x,&b) ys.each{|y| begin yield d[xk(x)][y] rescue yield nil end } end # Each Y yields all the values for a y row def each_y(y,&b) xs.each{|x| begin yield d[x][yk(y)] rescue yield nil end } end # Output delimted text def to_delimited(x_delimiter="\t",y_delimiter="\n") ys.map{|y| xs.map{|x| d[x][y] rescue nil }.join(x_delimiter) }.join(y_delimiter) end # Build data from delimited text def from_delimited(data,x_delimiter="\t",y_delimiter="\n") data.split(y_delimiter).each_with_index{|line,y| line.split(x_delimiter).each_with_index{|cell,x| begin d[xs[x]][ys[y]] = cell rescue d[xs[x]] ||= Hash.new d[xs[x]][ys[y]] = cell end } } end # Output a json array of hashes (You need to require json for this to work) def to_json_ah "[#{ys.map{|y| "{#{xs.map{|x| "#{x.to_json}: #{(d[x][y] rescue nil).to_json}" }.join(",")}}" }.join(",")}]" end # Output a json array of arrays (You need to require json for this to work) def to_json_aa "[#{ys.map{|y| "[#{xs.map{|x| (d[x][y] rescue nil).to_json }.join(",")}]" }.join(",")}]" end end