C++Guns – RoboBlog blogging the bot

02.03.2019

C++ Guns: A Sorted Data Type

Filed under: Allgemein — Tags: — Thomas @ 18:03

Mit dem C++ Typsystem ist es möglich Informationen und Algorithmen in das Programm zu codieren, die schon vor der Compilezeit fest stehen.

Wie würdet ihr folgendes Problem lösen:
Ein Algorithmus, zum Beispiel die binäre Suche, benötigt einen bereits sortieren Datensatz. Den Datensatz zu sortieren und danach die Suche anzuwenden ist trivial. Aber in einem anderen Teil des Programms muss danach noch einmal die binäre Suche angewendet werden. Nur kann dieser andere Programm Teil nicht annehmen, dass der übergebene Datensatz sortiert ist. Noch einmal die Sortierung angewandt kostet viel Laufzeit. Zu testen ob die Daten bereit sortiert sind, ist immer noch ein linearer Algorithmus.
Der Programmierer könnte auch eine bool Variable mit geben, die aussagt ob die Daten bereits sortiert sind. Das ist immerhin nur ein konstanter Mehraufwand. Der Datensatz muss stark mit dieser Statusvariable gekoppelt sein, damit keine Inkonsistenz entsteht. Also kommen beide zusammen in einem neuen Datentyp. Wenn man den sortierten Datensatz noch read-only macht, muss man kein Aufwand treiben festzustellen, ob der Datensatz durch ein Schreibzugriff nun nicht mehr sortiert ist. Damit ist auch die Statusvariable konstant.

Jetzt passiert etwas besonderes. Die Statusvariable wird von einer Funktion erzeugt und hat immer den selben Wert. Nämlich true, für "sortiert". Damit ist es möglich, diese Idee auch zur Compilezeit auszuführen und einen neuen Datentype Sorted einzuführen, der nur von einer sort() Funktion erzeugt werden kann und IMMER sortierte Daten enthält. Die bool Variable fällt komplett weg. Ihre Semantik ist im neuen Datentyp kodiert.

Wie sieht diese Idee in der Praxis aus? Es braucht eine sorted() Funktion welche die Daten sortiert und einen Sorted Typ zurück gibt. Damit, bei Bedarf, keine unnötigen Kopien erstellt werden, ist es auch möglich mit moveOwnership() die Daten in den Sorted Typ zu verschieben.

Jeder Algorithmus welcher sortiere Daten benötigt, soll nur Datensätze von Typ Sorted akzeptieren. Damit ist zur Compilezeit sichergestellt, dass nie aus Versehen unsortierte Daten übergeben werden und es wird nur einmal der Datensatz sortiert. Und alles kommt dank statischer Typisierung mit zero-overhead.

std::vector<int> vec {3,2,1};
// sort and move ownership to Sorted
Sorted sortedVec = sorted(acpl::moveOwnership(vec));

// can be used with the strong typed binary_search function
binary_search(sortedVec, valueToSearch);

Der Type Sorted implementiert nur ein read-only Interface und ist ein Wrapper um einen beliebigen Container. Der Konstruktor ist privat, damit nur die spezielle sorted Funktion diesen Typen erstellen kann.

auto nope = Sorted<std::vector<int>>{vec}; // error: ‘Sorted<Container>::Sorted(Container&&) is private within this context

Für bestehende Algorithmen können leicht Wrapper geschrieben werden. Wie beispielsweise für die binäre Suche:

template<typename Container, typename T>
bool binary_search(const Sorted<Container>& data, const T& value) {
    return std::binary_search(data.begin(), data.end(), value);
}

Die Implementierung der sorted() Funktion ist relativ einfach, wenn die Sache mit Universal Referenz geklärt ist

template<typename Container>
Sorted<std::remove_reference_t<Container>> sorted(Container&& data) {
    // variable data is a univeral reference. pattern: "deducted type&&" so we use forward
    std::remove_reference_t<Container> sorted(std::forward<Container>(data));
    std::sort(sorted.begin(), sorted.end());
    // variable sorted is not a universal reference so we use move
    return {std::move(sorted)};
}

Natürlich gibt es auch einen overload für die Funktion isSorted() diese gibt trivialierweise immer true zurück.

template<typename Container>
bool isSorted(const Sorted<Container>&) {
    return true;
}

Und die Implementierung des Typ Sorted selbst

template<typename Container>
// requires Sequence container
class Sorted {


    Sorted(Container&& data)
        : c(std::forward<Container>(data))
    {  }

public:
    using value_type = typename Container::value_type;
    using const_iterator = typename Container::const_iterator;
    using const_reference = typename Container::const_reference;

    auto size() const {
        return std::size(c);
    }

    const_iterator begin() const {
        return c.begin();
    }

    const_iterator end() const {
        return c.end();
    }

    const_reference at(const auto& i) const {
        return c.at(i);
    }

    const_reference operator[](const auto& i) const {
        return c[i];
    }

    const_reference front() const {
        return c.front();
    }

    template<typename C>
    friend Sorted<std::remove_reference_t<C>> sorted(C&& data);

    const Container c;
};

22.02.2019

C++ Guns: rvalue referenz, forwarding referenz, universal referenz WTF?

Filed under: Allgemein — Tags: — Thomas @ 18:02

Die gute Nachricht: Normale Menschen müssen das alles nicht verstehen. Auch Anwendungs- Programmierer und library designer brauchen die Details nicht zu wissen. Ihr könnt meine Regeln durchlesen, daran halten und dann STOP.
Die Details sind für Leute, die sich einem 1.5h Vortrag über && anhören können.

Streng genommen wird der Begriff "universal referenz" nicht im C++ std benutzt. Dies ist ein Begriff von Scott Meyers, der ihn benutzt um die Ding begreiflich zu machen. Ich stimme mit ihm überein und benutze in diesem Text nicht den Begriff "forwared referenz" sondern "universal referenz". Viele sagen C++ sei so schon kompliziert genug. Man muss es ja nicht noch schlimmer machen. Weiterhin werde ich Abkürzungen benutzen. rvalue referenz Rref. universal referenz Uref.

Nun, Rref und Uref tauchen (auch) bei ownership transfer auf. Das ist IMO die einzige Stelle bei der man das braucht. Beispiele hierzu in C+ Guns: ACPL ownership transfer.
Nun sehen sich Rref und Uref von der Syntax her ziemlich gleich. Beide haben einen Typ und dann das doppele Kaufmanns-Und, Et-Zeichen &&. Nicht zu verwechseln mit dem logischen AND Operator. Aber für diesen nutzt ihr selbstverständlich and und nicht &&.
Es kommt nun auf den Typ an. Wird er vom Compiler hergeleitet (deduction) und ist es GENAU (im Sinne von EXACT) die Form T&&, dann ist es Uref, sonst Rref. Der Typ T ist ein Template Parameter und sein Name natürlich frei wählbar. Aber sobald ein const hinzugefügt wird, verschwindet das Uref und das Rref taucht auf.

Im ersten Beispiel (in dem verlinkten Artikel) ist somit Rref am Werk. Im zweiten Beispiel Uref. Und im dritten garnichts, weil man von const Variablen nicht moven kann!

Funktionen überladen mit Rref und Uref ist so eine Sache. Uref ist eine _*UNIVERSAL*_!!! Referenz. Sie behandelt ALLES. Und damit ist exakt alles gemeint. Nun ist es technisch (Stand 02.2019 pre C++20) möglich, eine Funktion zu überladen, so dass die eine Rref und die andere Uref akzeptiert. Aber das macht keinen Sinn und ist meiner Meinung nach ein Compilezeit Fehler.

Funktionen mit Rref und "normalen" Referenzen zu überladen ist natürlich möglich, gewollt und hilfreich. Möchte man mit den einen doch etwas anderes machen als mit den anderen (move vs. read only). Und da sich Rref und Uref ähnlich sehen, viel Spass!

Jetzt wird aus jedem Rref und Uref wieder ein ganz normales lvalue. Weil die haben einen Variablennamen und eine Adresse. Zwangsläufig, sonst könnte man den Variablennamen beim programmieren ja nicht eintippen -_-
Also muss wann immer eine Rref oder Uref Variable weiter benutzt wird, ein std::move oder std::forward kommen. Für Rref, also move Operationen nutzen wir std::move. Für Uref, also move oder copy Operationen nutzen wird std::forward. Benutzen wir keines von beiden, ist das meiner Meinung nach ein Compilezeit Fehler. Jemand sollte ein GCC PLugin schreiben.

Regeln:

  • Uref und Rref braucht man (unter anderem) für ownership transfer. Ihr werde es kaum brauchen.
  • Uref genau wenn T ein template parameter und exact die Form T&& vorliegt, ohne const ö.ä, sonst Rref.
  • T&& verhält sich in der Praxis NICHT wie Rref. Es kommt auf T an.
  • Wenn immer T&& im Code auftaucht: Gelber ALARM! Braucht man das wirklich? Wird überall std::move() oder std::forward() angewandt?
  • Von const Variablen kann man nicht moven.
  • Funktionen überladen mit Uref -> Fehler
  • Rref und normale Referenzen überladen -> Gut
  • Rref -> std::move
  • Uref -> std::forward
  • Soll ownership tranfer UND eine Kopie möglich sein, macht die Kopie explizit im Code deutlich:
  •  // v ist KEINE uref, da kein template Parameter
    auto func(std::vector && v) 
    { }
    
    std::vector v;
    func(acpl::moveOwnership(v)); // ownership transfer
    func(std::vector(v)); // Kopie
    

    STOP

    Für weitere Informationen zu auto&&, decltype(x), decltype((x)) und sonstige Schmerzen schau euch den Vortrag von Scott Meyers an. Er ist wirklich gut!
    C++ and Beyond 2012: Scott Meyers - Universal References in C++11

    C+ Guns: ACPL ownership transfer

    Filed under: Allgemein — Tags: — Thomas @ 18:02

    acpl::moveOwnership bietet im Gegensatz zu std::move den Vorteil, dass ein move-from const Variablen einen Fehler zur Compilezeit erzeugt.

    Zwei Beispiele sollen ownership transfer verdeutlichen.
    Daten in ein Type moven und dort speichern.

    #include <core/util/functional.hpp>
    
    struct Node {};
    struct Edge {};
    struct Graph {
        std::vector<Node> nodes;
        std::vector<Edge> edges;
    
        Graph(std::vector<Node>&& nodes, std::vector<Edge>&& edges) 
        : nodes(acpl::moveOwnership(nodes)), edges(acpl::moveOwnership(edges))
        {
        }
    };
    
    void func() {
        std::vector<Node> nodes;
        std::vector<Edge> edges;
        // ... fill nodes and edges
        // graph is the new ownership of nodes and data
        Graph graph(acpl::moveOwnership(nodes), acpl::moveOwnership(edges));
        assert(nodes.empty());
        assert(edges.empty());
    }
    

    Daten entweder kopieren oder durch eine Funktion moven.

    #include <core/util/functional.hpp>
    
    template<typename Container>
    inline std::vector<int> copyOrMove(Container&& x) {
        Container data2(std::forward<Container>(x));
        return data2;
    }
    
    void func() {
        std::vector<int> x {1,2,3};
        std::vector<int> copy = copyOrMove(x);
        assert(not x.empty());    
        std::vector<int> moved = copyOrMove(acpl::moveOwnership(x));
        assert(x.empty());    
    }
    

    Move von const Variablen ist nicht möglich, da die Variablen geändert werden würden, was nicht möglich ist, weil sie const sind.

    void func() {
        const std::vector<int> x {1,2,3};
        std::vector<int> moved = acpl::moveOwnership(x); // error: static assertion failed: Move from const variables dosent make sense
    }
    

    15.02.2019

    C++ Guns: class template specialization with concepts aka C++20 std::ranges::value_type

    Filed under: Allgemein — Tags: — Thomas @ 20:02

    You can create a constrain for a class. And another constrain for the same class. Isn't this crazy stuff?

    With this we can build something like an identity function for types, like std::type_identity, for value_type. It's something like the C++17 non-member functions size() and empty(). Why must value_type be a member? It can be a non-member trait.

    It turns out, there is already this functionality in C++ 20 ranges TS in std::experimental::ranges::value_type. It's really hard to catch up... but implemented it for you. And users may specialize value_type I also put my part for arithmetic types here.

    This is part of ACPL functional.hpp file.

    /// Primary template is an empty struct.
    /// \note this is in C++20 ranges now except for the arithmetic types overload 
    /// https://en.cppreference.com/w/cpp/experimental/ranges/iterator/value_type
    template<class I>
    struct value_type { };
    
    /// Specialization for pointers.
    /// If T is an object type, provides a member type type equal to std::remove_cv_t<T>.
    ///  Otherwise, there is no member type.
    template<class T>
    struct value_type<T*> {
      using type = std::remove_cv_t<T>;
    };
    
    /// Specialization for array types.
    template<class I>
      requires(std::is_array<I>::value)
    struct value_type<I> : value_type<std::decay_t<I>> { };
    
    /// Specialization for const-qualified types.
    template<class T>
    struct value_type<const T> : value_type<std::decay_t<T>> { };
    
    /// Specialization for types that define a public and accessible member type value_type.
    /// If T::value_type is an object type, provides a member type type equal to T::value_type.
    /// Otherwise, there is no member type.
    /// \todo requires requires
    template<class T>
      requires requires{ typename T::value_type; }
    struct value_type<T> {
      using type = typename T::value_type;
    };
    
    /// Specialization for types that define a public and accessible member type element_type.
    /// (e.g., std::shared_ptr).
    /// If T::element_type is an object type, provides a member type type equal to std::remove_cv_t<typename T::element_type>.
    /// Otherwise, there is no member type.
    template<class T>
      requires requires{ typename T::element_type; }
    struct value_type<T> {
        using type = typename T::element_type;
    };
    
    /// Helper alias template
    template<class T>
    using value_type_t = typename value_type<T>::type;
    
    /// ACPL specialization for arithmetic types
    template<class T>
      requires(std::is_arithmetic_v<T>)
    struct value_type<T> {
        using type = T;
    };
    
        // Specialization for pointers.
        static_assert(std::is_same_v<int, acpl::value_type_t<int*>>);
        // Specialization for array types.
        static_assert(std::is_same_v<int, acpl::value_type_t<int[]>>);
        // Specialization for const-qualified types.
        static_assert(std::is_same_v<int, acpl::value_type_t<const int*>>);
        // Specialization for types that define a public and accessible member type value_type.
        static_assert(std::is_same_v<int, acpl::value_type_t<std::array<int,1>>>);
        // Specialization for types that define a public and accessible member type element_type.
        static_assert(std::is_same_v<int, acpl::value_type_t<std::unique_ptr<int>>>);
    
        // ACPL specialization for arithmetic types
        static_assert(std::is_same_v<int, acpl::value_type_t<int>>);
        static_assert(std::is_same_v<int, acpl::value_type_t<const int>>);
    

    For the record, this is my first try:

    template<typename T>
    struct value_type {
    };
    
    template<typename T>
    requires(std::is_scalar_v<T>)
    struct value_type<T> {
        using type = T;
    };
    
    template<typename T>
    requires(not std::is_scalar_v<T>)
    struct value_type<T> {
        using type = typename T::value_type;
    };
    
    template<typename T>
    using value_type_t = typename value_type<T>::type;
    
    static_assert(std::is_same_v<int, value_type_t<int>> );
    static_assert(std::is_same_v<int, value_type_t<std::vector<int>>> );
    

    03.02.2019

    C++ Guns: ACPL proudly presents: Histogram2D

    Filed under: Allgemein — Tags: — Thomas @ 21:02

    See also
    ACPL: Histogram1D
    ACPL: BinaryHeap

    Create even 2D Histogram in a fast and intuitive way.
    Define TWO access functions, Axis, Range, Titles, get stochastic moments for every dimension. Enjoy the 2D ASCII art output.
    See source code and more code examples at ACPL Histogram 2D

    std::vector<std::pair<double,double>> values;
    auto temp     = [](const std::pair<double, double>& p){ return p.first; };
    auto pressure = [](const std::pair<double, double>& p){ return p.second; };
    
    HistogramAxis Xaxis("temp [degree]",       FixBinWidth{0.5}, DataIntervalClosed{-4.0,7.0});
    HistogramAxis Yaxis("air pressure [hPa]",  FixBinSize{20},   makeDataIntervalClosed(values, pressure));
    Histogram2D hist = Histogram2D("City temperature VS air pressure",  Xaxis, temp, Yaxis, pressure, values);
    std::cout << hist;
    
    City temperature VS air pressure
    Axis: temp [degree]
    Number of bins: 22 bin width 0.5
    Under/ Overflow count: 4 14
               min    max   avg    var    std 
    Histogram -4 7 3.56703 6.43098 2.53594
    Data      -4.5 7.6 3.62 7.27079 2.69644  
    Axis: air pressure [hPa]
    Number of bins: 20 bin width 1.2
    Under/ Overflow count: 0 0
               min    max   avg    var    std 
    Histogram 1014 1038 1030.45 40.2583 6.34494
    Data      1014 1038 1030.45 40.2583 6.34494
          1038 |
             a |
             i |
             r |
               |
             p |
             r |
             e |
             s |
             s |
             u |
             r |
             e |
               |
             [ |
             h |
             P |
             a |
             ] |
          1014 |
               +-------------------------
                -4                 7
                  temp [degree]
    

    C++ Guns: ACPL proudly presents: Histogram1D

    Filed under: Allgemein — Tags: — Thomas @ 21:02

    See also
    ACPL: Histogram2D
    ACPL: BinaryHeap

    Create 1D Histogram in a fast and intuitive way.
    Define an Axis, Range, Title, get stochastic moments. Enjoy the ASCII art output.
    See source code and more code examples at ACPL Histogram 1D

    std::vector<double> tempValues;
    HistogramAxis axis("temp [degree]", FixBinSize{50}, makeDataIntervalClosed(tempValues));
    Histogram hist = Histogram("City temperature", axis, tempValues);
    cout << hist;
    
    City temperature
    Axis: temp [degree]
    Number of bins: 50 bin width 0.242
    Under/ Overflow count: 0 0
    min max avg var std
    Histogram -4.5 7.6 3.62 7.27079 2.69644
    Data -4.5 7.6 3.62 7.27079 2.69644
    

    30.01.2019

    C++ Guns: C++20 Aggregates can no longer declare constructors (schon wieder anders...)

    Filed under: Allgemein — Tags: — Thomas @ 21:01

    Ab GCC 9.
    P1008 Prohibit aggregates with user-declared constructors
    Initialization in modern C++ - Timur Doumler - Meeting C++ 2018

    Das Thema ist ziemlich lang und eigentlich total überflüssig. Wenn nicht immer die ganzen Altlasten da wären ... but we have to deal with it. Es gibt genügend Artikel im Internet wie gut oder schlecht die Initialisierung in C++ kaputt ist. Für einen guten Überblick, und wie man versucht es zu reparieren, empfehle ich obiges verlinktes PDF.

    struct A {
        A() = delete;
    };
    
    auto func() {
        // ok does not compile
        // A a; 
        // compiles in c++17 but not in 20
        A a{};      // error: use of deleted function 'A::A()'
    }
    

    27.01.2019

    C++ Guns: Stop using std::endl start using acpl::newline and std::clog std::cerr

    Filed under: Allgemein — Tags: — Thomas @ 13:01

    Schaut euch mal C++ Weekly - Ep7 Stop Using std::endl an. Eigentlich hat er ja recht, std::endl wird nicht gebraucht. Problem damit (wie immer), es macht mehr als man vermutet. Denn "end line" suggeriert nicht, dass nun ein Zeilenumbruch kommt und auch kein flush(). Das ganze hat bestimmt wieder historische Gründe.

    Jedenfalls gibt es neben std::cout auch noch std::cerr und std::clog. Die letzten beiden schreiben nicht nach stdout sondern nach stderr. Wobei std::cerr ungebuffert schreibt. Das macht auch Sinn. Fehlermeldungen und Debug Ausgaben müssen sofort raus geschrieben werden, bevor das Programm abstürzt. Die Log Meldungen können erste gepuffert werden um die Performance nicht zu beeinflussen.

    Durch den richtigen Einsatz von std:cout std::cerr und std::clog wird kein std::endl mehr benötigt. Wenn es denn ein normaler Weg gäbe einen Zeilenumbruch zu schreiben...

    Wie lernen denn Programmieranfänger in C++ ein Zeilenumbruch zu schreiben? Natürlich mit std::endl ! Wenn die Leute schon bereit sind, will man sie ja nicht gleich wieder mit ASCII Kontrollzeichen verschrecken. Dabei wäre eine newline() Funktion genau wie die endl() so einfach zu implementieren und standardisieren.

    Bsp:

    template<typename CharT, typename Traits>
    inline std::basic_ostream<CharT, Traits>& newline(std::basic_ostream<CharT, Traits>& os) {
        os << '\n';
        return os;
    }
    
    std::cout << "Test" << newline;
    

    24.01.2019

    Installing gcc, g++ and gfortran 8 from source

    Filed under: Allgemein — Tags: , , , — Thomas @ 22:01

    The procedure is quite the same as for gcc 4.8 as you can see in my older post Installing gcc, g++ and gfortran 4.8 from source

    Read the manual.
    Download, unpack, switch dir, download, unpack, link.

    $ wget ftp://ftp.gwdg.de/pub/misc/gcc/snapshots/LATEST-8/gcc-8-20190118.tar.xz
    $ xz -d gcc-8-20190118.tar.xz
    $ tar xf gcc-8-20190118.tar
    $ gcc-8-20190118/
    $ wget ftp://ftp.gmplib.org/pub/gmp-6.1.2/gmp-6.1.2.tar.bz2
    $ tar xjf gmp-6.1.2.tar.bz2
    $ ln -s gmp-6.1.2 gmp
    $ wget https://www.mpfr.org/mpfr-current/mpfr-4.0.1.tar.bz2
    $ tar xjf mpfr-4.0.1.tar.bz2
    $ ln -s mpfr-4.0.1 mpfr
    $ wget https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz
    $ tar xzf mpc-1.1.0.tar.gz
    $ ln -s mpc-1.1.0 mpc

    Make a build directory, start configure, start make, wait
    $ cd ..
    $ mkdir build-gcc-8-20190118
    $ cd build-gcc-8-20190118/
    $ ../gcc-8-20190118/configure --prefix=/opt/gcc-8 --enable-threads --enable-languages=c,c++,fortran
    $ make -j 32
    # make install

    23.01.2019

    C++ Guns: ACPL: Conway's Game of Life

    Filed under: Allgemein — Tags: — Thomas @ 18:01

    In den Heise Kommentaren (Ja ich schau da ab und zu rein) gab es letztens ein Vergleich zum Spaß von Conway's Game of Life einmal in C++ und GO. Natürlich war GO furchtbar langsam. Aber der C++ Code sah auch nicht sehr sinnvoll aus. Es ist an der Zeit es mit meinem ACPL Framework und den 2D Array auch mal zu probieren.

    Hier die Laufzeiten [sec] ganz grob zum vergleichen. Intel i7-2640M GCC 8.1 go1.6.2 linux/amd64

    GO: 1.9
    g++ -O1: 0.61
    g++ -O2: 0.60
    g++ -O3: 0.30

    Da bleibt nichts mehr übrig. Mal das Spielfeld und die Anzahl der Iterationen jeweils um Faktor 10 vergrößern. 1000x1000 mit 10000 Iterationen.

    GO: 32m6
    g++ -O1: 498.3 (8m18)
    g++ -O2: 493.3 (8m13)
    g++ -O3: 191.5 (3m11)

    Jetzt sieht man erst, das GO total unbenutzbar ist. Mit C++ ist man noch mit einer Kaffeepause dabei.
    Bei meiner Variante habe ich schon einmal die zwei 3er Schleifen von Hand ausgerollt. Das ist ja trivial. Ansonsten als Container nur acpl::ArrayND benutzt mit 2 Dimensionen und bool Werte. Folgende Zeiten haben sich ergeben:

    g++ -O1: 366.6 (6m6)
    g++ -O2: 341.1 (5m41)
    g++ -O3: 210.0 (3m30)

    Die Kaffeepause wird immer kürzer. Sehr interessant, dass bei Optimierung O3 die Laufzeit schlechter wurde. Dies betrifft das Schleifenausrollen. Das kann der Compiler wohl besser als ich. So soll es auch sein!
    Anbei der Quellcode. Ist ja wirklich nicht viel. Zu beachten ist, dass der rechte Index der schnell laufende Index ist. Gedanklich läuft man die Zeile entlang und spring dann runter zur nächsten Zeile. Daher werden die Koordinaten in y,x übergeben. In dieser Reihenfolge. Das könnte man in der nächsten ACPL Version auch weg optimieren und ein NDArray-Index benutzen.

    Update 15.09.2019
    Schöneren Code hochgeladen

    #include <iostream>
    #include <chrono>
    #include <thread>
    
    #include <core/util/ArrayND.hpp>
    
    using std::cout;
    using namespace std::chrono_literals;
    
    // Torusartiger Spielraum. Wrap around.
    struct Spielfeld : acpl::Array2D<bool>
    {
        using Base = acpl::Array2D<bool>;
        using Base::Base;
    
        // If the x or y coordinates are outside the field boundaries they are wrapped
        // toroidally. For instance, an x value of -1 is treated as width-1.
        bool isAlive(int y, int x) const {
            x += nCols();
            x %= nCols();
            y += nRows();
            y %= nRows();
            return operator()(y,x);
        }
    
        bool next(int y, int x) const {
            // Count the adjacent cells that are alive.
            int alive = 0;
            if(isAlive(y-1,x-1)) alive++;
            if(isAlive(y-1,  x)) alive++;
            if(isAlive(y-1,x+1)) alive++;
    
            if(isAlive(y, x-1)) alive++;
            if(isAlive(y, x+1)) alive++;
    
            if(isAlive(y+1, x-1)) alive++;
            if(isAlive(y+1, x))   alive++;
            if(isAlive(y+1, x+1)) alive++;
    
            // Return next state according to the game rules:
            //   exactly 3 neighbors: on,
            //   exactly 2 neighbors: maintain current state,
            //   otherwise: off.
            return alive==3 or (alive==2 and isAlive(y,x));
        }
    };
    
    struct ConwayGameOfLife {
        Spielfeld a,b;
    
        ConwayGameOfLife(unsigned int w, unsigned int h)
            : a({h,w}), b({h,w})
        {
            for(unsigned int i=0; i<(w*h/4); i++) {
                a(rand()%h, rand()%w) = true;
            }
        }
    
        void step() {
            for(unsigned int y=0;y < a.nRows(); y++) {
                for(unsigned int x=0; x < a.nCols(); x++) {
                    b(y,x) = a.next(y,x);
                }
            }
    
            std::swap(a,b);
        }
    
        auto width() const {
            return a.nCols();
        }
    
        auto height() const {
            return a.nRows();
        }
    };
    
    std::ostream& operator<<(std::ostream& s, const ConwayGameOfLife& game) {
        for(unsigned int x=0; x < game.width()+2; x++) {
            s << '-';
        }
        s << '\n';
        for(unsigned int y=0;y < game.height(); y++) {
            s << "|";
            for(unsigned int x=0; x < game.width(); x++) {
                if(game.a.isAlive(y,x)) {
                    s << '*';
                } else {
                    s << ' ';
                }
            }
            s << "|\n";
        }
    
        for(unsigned int x=0; x < game.width()+2; x++) {
            s << '-';
        }
        s << "\n";
    
        return s;
    }
    
    int main() {
        srand(2);
        auto start = std::chrono::high_resolution_clock::now();
    
        int maxIter = 220;
        ConwayGameOfLife game(60,30);
        for(int i=0; i < maxIter; i++) {
            game.step();
    
            system("clear");
            std::cout << i << " / " << maxIter << "\n";
            std::cout << game;
            std::this_thread::sleep_for(0.2s);
        }
    
        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> elapsed = end-start;
        std::cout << elapsed.count() << " seconds\n";
    
        //std::cout << game << "\n\n\n";
    }
    
    « Newer PostsOlder Posts »

    Powered by WordPress