Created
April 23, 2018 07:23
-
-
Save MartinBrugnara/14b585f361c7f1835f92c8fdc65b7ed3 to your computer and use it in GitHub Desktop.
fixing_matrix.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# matrix.rb | |
# MB version (heavily broken) | |
# class MatrixArgumentError < ArgumentError; end | |
# class MatrixRuntimeError < RuntimeError; end | |
class Matrix | |
def initialize(rows, cols = nil, value = 0) | |
@rows = rows | |
cols = rows if not cols | |
@cols = cols | |
if not @rows.is_a?(Integer) then | |
raise ArgumentError, "Rows must be int" | |
end | |
raise ArgumentError, "Cols must be int" if not @cols.is_a?(Integer) | |
raise ArgumentError, "Value must be Numeric" if not value.is_a?(Numeric) | |
@matrix = Array.new(@rows * @cols) {value} | |
if block_given? then | |
for i in 0...@rows | |
for j in 0...@cols | |
val = yield(i, j) | |
raise ArgumentError, "Value must be Numeric" if not val.is_a?(Numeric) | |
self[i, j] = val | |
end | |
end | |
end | |
end | |
attr_reader :rows, :cols, :matrix | |
protected :matrix | |
# Returns an n-by-n identity matrix with ones on the main diagonal | |
# and zeros elsewhere | |
def Matrix.eye(n) | |
return Matrix.new(n) {|i, j| i == j ? 1 : 0 } | |
end | |
# Returns an r-by-c matrix with random Integers | |
def Matrix.rand(r, c = nil, range = (0.0)..(1.0)) | |
return Matrix.new(r, c) {Random.rand(range)} | |
end | |
# --------------------------------------------------------------------------- | |
# Access operations | |
# Provide reading access | |
# Example: my_matrix[row, col] | |
def [](i, j) | |
raise ArgumentError, "i must be Integer" if not i.is_a? Integer | |
raise ArgumentError, "j must be Integer" if not j.is_a? Integer | |
if i >= @rows || j >= @cols || i < 0 || j < 0 | |
raise RuntimeError, "[#{i}, #{j}] OutOfBound [#{@rows-1}, #{@cols-1}]" | |
end | |
return @matrix[i * @cols + j] | |
end | |
# Provide writing cap. | |
# Example: my_matrix[row, col] = 42 | |
def []=(i, j, value) | |
raise ArgumentError, "value must be Numeric" if not value.is_a? Numeric | |
raise ArgumentError, "i must be Integer" if not i.is_a? Integer | |
raise ArgumentError, "j must be Integer" if not j.is_a? Integer | |
if i >= @rows || j >= @cols || i < 0 || j < 0 | |
raise RuntimeError, "[#{i}, #{j}] OutOfBound [#{@rows-1}, #{@cols-1}]" | |
end | |
@matrix[i * @cols + j] = value | |
return | |
end | |
# --------------------------------------------------------------------------- | |
# Math operations | |
# Returns sum of the two matrix as a new matrix. | |
def +(m) | |
raise ArgumentError, "m must be Matrix" if not m.is_a? Matrix | |
raise ArgumentError, "m must have same size" if m.size() != self.size() | |
return Matrix.new(@rows, @cols) {|i,j| self[i,j] + m[i,j]} | |
end | |
# Returns difference of the two matrix as a new matrix. | |
def -(m) | |
raise ArgumentError, "m must be Matrix" if not m.is_a? Matrix | |
raise ArgumentError, "m must have same size" if m.size() != self.size() | |
return Matrix.new(@rows, @cols) {|i,j| self[i,j] - m[i,j]} | |
end | |
# Matrix-by-Matrix product (new matrix returned) | |
# Matrix-by-Scalar product | |
def *(a) | |
if a.is_a? Numeric then | |
return Matrix.new(@rows, @cols) {|i,j| self[i,j] * a} | |
end | |
if not a.is_a? Matrix then | |
raise ArgumentError, "a must be Matrix or Numeric" | |
end | |
raise ArugmentError, "self.cols those not match other.rows" if @cols != a.rows | |
return Matrix.new(@rows, a.cols) { |i,j| | |
r = 0 | |
for k in 0...@cols | |
r += self[i,k] * a[k,j] | |
end | |
r | |
} | |
end | |
# Return transposition as new matrix. | |
def t | |
return Matrix.new(@cols, @rows) {|i,j| self[j,i]} | |
end | |
# --------------------------------------------------------------------------- | |
# Loops support | |
# https://ruby-doc.org/core-2.5.0/Enumerable.html | |
# Enumerable mixin provides map & friends, requires implementation of "each". | |
# Would provide also min, max, and sort but requires "<=>" | |
include Enumerable | |
def each | |
# impl. on matrix | |
#for i in [email protected] | |
# yield @matrix[i] | |
#end | |
for i in 0...@rows | |
for j in 0...@cols | |
yield(self[i,j]) | |
end | |
end | |
return self | |
end | |
# --------------------------------------------------------------------------- | |
# Common methods | |
def size | |
return [@rows, @cols] | |
end | |
def length | |
return @rows * @cols | |
end | |
def ==(o) | |
return false if not o.is_a? Matrix | |
return false if self.size != o.size | |
for i in 0...@rows | |
for j in 0...@cols | |
if self[i,j] != o[i,j] | |
return false | |
end | |
end | |
end | |
return true | |
end | |
def <=>(b) | |
# we must define (inventare) ordering first | |
raise NotImplementedError | |
end | |
# String representation | |
def to_s | |
s = [] | |
for i in 0...@rows | |
for j in 0...@cols | |
s << "#{self[i,j]} " | |
end | |
s << "\n" | |
end | |
return s.join('') | |
end | |
def to_file | |
s = [] | |
s << "#{@rows}" | |
s << "#{@cols}" | |
s += @matrix | |
return s.join(',') | |
end | |
def Matrix.from_file(repr) | |
a = repr.split(',') | |
rows, cols = a[0].chomp.to_i, a[1].chomp.to_i | |
return Matrix.new(rows, cols) {|i,j| | |
val = a[i * cols + j + 2].chomp | |
val.include?(".") ? val.to_f : val.to_i | |
} | |
end | |
end | |
# 1) Syntax errors: ruby fixing_matrix.rb | |
# 2a) New Instance & size() | |
m = Matrix.new(2) | |
raise "Contructor of square matrix || size" unless m.size() == [2,2] | |
m = Matrix.new(3, 4) | |
raise "Contructor of NxM matrix || size" unless m.size() == [3,4] | |
# 2b) New Instance with default value && to_s() | |
m = Matrix.new(2, 2) # default 0 | |
raise "Contructor square w/default const || to_s" unless m.to_s() == "0 0 \n0 0 \n" | |
m = Matrix.new(2, 2, 3) | |
raise "Contructor square w/default const || to_s" unless m.to_s() == "3 3 \n3 3 \n" | |
m = Matrix.new(2, 3, 5) | |
raise "Contructor NxM w/default const || to_s" unless m.to_s() == "5 5 5 \n5 5 5 \n" | |
# 2c) New Instance with value from block | |
m = Matrix.new(2, 2) {|r, c| (r+1)*10 + c+1} | |
raise "Contructor square w/block" unless m.to_s() == "11 12 \n21 22 \n" | |
m = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
raise "Contructor NxM w/block" unless m.to_s() == "11 12 13 \n21 22 23 \n" | |
# 3a) Static constructors eye | |
m = Matrix::eye(3) | |
raise "Contructor ::eye" unless m.to_s() == "1 0 0 \n0 1 0 \n0 0 1 \n" | |
# 3b) Static constructors eye | |
# can not check for actual value, but we can check for very unlikely scenarios: | |
# 1) all 0. 2) Two identical instances | |
m = Matrix::rand(3) | |
raise "Contructor ::random (prob. check)" if m.to_s() == "0 0 0 \n0 0 0 \n0 0 0 \n" | |
# Note we can not use m == m2 because we did not checked yet equality implementation | |
# but we can check their string representation (to_s) - for the purpose of this test. | |
m2 = Matrix::rand(3) | |
raise "Contructor ::random (prob. check)" if m.to_s() == m2.to_s() | |
# lets check dimension and random | |
m = Matrix::rand(1, 2) | |
raise "Contructor ::random (prob. check)" if m.size != [1,2] | |
# TODO: check random values. For now check that runs | |
Matrix::rand(1, 2, 2..5) | |
# Getter | |
m = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
raise "Getter" unless m[0, 0] == 11 # plain access | |
raise "Getter" unless m[0, 1] == 12 # check if swaps cols,rows | |
raise "Getter" unless m[1, 2] == 23 # random access | |
# Setter | |
m = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m[0, 1] = 112 | |
m[1, 2] = 113 | |
raise "Setter" unless m[0, 1] == 112 | |
raise "Setter" unless m[1, 2] == 113 | |
raise "Setter" unless m[0, 1] == 112 # check nothing changed | |
# == | |
m1 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m2 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m3 = Matrix.new(2, 3) {|r, c| r + c} | |
m4 = Matrix.new(2, 4) {|r, c| (r+1)*10 + c+1} | |
raise "== identity" unless m1 == m1 | |
raise "== same values" unless m1 == m2 | |
raise "== same size, diff values" unless m1 != m3 | |
raise "== different size, same subet values" unless m1 != m4 && m4 != m1 | |
raise "== different type" unless m1 != 3 && 5 != m1 | |
# Sum | |
m1 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m2 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m3 = Matrix.new(2, 3) {|r, c| ((r+1)*10 + c+1) * 2} | |
raise "sum" unless (m1 + m2) == m3 | |
# Sub | |
m1 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m2 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m3 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1 - 1} | |
raise "sub" unless (m1 - m2) == Matrix.new(2,3) | |
raise "sub" unless (m1 - Matrix.new(2,3,1)) == m3 | |
# mul scalar | |
m1 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m2 = Matrix.new(2, 3) {|r, c| ((r+1)*10 + c+1) * 3} | |
raise "* scalar" unless (m1 * 3) == m2 | |
# mul matrix (a) | |
m1 = Matrix.new(2, 2) {|r, c| (r+1)*10 + c+1} | |
# [[373, 396], [693, 736]] | |
m1m1 = Matrix.new(2, 2) | |
m1m1[0,0] = 373 | |
m1m1[0,1] = 396 | |
m1m1[1,0] = 693 | |
m1m1[1,1] = 736 | |
raise "* matrix" unless (m1 * m1) == m1m1 | |
# to_file, from_file (int) | |
m1 = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
raise "to_file" unless m1.to_file() == "2,3,11,12,13,21,22,23" | |
m2 = Matrix::from_file(m1.to_file()) | |
raise "::from_file int" unless m1 == m2 | |
# to_file, from_file (float) | |
m1 = Matrix::rand(3,3,(0.0)..(1.0)) | |
m2 = Matrix::from_file(m1.to_file()) | |
raise "::from_file float" unless m1 == m2 | |
# mul matrix (b) | |
m1 = Matrix::from_file("2,3,6,9,4,2,8,2") | |
m2 = Matrix::from_file("3,2,4,5,9,3,3,7") | |
m3 = Matrix::from_file("2,2,117,85,86,48") | |
raise "compatible matrix *" unless (m1 * m2) == m3 | |
# transpose | |
m1 = Matrix::from_file("2,3,6,9,4,2,8,2") | |
m2 = Matrix::from_file("3,2,6,2,9,8,4,2") | |
raise "transpose" unless m1.t() == m2 | |
raise "transpose" unless m1.t().t() == m1 | |
# loop | |
m = Matrix.new(2, 3) {|r, c| (r+1)*10 + c+1} | |
m.each_with_index { |item, index| | |
row = index / m.cols | |
col = index % m.cols | |
raise "loop error" unless m[row, col] == (row+1)*10 + col+1 | |
raise "loop error" unless m[row, col] == item | |
} | |
# size | |
m = Matrix::rand(3) | |
raise "length" unless m.length() == 9 | |
m = Matrix::rand(8) | |
raise "length" unless m.length() == m.cols * m.rows | |
# ------- Test precondition | |
begin | |
Matrix.new(22.1) | |
rescue | |
else | |
raise "[Miss] Constructor, rows" | |
end | |
begin | |
Matrix.new(1, 1.1) | |
rescue | |
else | |
raise "[Miss] Constructor, cols" | |
end | |
begin | |
Matrix.new(1, 2, "bla") | |
rescue | |
else | |
raise "[Miss] Constructor, const" | |
end | |
begin | |
Matrix.new(1, 2) {"sa"} | |
rescue | |
else | |
raise "[Miss] Constructor, block val" | |
end | |
m = Matrix::rand(3, 4) | |
# -- getter | |
begin | |
m[12.1, 2] | |
rescue | |
else | |
raise "[Miss] get, row type" | |
end | |
begin | |
m[2, 2.2] | |
rescue | |
else | |
raise "[Miss] get, col type" | |
end | |
begin | |
m[3, 0] | |
rescue | |
else | |
raise "[Miss] get, row range" | |
end | |
begin | |
m[-3, 0] | |
rescue | |
else | |
raise "[Miss] get, row range" | |
end | |
begin | |
m[0, 4] | |
rescue | |
else | |
raise "[Miss] get, col range" | |
end | |
begin | |
m[0, -4] | |
rescue | |
else | |
raise "[Miss] get, col range" | |
end | |
# -- setter | |
begin | |
m[12.1, 2] = 1 | |
rescue | |
else | |
raise "[Miss] set, row type" | |
end | |
begin | |
m[2, 2.2] = 1 | |
rescue | |
else | |
raise "[Miss] set, col type" | |
end | |
begin | |
m[3, 0] = 1 | |
rescue | |
else | |
raise "[Miss] set, row range" | |
end | |
begin | |
m[-3, 0] = 1 | |
rescue | |
else | |
raise "[Miss] set, row range" | |
end | |
begin | |
m[0, 4] = 1 | |
rescue | |
else | |
raise "[Miss] set, col range" | |
end | |
begin | |
m[0, -4] = 1 | |
rescue | |
else | |
raise "[Miss] set, col range" | |
end | |
m[0, 0] = 2.2 # Must pass | |
begin | |
m[0, 0] = "ciao" # must fail | |
rescue | |
else | |
raise "[Miss] set, col range" | |
end | |
# ---- math | |
begin | |
m + "ciao" | |
rescue | |
else | |
raise "[Miss] sum type" | |
end | |
begin | |
Math.new(2,3) + Math.new(4) | |
rescue | |
else | |
raise "[Miss] sum dimension" | |
end | |
begin | |
m - "ciao" | |
rescue | |
else | |
raise "[Miss] sub type" | |
end | |
begin | |
Math.new(2,3) - Math.new(4) | |
rescue | |
else | |
raise "[Miss] sub dimension" | |
end | |
begin | |
Math.new(2,3) * "f" | |
rescue | |
else | |
raise "[Miss] * type" | |
end | |
begin | |
Math.new(2,3) * Math.new(4) | |
rescue | |
else | |
raise "[Miss] * dimension" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment