first commit
This commit is contained in:
39
java/expression/Abs.java
Normal file
39
java/expression/Abs.java
Normal 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; }
|
||||
}
|
||||
114
java/expression/AbstractBinaryOperation.java
Normal file
114
java/expression/AbstractBinaryOperation.java
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
42
java/expression/AbstractExpression.java
Normal file
42
java/expression/AbstractExpression.java
Normal 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
44
java/expression/Add.java
Normal 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);
|
||||
}
|
||||
}
|
||||
363
java/expression/BigDecimalListExpression.java
Normal file
363
java/expression/BigDecimalListExpression.java
Normal 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);
|
||||
}
|
||||
}
|
||||
357
java/expression/BigIntegerListExpression.java
Normal file
357
java/expression/BigIntegerListExpression.java
Normal 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
62
java/expression/Cbrt.java
Normal 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; }
|
||||
}
|
||||
75
java/expression/Ceiling.java
Normal file
75
java/expression/Ceiling.java
Normal 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;
|
||||
}
|
||||
}
|
||||
46
java/expression/Clear.java
Normal file
46
java/expression/Clear.java
Normal 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
84
java/expression/Const.java
Normal file
84
java/expression/Const.java
Normal 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
55
java/expression/Cube.java
Normal 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; }
|
||||
}
|
||||
83
java/expression/Digits.java
Normal file
83
java/expression/Digits.java
Normal 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;
|
||||
}
|
||||
}
|
||||
45
java/expression/Divide.java
Normal file
45
java/expression/Divide.java
Normal 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);
|
||||
}
|
||||
}
|
||||
10
java/expression/DivisionByZeroException.java
Normal file
10
java/expression/DivisionByZeroException.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class DivisionByZeroException extends ArithmeticException {
|
||||
public DivisionByZeroException() {
|
||||
super("division by zero");
|
||||
}
|
||||
}
|
||||
285
java/expression/Expression.java
Normal file
285
java/expression/Expression.java
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
30
java/expression/ExpressionTest.java
Normal file
30
java/expression/ExpressionTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
415
java/expression/ExpressionTester.java
Normal file
415
java/expression/ExpressionTester.java
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
75
java/expression/Floor.java
Normal file
75
java/expression/Floor.java
Normal 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
51
java/expression/High.java
Normal 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; }
|
||||
}
|
||||
26
java/expression/ListExpression.java
Normal file
26
java/expression/ListExpression.java
Normal 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
46
java/expression/Log.java
Normal 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
52
java/expression/Log2.java
Normal 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
41
java/expression/Low.java
Normal 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
44
java/expression/Max.java
Normal 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
44
java/expression/Min.java
Normal 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);
|
||||
}
|
||||
}
|
||||
44
java/expression/Multiply.java
Normal file
44
java/expression/Multiply.java
Normal 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);
|
||||
}
|
||||
}
|
||||
10
java/expression/OverflowException.java
Normal file
10
java/expression/OverflowException.java
Normal 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
47
java/expression/Pow2.java
Normal 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; }
|
||||
}
|
||||
46
java/expression/Power.java
Normal file
46
java/expression/Power.java
Normal 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()); }
|
||||
}
|
||||
84
java/expression/Reverse.java
Normal file
84
java/expression/Reverse.java
Normal 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;
|
||||
}
|
||||
}
|
||||
46
java/expression/SetBit.java
Normal file
46
java/expression/SetBit.java
Normal 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
60
java/expression/Sqrt.java
Normal 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; }
|
||||
}
|
||||
51
java/expression/Square.java
Normal file
51
java/expression/Square.java
Normal 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; }
|
||||
}
|
||||
44
java/expression/Subtract.java
Normal file
44
java/expression/Subtract.java
Normal 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);
|
||||
}
|
||||
}
|
||||
10
java/expression/ToMiniString.java
Normal file
10
java/expression/ToMiniString.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface ToMiniString {
|
||||
default String toMiniString() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
317
java/expression/TripleExpression.java
Normal file
317
java/expression/TripleExpression.java
Normal 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);
|
||||
}
|
||||
}
|
||||
118
java/expression/Variable.java
Normal file
118
java/expression/Variable.java
Normal 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();
|
||||
}
|
||||
}
|
||||
32
java/expression/common/Expr.java
Normal file
32
java/expression/common/Expr.java
Normal 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);
|
||||
}
|
||||
}
|
||||
94
java/expression/common/ExpressionKind.java
Normal file
94
java/expression/common/ExpressionKind.java
Normal 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;
|
||||
}
|
||||
}
|
||||
173
java/expression/common/Generator.java
Normal file
173
java/expression/common/Generator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
java/expression/common/Node.java
Normal file
106
java/expression/common/Node.java
Normal 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);
|
||||
}
|
||||
}
|
||||
96
java/expression/common/NodeRenderer.java
Normal file
96
java/expression/common/NodeRenderer.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
java/expression/common/NodeRendererBuilder.java
Normal file
145
java/expression/common/NodeRendererBuilder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
java/expression/common/Reason.java
Normal file
60
java/expression/common/Reason.java
Normal 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);
|
||||
}
|
||||
}
|
||||
60
java/expression/common/Renderer.java
Normal file
60
java/expression/common/Renderer.java
Normal 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)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
56
java/expression/common/TestGenerator.java
Normal file
56
java/expression/common/TestGenerator.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
java/expression/common/TestGeneratorBuilder.java
Normal file
145
java/expression/common/TestGeneratorBuilder.java
Normal 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());
|
||||
}
|
||||
}
|
||||
51
java/expression/common/Type.java
Normal file
51
java/expression/common/Type.java
Normal 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);
|
||||
}
|
||||
}
|
||||
7
java/expression/common/package-info.java
Normal file
7
java/expression/common/package-info.java
Normal 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;
|
||||
29
java/expression/exceptions/CheckedAdd.java
Normal file
29
java/expression/exceptions/CheckedAdd.java
Normal 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); }
|
||||
}
|
||||
37
java/expression/exceptions/CheckedDivide.java
Normal file
37
java/expression/exceptions/CheckedDivide.java
Normal 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);
|
||||
}
|
||||
}
|
||||
32
java/expression/exceptions/CheckedMultiply.java
Normal file
32
java/expression/exceptions/CheckedMultiply.java
Normal 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); }
|
||||
}
|
||||
44
java/expression/exceptions/CheckedNegate.java
Normal file
44
java/expression/exceptions/CheckedNegate.java
Normal 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(); }
|
||||
}
|
||||
31
java/expression/exceptions/CheckedSubtract.java
Normal file
31
java/expression/exceptions/CheckedSubtract.java
Normal 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); }
|
||||
}
|
||||
30
java/expression/exceptions/ExceptionsTest.java
Normal file
30
java/expression/exceptions/ExceptionsTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
162
java/expression/exceptions/ExceptionsTestSet.java
Normal file
162
java/expression/exceptions/ExceptionsTestSet.java
Normal 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));
|
||||
}
|
||||
}
|
||||
100
java/expression/exceptions/ExceptionsTester.java
Normal file
100
java/expression/exceptions/ExceptionsTester.java
Normal 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);
|
||||
}
|
||||
}
|
||||
215
java/expression/exceptions/ExpressionParser.java
Normal file
215
java/expression/exceptions/ExpressionParser.java
Normal 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));
|
||||
}
|
||||
}
|
||||
13
java/expression/exceptions/ListParser.java
Normal file
13
java/expression/exceptions/ListParser.java
Normal 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;
|
||||
}
|
||||
8
java/expression/exceptions/package-info.java
Normal file
8
java/expression/exceptions/package-info.java
Normal 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;
|
||||
25
java/expression/generic/ArithmeticType.java
Normal file
25
java/expression/generic/ArithmeticType.java
Normal 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");
|
||||
}
|
||||
}
|
||||
61
java/expression/generic/BigIntegerType.java
Normal file
61
java/expression/generic/BigIntegerType.java
Normal 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);
|
||||
}
|
||||
}
|
||||
56
java/expression/generic/DoubleType.java
Normal file
56
java/expression/generic/DoubleType.java
Normal 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);
|
||||
}
|
||||
}
|
||||
56
java/expression/generic/FloatType.java
Normal file
56
java/expression/generic/FloatType.java
Normal 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);
|
||||
}
|
||||
}
|
||||
10
java/expression/generic/GenericExpr.java
Normal file
10
java/expression/generic/GenericExpr.java
Normal 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);
|
||||
}
|
||||
239
java/expression/generic/GenericParser.java
Normal file
239
java/expression/generic/GenericParser.java
Normal 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++;
|
||||
}
|
||||
}
|
||||
135
java/expression/generic/GenericTabulator.java
Normal file
135
java/expression/generic/GenericTabulator.java
Normal 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;
|
||||
}
|
||||
}
|
||||
184
java/expression/generic/GenericTest.java
Normal file
184
java/expression/generic/GenericTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
366
java/expression/generic/GenericTester.java
Normal file
366
java/expression/generic/GenericTester.java
Normal 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);
|
||||
}
|
||||
}
|
||||
71
java/expression/generic/IntCheckedType.java
Normal file
71
java/expression/generic/IntCheckedType.java
Normal 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");
|
||||
}
|
||||
}
|
||||
61
java/expression/generic/IntTruncType.java
Normal file
61
java/expression/generic/IntTruncType.java
Normal 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));
|
||||
}
|
||||
}
|
||||
57
java/expression/generic/IntUncheckedType.java
Normal file
57
java/expression/generic/IntUncheckedType.java
Normal 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);
|
||||
}
|
||||
}
|
||||
57
java/expression/generic/ShortType.java
Normal file
57
java/expression/generic/ShortType.java
Normal 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);
|
||||
}
|
||||
}
|
||||
18
java/expression/generic/Tabulator.java
Normal file
18
java/expression/generic/Tabulator.java
Normal 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;
|
||||
}
|
||||
8
java/expression/generic/package-info.java
Normal file
8
java/expression/generic/package-info.java
Normal 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;
|
||||
7
java/expression/package-info.java
Normal file
7
java/expression/package-info.java
Normal 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;
|
||||
68
java/expression/parser/Operations.java
Normal file
68
java/expression/parser/Operations.java
Normal 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> {}
|
||||
}
|
||||
236
java/expression/parser/ParserTestSet.java
Normal file
236
java/expression/parser/ParserTestSet.java
Normal 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;
|
||||
}
|
||||
}
|
||||
76
java/expression/parser/ParserTester.java
Normal file
76
java/expression/parser/ParserTester.java
Normal 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/expression/parser/package-info.java
Normal file
7
java/expression/parser/package-info.java
Normal 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;
|
||||
176
java/queue/AbstractQueue.java
Normal file
176
java/queue/AbstractQueue.java
Normal 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();
|
||||
}
|
||||
90
java/queue/ArrayQueue.java
Normal file
90
java/queue/ArrayQueue.java
Normal 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;
|
||||
}
|
||||
}
|
||||
150
java/queue/ArrayQueueADT.java
Normal file
150
java/queue/ArrayQueueADT.java
Normal 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;
|
||||
}
|
||||
}
|
||||
137
java/queue/ArrayQueueModule.java
Normal file
137
java/queue/ArrayQueueModule.java
Normal 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;
|
||||
}
|
||||
}
|
||||
46
java/queue/ArrayQueueTest.java
Normal file
46
java/queue/ArrayQueueTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
224
java/queue/ArrayQueueTester.java
Normal file
224
java/queue/ArrayQueueTester.java
Normal 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
114
java/queue/LinkedQueue.java
Normal 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
56
java/queue/Queue.java
Normal 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
51
java/queue/QueueTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
32
java/queue/QueueTester.java
Normal file
32
java/queue/QueueTester.java
Normal 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
490
java/queue/Queues.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
201
java/queue/ReflectionTest.java
Normal file
201
java/queue/ReflectionTest.java
Normal 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 {}
|
||||
}
|
||||
7
java/queue/package-info.java
Normal file
7
java/queue/package-info.java
Normal 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;
|
||||
65
java/search/BinarySearch.java
Normal file
65
java/search/BinarySearch.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
java/search/BinarySearch3233.java
Normal file
50
java/search/BinarySearch3233.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
48
java/search/BinarySearch3435.java
Normal file
48
java/search/BinarySearch3435.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
77
java/search/BinarySearch3637.java
Normal file
77
java/search/BinarySearch3637.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
java/search/BinarySearch3839.java
Normal file
77
java/search/BinarySearch3839.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
180
java/search/BinarySearchTest.java
Normal file
180
java/search/BinarySearchTest.java
Normal 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
48
java/search/IntList.java
Normal 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
Reference in New Issue
Block a user