300 lines
7.2 KiB
Ruby
300 lines
7.2 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
module RubyAlgebra
|
||
# Многочлен - сумма одночленов
|
||
class Polynomial
|
||
# одночлены
|
||
attr_reader :terms
|
||
|
||
def initialize(terms = [])
|
||
@terms = if terms.is_a? Term
|
||
[terms]
|
||
else
|
||
terms
|
||
end
|
||
simplify!
|
||
end
|
||
|
||
def to_s
|
||
if @terms.empty?
|
||
result = '0'
|
||
else
|
||
result = @terms[-1].to_s
|
||
i = @terms.length - 2
|
||
while i >= 0
|
||
result += if @terms[i].coeff > 0
|
||
" + #{@terms[i]}"
|
||
else
|
||
" - #{-@terms[i]}"
|
||
end
|
||
i -= 1
|
||
end
|
||
end
|
||
result
|
||
end
|
||
|
||
def diff(variable)
|
||
Polynomial.new(@terms.map { |term| term.diff(variable) })
|
||
end
|
||
|
||
def substitute(values)
|
||
Polynomial.new(@terms.map { |term| term.substitute(values) })
|
||
end
|
||
|
||
def +(other)
|
||
if other.is_a? Polynomial
|
||
Polynomial.new(@terms + other.terms).simplify
|
||
elsif other.is_a? Term
|
||
Polynomial.new(@terms + [other]).simplify
|
||
else
|
||
Polynomial.new(@terms + [Term.new(other)]).simplify
|
||
end
|
||
end
|
||
|
||
def *(other)
|
||
if other.is_a? Polynomial
|
||
result = Polynomial.new
|
||
other.terms.each do |term|
|
||
result += self * term
|
||
end
|
||
result
|
||
else
|
||
Polynomial.new(@terms.map { |term| term * other })
|
||
end
|
||
end
|
||
|
||
def /(other)
|
||
raise StandardError, "cannot divide by zero" if other.zero?
|
||
return [Polynomial.new, Polynomial.new] if self.zero?
|
||
|
||
# Сначала проверить, что оба многочлена от одной и той же переменной
|
||
t = if @terms.empty? || @terms[-1].variables.empty? then other.terms[-1] else @terms[-1] end
|
||
unless t.variables.empty?
|
||
only_variable = t.variables.keys[0]
|
||
@terms.each do |term|
|
||
next if term.variables.empty?
|
||
unless term.variables.length == 1 && term.variables.keys[0] == only_variable
|
||
raise StandardError, "cannot divide polynomials with different variables"
|
||
end
|
||
end
|
||
other.terms.each do |term|
|
||
next if term.variables.empty?
|
||
unless term.variables.length == 1 && term.variables.keys[0] == only_variable
|
||
raise StandardError, "cannot divide polynomials with different variables"
|
||
end
|
||
end
|
||
end
|
||
|
||
quotient = Polynomial.new
|
||
remainder = self
|
||
divisor_power = unless other.terms.last.variables.empty? then other.terms.last.variables.first[1] else 0 end
|
||
loop do
|
||
break if remainder.zero?
|
||
|
||
dividend_power = unless remainder.terms.last.variables.empty? then remainder.terms.last.variables.first[1] else 0 end
|
||
break if dividend_power < divisor_power
|
||
|
||
result_power = dividend_power - divisor_power
|
||
result_coeff = remainder.terms.last.coeff.to_f / other.terms.last.coeff
|
||
if result_power > 0
|
||
q = Term.new(result_coeff, {only_variable => result_power})
|
||
else
|
||
q = Term.new(result_coeff, {})
|
||
end
|
||
quotient += q
|
||
remainder -= other * q
|
||
end
|
||
[quotient, remainder]
|
||
end
|
||
|
||
def **(other)
|
||
result = Polynomial.new([Term.new(1)])
|
||
other.times do
|
||
result *= self
|
||
end
|
||
result
|
||
end
|
||
|
||
def -(other)
|
||
self + -other
|
||
end
|
||
|
||
def +@
|
||
self
|
||
end
|
||
|
||
def -@
|
||
self * -1
|
||
end
|
||
|
||
def ==(other)
|
||
@terms == other.terms
|
||
end
|
||
|
||
def _simplify
|
||
i = 0
|
||
new_terms = @terms.filter { |t| !t.zero? }
|
||
while i < new_terms.length
|
||
j = i + 1
|
||
annihilated = false
|
||
while j < new_terms.length
|
||
if new_terms[i].similar_to?(new_terms[j])
|
||
new_terms[i] += new_terms[j]
|
||
new_terms.delete_at(j)
|
||
if new_terms[i].zero?
|
||
new_terms.delete_at(i)
|
||
annihilated = true
|
||
break
|
||
end
|
||
else
|
||
j += 1
|
||
end
|
||
end
|
||
i += 1 unless annihilated
|
||
end
|
||
new_terms.sort_by { |t| t.total_power }
|
||
end
|
||
|
||
def simplify
|
||
Polynomial.new(_simplify)
|
||
end
|
||
|
||
def simplify!
|
||
@terms = _simplify
|
||
end
|
||
|
||
def zero?
|
||
@terms.all? { |t| t.zero? }
|
||
end
|
||
end
|
||
|
||
# Одночлен в форме a_n * x_1^k_1 * ... * x_n^k_n
|
||
class Term
|
||
attr_reader :coeff, :variables
|
||
|
||
def initialize(coeff = 0, variables = {})
|
||
@coeff = coeff
|
||
@variables = {}
|
||
variables.each do |var, power|
|
||
next if power.zero?
|
||
|
||
if @variables[var].nil?
|
||
@variables[var] = power
|
||
else
|
||
@variables[var] += power
|
||
end
|
||
end
|
||
end
|
||
|
||
def diff(variable)
|
||
return Term.new if @variables[variable].nil?
|
||
new_coeff = @coeff
|
||
new_variables = @variables.filter { |var, power| var != variable }
|
||
unless @variables[variable] == 1
|
||
new_coeff *= @variables[variable]
|
||
new_variables[variable] = @variables[variable] - 1
|
||
end
|
||
Term.new(new_coeff, new_variables)
|
||
end
|
||
|
||
def substitute(values)
|
||
new_coeff = @coeff
|
||
new_variables = {}
|
||
@variables.each do |var, pow|
|
||
if values[var].nil?
|
||
new_variables[var] = pow
|
||
else
|
||
new_coeff *= values[var] ** pow
|
||
end
|
||
end
|
||
Term.new(new_coeff, new_variables)
|
||
end
|
||
|
||
def +(other)
|
||
raise NotImplementedError, 'addition of non-similar terms is not implemented' unless similar_to?(other)
|
||
|
||
Term.new(@coeff + other.coeff, @variables)
|
||
end
|
||
|
||
def *(other)
|
||
return Term.new if zero? || other.zero?
|
||
|
||
return Term.new(coeff * other, variables) unless other.is_a? Term
|
||
|
||
new_variables = @variables.clone
|
||
other.variables.each do |symbol, power|
|
||
if new_variables[symbol].nil?
|
||
new_variables[symbol] = power
|
||
else
|
||
new_variables[symbol] += power
|
||
end
|
||
end
|
||
Term.new(@coeff * other.coeff, new_variables)
|
||
end
|
||
|
||
def /(other)
|
||
raise StandardError, "cannot divide by zero" if other.zero?
|
||
|
||
end
|
||
|
||
def +@
|
||
Term.new(@coeff, @variables)
|
||
end
|
||
|
||
def -@
|
||
Term.new(-@coeff, @variables)
|
||
end
|
||
|
||
def ==(other)
|
||
@coeff == other.coeff && @variables == other.variables
|
||
end
|
||
|
||
def total_power
|
||
@variables.values.sum
|
||
end
|
||
|
||
def to_s
|
||
if @coeff == 1 && !@variables.empty?
|
||
result = ''
|
||
elsif @coeff == -1 && !@variables.empty?
|
||
result = '-'
|
||
else
|
||
result = @coeff.to_s
|
||
end
|
||
unless @variables.empty?
|
||
first = true
|
||
@variables.each do |var, power|
|
||
if first
|
||
unless power == 1
|
||
result += "#{var}^#{power}"
|
||
else
|
||
result += "#{var}"
|
||
end
|
||
else
|
||
unless power == 1
|
||
result += " * #{var}^#{power}"
|
||
else
|
||
result += " * #{var}"
|
||
end
|
||
end
|
||
first = false
|
||
end
|
||
end
|
||
result
|
||
end
|
||
|
||
def similar_to?(other)
|
||
return false if @variables.length != other.variables.length
|
||
|
||
other.variables.each do |var, power|
|
||
return false if @variables[var] != power
|
||
end
|
||
true
|
||
end
|
||
|
||
def zero?
|
||
@coeff.zero?
|
||
end
|
||
end
|
||
end
|