Merge branch 'polynomials'
This commit is contained in:
34
bin/demo
34
bin/demo
@@ -4,21 +4,27 @@
|
|||||||
require 'bundler/setup'
|
require 'bundler/setup'
|
||||||
require 'ruby_algebra'
|
require 'ruby_algebra'
|
||||||
|
|
||||||
puts 'RubyMaple © RubyMaple Creators, 2026. No rights reserved.'
|
print "RubyMaple © RubyMaple Creators, 2026. No rights reserved.\nPress Ctrl+D to quit.\n"
|
||||||
|
|
||||||
|
interp = RubyAlgebra::Interpreter.new
|
||||||
loop do
|
loop do
|
||||||
puts 'Введите формулу (пустая строка для выхода): '
|
print '> '
|
||||||
inp = $stdin.gets.chomp
|
inp = $stdin.gets
|
||||||
exit if inp.empty?
|
break if inp.nil?
|
||||||
begin
|
|
||||||
formula = RubyAlgebra::Parser.parse(inp)
|
|
||||||
rescue StandardError
|
|
||||||
puts 'Ошибка синтаксиса'
|
|
||||||
end
|
|
||||||
puts "Формула: #{formula}"
|
|
||||||
puts "Производная по x: #{formula.diff('x')}"
|
|
||||||
puts "Производная по y: #{formula.diff('y')}"
|
|
||||||
|
|
||||||
value = formula.evaluate
|
inp = inp.chomp
|
||||||
puts "Числовое значение: #{value}" unless value.nil?
|
next if inp.empty?
|
||||||
|
|
||||||
|
begin
|
||||||
|
command = RubyAlgebra::Parser.parse_command(inp)
|
||||||
|
rescue StandardError => e
|
||||||
|
puts "Syntax error: #{e.message}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
puts "< #{interp.execute(command)}"
|
||||||
|
rescue StandardError => e
|
||||||
|
puts "Calculation error: #{e.message}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
puts
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative "ruby_algebra/version"
|
require_relative 'ruby_algebra/version'
|
||||||
require_relative "ruby_algebra/expression"
|
require_relative 'ruby_algebra/command'
|
||||||
require_relative "ruby_algebra/parser"
|
require_relative 'ruby_algebra/polynomial'
|
||||||
|
require_relative 'ruby_algebra/parser'
|
||||||
module RubyAlgebra
|
require_relative 'ruby_algebra/interpreter'
|
||||||
end
|
|
||||||
|
|||||||
72
lib/ruby_algebra/command.rb
Normal file
72
lib/ruby_algebra/command.rb
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RubyAlgebra
|
||||||
|
# Команда интерпретатора
|
||||||
|
class Command
|
||||||
|
attr_reader :type
|
||||||
|
|
||||||
|
def type
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Команда присвоения
|
||||||
|
class AssignmentCommand < Command
|
||||||
|
attr_reader :lhs, :operation, :operand1, :operand2
|
||||||
|
|
||||||
|
def initialize(lhs, operation, operand1, operand2)
|
||||||
|
@lhs = lhs
|
||||||
|
@operation = operation
|
||||||
|
@operand1 = operand1
|
||||||
|
@operand2 = operand2
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
result = "Assignment command: "
|
||||||
|
result += @lhs.is_a?(Array) ? @lhs.join(', ') : @lhs.to_s
|
||||||
|
if @operation == :diff
|
||||||
|
result += ' := Diff(' + @operand1.to_s + @operand2.map { |v| ", #{v}"}.join + ')'
|
||||||
|
elsif @operation == :subs
|
||||||
|
result += ' := Subs(' + @operand1.to_s + @operand2.map { |variable, value| ", #{variable}=#{value}"}.join + ')'
|
||||||
|
else
|
||||||
|
result += ' := ' + @operand1.to_s
|
||||||
|
case @operation
|
||||||
|
when :add
|
||||||
|
result += ' + '
|
||||||
|
when :sub
|
||||||
|
result += ' - '
|
||||||
|
when :mult, :scale
|
||||||
|
result += ' * '
|
||||||
|
when :div
|
||||||
|
result += ' / '
|
||||||
|
end
|
||||||
|
result += @operand2.to_s if @operation != :assign
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
:assignment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Команда вывода на экран
|
||||||
|
class DisplayCommand < Command
|
||||||
|
attr_reader :item
|
||||||
|
|
||||||
|
def initialize(item)
|
||||||
|
@type = :display
|
||||||
|
@item = item
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
result = "Display command: "
|
||||||
|
result += @item.to_s
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
:display
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,346 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RubyAlgebra
|
|
||||||
class Expression
|
|
||||||
def to_s
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_priority
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_assoc_type
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
def diff(v)
|
|
||||||
raise NotImplementedError, "#{self.class}#diff not implemented"
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
raise NotImplementedError, "#{self.class}#evaluate not implemented"
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
raise NotImplementedError, "#{self.class}#constant? not implemented"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Addition < Expression
|
|
||||||
attr_reader :lhs, :rhs
|
|
||||||
|
|
||||||
def initialize(lhs, rhs)
|
|
||||||
raise ArgumentError unless lhs.is_a?(Expression) && rhs.is_a?(Expression)
|
|
||||||
@lhs = lhs
|
|
||||||
@rhs = rhs
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
need_parentheses_left = lhs.op_priority < op_priority || (lhs.op_priority == op_priority && op_assoc_type == :right)
|
|
||||||
need_parentheses_right = rhs.op_priority < op_priority || (rhs.op_priority == op_priority && op_assoc_type == :left)
|
|
||||||
|
|
||||||
result = need_parentheses_left ? "(#{lhs}) + " : "#{lhs} + "
|
|
||||||
result + (need_parentheses_right ? "(#{rhs})" : rhs.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:add
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_priority
|
|
||||||
10
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_assoc_type
|
|
||||||
:left
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
return false if other.type != type
|
|
||||||
lhs == other.lhs && rhs == other.rhs
|
|
||||||
end
|
|
||||||
|
|
||||||
def diff(v)
|
|
||||||
Addition.new(@lhs.diff(v), @rhs.diff(v))
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
lhs_val = @lhs.evaluate
|
|
||||||
rhs_val = @rhs.evaluate
|
|
||||||
return nil if lhs_val.nil? || rhs_val.nil?
|
|
||||||
lhs_val + rhs_val
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
@lhs.constant? && @rhs.constant?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Subtraction < Addition
|
|
||||||
attr_reader :lhs, :rhs
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
need_parentheses_left = lhs.op_priority < op_priority || (lhs.op_priority == op_priority && op_assoc_type == :right)
|
|
||||||
need_parentheses_right = rhs.op_priority < op_priority || (rhs.op_priority == op_priority && op_assoc_type == :left)
|
|
||||||
|
|
||||||
result = need_parentheses_left ? "(#{lhs}) - " : "#{lhs} - "
|
|
||||||
result + (need_parentheses_right ? "(#{rhs})" : rhs.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:sub
|
|
||||||
end
|
|
||||||
|
|
||||||
def diff(v)
|
|
||||||
Subtraction.new(@lhs.diff(v), @rhs.diff(v))
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
lhs_val = @lhs.evaluate
|
|
||||||
rhs_val = @rhs.evaluate
|
|
||||||
return nil if lhs_val.nil? || rhs_val.nil?
|
|
||||||
lhs_val - rhs_val
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
@lhs.constant? && @rhs.constant?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Multiplication < Expression
|
|
||||||
attr_reader :lhs, :rhs
|
|
||||||
|
|
||||||
def initialize(lhs, rhs)
|
|
||||||
raise ArgumentError unless lhs.is_a?(Expression) && rhs.is_a?(Expression)
|
|
||||||
@lhs = lhs
|
|
||||||
@rhs = rhs
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
need_parentheses_left = lhs.op_priority < op_priority || (lhs.op_priority == op_priority && op_assoc_type == :right)
|
|
||||||
need_parentheses_right = rhs.op_priority < op_priority || (rhs.op_priority == op_priority && op_assoc_type == :left)
|
|
||||||
|
|
||||||
if lhs.type == :constant && lhs.value == 1
|
|
||||||
need_parentheses_right ? "(#{rhs})" : rhs.to_s
|
|
||||||
elsif lhs.type == :constant && lhs.value == -1
|
|
||||||
need_parentheses_right ? "-(#{rhs})" : "-#{rhs.to_s}"
|
|
||||||
elsif (need_parentheses_right || rhs.type == :variable) && (need_parentheses_left || lhs.type == :mult || lhs.type == :constant)
|
|
||||||
result = need_parentheses_left ? "(#{lhs})" : lhs.to_s
|
|
||||||
if lhs.type == :variable && rhs.type == :variable && lhs.single_letter? && rhs.single_letter?
|
|
||||||
result += " "
|
|
||||||
end
|
|
||||||
result + (need_parentheses_right ? "(#{rhs})" : rhs.to_s)
|
|
||||||
else
|
|
||||||
result = need_parentheses_left ? "(#{lhs}) * " : "#{lhs} * "
|
|
||||||
result + (need_parentheses_right ? "(#{rhs})" : rhs.to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:mult
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_priority
|
|
||||||
20
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_assoc_type
|
|
||||||
:left
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
return false if other.type != type
|
|
||||||
lhs == other.lhs && rhs == other.rhs
|
|
||||||
end
|
|
||||||
|
|
||||||
def diff(v)
|
|
||||||
u_prime = @lhs.diff(v)
|
|
||||||
v_prime = @rhs.diff(v)
|
|
||||||
term1 = Multiplication.new(u_prime, @rhs)
|
|
||||||
term2 = Multiplication.new(@lhs, v_prime)
|
|
||||||
Addition.new(term1, term2)
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
lhs_val = @lhs.evaluate
|
|
||||||
rhs_val = @rhs.evaluate
|
|
||||||
return nil if lhs_val.nil? || rhs_val.nil?
|
|
||||||
lhs_val * rhs_val
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
@lhs.constant? && @rhs.constant?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Division < Multiplication
|
|
||||||
def to_s
|
|
||||||
need_parentheses_left = lhs.op_priority < op_priority || (lhs.op_priority == op_priority && op_assoc_type == :right)
|
|
||||||
need_parentheses_right = rhs.op_priority < op_priority || (rhs.op_priority == op_priority && op_assoc_type == :left)
|
|
||||||
|
|
||||||
result = need_parentheses_left ? "(#{lhs}) / " : "#{lhs} / "
|
|
||||||
result + (need_parentheses_right ? "(#{rhs})" : rhs.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:div
|
|
||||||
end
|
|
||||||
def diff(v)
|
|
||||||
u_prime = @lhs.diff(v)
|
|
||||||
v_prime = @rhs.diff(v)
|
|
||||||
|
|
||||||
numerator = Subtraction.new(
|
|
||||||
Multiplication.new(u_prime, @rhs),
|
|
||||||
Multiplication.new(@lhs, v_prime)
|
|
||||||
)
|
|
||||||
denominator = Power.new(@rhs, Constant.new(2))
|
|
||||||
Division.new(numerator, denominator)
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
lhs_val = @lhs.evaluate
|
|
||||||
rhs_val = @rhs.evaluate
|
|
||||||
return nil if lhs_val.nil? || rhs_val.nil?
|
|
||||||
return nil if rhs_val == 0
|
|
||||||
lhs_val / rhs_val
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
@lhs.constant? && @rhs.constant?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Power < Expression
|
|
||||||
attr_reader :base, :exponent
|
|
||||||
|
|
||||||
def initialize(base, exponent)
|
|
||||||
raise ArgumentError unless base.is_a?(Expression) && exponent.is_a?(Expression)
|
|
||||||
@base = base
|
|
||||||
@exponent = exponent
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
need_parentheses_left = base.op_priority < op_priority || (base.op_priority == op_priority && op_assoc_type == :right)
|
|
||||||
need_parentheses_right = exponent.op_priority < op_priority || (exponent.op_priority == op_priority && op_assoc_type == :left)
|
|
||||||
|
|
||||||
result = need_parentheses_left ? "(#{base}) ^ " : "#{base} ^ "
|
|
||||||
result + (need_parentheses_right ? "(#{exponent})" : exponent.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:pow
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_priority
|
|
||||||
30
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_assoc_type
|
|
||||||
:right
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
return false if other.type != type
|
|
||||||
base == other.base && exponent == other.exponent
|
|
||||||
end
|
|
||||||
|
|
||||||
def diff(v)
|
|
||||||
unless @exponent.is_a?(Constant)
|
|
||||||
raise NotImplementedError, "Дифференцирование степени с неконстантным показателем не реализовано"
|
|
||||||
end
|
|
||||||
|
|
||||||
n = @exponent.value
|
|
||||||
# (u^n)' = n * u^(n-1) * u'
|
|
||||||
base_diff = @base.diff(v)
|
|
||||||
new_power = Power.new(@base, Constant.new(n - 1))
|
|
||||||
coeff = Constant.new(n)
|
|
||||||
Multiplication.new(coeff, Multiplication.new(new_power, base_diff))
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
base_val = @base.evaluate
|
|
||||||
exp_val = @exponent.evaluate
|
|
||||||
return nil if base_val.nil? || exp_val.nil?
|
|
||||||
base_val ** exp_val
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
@base.constant? && @exponent.constant?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Constant < Expression
|
|
||||||
attr_reader :value
|
|
||||||
|
|
||||||
def initialize(value)
|
|
||||||
@value = value
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@value.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:constant
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_priority
|
|
||||||
1000
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_assoc_type
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
return false if other.type != type
|
|
||||||
value == other.value
|
|
||||||
end
|
|
||||||
|
|
||||||
def diff(v)
|
|
||||||
Constant.new(0)
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
@value
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Variable < Expression
|
|
||||||
attr_reader :symbol
|
|
||||||
|
|
||||||
def initialize(symbol)
|
|
||||||
@symbol = symbol
|
|
||||||
@is_single_letter = symbol.length == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@symbol
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
:variable
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_priority
|
|
||||||
1000
|
|
||||||
end
|
|
||||||
|
|
||||||
def op_assoc_type
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def single_letter?
|
|
||||||
@is_single_letter
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
return false if other.type != type
|
|
||||||
symbol == other.symbol
|
|
||||||
end
|
|
||||||
|
|
||||||
def diff(v)
|
|
||||||
@symbol == v ? Constant.new(1) : Constant.new(0)
|
|
||||||
end
|
|
||||||
def evaluate
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
def constant?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
70
lib/ruby_algebra/interpreter.rb
Normal file
70
lib/ruby_algebra/interpreter.rb
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
module RubyAlgebra
|
||||||
|
class Interpreter
|
||||||
|
def initialize
|
||||||
|
@locals = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(command)
|
||||||
|
case command.type
|
||||||
|
when :assignment
|
||||||
|
unless command.operand1.is_a?(Polynomial)
|
||||||
|
operand1 = @locals[command.operand1]
|
||||||
|
return "Undeclared polynomial: #{command.operand1}" if operand1.nil?
|
||||||
|
else
|
||||||
|
operand1 = command.operand1
|
||||||
|
end
|
||||||
|
|
||||||
|
unless [:assign, :scale, :diff, :subs].include?(command.operation)
|
||||||
|
operand2 = @locals[command.operand2]
|
||||||
|
return "Undeclared polynomial: #{command.operand2}" if operand2.nil?
|
||||||
|
else
|
||||||
|
operand2 = command.operand2
|
||||||
|
end
|
||||||
|
|
||||||
|
case command.operation
|
||||||
|
when :assign
|
||||||
|
@locals[command.lhs] = operand1
|
||||||
|
return "#{command.lhs} = #{operand1}"
|
||||||
|
when :add
|
||||||
|
result = operand1 + operand2
|
||||||
|
@locals[command.lhs] = result
|
||||||
|
return "#{command.lhs} = #{result}"
|
||||||
|
when :sub
|
||||||
|
result = operand1 - operand2
|
||||||
|
@locals[command.lhs] = result
|
||||||
|
return "#{command.lhs} = #{result}"
|
||||||
|
when :mult, :scale
|
||||||
|
result = operand1 * operand2
|
||||||
|
@locals[command.lhs] = result
|
||||||
|
return "#{command.lhs} = #{result}"
|
||||||
|
when :div
|
||||||
|
result = operand1 / operand2
|
||||||
|
@locals[command.lhs[0]] = result[0]
|
||||||
|
@locals[command.lhs[1]] = result[1]
|
||||||
|
return "Quotient: #{command.lhs[0]} = #{result[0]}, remainder: #{command.lhs[1]} = #{result[1]}"
|
||||||
|
when :diff
|
||||||
|
result = operand1
|
||||||
|
operand2.each do |variable|
|
||||||
|
result = result.diff(variable)
|
||||||
|
end
|
||||||
|
@locals[command.lhs] = result
|
||||||
|
return "#{command.lhs} = #{result}"
|
||||||
|
when :subs
|
||||||
|
result = operand1.substitute(operand2)
|
||||||
|
@locals[command.lhs] = result
|
||||||
|
return "#{command.lhs} = #{result}"
|
||||||
|
end
|
||||||
|
when :display
|
||||||
|
unless command.item.is_a? Polynomial
|
||||||
|
unless @locals[command.item].nil?
|
||||||
|
return @locals[command.item].to_s
|
||||||
|
else
|
||||||
|
return "Undeclared polynomial: #{command.item}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return command.item.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,199 +2,404 @@
|
|||||||
|
|
||||||
module RubyAlgebra
|
module RubyAlgebra
|
||||||
module Parser
|
module Parser
|
||||||
|
# Токен выражения или команды
|
||||||
class Token
|
class Token
|
||||||
attr_reader :type
|
attr_reader :type
|
||||||
|
|
||||||
|
def initialize; end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NumberToken < Token
|
# Знак сложения
|
||||||
attr_reader :value
|
class PlusToken < Token
|
||||||
|
def initialize
|
||||||
def initialize(value)
|
super()
|
||||||
@type = :num
|
@type = :plus
|
||||||
@value = value
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class IdentifierToken < Token
|
# Знак вычитания
|
||||||
attr_reader :name
|
class MinusToken < Token
|
||||||
|
def initialize
|
||||||
def initialize(name)
|
super()
|
||||||
@type = :id
|
@type = :minus
|
||||||
@name = name
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class BinaryOperatorToken < Token
|
# Знак умножения
|
||||||
attr_reader :op
|
class MultiplyToken < Token
|
||||||
|
def initialize
|
||||||
def initialize(op)
|
super()
|
||||||
@type = :op
|
@type = :mult
|
||||||
@op = op
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class UnaryOperatorToken < Token
|
# Знак деления
|
||||||
attr_reader :op
|
class DivideToken < Token
|
||||||
|
def initialize
|
||||||
def initialize(op)
|
super()
|
||||||
@type = :unary_op
|
@type = :div
|
||||||
@op = op
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Знак возведения в степень
|
||||||
|
class PowerToken < Token
|
||||||
|
def initialize
|
||||||
|
super()
|
||||||
|
@type = :pow
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Скобка
|
||||||
class ParenthesisToken < Token
|
class ParenthesisToken < Token
|
||||||
attr_reader :closing
|
attr_reader :closing
|
||||||
|
|
||||||
def initialize(closing)
|
def initialize(closing)
|
||||||
|
super()
|
||||||
@type = :paren
|
@type = :paren
|
||||||
@closing = closing
|
@closing = closing
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Знак присвоения :=
|
||||||
|
class AssignmentToken < Token
|
||||||
|
def initialize
|
||||||
|
super()
|
||||||
|
@type = :assign
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Знак равенства =
|
||||||
|
class EqualsToken < Token
|
||||||
|
def initialize
|
||||||
|
super()
|
||||||
|
@type = :equals
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Запятая
|
||||||
|
class CommaToken < Token
|
||||||
|
def initialize
|
||||||
|
super()
|
||||||
|
@type = :comma
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Имя переменной или функции
|
||||||
|
class IdentifierToken < Token
|
||||||
|
attr_reader :symbol
|
||||||
|
|
||||||
|
def initialize(symbol)
|
||||||
|
super()
|
||||||
|
@type = :id
|
||||||
|
@symbol = symbol
|
||||||
|
end
|
||||||
|
|
||||||
|
def variable?
|
||||||
|
@symbol.match?(/^\p{Ll}/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Целое или дробное число
|
||||||
|
class NumberToken < Token
|
||||||
|
attr_reader :value
|
||||||
|
|
||||||
|
def initialize(value)
|
||||||
|
super()
|
||||||
|
@type = :num
|
||||||
|
@value = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Специальный токен, означающий конец ввода
|
||||||
class EndToken < Token
|
class EndToken < Token
|
||||||
def initialize()
|
def initialize
|
||||||
|
super()
|
||||||
@type = :end
|
@type = :end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.tokenize(e)
|
class TokenizerError < StandardError; end
|
||||||
i = 0
|
class ParserError < StandardError; end
|
||||||
add_implicit_mul = false
|
|
||||||
while i < e.length
|
# Класс для разбора выражений на токены
|
||||||
c = e[i]
|
class Tokenizer
|
||||||
if c.match?(/\p{Alpha}/)
|
attr_reader :lookahead
|
||||||
yield BinaryOperatorToken.new(:mult) if add_implicit_mul
|
|
||||||
j = i + 1
|
def initialize(expr)
|
||||||
j += 1 while j < e.length && e[j].match?(/\p{Alnum}/)
|
@expr = expr
|
||||||
add_implicit_mul = true
|
@i = 0
|
||||||
yield IdentifierToken.new(e[i...j])
|
@lookahead = _next_token
|
||||||
i = j
|
end
|
||||||
elsif c.match?(/[+\-*\/^()]/)
|
|
||||||
yield BinaryOperatorToken.new(:mult) if c == "(" && add_implicit_mul
|
def next_token
|
||||||
i += 1
|
token = @lookahead
|
||||||
case c
|
@lookahead = _next_token unless token.type == :end
|
||||||
when "+"
|
token
|
||||||
if add_implicit_mul
|
end
|
||||||
yield BinaryOperatorToken.new(:add)
|
|
||||||
else
|
def _next_token
|
||||||
yield UnaryOperatorToken.new(:positive)
|
loop do
|
||||||
|
return EndToken.new unless @i < @expr.length
|
||||||
|
c = @expr[@i]
|
||||||
|
if c.match?(/\p{Alpha}/)
|
||||||
|
j = @i + 1
|
||||||
|
j += 1 while j < @expr.length && @expr[j].match?(/\p{Alnum}/)
|
||||||
|
token = IdentifierToken.new(@expr[@i...j])
|
||||||
|
@i = j
|
||||||
|
return token
|
||||||
|
elsif c.match(%r{[+\-*/^():,=]})
|
||||||
|
token = nil
|
||||||
|
case c
|
||||||
|
when '+'
|
||||||
|
token = PlusToken.new
|
||||||
|
when '-'
|
||||||
|
token = MinusToken.new
|
||||||
|
when '*'
|
||||||
|
token = MultiplyToken.new
|
||||||
|
when '/'
|
||||||
|
token = DivideToken.new
|
||||||
|
when '^'
|
||||||
|
token = PowerToken.new
|
||||||
|
when '('
|
||||||
|
token = ParenthesisToken.new(false)
|
||||||
|
when ')'
|
||||||
|
token = ParenthesisToken.new(true)
|
||||||
|
when ','
|
||||||
|
token = CommaToken.new
|
||||||
|
when '='
|
||||||
|
token = EqualsToken.new
|
||||||
end
|
end
|
||||||
when "-"
|
@i += 1
|
||||||
if add_implicit_mul
|
if c == ':' && @expr[@i] == '='
|
||||||
yield BinaryOperatorToken.new(:sub)
|
token = AssignmentToken.new
|
||||||
else
|
@i += 1
|
||||||
yield UnaryOperatorToken.new(:negative)
|
|
||||||
end
|
end
|
||||||
when "*"
|
return token
|
||||||
yield BinaryOperatorToken.new(:mult)
|
elsif c.match?(/\p{Digit}|\./)
|
||||||
when "/"
|
j = @i + 1
|
||||||
yield BinaryOperatorToken.new(:div)
|
j += 1 while j < @expr.length && @expr[j].match?(/\p{Digit}|\./)
|
||||||
when "^"
|
s = @expr[@i...j]
|
||||||
yield BinaryOperatorToken.new(:pow)
|
@i = j
|
||||||
when "("
|
fl = s.count '.'
|
||||||
yield ParenthesisToken.new(false)
|
raise TokenizerError, 'malformed number' if fl > 1
|
||||||
when ")"
|
return fl.zero? ? NumberToken.new(s.to_i) : NumberToken.new(s.to_f)
|
||||||
yield ParenthesisToken.new(true)
|
elsif c == ' '
|
||||||
end
|
@i += 1
|
||||||
add_implicit_mul = c == ")"
|
|
||||||
elsif c.match?(/\p{Digit}|\./)
|
|
||||||
yield BinaryOperatorToken.new(:mult) if add_implicit_mul
|
|
||||||
j = i + 1
|
|
||||||
j += 1 while j < e.length && e[j].match?(/\p{Digit}|\./)
|
|
||||||
s = e[i...j]
|
|
||||||
fl = s.count "."
|
|
||||||
if fl > 1
|
|
||||||
raise Exception.new
|
|
||||||
end
|
|
||||||
add_implicit_mul = true
|
|
||||||
if fl == 0
|
|
||||||
yield NumberToken.new(s.to_i)
|
|
||||||
else
|
else
|
||||||
yield NumberToken.new(s.to_f)
|
raise TokenizerError, "unrecognized character '#{c}'"
|
||||||
end
|
end
|
||||||
i = j
|
|
||||||
elsif c == " "
|
|
||||||
i += 1
|
|
||||||
else
|
|
||||||
i += 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
yield EndToken.new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse(expr)
|
def self.parse_command(expr)
|
||||||
operators = []
|
tokenizer = Tokenizer.new(expr)
|
||||||
out_stack = []
|
|
||||||
tokenize(expr) do |token|
|
n = tokenizer.lookahead
|
||||||
if token.type == :num
|
if n.type == :id && !n.variable?
|
||||||
out_stack.push(Constant.new(token.value))
|
left_hand_side = tokenizer.next_token.symbol
|
||||||
elsif token.type == :end
|
else
|
||||||
_parse_make_op(operators, out_stack) until operators.empty?
|
return DisplayCommand.new(parse_polynomial(tokenizer))
|
||||||
elsif token.type == :op || token.type == :unary_op
|
end
|
||||||
p = _parse_op_prio token
|
|
||||||
while operators.length != 0 && (operators.last.type == :op || operators.last.type == :unary_op) && (_parse_op_prio(operators.last) > p || (_parse_op_prio(operators.last) == p && token.op != :pow && token.op != :positive && token.op != :negative))
|
if tokenizer.lookahead.type == :assign
|
||||||
_parse_make_op(operators, out_stack)
|
n = tokenizer.next_token
|
||||||
end
|
operand1 = tokenizer.lookahead
|
||||||
operators.push(token)
|
if operand1.type == :id && !operand1.variable?
|
||||||
elsif token.type == :paren
|
if operand1.symbol == 'Diff'
|
||||||
if token.closing
|
tokenizer.next_token
|
||||||
until operators.empty? || (operators.last.type == :paren && !operators.last.closing)
|
n = tokenizer.next_token
|
||||||
_parse_make_op(operators, out_stack)
|
unless n.type == :paren && n.closing == false
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected ("
|
||||||
end
|
end
|
||||||
if operators.empty? || operators.last.type != :paren || operators.last.closing
|
n = tokenizer.lookahead
|
||||||
raise Exception.new
|
if n.type == :id && !n.variable?
|
||||||
|
tokenizer.next_token
|
||||||
|
target = n.symbol
|
||||||
else
|
else
|
||||||
operators.pop
|
target = parse_polynomial(tokenizer)
|
||||||
end
|
end
|
||||||
|
diff_variables = []
|
||||||
|
while tokenizer.lookahead.type == :comma
|
||||||
|
tokenizer.next_token
|
||||||
|
n = tokenizer.next_token
|
||||||
|
unless n.type == :id && n.variable?
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected variable"
|
||||||
|
end
|
||||||
|
diff_variables << n.symbol
|
||||||
|
end
|
||||||
|
n = tokenizer.next_token
|
||||||
|
unless n.type == :paren && n.closing == true
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected )"
|
||||||
|
end
|
||||||
|
unless tokenizer.next_token.type == :end
|
||||||
|
raise ParserError, "unexpected token at the end"
|
||||||
|
end
|
||||||
|
return AssignmentCommand.new(left_hand_side, :diff, target, diff_variables)
|
||||||
|
elsif operand1.symbol == 'Subs'
|
||||||
|
tokenizer.next_token
|
||||||
|
n = tokenizer.next_token
|
||||||
|
unless n.type == :paren && n.closing == false
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected ("
|
||||||
|
end
|
||||||
|
n = tokenizer.lookahead
|
||||||
|
if n.type == :id && !n.variable?
|
||||||
|
tokenizer.next_token
|
||||||
|
target = n.symbol
|
||||||
|
else
|
||||||
|
target = parse_polynomial(tokenizer)
|
||||||
|
end
|
||||||
|
substitutions = {}
|
||||||
|
while tokenizer.lookahead.type == :comma
|
||||||
|
tokenizer.next_token
|
||||||
|
n = tokenizer.next_token
|
||||||
|
unless n.type == :id && n.variable?
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected variable"
|
||||||
|
end
|
||||||
|
variable = n.symbol
|
||||||
|
unless tokenizer.next_token.type == :equals
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected ="
|
||||||
|
end
|
||||||
|
n = tokenizer.next_token
|
||||||
|
unless n.type == :num || n.type == :plus || n.type == :minus
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected -, + or number"
|
||||||
|
end
|
||||||
|
if n.type == :plus || n.type == :minus
|
||||||
|
negative = true if n.type == :minus
|
||||||
|
n = tokenizer.next_token
|
||||||
|
end
|
||||||
|
unless n.type == :num
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected number"
|
||||||
|
end
|
||||||
|
substitutions[variable] = if negative then -n.value else n.value end
|
||||||
|
end
|
||||||
|
n = tokenizer.next_token
|
||||||
|
unless n.type == :paren && n.closing == true
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected )"
|
||||||
|
end
|
||||||
|
unless tokenizer.next_token.type == :end
|
||||||
|
raise ParserError, "unexpected token at the end"
|
||||||
|
end
|
||||||
|
return AssignmentCommand.new(left_hand_side, :subs, target, substitutions)
|
||||||
else
|
else
|
||||||
operators.push token
|
tokenizer.next_token
|
||||||
|
operator = tokenizer.next_token
|
||||||
|
raise ParserError, "unexpected token #{operator.type}, expected +, -, * or /" unless [:plus, :minus, :mult, :div].include?(operator.type)
|
||||||
|
|
||||||
|
operand2 = tokenizer.next_token
|
||||||
|
unless ([:plus, :minus, :mult].include?(operator.type) && operand2.type == :id && !operand2.variable?) ||
|
||||||
|
([:mult, :div].include?(operator.type) && operand2.type == :num)
|
||||||
|
raise ParserError, 'unsupported operation or invalid syntax'
|
||||||
|
end
|
||||||
|
if operand2.type == :num
|
||||||
|
if operator.type == :mult
|
||||||
|
return AssignmentCommand.new(left_hand_side, :scale, operand1.symbol, operand2.value)
|
||||||
|
else
|
||||||
|
return AssignmentCommand.new(left_hand_side, :scale, operand1.symbol, 1.0 / operand2.value)
|
||||||
|
end
|
||||||
|
elsif operand2.type == :id
|
||||||
|
case operator.type
|
||||||
|
when :plus
|
||||||
|
return AssignmentCommand.new(left_hand_side, :add, operand1.symbol, operand2.symbol)
|
||||||
|
when :minus
|
||||||
|
return AssignmentCommand.new(left_hand_side, :sub, operand1.symbol, operand2.symbol)
|
||||||
|
when :mult
|
||||||
|
return AssignmentCommand.new(left_hand_side, :mult, operand1.symbol, operand2.symbol)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elsif token.type == :id
|
|
||||||
out_stack.push(Variable.new(token.name))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
out_stack[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
def self._parse_make_op(operators, out_stack)
|
|
||||||
oper = operators.pop
|
|
||||||
unless (oper.type == :op && out_stack.length >= 2) || (oper.type == :unary_op && out_stack.length >= 1)
|
|
||||||
raise Exception.new
|
|
||||||
end
|
|
||||||
b = out_stack.pop if oper.type == :op
|
|
||||||
a = out_stack.pop
|
|
||||||
|
|
||||||
case oper.op
|
|
||||||
when :add
|
|
||||||
out_stack.push Addition.new(a, b)
|
|
||||||
when :sub
|
|
||||||
out_stack.push Subtraction.new(a, b)
|
|
||||||
when :mult
|
|
||||||
out_stack.push Multiplication.new(a, b)
|
|
||||||
when :div
|
|
||||||
out_stack.push Division.new(a, b)
|
|
||||||
when :pow
|
|
||||||
out_stack.push Power.new(a, b)
|
|
||||||
when :positive
|
|
||||||
out_stack.push a
|
|
||||||
when :negative
|
|
||||||
if a.type == :constant
|
|
||||||
out_stack.push Constant.new(-a.value)
|
|
||||||
else
|
else
|
||||||
out_stack.push Multiplication.new(Constant.new(-1), a)
|
return AssignmentCommand.new(left_hand_side, :assign, parse_polynomial(tokenizer), nil)
|
||||||
end
|
end
|
||||||
|
elsif tokenizer.lookahead.type == :comma
|
||||||
|
n = tokenizer.next_token
|
||||||
|
second_lhs = tokenizer.next_token
|
||||||
|
unless second_lhs.type == :id && !second_lhs.variable?
|
||||||
|
raise ParserError, "unexpected token #{second_lhs.type}, expected polynomial variable"
|
||||||
|
end
|
||||||
|
unless tokenizer.next_token.type == :assign
|
||||||
|
raise ParserError, "unexpected token, expected :="
|
||||||
|
end
|
||||||
|
operand1 = tokenizer.next_token
|
||||||
|
unless operand1.type == :id && !operand1.variable?
|
||||||
|
raise ParserError, "unexpected token #{operand1.type}, expected polynomial variable"
|
||||||
|
end
|
||||||
|
unless tokenizer.next_token.type == :div
|
||||||
|
raise ParserError, "unexpected token, expected /"
|
||||||
|
end
|
||||||
|
operand2 = tokenizer.next_token
|
||||||
|
unless operand2.type == :id && !operand2.variable?
|
||||||
|
raise ParserError, "unexpected token #{operand2.type}, expected polynomial variable"
|
||||||
|
end
|
||||||
|
return AssignmentCommand.new([left_hand_side, second_lhs.symbol], :div, operand1.symbol, operand2.symbol)
|
||||||
|
elsif tokenizer.lookahead.type == :end
|
||||||
|
return DisplayCommand.new(left_hand_side)
|
||||||
|
else
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected ':=' or ','"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self._parse_op_prio(op_token)
|
def self.parse_polynomial(tokenizer)
|
||||||
case op_token.op
|
first_term_negative = false
|
||||||
when :add, :sub
|
if tokenizer.lookahead.type == :plus || tokenizer.lookahead.type == :minus
|
||||||
1
|
first_term_negative = tokenizer.lookahead.type == :minus
|
||||||
when :mult, :div
|
tokenizer.next_token
|
||||||
2
|
|
||||||
when :pow, :positive, :negative
|
|
||||||
3
|
|
||||||
end
|
end
|
||||||
|
first_term = parse_term(tokenizer)
|
||||||
|
first_term *= -1 if first_term_negative
|
||||||
|
terms = [first_term]
|
||||||
|
while [:plus, :minus].include?(tokenizer.lookahead.type)
|
||||||
|
sign = tokenizer.next_token.type == :plus ? +1 : -1
|
||||||
|
terms << parse_term(tokenizer) * sign
|
||||||
|
end
|
||||||
|
Polynomial.new(terms)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.parse_term(tokenizer)
|
||||||
|
n = tokenizer.lookahead
|
||||||
|
unless n.type == :id && n.variable? || n.type == :num
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected number or variable"
|
||||||
|
end
|
||||||
|
if n.type == :num
|
||||||
|
tokenizer.next_token
|
||||||
|
coeff = n.value
|
||||||
|
variables = {}
|
||||||
|
else
|
||||||
|
coeff = 1
|
||||||
|
v = parse_variable_factor(tokenizer)
|
||||||
|
variables = {v[0] => v[1]}
|
||||||
|
end
|
||||||
|
loop do
|
||||||
|
n = tokenizer.lookahead
|
||||||
|
unless n.type == :mult || n.type == :id && n.variable?
|
||||||
|
break
|
||||||
|
end
|
||||||
|
v = parse_variable_factor(tokenizer)
|
||||||
|
variables[v[0]] = v[1]
|
||||||
|
end
|
||||||
|
Term.new(coeff, variables)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.parse_variable_factor(tokenizer)
|
||||||
|
n = tokenizer.lookahead
|
||||||
|
tokenizer.next_token
|
||||||
|
if n.type == :mult
|
||||||
|
n = tokenizer.next_token
|
||||||
|
end
|
||||||
|
unless n.type == :id && n.variable?
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected variable"
|
||||||
|
end
|
||||||
|
variable = n.symbol
|
||||||
|
if tokenizer.lookahead.type == :pow
|
||||||
|
tokenizer.next_token
|
||||||
|
unless tokenizer.lookahead.type == :num
|
||||||
|
raise ParserError, "unexpected token #{n.type}, expected number"
|
||||||
|
end
|
||||||
|
power = tokenizer.next_token.value
|
||||||
|
else
|
||||||
|
power = 1
|
||||||
|
end
|
||||||
|
[variable, power]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
299
lib/ruby_algebra/polynomial.rb
Normal file
299
lib/ruby_algebra/polynomial.rb
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# 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
|
||||||
Reference in New Issue
Block a user