Skip to content

Commit

Permalink
MultiplicativeExpression (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbressler13 authored Feb 9, 2024
1 parent 06c76a9 commit b977663
Show file tree
Hide file tree
Showing 19 changed files with 790 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package xyz.lbres.exactnumbers.expressions

import xyz.lbres.exactnumbers.expressions.expression.SimpleExpression
import xyz.lbres.exactnumbers.expressions.term.Term
import java.math.BigDecimal

/**
* Representation of an expression, consisting of at least one term
Expand All @@ -10,11 +11,16 @@ sealed class Expression : Number() {
abstract operator fun unaryMinus(): Expression
abstract operator fun unaryPlus(): Expression
abstract fun inverse(): Expression
abstract fun getValue(): BigDecimal

// open operator fun plus(other: Expression): Expression = AdditiveExpression(constMultiSetOf(this, other))
// open operator fun minus(other: Expression): Expression = AdditiveExpression(constMultiSetOf(this, -other))
// open operator fun times(other: Expression): Expression = MultiplicativeExpression(constMultiSetOf(this, other))
// open operator fun div(other: Expression): Expression = MultiplicativeExpression(constMultiSetOf(this, other.inverse()))
// abstract fun isZero(): Boolean

// abstract operator fun plus(other: Expression): Expression
// abstract operator fun minus(other: Expression): Expression
// abstract operator fun times(other: Expression): Expression
// abstract operator fun div(other: Expression): Expression

abstract fun toTerm(): Term

companion object {
val ZERO: Expression = SimpleExpression(Term.ZERO)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
package xyz.lbres.exactnumbers.expressions

import xyz.lbres.exactnumbers.utils.castToByte
import xyz.lbres.exactnumbers.utils.castToChar
import xyz.lbres.exactnumbers.utils.castToDouble
import xyz.lbres.exactnumbers.utils.castToFloat
import xyz.lbres.exactnumbers.utils.castToInt
import xyz.lbres.exactnumbers.utils.castToLong
import xyz.lbres.exactnumbers.utils.castToShort
import java.math.BigDecimal

// internal implementation of expression
internal abstract class ExpressionImpl : Expression()
@Suppress("EqualsOrHashCode")
internal abstract class ExpressionImpl : Expression() {
override fun getValue(): BigDecimal = toTerm().getValue()

override fun equals(other: Any?): Boolean = other is Expression && toTerm() == other.toTerm()

override fun toByte(): Byte = castToByte(getValue(), this, "Expression")
override fun toChar(): Char = castToChar(getValue(), this, "Expression")
override fun toShort(): Short = castToShort(getValue(), this, "Expression")
override fun toInt(): Int = castToInt(getValue(), this, "Expression")
override fun toLong(): Long = castToLong(getValue(), this, "Expression")
override fun toFloat(): Float = castToFloat(getValue(), this, "Expression")
override fun toDouble(): Double = castToDouble(getValue(), this, "Expression")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package xyz.lbres.exactnumbers.expressions.expression

import xyz.lbres.exactnumbers.expressions.Expression
import xyz.lbres.exactnumbers.expressions.ExpressionImpl
import xyz.lbres.exactnumbers.expressions.term.Term
import xyz.lbres.exactnumbers.utils.createHashCode
import xyz.lbres.exactnumbers.utils.getOrSet
import xyz.lbres.kotlinutils.bigdecimal.ext.isZero
import xyz.lbres.kotlinutils.collection.ext.toConstMultiSet
import xyz.lbres.kotlinutils.set.multiset.anyConsistent
import xyz.lbres.kotlinutils.set.multiset.const.ConstMultiSet
import xyz.lbres.kotlinutils.set.multiset.const.constMultiSetOf
import xyz.lbres.kotlinutils.set.multiset.mapToSetConsistent

/**
* Expression which is the product of several other expressions
*/
@Suppress("EqualsOrHashCode")
internal class MultiplicativeExpression private constructor(expressions: ConstMultiSet<Expression>) : ExpressionImpl() {
private val expressions: ConstMultiSet<Expression>
private var term: Term? = null

init {
this.expressions = when {
expressions.isEmpty() -> throw Exception("Invalid expression")
expressions.anyConsistent { it.getValue().isZero() } -> constMultiSetOf(ZERO)
else -> expressions
}
}

constructor(expr1: Expression, expr2: Expression) : this(constMultiSetOf(expr1, expr2))

override fun unaryPlus(): Expression = this
override fun unaryMinus(): Expression {
val newExpressions = (expressions + constMultiSetOf(-ONE)).toConstMultiSet()
return MultiplicativeExpression(newExpressions)
}

override fun inverse(): Expression {
val newExpressions = expressions.mapToSetConsistent { it.inverse() }.toConstMultiSet()
return MultiplicativeExpression(newExpressions)
}

override fun toTerm(): Term {
return getOrSet({ term }, { term = it }) {
expressions.fold(Term.ONE) { acc, expr -> acc * expr.toTerm() }.getSimplified()
}
}

override fun hashCode(): Int = createHashCode(listOf(expressions, "MultiplicativeExpression"))

override fun toString(): String = "(${expressions.joinToString("x")})"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,20 @@ package xyz.lbres.exactnumbers.expressions.expression
import xyz.lbres.exactnumbers.expressions.Expression
import xyz.lbres.exactnumbers.expressions.ExpressionImpl
import xyz.lbres.exactnumbers.expressions.term.Term
import xyz.lbres.exactnumbers.utils.castToByte
import xyz.lbres.exactnumbers.utils.castToChar
import xyz.lbres.exactnumbers.utils.castToDouble
import xyz.lbres.exactnumbers.utils.castToFloat
import xyz.lbres.exactnumbers.utils.castToInt
import xyz.lbres.exactnumbers.utils.castToLong
import xyz.lbres.exactnumbers.utils.castToShort
import xyz.lbres.exactnumbers.utils.createHashCode

/**
* Expression consisting of a single term
*/
@Suppress("EqualsOrHashCode")
internal class SimpleExpression(private val term: Term) : ExpressionImpl() {
override fun unaryMinus(): Expression = SimpleExpression(-term)
override fun unaryPlus(): Expression = this
override fun unaryMinus(): Expression = SimpleExpression(-term)
override fun inverse(): Expression = SimpleExpression(term.inverse())

override fun equals(other: Any?): Boolean = other is SimpleExpression && term == other.term
override fun hashCode(): Int = createHashCode(listOf(term, "Expression"))
override fun toTerm(): Term = term

override fun toByte(): Byte = castToByte(term.getValue(), this, "Expression")
override fun toChar(): Char = castToChar(term.getValue(), this, "Expression")
override fun toShort(): Short = castToShort(term.getValue(), this, "Expression")
override fun toInt(): Int = castToInt(term.getValue(), this, "Expression")
override fun toLong(): Long = castToLong(term.getValue(), this, "Expression")
override fun toFloat(): Float = castToFloat(term.getValue(), this, "Expression")
override fun toDouble(): Double = castToDouble(term.getValue(), this, "Expression")
override fun hashCode(): Int = createHashCode(listOf(term, "Expression"))

override fun toString(): String = "($term)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import xyz.lbres.exactnumbers.ext.divideBy
import xyz.lbres.exactnumbers.irrationals.IrrationalNumber
import xyz.lbres.exactnumbers.utils.createHashCode
import xyz.lbres.exactnumbers.utils.divideByZero
import xyz.lbres.exactnumbers.utils.getOrSet
import xyz.lbres.kotlinutils.collection.ext.toConstMultiSet
import xyz.lbres.kotlinutils.general.simpleIf
import xyz.lbres.kotlinutils.set.multiset.anyConsistent
Expand Down Expand Up @@ -80,11 +81,7 @@ internal class TermImpl(coefficient: ExactFraction, factors: ConstMultiSet<Irrat
* @return [Term] simplified version of this term
*/
override fun getSimplified(): Term {
if (simplified == null) {
simplified = createSimplifiedTerm(coefficient, factorTypeMapping)
}

return simplified!!
return getOrSet({ simplified }, { simplified = it }) { createSimplifiedTerm(coefficient, factorTypeMapping) }
}

/**
Expand All @@ -94,17 +91,14 @@ internal class TermImpl(coefficient: ExactFraction, factors: ConstMultiSet<Irrat
* @return [BigDecimal]
*/
override fun getValue(): BigDecimal {
if (value == null) {
return getOrSet({ value }, { value = it }) {
val simplified = getSimplified()

val factorProduct = simplified.factors.fold(BigDecimal.ONE) { acc, number -> acc * number.getValue() }
val numeratorProduct = simplified.coefficient.numerator.toBigDecimal() * factorProduct

val result = numeratorProduct.divideBy(simplified.coefficient.denominator.toBigDecimal())
value = result
numeratorProduct.divideBy(simplified.coefficient.denominator.toBigDecimal())
}

return value!!
}

/**
Expand All @@ -120,19 +114,17 @@ internal class TermImpl(coefficient: ExactFraction, factors: ConstMultiSet<Irrat
override fun toExpression(): Expression = SimpleExpression(this)

override fun toString(): String {
if (string == null) {
return getOrSet({ string }, { string = it }) {
val fractionString = coefficient.toFractionString()
val coeffString = simpleIf(fractionString.contains("/"), "[$fractionString]", fractionString)
val factorString = factorSet.joinToString("x")

string = when {
when {
factorString.isEmpty() -> "<$coeffString>"
coeffString == "1" -> "<$factorString>"
else -> "<${coeffString}x$factorString>"
}
}

return string!!
}

override fun equals(other: Any?): Boolean = other is Term && getValue() == other.getValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import xyz.lbres.exactnumbers.ext.divideBy
import xyz.lbres.exactnumbers.ext.isWholeNumber
import xyz.lbres.exactnumbers.utils.createHashCode
import xyz.lbres.exactnumbers.utils.divideByZero
import xyz.lbres.exactnumbers.utils.getOrSet
import xyz.lbres.kotlinutils.biginteger.ext.isZero
import xyz.lbres.kotlinutils.general.simpleIf
import java.math.BigDecimal
Expand Down Expand Up @@ -70,15 +71,13 @@ internal class LogImpl private constructor(

// TODO improve simplification using bases
override fun getSimplified(): Pair<ExactFraction, Log> {
if (simplified == null) {
simplified = when {
return getOrSet({ simplified }, { simplified = it }) {
when {
fullySimplified || isZero() || equals(ONE) -> Pair(ExactFraction.ONE, this)
isRational() -> Pair(getRationalValue()!!, ONE)
else -> Pair(ExactFraction.ONE, LogImpl(argument, base, isInverted, fullySimplified = true))
}
}

return simplified!!
}

// multiplicative inverse, *not* inverse log
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import xyz.lbres.exactnumbers.ext.divideBy
import xyz.lbres.exactnumbers.ext.isWholeNumber
import xyz.lbres.exactnumbers.utils.createHashCode
import xyz.lbres.exactnumbers.utils.divideByZero
import xyz.lbres.exactnumbers.utils.getOrSet
import java.math.BigDecimal

// implementation of Sqrt class
Expand Down Expand Up @@ -52,9 +53,11 @@ internal class SqrtImpl(override val radicand: ExactFraction) : Sqrt() {
}

override fun getSimplified(): Pair<ExactFraction, Sqrt> {
if (simplified == null && (radicand.isZero() || radicand == ExactFraction.ONE)) {
simplified = Pair(ExactFraction.ONE, this)
} else if (simplified == null) {
if (radicand.isZero() || radicand == ExactFraction.ONE) {
return Pair(ExactFraction.ONE, this)
}

return getOrSet({ simplified }, { simplified = it }) {
val numeratorWhole = extractWholeOf(radicand.numerator)
val denominatorWhole = extractWholeOf(radicand.denominator)
val whole = ExactFraction(numeratorWhole, denominatorWhole)
Expand All @@ -63,10 +66,8 @@ internal class SqrtImpl(override val radicand: ExactFraction) : Sqrt() {
val newDenominator = radicand.denominator / (denominatorWhole * denominatorWhole)
val newRadicand = ExactFraction(newNumerator, newDenominator)

simplified = Pair(whole, SqrtImpl(newRadicand))
Pair(whole, SqrtImpl(newRadicand))
}

return simplified!!
}

override fun compareTo(other: Sqrt): Int = radicand.compareTo(other.radicand)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package xyz.lbres.exactnumbers.utils

import xyz.lbres.kotlinutils.general.tryOrDefault
import xyz.lbres.kotlinutils.generic.ext.ifNull
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
Expand Down Expand Up @@ -30,6 +31,21 @@ internal fun getIntFromDecimal(decimal: BigDecimal, checkInt: (BigInteger) -> Bo
}
}

/**
* Update value of a variable if the current value is `null`, and return updated version
*
* @param getValue () -> T?: function to get value of variable
* @param setValue (T) -> Unit: function to update value of variable
* @param generateValue () -> T: function to generate a new value if [getValue] initially returns `null`
* @return T: the result of [getValue] or [generateValue]
*/
internal fun <T> getOrSet(getValue: () -> T?, setValue: (T) -> Unit, generateValue: () -> T): T {
return getValue().ifNull {
setValue(generateValue())
getValue()!!
}
}

/**
* Generate hash code from a list of values
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xyz.lbres.exactnumbers.expressions.expression

import xyz.lbres.exactnumbers.expressions.expression.multiplicative.* // ktlint-disable no-wildcard-imports no-unused-imports
import kotlin.test.Test

class MultiplicativeExpressionTest {
@Test fun testEquals() = runEqualsTests()
@Test fun testToString() = runToStringTests()

@Test fun testUnaryMinus() = runUnaryMinusTests()
@Test fun testUnaryPlus() = runUnaryPlusTests()
@Test fun testInverse() = runInverseTests()
@Test fun testGetValue() = runGetValueTests()

@Test fun testToTerm() = runToTermTests()
@Test fun testToByte() = runToByteTests()
@Test fun testToChar() = runToCharTests()
@Test fun testToShort() = runToShortTests()
@Test fun testToInt() = runToIntTests()
@Test fun testToLong() = runToLongTests()
@Test fun testToFloat() = runToFloatTests()
@Test fun testToDouble() = runToDoubleTests()
}
Loading

0 comments on commit b977663

Please sign in to comment.