diff --git a/tutorial/CoreTutorial/Any.cpp b/tutorial/CoreTutorial/Any.cpp new file mode 100644 index 000000000..6ccce0c58 --- /dev/null +++ b/tutorial/CoreTutorial/Any.cpp @@ -0,0 +1,26 @@ +#include "Tutorial.h" + +void AnyTutorial() +{ + /// .`Any` + + /// `Any` is a container that can contain none or one element of %any% type. `Any::Is` + /// method matches exact type ignoring class hierarchies (unlike `One::Is`). You can use + /// `Get` to retrieve a reference to the instance stored: + + for(int pass = 0; pass < 2; pass++) { + Any x; + if(pass) + x.Create() = "Hello!"; + else + x.Create() = Blue(); + + if(x.Is()) + LOG("Any is now String: " << x.Get()); + + if(x.Is()) + LOG("Any is now Color: " << x.Get()); + } + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Array.cpp b/tutorial/CoreTutorial/Array.cpp new file mode 100644 index 000000000..9aaf57bbf --- /dev/null +++ b/tutorial/CoreTutorial/Array.cpp @@ -0,0 +1,25 @@ +#include "Tutorial.h" + +void ArrayTutorial() +{ + /// .Array flavor + + /// If elements are not `Moveable` and therefore cannot be stored in `Vector` flavor, they + /// can still be stored in `Array` flavor. Another reason for using Array is the need for + /// referencing elements - Array flavor never invalidates references or pointers to them. + /// Finally, if sizeof(T) is large (say more than 100-200 bytes), using Array might be better + /// from performance perspective. + + /// Example of elements that cannot be stored in Vector flavor are standard library objects like + /// `std::string` (because obviously, standard library knows nothing about U++ Moveable + /// concept): + + Array as; + for(int i = 0; i < 4; i++) + as.Add("Test"); + + for(auto s : as) + DUMP(s.c_str()); + + /// +} diff --git a/tutorial/CoreTutorial/AsString.cpp b/tutorial/CoreTutorial/AsString.cpp new file mode 100644 index 000000000..6a39f70db --- /dev/null +++ b/tutorial/CoreTutorial/AsString.cpp @@ -0,0 +1,82 @@ +#include "Tutorial.h" + +void AsStringTutorial() +{ + /// .`AsString`, `ToString` and `operator<<` + + /// U++ Core provides simple yet effective standard schema for converting values to default + /// textual form. System is based on the combination of template functions (following code + /// is part of U++ library): + +#if 0 +namespace Upp { + template + inline String AsString(const T& x) + { + return x.ToString(); + } + + template + inline Stream& operator<<(Stream& s, const T& x) + { + s << AsString(x); + return s; + } + + template + inline String& operator<<(String& s, const T& x) + { + s.Cat(AsString(x)); + return s; + } +}; +#endif + + /// Client types have to either define `String ToString` method or specialize `AsString` + /// template in `Upp` namespace. Such types can be appended to Streams or Strings using + /// `operator<<`. Of course, U++ value types and primitive types have required items + /// predefined by U++: + + FileOut fout(ConfigFile("test.txt")); + String sout; + + fout << 1.23 << ' ' << GetSysDate() << ' ' << GetSysTime(); + sout << 1.23 << ' ' << GetSysDate() << ' ' << GetSysTime(); + + fout.Close(); + + DUMP(LoadFile(ConfigFile("test.txt"))); + DUMP(sout); + + /// Getting client types involved into this schema is not too difficult, all you need to do + /// is to add `ToString` method: + + struct BinFoo { + int x; + + String ToString() const { return FormatIntBase(x, 2); } + }; + + BinFoo bf; + bf.x = 30; + + sout.Clear(); + sout << bf; + DUMP(sout); + + /// If you cannot add `ToString`, you can still specialize template in Upp namespace: + + struct RomanFoo { + int x; + + RomanFoo(int x) : x(x) {} + }; + +#if 0 + namespace Upp { + template <> String Upp::AsString(const RomanFoo& a) { return FormatIntRoman(a.x); } + }; +#endif + + /// +} diff --git a/tutorial/CoreTutorial/Bidirectional.cpp b/tutorial/CoreTutorial/Bidirectional.cpp new file mode 100644 index 000000000..73ebd47bc --- /dev/null +++ b/tutorial/CoreTutorial/Bidirectional.cpp @@ -0,0 +1,54 @@ +#include "Tutorial.h" + +void Bidirectional() +{ + /// .Bidirectional containers + + /// `Vector` and `Array` containers allow fast adding and removing elements at the end of + /// sequence. Sometimes, same is needed at begin of sequence too (usually to support FIFO + /// queues). `BiVector` and `BiArray` are optimal for this scenario: + + BiVector n; + n.AddHead(1); + n.AddTail(2); + n.AddHead(3); + n.AddTail(4); + DUMP(n); + + /// + + n.DropHead(); + DUMP(n); + + /// + + n.DropTail(); + DUMP(n); + + /// + + struct Val { + virtual String ToString() const = 0; + virtual ~Val() {} + }; + + struct Number : Val { + int n; + virtual String ToString() const { return AsString(n); } + }; + + struct Text : Val { + String s; + virtual String ToString() const { return s; } + }; + + BiArray num; + num.CreateHead().n = 3; + num.CreateTail().s = "Hello"; + num.CreateHead().s = "World"; + num.CreateTail().n = 2; + + DUMP(num); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/ClientTypes.cpp b/tutorial/CoreTutorial/ClientTypes.cpp new file mode 100644 index 000000000..cf1173615 --- /dev/null +++ b/tutorial/CoreTutorial/ClientTypes.cpp @@ -0,0 +1,59 @@ +#include "Tutorial.h" + +void ContainerClientTypes() +{ + /// .Client types in U++ containers + + /// So far we were using int as type of elements. In order to store client defined types + /// into the `Vector` (and the Vector ^topic://Core/src/Overview$en-us:flavor^) the type + /// must satisfy ^topic://Core/src/Moveable$en-us:moveable^ requirement - in short, it must + /// not contain back-pointers nor virtual methods. Type must be marked as %moveable% in + /// order to define interface contract using `Moveable` + /// ^https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:CRTP idiom^: + + struct Distribution : Moveable { + String text; + Vector data; + + String ToString() const { return text + ": " + AsString(data); } + }; + + + /// Now to add `Distribution` elements you cannot use `Vector::Add(const T&)`, because it requires + /// elements to have default deep-copy constructor - and `Distribution does not have one, as + /// `Vector` has default pick-constructor, so Distribution itself has pick-constructor. + /// It would no be a good idea either, because deep-copy would involve expensive copying of + /// inner Vector. + + /// Instead, Add without parameters has to be used - it default constructs (that is cheap) + /// element in Vector and returns reference to it: + + Vector dist; + for(int n = 5; n <= 10; n++) { + Distribution& d = dist.Add(); + d.text << "Test " << n; + for(int i = 0; i < 10000; i++) + d.data.At(Random(n), 0)++; + } + + DUMPC(dist); + + /// Another possibility is to use `Vector::Add(T&&)` method, which uses pick-constructor + /// instead of deep-copy constructor. E.g. `Distribution` elements might be generated by + /// some function: + +#if 0 + Distribution CreateDist(int n); + + /// and code for adding such elements to Vector then looks like: + + for(n = 5; n <= 10; n++) + dist.Add(CreateDist(n)); + + /// alternatively, you can use default-constructed variant too + + dist.Add() = CreateDist(); +#endif + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/CombineHash.cpp b/tutorial/CoreTutorial/CombineHash.cpp new file mode 100644 index 000000000..3f62c68c4 --- /dev/null +++ b/tutorial/CoreTutorial/CombineHash.cpp @@ -0,0 +1,35 @@ +#include "Tutorial.h" + +void CombineHashTutorial() +{ + /// .CombineHash + + /// To simplify providing of high quality hash codes for composite types, U++ provides + /// `CombineHash` utility class. This class uses `GetHashValue` function to gather hash + /// codes of all values and combines them to provide final hash value for composite type: + + struct Foo { + String a; + int b; + + unsigned GetHashValue() const { return CombineHash(a, b); } + }; + + /// Note that `GetHashValue` is defined as function template that calls `GetHashValue` + /// method of its argument, therefore defining `GetHashValue` method defines `GetHashValue` + /// function too: + + Foo x; + x.a = "world"; + x.b = 22; + + DUMP(GetHashValue(x)); + + /// + + x.a << '!'; + + DUMP(GetHashValue(x)); + + /// +} diff --git a/tutorial/CoreTutorial/Compare.cpp b/tutorial/CoreTutorial/Compare.cpp new file mode 100644 index 000000000..122ca16e0 --- /dev/null +++ b/tutorial/CoreTutorial/Compare.cpp @@ -0,0 +1,134 @@ +#include "Tutorial.h" + +void ComparableTutorial() +{ + /// .SgnCompare and CombineCompare + + /// Traditional approach of C language of representing comparison results was 3-state: + /// comparing a and b results in negative value (if a < b), zero (if a == b) or positive + /// value (a > b). In C++ standard library, comparisons are usually represented with `bool` + /// predicates. + + /// However, with `bool` predicate it becomes somewhat more difficult to provide + /// comparisons for composite types: + + struct Foo { + String a; + int b; + int c; + + // we want to order Foo instances by a first, then b, then c + + bool operator<(const Foo& x) const { + return a < x.a ? true + : a == x.a ? b < x.b ? true + : b == x.b ? false + : c < x.c + : false; + } + }; + + /// U++ provides standard function `SgnCompare`, which returns negative value/zero/positive + /// in "C style": + + int a = 1; + int b = 2; + + DUMP(SgnCompare(a, b)); + DUMP(SgnCompare(b, a)); + DUMP(SgnCompare(a, a)); + + /// Default implementation of `SgnCompare` calls `Compare` method of value: + + struct MyClass { + int val; + + int Compare(const MyClass& x) const { return SgnCompare(val, x.val); } + }; + + /// `SgnCompare` is now defined for `MyClass`: + + MyClass u, v; + u.val = 1; + v.val = 2; + + DUMP(SgnCompare(u, v)); + DUMP(SgnCompare(v, u)); + DUMP(SgnCompare(v, v)); + + /// Now getting back to `Foo`, with `SgnCompare` `operator<` becomes much less difficult: + + struct Foo2 { + String a; + int b; + int c; + + bool operator<(const Foo2& x) const { + int q = SgnCompare(a, x.a); + if(q) return q < 0; + q = SgnCompare(b, x.b); + if(q) return q < 0; + q = SgnCompare(c, x.c); + return q < 0; + } + }; + + /// Alternatively, it is possible to define just `Compare` method and use `Comparable` + /// ^https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:CRTP idiom^ to + /// define all relation operators: + + struct Foo3 : Comparable { + String a; + int b; + int c; + + int Compare(const Foo3& x) const { + int q = SgnCompare(a, x.a); + if(q) return q; + q = SgnCompare(b, x.b); + if(q) return q; + return SgnCompare(c, x.c); + } + }; + + Foo3 m, n; + m.a = "A"; + m.b = 1; + m.c = 2; + n.a = "A"; + n.b = 1; + n.c = 3; + + DUMP(m < n); + DUMP(m == n); + DUMP(m != n); + DUMP(SgnCompare(m, n)); + + /// While the content of `Compare` method is trivial, it can be further simplified using + /// `CombineCompare` helper class: + + struct Foo4 : Comparable { + String a; + int b; + int c; + + int Compare(const Foo4& x) const { + return CombineCompare(a, x.a)(b, x.b)(c, x.c); + } + }; + + Foo4 o, p; + o.a = "A"; + o.b = 1; + o.c = 2; + p.a = "A"; + p.b = 1; + p.c = 3; + + DUMP(o < p); + DUMP(o == p); + DUMP(o != p); + DUMP(SgnCompare(o, p)); + + /// +} diff --git a/tutorial/CoreTutorial/CoreTutorial.upp b/tutorial/CoreTutorial/CoreTutorial.upp new file mode 100644 index 000000000..0f89cf439 --- /dev/null +++ b/tutorial/CoreTutorial/CoreTutorial.upp @@ -0,0 +1,45 @@ +uses + Core, + RichEdit; + +file + Tutorial.h, + Tutorial.cpp, + Core readonly separator, + ToDo.cpp, + Range.cpp, + Logging.cpp, + String.cpp, + StringBuffer.cpp, + WString.cpp, + DateTime.cpp, + AsString.cpp, + Value.cpp, + Null.cpp, + Value2.cpp, + CombineHash.cpp, + Compare.cpp, + Vector.cpp, + Vector2.cpp, + Transfer.cpp, + ClientTypes.cpp, + Array.cpp, + PolyArray.cpp, + Bidirectional.cpp, + Index.cpp, + IndexClient.cpp, + Map.cpp, + One.cpp, + Any.cpp, + InVector.cpp, + SortedMap.cpp, + Tuple.cpp, + Ranges.cpp, + Sort.cpp, + Function.cpp, + tutorial2.cpp, + help.qtf; + +mainconfig + "" = "GUI"; + diff --git a/tutorial/CoreTutorial/DateTime.cpp b/tutorial/CoreTutorial/DateTime.cpp new file mode 100644 index 000000000..2c1900f1e --- /dev/null +++ b/tutorial/CoreTutorial/DateTime.cpp @@ -0,0 +1,119 @@ +#include "Tutorial.h" + +void DateTime() +{ + /// .Date and Time + + /// To represent date and time, U++ provides `Date` and `Time` concrete types. + + Date date = GetSysDate(); + + DUMP(date); + + /// All data members of `Date` structure are public: + + DUMP((int)date.year); // we need to cast to int because some date members + DUMP((int)date.month); // are of unsigned character type which would log + DUMP((int)date.day); // as characters + + /// Dates can be compared: + + DUMP(date > Date(2000, 1, 1)); + + /// Adding a number to `Date` adds a number of days to it, incrementing/decrementing goes to + /// the next/previous day: + + DUMP(date + 1); + DUMP(--date); + DUMP(++date); + + /// Subtraction of dates yields a number of days between them: + + DUMP(date - Date(2000, 1, 1)); + + /// There are several `Date` and calendar related functions: + + DUMP(IsLeapYear(2012)); + DUMP(IsLeapYear(2014)); + DUMP(IsLeapYear(2015)); + DUMP(IsLeapYear(2016)); + DUMP(IsLeapYear(2017)); + + /// + + DUMP(GetDaysOfMonth(2, 2015)); + DUMP(GetDaysOfMonth(2, 2016)); + + /// + + DUMP(DayOfWeek(date)); // 0 is Sunday + + /// + + DUMP(LastDayOfMonth(date)); + DUMP(FirstDayOfMonth(date)); + DUMP(LastDayOfYear(date)); + DUMP(FirstDayOfYear(date)); + DUMP(DayOfYear(date)); // number of days since Jan-1 + 1 + DUMP(DayOfYear(Date(2016, 1, 1))); + + /// + + DUMP(AddMonths(date, 20)); + DUMP(GetMonths(date, date + 100)); // number of 'whole months' between two dates + DUMP(GetMonthsP(date, date + 100)); // number of 'whole or partial months' between two dates + DUMP(AddYears(date, 2)); + + /// + + DUMP(GetWeekDate(2015, 1)); + int year; + DUMP(GetWeek(Date(2016, 1, 1), year)); // first day of year can belong to previous year + DUMP(year); + + /// + + DUMP(EasterDay(2015)); + DUMP(EasterDay(2016)); + + /// U++ defines the beginning and the end of era, most algorithms can safely assume that as + /// minimal and maximal values `Date` can represent: + + DUMP(Date::Low()); + DUMP(Date::High()); + + /// Time is derived from `Date`, adding members to represent time: + + Time time = GetSysTime(); + DUMP(time); + DUMP((Date)time); + DUMP((int)time.hour); + DUMP((int)time.minute); + DUMP((int)time.second); + + /// Times can be compared: + + DUMP(time > Time(1970, 0, 0)); + + /// Warning: As `Time` is derived from the `Date`, most operations automatically convert + /// `Time` back to `Date`. You have to use `ToTime` conversion function to convert `Date` + /// to `Time`: + + DUMP(time > date); // time gets converted to Date... + DUMP(time > ToTime(date)); + + /// Like `Date`, `Time` supports add and subtract operations, but numbers represent seconds + /// (using `int64` datatype): + + DUMP(time + 1); + DUMP(time + 24 * 3600); + DUMP(time - date); // time converts to Date, so the result is in days + DUMP(time - ToTime(date)); // Time - Time is in seconds + + /// `Time` defines era limits too: + + DUMP(Time::Low()); + DUMP(Time::High()); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Function.cpp b/tutorial/CoreTutorial/Function.cpp new file mode 100644 index 000000000..ab0d807d6 --- /dev/null +++ b/tutorial/CoreTutorial/Function.cpp @@ -0,0 +1,90 @@ +#include "Tutorial.h" + +void FunctionTutorial() +{ + /// .Function + + /// U++ `Function` is quite similar to `std::function` - it is a function wrapper that can + /// store/copy/invoke any callable target. There are two important differences. + /// First, invoking empty `Function` is NOP, if `Function` has return type `T`, it returns + /// `T()`. Second, `Function` allows effective chaining of callable targets using + /// `operator<<`, if `Function` has return type, the return type of last callable appended + /// is used. + + /// Usually, the callable target is C++11 lambda: + + Function fn = [](int n) { LOG("Called A"); return 3 * n; }; + + LOG("About to call function"); + int n = fn(7); + DUMP(n); + + /// If you chain another lambda into `Function`, all are called, but the last one's return + /// value is used: + + fn << [](int n) { LOG("Called B"); return n * n; }; + LOG("About to call combined function"); + n = fn(7); + DUMP(n); + + /// Invoking empty lambda does nothing and returns default constructed return value. This + /// is quite useful for GUI classes, which have a lot of output events represented by + /// `Function` which are often unassigned to any action. + + fn.Clear(); + LOG("About to call empty function"); + n = fn(7); + DUMP(n); + + /// While using `Function` with lambda expression is the most common, you can use any + /// target that has corresponding `operator()` defined: + + struct Functor { + int operator()(int x) { LOG("Called Foo"); return x % 2; } + }; + + fn = Functor(); + LOG("About to call Functor"); + n = fn(7); + DUMP(n); + + /// As `Function` with `void` and `bool` return types are the most frequently used, U++ + /// defines template aliases `Event`: + + Event<> ev = [] { LOG("Event invoked"); }; + + ev(); + + /// and `Gate`: + + Gate gt = [](int x) { LOG("Gate invoked with " << x); return x < 10; }; + + bool b = gt(9); + DUMP(b); + b = gt(10); + DUMP(b); + + /// Using lambda to define calls to methods with more parameters can be verbose and + /// error-prone. The issue can be simplified by using `THISFN` macro: + + struct Foo { + void Test(int a, const String& b) { LOG("Foo::Test " << a << ", " << b); } + + typedef Foo CLASSNAME; // required for THISFN + + void Do() { + Event fn; + + fn = [=](int a, const String& b) { Test(a, b); }; + fn(1, "using lambda"); + + fn = THISFN(Test); // this is functionally equivalent, but less verbose + fn(2, "using THISFN"); + } + }; + + Foo f; + f.Do(); + + /// +} diff --git a/tutorial/CoreTutorial/InVector.cpp b/tutorial/CoreTutorial/InVector.cpp new file mode 100644 index 000000000..c58ab4fb2 --- /dev/null +++ b/tutorial/CoreTutorial/InVector.cpp @@ -0,0 +1,28 @@ +#include "Tutorial.h" + +void InVectorTutorial() +{ + /// .`InVector`, `InArray` + + /// `InVector` and `InArray` are container types quite similar to `Vector`/`Array`, but + /// they trade the speed of `operator[]` with the ability to insert or remove elements at + /// any position quickly. You can expect `operator[]` to be about 10 times slower than in + /// Vector (but that is still quite fast), while `Insert` at any position scales well up to + /// hundreds of megabytes of data (e.g. `InVector` containing 100M of String elements is + /// handled without problems). + + InVector v; + for(int i = 0; i < 1000000; i++) + v.Add(i); + v.Insert(0, -1); // This is fast + + /// While the interface of `InVector`/`InArray` is almost identical to `Vector`/`Array`, + /// `InVector`/`InArray` in addition implements `FindLowerBound`/`FindUpperBound` methods - + /// while normal generic range algorithms work, it is possible to provide + /// `InVector`/`InArray` specific optimizations that basically match the performace of + /// `Find*Bound` on simple `Vector`. + + DUMP(v.FindLowerBound(55)); + + /// +} diff --git a/tutorial/CoreTutorial/Index.cpp b/tutorial/CoreTutorial/Index.cpp new file mode 100644 index 000000000..202a8d8ba --- /dev/null +++ b/tutorial/CoreTutorial/Index.cpp @@ -0,0 +1,115 @@ +#include "Tutorial.h" + +void IndexTutorial() { + /// .`Index` + + /// `Index` is the the foundation of all U++ associative operations and is one of defining + /// features of U++. + + /// `Index` is a container very similar to the plain `Vector` (it is random access array of + /// elements with fast addition at the end) with one additional feature - it is able to fast + /// retrieve position of element with required value using `Find` method: + + Index ndx; + ndx.Add("alfa"); + ndx.Add("beta"); + ndx.Add("gamma"); + ndx.Add("delta"); + ndx.Add("kappa"); + + DUMP(ndx); + DUMP(ndx.Find("beta")); + + /// If element is not present in `Index`, `Find` returns a negative value: + + DUMP(ndx.Find("something")); + + /// Any element can be replaced using `Set` method: + + ndx.Set(1, "alfa"); + + DUMP(ndx); + + /// If there are more elements with the same value, they can be iterated using `FindNext` + /// method: + + int fi = ndx.Find("alfa"); + while(fi >= 0) { + DUMP(fi); + fi = ndx.FindNext(fi); + } + + /// `FindAdd` method retrieves position of element like `Find`, but if element is not + /// present in `Index`, it is added: + + DUMP(ndx.FindAdd("one")); + DUMP(ndx.FindAdd("two")); + DUMP(ndx.FindAdd("three")); + DUMP(ndx.FindAdd("two")); + DUMP(ndx.FindAdd("three")); + DUMP(ndx.FindAdd("one")); + + /// Removing elements from random access sequence tends to be expensive, that is why rather + /// than remove, `Index` supports `Unlink` and `UnlinkKey` operations, which retain the + /// element in `Index` but make it invisible for `Find` operation: + + ndx.Unlink(2); + ndx.UnlinkKey("kappa"); + + DUMP(ndx.Find(ndx[2])); + DUMP(ndx.Find("kappa")); + + /// You can test whether element at given position is unlinked using `IsUnlinked` method + + DUMP(ndx.IsUnlinked(1)); + DUMP(ndx.IsUnlinked(2)); + + /// Unlinked positions can be reused by `Put` method: + + ndx.Put("foo"); + + DUMP(ndx); + DUMP(ndx.Find("foo")); + + /// You can also remove all unlinked elements from `Index` using `Sweep` method: + + ndx.Sweep(); + + DUMP(ndx); + + /// Operations directly removing or inserting elements of Index are expensive, but + /// available too: + + ndx.Remove(1); + + DUMP(ndx); + + /// + + ndx.RemoveKey("two"); + + DUMP(ndx); + + /// + + ndx.Insert(0, "insert"); + + DUMP(ndx); + + /// PickKeys operation allows you to obtain Vector of elements of Index in low + /// constant time operation (while destroying source Index) + + Vector d = ndx.PickKeys(); + + DUMP(d); + + /// Pick-assigning `Vector` to `Index` is supported as well: + + d[0] = "test"; + + ndx = pick(d); + + DUMP(ndx); + + /// +} diff --git a/tutorial/CoreTutorial/IndexClient.cpp b/tutorial/CoreTutorial/IndexClient.cpp new file mode 100644 index 000000000..ce1eb8b97 --- /dev/null +++ b/tutorial/CoreTutorial/IndexClient.cpp @@ -0,0 +1,31 @@ +#include "Tutorial.h" + +void IndexClient() +{ + /// .Index and client types + + /// In order to store elements to `Index`, they type must be `Moveable`, have deep copy and + /// defined the `operator==` and a `GetHashValue` function or method to compute the hash + /// code. It is recommended to use `CombineHash` to combine hash values of types that + /// already provide `GetHashValue`: + + struct Person : Moveable { + String name; + String surname; + + unsigned GetHashValue() const { return CombineHash(name, surname); } + bool operator==(const Person& b) const { return name == b.name && surname == b.surname; } + + Person(String name, String surname) : name(name), surname(surname) {} + Person() {} + }; + + Index p; + p.Add(Person("John", "Smith")); + p.Add(Person("Paul", "Carpenter")); + p.Add(Person("Carl", "Engles")); + + DUMP(p.Find(Person("Paul", "Carpenter"))); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Logging.cpp b/tutorial/CoreTutorial/Logging.cpp new file mode 100644 index 000000000..2d309e794 --- /dev/null +++ b/tutorial/CoreTutorial/Logging.cpp @@ -0,0 +1,85 @@ +#include "Tutorial.h" + +void Logging() +{ + ///. Logging + + /// Logging is a useful technique to trace the flow of the code and examine results. In + /// this tutorial we will be using logging extensively, so let us start tutorial with the + /// explanation of logging. + + /// In debug mode and with default settings, macro `LOG` puts string into output log file. + /// Log file is placed into 'config-directory', which by default is .exe directory in Win32 + /// and ~/.upp/appname in POSIX. + + /// In TheIDE, you can access the log using 'Debug'/'View the log file Alt+L'. + + LOG("Hello world"); + + /// You can log values of various types, as long as they have `AsString` function defined + /// You can chain values in single `LOG` using `operator<<`: + + int x = 123; + LOG("Value of x is " << x); + + /// As it is very common to log a value of single variable, `DUMP` macro provides a useful + /// shortcut, creating a log line with the variable name and value: + + DUMP(x); + + /// To get the value in hexadecimal code, you can use `LOGHEX` / `DUMPHEX` + + DUMPHEX(x); + String h = "foo"; + DUMPHEX(h); + + /// To log the value of a container (or generic Range), you can either use normal + /// `LOG` / `DUMP`: + + Vector v = { 1, 2, 3 }; + + DUMP(v); + + /// or you can use DUMPC for multi-line output: + + DUMPC(v); + + /// For maps, use DUMPM: + + VectorMap map = { { 1, "one" }, { 2, "two" } }; + + DUMP(map); + + /// + + DUMPM(map); + + /// All normal `LOG`s are removed in release mode. If you need to log things in release mode, + /// you need to use `LOG`/`DUMP` variant with '`R`' prefix (`RLOG`, `RDUMP`, `RDUMPHEX`...): + + RLOG("This will be logged in release mode too!"); + + /// Sort of opposite situation is when adding temporary `LOG`s to the code for debugging. In + /// that case, '`D`' prefixed variants (`DLOG`, `DDUMP`, `DDUMPHEX`...) are handy - these cause + /// compile error in release mode, so will not get forgotten in the code past the release: + + DLOG("This would not compile in release mode."); + + /// The last flavor of `LOG` you can encounter while reading U++ sources is the one prefixed + /// with '`L`'. This one is not actually defined in U++ library and is just a convention. On + /// the start of file, there is usually something like: + + #define LLOG(x) // DLOG(x) + + /// and by uncommenting the body part, you can activate the logging in that particular file. + + /// While logging to .log file is default, there are various ways how to affect logging, + /// for example following line adjusts logging to output the log both to the console and + /// .log file: + +#if 0 + StdLogSetup(LOG_COUT|LOG_FILE); +#endif + + /// +} diff --git a/tutorial/CoreTutorial/Map.cpp b/tutorial/CoreTutorial/Map.cpp new file mode 100644 index 000000000..a96ad5926 --- /dev/null +++ b/tutorial/CoreTutorial/Map.cpp @@ -0,0 +1,138 @@ +#include "Tutorial.h" + +void Map() +{ + /// .`VectorMap`, `ArrayMap` + + /// `VectorMap` is nothing else than a simple composition of `Index` of keys and `Vector` + /// of values. You can use `Add` methods to put elements into the `VectorMap`: + + struct Person : Moveable { + String name; + String surname; + + String ToString() const { return String() << name << ' ' << surname; } + + Person(String name, String surname) : name(name), surname(surname) {} + Person() {} + }; + + VectorMap m; + + m.Add("1", Person("John", "Smith")); + m.Add("2", Person("Carl", "Engles")); + + Person& p = m.Add("3"); + p.name = "Paul"; + p.surname = "Carpenter"; + + DUMP(m); + + /// `VectorMap` provides read-only access to its `Index` of keys and read-write access to its + /// `Vector` of values: + + DUMP(m.GetKeys()); + DUMP(m.GetValues()); + + /// + + m.GetValues()[2].name = "Peter"; + + DUMP(m); + + /// You can use indices to iterate `VectorMap` contents: + + for(int i = 0; i < m.GetCount(); i++) + LOG(m.GetKey(i) << ": " << m[i]); + + /// Standard `begin` / `end` pair for `VectorMap` is the range of just values (internal Vector) + /// - it corresponds with `operator[]` returning values: + + for(const auto& p : m) + DUMP(p); + + /// To iterate through keys, you can use `begin`/`end` of internal `Index`: + + for(const auto& k : m.GetKeys()) + DUMP(p); + + /// Alternatively, it is possible to create 'projection range' of VectorMap that provides + /// convenient key/value iteration, using `operator~` (note that is also removes 'unliked' + /// items, see later): + + for(const auto& e : ~m) { + DUMP(e.key); + DUMP(e.value); + } + + /// You can use Find method to retrieve position of element with required key: + + DUMP(m.Find("2")); + + /// or Get method to retrieve corresponding value: + + DUMP(m.Get("2")); + + /// Passing key not present in `VectorMap` as `Get` parameter is undefined behavior (ASSERT + /// fails in debug mode), but there exists two parameter version of `Get` that returns second + /// parameter if the key is not found in VectorMap: + + DUMP(m.Get("33", Person("unknown", "person"))); + + /// As with `Index`, you can use `Unlink` to make elements invisible for Find operations: + + m.Unlink(1); + DUMP(m.Find("2")); + + /// `SetKey` changes the key of the element: + + m.SetKey(1, "33"); + DUMP(m.Get("33", Person("unknown", "person"))); + + /// If there are more elements with the same key in `VectorMap`, you can iterate them using + /// `FindNext` method: + + m.Add("33", Person("Peter", "Pan")); + + int q = m.Find("33"); + while(q >= 0) { + DUMP(m[q]); + q = m.FindNext(q); + } + + /// Unlinked positions can be 'reused' using Put method: + + m.UnlinkKey("33"); + m.Put("22", Person("Ali", "Baba")); + m.Put("44", Person("Ivan", "Wilks")); + + DUMP(m); + + /// `PickValues` / `PickIndex` / `PickKeys` / pick internal `Vector` / `Index` / `Vector` + /// of `Index`: + + Vector ps = m.PickValues(); + Vector ks = m.PickKeys(); + + DUMP(ps); + DUMP(ks); + DUMP(m); + + /// `VectorMap` pick constructor to create map by picking: + + ks[0] = "Changed key"; + + m = VectorMap(pick(ks), pick(ps)); + + DUMP(m); + + /// `ArrayMap` is composition of Index and Array, for cases where Array is better fit for + /// value type (e.g. they are polymorphic): + + ArrayMap am; + am.Create("key", "new", "person"); + + DUMP(am); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Null.cpp b/tutorial/CoreTutorial/Null.cpp new file mode 100644 index 000000000..76ae8723a --- /dev/null +++ b/tutorial/CoreTutorial/Null.cpp @@ -0,0 +1,44 @@ +#include "Tutorial.h" + +void NullTutorial() +{ + /// .`Null` + + /// U++ defines a special `Null` constant to represent an empty value. This constant is + /// convertible to many value types including primitive types `double`, `int` and `int64` + /// (defined as lowest number the type can represent). If type supports ordering (<, >), + /// all values of the type are greater than Null value. To test whether a value is empty, + /// use `IsNull` function. + + int x = Null; + int y = 120; + Date d = Null; + Date e = GetSysDate(); + + DUMP(x); + DUMP(y); + DUMP(d); + DUMP(e > d); + + /// Null is the only instance of `Nuller` type. Assigning `Null` to + /// primitive types is achieved by cast operators of `Nuller`, other types can do it using + /// constructor from `Nuller`. + + /// As a special case, if Value contains Null, it is convertible to any value type that can contain Null: + + Value v = x; // x is int + e = v; // e is Date, but v is Null, so Null is assigned to e + + DUMP(IsNull(e)); + + /// Function `Nvl` is U++ analog of well known SQL function coalesce (ifnull, Nvl), which + /// returns the first non-null argument (or `Null` if all are `Null`). + + int a = Null; + int b = 123; + int c = 1; + + DUMP(Nvl(a, b, c)); + + /// +} diff --git a/tutorial/CoreTutorial/One.cpp b/tutorial/CoreTutorial/One.cpp new file mode 100644 index 000000000..171348137 --- /dev/null +++ b/tutorial/CoreTutorial/One.cpp @@ -0,0 +1,52 @@ +#include "Tutorial.h" + +void OneTutorial() +{ + /// .`One` + + /// `One` is a container that can store none or one element of T or derived from T. It is + /// functionally quite similar to `std::unique_ptr`, but has some convenient features. + + struct Base { + virtual String Get() = 0; + virtual ~Base() {} + }; + + struct Derived1 : Base { + virtual String Get() { return "Derived1"; } + }; + + struct Derived2 : Base { + virtual String Get() { return "Derived2"; } + }; + + One s; + + /// `operator bool` of one returns true if it contains an element: + + DUMP((bool)s); + + /// + + s.Create(); + DUMP((bool)s); + DUMP(s->Get()); + + /// You can use `Is` to check if certain type is currently stored in `One`: + + DUMP(s.Is()); + DUMP(s.Is()); + DUMP(s.Is()); + + /// To get a pointer to the contained instance, use `operator~`: + + Base *b = ~s; + DUMP(b->Get()); + + /// Clear method removes the element from One: + + s.Clear(); + DUMP((bool)s); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/PolyArray.cpp b/tutorial/CoreTutorial/PolyArray.cpp new file mode 100644 index 000000000..e4ff7e05f --- /dev/null +++ b/tutorial/CoreTutorial/PolyArray.cpp @@ -0,0 +1,55 @@ +#include "Tutorial.h" + +void PolyArray() +{ + /// .Polymorphic `Array` + + /// `Array` can even be used for storing polymorphic elements: + + struct Number { + virtual double Get() const = 0; + String ToString() const { return AsString(Get()); } + virtual ~Number() {} + }; + + struct Integer : public Number { + int n; + virtual double Get() const { return n; } + }; + + struct Double : public Number { + double n; + virtual double Get() const { return n; } + }; + + /// To add such derived types to `Array`, you can best use in-place creation with `Create` + /// method: + + Array num; + num.Create().n = 15.5; + num.Create().n = 3; + + DUMP(num); + + /// Alternatively, you can use `Add(T *)` method and provide a pointer to the newly created + /// instance on the heap (`Add` returns a reference to the instance): + + Double *nd = new Double; + nd->n = 1.1; + num.Add(nd); + + DUMP(num); + + /// Array takes ownership of heap object and deletes it as appropriate. We recommend to use + /// this variant only if in-place creation with `Create` is not possible. + + /// It is OK do directly apply U++ algorithms on `Array` (the most stringent requirement of + /// any of basic algorithms is that there is `IterSwap` provided for container iterators + /// and that is specialized for `Array` iterators): + + Sort(num, [](const Number& a, const Number& b) { return a.Get() < b.Get(); }); + + DUMP(num); + + /// +} diff --git a/tutorial/CoreTutorial/Range.cpp b/tutorial/CoreTutorial/Range.cpp new file mode 100644 index 000000000..898e9f5a2 --- /dev/null +++ b/tutorial/CoreTutorial/Range.cpp @@ -0,0 +1,6 @@ +#include "Tutorial.h" + +void Ranges() +{ + +} diff --git a/tutorial/CoreTutorial/Ranges.cpp b/tutorial/CoreTutorial/Ranges.cpp new file mode 100644 index 000000000..541b5e7aa --- /dev/null +++ b/tutorial/CoreTutorial/Ranges.cpp @@ -0,0 +1,69 @@ +#include "Tutorial.h" + +void Range() +{ + /// .Range + + /// Unlike STL, which interface algorithms with data using `begin` / `end` pair, U++ algorithms + /// usually work on %Ranges%. Range is an object that has `begin` / `end` methods providing + /// random access to elements (all U++ containers are random access), `operator[]` and + /// `GetCount` method. + + /// Obviously, U++ containers are ranges: + + Vector x = { 1, 2, 3, 4, 5, 1, 2, 3, 4 }; + + DUMP(FindIndex(x, 2)); // FindIndex is a trivial algorithm that does linear search + + /// If you want the algorithm to run on part of container only, you can use `SubRange` + /// instance: + + DUMP(SubRange(x, 3, 6)); + DUMP(FindIndex(SubRange(x, 3, 6), 4)); + + /// As a side-job, SubRange can also be created from 'begin' / 'end' pair, thus e.g. + /// allowing algorithms to work on C arrays: + + int a[] = { 1, 22, 4, 2, 8 }; + + auto ar = SubRange(std::begin(a), std::end(a)); + + DUMP(ar); + + /// + + Sort(ar); + DUMP(ar); + + /// There are some macro aliases that make type management of ranges easier: + + DUMP(typeid(ValueTypeOf).name()); + DUMP(typeid(ValueTypeOf).name()); + DUMP(typeid(IteratorOf).name()); + DUMP(typeid(ConstIteratorOf).name()); + DUMP(typeid(SubRangeOf>).name()); + + /// While containers themselves and SubRange are the two most common range types, U++ has two + /// special ranges. `ConstRange` simply provides the range of single value: + + DUMP(ConstRange(1, 10)); + + /// `ViewRange` picks a source range and `Vector` of integer indices a provides a view of + /// source range through this `Vector`: + + Vector h{ 2, 4, 0 }; + + DUMP(ViewRange(x, clone(h))); + + /// + + Sort(ViewRange(x, clone(h))); + DUMP(ViewRange(x, clone(h))); + DUMP(x); + + /// Finally `FilterRange` creates a subrange of elements satisfying certain condition: + + DUMP(FilterRange(x, [](int x) { return x > 3; })); + + /// +} diff --git a/tutorial/CoreTutorial/Sort.cpp b/tutorial/CoreTutorial/Sort.cpp new file mode 100644 index 000000000..80c441713 --- /dev/null +++ b/tutorial/CoreTutorial/Sort.cpp @@ -0,0 +1,61 @@ +#include "Tutorial.h" + +void Sorting() +{ + /// .Sorting + + /// Unsurprisingly, `Sort` function sorts a range. You can specify sorting predicate, + /// default is `operator<`: + + Vector x { "1", "2", "10" }; + + Sort(x); + + DUMP(x); + + /// + + Sort(x, [](const String& a, const String& b) { return atoi(a) < atoi(b); }); + + DUMP(x); + + + /// `IndexSort` is sort variant that is able to sort two ranges (like `Vector` or `Array`) + /// of the same size, based on values in the first range: + + Vector a { 5, 10, 2, 9, 7, 3 }; + Vector b { "five", "ten", "two", "nine", "seven", "three" }; + + IndexSort(a, b); + + DUMP(a); + DUMP(b); + + /// + + IndexSort(b, a); + + DUMP(a); + DUMP(b); + + /// There are also `IndexSort2` and `IndexSort3` variants that sort 2 or 3 dependent ranges. + + /// Sometimes, instead of sorting items in the range, it is useful to know the order of + /// items as sorted, using `GetSortOrder`: + + Vector o = GetSortOrder(a); + + DUMP(o); + + /// Normal `Sort` is not stable - equal items can appear in sorted range in random order. + /// If maintaining original order of equal items is important, use `StableSort` variant + /// (with performance penalty): + + Vector t { Point(10, 10), Point(7, 1), Point(7, 2), Point(7, 3), Point(1, 0) }; + StableSort(t, [](const Point& a, const Point& b) { return a.x < b.x; }); + + DUMP(t); + + /// All sorting algorithms have they 'Stable' variant, so there is `StableIndexSort`, + /// `GetStableSortOrder` etc... +} diff --git a/tutorial/CoreTutorial/SortedMap.cpp b/tutorial/CoreTutorial/SortedMap.cpp new file mode 100644 index 000000000..7934d4d7f --- /dev/null +++ b/tutorial/CoreTutorial/SortedMap.cpp @@ -0,0 +1,37 @@ +#include "Tutorial.h" + +void SortedMap() +{ + /// .`SortedIndex`, `SortedVectorMap`, `SortedArrayMap` + + /// `SortedIndex` is similar to regular `Index`, but keeps its elements in sorted order + /// (sorting predicate is a template parameter, defaults to `StdLess`). Implementation is + /// using `InVector`, so it works fine even with very large number of elements (performance + /// is similar to tree based `std::set`). Unlike `Index`, `SortedIndex` provides + /// lower/upper bounds searches, so it allows range search. + + SortedIndex x; + x.Add(5); + x.Add(3); + x.Add(7); + x.Add(1); + + DUMPC(x); + DUMP(x.Find(3)); + DUMP(x.Find(3)); + DUMP(x.FindLowerBound(3)); + DUMP(x.FindUpperBound(6)); + + /// `SortedVectorMap` and `SortedArrayMap` are then `SortedIndex` based equivalents to + /// `VectorMap`/`ArrayMap`: + + SortedVectorMap m; + m.Add("zulu", 11); + m.Add("frank", 12); + m.Add("alfa", 13); + + DUMPM(m); + DUMP(m.Get("zulu")); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/String.cpp b/tutorial/CoreTutorial/String.cpp new file mode 100644 index 000000000..005f92530 --- /dev/null +++ b/tutorial/CoreTutorial/String.cpp @@ -0,0 +1,133 @@ +#include "Tutorial.h" + +void StringTutorial() +{ + + /// .String + + /// String is a value type useful for storing text or binary data. + + String a = "Hello"; + DUMP(a); + + /// You camn concatenate with another String or literal: + + a = a + " world"; + DUMP(a); + + /// Or single character or specified number of characters from another `String` or literal: + + a.Cat('!'); + DUMP(a); + + /// + + a.Cat("ABCDEFGHIJKLM", 3); + DUMP(a); + + /// `Clear` method empties the String: + + a.Clear(); + DUMP(a); + + /// You can use `operator<<` to append to existing `String`. Non-string values are + /// converted to appropriate `String` representation (using standard function `AsString`, + /// whose default template definition calls `ToString` method for value): + + for(int i = 0; i < 10; i++) + a << i << ", "; + + DUMP(a); + + /// Sometimes is is useful to use `operator<<` to produce a temporary `String` value (e.g. as + /// real argument to function call): + + String b = String() << "Number is " << 123 << "."; + + DUMP(b); + + + /// String provides many various methods for obtaining character count, inserting + /// characters into `String` or removing them: + + a = "0123456789"; + + DUMP(a.GetCount()); + + /// + + DUMP(a.GetLength()); // GetLength is a synonym of GetCount + + /// + + a.Insert(6, ""); + DUMP(a); + + /// + + a.Remove(2, 2); + DUMP(a); + + /// as well as searching and comparing methods: + + DUMP(a.Find('e')); + DUMP(a.ReverseFind('e')); + + /// + + DUMP(a.Find("ins")); + + /// + + DUMP(a.StartsWith("ABC")); + DUMP(a.StartsWith("01")); + DUMP(a.EndsWith("89")); + + /// You can get slice of String using Mid method; with single parameter it provides slice + /// to the end of String: + + DUMP(a.Mid(3, 3)); + DUMP(a.Mid(3)); + + /// You can also trim the length of String using Trim (this is faster than using any other + /// method): + + a.Trim(4); + DUMP(a); + + /// You can obtain integer values of individual characters using operator[]: + + DUMP(a[0]); + + /// or the value of first character using operator* (note that if `GetCount() == 0`, this + /// will return zero terminator): + + DUMP(*a); + + /// + + a.Clear(); + + DUMP(*a); + + /// `String` has implicit cast to zero terminated `const char *ptr` (only valid as long as + /// `String` does not mutate: + + a = "1234"; + const char *s = a; + while(*s) + LOG(*s++); + + /// `String` also has standard `begin` `end` methods, which e.g. allows for C++11 `for`: + + for(char ch : a) + LOG(ch); + + /// It is absolutely OK and common to use String for storing binary data, including zeroes: + + a.Cat(0); + + DUMPHEX(a); + + /// +} diff --git a/tutorial/CoreTutorial/StringBuffer.cpp b/tutorial/CoreTutorial/StringBuffer.cpp new file mode 100644 index 000000000..0de45a74b --- /dev/null +++ b/tutorial/CoreTutorial/StringBuffer.cpp @@ -0,0 +1,61 @@ +#include "Tutorial.h" + +void CApiFunction(char *c) +{ + strcpy(c, "Hello"); +} + +void StringBufferTutorial() +{ + + /// .StringBuffer + + /// If you need a direct write access to `String`'s C-string character buffer, you can use + /// complementary `StringBuffer` class. One of reasons to do so is when you have to deal + /// with some C-API functions that expects to write directly to `char *` and you would like + /// that result converted to the `String`: + +#if 0 + void CApiFunction(char *c) + { + strcpy(c, "Hello"); + } +#endif + + StringBuffer b; + b.SetLength(200); + CApiFunction(b); + b.Strlen(); + String x = b; + + DUMP(x); + + /// In this case, `SetLength` creates a C array of 200 characters. You can then call C-API + /// function. Later you set the real length using `Strlen` - this function performs strlen + /// of buffer and sets the length accordingly. Later you simply assign the `StringBuffer` + /// to `String`. Note that for performance reasons, this operation clears the + /// `StringBuffer` content (operation is fast and does not depend on the number of + /// characters). + + /// Another usage scenario of StringBuffer is altering existing String: + + b = x; + b[1] = 'a'; + x = b; + + DUMP(x); + + /// Similar to assigning StringBuffer to String, assigning String to StringBuffer clears + /// the source String. + + /// StringBuffer also provides appending operations: + + b = x; + b.Cat('!'); + x = b; + + DUMP(x); + + /// Note that sometimes when creating some String from a lot of single characters, using + /// StringBuffer for the operation is slightly faster then using String directly. +} diff --git a/tutorial/CoreTutorial/ToDo.cpp b/tutorial/CoreTutorial/ToDo.cpp new file mode 100644 index 000000000..598e34eae --- /dev/null +++ b/tutorial/CoreTutorial/ToDo.cpp @@ -0,0 +1,25 @@ +#include "Tutorial.h" + +#if 0 + return CombineCompare(a.NDX, b.NDX)(a.V_TEXT, b.V_TEXT)(a.V_NUMBER)(b.V_NUMBER); + + Vector id = clone(_id); + + SplitTo(ln.Mid(4), '@', path, ln); + + +CONSOLE_APP_MAIN +{ + Event&> h = [](const Vector& x) { DUMP(x); }; + + Event<> ev; + + { + Vector x = { 1, 2 }; + ev = [=, x = pick(x)] { h(x); }; + } + + ev(); +} +#endif + diff --git a/tutorial/CoreTutorial/Transfer.cpp b/tutorial/CoreTutorial/Transfer.cpp new file mode 100644 index 000000000..a0f3525fd --- /dev/null +++ b/tutorial/CoreTutorial/Transfer.cpp @@ -0,0 +1,65 @@ +#include "Tutorial.h" +#include + +void Transfer() +{ + /// .Transfer issues + + /// Often you need to pass content of one container to another of the same type. U++ + /// containers always support ^topic://Core/srcdoc/pick_$en-us:pick semantics^ (synonym of + /// std::move), and, depending on type stored, also might support + /// ^topic://Core/srcdoc/pick_$en-us:clone semantics^. When transferring the value, you + /// have to explicitly specify which one to use: + + Vector v{ 1, 2 }; + + DUMP(v); + + Vector v1 = pick(v); + + DUMP(v); + DUMP(v1); + + /// now source `Vector` `v` is empty, as elements were 'picked' to `v1`. + + /// If you really need to preserve value of source (and elements support deep copy + /// operation), you can use `clone`: + + v = clone(v1); + + DUMP(v); + DUMP(v1); + + /// The requirement of explicit `clone` has the advantage of avoiding unexpected deep + /// copies. For example: + + Vector> x; + x.Add() << 1 << 2 << 3; + +#if 0 + for(auto i : x) { LOG(i); } +#endif + + /// results in run-time error, whereas the equivalent code with `std::vector` compiles but + /// silently performs deep copy for each iteration: + +#if 0 + std::vector> sv; + sv.push_back({1, 2, 3}); + for(auto i : sv) // invokes std::vector copy constructor + for(auto j : i) + DUMP(j); +#endif + + /// That said, in certain cases it is simpler to have default copy instead of explicit + /// `clone`. You can easily achieve that using `WithDeepCopy` template: + + WithDeepCopy> v2; + + v2 = v; + + DUMP(v); + DUMP(v2); + + /// +} diff --git a/tutorial/CoreTutorial/Tuple.cpp b/tutorial/CoreTutorial/Tuple.cpp new file mode 100644 index 000000000..b581265d8 --- /dev/null +++ b/tutorial/CoreTutorial/Tuple.cpp @@ -0,0 +1,110 @@ +#include "Tutorial.h" + +void TupleTutorial() +{ + /// . Tuples + + /// Template class `Tuple` allows combining 2-4 values with + /// different types. These are principally similar to `std::tuple`, with some advantages. + /// Unlike `std::tuple`, individual elements are directly accessible as member variables + /// `a`..`d`, `Tuple` supports persistent storage patterns (`Serialize`, `Jsonize`, `Xmlize`), hash + /// code (`GetHashValue`), conversion to `String` and Value conversions. + + /// To create a `Tuple` value, you can use the `MakeTuple` function. + + Tuple x = MakeTuple(12, "hello", "world"); + + /// Individual values are accessible as members `a` .. `d`: + + DUMP(x.a); + DUMP(x.b); + DUMP(x.c); + + /// Or using `Get`: + + DUMP(x.Get<1>()); + DUMP(x.Get()); + + /// As long as all individual types have conversion to `String` (`AsString`), the tuple also + /// has such conversion and thus can e.g. be easily logged: + + DUMP(x); + + /// As long as individual types have defined `GetHashValue`, so does `Tuple`: + + DUMP(GetHashValue(x)); + + /// As long as individual types have defined `operator==`, `Tuple` has defined `operator==` + /// and `operator!=`: + + Tuple y = x; + DUMP(x == y); + DUMP(x != y); + y.a++; + DUMP(x == y); + DUMP(x != y); + + /// As long as all individual types have defined `SgnCompare`, + /// Tuple has SgnCompare, Compare method and operators <, <=, >, >=: + + DUMP(x.Compare(y)); + DUMP(SgnCompare(x, y)); + DUMP(x < y); + + /// GetCount returns the width of `Tuple`: + + DUMP(x.GetCount()); + + /// Elements that are directly convertible with `Value` can be 'Get'/'Set': + + for(int i = 0; i < x.GetCount(); i++) + DUMP(x.Get(i)); + + /// + + x.Set(1, "Hi"); + DUMP(x); + + /// As long as all individual types are convertible with `Value`, you can convert Tuple to + /// `ValueArray` and back: + + ValueArray va = x.GetArray(); + DUMP(va); + + va.Set(2, "Joe"); + x.SetArray(va); + + /// It is OK to assign `Tuple` to `Tuple` with different individual types, as long as types + /// are directly convertible: + + Tuple d = x; + DUMP(d); + + /// Tie can be used to assign tuple to l-values: + + int i; + String s1, s2; + + Tie(i, s1, s2) = x; + + DUMP(i); + DUMP(s1); + DUMP(s2); + + /// U++ Tuples are carefully designed as POD type, which allows POD arrays to be intialized + /// with classic C style: + + static Tuple2 map[] = { + { 1, "one" }, + { 2, "one" }, + { 3, "one" }, + }; + + + /// Simple FindTuple template function is provided to search for tuple based on the first + /// value (`a`) (linear O(n) search): + + DUMP(FindTuple(map, __countof(map), 3)->b); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Tutorial.cpp b/tutorial/CoreTutorial/Tutorial.cpp new file mode 100644 index 000000000..002878bcc --- /dev/null +++ b/tutorial/CoreTutorial/Tutorial.cpp @@ -0,0 +1,193 @@ +#include "RichEdit/RichEdit.h" + +using namespace Upp; + +String out; + +int major = 0; +int minor = 0; +String qtf = +"[ $$0,0#00000000000000000000000000000000:Default]" +"[a83;*R6 $$1,3#31310162474203024125188417583966:caption]" +"[H4;b83;*4 $$2,3#07864147445237544204411237157677:title]" +"[b42;a42 $$3,3#45413000475342174754091244180557:text]" +"[l100;C@5*;1 $$4,4#20902679421464641399138805415013:code]" +"[l100;*C$7;2 $$5,5#07531550463529505371228428965313:log]" +"[b73;*3 $$2,3#07864147445237544204111237153677:subtitle]" +; + +#define OUT(x) out << x << '\n'; + +void FlushDoc(String& docblock) +{ + docblock = TrimBoth(docblock); + if(docblock.GetCount() == 0) + return; + OUT("============= DOC"); + OUT(docblock); + + bool title = false; + + String style = "[s3;"; + if(docblock.StartsWith("..")) { + docblock = AsString(major) + "." + AsString(++minor) + ' ' + TrimBoth(docblock.Mid(2)); + style = "[s7;"; + title = true; + } + else + if(docblock.StartsWith(".")) { + docblock = AsString(++major) + "." + ' ' + TrimBoth(docblock.Mid(1)); + minor = 0; + style = "[s2;"; + title = true; + } + + qtf << style << " "; + + const char *s = docblock; + + while(*s) + if((s == ~docblock || findarg(s[-1], '(', ' ', '\'', '\"') >= 0) && findarg(s[0], '*', '%', '_', '`', '^') >= 0 && s[1] && s[1] != ' ') { + int c = *s++; + const char *b = s; + const char *dc = NULL; + while(*s && *s != c) { + if(*s == ':') + dc = s; + s++; + } + if(c == '^') { + qtf << "[^"; + if(dc) { + qtf.Cat(b, dc); + b = dc + 1; + } + else + qtf.Cat(b, s); + qtf << "^ "; + } + else + qtf << decode(c, '*', "[* ", '%', "[/ ", '_', "[_ ", '`', title ? "[C@5 " : "[C@5* ", ""); + while(b < s) + qtf << '`' << *b++; + qtf << "]"; + if(*s) s++; + } + else + qtf << '`' << *s++; + + qtf << "&]"; + docblock.Clear(); +} + +void FlushLog(Vector& log) +{ + if(log.GetCount() == 0) + return; + OUT("============ LOG"); + OUT(Join(log, "\r\n")); + + qtf << "[s5; \1" << Join(log, "\n") << "\1&]&"; + log.Clear(); +} + +void FlushCode(Vector& code) +{ + while(code.GetCount() && TrimBoth(code[0]).GetCount() == 0) + code.Remove(0); + while(code.GetCount() && TrimBoth(code.Top()).GetCount() == 0) + code.Drop(); + + bool tabs = true; + for(auto l : code) + if(l.GetCount() && *l != '\t') { + tabs = false; + break; + } + if(tabs) + for(auto& l : code) + if(l.GetCount()) + l.Remove(0); + + if(code.GetCount() == 0) + return; + OUT("============= CODE"); + OUT(Join(code, "\r\n")); + + qtf << "&[s4; \1" << Join(code, "\n") << "\1&]&"; + code.Clear(); +} + +void MakeTutorial() +{ + String log = LoadFile(GetStdLogPath()); + StringStream ss(log); + + VectorMap>> logline; + + String path; + int line; + + while(!ss.IsEof()) { + String ln = ss.GetLine(); + if(ln.StartsWith("-=>")) { + SplitTo(ln.Mid(4), '@', path, ln); + line = atoi(ln) - 1; + } + else + if(path.GetCount()) + logline.GetAdd(path).Add(MakeTuple(line, ln)); + } + + for(auto&& f : ~logline) { + Vector src = Split(Filter(LoadFile(f.key), [] (int c) { return c == '\r' ? 0 : c; }), '\n', false); + int i = 0; + int logi = 0; + bool wasdoc = false; + Vector code; + while(i < src.GetCount()) { + String block; + while(i < src.GetCount() && TrimLeft(src[i]).StartsWith("///")) { + FlushCode(code); + Vector logblock; + while(logi < f.value.GetCount() && f.value[logi].a <= i) + logblock.Add(f.value[logi++].b); + FlushLog(logblock); + + String line = src[i++]; + int q = line.FindAfter("///"); + if(TrimRight(line).GetCount() > q) { + if(block.GetCount()) + block << ' '; + block << TrimBoth(line.Mid(q)); + } + else + FlushDoc(block); + wasdoc = true; + } + FlushDoc(block); + while(i < src.GetCount() && !TrimLeft(src[i]).StartsWith("///")) { + String tl = TrimLeft(src[i]); + if(!tl.StartsWith("#if") && !tl.StartsWith("#end")) + code.Add(src[i]); + i++; + } + DUMPC(code); + if(!wasdoc) + code.Clear(); + } + } + + LOG("--------------------------------------------"); + LOG(out); + + LOG("--------------------------------------------"); + LOG(qtf); + + RichEditWithToolBar edit; + edit.SetReadOnly(); + edit <<= qtf; + TopWindow win; + win.Add(edit.SizePos()); + win.Run(); +} diff --git a/tutorial/CoreTutorial/Tutorial.h b/tutorial/CoreTutorial/Tutorial.h new file mode 100644 index 000000000..1ca8aeb1b --- /dev/null +++ b/tutorial/CoreTutorial/Tutorial.h @@ -0,0 +1,51 @@ +#include + +using namespace Upp; + +#define LOGPOS UPP::VppLog() << "-=> " << __FILE__ << '@' << __LINE__ << '\n', + +#undef LOG +#undef DUMP +#undef DUMPC +#undef DUMPM +#undef LOGHEX +#undef DUMPHEX + +#define LOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL +#define DUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL +#define DUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c)) +#define DUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c)) +#define LOGHEX(x) LOGPOS UPP::LogHex(x) +#define DUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x) + +#undef DLOG +#undef DDUMP +#undef DDUMPC +#undef DDUMPM +#undef DLOGHEX +#undef DDUMPHEX + +#define DLOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL +#define DDUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL +#define DDUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c)) +#define DDUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c)) +#define DLOGHEX(x) LOGPOS UPP::LogHex(x) +#define DDUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x) + +#undef RLOG +#undef RDUMP +#undef RDUMPC +#undef RDUMPM +#undef RLOGHEX +#undef RDUMPHEX + +#define RLOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL +#define RDUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL +#define RDUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c)) +#define RDUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c)) +#define RLOGHEX(x) LOGPOS UPP::LogHex(x) +#define RDUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x) + +#define DO(x) void x(); x(); + +void MakeTutorial(); \ No newline at end of file diff --git a/tutorial/CoreTutorial/Value.cpp b/tutorial/CoreTutorial/Value.cpp new file mode 100644 index 000000000..7557b687d --- /dev/null +++ b/tutorial/CoreTutorial/Value.cpp @@ -0,0 +1,77 @@ +#include "Tutorial.h" + +void ValueTutorial() +{ + /// .Value + + /// Value is sort of equivalent of polymorphic data types from scripting languages like Python + /// or JavaSript. `Value` can represent values of concrete types, some types also have + /// extended interoperability with `Value` and it is then possible to e.g. compare `Value`s + /// containing such types against each other or serialize them for persistent storage. + + /// Usually, Value compatible types define typecast operator to `Value` and constructor + /// from `Value`, so that interaction is for the most part seamless: + + Value a = 1; + Value b = 2.34; + Value c = GetSysDate(); + Value d = "hello"; + + DUMP(a); + DUMP(b); + DUMP(c); + DUMP(d); + + int x = a; + double y = b; + Date z = c; + String s = d; + + DUMP(x); + DUMP(y); + DUMP(z); + DUMP(s); + + /// As for primitive types, Value seamlessly works with `int`, `int64`, `bool` and `double`. + /// Casting `Value` to a type that it does not contain throws an exception: + + try { + s = a; + DUMP(s); // we never get here.... + } + catch(ValueTypeError) { + LOG("Failed Value conversion"); + } + + /// However, conversion between related types is possible (as long as it is supported by + /// these types): + + double i = a; + int j = b; + Time k = c; + WString t = d; + + DUMP(i); + DUMP(j); + DUMP(k); + DUMP(t); + + /// To determine type of value stored in `Value`, you can use `Is` method: + + DUMP(a.Is()); + DUMP(a.Is()); + DUMP(b.Is()); + DUMP(c.Is()); + DUMP(c.Is()); + DUMP(d.Is()); + + /// Note that Is tests for absolute type match, not for compatible types. For that reason, + /// for widely used compatible types helper functions are defined: + + DUMP(IsNumber(a)); + DUMP(IsNumber(b)); + DUMP(IsDateTime(c)); + DUMP(IsString(d)); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Value2.cpp b/tutorial/CoreTutorial/Value2.cpp new file mode 100644 index 000000000..7a253003e --- /dev/null +++ b/tutorial/CoreTutorial/Value2.cpp @@ -0,0 +1,90 @@ +#include "Tutorial.h" + +void Value2Tutorial() +{ + /// .Client types and `Value`, `RawValue`, `RichValue` + + /// There are two Value compatibility levels. The simple one, `RawValue`, has little + /// requirements for the type used - only copy constructor and assignment operator are + /// required (and there are even forms of `RawValue` that work for types missing these): + + struct RawFoo { + String x; + // default copy constructor and assignment operator are provided by compiler + }; + + /// To convert such type to `Value`, use `RawToValue`: + + RawFoo h; + h.x = "hello"; + Value q = RawToValue(h); + + DUMP(q.Is()); + + /// To convert it back, us 'To' templated member function of `Value`, it returns a constant + /// reference to the value: + + DUMP(q.To().x); + + /// `RichValue` level `Value`s provide more operations for `Value` - equality test, + /// `IsNull` test, hashing, conversion to text, serialization (possibly to XML and Json), + /// comparison. In order to make serialization work, type must also have assigned an + /// integer id (client types should use ids in range 10000..20000). Type can provide the + /// support for these operations via template function specializations or (perhaps more + /// convenient) using defined methods and inheriting from `ValueType` base class template: + + struct Foo : ValueType { + int x; + + Foo(const Nuller&) { x = Null; } + Foo(int x) : x(x) {} + Foo() {} + + // We provide these methods to allow automatic conversion of Foo to/from Value + operator Value() const { return RichToValue(*this); } + Foo(const Value& v) { *this = v.Get(); } + + String ToString() const { return AsString(x); } + unsigned GetHashValue() const { return x; } + void Serialize(Stream& s) { s % x; } + bool operator==(const Foo& b) const { return x == b.x; } + bool IsNullInstance() const { return IsNull(x); } + int Compare(const Foo& b) const { return SgnCompare(x, b.x); } + // This type does not define XML nor Json serialization + }; + +#if 0 + INITBLOCK { // This has to be at file level scope +#endif + Value::Register(); // need to register value type integer id to allow serialization +#if 0 + } +#endif + + Value a = Foo(54321); // uses Foo::operator Value + Value b = Foo(54321); + Value c = Foo(600); + + DUMP(a); // uses Foo::ToString + DUMP(a == b); // uses Foo::operator== + DUMP(a == c); + DUMP(c < a); // uses Foo::Compare + + DUMP(IsNull(a)); // uses Foo::IsNullInstance + + Foo foo = c; // Uses Foo::Foo(const Value&) + DUMP(foo); + + /// + + String s = StoreAsString(a); // Uses Foo::Serialize + + Value loaded; + // Using registered (Value::Registered) integer id creates the correct type, then uses + // Foo::Serialize to load the data from the stream + LoadFromString(loaded, s); + + DUMP(loaded); + + /// +}; diff --git a/tutorial/CoreTutorial/Vector.cpp b/tutorial/CoreTutorial/Vector.cpp new file mode 100644 index 000000000..c412a6786 --- /dev/null +++ b/tutorial/CoreTutorial/Vector.cpp @@ -0,0 +1,58 @@ +#include "Tutorial.h" + +void Vector1() +{ + /// .`Vector` basics + + /// `Vector` is the basic container of U++. It is the random access container similar to + /// `std::vector` with one important performance related difference: There are rules for + /// elements of `Vector` that allow its implementation to move elements in memory using + /// plain `memcpy`/`memmove` ("Moveable" concept). + + /// Anyway, for now let us start with simple `Vector` of `int`s: + + Vector v; + + /// You can add elements to the Vector as parameters of the Add method + + v.Add(1); + v.Add(2); + + DUMP(v); + + /// Alternative and very important possibility for U++ containers is 'in-place creation'. + /// In this case, parameter-less Add returns a reference to a new element in `Vector`: + + v.Add() = 3; + + DUMP(v); + + /// You can also use `operator<<` + + v << 4 << 5; + + DUMP(v); + + /// `Vector` also supports initializer lists: + + v.Append({ 6, 7 }); + + DUMP(v); + + /// To iterate `Vector` you can use indices: + + for(int i = 0; i < v.GetCount(); i++) + LOG(v[i]); + + /// begin/end interface: + + for(auto q = v.begin(), e = v.end(); q != e; q++) + LOG(*q); + + /// C++11 range-for syntax: + + for(const auto& q : v) + LOG(q); + + /// +} diff --git a/tutorial/CoreTutorial/Vector2.cpp b/tutorial/CoreTutorial/Vector2.cpp new file mode 100644 index 000000000..aa15565c1 --- /dev/null +++ b/tutorial/CoreTutorial/Vector2.cpp @@ -0,0 +1,46 @@ +#include "Tutorial.h" + +void Vector2() +{ + /// .`Vector` operations + + /// You can `Insert` or `Remove` elements at random positions of Vector (O(n) complexity): + + Vector v; + v.Add(1); + v.Add(2); + + v.Insert(1, 10); + + DUMP(v); + + /// + + v.Insert(0, { 7, 6, 5 }); + + DUMP(v); + + /// + + v.Remove(0); + + DUMP(v); + + /// At method returns element at specified position ensuring that such a position exists. + /// If there is not enough elements in `Vector`, required number of elements is added. If + /// second parameter of `At` is present, newly added elements are initialized to this value. + + v.Clear(); + for(int i = 0; i < 10000; i++) + v.At(Random(10), 0)++; + + DUMP(v); + + /// You can apply algorithms on containers, e.g. Sort + + Sort(v); + + DUMP(v); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/WString.cpp b/tutorial/CoreTutorial/WString.cpp new file mode 100644 index 000000000..acf2ce8f9 --- /dev/null +++ b/tutorial/CoreTutorial/WString.cpp @@ -0,0 +1,33 @@ +#include "Tutorial.h" + +void WStringTutorial() +{ + /// .WString + + /// String works with 8 bit characters. For 16-bit character encoding use `WString`. Both + /// classes are closely related and share most of interface methods. U++ also provides + /// conversions between `String` and `WString` and you can also use 8 bit string literals with + /// `WString`. Conversion is ruled by current default character set. Default value of default + /// character set is `CHARSET_UTF8`. This conversion is also used in `WString::ToString`, + /// e.g. when putting `WString` to log: + + WString x = "characters 280-300: "; // you can assign 8-bit character literal to WString + for(int i = 280; i < 300; i++) + x.Cat(i); + + DUMP(x); + + /// `ToString` converts `WString` to `String`: + + String y = x.ToString(); + DUMP(y); + + /// `ToWString` converts `String` to `WString`: + + y.Cat(" (appended)"); // you can use 8-bit character literals in most WString operations + x = y.ToWString(); + + DUMP(x); + + /// +} diff --git a/tutorial/CoreTutorial/help.qtf b/tutorial/CoreTutorial/help.qtf new file mode 100644 index 000000000..5ef9b667b --- /dev/null +++ b/tutorial/CoreTutorial/help.qtf @@ -0,0 +1,747 @@ +[ $$0,0#00000000000000000000000000000000:Default] +[a83;*R6 $$1,3#31310162474203024125188417583966:caption] +[H4;b83;*4 $$2,3#07864147445237544204411237157677:title] +[b42;a42;ph2 $$3,3#45413000475342174754091244180557:text] +[l321;C@5;1 $$4,4#20902679421464641399138805415013:code] +[l321;b83;a83;*C$7;2 $$5,5#07531550463529505371228428965313:result`-line] +[l321;*C$7;2 $$6,6#03451589433145915344929335295360:result] +[{_}%EN-US +[s1; U`+`+ Containers Tutorial&] +[s2; 1. Vector basics&] +[s3; Let us start with a simple [* Vector] of ints&] +[s4; -|[* Vector] v;&] +[s3; You can add elements to the Vector as parameters of the [* Add] +method&] +[s4; -|v.[* Add](1);&] +[s4; -|v.Add(2);&] +[s4; -|v.Add(3);&] +[s3; To iterate Vector you can use indices&] +[s4; -|for(int i `= 0; i < v.[* GetCount](); i`+`+)&] +[s4; -|-|LOG(v[* `[]i[* `]]);&] +[s3; Alternative, U`+`+ also provides standard C`+`+ begin/end interface&] +[s4; -|for(auto q `= v.[* begin](), e `= v.[* end](); q !`= e; q`+`+)&] +[s4; -|-|LOG([* `*]q);&] +[s3; or with C`+`+11 range`-for syntax:&] +[s4; -|for(const auto`& q : v)&] +[s4; -|-|LOG(q);&] +[s3;/ &] +[s3; [/ Note: LOG is diagnostics macro that outputs its argument to +the .log file in debug mode. Another similar macros we will use +in this tutorial are DUMP (similar to the LOG, but dumps the +source expression too) and DUMPC (dumps the content of the container).]&] +[s3;/ &] +[s2; 2. Vector operations&] +[s3; You can [* Insert] or [* Remove] elements at random positions of +Vector&] +[s4; -|Vector v;&] +[s4; -|v.Add(1);&] +[s4; -|v.Add(2);&] +[s4; -|&] +[s4; -|v.[* Insert](1, 10);&] +[s5; v `= `{ 1, 10, 2 `}&] +[s4; -|v.[* Remove](0);&] +[s5; v `= `{ 10, 2 `}&] +[s3; [* At] method returns element at specified position ensuring that +such a position exists. If there is not enough elements in Vector, +required number of elements is added. If second parameter of +At is present, newly added elements are initialized to this value. +As an example, we will create distribution of RandomValue with +unknown maximal value&] +[s4; &] +[s4; -|v.Clear();&] +[s4; -|for(int i `= 0; i < 10000; i`+`+)&] +[s4; -|-|v.[* At](RandomValue(), 0)`+`+;&] +[s5; v `= `{ 958, 998, 983, 1012, 1013, 1050, 989, 998, 1007, 992 +`}&] +[s3; You can apply algorithms on containers, e.g. [* Sort]&] +[s4; -|[* Sort](v);&] +[s5; v `= `{ 958, 983, 989, 992, 998, 998, 1007, 1012, 1013, 1050 +`}&] +[s3; &] +[s2; 3. Transfer issues&] +[s3; Often you need to pass content of one container to another of +the same type. NTL containers always support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ p +ick semantics] (equivalent of std`::move), and, depending on type +stored, also might support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ clone +sematics]. When transfering the value, you have to explicitly +specify which one to use:&] +[s4; -|Vector v;&] +[s4; -|v.Add(1);&] +[s4; -|v.Add(2);&] +[s4; &] +[s4; -|Vector v1 [* `=] pick(v);&] +[s3; now source Vector [C v] is destroyed `- picked `- and you can +no longer access its content. This behaviour has many advantages. +First, it is consistently fast and in most cases, transfer of +value instead of full copy is exactly what you need. Second, +NTL containers can store elements that lack copy operation `- +in that case, pick transfer is the only option anyway.&] +[s3; If you really need to preserve value of source (and elements +support deep copy operation), you can use [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ c +lone]&] +[s4; -|v [* `=] clone(v1);&] +[s3; &] +[s2; 4. Client types&] +[s3; So far we were using int as type of elements. In order to store +client defined types into the Vector (and the Vector [^topic`:`/`/Core`/src`/Overview`$en`-us^ f +lavor]) the type must satisfy [^topic`:`/`/Core`/src`/Moveable`$en`-us^ moveable] +requirement `- in short, it must not contain back`-pointers nor +virtual methods. Type must be marked as moveable in order to +define interface contract using&] +[s4; -|struct Distribution : [* Moveable] `{&] +[s4; -|-|String text;&] +[s4; -|-|Vector data;&] +[s4; &] +[s4; -|-|rval`_default(Distribution);&] +[s4; -|-|Distribution() `{`}&] +[s4; -|`};&] +[s3; Note that rval`_default macro is helper to restore default deleted +r`-value constructor in C`+`+11 and default constructor is also +default deleted in C`+`+11.&] +[s3; Now to add Distribution elements you cannot use Add with parameter, +because it requires elements to have default deep`-copy constructor +`- and Distribution does not have one, as Vector has default +pick`-constructor, so Distribution itself has pick`-constructor. +It would no be a good idea either, because deep`-copy would involve + expensive copying of inner Vector.&] +[s3; Instead, [* Add] without parameters has to be used `- it default +constructs (that is cheap) element in Vector and returns reference +to it&] +[s4; -|Vector dist;&] +[s4; -|for(int n `= 5; n <`= 10; n`+`+) `{&] +[s4; -|-|Distribution`& d `= dist.[* Add]();&] +[s4; -|-|d.text << `"Test `" << n;&] +[s4; -|-|for(int i `= 0; i < 10000; i`+`+)&] +[s4; -|-|-|d.data.At(rand() % n, 0)`+`+;&] +[s4; -|`}&] +[s4; &] +[s6; Test 5: `{ 2018, 1992, 2025, 1988, 1977 `}&] +[s6; Test 6: `{ 1670, 1682, 1668, 1658, 1646, 1676 `}&] +[s6; Test 7: `{ 1444, 1406, 1419, 1493, 1370, 1418, 1450 `}&] +[s6; Test 8: `{ 1236, 1199, 1245, 1273, 1279, 1302, 1250, 1216 `}&] +[s6; Test 9: `{ 1115, 1111, 1100, 1122, 1192, 1102, 1089, 1064, 1105 +`}&] +[s6; Test 10: `{ 969, 956, 1002, 1023, 1006, 994, 1066, 1022, 929, +1033 `}&] +[s3; Another possibility is to use AddPick method, which uses pick`-constructor +instead of deep`-copy constructor. E.g. Distribution elements +might be generated by some function &] +[s4; -|Distribution CreateDist(int n);&] +[s3; and code for adding such elements to Vector then looks like&] +[s4; -|for(n `= 5; n <`= 10; n`+`+)&] +[s4; -|-|dist.[* AddPick](CreateDist(n));&] +[s3; alternatively, you can use default`-constructed variant too&] +[s4; -|-|dist.Add() `= CreateDist(); // alternative&] +[s3; &] +[s2; 5. Array flavor&] +[s3; If elements do not satisfy requirements for Vector flavor, they +can still be stored in Array flavor. Another reason for using +Array is need for referencing elements `- Array flavor never +invalidates references or pointers to them.&] +[s3; Example of elements that cannot be stored in Vector flavor are +STL containers (STL does not specify the NTL flavor for obvious +reasons):&] +[s4; -|[* Array]< std`::list > al;&] +[s4; -|for(int i `= 0; i < 4; i`+`+) `{&] +[s4; -|-|std`::list`& l `= al.Add();&] +[s4; -|-|for(int q `= 0; q < i; q`+`+)&] +[s4; -|-|-|l.push`_back(q);&] +[s4; -|`}&] +[s3; &] +[s2; 6. Polymorphic Array&] +[s3; Array can even be used for storing polymorphic elements &] +[s4; struct Number `{&] +[s4; -|virtual double Get() const `= 0;&] +[s4; &] +[s4; -|String ToString() const `{ return AsString(Get()); `}&] +[s4; -|&] +[s4; -|virtual `~Number() `{`}&] +[s4; `};&] +[s4; &] +[s4; struct Integer : public Number `{&] +[s4; -|int n;&] +[s4; -|virtual double Get() const `{ return n; `}&] +[s4; &] +[s4; -|Integer() `{`}&] +[s4; `};&] +[s4; &] +[s4; struct Double : public Number `{&] +[s4; -|double n;&] +[s4; -|virtual double Get() const `{ return n; `}&] +[s4; &] +[s4; -|Double() `{`}&] +[s4; `};&] +[s4; &] +[s4; bool operator<(const Number`& a, const Number`& b)&] +[s4; `{&] +[s4; -|return a.Get() < b.Get();&] +[s4; `}&] +[s4; &] +[s3; In this case, elements are added using Add with pointer to base +element type parameter. Do not be confused by new and pointer, +Array takes ownership of passed object and behaves like container +of base type elements&] +[s4; -|Array num;&] +[s4; -|num.Create().n `= 15.5;&] +[s4; -|num.Create().n `= 3;&] +[s5; num `= `{ 15.5, 3 `}&] +[s3; Thanks to well defined algorithm requirements, you can e.g. +directly apply Sort on such Array&] +[s4; -|bool operator<(const Number`& a, const Number`& b)&] +[s4; -|`{&] +[s4; -|-|return a.Get() < b.Get();&] +[s4; -|`}&] +[s4; &] +[s4; .......&] +[s4; &] +[s4; -|Sort(num);&] +[s5; num `= `{ 3, 15.5 `}&] +[s3; &] +[s2; 7. Bidirectional containers&] +[s3; Vector and Array containers allow fast adding and removing elements +at the end of sequence. Sometimes, same is needed at begin of +sequence too (usually to support FIFO like operations). In such +case, BiVector and BiArray should be used&] +[s4; -|BiVector n;&] +[s4; -|n.[* AddHead](1);&] +[s4; -|n.[* AddTail](2);&] +[s4; -|n.AddHead(3);&] +[s4; -|n.AddTail(4);&] +[s5; n `= `{ 3, 1, 2, 4 `}&] +[s4; -|n.[* DropHead]();&] +[s5; n `= `{ 1, 2, 4 `}&] +[s4; -|n.[* DropTail]();&] +[s5; n `= `{ 1, 2 `}&] +[s4; -|BiArray num;&] +[s4; -|num.CreateHead().n `= 3;&] +[s4; -|num.CreateTail().n `= 15.5;&] +[s4; -|num.CreateHead().n `= 2.23;&] +[s4; -|num.CreateTail().n `= 2;&] +[s5; num `= `{ 2.23, 3, 15.5, 2 `}&] +[s3; &] +[s2; 8. Index&] +[s3; Index is a container very similar to the plain Vector (it is +random access array of elements with fast addition at the end) +with one unique feature `- it is able to fast retrieve position +of element with required value using [* Find] method&] +[s4; -|[* Index] ndx;&] +[s4; -|ndx.[* Add](`"alfa`");&] +[s4; -|ndx.Add(`"beta`");&] +[s4; -|ndx.Add(`"gamma`");&] +[s4; -|ndx.Add(`"delta`");&] +[s4; -|ndx.Add(`"kappa`");&] +[s5; ndx `= `{ alfa, beta, gamma, delta, kappa `}&] +[s4; -|DUMP(ndx.[* Find](`"beta`"));&] +[s5; ndx.Find(`"beta`") `= 1&] +[s3; If element is not present in Index, Find returns a negative +value&] +[s4; -|DUMP(ndx.Find(`"something`"));&] +[s5; ndx.Find(`"something`") `= `-1&] +[s3; Any element can be replaced using [* Set] method&] +[s4; -|ndx.[* Set](0, `"delta`");&] +[s5; ndx `= `{ delta, beta, gamma, delta, kappa `}&] +[s3; If there are more elements with the same value, they can be +iterated using [* FindNext] method&] +[s4; -|int fi `= ndx.Find(`"delta`");&] +[s4; -|while(fi >`= 0) `{&] +[s4; -|-|DUMP(fi);&] +[s4; -|-|fi `= ndx.[* FindNext](fi);&] +[s4; -|`}&] +[s4; -|cout << `'n`';&] +[s5; 0 3-|&] +[s3; [* FindAdd] method retrieves position of element like Find, but +if element is not present in Index, it is added&] +[s4; -|DUMP(ndx.[* FindAdd](`"one`"));&] +[s4; -|DUMP(ndx.FindAdd(`"two`"));&] +[s4; -|DUMP(ndx.FindAdd(`"three`"));&] +[s4; -|DUMP(ndx.FindAdd(`"two`"));&] +[s4; -|DUMP(ndx.FindAdd(`"three`"));&] +[s4; -|DUMP(ndx.FindAdd(`"one`"));&] +[s4; &] +[s6; ndx.FindAdd(`"one`") `= 5&] +[s6; ndx.FindAdd(`"two`") `= 6&] +[s6; ndx.FindAdd(`"three`") `= 7&] +[s6; ndx.FindAdd(`"two`") `= 6&] +[s6; ndx.FindAdd(`"three`") `= 7&] +[s6; ndx.FindAdd(`"one`") `= 5&] +[s3; Removing elements from random access sequence is always expensive, +that is why rather than remove, Index supports [* Unlink] and [* UnlinkKey +]operations, which leave element in Index but make it invisible +for Find operation&] +[s4; -|ndx.[* Unlink](2);&] +[s4; -|ndx.[* UnlinkKey](`"kappa`");&] +[s4; &] +[s4; -|DUMP(ndx.Find(ndx`[2`]));&] +[s4; -|DUMP(ndx.Find(`"kappa`"));&] +[s4; &] +[s6; ndx.Find(ndx`[2`]) `= `-1&] +[s6; ndx.Find(`"kappa`") `= `-1&] +[s3; You can test whether element at given position is unlinked using +[* IsUnlinked] method&] +[s4; -|DUMP(ndx.[* IsUnlinked](1));&] +[s4; -|DUMP(ndx.IsUnlinked(2));&] +[s4; &] +[s6; ndx.IsUnlinked(1) `= 0&] +[s6; ndx.IsUnlinked(2) `= 1&] +[s3; Unlinked positions can be reused by [* Put] method&] +[s4; -|ndx.[* Put](`"foo`");&] +[s5; ndx `= `{ delta, beta, foo, delta, kappa, one, two, three `}&] +[s4; -|DUMP(ndx.Find(`"foo`"));&] +[s5; ndx.Find(`"foo`") `= 2&] +[s3; You can also remove all unlinked elements from Index using [* Sweep] +method&] +[s4; -|ndx.Sweep();&] +[s5; ndx `= `{ delta, beta, foo, delta, one, two, three `}&] +[s3; As we said, operations directly removing or inserting elements +of Index are very expensive, but sometimes this might not matter, +so they are available too&] +[s4; -|ndx.[* Remove](1);&] +[s5; ndx `= `{ delta, foo, delta, one, two, three `}&] +[s4; -|ndx.[* RemoveKey](`"two`");&] +[s5; ndx `= `{ delta, foo, delta, one, three `}&] +[s4; -|ndx.[* Insert](0, `"insert`");&] +[s5; ndx `= `{ insert, delta, foo, delta, one, three `}&] +[s3; Finally, [* PickKeys] operation allows you to obtain Vector of +elements of Index in low constant time operation (while destroying +source Index)&] +[s4; -|Vector d `= ndx.[* PickKeys]();&] +[s4; -|Sort(d);&] +[s5; d `= `{ delta, delta, foo, insert, one, three `}&] +[s3; &] +[s2; 9. Index and client types&] +[s3; In order to store elements to Index, they must be moveable (you +can use [* ArrayIndex] for types that are not) and they must have +defined the operator`=`= and a function to compute hash value. +Notice usage THE of [* CombineHash] to combine hash values of types +already known to U`+`+ into final result&] +[s4; &] +[s4; struct Person : Moveable `{&] +[s4; -|String name;&] +[s4; -|String surname;&] +[s4; &] +[s4; -|unsigned [* GetHashValue]() const `{ return [* CombineHash](name, +surname); `}&] +[s4; -|bool [* operator`=`=](const Person`& b) const `{ return name +`=`= b.name `&`& surname `=`= b.surname; `}&] +[s4; &] +[s4; -|Person(String name, String surname) : name(name), surname(surname) +`{`}&] +[s4; -|Person() `{`}&] +[s4; `};&] +[s4; &] +[s4; .......&] +[s4; &] +[s4; -|Index p;&] +[s4; -|p.Add(Person(`"John`", `"Smith`"));&] +[s4; -|p.Add(Person(`"Paul`", `"Carpenter`"));&] +[s4; -|p.Add(Person(`"Carl`", `"Engles`"));&] +[s4; -| &] +[s4; -|DUMP(p.Find(Person(`"Paul`", `"Carpenter`")));&] +[s5; p.Find(Person(`"Paul`", `"Carpenter`")) `= 1&] +[s3; &] +[s2; 10. VectorMap, ArrayMap&] +[s3; VectorMap is nothing more than a simple composition of Index +and Vector. You can use [* Add] methods to put elements into the +VectorMap&] +[s4; -|[* VectorMap] m;&] +[s4; -|&] +[s4; -|m.[* Add](`"1`", Person(`"John`", `"Smith`"));&] +[s4; -|m.Add(`"2`", Person(`"Carl`", `"Engles`"));&] +[s4; &] +[s4; -|Person`& p `= m.[* Add](`"3`");&] +[s4; -|p.name `= `"Paul`";&] +[s4; -|p.surname `= `"Carpenter`";&] +[s3; VectorMap provides constant access to its underlying Index and +Vector&] +[s4; -|DUMP(m.[* GetKeys]());&] +[s4; -|DUMP(m.[* GetValues]());&] +[s4; &] +[s6; m.GetKeys() `= `{ 1, 2, 3 `}&] +[s6; m.GetValues() `= `{ John Smith, Carl Engles, Paul Carpenter +`}&] +[s4; &] +[s3; You can use indices to iterate map contents&] +[s4; -|for(int i `= 0; i < m.GetCount(); i`+`+)&] +[s4; -|-|LOG(m.[* GetKey](i) << `": `" << m[* `[]i[* `]]);&] +[s4; &] +[s6; 1: John Smith&] +[s6; 2: Carl Engles&] +[s6; 3: Paul Carpenter&] +[s4; &] +[s3; You can use [* Find] method to retrieve position of element with +required key&] +[s4; -|DUMP(m.[* Find](`"2`"));&] +[s5; m.Find(`"2`") `= 1&] +[s3; or [* Get] method to retrieve corresponding value&] +[s4; -|DUMP(m.[* Get](`"2`"));&] +[s5; m.Get(`"2`") `= Carl Engles&] +[s3; Passing key not present in VectorMap as Get parameter is a logic +error, but there exists two parameter version that returns second +parameter if key is not in VectorMap&] +[s4; -|DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&] +[s5; m.Get(`"33`", Person(`"unknown`", `"person`")) `= unknown person&] +[s3; As with Index, you can use [* Unlink] to make elements invisible +for Find operations&] +[s4; -|m.Unlink(1);&] +[s4; -|DUMP(m.Find(`"2`"));&] +[s5; m.Find(`"2`") `= `-1&] +[s3; You can use [* SetKey] method to change the key of the element&] +[s4; -|m.[* SetKey](1, `"33`");&] +[s4; -|DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&] +[s5; m.Get(`"33`", Person(`"unknown`", `"person`")) `= Carl Engles&] +[s3; If there are more elements with the same key in VectorMap, you +can iterate them using [* FindNext] method:&] +[s4; -|m.Add(`"33`", Person(`"Peter`", `"Pan`"));&] +[s4; &] +[s6; m.GetKeys() `= `{ 1, 33, 3, 33 `}&] +[s6; m.GetValues() `= `{ John Smith, Carl Engles, Paul Carpenter, +Peter Pan `}&] +[s4; &] +[s4; -|int q `= m.Find(`"33`");&] +[s4; -|while(q >`= 0) `{&] +[s4; -|-|cout << m`[q`] << `'n`';&] +[s4; -|-|q `= m.[* FindNext](q);&] +[s4; -|`}&] +[s4; -|&] +[s6; Carl Engles&] +[s6; Peter Pan&] +[s4; &] +[s3; You can reuse unlinked positions using [* Put] method:&] +[s4; -|m.[* UnlinkKey](`"33`");&] +[s4; -|m.[* Put](`"22`", Person(`"Ali`", `"Baba`"));&] +[s4; -|m.Put(`"44`", Person(`"Ivan`", `"Wilks`"));&] +[s4; &] +[s6; m.GetKeys() `= `{ 1, 22, 3, 44 `}&] +[s6; m.GetValues() `= `{ John Smith, Ali Baba, Paul Carpenter, Ivan +Wilks `}&] +[s4; &] +[s3; [* GetSortOrder] algorithm returns order of elements as Vector +container. You can use it to order content of VectorMap without +actually moving its elements&] +[s4; -|bool operator<(const Person`& a, const Person`& b)&] +[s4; -|`{&] +[s4; -|-|return a.surname `=`= b.surname ? a.name < b.name&] +[s4; -| : a.surname < b.surname;&] +[s4; -|`}&] +[s4; &] +[s4; .......&] +[s4; &] +[s4; -|Vector order `= [* GetSortOrder](m.GetValues());&] +[s5; order `= `{ 1, 2, 0, 3 `}&] +[s4; -|for(int i `= 0; i < order.GetCount(); i`+`+)&] +[s4; -|-|cout << m.GetKey(order`[i`]) << `": `" << m`[order`[i`]`] << +`'n`';&] +[s4; &] +[s6; 22: Ali Baba&] +[s6; 3: Paul Carpenter&] +[s6; 1: John Smith&] +[s6; 44: Ivan Wilks&] +[s4; &] +[s3; You can get Vector of values or keys using [* PickValues] resp. +[* PickKeys] methods in low constant time, while destroying content +of source VectorMap&] +[s4; -|Vector ps `= m.[* PickValues]();&] +[s5; ps `= `{ John Smith, Ali Baba, Paul Carpenter, Ivan Wilks `}&] +[s3; If type of values does not satisfy requirements for Vector elements +or if references to elements are needed, you can use [* ArrayMap] +instead&] +[s4; -|[* ArrayMap] am;&] +[s4; -|am.Create(`"A`").n `= 11;&] +[s4; -|am.Create(`"B`").n `= 2.1;&] +[s4; &] +[s6; am.GetKeys() `= `{ A, B `}&] +[s6; am.GetValues() `= `{ 11, 2.1 `}&] +[s4; &] +[s4; -|DUMP(am.Get(`"A`"));&] +[s4; -|DUMP(am.Find(`"B`"));&] +[s4; &] +[s6; am.Get(`"A`") `= 11&] +[s6; am.Find(`"B`") `= 1&] +[s3; &] +[s2; 11. One&] +[s3; One is a container that can store none or one element of T or +derived from T. It functionally quite similiar to std`::unique`_ptr, +but has some more convenient features (like Create method).&] +[s4; struct Base `{&] +[s4; -|virtual String Get() `= 0;&] +[s4; -|virtual `~Base() `{`}&] +[s4; `};&] +[s4; &] +[s4; struct Derived1 : Base `{&] +[s4; -|virtual String Get() `{ return `"Derived1`"; `}&] +[s4; `};&] +[s4; &] +[s4; struct Derived2 : Base `{&] +[s4; -|virtual String Get() `{ return `"Derived2`"; `}&] +[s4; `};&] +[s4; &] +[s4; void MakeDerived1(One`& t)&] +[s4; `{&] +[s4; -|t.Create();&] +[s4; `}&] +[s4; &] +[s4; void MakeDerived2(One`& t)&] +[s4; `{&] +[s4; -|t.Create();&] +[s4; `}&] +[s4; &] +[s4; .......&] +[s4; -|[* One] s;&] +[s3; Operator bool of one returns true if it contains an element:&] +[s4; -|DUMP([* (bool)]s);&] +[s4; &] +[s6; (bool)s `= false&] +[s4; &] +[s4; -|&] +[s4; -|s.[* Create]();&] +[s4; -|DUMP((bool)s);&] +[s4; -|DUMP(s`->Get());&] +[s4; &] +[s6; (bool)s `= true&] +[s6; s`->Get() `= Derived1&] +[s4; &] +[s3; Clear method removes the element from One:&] +[s4; -|s.[* Clear]();&] +[s4; -|DUMP((bool)s);&] +[s4; &] +[s6; (bool)s `= false&] +[s4; &] +[s4; &] +[s3; One represents a convenient and recommended method how to deal +with class factories in U`+`+: Define them as a function (or method) +with reference to One parameter, e.g.:&] +[s4; void MakeDerived1(One`& t)&] +[s4; `{&] +[s4; -|t.Create();&] +[s4; `}&] +[s4; &] +[s4; void MakeDerived2(One`& t)&] +[s4; `{&] +[s4; -|t.Create();&] +[s4; `}&] +[s4; &] +[s4; VectorMap`&)> factories;&] +[s4; &] +[s4; INITBLOCK `{&] +[s4; -|factories.Add(0, MakeDerived1);&] +[s4; -|factories.Add(1, MakeDerived2);&] +[s4; `};&] +[s4; &] +[s4; void Create(One`& t, int what)&] +[s4; `{&] +[s4; -|(`*factories.Get(what))(t);&] +[s4; `}&] +[s3; &] +[s2; 12. Any&] +[s3; Any is a container that can contain none or one element of [*/ any] +type, the only requirement is that the type has default constructor. +Important thing to remember is that [* Is] method matches [/ exact] +type ignoring class hierarchies (FileIn is derived from Stream, +but if Any contains FileIn, Is() returns false).&] +[s4; void Do([* Any]`& x)&] +[s4; `{&] +[s4; -|if(x.[* Is]())&] +[s4; -|-|LOG(`"String: `" << x.[* Get]());&] +[s4; -|if(x.[* Is]()) `{&] +[s4; -|-|LOG(`"`-`-`- File: `");&] +[s4; -|-|LOG(LoadStream(x.[* Get]()));&] +[s4; -|-|LOG(`"`-`-`-`-`-`-`-`-`-`-`");&] +[s4; -|`}&] +[s4; -|if(x.[* IsEmpty]())&] +[s4; -|-|LOG(`"empty`");&] +[s4; `}&] +[s4; &] +[s4; .....&] +[s4; &] +[s4; -|Any x;&] +[s4; -|x.[* Create]() `= `"Hello!`";&] +[s4; -|Do(x);&] +[s4; -|x.[* Create]().Open(GetDataFile(`"Ntl12.cpp`"));&] +[s4; -|Do(x);&] +[s4; -|x.[* Clear]();&] +[s4; -|Do(x);&] +[s3; &] +[s2; 13. InVector, InArray&] +[s3; InVector and InArray are vector types quite similar to Vector/Array, +but they trade the speed of operator`[`] with the ability to +insert or remove elements at any position quickly. You can expect +operator`[`] to be about 10 times slower than in Vector (but +that is still very fast), while Insert at any position scales +well up to hundreds of megabytes of data (e.g. InVector containing +100M of String elements is handled without problems).&] +[s4; -|[* InVector] v;&] +[s4; -|for(int i `= 0; i < 1000000; i`+`+)&] +[s4; -|-|v.Add(i);&] +[s4; -|v.[* Insert](0, `-1); // This is fast&] +[s4; &] +[s3; While the interface of InVector/InArray is almost identical +to Vector/Array, InVector/InArray in addition implements FindLowerBound/FindUpper +Bound functions `- while normal random access algorithms work, +it is possible to provide InVector specific optimization that +basically matches the performace of Find`*Bound on sample Vector.&] +[s4; &] +[s4; -|DUMP(v.[* FindLowerBound](55));&] +[s3; &] +[s2; 14. SortedIndex, SortedVectorMap, SortedArrayMap&] +[s3; SortedIndex is similar to regular Index, but keeps its elements +in sorted order (sorting predicate is a template parameter, defaults +to StdLess). Implementation is using InVector, so it works fine +even with very large number of elements (performance is similar +to tree based std`::set). Unlike Index, SortedIndex provides lower/upper +bounds searches, so it allow range search.&] +[s4; -|[* SortedIndex] x;&] +[s4; -|x.Add(5);&] +[s4; -|x.Add(3);&] +[s4; -|x.Add(7);&] +[s4; -|x.Add(1);&] +[s4; -|&] +[s4; -|DUMPC(x);&] +[s4; -|DUMP(x.[* Find](3));&] +[s4; -|DUMP(x.[* Find](3));&] +[s4; -|DUMP(x.[* FindLowerBound](3));&] +[s4; -|DUMP(x.[* FindUpperBound](6));&] +[s4; &] +[s3; SortedVectorMap and SortedArrayMap are then SortedIndex based +equivalents to VectorMap/ArrayMap `- maps that keep keys sorted:&] +[s4; -|[* SortedVectorMap] m;&] +[s4; -|m.Add(`"zulu`", 11);&] +[s4; -|m.Add(`"frank`", 12);&] +[s4; -|m.Add(`"alfa`", 13);&] +[s4; -|&] +[s4; -|DUMPM(m);&] +[s4; -|DUMP(m.[* Get](`"zulu`"));&] +[s3; &] +[s2; 15. Tuples&] +[s3; U`+`+ has template classes Tuple2, Tuple3 and Tuple4 for combining +2`-4 values with different types. These are quite similiar to +std`::tuple class, with some advantages.&] +[s3; To create a Tuple value, you can use the Tuple function. If +correct types canot be deduced from parameters, you can specify +them explicitly:&] +[s4; -|Tuple3 x `= [* Tuple](12, +`"hello`", `"world`");&] +[s4; &] +[s3; Individual values are accessible as members a .. d:&] +[s4; &] +[s4; -|DUMP(x.a);&] +[s4; -|DUMP(x.b);&] +[s4; -|DUMP(x.c);&] +[s4; -|&] +[s3; As long as all individual types have conversion to String (AsString), +the tuple also has such conversion and thus can e.g. be easily +logged:&] +[s4; &] +[s4; -|DUMP(x);&] +[s4; &] +[s3; Also, as long as individual types have defined GetHashValue, +so does Tuple:&] +[s4; &] +[s4; -|DUMP(GetHashValue(x));&] +[s4; &] +[s3; As long as individual types have defined operator`=`=, Tuple +has defined operator`=`= and operator!`=&] +[s4; &] +[s4; -|Tuple3 y `= x;&] +[s4; -|DUMP(x !`= y);&] +[s4; &] +[s3; Finally, as long as all individual types have defined SgnCompare +(most U`+`+ types have), Tuple has SgnCompare, Compare method +and operators <, <`=, >, >`=:&] +[s4; &] +[s4; -|DUMP(x.Compare(y));&] +[s4; -|&] +[s4; -|y.b `= `"a`";&] +[s4; -|&] +[s4; -|DUMP(SgnCompare(x, y));&] +[s4; -|DUMP(x < y);&] +[s4; -|&] +[s4; &] +[s3; U`+`+ Tuples are strictly designed as POD type, which allows +POD arrays to be intialized with classic C style:&] +[s4; -|static Tuple2 map`[`] `= `{&] +[s4; -|-|`{ 1, `"one`" `},&] +[s4; -|-|`{ 2, `"one`" `},&] +[s4; -|-|`{ 3, `"one`" `},&] +[s4; -|`};&] +[s4; -|&] +[s3; &] +[s3; Simple FindTuple template function is provided to search for +tuple based on the first value:&] +[s3; &] +[s2; 15. Sorting&] +[s3; IndexSort is sort variant that is able to sort two random access +container (like Vector or Array) of the same size, based on values +in on of containers:&] +[s4; -|Vector a;&] +[s4; -|Vector b;-|&] +[s4; -|&] +[s4; -|a << 5 << 10 << 2 << 9 << 7 << 3;&] +[s4; -|b << `"five`" << `"ten`" << `"two`" << `"nine`" << `"seven`" +<< `"three`";&] +[s4; -|&] +[s4; -|[* IndexSort](a, b);&] +[s4; &] +[s6; a `= `[2, 3, 5, 7, 9, 10`]&] +[s6; b `= `[two, three, five, seven, nine, ten`]&] +[s4; &] +[s4; -|[* IndexSort](b, a);&] +[s4; &] +[s6; a `= `[5, 9, 7, 10, 3, 2`]&] +[s6; b `= `[five, nine, seven, ten, three, two`]&] +[s3; Order of sorted items is defined by sorting predicate. By default, +operator< comparing items of container is used (this predicate +can be provided by StdLess template), but it is possible to specify +different sorting order, e.g. by using predefined StdGreater +predicate:&] +[s4; -|Sort(a, [* StdGreater]());&] +[s4; &] +[s6; a `= `[10, 9, 7, 5, 3, 2`]&] +[s3; Sometimes, instead of sorting items in the container, it is +useful to know the order of items as sorted, using GetSortOrder:&] +[s4; -|Vector o `= [* GetSortOrder](a);&] +[s4; &] +[s6; o `= `[5, 4, 3, 2, 1, 0`]&] +[s3; FieldRelation predefined predicate can be used to sort container +of structures by specific field:&] +[s4; -|Vector p;&] +[s4; -|p << Point(5, 10) << Point(7, 2) << Point(4, 8) << Point(1, +0);&] +[s4; -|&] +[s4; -|Sort(p, [* FieldRelation](`&Point`::x, StdLess()));&] +[s4; &] +[s6; p `= `[`[1, 0`], `[4, 8`], `[5, 10`], `[7, 2`]`]&] +[s3; MethodRelation is good for sorting of structures based on constant +method of structure:-|&] +[s4; struct Foo `{&] +[s4; -|String a;&] +[s4; -|&] +[s4; -|int [* Get]() const `{ return atoi(a); `}&] +[s4; -|....&] +[s4; `};&] +[s4; ....&] +[s4; -|Array f;&] +[s4; -|f << `"12`" << `"1`" << `"10`" << `"7`" << `"5`";&] +[s4; -|&] +[s4; -|Sort(f, [* MethodRelation](`&Foo`::[* Get], StdLess()));&] +[s4; &] +[s6; f `= `[1, 5, 7, 10, 12`]&] +[s3; Normal Sort is not stable `- equal items can appear in sorted +sequence in random order. If maintaining original order of equal +items is important, use StableSort variant (with slight performance +penalty):&] +[s4; -|Vector t;&] +[s4; -|t << Point(10, 10) << Point(7, 1) << Point(7, 2) << Point(7, +3) << Point(1, 0);&] +[s4; -|[* StableSort](t, FieldRelation(`&Point`::x, StdLess()));&] +[s4; &] +[s6; t `= `[`[1, 0`], `[7, 1`], `[7, 2`], `[7, 3`], `[10, 10`]`]&] +[s3; &] +[s2; Recommended tutorials:&] +[s3; If you want to learn more, we have several tutorials that you +can find useful:&] +[s3;l160;i150;O0; [^topic`:`/`/Core`/srcdoc`/CoreTutorial`$en`-us^ U`+`+ +Core value types] `- if you miss this one`- it it a good occasion +to catch up. Here you will learn about Core basics.&] +[s0; [^topic`:`/`/Sql`/srcdoc`/tutorial`$en`-us^ SQL] `- learn how +to use databases with U`+`+ framework.&] +[s0; ]] \ No newline at end of file diff --git a/tutorial/CoreTutorial/init b/tutorial/CoreTutorial/init new file mode 100644 index 000000000..09048b39b --- /dev/null +++ b/tutorial/CoreTutorial/init @@ -0,0 +1,5 @@ +#ifndef _tutorial2_icpp_init_stub +#define _tutorial2_icpp_init_stub +#include "Core/init" +#include "RichEdit/init" +#endif diff --git a/tutorial/CoreTutorial/tutorial2.cpp b/tutorial/CoreTutorial/tutorial2.cpp new file mode 100644 index 000000000..42b150518 --- /dev/null +++ b/tutorial/CoreTutorial/tutorial2.cpp @@ -0,0 +1,41 @@ +#include "Tutorial.h" + +GUI_APP_MAIN +{ + DO(Logging); + DO(StringTutorial); + DO(StringBufferTutorial); + DO(WStringTutorial); + DO(DateTime); + DO(AsStringTutorial); + DO(ValueTutorial); + DO(NullTutorial); + DO(Value2Tutorial); + DO(CombineHashTutorial); + DO(ComparableTutorial); + + DO(Vector1); + DO(Vector2); + DO(Transfer); + DO(ContainerClientTypes); + DO(ArrayTutorial); + DO(PolyArray); + + DO(Bidirectional); + + DO(IndexTutorial); + DO(IndexClient); + DO(Map); + DO(OneTutorial); + DO(AnyTutorial); + DO(InVectorTutorial); + DO(SortedMap); + DO(TupleTutorial); + + DO(Range); + DO(Sorting); + + DO(FunctionTutorial); + + MakeTutorial(); +}