add n_ary operators
This commit is contained in:
parent
698eff0a50
commit
daaa54b6f0
9 changed files with 146 additions and 2 deletions
|
@ -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
11
app/models/logic/and.rb
Normal 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
|
43
app/models/logic/n_ary_operator.rb
Normal file
43
app/models/logic/n_ary_operator.rb
Normal 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
11
app/models/logic/or.rb
Normal 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
|
18
spec/models/logic/and_spec.rb
Normal file
18
spec/models/logic/and_spec.rb
Normal 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
|
29
spec/models/logic/n_ary_operator_spec.rb
Normal file
29
spec/models/logic/n_ary_operator_spec.rb
Normal 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
|
|
@ -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
17
spec/models/or_spec.rb
Normal 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
|
Loading…
Reference in a new issue