migration

This commit is contained in:
root
2026-04-13 20:12:01 +03:00
commit 46ab1753a5
201 changed files with 16685 additions and 0 deletions

1
clojure/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.sh text eol=lf

1
clojure/RunClojure.cmd Normal file
View File

@@ -0,0 +1 @@
@java --class-path "%~dp0lib/*" clojure.main %*

4
clojure/RunClojure.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
java \
--class-path "$(dirname "0")/lib/*" \
clojure.main "$@"

23
clojure/TestClojure.cmd Normal file
View File

@@ -0,0 +1,23 @@
@echo off
if "%~2" == "" (
echo Usage: %~n0 TEST-CLASS MODE VARIANT?
exit /b 1
)
set "OUT=__OUT"
set "CLASS=%~1"
set "ARGS=%~2 %~3"
set "DIR=%~dp0"
set "DIR=%DIR:~0,-1%"
set "LIB=%DIR%/lib/*"
if exist "%OUT%" rmdir /s /q "%OUT%"
javac ^
-encoding utf-8 ^
-d "%OUT%" ^
"--class-path=%LIB%;%DIR%/../common;%DIR%" ^
"%DIR%/%CLASS:.=/%.java" ^
&& java -ea "--class-path=%LIB%;%OUT%" "%CLASS%" %ARGS%

23
clojure/TestClojure.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
set -euo pipefail
if [[ -z "$2" ]] ; then
echo Usage: $(basename "$0") TEST-CLASS MODE VARIANT?
exit 1
fi
CLASS="$1"
ARGS="$2 ${3-}"
OUT=__out
DIR="$(dirname "$0")"
LIB="$DIR/lib/*"
rm -rf "$OUT"
javac \
-encoding utf-8 \
-d "$OUT" \
"--class-path=$LIB:$DIR/../common:$DIR" \
"$DIR/${CLASS//\.//}.java" \
&& java -ea "--class-path=$LIB:$OUT" "$CLASS" $ARGS

View File

@@ -0,0 +1,53 @@
package cljtest;
import clojure.lang.IFn;
import common.Engine;
import java.util.Optional;
/**
* Clojure tests engine.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ClojureEngine implements Engine<Object> {
private static final IFn HASH_MAP = ClojureScript.var("clojure.core/hash-map");
private final Optional<IFn> evaluate;
private final String evaluateString;
private final ClojureScript.F<Object> parse;
private final ClojureScript.F<String> toString;
public ClojureEngine(final String script, final Optional<String> evaluate, final String parse, final String toString) {
ClojureScript.loadScript(script);
this.parse = ClojureScript.function(parse, Object.class);
this.toString = ClojureScript.function(toString, String.class);
this.evaluate = evaluate.map(ClojureScript::var);
evaluateString = evaluate.map(s -> s + " ").orElse("");
}
@Override
public Result<Object> prepare(final String expression) {
return new Result<>(expression, ClojureScript.LOAD_STRING_IN.invoke(expression));
}
@Override
public Result<Object> parse(final String expression) {
return parse.call(new Result<>("\"" + expression + "\"", expression));
}
@Override
public Result<Number> evaluate(final Result<Object> prepared, final double[] vars) {
final Object map = HASH_MAP.invoke("x", vars[0], "y", vars[1], "z", vars[2]);
final String context = "(%sexpr %s)\nwhere expr = %s".formatted(evaluateString, map, prepared.context());
return evaluate
.map(f -> ClojureScript.call(f, Number.class, context, new Object[]{prepared.value(), map}))
.orElseGet(() -> ClojureScript.call((IFn) prepared.value(), Number.class, context, new Object[]{map}));
}
@Override
public Result<String> toString(final Result<Object> prepared) {
return toString.call(prepared);
}
}

View File

@@ -0,0 +1,124 @@
package cljtest;
import clojure.java.api.Clojure;
import clojure.lang.ArraySeq;
import clojure.lang.IFn;
import common.Engine;
import common.EngineException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Utility class for Clojure tests.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ClojureScript {
public static final IFn LOAD_STRING = var("clojure.core/load-string");
public static final IFn LOAD_FILE = asUser("load-file");
public static final IFn LOAD_STRING_IN = asUser("load-string");
public static Path CLOJURE_ROOT = Path.of(".");
private ClojureScript() {
}
private static IFn asUser(final String function) {
return (IFn) LOAD_STRING.invoke(
"(fn " + function + "-in [arg]" +
" (binding [*ns* *ns*]" +
" (in-ns 'user)" +
" (" + function + " arg)))"
);
}
public static void loadScript(final String script) {
final String escaped = CLOJURE_ROOT.toString().replace("\\", "\\\\");
LOAD_STRING_IN.invoke("(defn load-file [file] (clojure.core/load-file (str \"" + escaped + "/\" file)))");
LOAD_FILE.invoke(CLOJURE_ROOT.resolve(script).toString());
}
static <T> Engine.Result<T> call(final IFn f, final Class<T> type, final String context, final Object[] args) {
final Object result;
try {
result = callUnsafe(f, args);
} catch (final AssertionError e) {
throw e;
} catch (final Throwable e) {
throw new EngineException("No error expected in " + context, e);
}
if (result == null) {
throw new EngineException("Expected %s, found null\n%s".formatted(type.getSimpleName(), context), new NullPointerException());
}
if (!type.isInstance(result)) {
throw new EngineException("Expected %s, found %s (%s)\n%s".formatted(type.getSimpleName(), result, result.getClass().getSimpleName(), context), null);
}
return new Engine.Result<>(context, type.cast(result));
}
private static Object callUnsafe(final IFn f, final Object[] args) {
return switch (args.length) {
case 0 -> f.invoke();
case 1 -> f.invoke(args[0]);
case 2 -> f.invoke(args[0], args[1]);
case 3 -> f.invoke(args[0], args[1], args[2]);
case 4 -> f.invoke(args[0], args[1], args[2], args[3]);
case 5 -> f.invoke(args[0], args[1], args[2], args[3], args[4]);
case 6 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5]);
case 7 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
case 8 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
case 9 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
default -> f.applyTo(ArraySeq.create(args));
};
}
public static Engine.Result<Throwable> expectException(final IFn f, final Object[] args, final String context) {
try {
callUnsafe(f, args);
} catch (final Throwable e) {
return new Engine.Result<>(context, e);
}
assert false : "Exception expected in " + context;
return null;
}
public static <T> F<T> function(final String name, final Class<T> type) {
return new F<>(name, type);
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record F<T>(String name, Class<T> type, IFn f) {
public F(final String name, final Class<T> type) {
this(name.substring(name.indexOf("/") + 1), type, var(name));
}
public Engine.Result<T> call(final Engine.Result<?>... args) {
return ClojureScript.call(
f,
type,
callToString(args),
Arrays.stream(args).map(Engine.Result::value).toArray()
);
}
public String callToString(final Engine.Result<?>[] args) {
return "(" + name + Arrays.stream(args).map(arg -> " " + arg.context()).collect(Collectors.joining()) + ")";
}
public Engine.Result<Throwable> expectException(final Engine.Result<?>... args) {
return ClojureScript.expectException(
f,
Arrays.stream(args).map(Engine.Result::value).toArray(),
"(" + name + " " + Arrays.stream(args).map(Engine.Result::context).collect(Collectors.joining(" ")) + ")"
);
}
}
public static IFn var(final String name) {
final String qualifiedName = (name.contains("/") ? "" : "user/") + name;
return Clojure.var(qualifiedName);
}
}

View File

@@ -0,0 +1,67 @@
package cljtest.example;
import base.Asserts;
import base.Selector;
import base.TestCounter;
import cljtest.ClojureScript;
import common.Engine;
import java.util.Arrays;
/**
* Tests for Example Clojure
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExampleTest {
private final TestCounter counter;
private final ClojureScript.F<String> hello;
private final ClojureScript.F<Number> add;
private ExampleTest(final TestCounter counter) {
this.counter = counter;
ClojureScript.loadScript("example.clj");
hello = ClojureScript.function("hello", String.class);
add = ClojureScript.function("add", Number.class);
}
private void test() {
counter.scope("hello", () -> {
assertHello("Clojure");
assertHello(new String[]{"easy", "hard"}[counter.mode()]);
});
counter.scope("add", () -> {
assertAdd();
assertAdd(1);
assertAdd(1, 2);
assertAdd(1, 2, 3);
});
}
private void assertHello(final String name) {
counter.test(() -> Asserts.assertEquals(
"Hello", "Hello, " + name + "!",
hello.call(new Engine.Result<>(name, name)).value()
));
}
private void assertAdd(final int... numbers) {
final Engine.Result<?>[] args = Arrays.stream(numbers).mapToObj(v -> new Engine.Result<>(
v + "",
v
)).toArray(Engine.Result<?>[]::new);
counter.test(() -> Asserts.assertEquals(
Arrays.toString(numbers), Arrays.stream(numbers).sum(),
add.call(args).value().intValue()
));
}
public static final Selector SELECTOR = new Selector(ExampleTest.class, "easy", "hard")
.variant("base", counter -> new ExampleTest(counter).test());
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,324 @@
package cljtest.linear;
import base.Asserts;
import base.ExtendedRandom;
import base.TestCounter;
import cljtest.ClojureScript;
import clojure.lang.IPersistentVector;
import common.Engine;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Clojure bridge.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface Item {
Item ZERO = value(0);
Item ONE = value(1);
int dim();
boolean isValid();
Item refill(ExtendedRandom random);
Engine.Result<?> toClojure();
default Value mapValue(final DoubleUnaryOperator f) {
return value(f.applyAsDouble(value()));
}
default Vector map(final Function<Item, Item> f) {
throw new UnsupportedOperationException("map");
}
default int size() { throw new UnsupportedOperationException("size"); }
default Item get(final int index) { throw new UnsupportedOperationException("get"); }
default double value() {
throw new UnsupportedOperationException("getValue");
}
static Stream<Item> args(final int argc, final Item shape, final ExtendedRandom random) {
return Stream.generate(() -> shape.refill(random)).limit(argc);
}
static Item fromClojure(final Object value) {
if (value instanceof Number n) {
return value(n.doubleValue());
} else if (value instanceof IPersistentVector vector) {
return vector(IntStream.range(0, vector.length()).mapToObj(vector::nth).map(Item::fromClojure));
} else {
throw new AssertionError(value == null ? "null result" : "Unknown type " + value.getClass().getSimpleName());
}
}
static Vector vector(final Stream<? extends Item> items) {
return new Vector(items(items));
}
static Value value(final double value) {
return new Value(value);
}
static List<Item> items(final Stream<? extends Item> items) {
return items.collect(Collectors.toUnmodifiableList());
}
static Supplier<Item> generator(final int... dims) {
Supplier<Item> generator = () -> ZERO;
for (int i = dims.length - 1; i >= 0; i--) {
final int dim = dims[i];
final Supplier<Item> gen = generator;
generator = () -> vector(Stream.generate(gen).limit(dim));
}
return generator;
}
static IntFunction<List<Item>> same(final Supplier<Item> generator) {
return same(generator.get());
}
static IntFunction<List<Item>> same(final Item shape) {
return n -> Collections.nCopies(n, shape);
}
static Engine.Result<?>[] toClojure(final List<Item> args) {
return toArray(args.stream().map(Item::toClojure));
}
static Engine.Result<?>[] toArray(final Stream<? extends Engine.Result<?>> resultStream) {
return resultStream.toArray(Engine.Result<?>[]::new);
}
static List<Fun> functions(final String prefix) {
return functions(prefix, Operation.values());
}
static List<Fun> functions(final String prefix, final Operation... ops) {
return Arrays.stream(ops).map(op -> op.function(prefix)).toList();
}
record Value(double value) implements Item {
public boolean isValid() {
return Double.isFinite(value);
}
@Override
public int dim() {
return 0;
}
@Override
public Value refill(final ExtendedRandom random) {
return new Value(random.nextInt(1, 99) / 10.0);
}
@Override
public Engine.Result<?> toClojure() {
return LinearTester.number(value);
}
@Override
public boolean equals(final Object obj) {
return obj instanceof Value v && Asserts.isEqual(value, v.value, 1e-7);
}
@Override
public String toString() {
return Double.toString(value);
}
}
final class Vector implements Item {
private final List<Item> items;
private final int dim;
private Vector(final List<Item> items) {
this.items = items;
dim = items.stream().mapToInt(Item::dim).max().orElse(0) + 1;
}
@Override
public boolean isValid() {
return items.stream().allMatch(Item::isValid);
}
@Override
public int dim() {
return dim;
}
public int size() {
return items.size();
}
public Item get(final int index) {
return items.get(index);
}
@Override
public Vector refill(final ExtendedRandom random) {
return vector(items.stream().map(item -> item.refill(random)));
}
@Override
public Engine.Result<?> toClojure() {
return LinearTester.vector(toArray(items.stream().map(Item::toClojure)));
}
@Override
public boolean equals(final Object obj) {
return obj instanceof Vector v && items.equals(v.items);
}
@Override
public String toString() {
return items.stream().map(Item::toString).collect(Collectors.joining(", ", "[", "]"));
}
@Override
public Vector map(final Function<Item, Item> f) {
return vector(items.stream().map(f));
}
}
class Fun {
private final Function<List<Item>, Item> expected;
private final ClojureScript.F<?> actual;
public Fun(final String name, final Function<List<Item>, Item> implementation) {
expected = implementation;
actual = ClojureScript.function(name, Object.class);
}
public void test(final TestCounter counter, final Stream<Item> argStream) {
final List<Item> args = items(argStream);
test(counter, args, args);
}
public void test(final TestCounter counter, final List<Item> args, final List<Item> fakeArgs) {
final Item expected = this.expected.apply(fakeArgs);
// if (!expected.isValid()) {
// return;
// }
test(counter, () -> {
final Engine.Result<?> result;
try {
result = actual.call(toClojure(args));
} catch (final RuntimeException | AssertionError e) {
throw new AssertionError("No error expected for " + actual.callToString(toClojure(args)), e);
}
final Item actual = fromClojure(result.value());
if (!expected.equals(actual)) {
throw new AssertionError(result.context() + ": expected " + expected + ", found " + actual);
}
});
// System.err.println("Testing? " + result.context);
}
private static void test(final TestCounter counter, final Runnable action) {
counter.test(() -> {
if (counter.getTestNo() % 1000 == 0) {
counter.println("Test " + counter.getTestNo());
}
action.run();
});
}
public void test(final int args, final Item shape, final TestCounter counter, final ExtendedRandom random) {
test(args, Item.same(shape), counter, random);
}
public void test(final int args, final IntFunction<List<Item>> shapes, final TestCounter counter, final ExtendedRandom random) {
test(shapes.apply(args), counter, random);
}
public void test(final List<Item> shapes, final TestCounter counter, final ExtendedRandom random) {
test(counter, shapes.stream().map(shape -> shape.refill(random)));
}
public void expectException(final TestCounter counter, final Stream<Item> items) {
expectException(counter, toClojure(items.toList()));
}
protected void expectException(final TestCounter counter, final Engine.Result<?>... args) {
test(counter, () -> {
final Engine.Result<Throwable> result = actual.expectException(args);
final boolean ok = result.value() instanceof AssertionError;
if (!ok) {
result.value().printStackTrace();
}
Asserts.assertTrue(
"AssertionError expected instead of " + result.value() + " in " + result.context(),
ok
);
});
}
}
enum Operation {
ADD("+", (a, b) -> a + b, a -> a, ZERO),
SUB("-", (a, b) -> a - b, a -> -a, ZERO),
MUL("*", (a, b) -> a * b, a -> a, ONE),
DIV("d", (a, b) -> a / b, a -> 1 / a, ONE);
private final String suffix;
private final DoubleBinaryOperator binary;
private final DoubleUnaryOperator unary;
private final Item neutral;
Operation(final String suffix, final DoubleBinaryOperator binary, final DoubleUnaryOperator unary,
final Item neutral
) {
this.suffix = suffix;
this.binary = binary;
this.unary = unary;
this.neutral = neutral;
}
public String suffix() {
return suffix;
}
public DoubleBinaryOperator binary() {
return binary;
}
public DoubleUnaryOperator unary() {
return unary;
}
public Item neutral() {
return neutral;
}
public Item apply(final List<Item> args) {
final Item first = args.get(0);
if (first instanceof Value) {
return value(args.size() == 1
? unary.applyAsDouble(first.value())
: args.stream().map(Value.class::cast).mapToDouble(Value::value).reduce(binary).getAsDouble());
} else {
return vector(IntStream.range(0, first.size())
.mapToObj(i -> apply(items(args.stream().map(Vector.class::cast).map(arg -> arg.get(i))))));
}
}
private Fun function(final String prefix) {
return new Fun(prefix + suffix, this::apply);
}
}
}

View File

@@ -0,0 +1,73 @@
package cljtest.linear;
import base.ExtendedRandom;
import base.Selector;
import base.TestCounter;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
/**
* Tests for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#clojure-linear">Linear Clojure</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class LinearTest {
// === Selector
public static final Selector SELECTOR = new Selector(LinearTester.class, "easy", "hard")
.variant("Base", v(LinearTester::new))
;
private LinearTest() {
}
/* package-private*/ static Consumer<TestCounter> v(final Function<TestCounter, LinearTester> variant) {
return counter -> variant.apply(counter).test();
}
/* package-private*/ static Consumer<TestCounter> variant(final List<Item.Fun> functions, final Consumer<Test> variant) {
return v(counter -> new LinearTester(counter) {
@Override
protected void test(final int args) {
variant.accept(new Test(this, functions, args));
}
});
}
/* package-private */ record Test(
LinearTester test,
List<Item.Fun> functions,
int args
) {
public void test(final Supplier<Item> generator) {
test.test(args, functions, Item.same(generator));
}
public void test(final IntFunction<List<Item>> generator) {
test.test(args, functions, generator);
}
public boolean isHard() {
return test.isHard();
}
public void expectException(final int[] okDims, final int[][] failDims) {
test.expectException(functions, okDims, failDims);
}
public ExtendedRandom random() {
return test.random();
}
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,231 @@
package cljtest.linear;
import base.Tester;
import base.TestCounter;
import cljtest.ClojureScript;
import clojure.lang.IPersistentVector;
import common.Engine;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Tester for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#clojure-linear">Linear Clojure</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class LinearTester extends Tester {
public static final ClojureScript.F<IPersistentVector> VECTOR_C = ClojureScript.function("clojure.core/vector", IPersistentVector.class);
private static final List<Item.Fun> VECTOR = Item.functions("v");
private static final List<Item.Fun> MATRIX = Item.functions("m");
static {
ClojureScript.loadScript("linear.clj");
}
public static final Item.Fun SCALAR = new Item.Fun("scalar", args -> Item.value(
IntStream.range(0, args.get(0).size())
.mapToDouble(i -> product(args.stream().map(arg -> arg.get(i))))
.sum()
));
public static final Item.Fun V_BY_S = new Item.Fun("v*s", args -> {
final double q = product(args.stream().skip(1));
return args.get(0).map(v -> v.mapValue(a -> a * q));
});
public static final Item.Fun M_BY_S = new Item.Fun("m*s", args -> {
final double q = product(args.stream().skip(1));
return args.get(0).map(row -> row.map(v -> v.mapValue(a -> a * q)));
});
public static final Item.Fun M_BY_V = new Item.Fun("m*v", args -> {
final Item matrix = args.get(0);
final Item vector = args.get(1);
final double[] result = new double[matrix.size()];
for (int i = 0; i < matrix.size(); i++) {
for (int j = 0; j < vector.size(); j++) {
result[i] += matrix.get(i).get(j).value() * vector.get(j).value();
}
}
return Item.vector(Arrays.stream(result).mapToObj(Item::value));
});
public static final Item.Fun M_BY_M = new Item.Fun("m*m", args -> {
Item a = args.get(0);
for (final Item b : args.subList(1, args.size())) {
final double[][] result = new double[a.size()][b.get(0).size()];
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[i].length; j++) {
for (int k = 0; k < b.size(); k++) {
result[i][j] += a.get(i).get(k).value() * b.get(k).get(j).value();
}
}
}
a = Item.vector(Arrays.stream(result).map(row -> Item.vector(Arrays.stream(row).mapToObj(Item::value))));
}
return a;
});
public static final Item.Fun VECT = new Item.Fun("vect", args -> {
double[] a = IntStream.range(0, 3).mapToDouble(i -> args.get(0).get(i).value()).toArray();
for (final Item bb : args.subList(1, args.size())) {
double[] b = IntStream.range(0, 3).mapToDouble(i -> bb.get(i).value()).toArray();
a = new double[]{a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]};
}
return Item.vector(Arrays.stream(a).mapToObj(Item::value));
});
public static final Item.Fun TRANSPOSE = new Item.Fun("transpose", args -> {
final Item matrix = args.get(0);
return Item.vector(IntStream.range(0, matrix.get(0).size()).mapToObj(i -> matrix.map(row -> row.get(i))));
});
private static double product(final Stream<Item> items) {
return items.mapToDouble(Item::value).reduce(1, (a, b) -> a * b);
}
public LinearTester(final TestCounter counter) {
super(counter);
}
protected static Engine.Result<IPersistentVector> vector(final Number... xs) {
return wrap(LinearTester::number, xs);
}
protected static Engine.Result<IPersistentVector> matrix(final Number[]... m) {
return wrap(LinearTester::vector, m);
}
protected static <I, T> Engine.Result<IPersistentVector> wrap(final Function<I, Engine.Result<T>> wrapper, final I[] m) {
return vector(Arrays.stream(m).map(wrapper).toArray(Engine.Result[]::new));
}
protected static Engine.Result<Number> number(final Number x) {
return new Engine.Result<>(x.toString(), x);
}
protected static Engine.Result<IPersistentVector> vector(final Engine.Result<?>... xs) {
return VECTOR_C.call(xs);
}
protected static Number[] row(final Number... numbers) {
return numbers;
}
@Override
public void test() {
runTest(2);
if (isHard()) {
runTest(1);
for (int i = 3; i <= 5; i++) {
runTest(i);
}
expectException(VECTOR, new int[]{3}, new int[][]{{}, {3, 3}});
expectException(MATRIX, new int[]{3, 3}, new int[][]{{}, {3}, {3, 3, 3}});
expectException(List.of(VECT, SCALAR), new int[]{3}, new int[][]{{}, {3, 3}});
final Engine.Result<IPersistentVector> v123 = vector(1L, 2L, 3L);
final Engine.Result<IPersistentVector> v12 = vector(1.1, 2.1);
final Engine.Result<IPersistentVector> m123_456 = matrix(row(1.1, 2.1, 3.1), row(4.1, 5.1, 6.1));
M_BY_S.expectException(counter, m123_456, v123);
M_BY_V.expectException(counter, m123_456, v12);
M_BY_M.expectException(counter, m123_456, v123);
}
}
private void runTest(final int args) {
counter.scope(args + " arg(s)", () -> test(args));
}
protected boolean isHard() {
return counter.mode() > 0;
}
protected void expectException(final List<Item.Fun> funs, final int[] okDims, final int[][] failDims) {
final Supplier<Item> ok = Item.generator(okDims);
Stream.concat(Arrays.stream(failDims), corrupted(okDims)).map(Item::generator).forEach(fail -> {
expectException(funs, ok, fail);
expectException(funs, fail, ok);
});
}
private static Stream<int[]> corrupted(final int... dims) {
return IntStream.range(0, dims.length)
.boxed()
.flatMap(i -> Stream.of(corruptIndex(i, -1, dims), corruptIndex(i, +1, dims)));
}
private static int[] corruptIndex(final int i, final int delta, final int[] dims) {
final int[] nd = dims.clone();
nd[i] += delta;
return nd;
}
@SafeVarargs
protected final void expectException(final List<Item.Fun> funs, final Supplier<Item>... generators) {
for (final Item.Fun fun : funs) {
final Stream<Item> args = Arrays.stream(generators).map(Supplier::get);
fun.expectException(counter, args);
}
}
protected void test(final int args) {
for (int dim = 0; dim <= 10 / TestCounter.DENOMINATOR2; dim++) {
final Supplier<Item> generator = Item.generator(dim);
test(args, VECTOR, generator);
V_BY_S.test(counter, andScalars(args, generator));
SCALAR.test(args, generator.get(), counter, random());
}
for (int complexity = 1; complexity <= 20 / TestCounter.DENOMINATOR2; complexity++) {
for (int dim1 = 1; dim1 <= complexity; dim1++) {
final int dim2 = complexity - dim1;
if (dim2 > 0 || isHard()) {
final Supplier<Item> generator = Item.generator(dim1, dim2);
test(args, MATRIX, generator);
M_BY_S.test(counter, andScalars(args, generator));
M_BY_V.test(counter, Stream.of(generator.get().refill(random()), Item.generator(dim2).get().refill(
random())));
TRANSPOSE.test(counter, Stream.of(generator.get().refill(random())));
}
}
final int complex = complexity;
final int[] dims = IntStream.generate(() -> 1 + random().nextInt(complex)).limit(args + 1).toArray();
M_BY_M.test(counter, IntStream.range(0, args).mapToObj(i -> Item.generator(dims[i], dims[i + 1]).get().refill(
random())));
}
VECT.test(args, Item.generator(3).get(), counter, random());
}
private Stream<Item> andScalars(final int args, final Supplier<Item> generator) {
return Stream.concat(Stream.of(generator.get().refill(random())), Item.args(args - 1, Item.ZERO, random()));
}
protected void test(final int args, final List<Item.Fun> funs, final IntFunction<List<Item>> generator) {
for (final Item.Fun fun : funs) {
fun.test(args, generator, counter, random());
}
}
protected void test(final int args, final List<Item.Fun> funs, final Supplier<Item> generator) {
test(args, funs, Item.same(generator));
}
}

7
clojure/example.clj Normal file
View File

@@ -0,0 +1,7 @@
(defn hello [name]
(let [message (str "Hello, " name "!")]
(println " " message)
message))
(def add +)

8
clojure/examples.clj Normal file
View File

@@ -0,0 +1,8 @@
(load-file "examples/0_1_magic.clj")
(lecture "1. Functions")
(load-file "examples/1_1_intro.clj")
(load-file "examples/1_2_functions.clj")
(load-file "examples/1_3_lists.clj")
(load-file "examples/1_4_vectors.clj")
(load-file "examples/1_5_functions-2.clj")

View File

@@ -0,0 +1,86 @@
(defn lecture [name]
(println)
(let [line (clojure.string/join (repeat (+ 16 (count name)) "="))]
(println line)
(println "=== Lecture" name "===")
(println line)))
(defn chapter [name]
(println)
(println "==========" name "=========="))
(defn section [name]
(println)
(println "---" name "---"))
(defn- safe-eval [expression]
(try
(eval expression)
(catch Throwable e e)))
(defmacro with-out-str-and-value [body]
`(let [s# (new java.io.StringWriter)]
(binding [*out* s#]
(let [v# (safe-eval ~body)]
(vector (str s#) v#)))))
(defn- init [seq]
(take (dec (count seq)) seq))
(defn- prepend [prefix]
(partial str prefix))
(defn- lines [indent & lines]
(apply str (map (prepend indent) (flatten lines))))
(defn- lines-collection [indent open close f value]
(let [items (mapv f value)]
(case (count items)
0 (str open close)
1 (str open (first items) close)
(lines indent
(str open (first items))
(map (prepend " ") (init (rest items)))
(str " " (last items) close)))))
(defn- remove-generated [value]
(clojure.string/replace value #"__[0-9]+#" "#"))
(defn- render [value]
(cond
(fn? value) (str "#function[" (clojure.string/replace (str (type value)) #"fn__[0-9]+" "fn") "]")
(delay? value) (str "#delay")
(instance? clojure.lang.Namespace value) (str "#namespace[" (ns-name value) "]")
(instance? Throwable value) (str (.getSimpleName (type value)) ": " (.getMessage value))
:else (remove-generated (pr-str value))))
(defn- prettify
([value] (prettify value "\n "))
([value indent]
(let [r-value (render value)]
(cond
(< (count (str indent r-value)) 80) r-value
(vector? value) (lines-collection indent "[" "]" #(clojure.string/triml (prettify % (str indent " "))) value)
(seq? value) (lines-collection indent "(" ")" #(clojure.string/triml (prettify % (str indent " "))) value)
(map? value) (lines-collection
indent "{" "}"
(fn [[key value]] (str (render key) " " (prettify value (str indent " "))))
value)
:else r-value))))
(defn example' [description & expressions]
{:pre [(not (empty? expressions))]}
(println (str " " description ": "))
(letfn [(run [expression]
(let [[output value] (with-out-str-and-value expression)]
(println " " (render expression) "->" (prettify value))
(if-not (empty? output)
(mapv #(println " >" %) (clojure.string/split-lines output)))))]
(mapv run expressions)))
(defmacro example [description & expressions]
`(apply example' ~description (quote ~expressions)))
(defn with-in-file [file action]
(with-in-str (slurp file) (action)))

View File

@@ -0,0 +1,90 @@
(chapter "Expressions and variables")
(section "Numbers and Expressions")
(example "Literals"
2
-10
"Hello world"
true)
(example "Simple expressions"
(+ 2 3)
(- 2 3)
(* 2 3)
(/ 2 3))
(example "Compound expressions"
(+ 2 (- 3 1)))
(example "Variable-args functions"
(- 10 1 2 3))
(example "Special cases"
(- 10))
(example "Nullary functions"
(+))
(section "Equality")
(example "Generic equality"
(= (* 2 3) 6)
(= (* 2 3) 6.0)
(= (* 2 3) 5)
(= (* 2 3) (+ 3 3)))
(example "Number equality"
(== (* 2 3) 6)
(== (* 2 3) 6.0)
(== (* 2 3) 5)
(== (* 2 3) (+ 3 3)))
(example "Reference equality"
(identical? (* 2 3) (+ 3 3))
(identical? (* 2 300) (+ 300 300)))
(example "Inequality"
(not (== (* 2 3) (+ 3 3))))
(section "Booleans")
(example "Literals"
true
false)
(example "not"
(not true)
(not false))
(example "and"
(and true true)
(and true false)
(and false false)
(and true true false false))
(example "or"
(or true true)
(or true false)
(or false false)
(or true true false false))
(section "Variables")
(example "Define"
(def x 10))
(example "Use"
(* x (+ x 3)))
(example "Output"
(println x))
(section "First-order functions")
(example "Output function"
+)
(example "Assign function"
(def add +))
(example "Variable as a function"
(add 10 20 30))
(section "Simple types")
(example "Integers"
(type 10))
(example "Floating-point"
(type 10.0))
(example "Rational"
(type (/ 2 3))
(type 2/3))
(example "BigInt"
(type 2N))
(example "String"
(type "Hello"))
(example "Booleans"
(type true))
(example "Type conversion"
(double 2/3)
(int 2/3))

View File

@@ -0,0 +1,91 @@
(chapter "Custom Functions")
(section "Simple Functions")
(example "Define function"
(defn square [x] (* x x)))(example "Use function"
(square 8))
(example "Multi-arg functions"
(defn mul-add [a b c] (+ (* a b) c))
(mul-add 3 10 5))
(example "Nullary function"
(defn nullary [] 10)
(nullary))
(example "Anonymous functions"
((fn [x] (+ x x)) 10)
(#(+ % %) 10)
(#(+ %1 %2) 10 20))
(example "Functions as values"
(defn twice [f] (fn [a] (f (f a))))
((twice square) 3))
(section "Recursive Functions")
(example "Recursive Fibonacci"
(defn rec-fib [n]
(cond
(== 0 n) 1
(== 1 n) 1
:else (+ (rec-fib (- n 1))
(rec-fib (- n 2)))))
(rec-fib 40))
(example "Memoized Fibonacci"
(def mem-fib
(memoize
(fn [n]
(cond
(== 0 n) 1
(== 1 n) 1
:else (+ (mem-fib (- n 1)) (mem-fib (- n 2)))))))
(mem-fib 90))
(example "Tail-recursive Fibonacci"
(defn iter-fib [n]
(letfn [(iter-fib' [n a b]
(if (== 0 n)
a
(iter-fib' (- n 1) b (+' a b))))]
(iter-fib' n 1 1)))
(iter-fib 90)
(iter-fib 10000)
(defn iter-fib-recur [n]
(letfn [(iter-fib' [n a b]
(if (== 0 n)
a
(recur (- n 1) b (+' a b))))]
(iter-fib' n 1 1)))
(iter-fib-recur 10000))
(example "Explicit loop Fibonacci"
(defn loop-fib [n]
(loop [n n a 1 b 1]
(if (== 0 n)
a
(recur (- n 1) b (+ a b)))))
(loop-fib 90))
(section "Pre and Post conditions")
(example "Fast power"
(defn power
"Raises a to the b-th power"
[a b]
{:pre [(<= 0 b)]
:post [(or (zero? b) (zero? a) (zero? (rem % a)))]}
(cond
(zero? b) 1
(even? b) (power (* a a) (quot b 2))
(odd? b) (* a (power a (dec b))))))
(example "Pre and postconditions ok"
(power 2 5)
(power 2 0)
(power 0 2))
(example "Precondition violated"
(power 2 -5))
(example "Invalid postcondition"
(defn ipower
[a b]
{:pre [(<= 0 b)]
:post [(zero? (rem % a)) (<= 0 %)]}
(power a b)))
(example "First part of invalid postcondition violated"
(ipower 2 0)
(power 2 0))
(example "Second part of invalid postcondition violated"
(ipower -2 3)
(power -2 3))

View File

@@ -0,0 +1,67 @@
(chapter "Lists")
(section "Definition and tests")
(example "Lists"
(list 1 2)
(list 1 2 "Hello" 3 4)
(list))
(example "List type"
(type (list 1 2))
(type (list))
(type ()))
(example "List variable"
(def lst (list 1 2 "Hello" 3 4)))
(example "List test"
(list? lst)
(list? (list 1))
(list? ()))
(section "Operations")
(example "Length"
(count lst))
(example "Head"
(first lst))
(example "Tail"
(rest lst))
(example "Last"
(last lst))
(example "Indexing"
(nth lst 0)
(nth lst 1)
(nth lst 2)
(nth lst 10)
(nth lst 10 "none"))
(example "Add element"
(cons 0 lst))
(example "Add elements"
(conj lst 100 200))
(example "Emptiness test"
(empty? (rest (list 1)))
(empty? (list))
(empty? ())
(empty? lst))
(section "Folds")
(example "Left fold"
(defn foldLeft
"Applies a binary operator f to a zero value and all elements of the list, going left to right"
[zero f items]
(if (empty? items)
zero
(foldLeft (f zero (first items)) f (rest items))))
(foldLeft 0 - (list 1 2 3 4)))
(example "Right fold"
(defn foldRight [zero f items]
"Applies a binary operator f to a zero value and all elements of the list, going right to left"
(if (empty? items)
zero
(f (first items) (foldRight zero f (rest items)))))
(foldRight 0 - (list 1 2 3 4)))
(example "Tail-call optimised left fold"
(defn foldLeft' [zero f items]
(if (empty? items)
zero
(recur (f zero (first items)) f (rest items))))
(count (range 1000000))
(foldLeft 0 + (range 1000000))
(foldLeft' 0 + (range 1000000)))

View File

@@ -0,0 +1,22 @@
(chapter "Vectors")
(example "Vectors"
(vector 1 2)
(vector 1 2 "Hello" 3 4)
[1 2]
(def vect [1 2 "Hello" 3 4]))
(example "Vector type"
(type [1 2])
(type (vector))
(type []))
(example "Queries"
(count vect)
(nth vect 2)
(nth vect 20 "none")
(vect 2)
(vect 10))
(example "Modifications"
(conj vect 100 200)
(peek vect)
(pop vect)
(assoc vect 0 100)
(assoc vect 0 100 2 200))

View File

@@ -0,0 +1,42 @@
(chapter "High-order Functions")
(section "Ordinary functions")
(example "Identity function"
(identity [1 2 3]))
(example "Constant function"
((constantly 10) 20 30))
(section "High-order functions")
(example "Function composition"
((comp square square square) 2)
((comp #(* % 2) #(+ % 2)) 10))
(example "Currying"
(def sum (partial foldLeft' 0 +))
(sum [1 2 3]))
(example "Reduce"
(def sum' (partial reduce + 0))
(sum' [1 2 3]))
(example "Application"
(apply + [1 2 3]))
(example "Map"
(mapv inc (range 10)))
(example "Juxtaposition"
((juxt + - * /) 1 2 3 4))
(section "Variable-argument functions")
(example "Sum of squares"
(defn sumSquares [& xs] (apply + (map square xs)))
(sumSquares 3 4))
(example "Sum of squares (anonymous)"
(#(apply + (map square %&)) 3 4))
(example "Explicit multi-arity"
(defn countArgs
([] "zero")
([a] "one")
([a b] "two")
([a b & as] (str (+ 2 (count as)))))
(countArgs)
(countArgs 1)
(countArgs 1 2)
(countArgs 1 2 3))

Binary file not shown.

Binary file not shown.

Binary file not shown.

92
clojure/linear.clj Normal file
View File

@@ -0,0 +1,92 @@
(defn scalar? [x] (number? x))
(defn v? [x]
(and (vector? x) (every? number? x)))
(defn m? [x]
(and (vector? x) (every? v? x)))
(defn- same-length? [& vs]
(apply = (map count vs)))
(defn- same-shape? [& ms]
(apply = (map (fn [m] (mapv count m)) ms)))
(defn- check [cond msg]
(assert cond msg))
(defn- vec-op [op & vs]
(check (every? v? vs) "Все аргументы должны быть векторами чисел")
(check (apply same-length? vs)
(str "Векторы должны быть одинаковой длины, получено: " (mapv count vs)))
(apply mapv op vs))
(def v+ (partial vec-op +))
(def v- (partial vec-op -))
(def v* (partial vec-op *))
(def vd (partial vec-op /))
(defn scalar [& vs]
(reduce + (apply v* vs)))
(defn vect
"Векторное произведение произвольного числа 3-мерных векторов."
[& vs]
(check (every? v? vs) "Все аргументы должны быть векторами")
(check (every? #(= 3 (count %)) vs)
"Векторное произведение определено только для 3-мерных векторов")
(reduce
(fn [a b]
(let [[a0 a1 a2] a
[b0 b1 b2] b]
[(- (* a1 b2) (* a2 b1))
(- (* a2 b0) (* a0 b2))
(- (* a0 b1) (* a1 b0))]))
vs))
(defn v*s [v & scalars]
(check (v? v) "Первый аргумент должен быть вектором")
(check (every? scalar? scalars) "Остальные аргументы должны быть скалярами")
(let [s (reduce * scalars)]
(mapv #(* % s) v)))
(defn- mat-op [op & ms]
(check (every? m? ms) "Все аргументы должны быть матрицами")
(check (apply same-shape? ms)
(str "Матрицы должны иметь одинаковую форму, получено: "
(mapv #(vector (count %) (count (first %))) ms)))
(apply mapv (fn [& rows] (apply vec-op op rows)) ms))
(def m+ (partial mat-op +))
(def m- (partial mat-op -))
(def m* (partial mat-op *))
(def md (partial mat-op /))
(defn m*s [m & scalars]
(check (m? m) "Первый аргумент должен быть матрицей")
(check (every? scalar? scalars) "Остальные аргументы должны быть скалярами")
(let [s (reduce * scalars)]
(mapv (fn [row] (mapv #(* % s) row)) m)))
(defn transpose [m]
(check (m? m) "Аргумент должен быть матрицей")
(apply mapv vector m))
(defn m*v [m v]
(check (m? m) "Первый аргумент должен быть матрицей")
(check (v? v) "Второй аргумент должен быть вектором")
(check (= (count (first m)) (count v))
(str "Число столбцов матрицы (" (count (first m))
") должно совпадать с длиной вектора (" (count v) ")"))
(mapv #(scalar % v) m))
(defn m*m [& ms]
(check (every? m? ms) "Все аргументы должны быть матрицами")
(reduce
(fn [a b]
(check (= (count (first a)) (count b))
(str "Число столбцов левой матрицы (" (count (first a))
") должно совпадать с числом строк правой (" (count b) ")"))
(let [bt (transpose b)]
(mapv (fn [row] (mapv #(scalar row %) bt)) a)))
ms))