migration
This commit is contained in:
1
clojure/.gitattributes
vendored
Normal file
1
clojure/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sh text eol=lf
|
||||
1
clojure/RunClojure.cmd
Normal file
1
clojure/RunClojure.cmd
Normal file
@@ -0,0 +1 @@
|
||||
@java --class-path "%~dp0lib/*" clojure.main %*
|
||||
4
clojure/RunClojure.sh
Normal file
4
clojure/RunClojure.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
java \
|
||||
--class-path "$(dirname "0")/lib/*" \
|
||||
clojure.main "$@"
|
||||
23
clojure/TestClojure.cmd
Normal file
23
clojure/TestClojure.cmd
Normal 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
23
clojure/TestClojure.sh
Executable 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
|
||||
53
clojure/cljtest/ClojureEngine.java
Normal file
53
clojure/cljtest/ClojureEngine.java
Normal 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);
|
||||
}
|
||||
}
|
||||
124
clojure/cljtest/ClojureScript.java
Normal file
124
clojure/cljtest/ClojureScript.java
Normal 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);
|
||||
}
|
||||
}
|
||||
67
clojure/cljtest/example/ExampleTest.java
Normal file
67
clojure/cljtest/example/ExampleTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
324
clojure/cljtest/linear/Item.java
Normal file
324
clojure/cljtest/linear/Item.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
clojure/cljtest/linear/LinearTest.java
Normal file
73
clojure/cljtest/linear/LinearTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
231
clojure/cljtest/linear/LinearTester.java
Normal file
231
clojure/cljtest/linear/LinearTester.java
Normal 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
7
clojure/example.clj
Normal file
@@ -0,0 +1,7 @@
|
||||
(defn hello [name]
|
||||
(let [message (str "Hello, " name "!")]
|
||||
(println " " message)
|
||||
message))
|
||||
|
||||
(def add +)
|
||||
|
||||
8
clojure/examples.clj
Normal file
8
clojure/examples.clj
Normal 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")
|
||||
86
clojure/examples/0_1_magic.clj
Normal file
86
clojure/examples/0_1_magic.clj
Normal 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)))
|
||||
90
clojure/examples/1_1_intro.clj
Normal file
90
clojure/examples/1_1_intro.clj
Normal 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))
|
||||
91
clojure/examples/1_2_functions.clj
Normal file
91
clojure/examples/1_2_functions.clj
Normal 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))
|
||||
67
clojure/examples/1_3_lists.clj
Normal file
67
clojure/examples/1_3_lists.clj
Normal 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)))
|
||||
22
clojure/examples/1_4_vectors.clj
Normal file
22
clojure/examples/1_4_vectors.clj
Normal 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))
|
||||
42
clojure/examples/1_5_functions-2.clj
Normal file
42
clojure/examples/1_5_functions-2.clj
Normal 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))
|
||||
BIN
clojure/lib/clojure-1.12.4.jar
Normal file
BIN
clojure/lib/clojure-1.12.4.jar
Normal file
Binary file not shown.
BIN
clojure/lib/core.specs.alpha-0.4.74.jar
Normal file
BIN
clojure/lib/core.specs.alpha-0.4.74.jar
Normal file
Binary file not shown.
BIN
clojure/lib/spec.alpha-0.5.238.jar
Normal file
BIN
clojure/lib/spec.alpha-0.5.238.jar
Normal file
Binary file not shown.
92
clojure/linear.clj
Normal file
92
clojure/linear.clj
Normal 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))
|
||||
Reference in New Issue
Block a user