add n_ary operators

This commit is contained in:
simon lehericey 2022-06-09 14:00:18 +02:00
parent 698eff0a50
commit daaa54b6f0
9 changed files with 146 additions and 2 deletions

View file

@ -8,7 +8,7 @@ module Logic
end
def self.class_from_name(name)
[Constant, Empty, LessThan, LessThanEq, Eq, GreaterThanEq, GreaterThan, EmptyOperator]
[Constant, Empty, LessThan, LessThanEq, Eq, GreaterThanEq, GreaterThan, EmptyOperator, And, Or]
.find { |c| c.name == name }
end
@ -36,4 +36,8 @@ module Logic
def empty = Logic::Empty.new
def empty_operator(left, right) = Logic::EmptyOperator.new(left, right)
def ds_or(operands) = Logic::Or.new(operands)
def ds_and(operands) = Logic::And.new(operands)
end

11
app/models/logic/and.rb Normal file
View file

@ -0,0 +1,11 @@
class Logic::And < Logic::NAryOperator
attr_reader :operands
def operator_name = 'Et'
def compute(champs = [])
@operands.map { |operand| operand.compute(champs) }.all?
end
def to_s = "(#{@operands.map(&:to_s).join(' && ')})"
end

View file

@ -0,0 +1,43 @@
class Logic::NAryOperator < Logic::Term
attr_reader :operands
def initialize(operands)
@operands = operands
end
def to_h
{
"op" => self.class.name,
"operands" => @operands.map(&:to_h)
}
end
def self.from_h(h)
self.new(h['operands'].map { |operand_h| Logic.from_h(operand_h) })
end
def errors(stable_ids = [])
errors = []
if @operands.empty?
errors += ["opérateur '#{operator_name}' vide"]
end
not_booleans = @operands.filter { |operand| operand.type != :boolean }
if not_booleans.present?
errors += ["'#{operator_name}' ne contient pas que des booléens : #{not_booleans.map(&:to_s).join(', ')}"]
end
errors + @operands.flat_map { |operand| operand.errors(stable_ids) }
end
def type = :boolean
def ==(other)
self.class == other.class &&
@operands.count == other.operands.count &&
@operands.all? do |operand|
@operands.count { |o| o == operand } == other.operands.count { |o| o == operand }
end
end
end

11
app/models/logic/or.rb Normal file
View file

@ -0,0 +1,11 @@
class Logic::Or < Logic::NAryOperator
attr_reader :operands
def operator_name = 'Ou'
def compute(champs = [])
@operands.map { |operand| operand.compute(champs) }.any?
end
def to_s = "(#{@operands.map(&:to_s).join(' || ')})"
end

View file

@ -0,0 +1,18 @@
describe Logic::And do
include Logic
describe '#compute' do
it { expect(and_from([true, true, true]).compute).to be true }
it { expect(and_from([true, true, false]).compute).to be false }
end
describe '#to_s' do
it do
expect(and_from([true, false, true]).to_s).to eq "(true && false && true)"
end
end
def and_from(boolean_to_constants)
ds_and(boolean_to_constants.map { |b| constant(b) })
end
end

View file

@ -1,7 +1,7 @@
include Logic
describe Logic::BinaryOperator do
let(:two_greater_than_one) { greater_than(constant(2), constant(1))}
let(:two_greater_than_one) { greater_than(constant(2), constant(1)) }
describe '#type' do
it { expect(two_greater_than_one.type).to eq(:boolean) }

View file

@ -0,0 +1,29 @@
describe Logic::NAryOperator do
include Logic
describe '#errors' do
it { expect(ds_and([]).errors).to eq(["opérateur 'Et' vide"]) }
it { expect(ds_and([constant(1), constant('toto')]).errors).to eq(["'Et' ne contient pas que des booléens : 1, toto"]) }
it { expect(ds_and([double(type: :boolean, errors: ['from double'])]).errors).to eq(["from double"]) }
end
describe '#==' do
it do
expect(and_from([true, true, false])).to eq(and_from([false, true, true]))
expect(and_from([true, true, false])).not_to eq(and_from([false, false, true]))
# perf test
left = [false, false] + Array.new(10) { true }
right = [false] + Array.new(11) { true }
expect(and_from(left)).not_to eq(and_from(right))
left = (1..10).to_a
right = (1..10).to_a.reverse
expect(and_from(left)).to eq(and_from(right))
end
end
def and_from(boolean_to_constants)
ds_and(boolean_to_constants.map { |b| constant(b) })
end
end

View file

@ -8,10 +8,21 @@ describe Logic do
expect(Logic.from_h(empty.to_h)).to eq(empty)
expect(Logic.from_h(greater_than(constant(1), constant(2)).to_h)).to eq(greater_than(constant(1), constant(2)))
expect(Logic.from_h(ds_and([constant(true), constant(true), constant(false)]).to_h))
.to eq(ds_and([constant(true), constant(true), constant(false)]))
end
describe '.compatible_type?' do
it { expect(Logic.compatible_type?(constant(true), constant(true))).to be true }
it { expect(Logic.compatible_type?(constant(1), constant(true))).to be false }
end
describe 'priority' do
# (false && true) || true = true
it { expect(ds_or([ds_and([constant(false), constant(true)]), constant(true)]).compute).to be true }
# false && (true || true) = false
it { expect(ds_and([constant(false), ds_or([constant(true), constant(true)])]).compute).to be false }
end
end

17
spec/models/or_spec.rb Normal file
View file

@ -0,0 +1,17 @@
describe Logic::Or do
include Logic
describe '#compute' do
it { expect(or_from([true, true, true]).compute).to be true }
it { expect(or_from([true, true, false]).compute).to be true }
it { expect(or_from([false, false, false]).compute).to be false }
end
describe '#to_s' do
it { expect(or_from([true, false, true]).to_s).to eq "(true || false || true)" }
end
def or_from(boolean_to_constants)
ds_or(boolean_to_constants.map { |b| constant(b) })
end
end