# 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