Skip to content

Commit

Permalink
fix: equality on expressions (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
albyrock87 authored Apr 22, 2024
1 parent 31342f2 commit fd7c3bd
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 45 deletions.
92 changes: 86 additions & 6 deletions src/Cassowary/Expression.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,74 @@
using System.Collections.Immutable;
using Cassowary.Extensions;

namespace Cassowary;

/// <summary>
/// An expression that can be the left hand or right hand side of a constraint equation.
/// It is a linear combination of variables, i.e. a sum of variables weighted by coefficients, plus an optional constant.
/// </summary>
public readonly record struct Expression(ImmutableArray<Term> Terms, double Constant)
public readonly struct Expression
{
/// <summary>
/// Gets the terms of the expression.
/// </summary>
public ImmutableArray<Term> Terms { get; init; }

/// <summary>
/// Gets the constant value of the expression.
/// </summary>
public double Constant { get; init; }

/// <summary>
/// Initializes a new instance of the <see cref="Expression"/> struct.
/// </summary>
/// <param name="terms">Terms of the expression.</param>
/// <param name="constant">Constant value.</param>
public Expression(ImmutableArray<Term> terms, double constant)
{
Terms = terms;
Constant = constant;
}

/// <summary>
/// Initializes a new instance of the <see cref="Expression"/> struct.
/// </summary>
public Expression()
{
Terms = ImmutableArray<Term>.Empty;
}

/// <summary>
/// Deconstructs the expression into terms and constant.
/// </summary>
/// <param name="terms"></param>
/// <param name="constant"></param>
public void Deconstruct(out ImmutableArray<Term> terms, out double constant)
{
terms = this.Terms;
constant = this.Constant;
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}

var other = (Expression)obj;
return other.Constant == Constant && Terms.SequenceEqual(other.Terms);
}

/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return Constant.GetHashCode() + Terms.Aggregate(19, (h, i) => (h * 19) + i.GetHashCode());
}
}

/// <summary>
/// Constructs an expression of the form _n_, where n is a constant real number, not a variable.
/// </summary>
Expand All @@ -22,7 +82,7 @@ public readonly record struct Expression(ImmutableArray<Term> Terms, double Cons
/// </summary>
/// <param name="term">The <see cref="Term"/>.</param>
/// <returns>New instance of <see cref="Expression"/>.</returns>
public static Expression From(Term term) => new(ImmutableArray<Term>.Empty.Add(term), 0);
public static Expression From(Term term) => new(ImmutableArray.Create(term), 0);

/// <summary>
/// Constructs an expression from a single variable. Forms an expression of the form _x_
Expand Down Expand Up @@ -88,7 +148,7 @@ public readonly record struct Expression(ImmutableArray<Term> Terms, double Cons
/// <param name="expression">The <see cref="Expression"/>.</param>
/// <returns>New instance of <see cref="Expression"/> with negative <see cref="Cassowary.Term"/> and constant.</returns>
public static Expression operator -(Expression expression)
=> new(expression.Terms.ToImmutableArray(term => -term), -expression.Constant);
=> new(ImmutableArray.CreateRange(expression.Terms, term => -term), -expression.Constant);

/// <summary>
/// Subtract <see cref="float"/> value from <see cref="Expression"/>.
Expand Down Expand Up @@ -192,7 +252,7 @@ public readonly record struct Expression(ImmutableArray<Term> Terms, double Cons
/// <param name="value">The value</param>
/// <returns>New <see cref="Expression"/> instance with <see cref="Term"/> and <see cref="Constant"/> multiply by <paramref name="value"/>.</returns>
public static Expression operator *(Expression expression, double value)
=> new(expression.Terms.ToImmutableArray(x => x * value),
=> new(ImmutableArray.CreateRange(expression.Terms, x => x * value),
expression.Constant * value);

/// <summary>
Expand Down Expand Up @@ -233,7 +293,27 @@ public readonly record struct Expression(ImmutableArray<Term> Terms, double Cons
/// <param name="value">The value</param>
/// <returns>New <see cref="Expression"/> instance with <see cref="Term"/> and <see cref="Constant"/> dividing by <paramref name="value"/>.</returns>
public static Expression operator /(Expression expression, double value)
=> new(expression.Terms.ToImmutableArray(term => term / value), expression.Constant / value);
=> new(ImmutableArray.CreateRange(expression.Terms, term => term / value), expression.Constant / value);

#endregion

#region operator =

/// <summary>
/// Checks if two <see cref="Expression"/> are equal.
/// </summary>
/// <param name="left">Left <see cref="Expression"/>.</param>
/// <param name="right">Right <see cref="Expression"/>.</param>
public static bool operator ==(Expression left, Expression right)
=> left.Equals(right);

/// <summary>
/// Checks if two <see cref="Expression"/> are not equal.
/// </summary>
/// <param name="left">Left <see cref="Expression"/>.</param>
/// <param name="right">Right <see cref="Expression"/>.</param>
public static bool operator !=(Expression left, Expression right)
=> !left.Equals(right);

#endregion
}
17 changes: 0 additions & 17 deletions src/Cassowary/Extensions/ImmutableArrayExtensions.cs

This file was deleted.

8 changes: 4 additions & 4 deletions src/Cassowary/Term.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public readonly record struct Term(Variable Variable, double Coefficient)
/// <param name="value">The value.</param>
/// <returns> Add <paramref name="value"/> to <paramref name="term"/> and return new <see cref="Expression"/>.</returns>
public static Expression operator +(Term term, double value)
=> new(ImmutableArray<Term>.Empty.Add(term), value);
=> new(ImmutableArray.Create(term), value);

/// <summary>
/// Add <paramref name="value"/> to <paramref name="term"/> and return new <see cref="Expression"/>.
Expand All @@ -64,7 +64,7 @@ public readonly record struct Term(Variable Variable, double Coefficient)
/// <param name="other">The <see cref="Term"/>.</param>
/// <returns>Create new instance of <see cref="Expression"/> with both <see cref="Term"/>.</returns>
public static Expression operator +(Term term, Term other)
=> new(ImmutableArray<Term>.Empty.AddRange(term, other), 0);
=> new(ImmutableArray.Create(term, other), 0);

#endregion

Expand Down Expand Up @@ -93,7 +93,7 @@ public readonly record struct Term(Variable Variable, double Coefficient)
/// <param name="value">The value</param>
/// <returns>New <see cref="Expression"/> with the <paramref name="term"/> and negate <paramref name="value"/>.</returns>
public static Expression operator -(Term term, double value)
=> new(ImmutableArray<Term>.Empty.Add(term), -value);
=> new(ImmutableArray.Create(term), -value);

/// <summary>
/// Subtract <paramref name="value"/> from <paramref name="term"/> and return new <see cref="Expression"/>.
Expand All @@ -111,7 +111,7 @@ public readonly record struct Term(Variable Variable, double Coefficient)
/// <param name="value">The value</param>
/// <returns>New <see cref="Expression"/> with the <paramref name="term"/> and negate <paramref name="value"/>.</returns>
public static Expression operator -(double value, Term term)
=> new(ImmutableArray<Term>.Empty.Add(-term), value);
=> new(ImmutableArray.Create(-term), value);

/// <summary>
/// Subtract <paramref name="term"/> from <paramref name="expression"/> and return new <see cref="Expression"/>.
Expand Down
15 changes: 6 additions & 9 deletions src/Cassowary/Variable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public Variable()
/// <param name="value">The value.</param>
/// <returns>New <see cref="Expression"/> instance with <paramref name="variable"/> value <paramref name="value"/> as <see cref="Expression.Constant"/>.</returns>
public static Expression operator +(Variable variable, double value)
=> new(ImmutableArray<Term>.Empty.Add(new(variable, 1)), value);
=> new(ImmutableArray.Create(new Term(variable, 1)), value);

/// <summary>
/// Add <paramref name="value"/> to <paramref name="variable"/> and return new <see cref="Expression"/>.
Expand All @@ -63,8 +63,7 @@ public Variable()
/// <param name="term">The <see cref="Term"/>.</param>
/// <returns>New <see cref="Expression"/> instance with <paramref name="variable"/> and <paramref name="term"/> and <see cref="Expression.Constant"/> as 0.</returns>
public static Expression operator +(Variable variable, Term term)
=> new(ImmutableArray<Term>.Empty
.AddRange(term, new(variable, 1)), 0);
=> new(ImmutableArray.Create(term, new(variable, 1)), 0);

/// <summary>
/// Add <paramref name="expression"/> to <paramref name="variable"/> and return new <see cref="Expression"/>.
Expand All @@ -82,8 +81,7 @@ public Variable()
/// <param name="other">The <see cref="Variable"/>.</param>
/// <returns>New <see cref="Expression"/> instance with both variables and constant as 0.</returns>
public static Expression operator +(Variable variable, Variable other)
=> new(ImmutableArray<Term>.Empty
.AddRange(new Term(variable, 1), new Term(other, 1)), 0);
=> new(ImmutableArray.Create(new Term(variable, 1), new Term(other, 1)), 0);

#endregion

Expand Down Expand Up @@ -113,7 +111,7 @@ public Variable()
/// <param name="value">The value.</param>
/// <returns>New <see cref="Expression"/> instance with <paramref name="variable"/> and negate <paramref name="value"/> as <see cref="Expression.Constant"/> .</returns>
public static Expression operator -(Variable variable, double value)
=> new(ImmutableArray<Term>.Empty.Add(new(variable, 1)), -value);
=> new(ImmutableArray.Create(new Term(variable, 1)), -value);

/// <summary>
/// Subtract <paramref name="value"/> from <paramref name="variable"/> and return new <see cref="Expression"/>.
Expand All @@ -131,7 +129,7 @@ public Variable()
/// <param name="value">The value.</param>
/// <returns>New <see cref="Expression"/> instance with <paramref name="variable"/> and negate <paramref name="value"/> as <see cref="Expression.Constant"/> .</returns>
public static Expression operator -(double value, Variable variable)
=> new(ImmutableArray<Term>.Empty.Add(new(variable, -1)), value);
=> new(ImmutableArray.Create(new Term(variable, -1)), value);

/// <summary>
/// Subtract <paramref name="term"/> from <paramref name="variable"/> and return new <see cref="Expression"/>.
Expand All @@ -140,8 +138,7 @@ public Variable()
/// <param name="term">The <see cref="Term"/>.</param>
/// <returns>New <see cref="Expression"/> instance.</returns>
public static Expression operator -(Variable variable, Term term)
=> new(ImmutableArray<Term>.Empty
.AddRange(new Term(variable, 1), -term), 0);
=> new(ImmutableArray.Create(new Term(variable, 1), -term), 0);

/// <summary>
/// Subtract <paramref name="expression"/> from <paramref name="variable"/> and return new <see cref="Expression"/>.
Expand Down
26 changes: 17 additions & 9 deletions tests/Cassowary.Tests/ExpressionTest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
using System.Collections.Immutable;
using AutoFixture;
using Cassowary.Extensions;
using FluentAssertions;

namespace Cassowary.Tests;

public class ExpressionTest
{
private readonly Fixture _fixture = new();

[Fact]
public void ExpressionEqualsExpression()
{
var guid = Guid.NewGuid();
var variable = new Variable(guid);
(variable | WeightedRelation.Eq(1) | 5)
.Should().Be(variable | WeightedRelation.Eq(1) | 5);
}

[Fact]
public void FromConstant()
Expand Down Expand Up @@ -85,7 +93,7 @@ public void NegateExpression()

var negated = -expression;
negated.Constant.Should().Be(-expression.Constant);
negated.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => -term));
negated.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => -term));
}

[Fact]
Expand Down Expand Up @@ -147,7 +155,7 @@ public void SubExpressionWithExpression()
var sub = expression - otherExpression;
sub.Constant.Should().Be(expression.Constant - otherExpression.Constant);
sub.Terms.Should()
.BeEquivalentTo(expression.Terms.AddRange(otherExpression.Terms.ToImmutableArray(term => -term)));
.BeEquivalentTo(expression.Terms.AddRange(ImmutableArray.CreateRange(otherExpression.Terms, term => -term)));
}

[Fact]
Expand All @@ -171,19 +179,19 @@ public void Multiply()

var product = expression * multiplier;
product.Constant.Should().Be(expression.Constant * multiplier);
product.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => term * multiplier));
product.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => term * multiplier));

product = expression * (float)multiplier;
product.Constant.Should().Be(expression.Constant * multiplier);
product.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => term * multiplier));
product.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => term * multiplier));

product = multiplier * expression;
product.Constant.Should().Be(expression.Constant * multiplier);
product.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => term * multiplier));
product.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => term * multiplier));

product = (float)multiplier * expression;
product.Constant.Should().Be(expression.Constant * multiplier);
product.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => term * multiplier));
product.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => term * multiplier));
}

[Fact]
Expand All @@ -196,10 +204,10 @@ public void Div()

var remaning = expression / divider;
remaning.Constant.Should().Be(expression.Constant / divider);
remaning.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => term / divider));
remaning.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => term / divider));

remaning = expression / (double)divider;
remaning.Constant.Should().Be(expression.Constant / divider);
remaning.Terms.Should().BeEquivalentTo(expression.Terms.ToImmutableArray(term => term / divider));
remaning.Terms.Should().BeEquivalentTo(ImmutableArray.CreateRange(expression.Terms, term => term / divider));
}
}
7 changes: 7 additions & 0 deletions tests/Cassowary.Tests/TermTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ public class TermTest
{
private readonly Fixture _fixture = new();

[Fact(DisplayName = "Term equals term")]
public void TermEqualsTerm()
{
var guid = Guid.NewGuid();
new Term(new Variable(guid), 0.5).Should().Be(new Term(new Variable(guid), 0.5));
}

[Fact]
public void Or()
{
Expand Down
7 changes: 7 additions & 0 deletions tests/Cassowary.Tests/VariableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ namespace Cassowary.Tests;
public class VariableTest
{
private readonly Fixture _fixture = new();

[Fact]
public void VariableEqualsVariable()
{
var guid = Guid.NewGuid();
new Variable(guid).Should().Be(new Variable(guid));
}

[Fact]
public void AddWithValue()
Expand Down

0 comments on commit fd7c3bd

Please sign in to comment.