first commit

This commit is contained in:
me
2026-04-08 21:25:17 +03:00
parent 3681b8eccd
commit 371b14c5e3
173 changed files with 14126 additions and 0 deletions

39
java/expression/Abs.java Normal file
View File

@@ -0,0 +1,39 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Abs extends AbstractExpression {
private final AbstractExpression operand;
public Abs(AbstractExpression operand) {
this.operand = operand;
}
private static int absInt(int n) {
if (n == Integer.MIN_VALUE) throw new OverflowException("abs");
return n < 0 ? -n : n;
}
@Override public int evaluate(int x) { return absInt(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return absInt(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return absInt(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).abs(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).abs(); }
@Override public String toString() { return "" + operand + ""; }
@Override public String toMiniString() { return "" + operand.toMiniString() + ""; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Abs)) return false;
return operand.equals(((Abs) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x41425321; }
}

View File

@@ -0,0 +1,114 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public abstract class AbstractBinaryOperation extends AbstractExpression {
protected final AbstractExpression left;
protected final AbstractExpression right;
protected AbstractBinaryOperation(
AbstractExpression left,
AbstractExpression right
) {
this.left = left;
this.right = right;
}
protected abstract String getOperator();
protected abstract int getPriority();
protected abstract boolean isRightAssoc();
protected abstract int applyInt(int a, int b);
protected abstract BigInteger applyBi(BigInteger a, BigInteger b);
protected abstract BigDecimal applyBd(BigDecimal a, BigDecimal b);
@Override
public int evaluate(int x) {
return applyInt(left.evaluate(x), right.evaluate(x));
}
@Override
public int evaluate(int x, int y, int z) {
return applyInt(left.evaluate(x, y, z), right.evaluate(x, y, z));
}
@Override
public int evaluate(List<Integer> vars) {
return applyInt(left.evaluate(vars), right.evaluate(vars));
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
return applyBi(left.evaluateBi(vars), right.evaluateBi(vars));
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
return applyBd(left.evaluateBd(vars), right.evaluateBd(vars));
}
@Override
public String toString() {
return "(" + left + " " + getOperator() + " " + right + ")";
}
@Override
public String toMiniString() {
return miniLeft() + " " + getOperator() + " " + miniRight();
}
private String miniLeft() {
if (
left instanceof AbstractBinaryOperation op &&
op.getPriority() < getPriority()
) {
return "(" + op.toMiniString() + ")";
}
return left.toMiniString();
}
private String miniRight() {
if (right instanceof AbstractBinaryOperation op) {
boolean samePriority = op.getPriority() == getPriority();
boolean lowerPriority = op.getPriority() < getPriority();
boolean needParens =
lowerPriority ||
(samePriority && isRightAssoc()) ||
(samePriority && op.isRightAssoc() && getPriority() > 1) ||
(samePriority &&
!getOperator().equals(op.getOperator()) &&
!op.isRightAssoc() &&
!isRightAssoc());
if (needParens) {
return "(" + op.toMiniString() + ")";
}
}
return right.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AbstractBinaryOperation other = (AbstractBinaryOperation) obj;
return left.equals(other.left) && right.equals(other.right);
}
@Override
public int hashCode() {
return (
31 * (31 * left.hashCode() + right.hashCode()) +
getClass().hashCode()
);
}
}

View File

@@ -0,0 +1,42 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public abstract class AbstractExpression
implements
Expression,
TripleExpression,
ListExpression,
BigIntegerListExpression,
BigDecimalListExpression
{
@Override
public abstract int evaluate(int x);
@Override
public abstract int evaluate(int x, int y, int z);
@Override
public abstract int evaluate(List<Integer> vars);
@Override
public abstract BigInteger evaluateBi(List<BigInteger> vars);
@Override
public abstract BigDecimal evaluateBd(List<BigDecimal> vars);
@Override
public abstract String toString();
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
}

44
java/expression/Add.java Normal file
View File

@@ -0,0 +1,44 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Add extends AbstractBinaryOperation {
public Add(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "+";
}
@Override
protected int getPriority() {
return 1;
}
@Override
protected boolean isRightAssoc() {
return false;
}
@Override
protected int applyInt(int a, int b) {
return a + b;
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.add(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
return a.add(b);
}
}

View File

@@ -0,0 +1,363 @@
package expression;
import base.Asserts;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.IntStream;
/**
* One-argument arithmetic expression over {@link BigDecimal}s.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface BigDecimalListExpression extends ToMiniString {
BigDecimal evaluateBd(List<BigDecimal> variables);
// Tests follow. You may temporarily remove everything til the end.
Add EXAMPLE = new Add(
new Subtract(new Variable(0), new Const(BigDecimal.ONE)),
new Multiply(new Variable(1), new Const(BigDecimal.TEN))
);
Type<BigDecimal> TYPE = new Type<>(
v -> new BigDecimal(v + ".000"),
random -> BigDecimal.valueOf(random.getRandom().nextGaussian()),
BigDecimal.class
);
ExpressionKind<BigDecimalListExpression, BigDecimal> KIND =
new ExpressionKind<>(
TYPE,
BigDecimalListExpression.class,
(r, c) ->
IntStream.range(0, c)
.mapToObj(name ->
Pair.<String, BigDecimalListExpression>of(
"$" + name,
new Variable(name)
)
)
.toList(),
(expr, variables, values) -> expr.evaluateBd(values)
);
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals(
"Example toString()",
"(($0 - 1) + ($1 * 10))",
EXAMPLE.toString()
);
Asserts.assertEquals(
EXAMPLE + " at (2, 3)",
BigDecimal.valueOf(31),
EXAMPLE.evaluateBd(
List.of(BigDecimal.valueOf(2), BigDecimal.valueOf(3))
)
);
final Variable vx = new Variable(0);
final Variable vy = new Variable(1);
return new ExpressionTester<>(
counter,
KIND,
c -> v -> c,
(op, a, b) -> v -> op.apply(a.evaluateBd(v), b.evaluateBd(v)),
BigDecimal::add,
BigDecimal::subtract,
BigDecimal::multiply,
BigDecimal::divide
)
.basic("10", "10", v -> v(10), c(10))
.basic("$x", "$x", BigDecimalListExpression::x, vx)
.basic("$y", "$y", BigDecimalListExpression::y, vy)
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
.basic(
"(2 - $x)",
"2 - $x",
v -> v(2).subtract(x(v)),
new Subtract(c(2), vx)
)
.basic(
"(3 * $x)",
"3 * $x",
v -> v(3).multiply(x(v)),
new Multiply(c(3), vx)
)
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
.basic(
"($x / -2)",
"$x / -2",
v -> x(v).divide(v(-2)),
new Divide(vx, c(-2))
)
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
.basic(
"((1 + 2) + 3)",
"1 + 2 + 3",
v -> v(6),
new Add(new Add(c(1), c(2)), c(3))
)
.basic(
"(1 + (2 * 3))",
"1 + 2 * 3",
v -> v(7),
new Add(c(1), new Multiply(c(2), c(3)))
)
.basic(
"(1 - (2 * 3))",
"1 - 2 * 3",
v -> v(-5),
new Subtract(c(1), new Multiply(c(2), c(3)))
)
.basic(
"(1 + (2 + 3))",
"1 + 2 + 3",
v -> v(6),
new Add(c(1), new Add(c(2), c(3)))
)
.basic(
"((1 - 2) - 3)",
"1 - 2 - 3",
v -> v(-4),
new Subtract(new Subtract(c(1), c(2)), c(3))
)
.basic(
"(1 - (2 - 3))",
"1 - (2 - 3)",
v -> v(2),
new Subtract(c(1), new Subtract(c(2), c(3)))
)
.basic(
"((1 * 2) * 3)",
"1 * 2 * 3",
v -> v(6),
new Multiply(new Multiply(c(1), c(2)), c(3))
)
.basic(
"(1 * (2 * 3))",
"1 * 2 * 3",
v -> v(6),
new Multiply(c(1), new Multiply(c(2), c(3)))
)
.basic(
"((10 / 2) / 3)",
"10 / 2 / 3",
v -> v(10).divide(v(2)).divide(v(3)),
new Divide(new Divide(c(10), c(2)), c(3))
)
.basic(
"(10 / (3 / 2))",
"10 / (3 / 2)",
v -> v(10).divide(v(3).divide(v(2))),
new Divide(c(10), new Divide(c(3), c(2)))
)
.basic(
"(($x * $x) + (($x - 1) / 10))",
"$x * $x + ($x - 1) / 10",
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
new Add(
new Multiply(vx, vx),
new Divide(new Subtract(vx, c(1)), c(10))
)
)
.basic(
"($x * -1000000000)",
"$x * -1000000000",
v -> x(v).multiply(v(-1_000_000_000)),
new Multiply(vx, c(-1_000_000_000))
)
.basic(
"($x * -1000000000000000)",
"$x * -1000000000000000",
v -> x(v).multiply(v(-1_000_000_000_000_000L)),
new Multiply(vx, c(-1_000_000_000_000_000L))
)
.basic(
"(10 / $x)",
"10 / $x",
v -> v(10).divide(x(v)),
new Divide(c(10), vx)
)
.basic(
"($x / $x)",
"$x / $x",
v -> x(v).divide(x(v)),
new Divide(vx, vx)
)
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
.advanced(
"($x - 1)",
"$x - 1",
v -> x(v).subtract(v(1)),
new Subtract(vx, c(1))
)
.advanced(
"(1 * 2)",
"1 * 2",
v -> v(1 * 2),
new Multiply(c(1), c(2))
)
.advanced(
"($x / 1)",
"$x / 1",
v -> x(v).divide(v(1)),
new Divide(vx, c(1))
)
.advanced(
"(1 + (2 + 1))",
"1 + 2 + 1",
v -> v(1 + 2 + 1),
new Add(c(1), new Add(c(2), c(1)))
)
.advanced(
"($x - ($x - 1))",
"$x - ($x - 1)",
v -> x(v).subtract(x(v).subtract(v(1))),
new Subtract(vx, new Subtract(vx, c(1)))
)
.advanced(
"(2 * ($x / 1))",
"2 * ($x / 1)",
v -> v(2).multiply(x(v).divide(v(1))),
new Multiply(c(2), new Divide(vx, c(1)))
)
.advanced(
"(2 / ($x - 1))",
"2 / ($x - 1)",
v -> v(2).divide(x(v).subtract(v(1))),
new Divide(c(2), new Subtract(vx, c(1)))
)
.advanced(
"((1 * 2) + $x)",
"1 * 2 + $x",
v -> v(1 * 2).add(x(v)),
new Add(new Multiply(c(1), c(2)), vx)
)
.advanced(
"(($x - 1) - 2)",
"$x - 1 - 2",
v -> x(v).subtract(v(3)),
new Subtract(new Subtract(vx, c(1)), c(2))
)
.advanced(
"(($x / 1) * 2)",
"$x / 1 * 2",
v -> x(v).multiply(v(2)),
new Multiply(new Divide(vx, c(1)), c(2))
)
.advanced(
"((2 + 1) / 1)",
"(2 + 1) / 1",
v -> v(3),
new Divide(new Add(c(2), c(1)), c(1))
)
.advanced(
"(1 + (1 + (2 + 1)))",
"1 + 1 + 2 + 1",
v -> v(1 + 1 + 2 + 1),
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"($x - ((1 * 2) + $x))",
"$x - (1 * 2 + $x)",
v -> x(v).subtract(v(1 * 2).add(x(v))),
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
)
.advanced(
"($x * (2 / ($x - 1)))",
"$x * (2 / ($x - 1))",
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
)
.advanced(
"($x / (1 + (2 + 1)))",
"$x / (1 + 2 + 1)",
v -> x(v).divide(v(1 + 2 + 1)),
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"((1 * 2) + (2 + 1))",
"1 * 2 + 2 + 1",
v -> v(1 * 2 + 2 + 1),
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
)
.advanced(
"((2 + 1) - (2 + 1))",
"2 + 1 - (2 + 1)",
v -> v(2 + 1 - (2 + 1)),
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
)
.advanced(
"(($x - 1) * ($x / 1))",
"($x - 1) * ($x / 1)",
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
)
.advanced(
"(($x - 1) / (1 * 2))",
"($x - 1) / (1 * 2)",
v -> x(v).subtract(v(1)).divide(v(2)),
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
)
.advanced(
"((($x - 1) - 2) + $x)",
"$x - 1 - 2 + $x",
v -> x(v).subtract(v(3)).add(x(v)),
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
)
.advanced(
"(((1 * 2) + $x) - 1)",
"1 * 2 + $x - 1",
v -> v(1).add(x(v)),
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
)
.advanced(
"(((2 + 1) / 1) * $x)",
"(2 + 1) / 1 * $x",
v -> v(3).multiply(x(v)),
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
)
.advanced(
"((2 / ($x - 1)) / 2)",
"2 / ($x - 1) / 2",
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
);
}
private static BigDecimal x(final List<BigDecimal> vars) {
return vars.get(0);
}
private static BigDecimal y(final List<BigDecimal> vars) {
return vars.get(1);
}
private static Const c(final BigDecimal v) {
return TYPE.constant(v);
}
private static Const c(final long v) {
return TYPE.constant(v(v));
}
private static BigDecimal v(final long v) {
return BigDecimal.valueOf(v);
}
static void main(final String... args) {
TripleExpression.SELECTOR.variant(
"BigDecimalList",
ExpressionTest.v(BigDecimalListExpression::tester)
).main(args);
}
}

View File

@@ -0,0 +1,357 @@
package expression;
import base.Asserts;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.IntStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface BigIntegerListExpression extends ToMiniString {
BigInteger evaluateBi(List<BigInteger> variables);
// Tests follow. You may temporarily remove everything til the end.
Add EXAMPLE = new Add(
new Subtract(new Variable(0), new Const(BigInteger.ONE)),
new Multiply(new Variable(1), new Const(BigInteger.TEN))
);
Type<BigInteger> TYPE = new Type<>(
BigInteger::valueOf,
random -> v(random.getRandom().nextLong()),
BigInteger.class
);
ExpressionKind<BigIntegerListExpression, BigInteger> KIND =
new ExpressionKind<>(
TYPE,
BigIntegerListExpression.class,
(r, c) ->
IntStream.range(0, c)
.mapToObj(name ->
Pair.<String, BigIntegerListExpression>of(
"$" + name,
new Variable(name)
)
)
.toList(),
(expr, variables, values) -> expr.evaluateBi(values)
);
@SuppressWarnings("PointlessArithmeticExpression")
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals(
"Example toString()",
"(($0 - 1) + ($1 * 10))",
EXAMPLE.toString()
);
Asserts.assertEquals(
EXAMPLE + " at (2, 3)",
BigInteger.valueOf(31),
EXAMPLE.evaluateBi(
List.of(BigInteger.valueOf(2), BigInteger.valueOf(3))
)
);
final Variable vx = new Variable(0);
final Variable vy = new Variable(1);
return new ExpressionTester<>(
counter,
KIND,
c -> v -> c,
(op, a, b) -> v -> op.apply(a.evaluateBi(v), b.evaluateBi(v)),
BigInteger::add,
BigInteger::subtract,
BigInteger::multiply,
BigInteger::divide
)
.basic("10", "10", v -> v(10), c(10))
.basic("$x", "$x", BigIntegerListExpression::x, vx)
.basic("$y", "$y", BigIntegerListExpression::y, vy)
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
.basic(
"(2 - $x)",
"2 - $x",
v -> v(2).subtract(x(v)),
new Subtract(c(2), vx)
)
.basic(
"(3 * $x)",
"3 * $x",
v -> v(3).multiply(x(v)),
new Multiply(c(3), vx)
)
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
.basic(
"($x / -2)",
"$x / -2",
v -> x(v).divide(v(-2)),
new Divide(vx, c(-2))
)
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
.basic(
"((1 + 2) + 3)",
"1 + 2 + 3",
v -> v(6),
new Add(new Add(c(1), c(2)), c(3))
)
.basic(
"(1 + (2 * 3))",
"1 + 2 * 3",
v -> v(7),
new Add(c(1), new Multiply(c(2), c(3)))
)
.basic(
"(1 - (2 * 3))",
"1 - 2 * 3",
v -> v(-5),
new Subtract(c(1), new Multiply(c(2), c(3)))
)
.basic(
"(1 + (2 + 3))",
"1 + 2 + 3",
v -> v(6),
new Add(c(1), new Add(c(2), c(3)))
)
.basic(
"((1 - 2) - 3)",
"1 - 2 - 3",
v -> v(-4),
new Subtract(new Subtract(c(1), c(2)), c(3))
)
.basic(
"(1 - (2 - 3))",
"1 - (2 - 3)",
v -> v(2),
new Subtract(c(1), new Subtract(c(2), c(3)))
)
.basic(
"((1 * 2) * 3)",
"1 * 2 * 3",
v -> v(6),
new Multiply(new Multiply(c(1), c(2)), c(3))
)
.basic(
"(1 * (2 * 3))",
"1 * 2 * 3",
v -> v(6),
new Multiply(c(1), new Multiply(c(2), c(3)))
)
.basic(
"((10 / 2) / 3)",
"10 / 2 / 3",
v -> v(10 / 2 / 3),
new Divide(new Divide(c(10), c(2)), c(3))
)
.basic(
"(10 / (3 / 2))",
"10 / (3 / 2)",
v -> v(10 / (3 / 2)),
new Divide(c(10), new Divide(c(3), c(2)))
)
.basic(
"(($x * $x) + (($x - 1) / 10))",
"$x * $x + ($x - 1) / 10",
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
new Add(
new Multiply(vx, vx),
new Divide(new Subtract(vx, c(1)), c(10))
)
)
.basic(
"($x * -1000000000)",
"$x * -1000000000",
v -> x(v).multiply(v(-1_000_000_000)),
new Multiply(vx, c(-1_000_000_000))
)
.basic(
"($x * -1000000000000000)",
"$x * -1000000000000000",
v -> x(v).multiply(v(-1_000_000_000_000_000L)),
new Multiply(vx, c(-1_000_000_000_000_000L))
)
.basic(
"(10 / $x)",
"10 / $x",
v -> v(10).divide(x(v)),
new Divide(c(10), vx)
)
.basic(
"($x / $x)",
"$x / $x",
v -> x(v).divide(x(v)),
new Divide(vx, vx)
)
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
.advanced(
"($x - 1)",
"$x - 1",
v -> x(v).subtract(v(1)),
new Subtract(vx, c(1))
)
.advanced(
"(1 * 2)",
"1 * 2",
v -> v(1 * 2),
new Multiply(c(1), c(2))
)
.advanced(
"($x / 1)",
"$x / 1",
v -> x(v).divide(v(1)),
new Divide(vx, c(1))
)
.advanced(
"(1 + (2 + 1))",
"1 + 2 + 1",
v -> v(1 + 2 + 1),
new Add(c(1), new Add(c(2), c(1)))
)
.advanced(
"($x - ($x - 1))",
"$x - ($x - 1)",
v -> x(v).subtract(x(v).subtract(v(1))),
new Subtract(vx, new Subtract(vx, c(1)))
)
.advanced(
"(2 * ($x / 1))",
"2 * ($x / 1)",
v -> v(2).multiply(x(v).divide(v(1))),
new Multiply(c(2), new Divide(vx, c(1)))
)
.advanced(
"(2 / ($x - 1))",
"2 / ($x - 1)",
v -> v(2).divide(x(v).subtract(v(1))),
new Divide(c(2), new Subtract(vx, c(1)))
)
.advanced(
"((1 * 2) + $x)",
"1 * 2 + $x",
v -> v(1 * 2).add(x(v)),
new Add(new Multiply(c(1), c(2)), vx)
)
.advanced(
"(($x - 1) - 2)",
"$x - 1 - 2",
v -> x(v).subtract(v(3)),
new Subtract(new Subtract(vx, c(1)), c(2))
)
.advanced(
"(($x / 1) * 2)",
"$x / 1 * 2",
v -> x(v).multiply(v(2)),
new Multiply(new Divide(vx, c(1)), c(2))
)
.advanced(
"((2 + 1) / 1)",
"(2 + 1) / 1",
v -> v(3),
new Divide(new Add(c(2), c(1)), c(1))
)
.advanced(
"(1 + (1 + (2 + 1)))",
"1 + 1 + 2 + 1",
v -> v(1 + 1 + 2 + 1),
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"($x - ((1 * 2) + $x))",
"$x - (1 * 2 + $x)",
v -> x(v).subtract(v(1 * 2).add(x(v))),
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
)
.advanced(
"($x * (2 / ($x - 1)))",
"$x * (2 / ($x - 1))",
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
)
.advanced(
"($x / (1 + (2 + 1)))",
"$x / (1 + 2 + 1)",
v -> x(v).divide(v(1 + 2 + 1)),
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"((1 * 2) + (2 + 1))",
"1 * 2 + 2 + 1",
v -> v(1 * 2 + 2 + 1),
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
)
.advanced(
"((2 + 1) - (2 + 1))",
"2 + 1 - (2 + 1)",
v -> v(2 + 1 - (2 + 1)),
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
)
.advanced(
"(($x - 1) * ($x / 1))",
"($x - 1) * ($x / 1)",
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
)
.advanced(
"(($x - 1) / (1 * 2))",
"($x - 1) / (1 * 2)",
v -> x(v).subtract(v(1)).divide(v(2)),
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
)
.advanced(
"((($x - 1) - 2) + $x)",
"$x - 1 - 2 + $x",
v -> x(v).subtract(v(3)).add(x(v)),
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
)
.advanced(
"(((1 * 2) + $x) - 1)",
"1 * 2 + $x - 1",
v -> v(1).add(x(v)),
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
)
.advanced(
"(((2 + 1) / 1) * $x)",
"(2 + 1) / 1 * $x",
v -> v(3).multiply(x(v)),
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
)
.advanced(
"((2 / ($x - 1)) / 2)",
"2 / ($x - 1) / 2",
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
);
}
private static BigInteger x(final List<BigInteger> vars) {
return vars.get(0);
}
private static BigInteger y(final List<BigInteger> vars) {
return vars.get(1);
}
private static Const c(final long v) {
return TYPE.constant(v(v));
}
private static BigInteger v(final long v) {
return BigInteger.valueOf(v);
}
static void main(final String... args) {
TripleExpression.SELECTOR.variant(
"BigIntegerList",
ExpressionTest.v(BigIntegerListExpression::tester)
).main(args);
}
}

62
java/expression/Cbrt.java Normal file
View File

@@ -0,0 +1,62 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Cbrt extends AbstractExpression {
private final AbstractExpression operand;
public Cbrt(AbstractExpression operand) {
this.operand = operand;
}
private static int cbrtInt(int n) {
if (n == 0) return 0;
boolean negative = n < 0;
int abs = (n == Integer.MIN_VALUE) ? Integer.MAX_VALUE : (negative ? -n : n);
int lo = 0, hi = 1290;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
if (mid * mid * mid <= abs) {
lo = mid;
} else {
hi = mid - 1;
}
}
return negative ? -lo : lo;
}
@Override public int evaluate(int x) { return cbrtInt(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return cbrtInt(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return cbrtInt(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) {
throw new UnsupportedOperationException("∛ not supported for BigInteger");
}
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) {
throw new UnsupportedOperationException("∛ not supported for BigDecimal");
}
@Override public String toString() { return "∛(" + operand + ")"; }
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "∛(" + operand.toMiniString() + ")";
}
return "" + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Cbrt)) return false;
return operand.equals(((Cbrt) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x43425254; }
}

View File

@@ -0,0 +1,75 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Ceiling extends AbstractExpression {
private final AbstractExpression operand;
public Ceiling(AbstractExpression operand) {
this.operand = operand;
}
private static int ceilInt(int n) {
return Math.ceilDiv(n, 1000) * 1000;
}
@Override
public int evaluate(int x) {
return ceilInt(operand.evaluate(x));
}
@Override
public int evaluate(int x, int y, int z) {
return ceilInt(operand.evaluate(x, y, z));
}
@Override
public int evaluate(List<Integer> vars) {
return ceilInt(operand.evaluate(vars));
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
throw new UnsupportedOperationException(
"ceiling not supported for BigInteger"
);
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
throw new UnsupportedOperationException(
"ceiling not supported for BigDecimal"
);
}
@Override
public String toString() {
return "ceiling(" + operand + ")";
}
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "ceiling(" + operand.toMiniString() + ")";
}
return "ceiling " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Ceiling)) return false;
return operand.equals(((Ceiling) obj).operand);
}
@Override
public int hashCode() {
return operand.hashCode() ^ 0x43454C47;
}
}

View File

@@ -0,0 +1,46 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Clear extends AbstractBinaryOperation {
public Clear(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "clear";
}
@Override
protected int getPriority() {
return 0;
}
@Override
protected boolean isRightAssoc() {
return true;
}
@Override
protected int applyInt(int a, int b) {
return a & ~(1 << b);
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.clearBit(b.intValueExact());
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
throw new UnsupportedOperationException(
"clear not supported for BigDecimal"
);
}
}

View File

@@ -0,0 +1,84 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Const extends AbstractExpression {
private final int intValue;
private final BigInteger biValue;
private final BigDecimal bdValue;
private final String repr;
public Const(int value) {
this.intValue = value;
this.biValue = BigInteger.valueOf(value);
this.bdValue = BigDecimal.valueOf(value);
this.repr = Integer.toString(value);
}
public Const(BigInteger value) {
this.intValue = value.intValueExact();
this.biValue = value;
this.bdValue = new BigDecimal(value);
this.repr = value.toString();
}
public Const(BigDecimal value) {
this.bdValue = value;
this.biValue = value.toBigIntegerExact();
this.intValue = biValue.intValueExact();
this.repr = value.toPlainString();
}
@Override
public int evaluate(int x) {
return intValue;
}
@Override
public int evaluate(int x, int y, int z) {
return intValue;
}
@Override
public int evaluate(List<Integer> vars) {
return intValue;
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
return biValue;
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
return bdValue;
}
@Override
public String toString() {
return repr;
}
@Override
public String toMiniString() {
return repr;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Const)) return false;
return repr.equals(((Const) obj).repr);
}
@Override
public int hashCode() {
return repr.hashCode();
}
}

55
java/expression/Cube.java Normal file
View File

@@ -0,0 +1,55 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Cube extends AbstractExpression {
private final AbstractExpression operand;
public Cube(AbstractExpression operand) {
this.operand = operand;
}
private static int checkedMul(int a, int b) {
if (a == 0 || b == 0) return 0;
if (a == Integer.MIN_VALUE || b == Integer.MIN_VALUE) throw new OverflowException("cube");
int r = a * b;
if (r / a != b) throw new OverflowException("cube");
return r;
}
private static int cubeInt(int n) {
return checkedMul(checkedMul(n, n), n);
}
@Override public int evaluate(int x) { return cubeInt(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return cubeInt(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return cubeInt(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).pow(3); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).pow(3); }
@Override public String toString() { return "(" + operand + ""; }
@Override
public String toMiniString() {
String s = operand.toMiniString();
boolean needParens = operand instanceof AbstractBinaryOperation
|| operand instanceof Cbrt
|| operand instanceof Sqrt
|| (s.startsWith("-") && (s.length() < 2 || !Character.isDigit(s.charAt(1))));
return needParens ? "(" + s + "" : s + "³";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Cube)) return false;
return operand.equals(((Cube) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x43554245; }
}

View File

@@ -0,0 +1,83 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Digits extends AbstractExpression {
private final AbstractExpression operand;
public Digits(AbstractExpression operand) {
this.operand = operand;
}
private static int digitsSum(int n) {
if (n == 0) return 0;
boolean negative = n < 0;
long abs = Math.abs((long) n);
int sum = 0;
while (abs > 0) {
sum += (int) (abs % 10);
abs /= 10;
}
return negative ? -sum : sum;
}
@Override
public int evaluate(int x) {
return digitsSum(operand.evaluate(x));
}
@Override
public int evaluate(int x, int y, int z) {
return digitsSum(operand.evaluate(x, y, z));
}
@Override
public int evaluate(List<Integer> vars) {
return digitsSum(operand.evaluate(vars));
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
throw new UnsupportedOperationException(
"digits not supported for BigInteger"
);
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
throw new UnsupportedOperationException(
"digits not supported for BigDecimal"
);
}
@Override
public String toString() {
return "digits(" + operand + ")";
}
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "digits(" + operand.toMiniString() + ")";
}
return "digits " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Digits)) return false;
return operand.equals(((Digits) obj).operand);
}
@Override
public int hashCode() {
return operand.hashCode() ^ 0x44494754;
}
}

View File

@@ -0,0 +1,45 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Divide extends AbstractBinaryOperation {
public Divide(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "/";
}
@Override
protected int getPriority() {
return 2;
}
@Override
protected boolean isRightAssoc() {
return true;
}
@Override
protected int applyInt(int a, int b) {
return a / b;
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.divide(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
return a.divide(b, MathContext.DECIMAL128);
}
}

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class DivisionByZeroException extends ArithmeticException {
public DivisionByZeroException() {
super("division by zero");
}
}

View File

@@ -0,0 +1,285 @@
package expression;
import base.Asserts;
import base.ExtendedRandom;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.util.List;
/**
* One-argument arithmetic expression over integers.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface Expression extends ToMiniString {
int evaluate(int x);
// Tests follow. You may temporarily remove everything til the end.
Subtract EXAMPLE = new Subtract(
new Multiply(new Const(2), new Variable("x")),
new Const(3)
);
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
ExpressionKind<Expression, Integer> KIND = new ExpressionKind<>(
TYPE,
Expression.class,
List.of(Pair.of("x", new Variable("x"))),
(expr, variables, values) -> expr.evaluate(values.get(0))
);
private static Const c(final int c) {
return new Const(c);
}
@SuppressWarnings({ "PointlessArithmeticExpression", "Convert2MethodRef" })
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals(
"Example toString()",
"((2 * x) - 3)",
EXAMPLE.toString()
);
Asserts.assertEquals("Example at 5", 7, EXAMPLE.evaluate(5));
Asserts.assertTrue(
"Example equals 1",
new Multiply(new Const(2), new Variable("x")).equals(
new Multiply(new Const(2), new Variable("x"))
)
);
Asserts.assertTrue(
"Example equals 2",
!new Multiply(new Const(2), new Variable("x")).equals(
new Multiply(new Variable("x"), new Const(2))
)
);
final Variable vx = new Variable("x");
final Const c1 = c(1);
final Const c2 = c(2);
return new ExpressionTester<>(
counter,
KIND,
c -> x -> c,
(op, a, b) -> x -> op.apply(a.evaluate(x), b.evaluate(x)),
(a, b) -> a + b,
(a, b) -> a - b,
(a, b) -> a * b,
(a, b) -> a / b
)
.basic("10", "10", x -> 10, c(10))
.basic("x", "x", x -> x, vx)
.basic("(x + 2)", "x + 2", x -> x + 2, new Add(vx, c(2)))
.basic("(2 - x)", "2 - x", x -> 2 - x, new Subtract(c(2), vx))
.basic("(3 * x)", "3 * x", x -> 3 * x, new Multiply(c(3), vx))
.basic("(x + x)", "x + x", x -> x + x, new Add(vx, vx))
.basic("(x / -2)", "x / -2", x -> -x / 2, new Divide(vx, c(-2)))
.basic("(2 + x)", "2 + x", x -> 2 + x, new Add(c(2), vx))
.basic(
"((1 + 2) + 3)",
"1 + 2 + 3",
x -> 6,
new Add(new Add(c(1), c(2)), c(3))
)
.basic(
"(1 + (2 + 3))",
"1 + 2 + 3",
x -> 6,
new Add(c(1), new Add(c(2), c(3)))
)
.basic(
"((1 - 2) - 3)",
"1 - 2 - 3",
x -> -4,
new Subtract(new Subtract(c(1), c(2)), c(3))
)
.basic(
"(1 - (2 - 3))",
"1 - (2 - 3)",
x -> 2,
new Subtract(c(1), new Subtract(c(2), c(3)))
)
.basic(
"((1 * 2) * 3)",
"1 * 2 * 3",
x -> 6,
new Multiply(new Multiply(c(1), c(2)), c(3))
)
.basic(
"(1 * (2 * 3))",
"1 * 2 * 3",
x -> 6,
new Multiply(c(1), new Multiply(c(2), c(3)))
)
.basic(
"((10 / 2) / 3)",
"10 / 2 / 3",
x -> 10 / 2 / 3,
new Divide(new Divide(c(10), c(2)), c(3))
)
.basic(
"(10 / (3 / 2))",
"10 / (3 / 2)",
x -> 10 / (3 / 2),
new Divide(c(10), new Divide(c(3), c(2)))
)
.basic(
"(10 * (3 / 2))",
"10 * (3 / 2)",
x -> 10 * (3 / 2),
new Multiply(c(10), new Divide(c(3), c(2)))
)
.basic(
"(10 + (3 - 2))",
"10 + 3 - 2",
x -> 10 + (3 - 2),
new Add(c(10), new Subtract(c(3), c(2)))
)
.basic(
"((x * x) + ((x - 1) / 10))",
"x * x + (x - 1) / 10",
x -> x * x + (x - 1) / 10,
new Add(
new Multiply(vx, vx),
new Divide(new Subtract(vx, c(1)), c(10))
)
)
.basic(
"(x * -1000000000)",
"x * -1000000000",
x -> x * -1_000_000_000,
new Multiply(vx, c(-1_000_000_000))
)
.basic("(10 / x)", "10 / x", x -> 10 / x, new Divide(c(10), vx))
.basic("(x / x)", "x / x", x -> x / x, new Divide(vx, vx))
.advanced("(2 + 1)", "2 + 1", x -> 2 + 1, new Add(c2, c1))
.advanced("(x - 1)", "x - 1", x -> x - 1, new Subtract(vx, c1))
.advanced("(1 * 2)", "1 * 2", x -> 1 * 2, new Multiply(c1, c2))
.advanced("(x / 1)", "x / 1", x -> x / 1, new Divide(vx, c1))
.advanced(
"(1 + (2 + 1))",
"1 + 2 + 1",
x -> 1 + 2 + 1,
new Add(c1, new Add(c2, c1))
)
.advanced(
"(x - (x - 1))",
"x - (x - 1)",
x -> x - (x - 1),
new Subtract(vx, new Subtract(vx, c1))
)
.advanced(
"(2 * (x / 1))",
"2 * (x / 1)",
x -> 2 * (x / 1),
new Multiply(c2, new Divide(vx, c1))
)
.advanced(
"(2 / (x - 1))",
"2 / (x - 1)",
x -> 2 / (x - 1),
new Divide(c2, new Subtract(vx, c1))
)
.advanced(
"((1 * 2) + x)",
"1 * 2 + x",
x -> 1 * 2 + x,
new Add(new Multiply(c1, c2), vx)
)
.advanced(
"((x - 1) - 2)",
"x - 1 - 2",
x -> x - 1 - 2,
new Subtract(new Subtract(vx, c1), c2)
)
.advanced(
"((x / 1) * 2)",
"x / 1 * 2",
x -> (x / 1) * 2,
new Multiply(new Divide(vx, c1), c2)
)
.advanced(
"((2 + 1) / 1)",
"(2 + 1) / 1",
x -> (2 + 1) / 1,
new Divide(new Add(c2, c1), c1)
)
.advanced(
"(1 + (1 + (2 + 1)))",
"1 + 1 + 2 + 1",
x -> 1 + 1 + 2 + 1,
new Add(c1, new Add(c1, new Add(c2, c1)))
)
.advanced(
"(x - ((1 * 2) + x))",
"x - (1 * 2 + x)",
x -> x - (1 * 2 + x),
new Subtract(vx, new Add(new Multiply(c1, c2), vx))
)
.advanced(
"(x * (2 / (x - 1)))",
"x * (2 / (x - 1))",
x -> x * (2 / (x - 1)),
new Multiply(vx, new Divide(c2, new Subtract(vx, c1)))
)
.advanced(
"(x / (1 + (2 + 1)))",
"x / (1 + 2 + 1)",
x -> x / (1 + 2 + 1),
new Divide(vx, new Add(c1, new Add(c2, c1)))
)
.advanced(
"((1 * 2) + (2 + 1))",
"1 * 2 + 2 + 1",
x -> 1 * 2 + 2 + 1,
new Add(new Multiply(c1, c2), new Add(c2, c1))
)
.advanced(
"((2 + 1) - (2 + 1))",
"2 + 1 - (2 + 1)",
x -> 2 + 1 - (2 + 1),
new Subtract(new Add(c2, c1), new Add(c2, c1))
)
.advanced(
"((x - 1) * (x / 1))",
"(x - 1) * (x / 1)",
x -> (x - 1) * (x / 1),
new Multiply(new Subtract(vx, c1), new Divide(vx, c1))
)
.advanced(
"((x - 1) / (1 * 2))",
"(x - 1) / (1 * 2)",
x -> (x - 1) / (1 * 2),
new Divide(new Subtract(vx, c1), new Multiply(c1, c2))
)
.advanced(
"(((x - 1) - 2) + x)",
"x - 1 - 2 + x",
x -> x - 1 - 2 + x,
new Add(new Subtract(new Subtract(vx, c1), c2), vx)
)
.advanced(
"(((1 * 2) + x) - 1)",
"1 * 2 + x - 1",
x -> 1 * 2 + x - 1,
new Subtract(new Add(new Multiply(c1, c2), vx), c1)
)
.advanced(
"(((2 + 1) / 1) * x)",
"(2 + 1) / 1 * x",
x -> ((2 + 1) / 1) * x,
new Multiply(new Divide(new Add(c2, c1), c1), vx)
)
.advanced(
"((2 / (x - 1)) / 2)",
"2 / (x - 1) / 2",
x -> 2 / (x - 1) / 2,
new Divide(new Divide(c2, new Subtract(vx, c1)), c2)
);
}
}

View File

@@ -0,0 +1,30 @@
package expression;
import base.Selector;
import base.TestCounter;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExpressionTest {
public static final Selector SELECTOR = new Selector(
ExpressionTest.class,
"easy",
"hard"
).variant("Base", v(Expression::tester));
private ExpressionTest() {}
public static Consumer<TestCounter> v(
final Function<TestCounter, ? extends ExpressionTester<?, ?>> tester
) {
return t -> tester.apply(t).test();
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,415 @@
package expression;
import static base.Asserts.assertTrue;
import base.*;
import expression.common.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExpressionTester<E extends ToMiniString, C> extends Tester {
private final List<Integer> VALUES = IntStream.rangeClosed(-10, 10)
.boxed()
.toList();
private final ExpressionKind<E, C> kind;
private final List<Test> basic = new ArrayList<>();
private final List<Test> advanced = new ArrayList<>();
private final Set<String> used = new HashSet<>();
private final GeneratorBuilder generator;
private final List<Pair<ToMiniString, String>> prev = new ArrayList<>();
private final Map<String, C> mappings;
protected ExpressionTester(
final TestCounter counter,
final ExpressionKind<E, C> kind,
final Function<C, E> expectedConstant,
final Binary<C, E> binary,
final BinaryOperator<C> add,
final BinaryOperator<C> sub,
final BinaryOperator<C> mul,
final BinaryOperator<C> div,
final Map<String, C> mappings
) {
super(counter);
this.kind = kind;
this.mappings = mappings;
generator = new GeneratorBuilder(
expectedConstant,
kind::constant,
binary,
kind::randomValue
);
generator.binary("+", 1600, add, Add.class);
generator.binary("-", 1602, sub, Subtract.class);
generator.binary("*", 2001, mul, Multiply.class);
generator.binary("/", 2002, div, Divide.class);
}
protected ExpressionTester(
final TestCounter counter,
final ExpressionKind<E, C> kind,
final Function<C, E> expectedConstant,
final Binary<C, E> binary,
final BinaryOperator<C> add,
final BinaryOperator<C> sub,
final BinaryOperator<C> mul,
final BinaryOperator<C> div
) {
this(
counter,
kind,
expectedConstant,
binary,
add,
sub,
mul,
div,
Map.of()
);
}
@Override
public String toString() {
return kind.getName();
}
@Override
public void test() {
counter.scope("Basic tests", () -> basic.forEach(Test::test));
counter.scope("Advanced tests", () -> advanced.forEach(Test::test));
counter.scope("Random tests", generator::testRandom);
}
@SuppressWarnings({ "ConstantValue", "EqualsWithItself" })
private void checkEqualsAndToString(
final String full,
final String mini,
final ToMiniString expression,
final ToMiniString copy
) {
checkToString("toString", full, expression.toString());
if (mode() > 0) {
checkToString("toMiniString", mini, expression.toMiniString());
}
counter.test(() -> {
assertTrue("Equals to this", expression.equals(expression));
assertTrue("Equals to copy", expression.equals(copy));
assertTrue("Equals to null", !expression.equals(null));
assertTrue("Copy equals to null", !copy.equals(null));
});
final String expressionToString = Objects.requireNonNull(
expression.toString()
);
for (final Pair<ToMiniString, String> pair : prev) {
counter.test(() -> {
final ToMiniString prev = pair.first();
final String prevToString = pair.second();
final boolean equals = prevToString.equals(expressionToString);
assertTrue(
"Equals to " + prevToString,
prev.equals(expression) == equals
);
assertTrue(
"Equals to " + prevToString,
expression.equals(prev) == equals
);
assertTrue(
"Inconsistent hashCode for " + prev + " and " + expression,
(prev.hashCode() == expression.hashCode()) == equals
);
});
}
}
private void checkToString(
final String method,
final String expected,
final String actual
) {
counter.test(() ->
assertTrue(
String.format(
"Invalid %s\n expected: %s\n actual: %s",
method,
expected,
actual
),
expected.equals(actual)
)
);
}
private void check(
final String full,
final E expected,
final E actual,
final List<String> variables,
final List<C> values
) {
final String vars = IntStream.range(0, variables.size())
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
.collect(Collectors.joining(","));
counter.test(() -> {
final Object expectedResult = evaluate(expected, variables, values);
final Object actualResult = evaluate(actual, variables, values);
final String reason = String.format(
"%s:%n expected `%s`,%n actual `%s`",
String.format("f(%s)\nwhere f is %s", vars, full),
Asserts.toString(expectedResult),
Asserts.toString(actualResult)
);
if (
expectedResult != null &&
actualResult != null &&
expectedResult.getClass() == actualResult.getClass() &&
(expectedResult.getClass() == Double.class ||
expectedResult.getClass() == Float.class)
) {
final double expectedValue = (
(Number) expectedResult
).doubleValue();
final double actualValue = (
(Number) actualResult
).doubleValue();
Asserts.assertEquals(reason, expectedValue, actualValue, 1e-6);
} else {
assertTrue(
reason,
Objects.deepEquals(expectedResult, actualResult)
);
}
});
}
private Object evaluate(
final E expression,
final List<String> variables,
final List<C> values
) {
try {
return kind.evaluate(expression, variables, values);
} catch (final Exception e) {
return e.getClass().getName();
}
}
protected ExpressionTester<E, C> basic(
final String full,
final String mini,
final E expected,
final E actual
) {
return basicF(full, mini, expected, vars -> actual);
}
protected ExpressionTester<E, C> basicF(
final String full,
final String mini,
final E expected,
final Function<List<String>, E> actual
) {
return basic(new Test(full, mini, expected, actual));
}
private ExpressionTester<E, C> basic(final Test test) {
Asserts.assertTrue(test.full, used.add(test.full));
basic.add(test);
return this;
}
protected ExpressionTester<E, C> advanced(
final String full,
final String mini,
final E expected,
final E actual
) {
return advancedF(full, mini, expected, vars -> actual);
}
protected ExpressionTester<E, C> advancedF(
final String full,
final String mini,
final E expected,
final Function<List<String>, E> actual
) {
Asserts.assertTrue(full, used.add(full));
advanced.add(new Test(full, mini, expected, actual));
return this;
}
protected static <E> Named<E> variable(
final String name,
final E expected
) {
return Named.of(name, expected);
}
@FunctionalInterface
public interface Binary<C, E> {
E apply(BinaryOperator<C> op, E a, E b);
}
private final class Test {
private final String full;
private final String mini;
private final E expected;
private final Function<List<String>, E> actual;
private Test(
final String full,
final String mini,
final E expected,
final Function<List<String>, E> actual
) {
this.full = full;
this.mini = mini;
this.expected = expected;
this.actual = actual;
}
private void test() {
final List<Pair<String, E>> variables = kind
.variables()
.generate(random(), 3);
final List<String> names = Functional.map(variables, Pair::first);
final E actual = kind.cast(this.actual.apply(names));
final String full = mangle(this.full, names);
final String mini = mangle(this.mini, names);
counter.test(() -> {
kind
.allValues(variables.size(), VALUES)
.forEach(values ->
check(mini, expected, actual, names, values)
);
checkEqualsAndToString(full, mini, actual, actual);
prev.add(Pair.of(actual, full));
});
}
private String mangle(String string, final List<String> names) {
for (int i = 0; i < names.size(); i++) {
string = string.replace("$" + (char) ('x' + i), names.get(i));
}
for (final Map.Entry<String, C> mapping : mappings.entrySet()) {
string = string.replace(
mapping.getKey(),
mapping.getValue().toString()
);
}
return string;
}
}
private final class GeneratorBuilder {
private final Generator.Builder<C> generator;
private final NodeRendererBuilder<C> renderer =
new NodeRendererBuilder<>(random());
private final Renderer.Builder<C, Unit, E> expected;
private final Renderer.Builder<C, Unit, E> actual;
private final Renderer.Builder<C, Unit, E> copy;
private final Binary<C, E> binary;
private GeneratorBuilder(
final Function<C, E> expectedConstant,
final Function<? super C, E> actualConstant,
final Binary<C, E> binary,
final Function<ExtendedRandom, C> randomValue
) {
generator = Generator.builder(
() -> randomValue.apply(random()),
random()
);
expected = Renderer.builder(expectedConstant::apply);
actual = Renderer.builder(actualConstant::apply);
copy = Renderer.builder(actualConstant::apply);
this.binary = binary;
}
private void binary(
final String name,
final int priority,
final BinaryOperator<C> op,
final Class<?> type
) {
generator.add(name, 2);
renderer.binary(name, priority);
expected.binary(name, (unit, a, b) -> binary.apply(op, a, b));
@SuppressWarnings("unchecked")
final Constructor<? extends E> constructor = (Constructor<
? extends E
>) Arrays.stream(type.getConstructors())
.filter(cons -> Modifier.isPublic(cons.getModifiers()))
.filter(cons -> cons.getParameterCount() == 2)
.findFirst()
.orElseGet(() ->
counter.fail(
"%s(..., ...) constructor not found",
type.getSimpleName()
)
);
final Renderer.BinaryOperator<Unit, E> actual = (unit, a, b) -> {
try {
return constructor.newInstance(a, b);
} catch (final Exception e) {
return counter.fail(e);
}
};
this.actual.binary(name, actual);
copy.binary(name, actual);
}
private void testRandom() {
final NodeRenderer<C> renderer = this.renderer.build();
final Renderer<C, Unit, E> expectedRenderer = this.expected.build();
final Renderer<C, Unit, E> actualRenderer = this.actual.build();
final expression.common.Generator<C, E> generator =
this.generator.build(kind.variables(), List.of());
generator.testRandom(counter, 1, expr -> {
final String full = renderer.render(expr, NodeRenderer.FULL);
final String mini = renderer.render(expr, NodeRenderer.MINI);
final E expected = expectedRenderer.render(expr, Unit.INSTANCE);
final E actual = actualRenderer.render(expr, Unit.INSTANCE);
final List<Pair<String, E>> variables = expr.variables();
final List<String> names = Functional.map(
variables,
Pair::first
);
final List<C> values = Stream.generate(() ->
kind.randomValue(random())
)
.limit(variables.size())
.toList();
checkEqualsAndToString(
full,
mini,
actual,
copy.build().render(expr, Unit.INSTANCE)
);
check(full, expected, actual, names, values);
});
}
}
}

View File

@@ -0,0 +1,75 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Floor extends AbstractExpression {
private final AbstractExpression operand;
public Floor(AbstractExpression operand) {
this.operand = operand;
}
private static int floorInt(int n) {
return Math.floorDiv(n, 1000) * 1000;
}
@Override
public int evaluate(int x) {
return floorInt(operand.evaluate(x));
}
@Override
public int evaluate(int x, int y, int z) {
return floorInt(operand.evaluate(x, y, z));
}
@Override
public int evaluate(List<Integer> vars) {
return floorInt(operand.evaluate(vars));
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
throw new UnsupportedOperationException(
"floor not supported for BigInteger"
);
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
throw new UnsupportedOperationException(
"floor not supported for BigDecimal"
);
}
@Override
public String toString() {
return "floor(" + operand + ")";
}
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "floor(" + operand.toMiniString() + ")";
}
return "floor " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Floor)) return false;
return operand.equals(((Floor) obj).operand);
}
@Override
public int hashCode() {
return operand.hashCode() ^ 0x464C4F52;
}
}

51
java/expression/High.java Normal file
View File

@@ -0,0 +1,51 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class High extends AbstractExpression {
private final AbstractExpression operand;
public High(AbstractExpression operand) {
this.operand = operand;
}
private static int highestBit(int n) {
if (n == 0) return 0;
n |= (n >> 1);
n |= (n >> 2);
n |= (n >> 4);
n |= (n >> 8);
n |= (n >> 16);
return n - (n >>> 1);
}
@Override public int evaluate(int x) { return highestBit(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return highestBit(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return highestBit(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
@Override public String toString() { return "high(" + operand + ")"; }
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "high(" + operand.toMiniString() + ")";
}
return "high " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof High)) return false;
return operand.equals(((High) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x48494748; }
}

View File

@@ -0,0 +1,26 @@
package expression;
import base.ExtendedRandom;
import base.Pair;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.util.List;
import java.util.stream.IntStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
public interface ListExpression extends ToMiniString {
ExpressionKind<ListExpression, Integer> KIND = new ExpressionKind<>(
new Type<>(a -> a, ExtendedRandom::nextInt, int.class),
ListExpression.class,
(r, c) -> IntStream.range(0, c)
.mapToObj(name -> Pair.<String, ListExpression>of("$" + name, new Variable(name)))
.toList(),
(expr, variables, values) -> expr.evaluate(values)
);
int evaluate(List<Integer> variables);
}

46
java/expression/Log.java Normal file
View File

@@ -0,0 +1,46 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Log extends AbstractBinaryOperation {
public Log(AbstractExpression l, AbstractExpression r) { super(l, r); }
@Override protected String getOperator() { return "//"; }
@Override protected int getPriority() { return 3; }
@Override protected boolean isRightAssoc() { return true; }
@Override
protected int applyInt(int a, int b) {
if (a <= 0) throw new ArithmeticException("logarithm of non-positive number");
if (b <= 1) throw new ArithmeticException("logarithm base must be > 1");
int result = 0;
int power = 1;
while (power <= a / b) {
power *= b;
result++;
}
return result;
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
if (a.signum() <= 0) throw new ArithmeticException("logarithm of non-positive number");
if (b.compareTo(BigInteger.TWO) < 0) throw new ArithmeticException("logarithm base must be > 1");
int result = 0;
BigInteger power = BigInteger.ONE;
while (power.multiply(b).compareTo(a) <= 0) {
power = power.multiply(b);
result++;
}
return BigInteger.valueOf(result);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
throw new UnsupportedOperationException("log not supported for BigDecimal");
}
}

52
java/expression/Log2.java Normal file
View File

@@ -0,0 +1,52 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Log2 extends AbstractExpression {
private final AbstractExpression operand;
public Log2(AbstractExpression operand) {
this.operand = operand;
}
private static int log2(int n) {
if (n <= 0) throw new ArithmeticException("log₂ of non-positive number: " + n);
int result = 0;
int v = n;
while (v > 1) {
v >>= 1;
result++;
}
return result;
}
@Override public int evaluate(int x) { return log2(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return log2(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return log2(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
@Override public String toString() { return "log₂(" + operand + ")"; }
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "log₂(" + operand.toMiniString() + ")";
}
return "log₂ " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Log2)) return false;
return operand.equals(((Log2) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x4C4F4732; }
}

41
java/expression/Low.java Normal file
View File

@@ -0,0 +1,41 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Low extends AbstractExpression {
private final AbstractExpression operand;
public Low(AbstractExpression operand) {
this.operand = operand;
}
@Override public int evaluate(int x) { int n = operand.evaluate(x); return n & -n; }
@Override public int evaluate(int x, int y, int z) { int n = operand.evaluate(x,y,z); return n & -n; }
@Override public int evaluate(List<Integer> vars) { int n = operand.evaluate(vars); return n & -n; }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
@Override public String toString() { return "low(" + operand + ")"; }
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "low(" + operand.toMiniString() + ")";
}
return "low " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Low)) return false;
return operand.equals(((Low) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x4C4F5700; }
}

44
java/expression/Max.java Normal file
View File

@@ -0,0 +1,44 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Max extends AbstractBinaryOperation {
public Max(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "max";
}
@Override
protected int getPriority() {
return 0;
}
@Override
protected boolean isRightAssoc() {
return false;
}
@Override
protected int applyInt(int a, int b) {
return Math.max(a, b);
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.max(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
return a.max(b);
}
}

44
java/expression/Min.java Normal file
View File

@@ -0,0 +1,44 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Min extends AbstractBinaryOperation {
public Min(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "min";
}
@Override
protected int getPriority() {
return 0;
}
@Override
protected boolean isRightAssoc() {
return false;
}
@Override
protected int applyInt(int a, int b) {
return Math.min(a, b);
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.min(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
return a.min(b);
}
}

View File

@@ -0,0 +1,44 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Multiply extends AbstractBinaryOperation {
public Multiply(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "*";
}
@Override
protected int getPriority() {
return 2;
}
@Override
protected boolean isRightAssoc() {
return false;
}
@Override
protected int applyInt(int a, int b) {
return a * b;
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.multiply(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
return a.multiply(b);
}
}

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class OverflowException extends ArithmeticException {
public OverflowException(String operation) {
super("overflow in " + operation);
}
}

47
java/expression/Pow2.java Normal file
View File

@@ -0,0 +1,47 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Pow2 extends AbstractExpression {
private final AbstractExpression operand;
public Pow2(AbstractExpression operand) {
this.operand = operand;
}
private static int pow2(int n) {
if (n < 0) throw new ArithmeticException("pow₂ of negative number: " + n);
if (n >= 31) throw new OverflowException("pow₂");
return 1 << n;
}
@Override public int evaluate(int x) { return pow2(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return pow2(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return pow2(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
@Override public String toString() { return "pow₂(" + operand + ")"; }
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "pow₂(" + operand.toMiniString() + ")";
}
return "pow₂ " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Pow2)) return false;
return operand.equals(((Pow2) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x504F5732; }
}

View File

@@ -0,0 +1,46 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Power extends AbstractBinaryOperation {
public Power(AbstractExpression l, AbstractExpression r) { super(l, r); }
@Override protected String getOperator() { return "**"; }
@Override protected int getPriority() { return 3; }
@Override protected boolean isRightAssoc() { return true; }
@Override
protected int applyInt(int base, int exp) {
if (exp < 0) throw new ArithmeticException("negative exponent");
if (base == 0 && exp == 0) throw new ArithmeticException("zero to the power of zero");
if (exp == 0) return 1;
if (base == 0) return 0;
int result = 1;
int b = base;
int e = exp;
while (e > 0) {
if ((e & 1) == 1) {
result = checkedMul(result, b);
}
if (e > 1) b = checkedMul(b, b);
e >>= 1;
}
return result;
}
private static int checkedMul(int a, int b) {
if (a == 0 || b == 0) return 0;
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("power");
if (b == Integer.MIN_VALUE && a == -1) throw new OverflowException("power");
int result = a * b;
if (result / a != b) throw new OverflowException("power");
return result;
}
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.pow(b.intValueExact()); }
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.pow(b.intValueExact()); }
}

View File

@@ -0,0 +1,84 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Reverse extends AbstractExpression {
private final AbstractExpression operand;
public Reverse(AbstractExpression operand) {
this.operand = operand;
}
private static int reverseInt(int n) {
boolean negative = n < 0;
long abs = Math.abs((long) n);
long reversed = 0;
while (abs > 0) {
reversed = reversed * 10 + (abs % 10);
abs /= 10;
}
long result = negative ? -reversed : reversed;
return (int) result;
}
@Override
public int evaluate(int x) {
return reverseInt(operand.evaluate(x));
}
@Override
public int evaluate(int x, int y, int z) {
return reverseInt(operand.evaluate(x, y, z));
}
@Override
public int evaluate(List<Integer> vars) {
return reverseInt(operand.evaluate(vars));
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
throw new UnsupportedOperationException(
"reverse not supported for BigInteger"
);
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
throw new UnsupportedOperationException(
"reverse not supported for BigDecimal"
);
}
@Override
public String toString() {
return "reverse(" + operand + ")";
}
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "reverse(" + operand.toMiniString() + ")";
}
return "reverse " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Reverse)) return false;
return operand.equals(((Reverse) obj).operand);
}
@Override
public int hashCode() {
return operand.hashCode() ^ 0x52455645;
}
}

View File

@@ -0,0 +1,46 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class SetBit extends AbstractBinaryOperation {
public SetBit(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "set";
}
@Override
protected int getPriority() {
return 0;
}
@Override
protected boolean isRightAssoc() {
return true;
}
@Override
protected int applyInt(int a, int b) {
return a | (1 << b);
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.setBit(b.intValueExact());
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
throw new UnsupportedOperationException(
"set not supported for BigDecimal"
);
}
}

60
java/expression/Sqrt.java Normal file
View File

@@ -0,0 +1,60 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Sqrt extends AbstractExpression {
private final AbstractExpression operand;
public Sqrt(AbstractExpression operand) {
this.operand = operand;
}
private static int sqrtInt(int n) {
if (n < 0) throw new ArithmeticException("sqrt of negative number: " + n);
if (n == 0) return 0;
// Binary search: floor(sqrt(n)), result <= 46340 (sqrt(MAX_INT) < 46341)
int lo = 0, hi = 46340;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
if (mid * mid <= n) {
lo = mid;
} else {
hi = mid - 1;
}
}
return lo;
}
@Override public int evaluate(int x) { return sqrtInt(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return sqrtInt(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return sqrtInt(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).sqrt(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) {
throw new UnsupportedOperationException("sqrt not supported for BigDecimal");
}
@Override public String toString() { return "√(" + operand + ")"; }
// @Override public String toMiniString() { return "√" + operand.toMiniString(); }
@Override
public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) {
return "√(" + operand.toMiniString() + ")";
}
return "" + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Sqrt)) return false;
return operand.equals(((Sqrt) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x53515254; }
}

View File

@@ -0,0 +1,51 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Square extends AbstractExpression {
private final AbstractExpression operand;
public Square(AbstractExpression operand) {
this.operand = operand;
}
private static int squareInt(int n) {
if (n == Integer.MIN_VALUE) throw new OverflowException("square");
int result = n * n;
// overflow check: result / n should == n (if n != 0)
if (n != 0 && result / n != n) throw new OverflowException("square");
return result;
}
@Override public int evaluate(int x) { return squareInt(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return squareInt(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return squareInt(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).pow(2); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).pow(2); }
@Override public String toString() { return "(" + operand + ""; }
@Override
public String toMiniString() {
String s = operand.toMiniString();
boolean needParens = operand instanceof AbstractBinaryOperation
|| operand instanceof Cbrt
|| operand instanceof Sqrt
|| (s.startsWith("-") && (s.length() < 2 || !Character.isDigit(s.charAt(1))));
return needParens ? "(" + s + "" : s + "²";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Square)) return false;
return operand.equals(((Square) obj).operand);
}
@Override public int hashCode() { return operand.hashCode() ^ 0x53515232; }
}

View File

@@ -0,0 +1,44 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Subtract extends AbstractBinaryOperation {
public Subtract(AbstractExpression l, AbstractExpression r) {
super(l, r);
}
@Override
protected String getOperator() {
return "-";
}
@Override
protected int getPriority() {
return 1;
}
@Override
protected boolean isRightAssoc() {
return true;
}
@Override
protected int applyInt(int a, int b) {
return a - b;
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
return a.subtract(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
return a.subtract(b);
}
}

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface ToMiniString {
default String toMiniString() {
return toString();
}
}

View File

@@ -0,0 +1,317 @@
package expression;
import base.ExtendedRandom;
import base.Pair;
import base.Selector;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.util.List;
/**
* Three-argument arithmetic expression over integers.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface TripleExpression extends ToMiniString {
int evaluate(int x, int y, int z);
// Tests follow. You may temporarily remove everything til the end.
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
ExpressionKind<TripleExpression, Integer> KIND = new ExpressionKind<>(
TYPE,
TripleExpression.class,
List.of(
Pair.of("x", new Variable("x")),
Pair.of("y", new Variable("y")),
Pair.of("z", new Variable("z"))
),
(expr, variables, values) ->
expr.evaluate(values.get(0), values.get(1), values.get(2))
);
@SuppressWarnings("PointlessArithmeticExpression")
static ExpressionTester<?, ?> tester(final TestCounter counter) {
final Variable vx = new Variable("x");
final Variable vy = new Variable("y");
final Variable vz = new Variable("z");
return new ExpressionTester<>(
counter,
KIND,
c -> (x, y, z) -> c,
(op, a, b) ->
(x, y, z) -> op.apply(a.evaluate(x, y, z), b.evaluate(x, y, z)),
Integer::sum,
(a, b) -> a - b,
(a, b) -> a * b,
(a, b) -> a / b
)
.basic("10", "10", (x, y, z) -> 10, c(10))
.basic("x", "x", (x, y, z) -> x, vx)
.basic("y", "y", (x, y, z) -> y, vy)
.basic("z", "z", (x, y, z) -> z, vz)
.basic("(x + 2)", "x + 2", (x, y, z) -> x + 2, new Add(vx, c(2)))
.basic(
"(2 - y)",
"2 - y",
(x, y, z) -> 2 - y,
new Subtract(c(2), vy)
)
.basic(
"(3 * z)",
"3 * z",
(x, y, z) -> 3 * z,
new Multiply(c(3), vz)
)
.basic(
"(x / -2)",
"x / -2",
(x, y, z) -> -x / 2,
new Divide(vx, c(-2))
)
.basic(
"((1 + 2) + 3)",
"1 + 2 + 3",
(x, y, z) -> 6,
new Add(new Add(c(1), c(2)), c(3))
)
.basic(
"(1 + (2 + 3))",
"1 + 2 + 3",
(x, y, z) -> 6,
new Add(c(1), new Add(c(2), c(3)))
)
.basic(
"((1 - 2) - 3)",
"1 - 2 - 3",
(x, y, z) -> -4,
new Subtract(new Subtract(c(1), c(2)), c(3))
)
.basic(
"(1 - (2 - 3))",
"1 - (2 - 3)",
(x, y, z) -> 2,
new Subtract(c(1), new Subtract(c(2), c(3)))
)
.basic(
"((1 * 2) * 3)",
"1 * 2 * 3",
(x, y, z) -> 6,
new Multiply(new Multiply(c(1), c(2)), c(3))
)
.basic(
"(1 * (2 * 3))",
"1 * 2 * 3",
(x, y, z) -> 6,
new Multiply(c(1), new Multiply(c(2), c(3)))
)
.basic(
"((10 / 2) / 3)",
"10 / 2 / 3",
(x, y, z) -> 10 / 2 / 3,
new Divide(new Divide(c(10), c(2)), c(3))
)
.basic(
"(10 / (3 / 2))",
"10 / (3 / 2)",
(x, y, z) -> 10,
new Divide(c(10), new Divide(c(3), c(2)))
)
.basic(
"((x * y) + ((z - 1) / 10))",
"x * y + (z - 1) / 10",
(x, y, z) -> x * y + (z - 1) / 10,
new Add(
new Multiply(vx, vy),
new Divide(new Subtract(vz, c(1)), c(10))
)
)
.basic("(x + y)", "x + y", (x, y, z) -> x + y, new Add(vx, vy))
.basic("(y + x)", "y + x", (x, y, z) -> y + x, new Add(vy, vx))
.advanced(
"(1 + 1)",
"1 + 1",
(x, y, z) -> 1 + 1,
new Add(c(1), c(1))
)
.advanced(
"(y - x)",
"y - x",
(x, y, z) -> y - x,
new Subtract(vy, vx)
)
.advanced(
"(2 * x)",
"2 * x",
(x, y, z) -> 2 * x,
new Multiply(c(2), vx)
)
.advanced(
"(2 / x)",
"2 / x",
(x, y, z) -> 2 / x,
new Divide(c(2), vx)
)
.advanced(
"(z + (1 + 1))",
"z + 1 + 1",
(x, y, z) -> z + 1 + 1,
new Add(vz, new Add(c(1), c(1)))
)
.advanced(
"(2 - (y - x))",
"2 - (y - x)",
(x, y, z) -> 2 - (y - x),
new Subtract(c(2), new Subtract(vy, vx))
)
.advanced(
"(z * (2 / x))",
"z * (2 / x)",
(x, y, z) -> z * (2 / x),
new Multiply(vz, new Divide(c(2), vx))
)
.advanced(
"(z / (y - x))",
"z / (y - x)",
(x, y, z) -> z / (y - x),
new Divide(vz, new Subtract(vy, vx))
)
.advanced(
"((2 * x) + y)",
"2 * x + y",
(x, y, z) -> 2 * x + y,
new Add(new Multiply(c(2), vx), vy)
)
.advanced(
"((y - x) - 2)",
"y - x - 2",
(x, y, z) -> y - x - 2,
new Subtract(new Subtract(vy, vx), c(2))
)
.advanced(
"((2 / x) * y)",
"2 / x * y",
(x, y, z) -> (2 / x) * y,
new Multiply(new Divide(c(2), vx), vy)
)
.advanced(
"((1 + 1) / x)",
"(1 + 1) / x",
(x, y, z) -> (1 + 1) / x,
new Divide(new Add(c(1), c(1)), vx)
)
.advanced(
"(1 + (2 * 3))",
"1 + 2 * 3",
(x, y, z) -> 7,
new Add(c(1), new Multiply(c(2), c(3)))
)
.advanced(
"(1 - (2 * 3))",
"1 - 2 * 3",
(x, y, z) -> -5,
new Subtract(c(1), new Multiply(c(2), c(3)))
)
.advanced(
"(1 + (2 / 3))",
"1 + 2 / 3",
(x, y, z) -> 1,
new Add(c(1), new Divide(c(2), c(3)))
)
.advanced(
"(1 - (2 / 3))",
"1 - 2 / 3",
(x, y, z) -> 1,
new Subtract(c(1), new Divide(c(2), c(3)))
)
.advanced(
"(2 + (z + (1 + 1)))",
"2 + z + 1 + 1",
(x, y, z) -> 2 + z + 1 + 1,
new Add(c(2), new Add(vz, new Add(c(1), c(1))))
)
.advanced(
"(1 - ((2 * x) + y))",
"1 - (2 * x + y)",
(x, y, z) -> 1 - (2 * x + y),
new Subtract(c(1), new Add(new Multiply(c(2), vx), vy))
)
.advanced(
"(1 * (z / (y - x)))",
"1 * (z / (y - x))",
(x, y, z) -> 1 * (z / (y - x)),
new Multiply(c(1), new Divide(vz, new Subtract(vy, vx)))
)
.advanced(
"(z / (z + (1 + 1)))",
"z / (z + 1 + 1)",
(x, y, z) -> z / (z + 1 + 1),
new Divide(vz, new Add(vz, new Add(c(1), c(1))))
)
.advanced(
"((2 * x) + (1 + 1))",
"2 * x + 1 + 1",
(x, y, z) -> 2 * x + 1 + 1,
new Add(new Multiply(c(2), vx), new Add(c(1), c(1)))
)
.advanced(
"((1 + 1) - (1 + 1))",
"1 + 1 - (1 + 1)",
(x, y, z) -> 1 + 1 - (1 + 1),
new Subtract(new Add(c(1), c(1)), new Add(c(1), c(1)))
)
.advanced(
"((y - x) * (2 / x))",
"(y - x) * (2 / x)",
(x, y, z) -> (y - x) * (2 / x),
new Multiply(new Subtract(vy, vx), new Divide(c(2), vx))
)
.advanced(
"((y - x) / (2 * x))",
"(y - x) / (2 * x)",
(x, y, z) -> (y - x) / (2 * x),
new Divide(new Subtract(vy, vx), new Multiply(c(2), vx))
)
.advanced(
"(((y - x) - 2) + 1)",
"y - x - 2 + 1",
(x, y, z) -> y - x - 2 + 1,
new Add(new Subtract(new Subtract(vy, vx), c(2)), c(1))
)
.advanced(
"(((2 * x) + y) - z)",
"2 * x + y - z",
(x, y, z) -> 2 * x + y - z,
new Subtract(new Add(new Multiply(c(2), vx), vy), vz)
)
.advanced(
"(((1 + 1) / x) * 2)",
"(1 + 1) / x * 2",
(x, y, z) -> ((1 + 1) / x) * 2,
new Multiply(new Divide(new Add(c(1), c(1)), vx), c(2))
)
.advanced(
"((z / (y - x)) / x)",
"z / (y - x) / x",
(x, y, z) -> z / (y - x) / x,
new Divide(new Divide(vz, new Subtract(vy, vx)), vx)
);
}
private static Const c(final Integer c) {
return TYPE.constant(c);
}
Selector SELECTOR = ExpressionTest.SELECTOR.variant(
"Triple",
ExpressionTest.v(TripleExpression::tester)
);
static void main(final String... args) {
TripleExpression.SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,118 @@
package expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class Variable extends AbstractExpression {
private final String name;
private final int index;
public Variable(String name) {
this.name = name;
this.index = -1;
}
public Variable(int index) {
this.index = index;
this.name = "$" + index;
}
public Variable(int index, String name) {
this.index = index;
this.name = name;
}
@Override
public int evaluate(int x) {
if (index >= 0) {
if (index == 0) return x;
throw new IllegalStateException(
"Positional variable $" +
index +
" cannot be evaluated with a single value"
);
}
if ("x".equals(name)) return x;
throw new IllegalStateException(
"Variable '" + name + "' is not 'x'; use evaluate(x, y, z) instead"
);
}
@Override
public int evaluate(int x, int y, int z) {
if (index >= 0) {
return switch (index) {
case 0 -> x;
case 1 -> y;
case 2 -> z;
default -> throw new IndexOutOfBoundsException(
"Variable index " +
index +
" out of range for triple evaluate"
);
};
}
return switch (name) {
case "x" -> x;
case "y" -> y;
case "z" -> z;
default -> throw new IllegalStateException(
"Unknown variable: " + name
);
};
}
@Override
public int evaluate(List<Integer> vars) {
return vars.get(resolvedIndex());
}
@Override
public BigInteger evaluateBi(List<BigInteger> vars) {
return vars.get(resolvedIndex());
}
@Override
public BigDecimal evaluateBd(List<BigDecimal> vars) {
return vars.get(resolvedIndex());
}
private int resolvedIndex() {
if (index >= 0) return index;
return switch (name) {
case "x" -> 0;
case "y" -> 1;
case "z" -> 2;
default -> throw new IllegalStateException(
"Unknown variable: " + name
);
};
}
@Override
public String toString() {
return name;
}
@Override
public String toMiniString() {
return name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Variable)) return false;
return name.equals(((Variable) obj).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}

View File

@@ -0,0 +1,32 @@
package expression.common;
import base.Functional;
import base.Pair;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record Expr<C, V>(Node<C> node, List<Pair<String, V>> variables) {
public <T> List<Pair<String, T>> variables(final BiFunction<String, V, T> f) {
return Functional.map(
variables,
variable -> variable.second(f.apply(variable.first(), variable.second()))
);
}
public <T> Expr<C, T> convert(final BiFunction<String, V, T> f) {
return of(node, variables(f));
}
public Expr<C, V> node(final Function<Node<C>, Node<C>> f) {
return of(f.apply(node), variables);
}
public static <C, V> Expr<C, V> of(final Node<C> node, final List<Pair<String, V>> variables) {
return new Expr<>(node, variables);
}
}

View File

@@ -0,0 +1,94 @@
package expression.common;
import base.ExtendedRandom;
import base.Functional;
import base.Pair;
import expression.ToMiniString;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExpressionKind<E extends ToMiniString, C> {
private final Type<C> type;
private final Class<E> kind;
private final Variables<E> variables;
private final Evaluator<E, C> evaluator;
public ExpressionKind(
final Type<C> type,
final Class<E> kind,
final Variables<E> variables,
final Evaluator<E, C> evaluator
) {
this.type = type;
this.kind = kind;
this.variables = variables;
this.evaluator = evaluator;
}
public ExpressionKind(
final Type<C> type,
final Class<E> kind,
final List<Pair<String, E>> variables,
final Evaluator<E, C> evaluator
) {
this(type, kind, (r, c) -> variables, evaluator);
}
public C evaluate(final E expression, final List<String> variables, final List<C> values) throws Exception {
return evaluator.evaluate(expression, variables, values);
}
public E cast(final Object expression) {
return kind.cast(expression);
}
public String getName() {
return kind.getSimpleName();
}
public E constant(final C value) {
return cast(type.constant(value));
}
public C randomValue(final ExtendedRandom random) {
return type.randomValue(random);
}
public List<List<C>> allValues(final int length, final List<Integer> values) {
return Functional.allValues(fromInts(values), length);
}
public List<C> fromInts(final List<Integer> values) {
return Functional.map(values, this::fromInt);
}
public C fromInt(final int value) {
return type.fromInt(value);
}
@Override
public String toString() {
return kind.getName();
}
public ExpressionKind<E, C> withVariables(final Variables<E> variables) {
return new ExpressionKind<>(type, kind, variables, evaluator);
}
public Variables<E> variables() {
return variables;
}
@FunctionalInterface
public interface Variables<E> {
List<Pair<String, E>> generate(final ExtendedRandom random, final int count);
}
@FunctionalInterface
public interface Evaluator<E, R> {
R evaluate(final E expression, final List<String> vars, final List<R> values) throws Exception;
}
}

View File

@@ -0,0 +1,173 @@
package expression.common;
import base.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Generator<C, E> {
private final Supplier<C> constant;
private final List<Named<Integer>> ops;
private final ExpressionKind.Variables<E> variables;
private final Set<String> forbidden;
private final ExtendedRandom random;
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests;
public Generator(
final Supplier<C> constant,
final List<Named<Integer>> ops,
final ExpressionKind.Variables<E> variables,
final Set<String> forbidden,
final ExtendedRandom random,
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
) {
this.constant = constant;
this.ops = List.copyOf(ops);
this.variables = variables;
this.forbidden = Set.copyOf(forbidden);
this.random = random;
this.basicTests = List.copyOf(basicTests);
}
public static <C> Builder<C> builder(final Supplier<C> constant, final ExtendedRandom random) {
return new Builder<>(random, constant);
}
public void testRandom(
final TestCounter counter,
final int denominator,
final Consumer<Expr<C, E>> consumer
) {
final int d = Math.max(TestCounter.DENOMINATOR, denominator);
testRandom(counter, consumer, 1, 100, 100 / d, (vars, depth) -> generateFullDepth(vars, Math.min(depth, 3)));
testRandom(counter, consumer, 2, 1000 / d, 1, this::generateSize);
testRandom(counter, consumer, 3, 12, 100 / d, this::generateFullDepth);
testRandom(counter, consumer, 4, 777 / d, 1, this::generatePartialDepth);
}
private void testRandom(
final TestCounter counter,
final Consumer<Expr<C, E>> consumer,
final int seq,
final int levels,
final int perLevel,
final BiFunction<List<Node<C>>, Integer, Node<C>> generator
) {
counter.scope("Random tests #" + seq, () -> {
final int total = levels * perLevel;
int generated = 0;
for (int level = 0; level < levels; level++) {
for (int j = 0; j < perLevel; j++) {
if (generated % 100 == 0) {
progress(counter, total, generated);
}
generated++;
final List<Pair<String, E>> vars = variables(random.nextInt(10) + 1);
consumer.accept(Expr.of(generator.apply(Functional.map(vars, v -> Node.op(v.first())), level), vars));
}
}
progress(counter, generated, total);
});
}
private static void progress(final TestCounter counter, final int total, final int generated) {
counter.format("Completed %4d out of %d%n", generated, total);
}
private Node<C> generate(
final List<Node<C>> variables,
final boolean nullary,
final Supplier<Node<C>> unary,
final Supplier<Pair<Node<C>, Node<C>>> binary
) {
if (nullary || ops.isEmpty()) {
return random.nextBoolean() ? random.randomItem(variables) : Node.constant(constant.get());
} else {
final Named<Integer> op = random.randomItem(ops);
if (Math.abs(op.value()) == 1) {
return Node.op(op.name(), (op.value() + 1) >> 1, unary.get());
} else {
final Pair<Node<C>, Node<C>> pair = binary.get();
return Node.op(op.name(), pair.first(), pair.second());
}
}
}
private Node<C> generate(final List<Node<C>> variables, final boolean nullary, final Supplier<Node<C>> child) {
return generate(variables, nullary, child, () -> Pair.of(child.get(), child.get()));
}
private Node<C> generateFullDepth(final List<Node<C>> variables, final int depth) {
return generate(variables, depth == 0, () -> generateFullDepth(variables, depth - 1));
}
private Node<C> generatePartialDepth(final List<Node<C>> variables, final int depth) {
return generate(variables, depth == 0, () -> generatePartialDepth(variables, random.nextInt(depth)));
}
private Node<C> generateSize(final List<Node<C>> variables, final int size) {
final int first = size <= 1 ? 0 : random.nextInt(size);
return generate(
variables,
size == 0,
() -> generateSize(variables, size - 1),
() -> Pair.of(
generateSize(variables, first),
generateSize(variables, size - 1 - first)
)
);
}
public void testBasic(final Consumer<Expr<C, E>> consumer) {
basicTests.forEach(test -> {
final List<Pair<String, E>> vars = variables(random.nextInt(5) + 3);
test.apply(Functional.map(vars, v -> Node.op(v.first())))
.map(node -> Expr.of(node, vars))
.forEachOrdered(consumer);
});
}
public List<Pair<String, E>> variables(final int count) {
List<Pair<String, E>> vars;
do {
vars = variables.generate(random, count);
} while (vars.stream().map(Pair::first).anyMatch(forbidden::contains));
return vars;
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public static final class Builder<C> {
private final ExtendedRandom random;
private final Supplier<C> constant;
private final List<Named<Integer>> ops = new ArrayList<>();
private final Set<String> forbidden = new HashSet<>();
private Builder(final ExtendedRandom random, final Supplier<C> constant) {
this.random = random;
this.constant = constant;
}
public void add(final String name, final int arity) {
ops.add(Named.of(name, arity));
forbidden.add(name);
}
public <E> Generator<C, E> build(
final ExpressionKind.Variables<E> variables,
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
) {
return new Generator<>(constant, ops, variables, forbidden, random, basicTests);
}
}
}

View File

@@ -0,0 +1,106 @@
package expression.common;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public abstract class Node<T> {
private Node() {
}
public abstract <R> R get(Const<T, R> con, Nullary<R> nul, Unary<Node<T>, R> un, Binary<Node<T>, R> bin);
public abstract <R> R cata(Const<T, R> con, Nullary<R> nul, Unary<R, R> un, Binary<R, R> bin);
public final String toPolish() {
return cata(
T::toString,
name -> name,
(name, priority, a) -> a + " " + name + ":1",
(name, a1, a2) -> a1 + " " + a2 + " " + name + ":2"
);
}
@Override
public final String toString() {
return cata(
T::toString,
name -> name,
(name, priority, a) -> name.equals("[") ? "[" + a + "]" :
(priority & 1) == 1 ? "(" + name + " " + a + ")" : "(" + a + " " + name + ")",
(name, a1, a2) -> "(" + a1 + " " + name + " " + a2 + ")"
);
}
public static <T> Node<T> constant(final T value) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return con.apply(value);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return con.apply(value);
}
};
}
public static <T> Node<T> op(final String name) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return nul.apply(name);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return nul.apply(name);
}
};
}
public static <T> Node<T> op(final String name, final int priority, final Node<T> arg) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return un.apply(name, priority, arg);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return un.apply(name, priority, arg.cata(con, nul, un, bin));
}
};
}
public static <T> Node<T> op(final String name, final Node<T> arg1, final Node<T> arg2) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return bin.apply(name, arg1, arg2);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return bin.apply(name, arg1.cata(con, nul, un, bin), arg2.cata(con, nul, un, bin));
}
};
}
@FunctionalInterface
public interface Const<T, R> extends Function<T, R> {}
@FunctionalInterface
public interface Nullary<R> extends Function<String, R> {}
@FunctionalInterface
public interface Unary<T, R> {
R apply(String name, int priority, T arg);
}
@FunctionalInterface
public interface Binary<T, R> {
R apply(String name, T arg1, T arg2);
}
}

View File

@@ -0,0 +1,96 @@
package expression.common;
import base.ExtendedRandom;
import java.util.List;
import java.util.Map;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class NodeRenderer<C> {
public static final String PAREN = "[";
public static final List<Paren> DEFAULT_PARENS = List.of(paren("(", ")"));
public static final Mode MINI_MODE = Mode.SIMPLE_MINI; // Replace by TRUE_MINI for some challenge;
public static final Settings FULL = Mode.FULL.settings(0);
public static final Settings FULL_EXTRA = Mode.FULL.settings(Integer.MAX_VALUE / 4);
public static final Settings SAME = Mode.SAME.settings(0);
public static final Settings MINI = MINI_MODE.settings(0);
public static final Settings TRUE_MINI = Mode.TRUE_MINI.settings(0);
private final Renderer<C, Settings, Node<C>> renderer;
private final Map<String, String> brackets;
private final ExtendedRandom random;
public NodeRenderer(
final Renderer<C, Settings, Node<C>> renderer,
final Map<String, String> brackets,
final ExtendedRandom random
) {
this.renderer = renderer;
this.brackets = Map.copyOf(brackets);
this.random = random;
}
public static <C> Node<C> paren(final boolean condition, final Node<C> node) {
return condition ? Node.op(PAREN, 1, node) : node;
}
public static Paren paren(final String open, final String close) {
return new Paren(open, close);
}
public Node<C> renderToNode(final Settings settings, final Expr<C, ?> expr) {
final Expr<C, Node<C>> convert = expr.convert((name, variable) -> Node.op(name));
return renderer.render(convert, settings);
}
public String render(final Node<C> node, final List<Paren> parens) {
return node.cata(
String::valueOf,
name -> name,
(name, priority, arg) ->
name == PAREN ? random.randomItem(parens).apply(arg) :
priority == Integer.MAX_VALUE ? name + arg + brackets.get(name) :
(priority & 1) == 1 ? name + arg :
arg + name,
(name, a, b) -> a + " " + name + " " + b
);
}
public String render(final Expr<C, ?> expr, final Settings settings) {
return render(renderToNode(settings, expr), settings.parens());
}
public enum Mode {
FULL, SAME, TRUE_MINI, SIMPLE_MINI;
public Settings settings(final int limit) {
return new Settings(this, limit);
}
}
public record Paren(String open, String close) {
String apply(final String expression) {
return open() + expression + close();
}
}
public record Settings(Mode mode, int limit, List<Paren> parens) {
public Settings(final Mode mode, final int limit) {
this(mode, limit, DEFAULT_PARENS);
}
public <C> Node<C> extra(Node<C> node, final ExtendedRandom random) {
while (random.nextInt(Integer.MAX_VALUE) < limit) {
node = paren(true, node);
}
return node;
}
public Settings withParens(final List<Paren> parens) {
return this.parens.equals(parens) ? this : new Settings(mode, limit, List.copyOf(parens));
}
}
}

View File

@@ -0,0 +1,145 @@
package expression.common;
import base.ExtendedRandom;
import base.Functional;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public class NodeRendererBuilder<C> {
private final Renderer.Builder<C, NodeRenderer.Settings, Node<C>> nodeRenderer = Renderer.builder(Node::constant);
private final Map<String, Priority> priorities = new HashMap<>();
private final Map<String, String> brackets = new HashMap<>();
private final ExtendedRandom random;
public NodeRendererBuilder(final ExtendedRandom random) {
this.random = random;
nodeRenderer.unary(NodeRenderer.PAREN, (mode, arg) -> NodeRenderer.paren(true, arg));
}
public void unary(final String name, final int priority) {
final String space = name.equals("-") || Character.isLetter(name.charAt(0)) ? " " : "";
nodeRenderer.unary(
name,
(settings, arg) -> settings.extra(Node.op(name, priority, inner(settings, priority, arg, space)), random)
);
}
public void unary(final String left, final String right) {
brackets.put(left, right);
nodeRenderer.unary(
left,
(settings, arg) -> settings.extra(Node.op(left, Integer.MAX_VALUE, arg), random)
);
}
private Node<C> inner(final NodeRenderer.Settings settings, final int priority, final Node<C> arg, final String space) {
if (settings.mode() == NodeRenderer.Mode.FULL) {
return NodeRenderer.paren(true, arg);
} else {
final String op = arg.get(
c -> space,
n -> space,
(n, p, a) ->
priority > unaryPriority(arg) ? NodeRenderer.PAREN :
NodeRenderer.PAREN.equals(n) ? "" :
space,
(n, a, b) -> NodeRenderer.PAREN
);
return op.isEmpty() ? arg : Node.op(op, Priority.MAX.priority | 1, arg);
}
}
private static <C> Integer unaryPriority(final Node<C> node) {
return node.get(c -> Integer.MAX_VALUE, n -> Integer.MAX_VALUE, (n, p, a) -> p, (n, a, b) -> Integer.MIN_VALUE);
}
public void binary(final String name, final int priority) {
final Priority mp = new Priority(name, priority);
priorities.put(name, mp);
nodeRenderer.binary(name, (settings, l, r) -> settings.extra(process(settings, mp, l, r), random));
}
private Node<C> process(final NodeRenderer.Settings settings, final Priority mp, final Node<C> l, final Node<C> r) {
if (settings.mode() == NodeRenderer.Mode.FULL) {
return NodeRenderer.paren(true, op(mp, l, r));
}
final Priority lp = priority(l);
final Priority rp = priority(r);
final int rc = rp.compareLevels(mp);
// :NOTE: Especially ugly code, do not replicate
final boolean advanced = settings.mode() == NodeRenderer.Mode.SAME
|| mp.has(2)
|| mp.has(1) && (mp != rp || (settings.mode() == NodeRenderer.Mode.TRUE_MINI && hasOther(r, rp)));
final Node<C> al = NodeRenderer.paren(lp.compareLevels(mp) < 0, l);
if (rc == 0 && !advanced) {
return get(r, null, (n, a, b) -> rp.op(mp.op(al, a), b));
} else {
return mp.op(al, NodeRenderer.paren(rc == 0 && advanced || rc < 0, r));
}
}
private boolean hasOther(final Node<C> node, final Priority priority) {
return get(node, () -> false, (name, l, r) -> {
final Priority p = Functional.get(priorities, name);
if (p.compareLevels(priority) != 0) {
return false;
}
return p != priority || hasOther(l, priority);
});
}
private Node<C> op(final Priority mp, final Node<C> l, final Node<C> r) {
return mp.op(l, r);
}
private Priority priority(final Node<C> node) {
return get(node, () -> Priority.MAX, (n, a, b) -> Functional.get(priorities, n));
}
private <R> R get(final Node<C> node, final Supplier<R> common, final Node.Binary<Node<C>, R> binary) {
return node.get(
c -> common.get(),
n -> common.get(),
(n, p, a) -> common.get(),
binary
);
}
public NodeRenderer<C> build() {
return new NodeRenderer<>(nodeRenderer.build(), brackets, random);
}
// :NOTE: Especially ugly bit-fiddling, do not replicate
private record Priority(String op, int priority) {
private static final int Q = 3;
private static final Priority MAX = new Priority("MAX", Integer.MAX_VALUE - Q);
private int compareLevels(final Priority that) {
return (priority | Q) - (that.priority | Q);
}
@Override
public String toString() {
return String.format("Priority(%s, %d, %d)", op, priority | Q, priority & Q);
}
public <C> Node<C> op(final Node<C> l, final Node<C> r) {
return Node.op(op, l, r);
}
private boolean has(final int value) {
return (priority & Q) == value;
}
}
}

View File

@@ -0,0 +1,60 @@
package expression.common;
import base.Either;
import java.util.function.LongUnaryOperator;
import java.util.function.Supplier;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Reason {
public static final Reason OVERFLOW = new Reason("Overflow");
public static final Reason DBZ = new Reason("Division by zero");
private final String description;
public Reason(final String description) {
this.description = description;
}
public static <T> Either<Reason, T> eval(final Supplier<T> action) {
try {
return Either.right(action.get());
} catch (final ReasonException e) {
return Either.left(e.reason);
}
}
public static int overflow(final long value) {
return value < Integer.MIN_VALUE || Integer.MAX_VALUE < value
? OVERFLOW.error()
: (int) value;
}
public <T> T error() {
throw new ReasonException(this);
}
public LongUnaryOperator less(final long limit, final LongUnaryOperator op) {
return a -> a < limit ? error() : op.applyAsLong(a);
}
public LongUnaryOperator greater(final int limit, final LongUnaryOperator op) {
return a -> a > limit ? error() : op.applyAsLong(a);
}
private static class ReasonException extends RuntimeException {
private final Reason reason;
public ReasonException(final Reason reason) {
super(reason.description);
this.reason = reason;
}
}
@Override
public String toString() {
return String.format("Reason(%s)", description);
}
}

View File

@@ -0,0 +1,60 @@
package expression.common;
import base.Functional;
import base.Pair;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface Renderer<C, S, R> {
static <C, S, R> Builder<C, S, R> builder(final Node.Const<C, R> constant) {
return new Builder<>(constant);
}
R render(final Expr<C, R> expr, final S settings);
@FunctionalInterface
interface UnaryOperator<S, R> {
R apply(S settings, R arg);
}
@FunctionalInterface
interface BinaryOperator<S, R> {
R apply(S settings, R arg1, R arg2);
}
final class Builder<C, S, R> {
private final Node.Const<C, R> constant;
private final Map<String, UnaryOperator<S, R>> unary = new HashMap<>();
private final Map<String, BinaryOperator<S, R>> binary = new HashMap<>();
private Builder(final Node.Const<C, R> constant) {
this.constant = constant;
}
public void unary(final String name, final UnaryOperator<S, R> op) {
unary.put(name, op);
}
public void binary(final String name, final BinaryOperator<S, R> op) {
binary.put(name, op);
}
public Renderer<C, S, R> build() {
return (expr, settings) -> {
final Map<String, R> vars = expr.variables().stream()
.collect(Collectors.toMap(Pair::first, Pair::second));
return expr.node().cata(
constant,
name -> Functional.get(vars, name),
(name, p, arg) -> Functional.get(unary, name).apply(settings, arg),
(name, arg1, arg2) -> Functional.get(binary, name).apply(settings, arg1, arg2)
);
};
}
}
}

View File

@@ -0,0 +1,56 @@
package expression.common;
import base.Pair;
import base.TestCounter;
import expression.ToMiniString;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class TestGenerator<C, E extends ToMiniString> {
private final Generator<C, E> generator;
private final NodeRenderer<C> renderer;
public TestGenerator(final Generator<C, E> generator, final NodeRenderer<C> renderer) {
this.generator = generator;
this.renderer = renderer;
}
public void testBasic(final Consumer<Test<C, E>> test) {
generator.testBasic(consumer(test));
}
public void testRandom(final TestCounter counter, final int denominator, final Consumer<Test<C, E>> test) {
generator.testRandom(counter, denominator, consumer(test));
}
private Consumer<Expr<C, E>> consumer(final Consumer<TestGenerator.Test<C, E>> consumer) {
return expr -> consumer.accept(new TestGenerator.Test<>(expr, renderer));
}
public List<Pair<String, E>> variables(final int count) {
return generator.variables(count);
}
public String render(final Expr<C, ?> expr, final NodeRenderer.Settings settings) {
return renderer.render(expr, settings);
}
public static class Test<C, E> {
public final Expr<C, E> expr;
private final Map<NodeRenderer.Settings, String> rendered = new HashMap<>();
private final NodeRenderer<C> renderer;
public Test(final Expr<C, E> expr, final NodeRenderer<C> renderer) {
this.expr = expr;
this.renderer = renderer;
}
public String render(final NodeRenderer.Settings settings) {
return rendered.computeIfAbsent(settings, s -> renderer.render(expr, s));
}
}
}

View File

@@ -0,0 +1,145 @@
package expression.common;
import base.ExtendedRandom;
import base.Functional;
import expression.ToMiniString;
import expression.common.ExpressionKind.Variables;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class TestGeneratorBuilder<C> {
private final ExtendedRandom random;
private final Generator.Builder<C> generator;
private final NodeRendererBuilder<C> renderer;
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests = new ArrayList<>();
private final List<Node<C>> consts;
private final boolean verbose;
public TestGeneratorBuilder(
final ExtendedRandom random,
final Supplier<C> constant,
final List<C> constants,
final boolean verbose
) {
this.random = random;
this.verbose = verbose;
generator = Generator.builder(constant, random);
renderer = new NodeRendererBuilder<>(random);
consts = Functional.map(constants, Node::constant);
basicTests.add(vars -> consts.stream());
basicTests.add(List::stream);
}
private Node<C> c() {
return random.randomItem(consts);
}
private Node<C> v(final List<Node<C>> variables) {
return random.randomItem(variables);
}
private static <C> Node<C> f(final String name, final int priority, final Node<C> arg) {
return Node.op(name, priority, arg);
}
private static <C> Node<C> f(final String left, final Node<C> arg) {
return Node.op(left, Integer.MAX_VALUE, arg);
}
private static <C> Node<C> f(final String name, final Node<C> arg1, final Node<C> arg2) {
return Node.op(name, arg1, arg2);
}
@SafeVarargs
private void basicTests(final Function<List<Node<C>>, Node<C>>... tests) {
Arrays.stream(tests).map(test -> test.andThen(Stream::of)).forEachOrdered(basicTests::add);
}
public void unary(final String name, final int priority) {
generator.add(name, (priority & 1) * 2 - 1);
renderer.unary(name, priority);
if (verbose) {
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(name, priority, a)));
} else {
basicTests(vars -> f(name, priority, c()), vars -> f(name, priority, v(vars)));
}
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, priority, f(name, priority, f("+", v(vars), c())));
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, priority, c())));
basicTests(
vars -> f(name, priority, f("+", v(vars), v(vars))),
vars -> f(name, priority, f(name, priority, v(vars))),
vars -> f(name, priority, f("/", f(name, priority, v(vars)), f("+", v(vars), v(vars)))),
p1,
p2,
vars -> f("+", p1.apply(vars), p2.apply(vars))
);
}
public void unary(final String left, final String right) {
generator.add(left, 1);
renderer.unary(left, right);
if (verbose) {
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(left, a)));
} else {
basicTests(vars -> f(left, c()), vars -> f(left, v(vars)));
}
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(left, f(left, f("+", v(vars), c())));
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(left, c())));
basicTests(
vars -> f(left, f("+", v(vars), v(vars))),
vars -> f(left, f(left, v(vars))),
vars -> f(left, f("/", f(left, v(vars)), f("+", v(vars), v(vars)))),
p1,
p2,
vars -> f("+", p1.apply(vars), p2.apply(vars))
);
}
public void binary(final String name, final int priority) {
generator.add(name, 2);
renderer.binary(name, priority);
if (verbose) {
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream().limit(3))
.flatMap(a -> consts.stream().map(b -> f(name, a, b))));
} else {
basicTests(
vars -> f(name, c(), c()),
vars -> f(name, v(vars), c()),
vars -> f(name, c(), v(vars)),
vars -> f(name, v(vars), v(vars))
);
}
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, f(name, f("+", v(vars), c()), v(vars)), v(vars));
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, c(), v(vars))));
basicTests(
vars -> f(name, f(name, v(vars), v(vars)), v(vars)),
vars -> f(name, v(vars), f(name, v(vars), v(vars))),
vars -> f(name, f(name, v(vars), v(vars)), f(name, v(vars), v(vars))),
vars -> f(name, f("-", f(name, v(vars), v(vars)), c()), f("+", v(vars), v(vars))),
p1,
p2,
vars -> f("+", p1.apply(vars), p2.apply(vars))
);
}
public <E extends ToMiniString> TestGenerator<C,E> build(final Variables<E> variables) {
return new TestGenerator<>(generator.build(variables, basicTests), renderer.build());
}
}

View File

@@ -0,0 +1,51 @@
package expression.common;
import base.Asserts;
import base.ExtendedRandom;
import expression.Const;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.Function;
import java.util.function.IntFunction;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class Type<C> {
private final IntFunction<C> fromInt;
private final Function<ExtendedRandom, C> random;
private final Function<C, Const> constant;
public Type(final IntFunction<C> fromInt, final Function<ExtendedRandom, C> random, final Class<?> type) {
this.fromInt = fromInt;
this.random = random;
try {
final MethodHandle constructor = MethodHandles.publicLookup()
.findConstructor(Const.class, MethodType.methodType(void.class, type));
constant = c -> {
try {
return (Const) constructor.invoke(c);
} catch (final Throwable e) {
throw Asserts.error("Cannot create new Const(%s): %s", c, e);
}
};
} catch (final IllegalAccessException | NoSuchMethodException e) {
throw Asserts.error("Cannot find constructor Const(%s): %s", type, e);
}
}
public Const constant(final C value) {
return constant.apply(value);
}
public C fromInt(final int value) {
return fromInt.apply(value);
}
public C randomValue(final ExtendedRandom random) {
return this.random.apply(random);
}
}

View File

@@ -0,0 +1,7 @@
/**
* Expressions generators for expression-based homeworks
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.common;

View File

@@ -0,0 +1,29 @@
package expression.exceptions;
import java.math.BigDecimal;
import java.math.BigInteger;
import expression.*;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class CheckedAdd extends AbstractBinaryOperation {
public CheckedAdd(AbstractExpression l, AbstractExpression r) { super(l, r); }
@Override protected String getOperator() { return "+"; }
@Override protected int getPriority() { return 1; }
@Override protected boolean isRightAssoc() { return false; }
@Override
protected int applyInt(int a, int b) {
int result = a + b;
if (((a ^ result) & (b ^ result)) < 0) {
throw new OverflowException("addition");
}
return result;
}
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.add(b); }
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.add(b); }
}

View File

@@ -0,0 +1,37 @@
package expression.exceptions;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import expression.*;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class CheckedDivide extends AbstractBinaryOperation {
public CheckedDivide(AbstractExpression l, AbstractExpression r) { super(l, r); }
@Override protected String getOperator() { return "/"; }
@Override protected int getPriority() { return 2; }
@Override protected boolean isRightAssoc() { return true; }
@Override
protected int applyInt(int a, int b) {
if (b == 0) throw new DivisionByZeroException();
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("division");
return a / b;
}
@Override
protected BigInteger applyBi(BigInteger a, BigInteger b) {
if (b.signum() == 0) throw new DivisionByZeroException();
return a.divide(b);
}
@Override
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
if (b.signum() == 0) throw new DivisionByZeroException();
return a.divide(b, MathContext.DECIMAL128);
}
}

View File

@@ -0,0 +1,32 @@
package expression.exceptions;
import java.math.BigDecimal;
import java.math.BigInteger;
import expression.*;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class CheckedMultiply extends AbstractBinaryOperation {
public CheckedMultiply(AbstractExpression l, AbstractExpression r) { super(l, r); }
@Override protected String getOperator() { return "*"; }
@Override protected int getPriority() { return 2; }
@Override protected boolean isRightAssoc() { return false; }
@Override
protected int applyInt(int a, int b) {
if (a == 0 || b == 0) return 0;
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("multiplication");
if (b == Integer.MIN_VALUE && a == -1) throw new OverflowException("multiplication");
int result = a * b;
if (result / a != b) {
throw new OverflowException("multiplication");
}
return result;
}
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.multiply(b); }
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.multiply(b); }
}

View File

@@ -0,0 +1,44 @@
package expression.exceptions;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import expression.*;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class CheckedNegate extends AbstractExpression {
private final AbstractExpression operand;
public CheckedNegate(AbstractExpression operand) {
this.operand = operand;
}
private static int checkedNegate(int n) {
if (n == Integer.MIN_VALUE) throw new OverflowException("negation");
return -n;
}
@Override public int evaluate(int x) { return checkedNegate(operand.evaluate(x)); }
@Override public int evaluate(int x, int y, int z) { return checkedNegate(operand.evaluate(x,y,z)); }
@Override public int evaluate(List<Integer> vars) { return checkedNegate(operand.evaluate(vars)); }
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).negate(); }
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).negate(); }
@Override public String toString() { return "-(" + operand + ")"; }
@Override public String toMiniString() {
if (operand instanceof AbstractBinaryOperation) return "-(" + operand.toMiniString() + ")";
return "- " + operand.toMiniString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof CheckedNegate)) return false;
return operand.equals(((CheckedNegate) obj).operand);
}
@Override public int hashCode() { return -operand.hashCode(); }
}

View File

@@ -0,0 +1,31 @@
package expression.exceptions;
import expression.AbstractBinaryOperation;
import expression.AbstractExpression;
import expression.OverflowException;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class CheckedSubtract extends AbstractBinaryOperation {
public CheckedSubtract(AbstractExpression l, AbstractExpression r) { super(l, r); }
@Override protected String getOperator() { return "-"; }
@Override protected int getPriority() { return 1; }
@Override protected boolean isRightAssoc() { return true; }
@Override
protected int applyInt(int a, int b) {
int result = a - b;
if (((a ^ b) & (a ^ result)) < 0) {
throw new OverflowException("subtraction");
}
return result;
}
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.subtract(b); }
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.subtract(b); }
}

View File

@@ -0,0 +1,30 @@
package expression.exceptions;
import base.Selector;
import expression.ListExpression;
import expression.parser.Operations;
import static expression.parser.Operations.*;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExceptionsTest {
private static final ExpressionParser PARSER = new ExpressionParser();
private static final Operations.Operation LIST = kind(ListExpression.KIND, PARSER::parse);
public static final Selector SELECTOR = Selector.composite(ExceptionsTest.class, ExceptionsTester::new, "easy", "hard")
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
.variant("3637", ABS, CUBE, CBRT)
.variant("3839", ABS, CUBE, CBRT, SQUARE, SQRT)
.variant("3435", ABS, SQRT)
.variant("3233", ABS, CBRT)
.selector();
private ExceptionsTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,162 @@
package expression.exceptions;
import base.Functional;
import base.Named;
import base.Pair;
import expression.ToMiniString;
import expression.Variable;
import expression.common.ExpressionKind;
import expression.parser.ParserTestSet;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongBinaryOperator;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExceptionsTestSet<E extends ToMiniString, C> extends ParserTestSet<E, C> {
private static final int D = 5;
private static final List<Integer> OVERFLOW_VALUES = new ArrayList<>();
private final char[] CHARS = "AZ+-*%()[]<>".toCharArray();
static {
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE + D);
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE / 2);
Functional.addRange(OVERFLOW_VALUES, D, (int) -Math.sqrt(Integer.MAX_VALUE));
Functional.addRange(OVERFLOW_VALUES, D, 0);
Functional.addRange(OVERFLOW_VALUES, D, (int) Math.sqrt(Integer.MAX_VALUE));
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE / 2);
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE - D);
}
private final List<Named<String>> parsingTest;
public ExceptionsTestSet(final ExceptionsTester tester, final ParsedKind<E, C> kind) {
super(tester, kind, false);
parsingTest = tester.parsingTest;
}
private void testParsingErrors() {
counter.testForEach(parsingTest, op -> {
final List<String> names = Functional.map(kind.kind().variables().generate(counter.random(), 3), Pair::first);
final String expr = mangle(op.value(), names);
try {
kind.parse(expr, names);
counter.fail("Successfully parsed '%s'", op.value());
} catch (final Exception e) {
counter.format("%-30s %s%n", op.name(), e.getClass().getSimpleName() + ": " + e.getMessage());
}
});
}
private void testOverflow() {
final List<Pair<String, E>> variables = kind.kind().variables().generate(counter.random(), 3);
final List<String> names = Functional.map(variables, Pair::first);
final Variable vx = (Variable) variables.get(0).second();
final Variable vy = (Variable) variables.get(1).second();
//noinspection Convert2MethodRef
testOverflow(names, (a, b) -> a + b, "+", new CheckedAdd(vx, vy));
testOverflow(names, (a, b) -> a - b, "-", new CheckedSubtract(vx, vy));
testOverflow(names, (a, b) -> a * b, "*", new CheckedMultiply(vx, vy));
testOverflow(names, (a, b) -> b == 0 ? Long.MAX_VALUE : a / b, "/", new CheckedDivide(vx, vy));
testOverflow(names, (a, b) -> -b, "<- ignore first argument, unary -", new CheckedNegate(vy));
}
private void testOverflow(final List<String> names, final LongBinaryOperator f, final String op, final Object expression) {
final ExpressionKind<E, C> kind = this.kind.kind();
for (final int a : OVERFLOW_VALUES) {
for (final int b : OVERFLOW_VALUES) {
final long expected = f.applyAsLong(a, b);
final boolean isInt = Integer.MIN_VALUE <= expected && expected <= Integer.MAX_VALUE;
try {
final C actual = kind.evaluate(
kind.cast(expression),
names,
kind.fromInts(List.of(a, b, 0))
);
counter.checkTrue(
isInt && kind.fromInt((int) expected).equals(actual),
"%d %s %d == %d", a, op, b, actual
);
} catch (final Exception e) {
if (isInt) {
counter.fail(e, "Unexpected error in %d %s %d", a, op, b);
}
}
}
}
}
@Override
protected void test() {
counter.scope("Overflow tests", (Runnable) this::testOverflow);
super.test();
counter.scope("Parsing error tests", this::testParsingErrors);
}
@Override
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
final String expr = expression.strip();
if (expr.length() > 10) {
for (final char ch : CHARS) {
for (int i = 0; i < 10; i++) {
final int index = 1 + tester.random().nextInt(expr.length() - 2);
int pi = index - 1;
while (Character.isWhitespace(expr.charAt(pi))) {
pi--;
}
int ni = index;
while (Character.isWhitespace(expr.charAt(ni))) {
ni++;
}
final char pc = expr.charAt(pi);
final char nc = expr.charAt(ni);
if (
"-([{*∛√²³₂₃!‖⎵⎴⌊⌈=?".indexOf(nc) < 0 &&
(!Character.isLetterOrDigit(pc) || !Character.isLetterOrDigit(ch)) &&
nc != ch && pc != ch &&
!Character.isLetterOrDigit(nc) && nc != '$'
) {
shouldFail(
variables,
"Parsing error expected for " + insert(expr, index, "<ERROR_INSERTED -->" + ch + "<-- ERROR_INSERTED>"),
insert(expr, index, String.valueOf(ch))
);
break;
}
}
}
parens(variables, expr, '[', ']');
parens(variables, expr, '{', '}');
}
return counter.testV(() -> counter.call("parse", () -> kind.parse(expr, variables)));
}
private static String insert(final String expr, final int index, final String value) {
return expr.substring(0, index) + value + expr.substring(index);
}
private void parens(final List<String> variables, final String expr, final char open, final char close) {
if (expr.indexOf(open) >= 0) {
replaces(variables, expr, open, '(');
replaces(variables, expr, close, ')');
if (expr.indexOf('(') >= 0) {
replaces(variables, expr, '(', open);
replaces(variables, expr, ')', close);
}
}
}
private void replaces(final List<String> variables, final String expr, final char what, final char by) {
final String input = expr.replace(what, by);
shouldFail(variables, "Unmatched parentheses: " + input, input);
}
private void shouldFail(final List<String> variables, final String message, final String input) {
counter.shouldFail(message, () -> kind.parse(input, variables));
}
}

View File

@@ -0,0 +1,100 @@
package expression.exceptions;
import base.Named;
import base.TestCounter;
import expression.common.Reason;
import expression.parser.ParserTestSet;
import expression.parser.ParserTester;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.LongBinaryOperator;
import java.util.function.LongToIntFunction;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExceptionsTester extends ParserTester {
/* package-private */ final List<Named<String>> parsingTest = new ArrayList<>(List.of(
Named.of("No first argument", "* $y * $z"),
Named.of("No middle argument", "$x * * $z"),
Named.of("No last argument", "$x * $y * "),
Named.of("No first argument'", "1 + (* $y * $z) + 2"),
Named.of("No middle argument'", "1 + ($x * / 9) + 3"),
Named.of("No last argument'", "1 + ($x * $y - ) + 3"),
Named.of("No opening parenthesis", "$x * $y)"),
Named.of("No closing parenthesis", "($x * $y"),
Named.of("Mismatched closing parenthesis", "($x * $y]"),
Named.of("Mismatched open parenthesis", "[$x * $y)"),
Named.of("Start symbol", "@$x * $y"),
Named.of("Middle symbol", "$x @ * $y"),
Named.of("End symbol", "$x * $y@"),
Named.of("Constant overflow 1", Integer.MIN_VALUE - 1L + ""),
Named.of("Constant overflow 2", Integer.MAX_VALUE + 1L + ""),
Named.of("Bare +", "+"),
Named.of("Bare -", "-"),
Named.of("Bare a", "a"),
Named.of("(())", "(())"),
Named.of("Spaces in numbers", "10 20")
));
public ExceptionsTester(final TestCounter counter) {
super(counter);
}
private void parsingTests(final String... tests) {
for (final String test : tests) {
parsingTest.add(Named.of(test, test));
}
}
@Override
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
if (allowed(name)) {
parsingTests(name, "1 * " + name, name + " * 1");
}
parsingTests(name + "()", name + "(1, 2)");
if (name.length() > 1) {
parsingTests(name + "q");
}
if (allLetterAndDigit(name)) {
parsingTests(name + "1", name + "q");
}
super.unary(name, priority, op);
}
private static boolean allowed(final String name) {
return !"xyz".contains(name.substring(0, 1)) && !"xyz".contains(name.substring(name.length() - 1));
}
@Override
public void binary(final String name, final int priority, final LongBinaryOperator op) {
if (allowed(name)) {
parsingTests(name);
}
parsingTests("1 " + name, "1 " + name + " * 3");
if (!"-".equals(name)) {
parsingTests(name + " 1", "1 * " + name + " 2");
}
if (allLetterAndDigit(name)) {
parsingTests("5" + name + "5", "5 " + name + "5", "5 " + name + "5 5", "1" + name + "x 1", "1 " + name + "x 1");
}
super.binary(name, priority, op);
}
private static boolean allLetterAndDigit(final String name) {
return name.chars().allMatch(Character::isLetterOrDigit);
}
@Override
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
new ExceptionsTestSet<>(this, kind).test();
}
@Override
protected int cast(final long value) {
return Reason.overflow(value);
}
}

View File

@@ -0,0 +1,215 @@
package expression.exceptions;
import expression.*;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class ExpressionParser implements ListParser {
private String src;
private int pos;
private java.util.List<String> variables;
@Override
public ListExpression parse(String expression, java.util.List<String> variables) {
// System.err.println("PARSE: [" + expression + "]");
this.src = expression;
this.pos = 0;
this.variables = variables;
AbstractExpression result = parseMinMax();
skipWhitespace();
if (pos < src.length()) {
throw new IllegalArgumentException(
"Unexpected character '" + src.charAt(pos) + "' at position " + pos);
}
return result;
}
private AbstractExpression parseMinMax() {
AbstractExpression left = parseAddSub();
while (true) {
skipWhitespace();
if (tryConsume("min")) left = new Min(left, parseAddSub());
else if (tryConsume("max")) left = new Max(left, parseAddSub());
else if (tryConsume("set")) left = new SetBit(left, parseAddSub());
else if (tryConsume("clear")) left = new Clear(left, parseAddSub());
else break;
}
return left;
}
private AbstractExpression parseAddSub() {
AbstractExpression left = parseMulDiv();
while (true) {
skipWhitespace();
if (pos < src.length() && src.charAt(pos) == '+') {
pos++;
left = new CheckedAdd(left, parseMulDiv());
} else if (pos < src.length() && src.charAt(pos) == '-') {
pos++;
left = new CheckedSubtract(left, parseMulDiv());
} else break;
}
return left;
}
private AbstractExpression parseMulDiv() {
AbstractExpression left = parsePower();
while (true) {
skipWhitespace();
if (pos < src.length() && src.charAt(pos) == '*' && nextCharIs('*')) {
pos++;
left = new CheckedMultiply(left, parsePower());
} else if (pos < src.length() && src.charAt(pos) == '/' && nextCharIs('/')) {
pos++;
left = new CheckedDivide(left, parsePower());
} else break;
}
return left;
}
private AbstractExpression parsePower() {
AbstractExpression left = parseUnary();
while (true) {
skipWhitespace();
if (pos + 1 < src.length() && src.charAt(pos) == '*' && src.charAt(pos + 1) == '*') {
pos += 2;
left = new Power(left, parseUnary());
} else if (pos + 1 < src.length() && src.charAt(pos) == '/' && src.charAt(pos + 1) == '/') {
pos += 2;
left = new Log(left, parseUnary());
} else break;
}
return left;
}
private AbstractExpression parseUnary() {
skipWhitespace();
if (pos >= src.length())
throw new IllegalArgumentException("Unexpected end of expression at position " + pos);
if (src.charAt(pos) == '-') {
pos++;
if (pos < src.length() && Character.isDigit(src.charAt(pos))) return parsePostfix(parseNumber(true));
return new CheckedNegate(parseUnary());
}
// abs: ||x|| (U+2016 DOUBLE VERTICAL LINE)
if (src.charAt(pos) == '‖') {
pos++;
AbstractExpression inner = parseMinMax();
skipWhitespace();
if (pos >= src.length() || src.charAt(pos) != '‖')
throw new IllegalArgumentException("Expected ‖ at position " + pos);
pos++;
return parsePostfix(new Abs(inner));
}
// prefix unary: sqrt U+221A, cbrt U+221B
if (src.charAt(pos) == '√') { pos++; return parsePostfix(new Sqrt(parseUnary())); }
if (src.charAt(pos) == '∛') { pos++; return parsePostfix(new Cbrt(parseUnary())); }
if (tryConsume("reverse")) return parsePostfix(new Reverse(parseUnary()));
if (tryConsume("digits")) return parsePostfix(new Digits(parseUnary()));
if (tryConsume("floor")) return parsePostfix(new Floor(parseUnary()));
if (tryConsume("ceiling")) return parsePostfix(new Ceiling(parseUnary()));
if (tryConsume("log₂") || tryConsume("log2")) return parsePostfix(new Log2(parseUnary()));
if (tryConsume("pow₂") || tryConsume("pow2")) return parsePostfix(new Pow2(parseUnary()));
if (tryConsume("low")) return parsePostfix(new Low(parseUnary()));
if (tryConsume("high")) return parsePostfix(new High(parseUnary()));
return parsePostfix(parsePrimary());
}
private AbstractExpression parsePostfix(AbstractExpression base) {
skipWhitespace();
if (pos < src.length() && src.charAt(pos) == '²') { pos++; return parsePostfix(new Square(base)); }
if (pos < src.length() && src.charAt(pos) == '³') { pos++; return parsePostfix(new Cube(base)); }
return base;
}
private AbstractExpression parsePrimary() {
skipWhitespace();
if (pos >= src.length()) throw new IllegalArgumentException("Unexpected end of expression");
char c = src.charAt(pos);
if (c == '(') {
pos++;
AbstractExpression inner = parseMinMax();
skipWhitespace();
expect();
return inner;
}
if (c == '$') { pos++; return new Variable(parseIndex()); }
if (Character.isDigit(c)) return parseNumber(false);
if (Character.isLetter(c)) {
int start = pos;
while (pos < src.length() && (Character.isLetterOrDigit(src.charAt(pos)) || src.charAt(pos) == '_'))
pos++;
String name = src.substring(start, pos);
int idx = variables != null ? variables.indexOf(name) : -1;
if (idx >= 0) return new Variable(idx, name);
throw new IllegalArgumentException("Unknown identifier '" + name + "' at position " + start);
}
throw new IllegalArgumentException("Unexpected character '" + c + "' at position " + pos);
}
private void skipWhitespace() {
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) pos++;
}
private boolean nextCharIs(char next) {
return pos + 1 >= src.length() || src.charAt(pos + 1) != next;
}
private boolean tryConsume(String keyword) {
skipWhitespace();
if (!src.startsWith(keyword, pos)) return false;
int end = pos + keyword.length();
if (end < src.length()) {
char next = src.charAt(end);
if (Character.isLetterOrDigit(next) || next == '_') return false;
}
if (variables != null && variables.contains(keyword)) return false;
pos = end;
return true;
}
private void expect() {
if (pos >= src.length() || src.charAt(pos) != ')')
throw new IllegalArgumentException("Expected '" + ')' + "' at position " + pos
+ (pos < src.length() ? ", got '" + src.charAt(pos) + "'" : ", got end of input"));
pos++;
}
private AbstractExpression parseNumber(boolean negative) {
int start = pos;
while (pos < src.length() && Character.isDigit(src.charAt(pos))) pos++;
if (start == pos) throw new IllegalArgumentException("Expected digit at position " + pos);
String numStr = src.substring(start, pos);
int result = 0;
for (int i = 0; i < numStr.length(); i++) {
int digit = numStr.charAt(i) - '0';
if (!negative) {
if (result > (Integer.MAX_VALUE - digit) / 10)
throw new OverflowException("constant " + numStr);
result = result * 10 + digit;
} else {
if (result < (Integer.MIN_VALUE + digit) / 10)
throw new OverflowException("constant -" + numStr);
result = result * 10 - digit;
}
}
return new Const(result);
}
private int parseIndex() {
int start = pos;
while (pos < src.length() && Character.isDigit(src.charAt(pos))) pos++;
if (start == pos) throw new IllegalArgumentException("Expected digit after '$' at position " + pos);
return Integer.parseInt(src.substring(start, pos));
}
}

View File

@@ -0,0 +1,13 @@
package expression.exceptions;
import expression.ListExpression;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
public interface ListParser {
ListExpression parse(String expression, final List<String> variables) throws Exception;
}

View File

@@ -0,0 +1,8 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#expressions-exceptions">Expression Error Handling</a> homework
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
*
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.exceptions;

View File

@@ -0,0 +1,25 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public interface ArithmeticType<T> {
T fromInt(int value);
T add(T a, T b);
T subtract(T a, T b);
T multiply(T a, T b);
T divide(T a, T b);
T negate(T a);
default T count(T a) {
throw new UnsupportedOperationException("count");
}
default T min(T a, T b) {
throw new UnsupportedOperationException("min");
}
default T max(T a, T b) {
throw new UnsupportedOperationException("max");
}
}

View File

@@ -0,0 +1,61 @@
package expression.generic;
import java.math.BigInteger;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class BigIntegerType implements ArithmeticType<BigInteger> {
public static final BigIntegerType INSTANCE = new BigIntegerType();
private BigIntegerType() {}
@Override
public BigInteger fromInt(int value) {
return BigInteger.valueOf(value);
}
@Override
public BigInteger add(BigInteger a, BigInteger b) {
return a.add(b);
}
@Override
public BigInteger subtract(BigInteger a, BigInteger b) {
return a.subtract(b);
}
@Override
public BigInteger multiply(BigInteger a, BigInteger b) {
return a.multiply(b);
}
@Override
public BigInteger divide(BigInteger a, BigInteger b) {
if (b.equals(BigInteger.ZERO)) throw new ArithmeticException(
"Division by zero"
);
return a.divide(b);
}
@Override
public BigInteger negate(BigInteger a) {
return a.negate();
}
@Override
public BigInteger count(BigInteger a) {
return BigInteger.valueOf(a.bitCount());
}
@Override
public BigInteger min(BigInteger a, BigInteger b) {
return a.min(b);
}
@Override
public BigInteger max(BigInteger a, BigInteger b) {
return a.max(b);
}
}

View File

@@ -0,0 +1,56 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class DoubleType implements ArithmeticType<Double> {
public static final DoubleType INSTANCE = new DoubleType();
private DoubleType() {}
@Override
public Double fromInt(int value) {
return (double) value;
}
@Override
public Double add(Double a, Double b) {
return a + b;
}
@Override
public Double subtract(Double a, Double b) {
return a - b;
}
@Override
public Double multiply(Double a, Double b) {
return a * b;
}
@Override
public Double divide(Double a, Double b) {
return a / b;
}
@Override
public Double negate(Double a) {
return -a;
}
@Override
public Double count(Double a) {
return (double) Long.bitCount(Double.doubleToLongBits(a));
}
@Override
public Double min(Double a, Double b) {
return Math.min(a, b);
}
@Override
public Double max(Double a, Double b) {
return Math.max(a, b);
}
}

View File

@@ -0,0 +1,56 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class FloatType implements ArithmeticType<Float> {
public static final FloatType INSTANCE = new FloatType();
private FloatType() {}
@Override
public Float fromInt(int value) {
return (float) value;
}
@Override
public Float add(Float a, Float b) {
return a + b;
}
@Override
public Float subtract(Float a, Float b) {
return a - b;
}
@Override
public Float multiply(Float a, Float b) {
return a * b;
}
@Override
public Float divide(Float a, Float b) {
return a / b;
}
@Override
public Float negate(Float a) {
return -a;
}
@Override
public Float count(Float a) {
return (float) Integer.bitCount(Float.floatToIntBits(a));
}
@Override
public Float min(Float a, Float b) {
return Math.min(a, b);
}
@Override
public Float max(Float a, Float b) {
return Math.max(a, b);
}
}

View File

@@ -0,0 +1,10 @@
package expression.generic;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public interface GenericExpr<T> {
T evaluate(ArithmeticType<T> type, List<T> vars);
}

View File

@@ -0,0 +1,239 @@
package expression.generic;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class GenericParser {
private static final List<String> VARIABLES = List.of("x", "y", "z");
private String src;
private int pos;
public <T> GenericExpr<T> parse(String expression) {
this.src = expression;
this.pos = 0;
GenericExpr<T> result = parseMinMax();
skipWhitespace();
if (pos < src.length()) {
throw new IllegalArgumentException(
"Unexpected character '" +
src.charAt(pos) +
"' at position " +
pos
);
}
return result;
}
private <T> GenericExpr<T> parseMinMax() {
GenericExpr<T> left = parseAddSub();
while (true) {
skipWhitespace();
if (matchKeyword("min")) {
GenericExpr<T> right = parseAddSub();
GenericExpr<T> l = left;
left = (type, vars) ->
type.min(
l.evaluate(type, vars),
right.evaluate(type, vars)
);
} else if (matchKeyword("max")) {
GenericExpr<T> right = parseAddSub();
GenericExpr<T> l = left;
left = (type, vars) ->
type.max(
l.evaluate(type, vars),
right.evaluate(type, vars)
);
} else {
break;
}
}
return left;
}
private <T> GenericExpr<T> parseAddSub() {
GenericExpr<T> left = parseMulDiv();
while (true) {
skipWhitespace();
if (pos < src.length() && src.charAt(pos) == '+') {
pos++;
GenericExpr<T> right = parseMulDiv();
GenericExpr<T> l = left;
left = (type, vars) ->
type.add(
l.evaluate(type, vars),
right.evaluate(type, vars)
);
} else if (
pos < src.length() && src.charAt(pos) == '-' && !nextIsKeyword()
) {
pos++;
GenericExpr<T> right = parseMulDiv();
GenericExpr<T> l = left;
left = (type, vars) ->
type.subtract(
l.evaluate(type, vars),
right.evaluate(type, vars)
);
} else {
break;
}
}
return left;
}
private <T> GenericExpr<T> parseMulDiv() {
GenericExpr<T> left = parseUnary();
while (true) {
skipWhitespace();
if (pos < src.length() && src.charAt(pos) == '*') {
pos++;
GenericExpr<T> right = parseUnary();
GenericExpr<T> l = left;
left = (type, vars) ->
type.multiply(
l.evaluate(type, vars),
right.evaluate(type, vars)
);
} else if (pos < src.length() && src.charAt(pos) == '/') {
pos++;
GenericExpr<T> right = parseUnary();
GenericExpr<T> l = left;
left = (type, vars) ->
type.divide(
l.evaluate(type, vars),
right.evaluate(type, vars)
);
} else {
break;
}
}
return left;
}
private <T> GenericExpr<T> parseUnary() {
skipWhitespace();
if (pos >= src.length()) {
throw new IllegalArgumentException(
"Unexpected end of expression at position " + pos
);
}
if (src.charAt(pos) == '-') {
pos++;
skipWhitespace();
if (pos < src.length() && Character.isDigit(src.charAt(pos))) {
return parseNumber(true);
}
GenericExpr<T> operand = parseUnary();
return (type, vars) -> type.negate(operand.evaluate(type, vars));
}
if (matchKeyword("count")) {
GenericExpr<T> operand = parseUnary();
return (type, vars) -> type.count(operand.evaluate(type, vars));
}
return parsePrimary();
}
private <T> GenericExpr<T> parsePrimary() {
skipWhitespace();
if (pos >= src.length()) {
throw new IllegalArgumentException("Unexpected end of expression");
}
char c = src.charAt(pos);
if (c == '(') {
pos++;
GenericExpr<T> inner = parseMinMax();
skipWhitespace();
expect();
return inner;
}
if (Character.isDigit(c)) {
return parseNumber(false);
}
if (Character.isLetter(c)) {
int start = pos;
while (
pos < src.length() && Character.isLetterOrDigit(src.charAt(pos))
) {
pos++;
}
String name = src.substring(start, pos);
int idx = VARIABLES.indexOf(name);
if (idx >= 0) {
return (type, vars) -> vars.get(idx);
}
throw new IllegalArgumentException(
"Unknown variable '" + name + "' at position " + start
);
}
throw new IllegalArgumentException(
"Unexpected character '" + c + "' at position " + pos
);
}
private <T> GenericExpr<T> parseNumber(boolean negative) {
int start = pos;
while (pos < src.length() && Character.isDigit(src.charAt(pos))) {
pos++;
}
if (start == pos) {
throw new IllegalArgumentException(
"Expected digit at position " + pos
);
}
String numStr = src.substring(start, pos);
long val = Long.parseLong(numStr);
if (negative) val = -val;
if (val < Integer.MIN_VALUE || val > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Integer overflow: " + val);
}
int intVal = (int) val;
return (type, vars) -> type.fromInt(intVal);
}
private boolean matchKeyword(String keyword) {
int i = pos;
for (char ch : keyword.toCharArray()) {
if (i >= src.length() || src.charAt(i) != ch) return false;
i++;
}
if (
i < src.length() && Character.isLetterOrDigit(src.charAt(i))
) return false;
pos = i;
return true;
}
private boolean nextIsKeyword() {
return false;
}
private void skipWhitespace() {
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) {
pos++;
}
}
private void expect() {
if (pos >= src.length() || src.charAt(pos) != ')') {
throw new IllegalArgumentException(
"Expected '" +
')' +
"' at position " +
pos +
(pos < src.length()
? ", got '" + src.charAt(pos) + "'"
: ", got end of input")
);
}
pos++;
}
}

View File

@@ -0,0 +1,135 @@
package expression.generic;
import java.math.BigInteger;
import java.util.List;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class GenericTabulator implements Tabulator {
@Override
public Object[][][] tabulate(
String mode,
String expression,
int x1,
int x2,
int y1,
int y2,
int z1,
int z2
) throws Exception {
return switch (mode) {
case "i" -> compute(
IntCheckedType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
case "d" -> compute(
DoubleType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
case "bi" -> compute(
BigIntegerType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
case "u" -> compute(
IntUncheckedType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
case "s" -> compute(
ShortType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
case "f" -> compute(
FloatType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
case "t", "it" -> compute(
IntTruncType.INSTANCE,
expression,
x1,
x2,
y1,
y2,
z1,
z2
);
default -> throw new IllegalArgumentException(
"Unknown mode: " + mode
);
};
}
private <T> Object[][][] compute(
ArithmeticType<T> type,
String expression,
int x1,
int x2,
int y1,
int y2,
int z1,
int z2
) {
GenericExpr<T> expr = new GenericParser().parse(expression);
int xLen = x2 - x1 + 1;
int yLen = y2 - y1 + 1;
int zLen = z2 - z1 + 1;
Object[][][] result = new Object[xLen][yLen][zLen];
for (int xi = 0; xi < xLen; xi++) {
for (int yi = 0; yi < yLen; yi++) {
for (int zi = 0; zi < zLen; zi++) {
T x = type.fromInt(x1 + xi);
T y = type.fromInt(y1 + yi);
T z = type.fromInt(z1 + zi);
try {
result[xi][yi][zi] = expr.evaluate(
type,
List.of(x, y, z)
);
} catch (Exception e) {
result[xi][yi][zi] = null;
}
}
}
}
return result;
}
}

View File

@@ -0,0 +1,184 @@
package expression.generic;
import base.Selector;
import java.math.BigInteger;
import java.util.function.*;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class GenericTest {
// === Base
private static final Consumer<GenericTester> ADD = binary("+", 200);
private static final Consumer<GenericTester> SUBTRACT = binary("-", -200);
private static final Consumer<GenericTester> MULTIPLY = binary("*", 301);
private static final Consumer<GenericTester> DIVIDE = binary("/", -300);
private static final Consumer<GenericTester> NEGATE = unary("-");
// === Cmm
private static final Consumer<GenericTester> COUNT = unary("count");
private static final Consumer<GenericTester> MIN = binary("min", 50);
private static final Consumer<GenericTester> MAX = binary("max", 50);
// === Checked integers
private static Integer i(final long v) {
if (v != (int) v) {
throw new ArithmeticException("Overflow");
}
return (int) v;
}
private static final GenericTester.Mode.Builder<Integer> INTEGER_CHECKED = mode("i", c -> c)
.binary("+", (a, b) -> i(a + (long) b))
.binary("-", (a, b) -> i(a - (long) b))
.binary("*", (a, b) -> i(a * (long) b))
.binary("/", (a, b) -> i(a / (long) b))
.unary("-", a -> i(- (long) a))
.unary("count", Integer::bitCount)
.binary("min", Math::min)
.binary("max", Math::max)
;
// === Doubles
private static final GenericTester.Mode.Builder<Double> DOUBLE = mode("d", c -> (double) c)
.binary("+", Double::sum)
.binary("-", (a, b) -> a - b)
.binary("*", (a, b) -> a * b)
.binary("/", (a, b) -> a / b)
.unary("-", a -> -a)
.unary("count", a -> (double) Long.bitCount(Double.doubleToLongBits(a)))
.binary("min", Math::min)
.binary("max", Math::max)
;
// === BigIntegers
private static final GenericTester.Mode.Builder<BigInteger> BIG_INTEGER = mode("bi", BigInteger::valueOf)
.binary("+", BigInteger::add)
.binary("-", BigInteger::subtract)
.binary("*", BigInteger::multiply)
.binary("/", BigInteger::divide)
.unary("-", BigInteger::negate)
.unary("count", a -> BigInteger.valueOf(a.bitCount()))
.binary("min", BigInteger::min)
.binary("max", BigInteger::max)
;
// === Unchecked integers
private static final GenericTester.Mode.Builder<Integer> INTEGER_UNCHECKED = mode("u", c -> c)
.binary("+", Integer::sum)
.binary("-", (a, b) -> a - b)
.binary("*", (a, b) -> a * b)
.binary("/", (a, b) -> a / b)
.unary("-", a -> -a)
.unary("count", Integer::bitCount)
.binary("min", Math::min)
.binary("max", Math::max)
;
// === Short
private static short s(final int x) {
return (short) x;
}
private static BinaryOperator<Short> s(final IntBinaryOperator op) {
return (a, b) -> s(op.applyAsInt(a, b));
}
private static final GenericTester.Mode.Builder<Short> SHORT = mode("s", c -> (short) c, c -> (short) c)
.binary("+", s(Integer::sum))
.binary("-", s((a, b) -> a - b))
.binary("*", s((a, b) -> a * b))
.binary("/", s((a, b) -> a / b))
.unary("-", a -> s(-a))
.unary("count", a -> s(Integer.bitCount(a & 0xffff)))
.binary("min", s(Math::min))
.binary("max", s(Math::max))
;
// == Floats
private static BinaryOperator<Float> f(final IntPredicate p) {
return (a, b) -> p.test(a.compareTo(b)) ? 1.0f : 0.0f;
}
private static final GenericTester.Mode.Builder<Float> FLOAT = mode("f", c -> (float) c)
.binary("+", Float::sum)
.binary("-", (a, b) -> a - b)
.binary("*", (a, b) -> a * b)
.binary("/", (a, b) -> a / b)
.unary("-", a -> -a)
.unary("count", a -> (float) Integer.bitCount(Float.floatToIntBits(a)))
.binary("min", Math::min)
.binary("max", Math::max)
;
// === Truncated integers
/* package-private */ static final int TRUNCATE = 10;
private static int it(final int v) {
return v / TRUNCATE * TRUNCATE;
}
private static final GenericTester.Mode.Builder<Integer> INTEGER_TRUNCATE = mode("it", GenericTest::it)
.binary("+", (a, b) -> it(a + b))
.binary("-", (a, b) -> it(a - b))
.binary("*", (a, b) -> it(a * b))
.binary("/", (a, b) -> it(a / b))
.unary("-", a -> it(-a))
.unary("count", a -> it(Integer.bitCount(a)))
.binary("min", Math::min)
.binary("max", Math::max)
;
// === Common
private GenericTest() {
}
/* package-private */ static Consumer<GenericTester> unary(final String name) {
return tester -> tester.unary(name, 1);
}
/* package-private */ static Consumer<GenericTester> binary(final String name, final int priority) {
return tester -> tester.binary(name, priority);
}
public static final Selector SELECTOR = Selector.composite(GenericTest.class, GenericTester::new, "easy", "hard")
.variant("Base", INTEGER_CHECKED, DOUBLE, BIG_INTEGER, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
.variant("3637", INTEGER_UNCHECKED, SHORT, FLOAT, COUNT, MIN, MAX)
.variant("3839", INTEGER_UNCHECKED, SHORT, INTEGER_TRUNCATE, COUNT, MIN, MAX)
.variant("3435", INTEGER_UNCHECKED, COUNT, MIN, MAX)
.variant("3233", INTEGER_UNCHECKED, SHORT, FLOAT)
.selector();
private static <T> GenericTester.Mode.Builder<T> mode(final String mode, final IntFunction<T> constant) {
return GenericTester.Mode.builder(mode, constant, IntUnaryOperator.identity());
}
private static <T> GenericTester.Mode.Builder<T> mode(final String mode, final IntFunction<T> constant, final IntUnaryOperator fixer) {
return GenericTester.Mode.builder(mode, constant, fixer);
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,366 @@
package expression.generic;
import base.*;
import expression.ToMiniString;
import expression.common.*;
import expression.parser.ParserTestSet;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class GenericTester extends Tester {
private static final int SIZE = 10;
private static final int MAX = Integer.MAX_VALUE - 1;
private static final int MIN = Integer.MIN_VALUE;
private static final List<Pair<String, F<?>>> VARIABLES = List.of(
Pair.of("x", (x, y, z) -> x),
Pair.of("y", (x, y, z) -> y),
Pair.of("z", (x, y, z) -> z)
);
private static final ExpressionKind.Variables<F<?>> VARS = (c, r) ->
VARIABLES;
protected final List<Named<IF<?>>> tests = new ArrayList<>();
private final Tabulator tabulator = new GenericTabulator();
private final Set<String> operations = new HashSet<>();
private final List<NodeRenderer.Paren> parens = new ArrayList<>(
List.of(NodeRenderer.paren("(", ")"))
);
private final TestGeneratorBuilder<Integer> generator;
private final List<Mode.Builder<?>> modes = new ArrayList<>();
public GenericTester(final TestCounter counter) {
super(counter);
generator = new TestGeneratorBuilder<>(
random(),
random()::nextInt,
ParserTestSet.CONSTS,
false
);
}
protected void test(
final String expression,
final String name,
final IF<?> f
) {
tests.add(Named.of(name + ": " + expression, f));
}
@Override
public void test() {
final List<Mode> modes = this.modes.stream()
.map(mode -> mode.build(this))
.toList();
for (final Named<IF<?>> test : tests) {
final String[] parts = test.name().split(": ");
testFull(parts[0], parts[1], test.value());
}
final TestGenerator<Integer, F<?>> generator = this.generator.build(
VARS
);
counter.scope("basic", () -> generator.testBasic(test(true, modes)));
counter.scope("random", () ->
generator.testRandom(
counter,
20 + (TestCounter.DENOMINATOR - 1) * 2,
test(false, modes)
)
);
}
private void testFull(
final String mode,
final String expression,
final IF<?> f
) {
testShort(mode, expression, f);
test(mode, expression, f, MAX, -1, MAX, 0);
test(mode, expression, f, MIN, 0, MIN, 1);
}
private void testShort(
final String mode,
final String expression,
final IF<?> f
) {
test(mode, expression, f, 0, -1, 0, 1);
}
private void test(
final String mode,
final String expression,
final IF<?> f,
final int min,
final int dMin,
final int max,
final int dMax
) {
test(
mode,
expression,
f,
min + random().nextInt(SIZE) * dMin,
max + random().nextInt(SIZE) * dMax,
min + random().nextInt(SIZE) * dMin,
max + random().nextInt(SIZE) * dMax,
min + random().nextInt(SIZE) * dMin,
max + random().nextInt(SIZE) * dMax
);
}
private Consumer<TestGenerator.Test<Integer, F<?>>> test(
final boolean full,
final List<Mode> modes
) {
final NodeRenderer.Settings settings = NodeRenderer.FULL.withParens(
parens
);
return test ->
modes.forEach(mode ->
mode.test(test.expr, test.render(settings), full)
);
}
private <T> void test(
final String mode,
final String expression,
final IF<T> f,
final int x1,
final int x2,
final int y1,
final int y2,
final int z1,
final int z2
) {
final String context = String.format(
"mode=%s, x=[%d, %d] y=[%d, %d] z=[%d, %d], expression=%s%n",
mode,
x1,
x2,
y1,
y2,
z1,
z2,
expression
);
final Object[][][] result = counter.testV(() ->
TestCounter.get(() ->
tabulator.tabulate(mode, expression, x1, x2, y1, y2, z1, z2)
).either(
e -> counter.fail(e, "%s %s", "tabulate", context),
Function.identity()
)
);
IntStream.rangeClosed(x1, x2).forEach(x ->
IntStream.rangeClosed(y1, y2).forEach(y ->
IntStream.rangeClosed(z1, z2).forEach(z ->
counter.test(() -> {
final Object expected = TestCounter.get(() ->
f.apply(x, y, z)
).either(e -> null, Function.identity());
final Object actual = result[x - x1][y - y1][z - z1];
counter.checkTrue(
Objects.equals(actual, expected),
"table[%d][%d][%d](x=%d, y=%d, z=%d]) = %s (expected %s)%n%s",
x - x1,
y - y1,
z - z1,
x,
y,
z,
actual,
expected,
context
);
})
)
)
);
}
public void binary(final String name, final int priority) {
operations.add(name + ":2");
generator.binary(name, priority);
}
public void unary(final String name, final int priority) {
operations.add(name + ":1");
generator.unary(name, priority);
}
public void parens(final String... parens) {
assert parens.length % 2 == 0 : "Parens should come in pairs";
for (int i = 0; i < parens.length; i += 2) {
this.parens.add(NodeRenderer.paren(parens[i], parens[i + 1]));
}
}
/* package-private */ interface Mode {
static <T> Builder<T> builder(
final String mode,
final IntFunction<T> constant,
final IntUnaryOperator fixer
) {
return new Builder<>(mode, constant, fixer);
}
void test(
final Expr<Integer, ? extends F<?>> expr,
final String expression,
final boolean full
);
/* package-private */ final class Builder<
T
> implements Consumer<GenericTester> {
private final String mode;
private final IntFunction<T> constant;
private final IntUnaryOperator fixer;
private final List<Named<UnaryOperator<GenericTester.F<T>>>> unary =
new ArrayList<>();
private final List<
Named<BinaryOperator<GenericTester.F<T>>>
> binary = new ArrayList<>();
private Builder(
final String mode,
final IntFunction<T> constant,
final IntUnaryOperator fixer
) {
this.mode = mode;
this.constant = constant;
this.fixer = fixer;
}
public Builder<T> unary(
final String name,
final UnaryOperator<T> op
) {
unary.add(
Named.of(
name,
arg -> (x, y, z) -> op.apply(arg.apply(x, y, z))
)
);
return this;
}
public Builder<T> binary(
final String name,
final BinaryOperator<T> op
) {
binary.add(
Named.of(
name,
(a, b) ->
(x, y, z) ->
op.apply(a.apply(x, y, z), b.apply(x, y, z))
)
);
return this;
}
@Override
public void accept(final GenericTester tester) {
tester.modes.add(this);
}
private Mode build(final GenericTester tester) {
final Set<String> ops = Stream.concat(
unary.stream().map(op -> op.name() + ":1"),
binary.stream().map(op -> op.name() + ":2")
).collect(Collectors.toUnmodifiableSet());
final List<String> diff = tester.operations
.stream()
.filter(Predicate.not(ops::contains))
.toList();
Asserts.assertTrue(
String.format("Missing operations for %s: %s", mode, diff),
diff.isEmpty()
);
final Renderer.Builder<
Integer,
Unit,
GenericTester.F<T>
> builder = Renderer.builder(
value -> (x, y, z) -> constant.apply(value)
);
unary.forEach(op ->
builder.unary(op.name(), (unit, arg) ->
op.value().apply(arg)
)
);
binary.forEach(op ->
builder.binary(op.name(), (unit, a, b) ->
op.value().apply(a, b)
)
);
final Renderer<Integer, Unit, F<T>> renderer = builder.build();
final TestGenerator<Integer, F<?>> genRenderer =
tester.generator.build(VARS);
return (expr, expression, full) -> {
final String fixed =
fixer == IntUnaryOperator.identity()
? expression
: genRenderer.render(cata(expr), NodeRenderer.FULL);
@SuppressWarnings("unchecked")
final Expr<Integer, F<T>> converted = (Expr<
Integer,
F<T>
>) expr;
final F<T> expected = renderer.render(
converted,
Unit.INSTANCE
);
final IF<T> f = (x, y, z) ->
expected.apply(
constant.apply(x),
constant.apply(y),
constant.apply(z)
);
if (full) {
tester.testFull(mode, fixed, f);
} else {
tester.testShort(mode, fixed, f);
}
};
}
private Expr<Integer, ? extends F<?>> cata(
final Expr<Integer, ? extends F<?>> expr
) {
return expr.node(node ->
node.cata(
c -> Node.constant(fixer.applyAsInt(c)),
Node::op,
Node::op,
Node::op
)
);
}
}
}
@FunctionalInterface
protected interface IF<T> {
T apply(int x, int y, int z);
}
@FunctionalInterface
protected interface F<T> extends ToMiniString {
T apply(T x, T y, T z);
}
}

View File

@@ -0,0 +1,71 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class IntCheckedType implements ArithmeticType<Integer> {
public static final IntCheckedType INSTANCE = new IntCheckedType();
private IntCheckedType() {}
@Override
public Integer fromInt(int value) {
return value;
}
@Override
public Integer add(Integer a, Integer b) {
long result = (long) a + b;
checkOverflow(result);
return (int) result;
}
@Override
public Integer subtract(Integer a, Integer b) {
long result = (long) a - b;
checkOverflow(result);
return (int) result;
}
@Override
public Integer multiply(Integer a, Integer b) {
long result = (long) a * b;
checkOverflow(result);
return (int) result;
}
@Override
public Integer divide(Integer a, Integer b) {
if (b == 0) throw new ArithmeticException("Division by zero");
if (a == Integer.MIN_VALUE && b == -1) throw new ArithmeticException(
"Overflow"
);
return a / b;
}
@Override
public Integer negate(Integer a) {
if (a == Integer.MIN_VALUE) throw new ArithmeticException("Overflow");
return -a;
}
@Override
public Integer count(Integer a) {
return Integer.bitCount(a);
}
@Override
public Integer min(Integer a, Integer b) {
return Math.min(a, b);
}
@Override
public Integer max(Integer a, Integer b) {
return Math.max(a, b);
}
private static void checkOverflow(long result) {
if (result != (int) result) throw new ArithmeticException("Overflow");
}
}

View File

@@ -0,0 +1,61 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class IntTruncType implements ArithmeticType<Integer> {
public static final IntTruncType INSTANCE = new IntTruncType();
private IntTruncType() {}
private static int t(int v) {
return v - (v % 10);
}
@Override
public Integer fromInt(int value) {
return t(value);
}
@Override
public Integer add(Integer a, Integer b) {
return t(a + b);
}
@Override
public Integer subtract(Integer a, Integer b) {
return t(a - b);
}
@Override
public Integer multiply(Integer a, Integer b) {
return t(a * b);
}
@Override
public Integer divide(Integer a, Integer b) {
if (b == 0) throw new ArithmeticException("Division by zero");
return t(a / b);
}
@Override
public Integer negate(Integer a) {
return t(-a);
}
@Override
public Integer count(Integer a) {
return t(Integer.bitCount(a));
}
@Override
public Integer min(Integer a, Integer b) {
return t(Math.min(a, b));
}
@Override
public Integer max(Integer a, Integer b) {
return t(Math.max(a, b));
}
}

View File

@@ -0,0 +1,57 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class IntUncheckedType implements ArithmeticType<Integer> {
public static final IntUncheckedType INSTANCE = new IntUncheckedType();
private IntUncheckedType() {}
@Override
public Integer fromInt(int value) {
return value;
}
@Override
public Integer add(Integer a, Integer b) {
return a + b;
}
@Override
public Integer subtract(Integer a, Integer b) {
return a - b;
}
@Override
public Integer multiply(Integer a, Integer b) {
return a * b;
}
@Override
public Integer divide(Integer a, Integer b) {
if (b == 0) throw new ArithmeticException("Division by zero");
return a / b;
}
@Override
public Integer negate(Integer a) {
return -a;
}
@Override
public Integer count(Integer a) {
return Integer.bitCount(a);
}
@Override
public Integer min(Integer a, Integer b) {
return Math.min(a, b);
}
@Override
public Integer max(Integer a, Integer b) {
return Math.max(a, b);
}
}

View File

@@ -0,0 +1,57 @@
package expression.generic;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class ShortType implements ArithmeticType<Short> {
public static final ShortType INSTANCE = new ShortType();
private ShortType() {}
@Override
public Short fromInt(int value) {
return (short) value;
}
@Override
public Short add(Short a, Short b) {
return (short) (a + b);
}
@Override
public Short subtract(Short a, Short b) {
return (short) (a - b);
}
@Override
public Short multiply(Short a, Short b) {
return (short) (a * b);
}
@Override
public Short divide(Short a, Short b) {
if (b == 0) throw new ArithmeticException("Division by zero");
return (short) (a / b);
}
@Override
public Short negate(Short a) {
return (short) (-a);
}
@Override
public Short count(Short a) {
return (short) Integer.bitCount(a & 0xffff);
}
@Override
public Short min(Short a, Short b) {
return (short) Math.min(a, b);
}
@Override
public Short max(Short a, Short b) {
return (short) Math.max(a, b);
}
}

View File

@@ -0,0 +1,18 @@
package expression.generic;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
public interface Tabulator {
Object[][][] tabulate(
String mode,
String expression,
int x1,
int x2,
int y1,
int y2,
int z1,
int z2
) throws Exception;
}

View File

@@ -0,0 +1,8 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#expressions-generic">Generic Expressions</a> homework
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
*
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.generic;

View File

@@ -0,0 +1,7 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#expressions">Expressions</a> homework
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression;

View File

@@ -0,0 +1,68 @@
package expression.parser;
import expression.ToMiniString;
import expression.common.ExpressionKind;
import expression.common.Reason;
import java.util.function.*;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Operations {
// === Base
public static final Operation NEGATE = unary("-", 1, a -> -a);
@SuppressWarnings("Convert2MethodRef")
public static final Operation ADD = binary("+", 1600, (a, b) -> a + b);
public static final Operation SUBTRACT = binary("-", 1602, (a, b) -> a - b);
public static final Operation MULTIPLY = binary("*", 2001, (a, b) -> a * b);
public static final Operation DIVIDE = binary("/", 2002, (a, b) -> b == 0 ? Reason.DBZ.error() : a / b);
// === 3637
public static final Operation ABS = unary("", "", Math::abs);
public static final Operation CUBE = unary("³", 2, (a, c) -> c.applyAsInt(a * a) * a);
public static final Operation CBRT = unary("", 1, a -> (long) Math.cbrt(a));
// === 3839
private static final Reason NEGATIVE_SQRT = new Reason("Square root of negative value");
public static final Operation SQUARE = unary("²", 2, a -> a * a);
public static final Operation SQRT = unary("", 1, NEGATIVE_SQRT.less(0, a -> (long) Math.sqrt(a)));
// === Common
private Operations() {
}
public static Operation unary(final String name, final int priority, final LongUnaryOperator op) {
return unary(name, priority, (a, c) -> op.applyAsLong(a));
}
public static Operation unary(final String left, final String right, final LongUnaryOperator op) {
return unary(left, right, (a, c) -> op.applyAsLong(a));
}
public static Operation unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
return tests -> tests.unary(name, priority, op);
}
public static Operation unary(final String left, final String right, final BiFunction<Long, LongToIntFunction, Long> op) {
return tests -> tests.unary(left, right, op);
}
public static Operation binary(final String name, final int priority, final LongBinaryOperator op) {
return tests -> tests.binary(name, priority, op);
}
public static <E extends ToMiniString, C> Operation kind(
final ExpressionKind<E, C> kind,
final ParserTestSet.Parser<E> parser
) {
return factory -> factory.kind(kind, parser);
}
@FunctionalInterface
public interface Operation extends Consumer<ParserTester> {}
}

View File

@@ -0,0 +1,236 @@
package expression.parser;
import base.*;
import expression.ToMiniString;
import expression.common.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ParserTestSet<E extends ToMiniString, C> {
private static final int D = 5;
private static final List<Integer> TEST_VALUES = new ArrayList<>();
static {
Functional.addRange(TEST_VALUES, D, D);
Functional.addRange(TEST_VALUES, D, -D);
}
public static final List<Integer> CONSTS
= List.of(0, 1, -1, 4, -4, 10, -10, 30, -30, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE);
protected final ParserTester tester;
protected final ParsedKind<E, C> kind;
private final boolean safe;
protected final TestCounter counter;
public ParserTestSet(final ParserTester tester, final ParsedKind<E, C> kind) {
this(tester, kind, true);
}
protected ParserTestSet(final ParserTester tester, final ParsedKind<E, C> kind, final boolean safe) {
this.tester = tester;
this.kind = kind;
this.safe = safe;
counter = tester.getCounter();
}
private void examples(final TestGenerator<Integer, E> generator) {
example(generator, "$x+2", (x, y, z) -> x + 2);
example(generator, "2-$y", (x, y, z) -> 2 - y);
example(generator, " 3* $z ", (x, y, z) -> 3 * z);
example(generator, "$x/ - 2", (x, y, z) -> -x / 2);
example(generator, "$x*$y+($z-1 )/10", (x, y, z) -> x * y + (int) (z - 1) / 10);
example(generator, "-(-(-\t\t-5 + 16 *$x*$y) + 1 * $z) -(((-11)))", (x, y, z) -> -(-(5 + 16 * x * y) + z) + 11);
example(generator, "" + Integer.MAX_VALUE, (x, y, z) -> (long) Integer.MAX_VALUE);
example(generator, "" + Integer.MIN_VALUE, (x, y, z) -> (long) Integer.MIN_VALUE);
example(generator, "$x--$y--$z", (x, y, z) -> x + y + z);
example(generator, "((2+2))-0/(--2)*555", (x, y, z) -> 4L);
example(generator, "$x-$x+$y-$y+$z-($z)", (x, y, z) -> 0L);
example(generator, "(".repeat(300) + "$x + $y + (-10*-$z)" + ")".repeat(300), (x, y, z) -> x + y + 10 * z);
example(generator, "$x / $y / $z", (x, y, z) -> y == 0 || z == 0 ? Reason.DBZ.error() : (int) x / (int) y / z);
}
private void example(final TestGenerator<Integer, E> generator, final String expr, final ExampleExpression expression) {
final List<String> names = Functional.map(generator.variables(3), Pair::first);
final TExpression expected = vars -> expression.evaluate(vars.get(0), vars.get(1), vars.get(2));
counter.test(() -> {
final String mangled = mangle(expr, names);
final E parsed = parse(mangled, names, true);
Functional.allValues(TEST_VALUES, 3).forEach(values -> check(expected, parsed, names, values, mangled));
});
}
protected static String mangle(final String expr, final List<String> names) {
return expr
.replace("$x", names.get(0))
.replace("$y", names.get(1))
.replace("$z", names.get(2));
}
protected void test() {
final TestGenerator<Integer, E> generator = tester.generator.build(kind.kind.variables());
final Renderer<Integer, Unit, TExpression> renderer = tester.renderer.build();
final Consumer<TestGenerator.Test<Integer, E>> consumer = test -> test(renderer, test);
counter.scope("Basic tests", () -> generator.testBasic(consumer));
counter.scope("Handmade tests", () -> examples(generator));
counter.scope("Random tests", () -> generator.testRandom(counter, 1, consumer));
}
private void test(final Renderer<Integer, Unit, TExpression> renderer, final TestGenerator.Test<Integer, E> test) {
final Expr<Integer, E> expr = test.expr;
final List<Pair<String, E>> vars = expr.variables();
final List<String> variables = Functional.map(vars, Pair::first);
final String full = test.render(NodeRenderer.FULL);
final String mini = test.render(NodeRenderer.MINI);
final E fullParsed = parse(test, variables, NodeRenderer.FULL);
final E miniParsed = parse(test, variables, NodeRenderer.MINI);
final E safeParsed = parse(test, variables, NodeRenderer.SAME);
checkToString(full, mini, "base", fullParsed);
if (tester.mode() > 0) {
counter.test(() -> Asserts.assertEquals("mini.toMiniString", mini, miniParsed.toMiniString()));
counter.test(() -> Asserts.assertEquals("safe.toMiniString", mini, safeParsed.toMiniString()));
}
checkToString(full, mini, "extraParentheses", parse(test, variables, NodeRenderer.FULL_EXTRA));
checkToString(full, mini, "noSpaces", parse(removeSpaces(full), variables, false));
checkToString(full, mini, "extraSpaces", parse(extraSpaces(full), variables, false));
final TExpression expected = renderer.render(
Expr.of(
expr.node(),
Functional.map(vars, (i, var) -> Pair.of(var.first(), args -> args.get(i)))
),
Unit.INSTANCE
);
check(expected, fullParsed, variables, tester.random().random(variables.size(), ExtendedRandom::nextInt), full);
if (this.safe) {
final String safe = test.render(NodeRenderer.SAME);
check(expected, safeParsed, variables, tester.random().random(variables.size(), ExtendedRandom::nextInt), safe);
}
}
private E parse(
final TestGenerator.Test<Integer, E> test,
final List<String> variables,
final NodeRenderer.Settings settings
) {
return parse(test.render(settings.withParens(tester.parens)), variables, false);
}
private static final String LOOKBEHIND = "(?<![a-zA-Z0-9<>*/+=!-])";
private static final String LOOKAHEAD = "(?![a-zA-Z0-9<>*/])";
private static final Pattern SPACES = Pattern.compile(LOOKBEHIND + " | " + LOOKAHEAD + "|" + LOOKAHEAD + LOOKBEHIND);
private String extraSpaces(final String expression) {
return SPACES.matcher(expression).replaceAll(r -> tester.random().randomString(
ExtendedRandom.SPACES,
tester.random().nextInt(5)
));
}
private static String removeSpaces(final String expression) {
return SPACES.matcher(expression).replaceAll("");
}
private void checkToString(final String full, final String mini, final String context, final ToMiniString parsed) {
counter.test(() -> {
assertEquals(context + ".toString", full, full, parsed.toString());
if (tester.mode() > 0) {
assertEquals(context + ".toMiniString", full, mini, parsed.toMiniString());
}
});
}
private static void assertEquals(
final String context,
final String original,
final String expected,
final String actual
) {
final String message = String.format("%s:%n original `%s`,%n expected `%s`,%n actual `%s`",
context, original, expected, actual);
Asserts.assertTrue(message, Objects.equals(expected, actual));
}
private Either<Reason, Integer> eval(final TExpression expression, final List<Integer> vars) {
return Reason.eval(() -> tester.cast(expression.evaluate(vars)));
}
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
return counter.testV(() -> {
final E parsed = counter.testV(() -> counter.call("parse",
() -> kind.parse(expression, variables)));
if (reparse) {
counter.testV(() -> counter.call("parse", () -> kind.parse(parsed.toString(), variables)));
}
return parsed;
});
}
private void check(
final TExpression expectedExpression,
final E expression,
final List<String> variables,
final List<Integer> values,
final String unparsed
) {
counter.test(() -> {
final Either<Reason, Integer> answer = eval(expectedExpression, values);
final String args = IntStream.range(0, variables.size())
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
.collect(Collectors.joining(", "));
final String message = String.format("f(%s)%n\twhere f=%s%n\tyour f=%s", args, unparsed, expression);
try {
final C actual = kind.kind.evaluate(expression, variables, kind.kind.fromInts(values));
counter.checkTrue(answer.isRight(), "Error expected for f(%s)%n\twhere f=%s%n\tyour f=%s", args, unparsed, expression);
Asserts.assertEquals(message, answer.getRight(), actual);
} catch (final Exception e) {
if (answer.isRight()) {
counter.fail(e, "No error expected for %s", message);
}
}
});
}
@FunctionalInterface
public interface TExpression {
long evaluate(List<Integer> vars);
}
@FunctionalInterface
protected interface ExampleExpression {
long evaluate(long x, long y, long z);
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record ParsedKind<E extends ToMiniString, C>(ExpressionKind<E, C> kind, Parser<E> parser) {
public E parse(final String expression, final List<String> variables) throws Exception {
return parser.parse(expression, variables);
}
@Override
public String toString() {
return kind.toString();
}
}
@FunctionalInterface
public interface Parser<E> {
E parse(final String expression, final List<String> variables) throws Exception;
}
}

View File

@@ -0,0 +1,76 @@
package expression.parser;
import base.ExtendedRandom;
import base.TestCounter;
import base.Tester;
import base.Unit;
import expression.ToMiniString;
import expression.common.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.LongBinaryOperator;
import java.util.function.LongToIntFunction;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ParserTester extends Tester {
/* package-private */ final TestGeneratorBuilder<Integer> generator;
/* package-private */ final Renderer.Builder<Integer, Unit, ParserTestSet.TExpression> renderer;
private final List<ParserTestSet.ParsedKind<?, ?>> kinds = new ArrayList<>();
/* package-private */ final List<NodeRenderer.Paren> parens = new ArrayList<>(List.of(NodeRenderer.paren("(", ")")));
public ParserTester(final TestCounter counter) {
super(counter);
renderer = Renderer.builder(c -> vars -> c);
final ExtendedRandom random = counter.random();
generator = new TestGeneratorBuilder<>(random, random::nextInt, ParserTestSet.CONSTS, true);
}
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
generator.unary(name, priority);
renderer.unary(name, (unit, a) -> vars -> cast(op.apply(a.evaluate(vars), this::cast)));
}
public void unary(final String left, final String right, final BiFunction<Long, LongToIntFunction, Long> op) {
generator.unary(left, right);
renderer.unary(left, (unit, a) -> vars -> cast(op.apply(a.evaluate(vars), this::cast)));
}
public void binary(final String name, final int priority, final LongBinaryOperator op) {
generator.binary(name, priority);
renderer.binary(name, (unit, a, b) -> vars -> cast(op.applyAsLong(a.evaluate(vars), b.evaluate(vars))));
}
<E extends ToMiniString, C> void kind(final ExpressionKind<E, C> kind, final ParserTestSet.Parser<E> parser) {
kinds.add(new ParserTestSet.ParsedKind<>(kind, parser));
}
@Override
public void test() {
for (final ParserTestSet.ParsedKind<?, ?> kind : kinds) {
counter.scope(kind.toString(), () -> test(kind));
}
}
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
new ParserTestSet<>(this, kind).test();
}
public TestCounter getCounter() {
return counter;
}
protected int cast(final long value) {
return (int) value;
}
public void parens(final String... parens) {
assert parens.length % 2 == 0 : "Parens should come in pairs";
for (int i = 0; i < parens.length; i += 2) {
this.parens.add(NodeRenderer.paren(parens[i], parens[i + 1]));
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#expressions-parsing">Expressions Parsing</a> homework
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.parser;

View File

@@ -0,0 +1,176 @@
package queue;
import java.util.function.Predicate;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public abstract class AbstractQueue implements Queue {
protected int size;
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void clear() {
clearImpl();
size = 0;
}
@Override
public int count(Object element) {
assert element != null : "Element must not be null";
int cnt = 0;
for (int i = 0; i < size; i++) {
if (element.equals(get(i))) cnt++;
}
return cnt;
}
@Override
public int countIf(Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
int cnt = 0;
for (int i = 0; i < size; i++) {
if (predicate.test(get(i))) cnt++;
}
return cnt;
}
@Override
public int indexIf(Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
for (int i = 0; i < size; i++) {
if (predicate.test(get(i))) return i;
}
return -1;
}
@Override
public int lastIndexIf(Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
for (int i = size - 1; i >= 0; i--) {
if (predicate.test(get(i))) return i;
}
return -1;
}
@Override
public int indexOf(Object element) {
assert element != null : "Predicate must not be null";
for (int i = 0; i < size; i++) {
if (get(i).equals(element)) return i;
}
return -1;
}
@Override
public int lastIndexOf(Object element) {
assert element != null : "Predicate must not be null";
for (int i = size - 1; i >= 0; i--) {
if (get(i).equals(element)) return i;
}
return -1;
}
@Override
public boolean contains(Object element) {
assert element != null : "Element must not be null";
return indexOf(element) != -1;
}
@Override
public boolean removeFirst(Object element) {
assert element != null : "Element must not be null";
int originalSize = size;
boolean found = false;
for (int i = 0; i < originalSize; i++) {
Object elem = dequeue();
if (!found && element.equals(elem)) {
found = true;
} else {
enqueue(elem);
}
}
return found;
}
@Override
public Queue getNth(int n) {
Queue result = createEmpty();
for (int i = 0; i < size; i++) {
Object elem = get(i);
if ((i + 1) % n == 0) result.enqueue(elem);
}
return result;
}
@Override
public Queue removeNth(int n) {
Queue result = createEmpty();
int originalSize = size;
for (int i = 0; i < originalSize; i++) {
if ((i + 1) % n == 0) result.enqueue(get(i));
}
Queue temp = createEmpty();
for (int i = 0; i < originalSize; i++) {
Object elem = dequeue();
if ((i + 1) % n != 0) temp.enqueue(elem);
}
while (!temp.isEmpty()) enqueue(temp.dequeue());
return result;
}
@Override
public void dropNth(int n) {
int originalSize = size;
Queue temp = createEmpty();
for (int i = 0; i < originalSize; i++) {
Object elem = dequeue();
if ((i + 1) % n != 0) temp.enqueue(elem);
}
while (!temp.isEmpty()) enqueue(temp.dequeue());
}
@Override
public void removeIf(java.util.function.Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
int originalSize = size;
for (int i = 0; i < originalSize; i++) {
Object elem = dequeue();
if (!predicate.test(elem)) enqueue(elem);
}
}
@Override
public void retainIf(java.util.function.Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
removeIf(predicate.negate());
}
@Override
public void removeAll(Object element) {
assert element != null : "Element must not be null";
removeIf(element::equals);
}
@Override
public void retainAll(Object element) {
assert element != null : "Element must not be null";
removeIf(e -> !element.equals(e));
}
protected abstract AbstractQueue createEmpty();
protected abstract Object get(int i);
protected abstract void clearImpl();
}

View File

@@ -0,0 +1,90 @@
package queue;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class ArrayQueue extends AbstractQueue {
private Object[] queue;
private int head;
public ArrayQueue() {
queue = new Object[4];
head = 0;
}
@Override
public void enqueue(Object element) {
assert element != null : "Element must not be null";
ensureCapacity();
queue[(head + size) % queue.length] = element;
size++;
}
@Override
public void push(Object element) {
assert element != null : "Element must not be null";
ensureCapacity();
head = (head - 1 + queue.length) % queue.length;
queue[head] = element;
size++;
}
@Override
public Object element() {
assert size > 0 : "Queue is empty";
return queue[head];
}
@Override
public Object peek() {
assert size > 0 : "Queue is empty";
return queue[(head + size - 1) % queue.length];
}
@Override
public Object dequeue() {
assert size > 0 : "Queue is empty";
Object result = queue[head];
queue[head] = null;
head = (head + 1) % queue.length;
size--;
return result;
}
@Override
public Object remove() {
assert size > 0 : "Queue is empty";
int tail = (head + size - 1) % queue.length;
Object result = queue[tail];
queue[tail] = null;
size--;
return result;
}
@Override
protected Object get(int i) {
return queue[(head + i) % queue.length];
}
@Override
protected void clearImpl() {
queue = new Object[4];
head = 0;
}
@Override
protected AbstractQueue createEmpty() {
return new ArrayQueue();
}
private void ensureCapacity() {
if (size < queue.length) return;
Object[] newQueue = new Object[queue.length * 2];
for (int i = 0; i < size; i++) {
newQueue[i] = queue[(head + i) % queue.length];
}
queue = newQueue;
head = 0;
}
}

View File

@@ -0,0 +1,150 @@
package queue;
import java.util.function.Predicate;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class ArrayQueueADT {
private Object[] queue;
private int head;
private int size;
public ArrayQueueADT() {
queue = new Object[4];
head = 0;
size = 0;
}
public static ArrayQueueADT create() {
return new ArrayQueueADT();
}
public static void enqueue(ArrayQueueADT q, Object element) {
assert q != null && element != null : "Queue and element must not be null";
ensureCapacity(q);
q.queue[(q.head + q.size) % q.queue.length] = element;
q.size++;
}
public static void push(ArrayQueueADT q, Object element) {
assert q != null && element != null : "Queue and element must not be null";
ensureCapacity(q);
q.head = (q.head - 1 + q.queue.length) % q.queue.length;
q.queue[q.head] = element;
q.size++;
}
public static Object element(ArrayQueueADT q) {
assert q != null && q.size > 0 : "Queue must not be null and not be empty";
return q.queue[q.head];
}
public static Object peek(ArrayQueueADT q) {
assert q != null && q.size > 0 : "Queue must not be null and not be empty";
return q.queue[(q.head + q.size - 1) % q.queue.length];
}
public static Object dequeue(ArrayQueueADT q) {
assert q != null && q.size > 0 : "Queue must not be null and not be empty";
Object result = q.queue[q.head];
q.queue[q.head] = null;
q.head = (q.head + 1) % q.queue.length;
q.size--;
return result;
}
public static Object remove(ArrayQueueADT q) {
assert q != null && q.size > 0 : "Queue must not be null and not be empty";
int tail = (q.head + q.size - 1) % q.queue.length;
Object result = q.queue[tail];
q.queue[tail] = null;
q.size--;
return result;
}
public static int size(ArrayQueueADT q) {
assert q != null : "Queue must not be null";
return q.size;
}
public static boolean isEmpty(ArrayQueueADT q) {
assert q != null : "Queue must not be null";
return q.size == 0;
}
public static void clear(ArrayQueueADT q) {
assert q != null : "Queue must not be null";
q.queue = new Object[4];
q.head = 0;
q.size = 0;
}
public static int count(ArrayQueueADT q, Object element) {
assert q != null && element != null : "Queue and element must not be null";
int cnt = 0;
for (int i = 0; i < q.size; i++) {
if (element.equals(q.queue[(q.head + i) % q.queue.length])) {
cnt++;
}
}
return cnt;
}
public static int countIf(ArrayQueueADT q, Predicate<Object> predicate) {
assert q != null && predicate != null : "Queue and predicate must not be null";
int cnt = 0;
for (int i = 0; i < q.size; i++) {
if (predicate.test(q.queue[(q.head + i) % q.queue.length])) {
cnt++;
}
}
return cnt;
}
public static int indexIf(ArrayQueueADT q, Predicate<Object> predicate) {
assert q != null && predicate != null : "Queue and predicate must not be null";
for (int i = 0; i < q.size; i++) {
if (predicate.test(q.queue[(q.head + i) % q.queue.length])) {
return i;
}
}
return -1;
}
public static int lastIndexIf(ArrayQueueADT q, Predicate<Object> predicate) {
assert q != null && predicate != null : "Queue and predicate must not be null";
for (int i = q.size - 1; i >= 0; i--) {
if (predicate.test(q.queue[(q.head + i) % q.queue.length])) {
return i;
}
}
return -1;
}
public static int indexOf(ArrayQueueADT q, Object element) {
assert element != null : "Predicate must not be null";
for (int i = 0; i < q.size; i++) {
if (q.queue[(q.head + i) % q.queue.length].equals(element)) return i;
}
return -1;
}
public static int lastIndexOf(ArrayQueueADT q, Object element) {
assert element != null : "Predicate must not be null";
for (int i = q.size - 1; i >= 0; i--) {
if (q.queue[(q.head + i) % q.queue.length].equals(element)) return i;
}
return -1;
}
private static void ensureCapacity(ArrayQueueADT q) {
if (q.size < q.queue.length) return;
Object[] newQueue = new Object[q.queue.length * 2];
for (int i = 0; i < q.size; i++) {
newQueue[i] = q.queue[(q.head + i) % q.queue.length];
}
q.queue = newQueue;
q.head = 0;
}
}

View File

@@ -0,0 +1,137 @@
package queue;
import java.util.function.Predicate;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class ArrayQueueModule {
private static Object[] queue = new Object[4];
private static int head = 0;
private static int size = 0;
public static void enqueue(Object element) {
assert element != null : "Element must not be null";
ensureCapacity();
queue[(head + size) % queue.length] = element;
size++;
}
public static void push(Object element) {
assert element != null : "Element must not be null";
ensureCapacity();
head = (head - 1 + queue.length) % queue.length;
queue[head] = element;
size++;
}
public static Object element() {
assert size > 0 : "Queue is empty";
return queue[head];
}
public static Object peek() {
assert size > 0 : "Queue is empty";
return queue[(head + size - 1) % queue.length];
}
public static Object dequeue() {
assert size > 0 : "Queue is empty";
Object result = queue[head];
queue[head] = null;
head = (head + 1) % queue.length;
size--;
return result;
}
public static Object remove() {
assert size > 0 : "Queue is empty";
int tail = (head + size - 1) % queue.length;
Object result = queue[tail];
queue[tail] = null;
size--;
return result;
}
public static int size() {
return size;
}
public static boolean isEmpty() {
return size == 0;
}
public static void clear() {
queue = new Object[4];
head = 0;
size = 0;
}
public static int count(Object element) {
assert element != null : "Element must not be null";
int cnt = 0;
for (int i = 0; i < size; i++) {
if (element.equals(queue[(head + i) % queue.length])) {
cnt++;
}
}
return cnt;
}
public static int countIf(Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
int cnt = 0;
for (int i = 0; i < size; i++) {
if (predicate.test(queue[(head + i) % queue.length])) {
cnt++;
}
}
return cnt;
}
public static int indexIf(Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
for (int i = 0; i < size; i++) {
if (predicate.test(queue[(head + i) % queue.length])) {
return i;
}
}
return -1;
}
public static int lastIndexIf(Predicate<Object> predicate) {
assert predicate != null : "Predicate must not be null";
for (int i = size - 1; i >= 0; i--) {
if (predicate.test(queue[(head + i) % queue.length])) {
return i;
}
}
return -1;
}
public static int indexOf(Object element) {
assert element != null : "Predicate must not be null";
for (int i = 0; i < size; i++) {
if (queue[(head + i) % queue.length].equals(element)) return i;
}
return -1;
}
public static int lastIndexOf(Object element) {
assert element != null : "Predicate must not be null";
for (int i = size - 1; i >= 0; i--) {
if (queue[(head + i) % queue.length].equals(element)) return i;
}
return -1;
}
private static void ensureCapacity() {
if (size < queue.length) return;
Object[] newQueue = new Object[queue.length * 2];
for (int i = 0; i < size; i++) {
newQueue[i] = queue[(head + i) % queue.length];
}
queue = newQueue;
head = 0;
}
}

View File

@@ -0,0 +1,46 @@
package queue;
import base.Selector;
import base.TestCounter;
import java.util.List;
import java.util.function.Consumer;
import static queue.Queues.*;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ArrayQueueTest {
public static final Selector SELECTOR = new Selector(ArrayQueueTest.class)
.variant("Base", variant(QueueModel.class, d -> () -> d))
.variant("3637", variant(DequeCountModel.class, (DequeChecker<DequeCountModel>) d -> () -> d, DEQUE_COUNT))
.variant("3839", variant(DequeCountIfModel.class, (DequeChecker<DequeCountIfModel>) d -> () -> d, DEQUE_COUNT_IF))
.variant("3435", variant(IndexIfModel.class, d -> () -> d, INDEX_IF))
.variant("3233", variant(IndexModel.class, d -> () -> d, INDEX))
;
private ArrayQueueTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
/* package-private */
static <M extends QueueModel> Consumer<TestCounter> variant(
final Class<M> type,
final QueueChecker<M> tester,
final Splitter<M> splitter
) {
return new ArrayQueueTester<>(type, tester, splitter)::test;
}
/* package-private */
static <M extends QueueModel> Consumer<TestCounter> variant(
final Class<M> type,
final QueueChecker<M> tester
) {
return variant(type, tester, (t, q, r) -> List.of());
}
}

View File

@@ -0,0 +1,224 @@
package queue;
import base.Asserts;
import base.ExtendedRandom;
import base.TestCounter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ArrayQueueTester<M extends Queues.QueueModel> {
private static final int OPERATIONS = 50_000;
/* package-private */ static final Object[] ELEMENTS = new Object[]{
"Hello",
"world",
1, 2, 3,
List.of("a"),
List.of("a"),
List.of("b"),
Map.of()
};
private final Class<M> model;
private final Queues.QueueChecker<M> tester;
protected ArrayQueueTester(final Class<M> model, final Queues.QueueChecker<M> tester, final Queues.Splitter<M> splitter) {
this.model = model;
this.tester = new Queues.QueueChecker<>() {
@Override
public M wrap(final ArrayDeque<Object> reference) {
return tester.wrap(reference);
}
@Override
public void check(final M queue, final ExtendedRandom random) {
tester.check(queue, random);
}
@Override
public void add(final M queue, final Object element, final ExtendedRandom random) {
tester.add(queue, element, random);
}
@Override
public void remove(final M queue, final ExtendedRandom random) {
tester.remove(queue, random);
}
@Override
public List<M> linearTest(final M queue, final ExtendedRandom random) {
if (random.nextInt(50) == 0) {
queue.clear();
return List.of();
} else {
return splitter.split(this, queue, random);
}
}
};
}
protected void test(final TestCounter counter) {
test(counter, "ArrayQueue", ReflectionTest.Mode.values());
}
protected void test(final TestCounter counter, final String className, final ReflectionTest.Mode... modes) {
for (final ReflectionTest.Mode mode : modes) {
final String scope = "Running %s for %s in %s mode%n"
.formatted(model.getEnclosingClass().getSimpleName(), className, mode);
counter.scope(scope, () -> new Variant(counter).test(className, mode));
}
}
protected void checkImplementation(final Class<?> implementation) {
// Do nothing by default
}
private static List<Object> toList(final Queues.QueueModel queue) {
final List<Object> list = Stream.generate(queue::dequeue).limit(queue.size()).toList();
list.forEach(queue::enqueue);
return list;
}
protected static ArrayDeque<Object> collect(final Stream<Object> elements) {
return elements.collect(Collectors.toCollection(ArrayDeque::new));
}
private class Variant extends ReflectionTest {
private final TestCounter counter;
public Variant(final TestCounter counter) {
this.counter = counter;
}
protected void testEmpty(final M queue) {
counter.scope("testEmpty", () -> assertSize(0, queue));
}
protected void testSingleton(final M queue) {
counter.scope("testSingleton", () -> {
assertSize(0, queue);
final String value = "value";
queue.enqueue(value);
assertSize(1, queue);
Asserts.assertEquals("element()", value, queue.element());
Asserts.assertEquals("dequeue()", value, queue.dequeue());
assertSize(0, queue);
});
}
protected void testClear(final M queue) {
counter.scope("testClear", () -> {
assertSize(0, queue);
final String value = "value";
queue.enqueue(value);
queue.enqueue(value);
queue.clear();
assertSize(0, queue);
final String value1 = "value1";
queue.enqueue(value1);
Asserts.assertEquals("deque()", value1, queue.dequeue());
});
}
private int checkAndSize(final M queue) {
final int size = queue.size();
if (!queue.isEmpty() && random().nextBoolean()) {
tester.check(queue, random());
}
return size;
}
protected Object randomElement() {
return ELEMENTS[random().nextInt(ELEMENTS.length)];
}
protected void assertSize(final int size, final M queue) {
counter.test(() -> {
Asserts.assertEquals("size()", size, queue.size());
Asserts.assertTrue("Expected isEmpty() " + (size == 0) + ", found " + queue.isEmpty(), (size == 0) == queue.isEmpty());
});
}
@Override
protected void checkResult(final String call, final Object expected, final Object actual) {
if (expected instanceof Queues.QueueModel model) {
super.checkResult(call, toList(model), toList((Queues.QueueModel) actual));
} else {
super.checkResult(call, expected, actual);
}
}
protected Supplier<M> factory(final String name, final Mode mode) {
final ProxyFactory<M> factory = new ProxyFactory<>(model, mode, "queue." + name);
checkImplementation(factory.implementation);
return () -> checking(counter, model, tester.wrap(new ArrayDeque<>()), factory.create());
}
private void test(final String className, final Mode mode) {
final Supplier<M> factory = factory(className, mode);
testEmpty(factory.get());
testSingleton(factory.get());
testClear(factory.get());
for (int i = 0; i <= 10; i += 2) {
testRandom(factory.get(), (double) i / 10);
}
}
private void testRandom(final M initial, final double addFreq) {
counter.scope("testRandom, add frequency = " + addFreq, () -> {
final List<M> queues = new ArrayList<>();
queues.add(initial);
int ops = 0;
for (int i = 0; i < OPERATIONS / TestCounter.DENOMINATOR / TestCounter.DENOMINATOR; i++) {
final M queue = queues.get(random().nextInt(queues.size()));
final int size = counter.testV(() -> {
if (queue.isEmpty() || random().nextDouble() < addFreq) {
tester.add(queue, randomElement(), random());
} else {
tester.remove(queue, random());
}
return checkAndSize(queue);
});
if (ops++ >= size && random().nextInt(4) == 0) {
ops -= size;
counter.test(() -> {
queues.addAll(tester.linearTest(queue, random()));
checkAndSize(queue);
});
}
}
for (final M queue : queues) {
counter.test(() -> {
tester.linearTest(queue, random());
checkAndSize(queue);
for (int i = queue.size(); i > 0; i--) {
tester.remove(queue, random());
checkAndSize(queue);
}
});
}
});
}
private ExtendedRandom random() {
return counter.random();
}
}
}

114
java/queue/LinkedQueue.java Normal file
View File

@@ -0,0 +1,114 @@
package queue;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class LinkedQueue extends AbstractQueue {
private Node head;
private Node tail;
private static class Node {
Object value;
Node next;
Node prev;
Node(Object value) {
this.value = value;
}
}
public LinkedQueue() {
head = null;
tail = null;
}
@Override
public void enqueue(Object element) {
assert element != null : "Element must not be null";
Node node = new Node(element);
if (tail == null) {
head = tail = node;
} else {
node.prev = tail;
tail.next = node;
tail = node;
}
size++;
}
@Override
public void push(Object element) {
assert element != null : "Element must not be null";
Node node = new Node(element);
if (head == null) {
head = tail = node;
} else {
node.next = head;
head.prev = node;
head = node;
}
size++;
}
@Override
public Object element() {
assert size > 0 : "Queue is empty";
return head.value;
}
@Override
public Object peek() {
assert size > 0 : "Queue is empty";
return tail.value;
}
@Override
public Object dequeue() {
assert size > 0 : "Queue is empty";
Object result = head.value;
head = head.next;
if (head != null) {
head.prev = null;
} else {
tail = null;
}
size--;
return result;
}
@Override
public Object remove() {
assert size > 0 : "Queue is empty";
Object result = tail.value;
tail = tail.prev;
if (tail != null) {
tail.next = null;
} else {
head = null;
}
size--;
return result;
}
@Override
protected Object get(int i) {
Node cur = head;
for (int j = 0; j < i; j++) {
cur = cur.next;
}
return cur.value;
}
@Override
protected void clearImpl() {
head = null;
tail = null;
}
@Override
protected AbstractQueue createEmpty() {
return new LinkedQueue();
}
}

56
java/queue/Queue.java Normal file
View File

@@ -0,0 +1,56 @@
package queue;
import java.util.function.Predicate;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public interface Queue {
void enqueue(Object element);
void push(Object element);
Object element();
Object peek();
Object dequeue();
Object remove();
int size();
boolean isEmpty();
void clear();
int count(Object element);
int countIf(Predicate<Object> predicate);
int indexIf(Predicate<Object> predicate);
int lastIndexIf(Predicate<Object> predicate);
int indexOf(Object element);
int lastIndexOf(Object element);
boolean contains(Object element);
boolean removeFirst(Object element);
Queue getNth(int n);
Queue removeNth(int n);
void dropNth(int n);
void removeIf(java.util.function.Predicate<Object> predicate);
void retainIf(java.util.function.Predicate<Object> predicate);
void removeAll(Object element);
void retainAll(Object element);
}

51
java/queue/QueueTest.java Normal file
View File

@@ -0,0 +1,51 @@
package queue;
import static queue.Queues.*;
import base.Selector;
import base.TestCounter;
import java.util.List;
import java.util.function.Consumer;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class QueueTest {
public static final Selector SELECTOR = new Selector(QueueTest.class)
.variant("Base", variant(QueueModel.class, d -> () -> d))
.variant("3637", variant(ContainsModel.class, d -> () -> d, CONTAINS))
.variant("3839", variant(NthModel.class, d -> () -> d, NTH))
.variant(
"3435",
variant(RemoveIfModel.class, d -> () -> d, REMOVE_IF_TEST)
)
.variant(
"3233",
variant(RemoveEqModel.class, d -> () -> d, REMOVE_EQ_TEST)
);
private QueueTest() {}
public static void main(final String... args) {
SELECTOR.main(args);
}
/* package-private */ static <
M extends QueueModel,
T extends QueueChecker<M>
> Consumer<TestCounter> variant(
final Class<M> type,
final T tester,
final Queues.Splitter<M> splitter
) {
return new QueueTester<>(type, tester, splitter)::test;
}
/* package-private */ static <
M extends QueueModel,
T extends QueueChecker<M>
> Consumer<TestCounter> variant(final Class<M> type, final T tester) {
return variant(type, tester, (t, q, r) -> List.of());
}
}

View File

@@ -0,0 +1,32 @@
package queue;
import base.Asserts;
import base.TestCounter;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class QueueTester<M extends Queues.QueueModel> extends ArrayQueueTester<M> {
/* package-private */ QueueTester(final Class<M> type, final Queues.QueueChecker<M> tester, final Queues.Splitter<M> splitter) {
super(type, tester, splitter);
}
public void test(final TestCounter counter) {
test(counter, "LinkedQueue", ReflectionTest.Mode.CLASS);
test(counter, "ArrayQueue", ReflectionTest.Mode.CLASS);
}
private static boolean implementsQueue(final Class<?> type) {
return type != Object.class
&& (Stream.of(type.getInterfaces()).map(Class::getName).anyMatch("queue.Queue"::equals)
|| implementsQueue(type.getSuperclass()));
}
@Override
protected void checkImplementation(final Class<?> type) {
Asserts.assertTrue(type + " should extend AbstractQueue", "queue.AbstractQueue".equals(type.getSuperclass().getName()));
Asserts.assertTrue(type + " should implement interface Queue", implementsQueue(type));
}
}

490
java/queue/Queues.java Normal file
View File

@@ -0,0 +1,490 @@
package queue;
import base.ExtendedRandom;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("UnusedReturnValue")
public final class Queues {
private Queues() {}
protected interface QueueModel {
@ReflectionTest.Ignore
ArrayDeque<Object> model();
default Object dequeue() {
return model().removeFirst();
}
default int size() {
return model().size();
}
default boolean isEmpty() {
return model().isEmpty();
}
default void clear() {
model().clear();
}
default void enqueue(final Object element) {
model().addLast(element);
}
default Object element() {
return model().getFirst();
}
}
protected interface QueueChecker<M extends QueueModel> {
M wrap(ArrayDeque<Object> reference);
default List<M> linearTest(final M queue, final ExtendedRandom random) {
// Do nothing by default
return List.of();
}
default void check(final M queue, final ExtendedRandom random) {
queue.element();
}
default void add(
final M queue,
final Object element,
final ExtendedRandom random
) {
queue.enqueue(element);
}
default Object randomElement(final ExtendedRandom random) {
return ArrayQueueTester
.ELEMENTS[random.nextInt(ArrayQueueTester.ELEMENTS.length)];
}
default void remove(final M queue, final ExtendedRandom random) {
queue.dequeue();
}
@SuppressWarnings("unchecked")
default M cast(final QueueModel model) {
return (M) model;
}
}
@FunctionalInterface
protected interface Splitter<M extends QueueModel> {
List<M> split(
final QueueChecker<? extends M> tester,
final M queue,
final ExtendedRandom random
);
}
@FunctionalInterface
/* package-private */ interface LinearTester<
M extends QueueModel
> extends Splitter<M> {
void test(
final QueueChecker<? extends M> tester,
final M queue,
final ExtendedRandom random
);
@Override
default List<M> split(
final QueueChecker<? extends M> tester,
final M queue,
final ExtendedRandom random
) {
test(tester, queue, random);
return List.of();
}
}
// === 3637 (ArrayQueue)
/* package-private */ interface CountModel extends ReflectionModel {
default int count(final Object element) {
return reduce(0, element, (v, i) -> v + 1);
}
}
/* package-private */ static final Queues.LinearTester<CountModel> COUNT = (
tester,
queue,
random
) -> queue.count(tester.randomElement(random));
/* package-private */ interface DequeCountModel
extends DequeModel, CountModel {}
/* package-private */ static final Queues.LinearTester<
DequeCountModel
> DEQUE_COUNT = COUNT::test;
// === 3839 (ArrayQueue)
/* package-private */ interface CountIfModel extends ReflectionModel {
default int countIf(final Predicate<Object> p) {
return reduce(0, p, (v, i) -> v + 1);
}
}
/* package-private */ static final Queues.LinearTester<
CountIfModel
> COUNT_IF = (t, q, r) -> q.countIf(randomPredicate(t, r));
/* package-private */ interface DequeCountIfModel
extends DequeModel, CountIfModel {}
/* package-private */ static final Queues.LinearTester<
DequeCountIfModel
> DEQUE_COUNT_IF = COUNT_IF::test;
record NamedPredicate<T>(
String name,
Predicate<? super T> predicate
) implements Predicate<T> {
@Override
public String toString() {
return name;
}
@Override
public boolean test(final T t) {
return predicate.test(t);
}
}
/* package-private */ static Predicate<Object> randomPredicate(
final Queues.QueueChecker<? extends Queues.QueueModel> tester,
final ExtendedRandom random
) {
final Object item = tester.randomElement(random);
return random
.<Supplier<Predicate<Object>>>randomItem(
() -> new NamedPredicate<>("item == ", o -> item == o),
() -> new NamedPredicate<>(item + ".equals", item::equals),
() -> new NamedPredicate<>("null == ", Objects::isNull)
)
.get();
}
@SafeVarargs
private static <M extends Queues.QueueModel> Queues.LinearTester<
M
> randomOf(final Queues.LinearTester<? super M>... variants) {
return (tester, queue, random) ->
random.randomItem(variants).test(tester, queue, random);
}
// === 3435 (ArrayQueue)
/* package-private */
interface IndexIfModel extends ReflectionModel {
default int indexIf(final Predicate<Object> p) {
return reduce(-1, p, (v, i) -> v == -1 ? i : v);
}
default int lastIndexIf(final Predicate<Object> p) {
return reduce(-1, p, (v, i) -> i);
}
}
/* package-private */ static final Queues.LinearTester<
IndexIfModel
> INDEX_IF = randomOf(
(t, q, r) -> q.indexIf(randomPredicate(t, r)),
(t, q, r) -> q.lastIndexIf(randomPredicate(t, r))
);
// === 3233 (ArrayQueue)
/* package-private */
interface IndexModel extends ReflectionModel {
default int indexOf(final Object element) {
return reduce(-1, element, (v, i) -> v == -1 ? i : v);
}
default int lastIndexOf(final Object element) {
return reduce(-1, element, (v, i) -> i);
}
}
/* package-private */ static final Queues.LinearTester<IndexModel> INDEX =
randomOf(
(t, q, r) -> q.indexOf(t.randomElement(r)),
(t, q, r) -> q.lastIndexOf(t.randomElement(r))
);
// === 3637 (Queues)
/* package-private */ interface ContainsModel extends Queues.QueueModel {
default boolean contains(final Object element) {
return model().contains(element);
}
@SuppressWarnings("UnusedReturnValue")
default boolean removeFirst(final Object element) {
return model().removeFirstOccurrence(element);
}
}
/* package-private */ static final Queues.LinearTester<
ContainsModel
> CONTAINS = (tester, queue, random) -> {
final Object element = random.nextBoolean()
? tester.randomElement(random)
: random.nextInt();
if (random.nextBoolean()) {
queue.contains(element);
} else {
queue.removeFirst(element);
}
};
// === 3839 (Queues)
/* package-private */ interface NthModel extends Queues.QueueModel {
// Deliberately ugly implementation
@ReflectionTest.Wrap
default NthModel getNth(final int n) {
final ArrayDeque<Object> deque = new ArrayDeque<>();
final int[] index = { 0 };
model().forEach(e -> {
if (++index[0] % n == 0) {
deque.add(e);
}
});
return () -> deque;
}
// Deliberately ugly implementation
@ReflectionTest.Wrap
default NthModel removeNth(final int n) {
final ArrayDeque<Object> deque = new ArrayDeque<>();
final int[] index = { 0 };
model().removeIf(e -> {
if (++index[0] % n == 0) {
deque.add(e);
return true;
} else {
return false;
}
});
return () -> deque;
}
default void dropNth(final int n) {
final int[] index = { 0 };
model().removeIf(e -> ++index[0] % n == 0);
}
}
/* package-private */ static final Queues.Splitter<NthModel> NTH = (
tester,
queue,
random
) -> {
final int n = random.nextInt(5) + 1;
return switch (random.nextInt(3)) {
case 0 -> {
final NthModel model = queue.removeNth(n);
yield List.of(tester.cast(model));
}
case 1 -> {
queue.dropNth(n);
yield List.of();
}
case 2 -> List.of(tester.cast(queue.getNth(n)));
default -> throw new AssertionError();
};
};
// === 3435 (Queues)
/* package-private */ interface RemoveIfModel extends Queues.QueueModel {
default void removeIf(final Predicate<Object> p) {
model().removeIf(p);
}
default void retainIf(final Predicate<Object> p) {
model().removeIf(Predicate.not(p));
}
}
private static final Queues.LinearTester<RemoveIfModel> REMOVE_IF = (
tester,
queue,
random
) -> queue.removeIf(randomPredicate(tester, random));
private static final Queues.LinearTester<RemoveIfModel> RETAIN_IF = (
tester,
queue,
random
) -> queue.retainIf(randomPredicate(tester, random));
/* package-private */ static final Queues.LinearTester<
RemoveIfModel
> REMOVE_IF_TEST = randomOf(REMOVE_IF, RETAIN_IF);
// 3233 (Queues)
/* package-private */ interface RemoveEqModel extends Queues.QueueModel {
default void removeAll(final Object element) {
model().removeIf(Predicate.isEqual(element));
}
default void retainAll(final Object element) {
model().removeIf(Predicate.not(Predicate.isEqual(element)));
}
}
private static final Queues.LinearTester<RemoveEqModel> REMOVE_ALL = (
tester,
queue,
random
) -> queue.removeAll(tester.randomElement(random));
private static final Queues.LinearTester<RemoveEqModel> RETAIL_ALL = (
tester,
queue,
random
) -> queue.retainAll(tester.randomElement(random));
/* package-private */ static final Queues.LinearTester<
RemoveEqModel
> REMOVE_EQ_TEST = randomOf(REMOVE_ALL, RETAIL_ALL);
// === Reflection
/* package-private */ interface ReflectionModel extends QueueModel {
Field ELEMENTS = getField("elements");
Field HEAD = getField("head");
@SuppressWarnings("unchecked")
private <Z> Z get(final Field field) {
try {
return (Z) field.get(model());
} catch (final IllegalAccessException e) {
throw new AssertionError(
"Cannot access field " +
field.getName() +
": " +
e.getMessage(),
e
);
}
}
private static Field getField(final String name) {
try {
final Field field = ArrayDeque.class.getDeclaredField(name);
field.setAccessible(true);
return field;
} catch (final NoSuchFieldException e) {
throw new AssertionError(
"Reflection error: " + e.getMessage(),
e
);
}
}
@ReflectionTest.Ignore
default int head() {
return get(HEAD);
}
@ReflectionTest.Ignore
default Object[] elements() {
return get(ELEMENTS);
}
@ReflectionTest.Ignore
default <R> R reduce(
final R zero,
final Predicate<Object> p,
final BiFunction<R, Integer, R> f
) {
final int size = size();
final Object[] elements = elements();
final int head = head();
R result = zero;
for (int i = 0; i < size; i++) {
if (p.test(elements[(head + i) % elements.length])) {
result = f.apply(result, i);
}
}
return result;
}
@ReflectionTest.Ignore
@SuppressWarnings("unused")
default <R> R reduce(
final R zero,
final Object v,
final BiFunction<R, Integer, R> f
) {
return reduce(zero, o -> Objects.equals(v, o), f);
}
}
// === Deque
/* package-private */ interface DequeModel extends QueueModel {
default void push(final Object element) {
model().addFirst(element);
}
@SuppressWarnings("UnusedReturnValue")
default Object peek() {
return model().getLast();
}
default Object remove() {
return model().removeLast();
}
}
/* package-private */ interface DequeChecker<
M extends DequeModel
> extends QueueChecker<M> {
@Override
default void add(
final M queue,
final Object element,
final ExtendedRandom random
) {
if (random.nextBoolean()) {
QueueChecker.super.add(queue, element, random);
} else {
queue.push(element);
}
}
@Override
default void check(final M queue, final ExtendedRandom random) {
if (random.nextBoolean()) {
QueueChecker.super.check(queue, random);
} else {
queue.peek();
}
}
@Override
default void remove(final M queue, final ExtendedRandom random) {
if (random.nextBoolean()) {
QueueChecker.super.remove(queue, random);
} else {
queue.remove();
}
}
}
}

View File

@@ -0,0 +1,201 @@
package queue;
import base.Asserts;
import base.TestCounter;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ReflectionTest {
private static final boolean DEBUG = false;
public static final Collector<CharSequence, ?, String> JOINER = Collectors.joining(", ", "(", ")");
protected final Set<Method> methods = new HashSet<>();
public ReflectionTest() {
Asserts.checkAssert(getClass());
}
protected void checkResult(final String call, final Object expected, final Object actual) {
Asserts.assertEquals(call, expected, actual);
}
protected <T> T checking(final TestCounter counter, final Class<T> type, final T reference, final T tested) {
return proxy(type, (proxy, method, args) -> {
final String call = method.getName() + (args == null ? "()" : args(args));
if (DEBUG) {
counter.format("\t%s%n", call);
}
methods.add(method);
final Object expected;
final Object actual;
try {
expected = method.invoke(reference, args);
actual = method.invoke(tested, args);
} catch (final InvocationTargetException e) {
throw e.getCause();
}
checkResult(call, expected, actual);
return actual;
});
}
protected static String args(final Object[] args) {
return Stream.of(args).map(Objects::toString).collect(JOINER);
}
protected static <T> T proxy(final Class<T> type, final InvocationHandler handler) {
return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler));
}
private interface IMethod {
Object invoke(final Object instance, final Object[] args) throws InvocationTargetException, IllegalAccessException;
}
protected enum Mode {
MODULE("Module") {
@Override
IMethod lookupMethod(final Class<?> type, final Method method) {
return findMethod(true, type, method);
}
},
ADT("ADT") {
@Override
IMethod lookupMethod(final Class<?> type, final Method method) {
final Class<?>[] argTypes = Stream.concat(Stream.of(type), Stream.of(method.getParameterTypes()))
.toArray(Class<?>[]::new);
final Method actual = findMethod(true, type, method.getName(), argTypes);
final Object[] a = new Object[method.getParameterTypes().length + 1];
return (instance, args) -> {
a[0] = instance;
if (args != null) {
System.arraycopy(args, 0, a, 1, args.length);
}
return actual.invoke(null, a);
};
}
},
CLASS("") {
@Override
IMethod lookupMethod(final Class<?> type, final Method method) {
return findMethod(false, type, method);
}
};
private static IMethod findMethod(final boolean isStatic, final Class<?> type, final Method method) {
return findMethod(isStatic, type, method.getName(), method.getParameterTypes())::invoke;
}
private static Method findMethod(final boolean isStatic, final Class<?> type, final String name, final Class<?>[] parameterTypes) {
final String description = name + args(parameterTypes);
final Method method;
try {
method = type.getMethod(name, parameterTypes);
} catch (final NoSuchMethodException e) {
throw Asserts.error("Missing method %s in %s", description, type);
}
if (isStatic != Modifier.isStatic(method.getModifiers())) {
throw Asserts.error("Method %s in %s %s be static", description, type, isStatic ? "must" : "must not");
}
return method;
}
private final String suffix;
Mode(final String suffix) {
this.suffix = suffix;
}
abstract IMethod lookupMethod(final Class<?> type, final Method method);
private Class<?> loadClass(final String baseName) {
final String className = baseName + suffix;
try {
final URL url = Paths.get(".").toUri().toURL();
//noinspection resource
return new URLClassLoader(new URL[]{url}).loadClass(className);
} catch (final MalformedURLException e) {
throw new AssertionError("Cannot load classes from .", e);
} catch (final ClassNotFoundException e) {
throw new AssertionError("Cannot find class %s: %s".formatted(className, e.getMessage()), e);
}
}
}
protected static class ProxyFactory<T> {
private final Class<T> type;
private final Map<Method, IMethod> methods;
private final Constructor<?> constructor;
protected final Class<?> implementation;
protected ProxyFactory(final Class<T> type, final Mode mode, final String baseName) {
implementation = mode.loadClass(baseName);
try {
constructor = implementation.getConstructor();
} catch (final NoSuchMethodException e) {
throw Asserts.error("%s should have default constructor", implementation.getName());
}
this.type = type;
methods = Stream.of(type.getMethods())
.filter(method -> !method.isAnnotationPresent(Ignore.class))
.collect(Collectors.toMap(Function.identity(), method -> mode.lookupMethod(implementation, method)));
}
public T create() {
try {
return wrap(constructor.newInstance());
} catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError("Cannot create " + implementation.getName() + ": " + e.getMessage(), e);
}
}
private T wrap(final Object instance) {
Asserts.assertEquals("same class", implementation, instance.getClass());
return proxy(type, (proxy, method, args) -> {
if (method.getName().equals("toString")) {
return instance.toString();
}
final Object result;
try {
result = methods.get(method).invoke(instance, args);
} catch (final InvocationTargetException e) {
throw e.getCause();
}
if (method.isAnnotationPresent(Wrap.class)) {
return wrap(result);
}
return result;
});
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
protected @interface Ignore {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
protected @interface Wrap {}
}

View File

@@ -0,0 +1,7 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#java-array-queue">Queue</a> homeworks
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package queue;

View File

@@ -0,0 +1,65 @@
package search;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class BinarySearch {
public static void main(String[] args) {
IntList a = new IntList();
int x = Integer.parseInt(args[0]);
int n = args.length;
for (int i = 1; i < n; i++) {
a.put(Integer.parseInt(args[i]));
}
System.out.println(searchRecursive(x, a));
// System.out.println(searchIterative(x, a));
}
static int searchIterative(int x, IntList a) {
if (a.getLength() == 0) {
return 0;
}
int low = 0,
high = a.getLength() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a.get(mid) <= x) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}
static int searchRecursive(int x, IntList a) {
return searchRecursiveHelper(x, a, 0, a.getLength() - 1);
}
private static int searchRecursiveHelper(
int x,
IntList a,
int low,
int high
) {
if (low > high) {
return low;
}
int mid = low + (high - low) / 2;
if (a.get(mid) <= x) {
return searchRecursiveHelper(x, a, low, mid - 1);
} else {
return searchRecursiveHelper(x, a, mid + 1, high);
}
}
}

View File

@@ -0,0 +1,50 @@
package search;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class BinarySearch3233 {
public static void main(String[] args) {
IntList a = new IntList();
for (String arg : args) {
a.put(Integer.parseInt(arg));
}
// System.out.println(searchIterative(a));
System.out.println(searchRecursive(a, 0, a.getLength() - 1));
}
static int searchIterative(IntList a) {
int arrLength = a.getLength();
if (arrLength <= 1) return 0;
int low = 0, high = arrLength - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (a.get(mid) < a.get(arrLength - 1)) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
static int searchRecursive(IntList a, int low, int high) {
if (low >= high) {
return low;
}
int mid = low + (high - low) / 2;
if (a.get(mid) < a.get(a.getLength() - 1)) {
return searchRecursive(a, low, mid);
} else {
return searchRecursive(a, mid + 1, high);
}
}
}

View File

@@ -0,0 +1,48 @@
package search;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class BinarySearch3435 {
public static void main(String[] args) {
IntList a = new IntList();
for (String arg : args) {
a.put(Integer.parseInt(arg));
}
// System.out.println(searchIterative(a));
System.out.println(searchRecursive(a, 0, a.getLength() - 1));
}
static int searchIterative(IntList a) {
int arrLength = a.getLength();
if (arrLength <= 1) return 0;
int low = 0, high = arrLength - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (a.get(mid) > a.get(arrLength - 1)) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
static int searchRecursive(IntList a, int low, int high) {
if (low >= high) {
return low;
}
int mid = low + (high - low) / 2;
if (a.get(mid) > a.get(a.getLength() - 1)) {
return searchRecursive(a, low, mid);
} else {
return searchRecursive(a, mid + 1, high);
}
}
}

View File

@@ -0,0 +1,77 @@
package search;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class BinarySearch3637 {
public static void main(String[] args) {
IntList a = new IntList();
int x = Integer.parseInt(args[0]);
int n = args.length;
for (int i = 1; i < n; i++) {
a.put(Integer.parseInt(args[i]));
}
// System.out.println(rightBoundIterative(x, a) - leftBoundIterative(x, a));
System.out.println(
rightBoundRecursive(x, a, 0, a.getLength() - 1) -
leftBoundRecursive(x, a, 0, a.getLength() - 1)
);
}
static int leftBoundIterative(int x, IntList a) {
int low = 0,
high = a.getLength() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a.get(mid) > x) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return high + 1;
}
static int rightBoundIterative(int x, IntList a) {
int low = 0,
high = a.getLength() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a.get(mid) >= x) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return high + 1;
}
static int leftBoundRecursive(int x, IntList a, int low, int high) {
if (low > high) {
return high + 1;
}
int mid = low + (high - low) / 2;
if (a.get(mid) > x) {
return leftBoundRecursive(x, a, mid + 1, high);
} else {
return leftBoundRecursive(x, a, low, mid - 1);
}
}
static int rightBoundRecursive(int x, IntList a, int low, int high) {
if (low > high) {
return high + 1;
}
int mid = low + (high - low) / 2;
if (a.get(mid) >= x) {
return rightBoundRecursive(x, a, mid + 1, high);
} else {
return rightBoundRecursive(x, a, low, mid - 1);
}
}
}

View File

@@ -0,0 +1,77 @@
package search;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class BinarySearch3839 {
public static void main(String[] args) {
IntList a = new IntList();
int x = Integer.parseInt(args[0]);
int n = args.length;
for (int i = 1; i < n; i++) {
a.put(Integer.parseInt(args[i]));
}
int leftBound = leftBoundIterative(x, a);
int rightBound = rightBoundIterative(x, a);
int range = rightBound - leftBound;
System.out.println(leftBound + " " + range);
}
static int leftBoundIterative(int x, IntList a) {
int low = 0,
high = a.getLength() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a.get(mid) > x) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return high + 1;
}
static int rightBoundIterative(int x, IntList a) {
int low = 0,
high = a.getLength() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a.get(mid) >= x) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return high + 1;
}
static int leftBoundRecursive(int x, IntList a, int low, int high) {
if (low > high) {
return high + 1;
}
int mid = low + (high - low) / 2;
if (a.get(mid) > x) {
return leftBoundRecursive(x, a, mid + 1, high);
} else {
return leftBoundRecursive(x, a, low, mid - 1);
}
}
static int rightBoundRecursive(int x, IntList a, int low, int high) {
if (low > high) {
return high + 1;
}
int mid = low + (high - low) / 2;
if (a.get(mid) >= x) {
return rightBoundRecursive(x, a, mid + 1, high);
} else {
return rightBoundRecursive(x, a, low, mid - 1);
}
}
}

View File

@@ -0,0 +1,180 @@
package search;
import base.MainChecker;
import base.Runner;
import base.Selector;
import base.TestCounter;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.util.stream.IntStream.range;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class BinarySearchTest {
public static final int[] SIZES = {5, 4, 2, 1, 10, 50, 100, 200, 300};
public static final int[] VALUES = new int[]{5, 4, 2, 1, 0, 10, 100, Integer.MAX_VALUE / 2};
private BinarySearchTest() {
}
// === Base
/* package-private */ static int base(final int c, final int x, final int[] a) {
return IntStream.range(0, a.length).filter(i -> Integer.compare(a[i], x) != c).findFirst().orElse(a.length);
}
// === 3637
private static int count(final int c, final int x, final int[] a) {
return (int) range(0, a.length).filter(i -> a[i] == x).count();
}
// === 3839
private static String span(final int c, final int x, final int[] a) {
final int begin = range(0, a.length).filter(i -> a[i] == x || Integer.compare(x, a[i]) == c).findFirst().orElse(a.length);
final long length = range(0, a.length).filter(i -> a[i] == x).count();
return begin + " " + length;
}
// === 3435
private static Consumer<TestCounter> shift(final String name, final Kind kind) {
final Sampler sampler = new Sampler(kind, false, false);
return variant(name, variant -> {
for (final int s : SIZES) {
final int size = s > 3 * TestCounter.DENOMINATOR ? s / TestCounter.DENOMINATOR : s;
for (final int max : VALUES) {
final int[] a = sampler.sample(variant, size, max);
for (int k = 0; k < a.length; k++) {
variant.test(k, a);
final int last = a[a.length - 1];
System.arraycopy(a, 0, a, 1, a.length - 1);
a[0] = last;
}
}
}
});
}
// === Common code
public static final Selector SELECTOR = new Selector(BinarySearchTest.class)
.variant("Base", Solver.variant0("", Kind.DESC, BinarySearchTest::base))
.variant("3637", Solver.variant0("3637", Kind.DESC, BinarySearchTest::count))
.variant("3839", Solver.variant0("3839", Kind.DESC, BinarySearchTest::span))
.variant("3435", shift("3435", Kind.DESC))
.variant("3233", shift("3233", Kind.ASC))
;
public static void main(final String... args) {
SELECTOR.main(args);
}
/* package-private */ static Consumer<TestCounter> variant(final String name, final Consumer<Variant> variant) {
final String className = "BinarySearch" + name;
return counter -> variant.accept(new Variant(counter, new MainChecker(Runner.packages("search").args(className))));
}
/* package-private */ interface Solver {
static Consumer<TestCounter> variant0(final String name, final Kind kind, final Solver solver) {
return variant(name, kind, true, solver);
}
static Consumer<TestCounter> variant1(final String name, final Kind kind, final Solver solver) {
return variant(name, kind, false, solver);
}
private static Consumer<TestCounter> variant(final String name, final Kind kind, final boolean empty, final Solver solver) {
final Sampler sampler = new Sampler(kind, true, true);
return BinarySearchTest.variant(name, vrt -> {
if (empty) {
solver.test(kind, vrt);
}
solver.test(kind, vrt, 0);
for (final int s1 : SIZES) {
final int size = s1 > 3 * TestCounter.DENOMINATOR ? s1 / TestCounter.DENOMINATOR : s1;
for (final int max : VALUES) {
solver.test(kind, vrt, sampler.sample(vrt, size, max));
}
}
});
}
private static int[] probes(final int[] a, final int limit) {
return Stream.of(
Arrays.stream(a),
IntStream.range(1, a.length).map(i -> (a[i - 1] + a[i]) / 2),
IntStream.of(
0, Integer.MIN_VALUE, Integer.MAX_VALUE,
a.length > 0 ? a[0] - 1 : -1,
a.length > 0 ? a[a.length - 1] + 1 : 1
)
)
.flatMapToInt(Function.identity())
.distinct()
.sorted()
.limit(limit)
.toArray();
}
Object solve(final int c, final int x, final int... a);
default void test(final Kind kind, final Variant variant, final int... a) {
test(kind, variant, a, Integer.MAX_VALUE);
}
default void test(final Kind kind, final Variant variant, final int[] a, final int limit) {
for (final int x : probes(a, limit)) {
variant.test(solve(kind.d, x, a), IntStream.concat(IntStream.of(x), Arrays.stream(a)));
}
}
}
public enum Kind {
ASC(-1), DESC(1);
public final int d;
Kind(final int d) {
this.d = d;
}
}
public record Variant(TestCounter counter, MainChecker checker) {
void test(final Object expected, final IntStream ints) {
final List<String> input = ints.mapToObj(Integer::toString).toList();
checker.testEquals(counter, input, List.of(expected.toString()));
}
public void test(final Object expected, final int[] a) {
test(expected, Arrays.stream(a));
}
}
public record Sampler(Kind kind, boolean dups, boolean zero) {
public int[] sample(final Variant variant, final int size, final int max) {
final IntStream sorted = variant.counter.random().getRandom().ints(zero ? size : Math.max(size, 1), -max, max + 1).sorted();
final int[] ints = (dups ? sorted : sorted.distinct()).toArray();
if (kind == Kind.DESC) {
final int sz = ints.length;
for (int i = 0; i < sz / 2; i++) {
final int t = ints[i];
ints[i] = ints[sz - i - 1];
ints[sz - i - 1] = t;
}
}
return ints;
}
}
}

48
java/search/IntList.java Normal file
View File

@@ -0,0 +1,48 @@
package search;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class IntList {
protected int[] list = new int[8];
protected int idx = 0;
public IntList() {}
public IntList(int[] list) {
this.list = list;
}
public void put(int val) {
if (idx >= list.length) {
list = Arrays.copyOf(list, list.length * 2);
}
list[idx++] = val;
}
public int getLength() {
return idx;
}
public int get(int index) {
return list[index];
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < idx; i++) {
if (i == idx - 1) {
sb.append(String.valueOf(list[i]) + "\n");
} else {
sb.append(String.valueOf(list[i]) + " ");
}
}
return sb.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More