Skip to content

Instantly share code, notes, and snippets.

@envp
Last active December 23, 2015 06:38
Show Gist options
  • Select an option

  • Save envp/9f704ca9cf9307c9c89b to your computer and use it in GitHub Desktop.

Select an option

Save envp/9f704ca9cf9307c9c89b to your computer and use it in GitHub Desktop.
A ridiculously simplified linear equation parser + sovler for systems of them, whipped up at work
require 'matrix'
module NumericUnicodeSubscripts
UNICODE_SUBSCRIPTS_0_TO_9 = [
"\u2080",
"\u2081",
"\u2082",
"\u2083",
"\u2084",
"\u2085",
"\u2086",
"\u2087",
"\u2088",
"\u2089"
]
def self.to_subscript(n)
n.to_s.
split('').
map {|ns| UNICODE_SUBSCRIPTS_0_TO_9[ns.to_i].encode('utf-8')}.
join
end
end
module LinearEquationParser
# Given a linear equation represented as a string
# Parse it into a Hash
OPERATOR = /[+=-]/
COEFFT = /\d*[.]?\d+/
VARIABLE = /[A-Za-z]+/
RHS = /=\s*(\d*[.]?\d+)/
OPERATOR_MAP = {:'+' => :add, :'-' => :sub, :'=' => :eql}
def self.parse(string)
tokens = []
str = string.dup
str.gsub!(/\s+/, '')
ops = str.scan(OPERATOR).map {|o| OPERATOR_MAP[o.to_sym]}
rhs = str.scan(RHS).flatten.first.to_f
vars = []
coeffts = []
# The final 'operation' in an equation must be
# an assertion of equality
fail unless ops.last == :eql
str.split(OPERATOR).each do |q|
next if q.scan(VARIABLE).empty?
if q.scan(VARIABLE)
vars << q.scan(VARIABLE).first.to_sym
if q.scan(COEFFT).empty?
coeffts << 1.0
else
coeffts << q.scan(COEFFT).first.to_f
end
end
end
signs = ops[0..-2]
# Apply signs to coefficients so as to define all ops as :add
signs.each_with_index do |s, i|
coeffts[i + 1] *= -1 if s == :sub
end
{
:coefficients => coeffts,
:symbols => vars,
:constant => rhs
}
end
end
class LinearEquation
include NumericUnicodeSubscripts
include LinearEquationParser
attr_accessor :coefficients, :constant, :variables
attr_reader :operations
def initialize
@coefficients = nil
@constant = nil
@variables = nil
end
def self.new_from_values(coefficients, constant, vars=nil)
obj = new
obj.coefficients = Matrix.row_vector(coefficients)
obj.constant = constant
if vars.nil?
obj.variables = Array.new(obj.coefficients.column_count) do |i|
"X#{NumericUnicodeSubscripts.to_subscript(i)}"
end
else
obj.variables = vars
end
obj
end
def self.new_from_s(string)
parsed = LinearEquationParser.parse(string)
obj = new
obj.coefficients = parsed[:coefficients].
obj.variables = parsed[:symbols]
obj.constant = parsed[:constant]
obj
end
def to_s
signs = @coefficients.map do |c|
:'+' if c.to_s.to_r >= 0.to_r
:'-' if c.to_s.to_r < 0.to_r
end
signs[0] = '' if signs.first == :'+'
signs.
zip(@coefficients, @variables).
map {|s, c, v| "#{s}#{c}#{v}"}.
join
end
end
class LinearSystem
attr_accessor :coefficients, :constant
def initialize(*lines)
matrices = lines.map {|line| line.coefficients}
@coefficients = Matrix.vstack(*matrices)
@constant = Matrix.column_vector(lines.map {|line| line.constant})
end
def solve
coefft_matrix = @coefficients.dup
augmented_matrix = Matrix.hstack(@coefficients, @constant)
# System must be consistent for atleast 1 solution to exist
if augmented_matrix.rank > coefft_matrix.rank
raise StandardError, "Inconsistent system of equations"
else
nil
end
end
end
l1 = LinearEquation.new_from_values([1, 2, 3], 14)
l2 = LinearEquation.new_from_values([0, 2, 3], 13)
l3 = LinearEquation.new_from_values([0, 0, 3], 9)
l4 = LinearEquation.new_from_s("2.078x + 3y -.0778z - u = 6")
s = LinearSystem.new(l1, l2, l3)
puts l1
puts l2
puts l3
puts l4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment