initial commit

This commit is contained in:
2026-01-29 23:20:12 +05:00
parent fb1ce36970
commit 67357cf271
76 changed files with 7115 additions and 0 deletions

178
java/sum/SumTest.java Normal file
View File

@@ -0,0 +1,178 @@
package sum;
import base.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
import java.util.function.*;
import java.util.stream.Collectors;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class SumTest {
// === Base
@FunctionalInterface
/* package-private */ interface Op<T extends Number> extends UnaryOperator<SumTester<T>> {}
private static final BiConsumer<Number, String> TO_STRING = (expected, out) -> Asserts.assertEquals("Sum", expected.toString(), out);
private static final Named<Supplier<SumTester<Integer>>> BASE = Named.of("", () -> new SumTester<>(
Integer::sum, n -> (int) n, (r, max) -> r.nextInt() % max, TO_STRING,
10, 100, Integer.MAX_VALUE
));
/* package-private */ static <T extends Number> Named<Op<T>> plain() {
return Named.of("", test -> test);
}
// === DoubleHex
private static BiConsumer<Number, String> approximate(final Function<String, Number> parser, final double precision) {
return (expected, out) ->
Asserts.assertEquals("Sum", expected.doubleValue(), parser.apply(out).doubleValue(), precision);
}
private static final Named<Supplier<SumTester<Double>>> DOUBLE = Named.of("Double", () -> new SumTester<>(
Double::sum, n -> (double) n, (r, max) -> (r.getRandom().nextDouble() - 0.5) * 2 * max,
approximate(Double::parseDouble, 1e-10),
10.0, 0.01, 1e20, 1e100, Double.MAX_VALUE / 10000)
.test(5, "2.5 2.5")
.test(0, "1e100 -1e100")
.testT(2e100, "1.5e100 0.5e100"));
private static <T extends Number> Named<Op<T>> hexFull(final Function<T, String> toHex) {
final Function<T, String> toHexSpoiled = toHex.andThen(s ->s.chars()
.map(ch -> ((ch ^ s.hashCode()) & 1) == 0 ? Character.toLowerCase(ch) : Character.toUpperCase(ch))
.mapToObj(Character::toString)
.collect(Collectors.joining()));
return Named.of("Hex", test -> test
.test(toHex, 1)
.test(toHex, 0x1a)
.test(toHexSpoiled, 0xA2)
.test(toHexSpoiled, 0X0, 0X1, 0XF, 0XF, 0x0, 0x1, 0xF, 0xf)
.test(toHexSpoiled, 0x12345678)
.test(toHexSpoiled, 0x09abcdef)
.test(toHexSpoiled, 0x3CafeBab)
.test(toHexSpoiled, 0x3DeadBee)
.test(toHex, Integer.MAX_VALUE)
.test(toHex, Integer.MIN_VALUE)
.setToString(number -> {
final int hashCode = number.hashCode();
if ((hashCode & 1) == 0) {
return number.toString();
}
return toHexSpoiled.apply(number);
})
);
}
// === Octal
private static <T extends Number> Named<Op<T>> octal(final Function<T, String> toOctal) {
//noinspection OctalInteger,StringConcatenationMissingWhitespace
return Named.of("Octal", test -> test
.test(1, "1o")
.test(017, "17o")
.testSpaces(6, " 1o 2o 3O ")
.test(01234567, "1234567O")
.test(Integer.MIN_VALUE, "-0" + String.valueOf(Integer.MIN_VALUE).substring(1))
.test(Integer.MAX_VALUE, "0" + Integer.MAX_VALUE)
.test(Integer.MAX_VALUE, Integer.toOctalString(Integer.MAX_VALUE) + "o")
.test(Integer.MAX_VALUE, "0" + Integer.toOctalString(Integer.MAX_VALUE) + "O")
.setToString(number -> {
final int hashCode = number.hashCode();
if ((hashCode & 1) == 0) {
return number.toString();
}
final String lower = toOctal.apply(number).toLowerCase(Locale.ROOT) + "o";
return (hashCode & 2) == 0 ? lower : lower.toUpperCase(Locale.ROOT);
})
);
}
// === Long
private static final Named<Supplier<SumTester<Long>>> LONG = Named.of("Long", () -> new SumTester<>(
Long::sum, n -> n, (r, max) -> r.getRandom().nextLong() % max, TO_STRING,
10L, 100L, (long) Integer.MAX_VALUE, Long.MAX_VALUE)
.test(12345678901234567L, " +12345678901234567 ")
.test(0L, " +12345678901234567 -12345678901234567")
.test(0L, " +12345678901234567 -12345678901234567"));
// === BigInteger
private static final Named<Supplier<SumTester<BigInteger>>> BIG_INTEGER = Named.of("BigInteger", () -> new SumTester<>(
BigInteger::add, BigInteger::valueOf, (r, max) -> new BigInteger(max.bitLength(), r.getRandom()), TO_STRING,
BigInteger.TEN, BigInteger.TEN.pow(10), BigInteger.TEN.pow(100), BigInteger.TWO.pow(1000))
.test(0, "10000000000000000000000000000000000000000 -10000000000000000000000000000000000000000"));
// === BigDecimalHex
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
private static final Named<Supplier<SumTester<BigDecimal>>> BIG_DECIMAL = Named.of("BigDecimal", () -> new SumTester<>(
BigDecimal::add, BigDecimal::valueOf,
(r, max) -> {
final BigInteger unscaled = new BigInteger((max.precision() - max.scale() + 2) * 3, r.getRandom());
return new BigDecimal(unscaled, 3);
},
TO_STRING,
BigDecimal.TEN, BigDecimal.TEN.pow(10), BigDecimal.TEN.pow(100), BigDecimal.ONE.add(BigDecimal.ONE).pow(1000))
.testT(BigDecimal.ZERO.setScale(3), "10000000000000000000000000000000000000000.123 -10000000000000000000000000000000000000000.123"));
private static String bigDecimalToString(final BigDecimal number) {
final int scale = number.scale();
return "0x" + number.unscaledValue().toString(16) + (scale == 0 ? "" : "s" + Integer.toString(scale, 16));
}
// === Hex
private static <T extends Number> Named<Op<T>> hex(final Function<T, String> toHex) {
return hexFull(v -> "0x" + toHex.apply(v));
}
// === Common
/* package-private */ static <T extends Number> Consumer<TestCounter> variant(
final Named<Function<String, Runner>> runner,
final Named<Supplier<SumTester<T>>> test,
final Named<? extends Function<? super SumTester<T>, ? extends SumTester<?>>> modifier
) {
return counter -> modifier.value().apply(test.value().get())
.test("Sum" + test.name() + modifier.name() + runner.name(), counter, runner.value());
}
/* package-private */ static final Named<Function<String, Runner>> RUNNER =
Named.of("", Runner.packages("", "sum")::args);
public static final Selector SELECTOR = selector(SumTest.class, RUNNER);
private SumTest() {
// Utility class
}
public static Selector selector(final Class<?> owner, final Named<Function<String, Runner>> runner) {
return new Selector(owner)
.variant("Base", variant(runner, BASE, plain()))
.variant("3637", variant(runner, DOUBLE, hexFull(value -> value == value.intValue() && value > 0 ? "0x" + Integer.toHexString(value.intValue()) : Double.toHexString(value))))
.variant("3839", variant(runner, BIG_DECIMAL, hexFull(SumTest::bigDecimalToString)))
.variant("3435", variant(runner, BASE, hex(Integer::toHexString)))
.variant("3233", variant(runner, DOUBLE, plain()))
.variant("4749", variant(runner, LONG, octal(Long::toOctalString)))
.variant("4142", variant(runner, BIG_INTEGER, octal(number -> number.toString(8))))
;
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

189
java/sum/SumTester.java Normal file
View File

@@ -0,0 +1,189 @@
package sum;
import base.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class SumTester<T extends Number> {
private static final List<String> SPACES = List.of(
" \t\n\u000B\u2029\f",
IntStream.rangeClosed(0, Character.MAX_VALUE)
.filter(Character::isWhitespace)
.mapToObj(Character::toString)
.collect(Collectors.joining())
);
private final BinaryOperator<T> add;
private final LongFunction<T> fromLong;
private BiFunction<ExtendedRandom, T, String> toString;
private final BiFunction<ExtendedRandom, T, T> randomValue;
private final BiConsumer<Number, String> verifier;
private List<String> spaces;
private final List<T> limits;
private final List<Consumer<Checker>> tests = new ArrayList<>();
@SafeVarargs
public SumTester(
final BinaryOperator<T> add,
final LongFunction<T> fromLong,
final BiFunction<ExtendedRandom, T, T> randomValue,
final BiConsumer<Number, String> verifier,
final T... limits
) {
this.add = add;
this.fromLong = fromLong;
this.randomValue = randomValue;
this.verifier = verifier;
this.limits = List.of(limits);
setSpaces(SPACES);
setToString(String::valueOf);
test(1, "1");
test(6, "1", "2", "3");
test(1, " 1");
test(1, "1 ");
test(1, " 1 ");
test(12345, " 12345 ");
test(60, "010", "020", "030");
testSpaces(1368, " 123 456 789 ");
test(-1, "-1");
test(-6, "-1", "-2", "-3");
test(-12345, " -12345 ");
testSpaces(-1368, " -123 -456 -789 ");
test(1, "+1");
test(6, "+1", "+2", "+3");
test(12345, " +12345 ");
testSpaces(1368, " +123 +456 +789 ");
test(0);
testSpaces(0, " ", " ");
}
protected SumTester<T> setSpaces(final List<String> spaces) {
this.spaces = spaces;
return this;
}
protected SumTester<T> addSpaces(final String... spaces) {
this.spaces = Stream.concat(this.spaces.stream(), Arrays.stream(spaces)).toList();
return this;
}
public SumTester<T> setToString(final Function<T, String> toString) {
return setToString((r, n) -> toString.apply(n));
}
public SumTester<T> setToString(final BiFunction<ExtendedRandom, T, String> toString) {
this.toString = toString;
return this;
}
protected SumTester<T> test(final Function<T, String> toString, final long... input) {
return testT(
fromLong.apply(LongStream.of(input).sum()),
LongStream.of(input).mapToObj(fromLong).map(toString).collect(Collectors.joining(" "))
);
}
protected SumTester<T> test(final long result, final String... input) {
return testT(fromLong.apply(result), input);
}
protected SumTester<T> testT(final T result, final String... input) {
return testT(result, Arrays.asList(input));
}
private SumTester<T> testT(final T result, final List<String> input) {
tests.add(checker -> checker.test(result, input));
return this;
}
public SumTester<T> testSpaces(final long result, final String... input) {
final T res = fromLong.apply(result);
tests.add(checker -> spaces.stream()
.flatMapToInt(String::chars)
.forEach(space -> checker.test(
res,
Functional.map(Arrays.asList(input), s -> s.replace(' ', (char) space))
))
);
return this;
}
public void test(final String name, final TestCounter counter, final Function<String, Runner> runner) {
new Checker(counter, runner.apply(name)).test();
}
private class Checker extends BaseChecker {
private final Runner runner;
public Checker(final TestCounter counter, final Runner runner) {
super(counter);
this.runner = runner;
}
public void test() {
tests.forEach(test -> test.accept(this));
for (final T limit : limits) {
for (int n = 10; n <= 10_000 / TestCounter.DENOMINATOR; n *= 10) {
randomTest(n, limit);
}
}
}
private void test(final T result, final List<String> input) {
counter.test(() -> {
final List<String> out = runner.run(counter, input);
Asserts.assertEquals("Single line expected", 1, out.size());
verifier.accept(result, out.get(0));
});
}
private void randomTest(final int numbers, final T max) {
for (final String spaces : spaces) {
randomTest(numbers, max, spaces);
}
}
private void randomTest(final int numbers, final T max, final String spaces) {
final List<T> values = new ArrayList<>();
for (int i = 0; i < numbers; i++) {
values.add(randomValue.apply(random(), max));
}
testRandom(values.stream().reduce(fromLong.apply(0), add), values, spaces);
}
private void testRandom(final T result, final List<T> args, final String spaces) {
final List<String> spaced = args.stream()
.map(n -> toString.apply(random(), n))
.map(value -> randomSpace(spaces) + value + randomSpace(spaces))
.toList();
final List<String> argsList = new ArrayList<>();
for (final Iterator<String> i = spaced.listIterator(); i.hasNext(); ) {
final StringBuilder next = new StringBuilder(i.next());
while (i.hasNext() && random().nextBoolean()) {
next.append(randomSpace(spaces)).append(i.next());
}
argsList.add(next.toString());
}
test(result, argsList);
}
private String randomSpace(final String spaces) {
return random().randomString(spaces);
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#sum">Sum</a> homework
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package sum;