Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix equality on expressions #13

Merged
merged 2 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading