diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..18af4df12 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,103 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/ubuntu/.devcontainer/base.Dockerfile + +# [Choice] Ubuntu version: bionic, focal +ARG VARIANT="focal" +FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends build-essential software-properties-common xz-utils g++ sbcl julia python3 python3-pip python3-dev ghc openjdk-11-jdk rustc libssl-dev gfortran libxml2-dev libyaml-dev libgmp-dev libz-dev libncurses5 gnuplot nodejs npm lua5.3 ocaml php ruby-full gnu-smalltalk scratch + +# Setup Crystal +RUN echo 'deb http://download.opensuse.org/repositories/devel:/languages:/crystal/xUbuntu_20.04/ /' | sudo tee /etc/apt/sources.list.d/devel:languages:crystal.list +RUN curl -fsSL https://download.opensuse.org/repositories/devel:languages:crystal/xUbuntu_20.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/devel_languages_crystal.gpg > /dev/null + +# Setup Dart +RUN sudo sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' +RUN sudo sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list' + +# Setup Powershell +RUN sudo sh -c 'wget -q https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb' +RUN sudo sh -c 'dpkg -i packages-microsoft-prod.deb' + +# Setup Clojure +RUN sudo sh -c 'curl -O https://download.clojure.org/install/linux-install-1.10.3.967.sh' +RUN sudo sh -c 'chmod +x linux-install-1.10.3.967.sh' +RUN sudo sh -c 'sudo ./linux-install-1.10.3.967.sh' + +# Setup dotnet +RUN sudo sh -c 'wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb' +RUN sudo sh -c 'sudo dpkg -i packages-microsoft-prod.deb' +RUN sudo sh -c 'rm packages-microsoft-prod.deb' + +# Setup D Lang +ENV DLANG_VERSION=2.097.2 +RUN mkdir -p ~/dlang && wget https://dlang.org/install.sh -O ~/dlang/install.sh +RUN bash ~/dlang/install.sh dmd-$DLANG_VERSION +ENV PATH=$PATH:~/dlang/dmd-$DLANG_VERSION/linux/bin64/ + +# Setup Go +RUN sudo sh -c 'wget -c https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local' +ENV PATH=$PATH:/usr/local/go/bin + +# Setup Kotlin (doesnt unzip right maybe?) +RUN sudo sh -c 'wget -c https://github.com/JetBrains/kotlin/releases/download/v1.5.30/kotlin-compiler-1.5.30.zip -O /usr/local/kotlinc.zip' +RUN unzip /usr/local/kotlinc.zip +ENV PATH=$PATH:/usr/local/kotlinc/bin + +# Setup lolcode +## Use: https://github.com/justinmeza/lci + +# Setup Piet +## Use: https://github.com/boothby/repiet + + +# Setup Matlab +# ?????? This is a licensed language??? + +# Setup Emojicode +RUN mkdir -p ~/emojicode && wget -c https://github.com/emojicode/emojicode/releases/download/v1.0-beta.2/Emojicode-1.0-beta.2-Linux-x86_64.tar.gz -O ~/emojicode/emojicode.tar.gz && \ + tar -xzf ~/emojicode/emojicode.tar.gz -C ~/emojicode --strip-components=1 +ENV PATH=$PATH:~/emojicode + +# Setup Factor +RUN mkdir -p ~/factor && wget https://downloads.factorcode.org/releases/0.98/factor-linux-x86-64-0.98.tar.gz -O ~/factor/factor.tar.gz && tar -xzf ~/factor/factor.tar.gz -C ~/factor --strip-components=1 +ENV PATH=$PATH:~/factor/factor + +# Setup R +RUN sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 +RUN sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu focal-cran40/' + +# Setup Racket and Scheme +# To run scheme files, use `racket -f ` +RUN sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D9D33FCD84D82C17288BA03B3C9A6980F827E01E +RUN sudo add-apt-repository 'deb http://ppa.launchpad.net/plt/racket/ubuntu focal main' + +# Setup Scratch +## using 1.x right now.... in future checkout snap or adobe air? + +# Setup Swift +RUN mkdir -p ~/swift && wget https://swift.org/builds/swift-5.5-release/ubuntu2004/swift-5.5-RELEASE/swift-5.5-RELEASE-ubuntu20.04.tar.gz -O ~/swift/swift.tar.gz && \ + tar -xzf ~/swift/swift.tar.gz -C ~/swift --strip-components=1 +ENV PATH=$PATH:~/swift/usr/bin + +# Setup viml +## ? + +# Setup whitespace +## ? + +# Setup Elm +## https://github.com/elm/compiler/blob/master/installers/linux/README.md + +# Setup V +## https://github.com/vlang/v/blob/master/doc/docs.md + + +# Install the packages that needed extra help +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends crystal dart nim powershell scala dotnet-sdk-5.0 r-base racket + +RUN pip install wheel matplotlib numpy coconut + +RUN sudo sh -c 'npm install -g typescript' diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..d09155c0b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/ubuntu +{ + "name": "Ubuntu", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick an Ubuntu version: focal, bionic + "args": { "VARIANT": "focal" } + }, + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} diff --git a/.editorconfig b/.editorconfig index 4ea5e2ea9..a3fbd6e02 100644 --- a/.editorconfig +++ b/.editorconfig @@ -172,3 +172,6 @@ indent_size = 4 indent_style = space indent_size = 2 +[*.coco] +indent_style = space +indent_size = 4 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index fb8ecde19..9b2c1b460 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -59,3 +59,5 @@ This file lists everyone, who contributed to this repo and wanted to show up her - Mahdi Sarikhani - Ridham177 - Hugo Salou +- Dimitri Belopopsky ++ Henrik Abel Christensen diff --git a/README.md b/README.md index e8d947857..ff37394dd 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Here are some essential links: - Book / website: - GitHub repository: - YouTube channel (LeiosOS): -- Twitch livestream (simuleios): +- Twitch livestream: - Discord server: Note that this project is essentially a book about algorithms collaboratively written by an online community. diff --git a/contents/IFS/IFS.md b/contents/IFS/IFS.md index d16a44efc..2ed1fe484 100644 --- a/contents/IFS/IFS.md +++ b/contents/IFS/IFS.md @@ -140,6 +140,8 @@ Here, instead of tracking children of children, we track a single individual tha [import:5-12, lang:"python"](code/python/IFS.py) {% sample lang="c" %} [import:18-29, lang:"c"](code/c/IFS.c) +{% sample lang="lisp" %} +[import:5-14, lang:"lisp"](code/clisp/ifs.lisp) {% sample lang="coco" %} [import:4-16, lang:"coconut"](code/coconut/IFS.coco) {% sample lang="java" %} @@ -224,6 +226,8 @@ In addition, we have written the chaos game code to take in a set of points so t [import, lang:"python"](code/python/IFS.py) {% sample lang="c" %} [import, lang:"c"](code/c/IFS.c) +{% sample lang="lisp" %} +[import, lang:"lisp"](code/clisp/ifs.lisp) {%sample lang="coco" %} [import, lang:"coconut"](code/coconut/IFS.coco) {%sample lang="java" %} diff --git a/contents/IFS/code/clisp/ifs.lisp b/contents/IFS/code/clisp/ifs.lisp new file mode 100644 index 000000000..4354ab54b --- /dev/null +++ b/contents/IFS/code/clisp/ifs.lisp @@ -0,0 +1,28 @@ +;;;; Iterated Function System implementation + +(defstruct (point (:constructor make-point (x y))) x y) + +(defun chaos-game (iterations shape-points) + "Plays a chaos game with a certain shape for a determined amount of iterations" + (loop + repeat iterations + for rand-point = (svref shape-points (random (length shape-points))) + for point = (make-point (random 1.0) (random 1.0)) ; starting point + then (make-point + (* 0.5 (+ (point-x rand-point) (point-x point))) + (* 0.5 (+ (point-y rand-point) (point-y point)))) ; every subsequent point + collect point)) + +(defparameter *shape-points* + (map + 'vector + (lambda (e) (apply #'make-point e)) + ;; the backquote allows us to selectively evaluate (sqrt 0.75) with the comma + `((0 0) (0.5 ,(sqrt 0.75)) (1 0)))) + +;; output the data to the "out.dat" file +(with-open-file (out "out.dat" :direction :output :if-exists :supersede) + (flet ((format-point (p) + ;; this is not very clean, but it's the simplest way to insert a tab into a string. + (format nil "~f~c~f" (point-x p) #\tab (point-y p)))) + (format out "~{~a~%~}" (map 'list #'format-point (chaos-game 10000 *shape-points*))))) diff --git a/contents/approximate_counting/approximate_counting.md b/contents/approximate_counting/approximate_counting.md index 917e79922..654721844 100644 --- a/contents/approximate_counting/approximate_counting.md +++ b/contents/approximate_counting/approximate_counting.md @@ -127,7 +127,7 @@ Here is a table for the true count, approximate count, and percent error for 10, | 500,000 | 499,813.2 | 0.037 | | 1,000,000 | 999,466.0 | 0.053 | -Here, it seems that the percent error is 10 times higher when we count 10,000 items; however, +Here, it seems that the percent error is 10 times higher when we count 10,000 items; however, with these numbers, I could imagine some people reading this are thinking that we are splitting hairs. A 0.42% error is still really good, right? Right. @@ -200,7 +200,7 @@ To be clear, here is a table of several values that could be stored in a bitstri | $$00000100 = 4$$ | $$15$$ | | $$00010000 = 16$$ | $$65535$$ | | $$01000000 = 64$$ | $$1.85 \times 10^{19}$$ | -| $$10000000 = 128$$ | $$3.40 \times 10^{38}$$ | +| $$10000000 = 128$$ | $$3.40 \times 10^{38}$$ | | $$11111111 = 255$$ | $$5.79 \times 10^{76}$$ | This means that we can hold from $$0$$ to $$2^{255} - 1 \approx 5.79 \times 10^{76}$$ with 8 bits using this new method. @@ -250,7 +250,7 @@ In the next section, we will consider how to generalize this logarithmic method ## A slightly more general logarithm Let's start by considering the differences between base $$2$$ and base $$e$$. -For base $$e$$, +For base $$e$$, $$ \begin{align} @@ -283,14 +283,14 @@ Going one step further, we need to chose a specific base to a logarithm that wil $$ \begin{align} - v &= \frac{\log(1+n/a)}{\log(1+1/a)}. \\ + v &= \frac{\log(1+n/a)}{\log(1+1/a)}. \\ n_v &= a\left(\left(1+\frac{1}{a}\right)^v-1\right). \end{align} $$ Here, $$a$$ is an effective tuning parameter and sets the maximum count allowed by the bitstring and the expected error. The expression $$1+1/a$$ acts as a base for the logarithm and exponents and ensures that the first count of $$n=1$$ will also set the value $$v=1$$. -As an example, if the bitstring can be a maximum of 255 (for 8 bits) and we arbitrarily set +As an example, if the bitstring can be a maximum of 255 (for 8 bits) and we arbitrarily set $$a=30$$, then the highest possible count with this approach will be $$\approx 130,000$$, which was the number reported in Morris's paper. If we perform a few counting experiments, we find that this formula more closely tracks smaller numbers than before (when we were not using the logarithm): @@ -360,8 +360,12 @@ As we do not have any objects to count, we will instead simulate the counting wi {% method %} {% sample lang="jl" %} [import, lang:"julia"](code/julia/approximate_counting.jl) +{% sample lang="c" %} +[import, lang:"c"](code/c/approximate_counting.c) {% sample lang="cpp" %} [import, lang:"cpp"](code/c++/approximate_counting.cpp) +{% sample lang="python" %} +[import, lang:"python"](code/python/approximate_counting.py) {% endmethod %} ### Bibliography diff --git a/contents/approximate_counting/code/c++/approximate_counting.cpp b/contents/approximate_counting/code/c++/approximate_counting.cpp index 7f3f1a16c..53f4641af 100644 --- a/contents/approximate_counting/code/c++/approximate_counting.cpp +++ b/contents/approximate_counting/code/c++/approximate_counting.cpp @@ -61,11 +61,11 @@ auto test_approximate_count( int main() { std::cout << "Counting Tests, 100 trials\n"; - std::cout << "testing 1,000, a = 30, 1% error " + std::cout << "testing 1,000, a = 30, 10% error " << test_approximate_count(100, 1000, 30, 0.1) << "\n"; - std::cout << "testing 12,345, a = 10, 1% error " + std::cout << "testing 12,345, a = 10, 10% error " << test_approximate_count(100, 12345, 10, 0.1) << "\n"; // Note : with a lower a, we need more trials, so a higher % error here. - std::cout << "testing 222,222, a = 0.5, 10% error " + std::cout << "testing 222,222, a = 0.5, 20% error " << test_approximate_count(100, 222222, 0.5, 0.2) << "\n"; } diff --git a/contents/approximate_counting/code/c/approximate_counting.c b/contents/approximate_counting/code/c/approximate_counting.c new file mode 100644 index 000000000..da44334a7 --- /dev/null +++ b/contents/approximate_counting/code/c/approximate_counting.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +// This function returns a pseudo-random number between 0 and 1 +double drand() +{ + return (double)rand() / RAND_MAX; +} + +// This function takes +// - v: value in register +// - a: a scaling value for the logarithm based on Morris's paper +// It returns the approximate count +double n(double v, double a) +{ + return a * (pow(1 + 1 / a, v) - 1); +} + +// This function takes +// - v: value in register +// - a: a scaling value for the logarithm based on Morris's paper +// It returns a new value for v +double increment(double v, double a) +{ + // delta is the probability of incrementing our counter + double delta = 1 / (n(v + 1, a) - n(v, a)); + + if (drand() <= delta) { + return v + 1; + } + return v; +} + +// This function simulates counting and takes +// - n_items: number of items to count and loop over +// - a: a scaling value for the logarithm based on Morris's paper +// It returns n(v, a), the approximate count +double approximate_count(size_t n_items, double a) +{ + int v = 0; + for (size_t i = 0; i < n_items; ++i) { + v = increment(v, a); + } + + return n(v, a); +} + +// This function takes +// - n_trials: the number of counting trials +// - n_items: the number off items to count +// - a: a scaling value for the logarithm based on Morris's paper +// - threshold: the maximum percent error allowed +// It terminates the program on failure +void test_approximation_count(size_t n_trials, size_t n_items, double a, + double threshold) +{ + double sum = 0.0; + for (size_t i = 0; i < n_trials; ++i) { + sum += approximate_count(n_items, a); + } + double avg = sum / n_trials; + + assert(fabs((avg - n_items) / n_items) < threshold); +} + +int main() +{ + srand(time(NULL)); + + printf("Counting Tests, 100 trials\n"); + printf("testing 1000, a = 30, 10%% error\n"); + test_approximation_count(100, 1000, 30, 0.1); + printf("testing 12345, a = 10, 10%% error\n"); + test_approximation_count(100, 12345, 10, 0.1); + printf("testing 222222, a = 0.5, 20%% error\n"); + test_approximation_count(100, 222222, 0.5, 0.2); + + return 0; +} diff --git a/contents/approximate_counting/code/julia/approximate_counting.jl b/contents/approximate_counting/code/julia/approximate_counting.jl index 36b07651e..c6cf3b223 100644 --- a/contents/approximate_counting/code/julia/approximate_counting.jl +++ b/contents/approximate_counting/code/julia/approximate_counting.jl @@ -51,11 +51,11 @@ function test_approximate_count(n_trials, n_items, a, threshold) end @testset "Counting Tests, 100 trials" begin - println("testing 1,000, a = 30, 1% error") + println("testing 1,000, a = 30, 10% error") test_approximate_count(100, 1000, 30, 0.1) - println("testing 12,345, a = 10, 1% error") + println("testing 12,345, a = 10, 10% error") test_approximate_count(100, 12345, 10, 0.1) # Note: with a lower a, we need more trials, so a higher % error here. - println("testing 222,222, a = 0.5, 10% error") + println("testing 222,222, a = 0.5, 20% error") test_approximate_count(100, 222222, 0.5, 0.2) end diff --git a/contents/approximate_counting/code/python/approximate_counting.py b/contents/approximate_counting/code/python/approximate_counting.py new file mode 100644 index 000000000..0088debcc --- /dev/null +++ b/contents/approximate_counting/code/python/approximate_counting.py @@ -0,0 +1,49 @@ +from random import random + +# This function takes +# - v: value in register +# - a: a scaling value for the logarithm based on Morris's paper +# It returns n(v,a), the approximate_count +def n(v, a): + return a*((1 + 1/a)**v - 1) + +# This function takes +# - v: value in register +# - a: a scaling value for the logarithm based on Morris's paper +# It returns a new value for v +def increment(v, a): + delta = 1/(n(v + 1, a) - n(v, a)) + if random() <= delta: + return v + 1 + else: + return v + +#This simulates counting and takes +# - n_items: number of items to count and loop over +# - a: a scaling value for the logarithm based on Morris's paper +# It returns n(v,a), the approximate count +def approximate_count(n_items, a): + v = 0 + for i in range(1, n_items + 1): + v = increment(v, a) + return n(v, a) + +# This function takes +# - n_trials: the number of counting trials +# - n_items: the number of items to count to +# - a: a scaling value for the logarithm based on Morris's paper +# - threshold: the maximum percent error allowed +# It returns a true / false test value +def test_approximate_count(n_trials, n_items, a, threshold): + samples = [approximate_count(n_items, a) for i in range(1, n_trials + 1)] + avg = sum(samples)/n_trials + + if abs((avg - n_items)/n_items) < threshold: + print("passed") + +print("testing 1,000, a = 30, 10% error") +test_approximate_count(100, 1000, 30, 0.1) +print("testing 12,345, a = 10, 10% error") +test_approximate_count(100, 12345, 10, 0.1) +print("testing 222,222, a = 0.5, 20% error") +test_approximate_count(100, 222222, 0.5, 0.2) diff --git a/contents/barnsley/barnsley.md b/contents/barnsley/barnsley.md index 0c0cf682b..f696b9ffb 100644 --- a/contents/barnsley/barnsley.md +++ b/contents/barnsley/barnsley.md @@ -58,7 +58,7 @@ Now let's hop into disecting the Barnsley fern by seeing how each transform affe | $$f_4(P) = \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix}$$

This operation flips every point and rotates to the right.|

| At this stage, it *might* be clear what is going on, but it's not exactly obvious. -Essentiallg, each operation corresponds to another part of the fern: +Essentially, each operation corresponds to another part of the fern: * $$f_1$$ creates the stem. * $$f_2$$ creates successively smaller ferns moving up and to the right. @@ -85,7 +85,7 @@ To account for this, each function is also given a probability of being chosen: | Function | Probability | | -------- | ----------- | | $$f_1(P) = \begin{bmatrix} 0 &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix}$$ | 0.01 | -| $$f_2(P) = \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ | 0.85 | +| $$f_2(P) = \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ | 0.85 | | $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &022 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ | 0.07 | | $$f_4(P) = \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix}$$ | 0.07 | @@ -125,12 +125,16 @@ The biggest differences between the two code implementations is that the Barnsle {% method %} {% sample lang="jl" %} [import, lang:"julia"](code/julia/barnsley.jl) +{% sample lang="rs" %} +[import, lang:"rust"](code/rust/src/main.rs) {% sample lang="cpp" %} [import, lang:"cpp"](code/c++/barnsley.cpp) {% sample lang="c" %} [import, lang:"c"](code/c/barnsley.c) {% sample lang="java" %} [import, lang:"java"](code/java/Barnsley.java) +{% sample lang="coco" %} +[import, lang:"coconut"](code/coconut/barnsley.coco) {% endmethod %} ### Bibliography diff --git a/contents/barnsley/code/coconut/barnsley.coco b/contents/barnsley/code/coconut/barnsley.coco new file mode 100644 index 000000000..a27fc7c5c --- /dev/null +++ b/contents/barnsley/code/coconut/barnsley.coco @@ -0,0 +1,44 @@ +from random import choices +import numpy as np + +data Point(x=0, y=0): + def __rmatmul__(self, mat: np.array): + point_array = np.array([self.x, self.y, 1]) + x, y, *_ = tuple(*(mat @ point_array)) + return Point(x, y) + + +def chaos_game(initial_location is Point, hutchinson_op, probabilities): + point = initial_location + while True: + yield (point := choices(hutchinson_op, probabilities) @ point) + +barnsley_hutchinson = [ + np.array([ + [0., 0., 0.], + [0., 0.16, 0.], + [0., 0., 1.], + ]), + np.array([ + [0.85, 0.04, 0.], + [-0.04, 0.85, 1.6], + [0., 0., 1.], + ]), + np.array([ + [0.2, -0.26, 0.], + [0.23, 0.22, 1.6], + [0., 0., 1.], + ]), + np.array([ + [-0.15, 0.28, 0.], + [0.26, 0.24, 0.44], + [0., 0., 1.], + ]), +] + +barnsley_probabilities = [0.01, 0.85, 0.07, 0.07] + +if __name__ == '__main__': + output_gen = chaos_game(Point(0, 0), barnsley_hutchinson, barnsley_probabilities) + output_points = np.array([*output_gen$[:10000]]) + np.savetxt("out.dat", output_points) diff --git a/contents/barnsley/code/rust/Cargo.toml b/contents/barnsley/code/rust/Cargo.toml new file mode 100644 index 000000000..40780594a --- /dev/null +++ b/contents/barnsley/code/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.4" \ No newline at end of file diff --git a/contents/barnsley/code/rust/src/main.rs b/contents/barnsley/code/rust/src/main.rs new file mode 100644 index 000000000..6fee75375 --- /dev/null +++ b/contents/barnsley/code/rust/src/main.rs @@ -0,0 +1,105 @@ +use rand::prelude::*; +#[derive(Clone, Copy)] +struct Point2 { + x: f64, + y: f64, +} + +#[derive(Clone, Copy)] +struct Point3 { + x: f64, + y: f64, + z: f64, +} + +impl Point3 { + fn new(x: f64, y: f64, z: f64) -> Self { + Self { x, y, z } + } + + fn matrix_mul(self, rhs: Vec) -> Self { + let x = rhs[0].x * self.x + rhs[0].y * self.y + rhs[0].z * self.z; + let y = rhs[1].x * self.x + rhs[1].y * self.y + rhs[1].z * self.z; + let z = rhs[2].x * self.x + rhs[2].y * self.y + rhs[2].z * self.z; + Self::new(x, y, z) + } +} + +fn select_array(hutchinson_op: &[Vec], probabilities: &[f64]) -> Vec { + let mut rng = rand::thread_rng(); + let mut rnd = rng.gen::(); + + for (i, probability) in probabilities.iter().enumerate() { + if rnd < *probability { + return hutchinson_op[i].clone(); + } + rnd -= probability; + } + + return vec![]; +} + +fn chaos_game( + iters: usize, + initial_location: Point2, + hutchinson_op: &[Vec], + probabilities: &[f64], +) -> Vec { + let mut point = Point3 { + x: initial_location.x, + y: initial_location.y, + z: 1.0, + }; + (0..iters) + .into_iter() + .map(|_| { + let old_point = point; + let operation = select_array(hutchinson_op, probabilities); + point = point.matrix_mul(operation); + Point2 { + x: old_point.x, + y: old_point.y, + } + }) + .collect() +} + +fn main() { + let barnsley_hutchinson = vec![ + vec![ + Point3::new(0.0, 0.0, 0.0), + Point3::new(0.0, 0.16, 0.0), + Point3::new(0.0, 0.0, 1.0), + ], + vec![ + Point3::new(0.85, 0.04, 0.0), + Point3::new(-0.04, 0.85, 1.60), + Point3::new(0.0, 0.0, 1.0), + ], + vec![ + Point3::new(0.20, -0.26, 0.0), + Point3::new(0.23, 0.22, 1.60), + Point3::new(0.0, 0.0, 1.0), + ], + vec![ + Point3::new(-0.15, 0.28, 0.0), + Point3::new(0.26, 0.24, 0.44), + Point3::new(0.0, 0.0, 1.0), + ], + ]; + + let barnsley_probabilities = vec![0.01, 0.85, 0.07, 0.07]; + + let mut out = String::new(); + + for point in chaos_game( + 10_000, + Point2 { x: 0.0, y: 0.0 }, + &barnsley_hutchinson, + &barnsley_probabilities, + ) { + out += format!("{}\t{}\n", point.x, point.y).as_str(); + } + + std::fs::write("./out.dat", out).unwrap(); +} diff --git a/contents/convolutions/1d/1d.md b/contents/convolutions/1d/1d.md index 3e663938e..9a2e652d1 100644 --- a/contents/convolutions/1d/1d.md +++ b/contents/convolutions/1d/1d.md @@ -53,7 +53,11 @@ With this in mind, we can almost directly transcribe the discrete equation into {% method %} {% sample lang="jl" %} -[import:27-46, lang:"julia"](../code/julia/1d_convolution.jl) +[import:27-46, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:63-84, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import:20-31, lang:"python"](code/python/1d_convolution.py) {% endmethod %} The easiest way to reason about this code is to read it as you might read a textbook. @@ -184,7 +188,11 @@ Here it is again for clarity: {% method %} {% sample lang="jl" %} -[import:27-46, lang:"julia"](../code/julia/1d_convolution.jl) +[import:27-46, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:63-84, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import:20-31, lang:"python"](code/python/1d_convolution.py) {% endmethod %} Here, the main difference between the bounded and unbounded versions is that the output array size is smaller in the bounded case. @@ -192,14 +200,22 @@ For an unbounded convolution, the function would be called with a the output arr {% method %} {% sample lang="jl" %} -[import:58-59, lang:"julia"](../code/julia/1d_convolution.jl) +[import:58-59, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:96-97, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import:41-42, lang:"python"](code/python/1d_convolution.py) {% endmethod %} On the other hand, the bounded call would set the output array size to simply be the length of the signal {% method %} {% sample lang="jl" %} -[import:61-62, lang:"julia"](../code/julia/1d_convolution.jl) +[import:61-62, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:98-99, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import:44-45, lang:"python"](code/python/1d_convolution.py) {% endmethod %} Finally, as we mentioned before, it is possible to center bounded convolutions by changing the location where we calculate the each point along the filter. @@ -207,7 +223,11 @@ This can be done by modifying the following line: {% method %} {% sample lang="jl" %} -[import:35-35, lang:"julia"](../code/julia/1d_convolution.jl) +[import:35-35, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:71-71, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import:25-25, lang:"python"](code/python/1d_convolution.py) {% endmethod %} Here, `j` counts from `i-length(filter)` to `i`. @@ -239,7 +259,11 @@ In code, this typically amounts to using some form of modulus operation, as show {% method %} {% sample lang="jl" %} -[import:4-25, lang:"julia"](../code/julia/1d_convolution.jl) +[import:4-25, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:38-61, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import:5-17, lang:"python"](code/python/1d_convolution.py) {% endmethod %} This is essentially the same as before, except for the modulus operations, which allow us to work on a periodic domain. @@ -254,7 +278,11 @@ For the code associated with this chapter, we have used the convolution to gener {% method %} {% sample lang="jl" %} -[import, lang:"julia"](../code/julia/1d_convolution.jl) +[import, lang:"julia"](code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import, lang:"csharp"](code/csharp/1DConvolution.cs) +{% sample lang="py" %} +[import, lang:"python"](code/python/1d_convolution.py) {% endmethod %} At a test case, we have chosen to use two sawtooth functions, which should produce the following images: diff --git a/contents/convolutions/1d/code/csharp/1DConvolution.cs b/contents/convolutions/1d/code/csharp/1DConvolution.cs new file mode 100755 index 000000000..c4c1016c6 --- /dev/null +++ b/contents/convolutions/1d/code/csharp/1DConvolution.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; + +namespace Convolution1D +{ + public class Convolution1D + { + // Creates a sawtooth function with the given length. + static double[] CreateSawtooth(int length) + { + var array = new double[length]; + for (var i = 0; i < length; i++) + array[i] = (i + 1) / 200f; + return array; + } + + // Normalizes the given array. + static void Normalize(double[] array) + { + var norm = Norm(array); + for (var i = 0; i < array.Length; i++) + array[i] /= norm; + } + + // Calculates the norm of the array. + static double Norm(double[] array) + { + var sum = 0.0; + for (var i = 0; i < array.Length; i++) + sum += Math.Pow(array[i], 2); + return Math.Sqrt(sum); + } + + // Modulus function which handles negative values properly. + // Assumes that y >= 0. + static int Mod(int x, int y) => ((x % y) + y) % y; + + static double[] ConvolveCyclic(double[] signal, double[] filter) + { + var outputSize = Math.Max(signal.Length, filter.Length); + + // Convolutional output. + var output = new double[outputSize]; + var sum = 0.0; + + for (var i = 0; i < outputSize; i++) + { + for (var j = 0; j < outputSize; j++) + { + if (Mod(i - j, outputSize) < filter.Length) + { + sum += signal[Mod(j - 1, outputSize)] * filter[Mod(i - j, outputSize)]; + } + } + + output[i] = sum; + sum = 0.0; + } + + return output; + } + + static double[] ConvolveLinear(double[] signal, double[] filter, int outputSize) + { + // Convolutional output. + var output = new double[outputSize]; + var sum = 0.0; + + for (var i = 0; i < outputSize; i++) + { + for (var j = Math.Max(0, i - filter.Length); j <= i; j++) + { + if (j < signal.Length && (i - j) < filter.Length) + { + sum += signal[j] * filter[i - j]; + } + } + + output[i] = sum; + sum = 0.0; + } + + return output; + } + + static void Main() + { + // Create sawtooth functions for x and y. + var x = CreateSawtooth(200); + var y = CreateSawtooth(200); + + // Normalization is not strictly necessary, but good practice. + Normalize(x); + Normalize(y); + + // Full convolution, output will be the size of x + y - 1. + var fullLinearOutput = ConvolveLinear(x, y, x.Length + y.Length - 1); + // Simple boundaries. + var simpleLinearOutput = ConvolveLinear(x, y, x.Length); + // Cyclic convolution. + var cyclicOutput = ConvolveCyclic(x, y); + + // Output convolutions to different files for plotting. + File.WriteAllText("full_linear.dat", String.Join(Environment.NewLine, fullLinearOutput)); + File.WriteAllText("simple_linear.dat", String.Join(Environment.NewLine, simpleLinearOutput)); + File.WriteAllText("cyclic.dat", String.Join(Environment.NewLine, cyclicOutput)); + } + } +} + diff --git a/contents/convolutions/code/julia/1d_convolution.jl b/contents/convolutions/1d/code/julia/1d_convolution.jl similarity index 100% rename from contents/convolutions/code/julia/1d_convolution.jl rename to contents/convolutions/1d/code/julia/1d_convolution.jl diff --git a/contents/convolutions/1d/code/python/1d_convolution.py b/contents/convolutions/1d/code/python/1d_convolution.py new file mode 100644 index 000000000..e77e68d09 --- /dev/null +++ b/contents/convolutions/1d/code/python/1d_convolution.py @@ -0,0 +1,53 @@ +import numpy as np + +def mod1(x, y): return ((x % y) + y) % y + +def convolve_cyclic(signal, filter_array): + output_size = max(len(signal), len(filter_array)) + out = np.zeros(output_size) + s = 0 + + for i in range(output_size): + for j in range(output_size): + if(mod1(i - j, output_size) < len(filter_array)): + s += signal[mod1(j - 1, output_size)] * filter_array[mod1(i - j, output_size)] + out[i] = s + s = 0 + + return out + + +def convolve_linear(signal, filter_array, output_size): + out = np.zeros(output_size) + s = 0 + + for i in range(output_size): + for j in range(max(0, i - len(filter_array)), i + 1): + if j < len(signal) and (i - j) < len(filter_array): + s += signal[j] * filter_array[i - j] + out[i] = s + s = 0 + + return out + +# sawtooth functions for x and y +x = [float(i + 1)/200 for i in range(200)] +y = [float(i + 1)/200 for i in range(200)] + +# Normalization is not strictly necessary, but good practice +x /= np.linalg.norm(x) +y /= np.linalg.norm(y) + +# full convolution, output will be the size of x + y - 1 +full_linear_output = convolve_linear(x, y, len(x) + len(y) - 1) + +# simple boundaries +simple_linear_output = convolve_linear(x, y, len(x)) + +# cyclic convolution +cyclic_output = convolve_cyclic(x, y) + +# outputting convolutions to different files for plotting in external code +np.savetxt('full_linear.dat', full_linear_output) +np.savetxt('simple_linear.dat', simple_linear_output) +np.savetxt('cyclic.dat', cyclic_output) diff --git a/contents/convolutions/2d/2d.md b/contents/convolutions/2d/2d.md index 7fc21aeb6..dc52157fe 100644 --- a/contents/convolutions/2d/2d.md +++ b/contents/convolutions/2d/2d.md @@ -20,9 +20,9 @@ In code, a two-dimensional convolution might look like this: {% method %} {% sample lang="jl" %} -[import:4-28, lang:"julia"](../code/julia/2d_convolution.jl) +[import:4-28, lang:"julia"](code/julia/2d_convolution.jl) {% sample lang="py" %} -[import:5-19, lang:"python"](../code/python/2d_convolution.py) +[import:5-19, lang:"python"](code/python/2d_convolution.py) {% endmethod %} This is very similar to what we have shown in previous sections; however, it essentially requires four iterable dimensions because we need to iterate through each axis of the output domain *and* the filter. @@ -49,9 +49,9 @@ At this stage, it is important to write some code, so we will generate a simple {% method %} {% sample lang="jl" %} -[import:30-47, lang:"julia"](../code/julia/2d_convolution.jl) +[import:30-47, lang:"julia"](code/julia/2d_convolution.jl) {% sample lang="py" %} -[import:21-33, lang:"python"](../code/python/2d_convolution.py) +[import:21-33, lang:"python"](code/python/2d_convolution.py) {% endmethod %} Though it is entirely possible to create a Gaussian kernel whose standard deviation is independent on the kernel size, we have decided to enforce a relation between the two in this chapter. @@ -138,9 +138,9 @@ In code, the Sobel operator involves first finding the operators in $$x$$ and $$ {% method %} {% sample lang="jl" %} -[import:49-63, lang:"julia"](../code/julia/2d_convolution.jl) +[import:49-63, lang:"julia"](code/julia/2d_convolution.jl) {% sample lang="py" %} -[import:36-52, lang:"python"](../code/python/2d_convolution.py) +[import:36-52, lang:"python"](code/python/2d_convolution.py) {% endmethod %} With that, I believe we are at a good place to stop discussions on two-dimensional convolutions. @@ -153,9 +153,9 @@ We have also added code to create the Gaussian kernel and Sobel operator and app {% method %} {% sample lang="jl" %} -[import, lang:"julia"](../code/julia/2d_convolution.jl) +[import, lang:"julia"](code/julia/2d_convolution.jl) {% sample lang="py" %} -[import, lang:"python"](../code/python/2d_convolution.py) +[import, lang:"python"](code/python/2d_convolution.py) {% endmethod %} @@ -54,6 +64,11 @@ MathJax.Hub.Queue(["Typeset",MathJax.Hub]); The code examples are licensed under the MIT license (found in [LICENSE.md](https://github.com/algorithm-archivists/algorithm-archive/blob/master/LICENSE.md)). +##### Images/Graphics + +- The image "[Cyclic](../res/cyclic.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + + ##### Text The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). diff --git a/contents/euclidean_algorithm/code/scheme/euclidalg.ss b/contents/euclidean_algorithm/code/scheme/euclidalg.ss index 3d891ba73..959ebdeca 100644 --- a/contents/euclidean_algorithm/code/scheme/euclidalg.ss +++ b/contents/euclidean_algorithm/code/scheme/euclidalg.ss @@ -12,4 +12,5 @@ (euclid-mod b (modulo a b)))) (display (euclid-mod (* 64 67) (* 64 81))) (newline) -(display (euclid-sub (* 64 12) (* 64 27))) (newline) +(display (euclid-sub (* 128 12) (* 128 77))) (newline) + diff --git a/contents/flood_fill/code/coconut/flood_fill.coco b/contents/flood_fill/code/coconut/flood_fill.coco new file mode 100644 index 000000000..f2580a608 --- /dev/null +++ b/contents/flood_fill/code/coconut/flood_fill.coco @@ -0,0 +1,113 @@ +from collections import deque +import numpy as np + + +data Point(x, y): + def __add__(self, other is Point) = Point(self.x + other.x, self.y + other.y) + + +# This function is necessary, because negative indices wrap around the +# array in Coconut. +def inbounds(canvas_shape, location is Point) = + min(location) >= 0 and location.x < canvas_shape[0] and location.y < canvas_shape[1] + + +def find_neighbours(canvas, location is Point, old_value): + possible_neighbours = ((Point(0, 1), Point(1, 0), Point(0, -1), Point(-1, 0)) + |> map$(location.__add__)) + + yield from possible_neighbours |> filter$(x -> (inbounds(canvas.shape, x) + and canvas[x] == old_value)) + + +def stack_fill(canvas, location is Point, old_value, new_value): + if new_value == old_value or not inbounds(canvas.shape, location): + return + + stack = [location] + + while stack: + current_location = stack.pop() + if canvas[current_location] == old_value: + canvas[current_location] = new_value + stack.extend(find_neighbours(canvas, current_location, old_value)) + + +def queue_fill(canvas, location is Point, old_value, new_value): + if new_value == old_value or not inbounds(canvas.shape, location): + return + + queue = deque() + queue.append(location) + + canvas[location] = new_value + + while queue: + current_location = queue.popleft() + for neighbour in find_neighbours(canvas, current_location, old_value): + canvas[neighbour] = new_value + queue.append(neighbour) + + +def recursive_fill(canvas, location is Point, old_value, new_value): + if new_value == old_value or not inbounds(canvas.shape, location): + return + + canvas[location] = new_value + # consume is important here, because otherwise, the recursive function is not called again + consume( + find_neighbours(canvas, location, old_value) + |> map$(recursive_fill$(canvas, ?, old_value, new_value)) + ) + + +def test_grid(initial_canvas, final_canvas, function): + canvas = initial_canvas.copy() # ensure the initial_canvas is unchanged + function(canvas) + return (canvas == final_canvas).all() + +def test(): + from collections import namedtuple + + TestResults = namedtuple('TestResults', 'passes failures') + pass_count = failure_count = 0 + + grid = np.zeros((5, 5)) + grid[2,:] = 1 + solution_grid = np.zeros((5, 5)) + solution_grid[:3,] = 1 + + starting_location = Point(0, 0) + + recursive_test_func = recursive_fill$(?, starting_location, 0, 1) + # The following is manual unit testing of the function + if test_grid(grid, solution_grid, recursive_test_func): + pass_count += 1 + print('.', end='') + else: + failure_count += 1 + print('F', end='') + + stack_test_func = stack_fill$(?, starting_location, 0, 1) + if test_grid(grid, solution_grid, stack_test_func): + print('.', end='') + pass_count += 1 + else: + print('F', end='') + failure_count += 1 + + queue_test_func = queue_fill$(?, starting_location, 0, 1) + if test_grid(grid, solution_grid, queue_test_func): + print('.', end='') + pass_count += 1 + else: + print('F', end='') + failure_count += 1 + + print() + print(TestResults(pass_count, failure_count)) + +if __name__ == '__main__': + # Testing setup + test() + diff --git a/contents/flood_fill/code/cpp/flood_fill.cpp b/contents/flood_fill/code/cpp/flood_fill.cpp new file mode 100644 index 000000000..918566809 --- /dev/null +++ b/contents/flood_fill/code/cpp/flood_fill.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include + +using CartesianIndex = std::array; + +auto inbounds(CartesianIndex size, CartesianIndex loc) { + if (loc[0] < 0 || loc[1] < 0) { + return false; + } else if (loc[0] >= size[0] || loc[1] >= size[1]) { + return false; + } + return true; +} + +auto find_neighbors( + std::vector> const& grid, + CartesianIndex loc, + float old_value, + float /* new_value */) { + + const std::vector possible_neighbors{ + {loc[0], loc[1] + 1}, + {loc[0] + 1, loc[1]}, + {loc[0], loc[1] - 1}, + {loc[0] - 1, loc[1]}}; + + std::vector neighbors; + + for (auto const& possible_neighbor : possible_neighbors) { + const auto size = CartesianIndex{ + static_cast(grid[0].size()), static_cast(grid.size())}; + const auto x = static_cast(possible_neighbor[0]); + const auto y = static_cast(possible_neighbor[1]); + if (inbounds(size, possible_neighbor) && grid[x][y] == old_value) { + neighbors.push_back(possible_neighbor); + } + } + + return neighbors; +} + +void recursive_fill( + std::vector>& grid, + CartesianIndex loc, + float old_value, + float new_value) { + if (old_value == new_value) { + return; + } + + const auto x = static_cast(loc[0]); + const auto y = static_cast(loc[1]); + + grid[x][y] = new_value; + + const auto possible_neighbors = find_neighbors(grid, loc, old_value, new_value); + for (auto const& possible_neighbor : possible_neighbors) { + recursive_fill(grid, possible_neighbor, old_value, new_value); + } +} + +void queue_fill( + std::vector>& grid, + CartesianIndex loc, + float old_value, + float new_value) { + if (old_value == new_value) { + return; + } + + auto q = std::queue{}; + q.push(loc); + const auto x = static_cast(loc[0]); + const auto y = static_cast(loc[1]); + grid[x][y] = new_value; + + while (q.size() > 0) { + const auto current_loc = q.front(); + q.pop(); + const auto possible_neighbors = + find_neighbors(grid, current_loc, old_value, new_value); + for (auto const& neighbor : possible_neighbors) { + const auto neighbor_x = static_cast(neighbor[0]); + const auto neighbor_y = static_cast(neighbor[1]); + grid[neighbor_x][neighbor_y] = new_value; + q.push(neighbor); + } + } +} + +void stack_fill( + std::vector>& grid, + CartesianIndex loc, + float old_value, + float new_value) { + if (old_value == new_value) { + return; + } + + auto s = std::stack{}; + s.push(loc); + + while (s.size() > 0) { + const auto current_loc = s.top(); + s.pop(); + + const auto x = static_cast(current_loc[0]); + const auto y = static_cast(current_loc[1]); + + if (grid[x][y] == old_value) { + grid[x][y] = new_value; + const auto possible_neighbors = + find_neighbors(grid, current_loc, old_value, new_value); + for (auto const& neighbor : possible_neighbors) { + s.push(neighbor); + } + } + } +} + +int main() { + + const std::vector> grid{ + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}}; + + const std::vector> solution_grid{ + {1, 1, 1, 0, 0}, + {1, 1, 1, 0, 0}, + {1, 1, 1, 0, 0}, + {1, 1, 1, 0, 0}, + {1, 1, 1, 0, 0}}; + + const CartesianIndex start_loc{1, 1}; + + auto test_grid = grid; + recursive_fill(test_grid, start_loc, 0.0, 1.0); + assert(test_grid == solution_grid); + + test_grid = grid; + queue_fill(test_grid, start_loc, 0.0, 1.0); + assert(test_grid == solution_grid); + + test_grid = grid; + stack_fill(test_grid, start_loc, 0.0, 1.0); + assert(test_grid == solution_grid); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 87ec2b7a7..4c7e5936e 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -90,8 +90,12 @@ In code, this might look like this: [import:23-41, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:28-46, lang:"c"](code/c/flood_fill.c) +{% sample lang="cpp" %} +[import:19-44, lang:"cpp"](code/cpp/flood_fill.cpp) {% sample lang="py" %} [import:10-25, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:15-19, lang="coconut"](code/coconut/flood_fill.coco) {% endmethod %} @@ -108,8 +112,12 @@ In code, it might look like this: [import:92-104, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:174-189, lang:"c"](code/c/flood_fill.c) +{% sample lang="cpp" %} +[import:46-64, lang:"cpp"](code/cpp/flood_fill.cpp) {% sample lang="py" %} [import:55-63, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:54-63, lang:"coconut"](code/coconut/flood_fill.coco) {% endmethod %} The above code continues recursing through available neighbors as long as neighbors exist, and this should work so long as we are adding the correct set of neighbors. @@ -121,8 +129,12 @@ Additionally, it is possible to do the same type of traversal by managing a stac [import:43-63, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:79-102, lang:"c"](code/c/flood_fill.c) +{% sample lang="cpp" %} +[import:95-123, lang:"cpp"](code/cpp/flood_fill.cpp) {% sample lang="py" %} [import:27-36, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:23-34, lang:"coconut"](code/coconut/flood_fill.coco) {% endmethod %} This is ultimately the same method of traversal as before; however, because we are managing our own data structure, there are a few distinct differences: @@ -162,8 +174,12 @@ The code would look something like this: [import:66-90, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:149-172, lang:"c"](code/c/flood_fill.c) +{% sample lang="cpp" %} +[import:66-93, lang:"cpp"](code/cpp/flood_fill.cpp) {% sample lang="py" %} [import:38-53, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:37-51, lang:"coconut"](code/coconut/flood_fill.coco) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. @@ -242,8 +258,12 @@ After, we will fill in the left-hand side of the array to be all ones by choosin [import, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import, lang:"c"](code/c/flood_fill.c) +{% sample lang="cpp" %} +[import, lang:"cpp"](code/cpp/flood_fill.cpp) {% sample lang="py" %} [import:, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import, lang="coconut"](code/coconut/flood_fill.coco) {% endmethod %} diff --git a/contents/huffman_encoding/code/coconut/huffman.coco b/contents/huffman_encoding/code/coconut/huffman.coco new file mode 100644 index 000000000..640112167 --- /dev/null +++ b/contents/huffman_encoding/code/coconut/huffman.coco @@ -0,0 +1,119 @@ +from collections import Counter, deque +from bisect import bisect + +class Tree + +data Empty() from Tree +data Leaf(char, n is int) from Tree: + def __str__(self): + return f'Leaf({self.char}, {self.n})' + + __repr__ = __str__ + +data Node(left is Tree, right is Tree) from Tree: + def __str__(self): + return f'Node({str(self.left)}, {str(self.right)})' + __repr__ = __str__ + +def weight(Tree()) = 0 +addpattern def weight(Leaf(char, n)) = n +addpattern def weight(Node(left, right)) = weight(left) + weight(right) + +def build_huffman_tree(message): + + # get sorted list of character and frequency pairs + frequencies = Counter(message) + trees = frequencies.most_common() |> map$(t -> Leaf(*t)) |> reversed |> deque + + if not trees: + return Empty() + + # while there is more than one tree + while len(trees) > 1: + + # pop off the two trees of least weight from the trees list + tree_left = trees.popleft() + tree_right = trees.popleft() + + # combine the nodes and add back to the nodes list + new_tree = Node(tree_left, tree_right) + + # find the first tree that has a weight smaller than new_weight + # and returns its index in the list. + # If no such tree can be found, use len(trees) instead to append + index = bisect(trees |> map$(weight) |> list, weight(new_tree)) + + # insert the new tree there + trees.insert(index, new_tree) + + huffman_tree = trees[0] + return huffman_tree + + +def build_codebook(Empty(), code='') = [] +addpattern def build_codebook(Leaf(char, n), code='') = [(char, code)] +addpattern def build_codebook(Node(left, right), code='') = + build_codebook(left, code+'0') + build_codebook(right, code+'1') + +def huffman_encode(codebook, message): + + if len(codebook) == 1: + return '0' * len(message) + + # build a char -> code dictionary + forward_dict = dict(codebook) + + return ''.join(message |> map$(forward_dict[])) + +def huffman_decode(codebook, encoded_message): + + decoded_message = [] + key = '' + + if not codebook: + return '' + elif len(codebook) == 1: + return codebook[0][0] * len(encoded_message) + + # build a code -> char dictionary + inverse_dict = dict((v, k) for k, v in codebook) + + # for each bit in the encoding + # if the bit is in the dictionary, replace the bit with the paired + # character else look at the bit and the following bits together + # until a match occurs move to the next bit not yet looked at. + if encoded_message == '': + return inverse_dict[''] + + for bit in encoded_message: + key += bit + if key in inverse_dict: + decoded_message.append(inverse_dict[key]) + key = '' + + return ''.join(decoded_message) + + +if __name__ == '__main__': + # test example + message = 'bibbity_bobbity' + tree = build_huffman_tree(message) + codebook = build_codebook(tree) + encoded_message = huffman_encode(codebook, message) + decoded_message = huffman_decode(codebook, encoded_message) + + print('message:', message) + print('huffman tree:', tree) + print('codebook:', codebook) + print('encoded message:', encoded_message) + print('decoded message:', decoded_message) + + # prints the following: + # + # message: bibbity_bobbity + # huffman_tree: Node(Leaf(b, 6), Node(Node(Leaf(y, 2), Leaf(t, 2)), + # Node(Node(Leaf(o, 1), Leaf(_, 1)), Leaf(i, 3)))) + # codebook: [('b', '0'), ('y', '100'), ('t', '101'), + # ('o', '1100'), ('_', '1101'), ('i', '111')] + # encoded_message: 01110011110110011010110000111101100 + # decoded_message: bibbity_bobbity diff --git a/contents/huffman_encoding/code/julia/huffman.jl b/contents/huffman_encoding/code/julia/huffman.jl index 593d4b4f8..e01fd1dfd 100644 --- a/contents/huffman_encoding/code/julia/huffman.jl +++ b/contents/huffman_encoding/code/julia/huffman.jl @@ -1,3 +1,5 @@ +using Test + # This is for the PriorityQueue using DataStructures @@ -13,8 +15,6 @@ struct Branch end const Node = Union{Leaf, Branch} -isbranch(branch::Branch) = true -isbranch(other::T) where {T} = false function codebook_recurse!(leaf::Leaf, code::String, dict::Dict{Char,String}) @@ -33,7 +33,11 @@ end # This outputs encoding Dict to be used for encoding function create_codebook(n::Node) codebook = Dict{Char,String}() - codebook_recurse!(n, "", codebook) + if isa(n, Leaf) + codebook[n.key]="0" + else + codebook_recurse!(n, "", codebook) + end return codebook end @@ -85,14 +89,19 @@ function decode(huffman_tree::Node, bitstring::String) current = huffman_tree final_string = "" for i in bitstring - if (i == '1') - current = current.left + if isa(huffman_tree, Branch) + if (i == '1') + current = current.left + else + current = current.right + end + + if (!isa(current, Branch)) + final_string *= string(current.key) + current = huffman_tree + end else - current = current.right - end - if (!isbranch(current)) - final_string = final_string * string(current.key) - current = huffman_tree + final_string *= string(huffman_tree.key) end end @@ -102,11 +111,13 @@ end function two_pass_huffman(phrase::String) huffman_tree = create_tree(phrase) codebook = create_codebook(huffman_tree) - println(codebook) bitstring = encode(codebook, phrase) final_string = decode(huffman_tree, bitstring) - println(bitstring) - println(final_string) + return final_string end -two_pass_huffman("bibbity bobbity") +@testset "b-string tests" begin + @test two_pass_huffman("b") == "b" + @test two_pass_huffman("bbbbbbbb") == "bbbbbbbb" + @test two_pass_huffman("bibbity bobbity") == "bibbity bobbity" +end diff --git a/contents/huffman_encoding/huffman_encoding.md b/contents/huffman_encoding/huffman_encoding.md index f51505b32..44bc1e783 100644 --- a/contents/huffman_encoding/huffman_encoding.md +++ b/contents/huffman_encoding/huffman_encoding.md @@ -98,6 +98,8 @@ The code snippet was taken from this [scratch project](https://scratch.mit.edu/p

+{% sample lang="coco" %} +[import, lang:"coconut"](code/coconut/huffman.coco) {% endmethod %}