From ad03f0c326844f103e26e591fc345bd5efdc12e1 Mon Sep 17 00:00:00 2001 From: cxl Date: Tue, 20 Dec 2016 13:46:26 +0000 Subject: [PATCH] .docs git-svn-id: svn://ultimatepp.org/upp/trunk@10562 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/Core/srcdoc.tpp/Tutorial$en-us.tpp | 880 ++++++++++++++++------ 1 file changed, 666 insertions(+), 214 deletions(-) diff --git a/uppsrc/Core/srcdoc.tpp/Tutorial$en-us.tpp b/uppsrc/Core/srcdoc.tpp/Tutorial$en-us.tpp index 0e9a4b3c5..4d25fabc5 100644 --- a/uppsrc/Core/srcdoc.tpp/Tutorial$en-us.tpp +++ b/uppsrc/Core/srcdoc.tpp/Tutorial$en-us.tpp @@ -2,7 +2,7 @@ topic "U++ Containers Tutorial"; [2 $$0,0#00000000000000000000000000000000:Default] [l288;i1120;a17;O9;~~~.1408;2 $$1,0#10431211400427159095818037425705:param] [a83;*R6 $$2,5#31310162474203024125188417583966:caption] -[H4;b83;*4 $$3,5#07864147445237544204411237157677:title] +[b83;*4 $$3,5#07864147445237544204411237157677:title] [i288;O9;C2 $$4,6#40027414424643823182269349404212:item] [b42;a42;ph2 $$5,5#45413000475342174754091244180557:text] [l288;b17;a17;2 $$6,6#27521748481378242620020725143825:desc] @@ -24,7 +24,8 @@ topic "U++ Containers Tutorial"; [H8;b73;*+150 $$22,5#07864147445237544204111237153677:subtitle] [{_} [s2;%% U`+`+ Core Tutorial&] -[s22;H4; [5 1. Logging]&] +[s22;H6; [7 1. Basics]&] +[s3; 1.1 Logging&] [s5; 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 @@ -65,8 +66,8 @@ variable name and value:&] [s7; DUMPHEX(h);&] [s0; &] [s17; x `= 0x7b&] -[s17; h `= Memory at 0x01E9FA74, size 0x3 `= 3&] -[s17; `+0 0x01E9FA74 66 6F 6F +[s17; h `= Memory at 0x0271F8DC, size 0x3 `= 3&] +[s17; `+0 0x0271F8DC 66 6F 6F foo &] [s0; &] [s5; To log the value of a container (or generic Range), you can @@ -136,7 +137,7 @@ to output the log both to the console and .log file:&] [s0; &] [s7; StdLogSetup(LOG`_COUT`|LOG`_FILE);&] [s0; &] -[s22; 2. String&] +[s3;H4; 1.2 String&] [s5; String is a value type useful for storing text or binary data.&] [s0; &] [s7; String a `= `"Hello`";&] @@ -310,11 +311,11 @@ data, including zeroes:&] [s7; &] [s7; DUMPHEX(a);&] [s0; &] -[s17; a `= Memory at 0x01E9FAD4, size 0x5 `= 5&] -[s17; `+0 0x01E9FAD4 31 32 33 34 00 +[s17; a `= Memory at 0x0271F93C, size 0x5 `= 5&] +[s17; `+0 0x0271F93C 31 32 33 34 00 1234. &] [s0; &] -[s22; 3. StringBuffer&] +[s3;H4; 1.3 StringBuffer&] [s5; If you need a direct write access to [*C@5 String]`'s C`-string character buffer, you can use complementary [*C@5 StringBuffer] class. One of reasons to do so is when you have to deal with @@ -369,7 +370,7 @@ to StringBuffer clears the source String.&] [s5; 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.&] -[s22; 4. WString&] +[s3;H4; 1.4 WString&] [s5; String works with 8 bit characters. For 16`-bit character encoding use [*C@5 WString]. Both classes are closely related and share most of interface methods. U`+`+ also provides conversions between @@ -406,7 +407,7 @@ in most WString operations&] [s17; x `= characters 280`-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī (appended)&] [s0; &] -[s22; 5. Date and Time&] +[s3;H4; 1.5 Date and Time&] [s5; To represent date and time, U`+`+ provides [*C@5 Date] and [*C@5 Time] concrete types.&] [s0; &] @@ -414,7 +415,7 @@ concrete types.&] [s7; &] [s7; DUMP(date);&] [s0; &] -[s17; date `= 12/16/2016&] +[s17; date `= 12/20/2016&] [s0; &] [s5; All data members of [*C@5 Date] structure are public:&] [s0; &] @@ -426,7 +427,7 @@ would log&] [s0; &] [s17; (int)date.year `= 2016&] [s17; (int)date.month `= 12&] -[s17; (int)date.day `= 16&] +[s17; (int)date.day `= 20&] [s0; &] [s5; Dates can be compared:&] [s0; &] @@ -441,15 +442,15 @@ ing goes to the next/previous day:&] [s7; DUMP(`-`-date);&] [s7; DUMP(`+`+date);&] [s0; &] -[s17; date `+ 1 `= 12/17/2016&] -[s17; `-`-date `= 12/15/2016&] -[s17; `+`+date `= 12/16/2016&] +[s17; date `+ 1 `= 12/21/2016&] +[s17; `-`-date `= 12/19/2016&] +[s17; `+`+date `= 12/20/2016&] [s0; &] [s5; Subtraction of dates yields a number of days between them:&] [s0; &] [s7; DUMP(date `- Date(2000, 1, 1));&] [s0; &] -[s17; date `- Date(2000, 1, 1) `= 6194&] +[s17; date `- Date(2000, 1, 1) `= 6198&] [s0; &] [s5; There are several [*C@5 Date] and calendar related functions:&] [s0; &] @@ -475,7 +476,7 @@ ing goes to the next/previous day:&] [s0; &] [s7; DUMP(DayOfWeek(date)); // 0 is Sunday&] [s0; &] -[s17; DayOfWeek(date) `= 5&] +[s17; DayOfWeek(date) `= 2&] [s0; &] [s0; &] [s7; DUMP(LastDayOfMonth(date));&] @@ -489,7 +490,7 @@ ing goes to the next/previous day:&] [s17; FirstDayOfMonth(date) `= 12/01/2016&] [s17; LastDayOfYear(date) `= 12/31/2016&] [s17; FirstDayOfYear(date) `= 01/01/2016&] -[s17; DayOfYear(date) `= 351&] +[s17; DayOfYear(date) `= 355&] [s17; DayOfYear(Date(2016, 1, 1)) `= 1&] [s0; &] [s0; &] @@ -500,10 +501,10 @@ between two dates&] partial months`' between two dates&] [s7; DUMP(AddYears(date, 2));&] [s0; &] -[s17; AddMonths(date, 20) `= 08/16/2018&] +[s17; AddMonths(date, 20) `= 08/20/2018&] [s17; GetMonths(date, date `+ 100) `= 3&] [s17; GetMonthsP(date, date `+ 100) `= 4&] -[s17; AddYears(date, 2) `= 12/16/2018&] +[s17; AddYears(date, 2) `= 12/20/2018&] [s0; &] [s0; &] [s7; DUMP(GetWeekDate(2015, 1));&] @@ -543,11 +544,11 @@ time:&] [s7; DUMP((int)time.minute);&] [s7; DUMP((int)time.second);&] [s0; &] -[s17; time `= 12/16/2016 16:17:40&] -[s17; (Date)time `= 12/16/2016&] -[s17; (int)time.hour `= 16&] -[s17; (int)time.minute `= 17&] -[s17; (int)time.second `= 40&] +[s17; time `= 12/20/2016 14:43:46&] +[s17; (Date)time `= 12/20/2016&] +[s17; (int)time.hour `= 14&] +[s17; (int)time.minute `= 43&] +[s17; (int)time.second `= 46&] [s0; &] [s5; Times can be compared:&] [s0; &] @@ -574,10 +575,10 @@ but numbers represent seconds (using [*C@5 int64] datatype):&] is in days&] [s7; DUMP(time `- ToTime(date)); // Time `- Time is in seconds&] [s0; &] -[s17; time `+ 1 `= 12/16/2016 16:17:41&] -[s17; time `+ 24 `* 3600 `= 12/17/2016 16:17:40&] +[s17; time `+ 1 `= 12/20/2016 14:43:47&] +[s17; time `+ 24 `* 3600 `= 12/21/2016 14:43:46&] [s17; time `- date `= 0&] -[s17; time `- ToTime(date) `= 58660&] +[s17; time `- ToTime(date) `= 53026&] [s0; &] [s5; [*C@5 Time] defines era limits too:&] [s0; &] @@ -587,7 +588,7 @@ is in days&] [s17; Time`::Low() `= 01/01/`-4000 00:00:00&] [s17; Time`::High() `= 01/01/4000 00:00:00&] [s0; &] -[s22; 6. [C@5 AsString], [C@5 ToString] and [C@5 operator<<]&] +[s3;H4; 1.6 [C@5 AsString], [C@5 ToString] and [C@5 operator<<]&] [s5; 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 @@ -632,9 +633,9 @@ items predefined by U`+`+:&] [s7; DUMP(LoadFile(ConfigFile(`"test.txt`")));&] [s7; DUMP(sout);&] [s0; &] -[s17; LoadFile(ConfigFile(`"test.txt`")) `= 1.23 12/16/2016 12/16/2016 -16:17:40&] -[s17; sout `= 1.23 12/16/2016 12/16/2016 16:17:40&] +[s17; LoadFile(ConfigFile(`"test.txt`")) `= 1.23 12/20/2016 12/20/2016 +14:43:46&] +[s17; sout `= 1.23 12/20/2016 12/20/2016 14:43:46&] [s0; &] [s5; Getting client types involved into this schema is not too difficult, all you need to do is to add [*C@5 ToString] method:&] @@ -668,7 +669,7 @@ in Upp namespace:&] FormatIntRoman(a.x); `}&] [s7; `};&] [s0; &] -[s22; 7. CombineHash&] +[s3;H4; 1.7 CombineHash&] [s5; To simplify providing of high quality hash codes for composite types, U`+`+ provides [*C@5 CombineHash] utility class. This class uses [*C@5 GetHashValue] function to gather hash codes of all values @@ -702,7 +703,7 @@ too:&] [s0; &] [s17; GetHashValue(x) `= 3378606405&] [s0; &] -[s22; 8. SgnCompare and CombineCompare&] +[s3;H4; 1.8 SgnCompare and CombineCompare&] [s5; 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 @@ -851,7 +852,8 @@ be further simplified using [*C@5 CombineCompare] helper class:&] [s17; o !`= p `= true&] [s17; SgnCompare(o, p) `= `-1&] [s0; &] -[s22; 9. [C@5 Vector] basics&] +[s22; 2. Array containers&] +[s3; 2.1 [C@5 Vector] basics&] [s5; [*C@5 Vector] is the basic container of U`+`+. It is the random access container similar to [*C@5 std`::vector] with one important performance related difference: There are rules for elements of @@ -936,7 +938,7 @@ returns a reference to a new element in [*C@5 Vector]:&] [s17; 6&] [s17; 7&] [s0; &] -[s22; 10. [C@5 Vector] operations&] +[s3;H4; 2.2 [C@5 Vector] operations&] [s5; You can [*C@5 Insert] or [*C@5 Remove] elements at random positions of Vector (O(n) complexity):&] [s0; &] @@ -976,7 +978,7 @@ value.&] [s7; &] [s7; DUMP(v);&] [s0; &] -[s17; v `= `[963, 1040, 980, 1033, 968, 1066, 944, 1000, 1017, 989`]&] +[s17; v `= `[1043, 988, 985, 1020, 964, 974, 1041, 1005, 1009, 971`]&] [s0; &] [s5; You can apply algorithms on containers, e.g. Sort&] [s0; &] @@ -984,9 +986,9 @@ value.&] [s7; &] [s7; DUMP(v);&] [s0; &] -[s17; v `= `[944, 963, 968, 980, 989, 1000, 1017, 1033, 1040, 1066`]&] +[s17; v `= `[964, 971, 974, 985, 988, 1005, 1009, 1020, 1041, 1043`]&] [s0; &] -[s22; 11. Transfer issues&] +[s3;H4; 2.3 Transfer issues&] [s5; 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^ p ick semantics] (synonym of std`::move), and, depending on type @@ -1052,7 +1054,7 @@ instead of explicit [*C@5 clone]. You can easily achieve that using [s17; v `= `[1, 2`]&] [s17; v2 `= `[1, 2`]&] [s0; &] -[s22; 12. Client types in U`+`+ containers&] +[s3;H4; 2.4 Client types in U`+`+ containers&] [s5; So far we were using int as type of elements. In order to store client defined types into the [*C@5 Vector] (and the Vector [^topic`:`/`/Core`/src`/Overview`$en`-us^ f lavor]) the type must satisfy [^topic`:`/`/Core`/src`/Moveable`$en`-us^ moveable] @@ -1090,15 +1092,15 @@ to it:&] [s7; DUMPC(dist);&] [s0; &] [s17; dist:&] -[s17; -|`[0`] `= Test 5: `[1971, 2035, 2048, 1968, 1978`]&] -[s17; -|`[1`] `= Test 6: `[1658, 1625, 1702, 1660, 1647, 1708`]&] -[s17; -|`[2`] `= Test 7: `[1492, 1405, 1401, 1478, 1408, 1386, 1430`]&] -[s17; -|`[3`] `= Test 8: `[1316, 1264, 1248, 1242, 1260, 1232, 1228, -1210`]&] -[s17; -|`[4`] `= Test 9: `[1185, 1097, 1113, 1099, 1120, 1077, 1131, -1086, 1092`]&] -[s17; -|`[5`] `= Test 10: `[1026, 965, 969, 1019, 951, 1004, 988, 975, -1038, 1065`]&] +[s17; -|`[0`] `= Test 5: `[1964, 1995, 1971, 2002, 2068`]&] +[s17; -|`[1`] `= Test 6: `[1651, 1651, 1644, 1687, 1688, 1679`]&] +[s17; -|`[2`] `= Test 7: `[1437, 1411, 1404, 1378, 1457, 1470, 1443`]&] +[s17; -|`[3`] `= Test 8: `[1227, 1344, 1263, 1226, 1219, 1259, 1201, +1261`]&] +[s17; -|`[4`] `= Test 9: `[1141, 1168, 1117, 1100, 1087, 1084, 1070, +1141, 1092`]&] +[s17; -|`[5`] `= Test 10: `[1033, 999, 996, 1003, 997, 1015, 1030, 951, +977, 999`]&] [s0; &] [s5; Another possibility is to use [*C@5 Vector`::Add(T`&`&)] method, which uses pick`-constructor instead of deep`-copy constructor. @@ -1115,7 +1117,7 @@ E.g. [*C@5 Distribution] elements might be generated by some function:&] [s0; &] [s7; -|dist.Add() `= CreateDist();&] [s0; &] -[s22; 13. Array flavor&] +[s3;H4; 2.5 Array flavor&] [s5; If elements are not [*C@5 Moveable] and therefore cannot be stored in [*C@5 Vector] flavor, they can still be stored in [*C@5 Array] flavor. Another reason for using Array is the need for referencing @@ -1138,7 +1140,7 @@ standard library knows nothing about U`+`+ Moveable concept):&] [s17; s.c`_str() `= Test&] [s17; s.c`_str() `= Test&] [s0; &] -[s22; 14. Polymorphic [C@5 Array]&] +[s3;H4; 2.6 Polymorphic [C@5 Array]&] [s5; [*C@5 Array] can even be used for storing polymorphic elements:&] [s0; &] [s7; struct Number `{&] @@ -1195,7 +1197,7 @@ a.Get() < b.Get(); `});&] [s0; &] [s17; num `= `[1.1, 3, 15.5`]&] [s0; &] -[s22; 15. Bidirectional containers&] +[s3;H4; 2.7 Bidirectional containers&] [s5; [*C@5 Vector] and [*C@5 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). @@ -1248,7 +1250,7 @@ needed at begin of sequence too (usually to support FIFO queues). [s0; &] [s17; num `= `[World, 3, Hello, 2`]&] [s0; &] -[s22; 16. [C@5 Index]&] +[s3;H4; 2.8 [C@5 Index]&] [s5; [*C@5 Index] is the the foundation of all U`+`+ associative operations and is one of defining features of U`+`+.&] [s5; [*C@5 Index] is a container very similar to the plain [*C@5 Vector] @@ -1398,7 +1400,7 @@ Index)&] [s0; &] [s17; ndx `= `[test, alfa, foo, delta, one, three`]&] [s0; &] -[s22; 17. Index and client types&] +[s3;H4; 2.9 Index and client types&] [s5; In order to store elements to [*C@5 Index], they type must be [*C@5 Moveable], have deep copy and defined the [*C@5 operator`=`=] and a [*C@5 GetHashValue] function or method to compute the hash @@ -1428,7 +1430,7 @@ b.name `&`& surname `=`= b.surname; `}&] [s0; &] [s17; p.Find(Person(`"Paul`", `"Carpenter`")) `= 1&] [s0; &] -[s22; 18. [C@5 VectorMap], [C@5 ArrayMap]&] +[s3;H4; 2.10 [C@5 VectorMap], [C@5 ArrayMap]&] [s5; [*C@5 VectorMap] is nothing else than a simple composition of [*C@5 Index] of keys and [*C@5 Vector] of values. You can use [*C@5 Add] methods to put elements into the [*C@5 VectorMap]:&] @@ -1618,7 +1620,7 @@ Array is better fit for value type (e.g. they are polymorphic):&] [s0; &] [s17; am `= `{key: new person`}&] [s0; &] -[s22; 19. [C@5 One]&] +[s3;H4; 2.11 [C@5 One]&] [s5; [*C@5 One] is a container that can store none or one element of T or derived from T. It is functionally quite similar to [*C@5 std`::unique`_ptr], but has some convenient features.&] @@ -1677,7 +1679,7 @@ in [*C@5 One]:&] [s0; &] [s17; (bool)s `= false&] [s0; &] -[s22; 20. [C@5 Any]&] +[s3;H4; 2.12 [C@5 Any]&] [s5; [*C@5 Any] is a container that can contain none or one element of [/ any] type. [*C@5 Any`::Is] method matches exact type ignoring class hierarchies (unlike [*C@5 One`::Is]). You can use [*C@5 Get] @@ -1700,7 +1702,7 @@ to retrieve a reference to the instance stored:&] [s17; Any is now Color: Color(0, 0, 128)&] [s17; Any is now String: Hello!&] [s0; &] -[s22; 21. [C@5 InVector], [C@5 InArray]&] +[s3;H4; 2.13 [C@5 InVector], [C@5 InArray]&] [s5; [*C@5 InVector] and [*C@5 InArray] are container types quite similar to [*C@5 Vector]/``Array``, but they trade the speed of [*C@5 operator`[`]] with the ability to insert or remove elements at any position @@ -1727,7 +1729,7 @@ basically match the performace of [*C@5 Find`*Bound] on simple [s0; &] [s17; v.FindLowerBound(55) `= 56&] [s0; &] -[s22; 22. [C@5 SortedIndex], [C@5 SortedVectorMap], [C@5 SortedArrayMap]&] +[s3;H4; 2.14 [C@5 SortedIndex], [C@5 SortedVectorMap], [C@5 SortedArrayMap]&] [s5; [*C@5 SortedIndex] is similar to regular [*C@5 Index], but keeps its elements in sorted order (sorting predicate is a template parameter, defaults to [*C@5 StdLess]). Implementation is using @@ -1775,7 +1777,7 @@ based equivalents to [*C@5 VectorMap]/``ArrayMap``:&] [s17; -|`[2`] `= (zulu) 11&] [s17; m.Get(`"zulu`") `= 11&] [s0; &] -[s22; 23. Tuples&] +[s3;H4; 2.15 Tuples&] [s5; Template class [*C@5 Tuple] allows combining 2`-4 values with different types. These are principally similar to [*C@5 std`::tuple], with some advantages. Unlike [*C@5 std`::tuple], individual elements @@ -1920,7 +1922,8 @@ tuple based on the first value ([*C@5 a]) (linear O(n) search):&] [s0; &] [s17; FindTuple(map, `_`_countof(map), 3)`->b `= one&] [s0; &] -[s22; 24. Range&] +[s22; 3. Ranges and algoritims&] +[s3; 3.1 Range&] [s5; Unlike STL, which interface algorithms with data using [*C@5 begin] / [*C@5 end] pair, U`+`+ algorithms usually work on [/ Ranges]. Range is an object that has [*C@5 begin] / [*C@5 end] methods providing @@ -2010,7 +2013,95 @@ certain condition:&] [s0; &] [s17; FilterRange(x, `[`](int x) `{ return x > 3; `}) `= `[5, 4, 4`]&] [s0; &] -[s22; 25. Sorting&] +[s3;H4; 3.2 Algorithms&] +[s5; In principle, is is possible to apply C`+`+ standard library +algorithms on U`+`+ containers or ranges.&] +[s5; U`+`+ algorithms are tuned for U`+`+ approach `- they work on +ranges and they prefer indices. Sometimes, U`+`+ algorithm will +perform faster with U`+`+ types than standard library algorithm.&] +[s5; [*C@5 FindIndex] performs linear search to find element with given +value and returns its index or `-1 if not found:&] +[s0; &] +[s7; Vector data `{ 5, 3, 7, 9, 3, 4, 2 `};&] +[s7; &] +[s7; &] +[s7; DUMP(FindIndex(data, 3));&] +[s7; DUMP(FindIndex(data, 6));&] +[s0; &] +[s17; FindIndex(data, 3) `= 1&] +[s17; FindIndex(data, 6) `= `-1&] +[s0; &] +[s5; [*C@5 SubRange] can be used to apply algorithm on subrange of +container:&] +[s0; &] +[s7; DUMP(FindIndex(SubRange(data, 2, data.GetCount() `- 2), 3));&] +[s0; &] +[s17; FindIndex(SubRange(data, 2, data.GetCount() `- 2), 3) `= 2&] +[s0; &] +[s5; [*C@5 FindMin] and [*C@5 FindMax] return the index of minimal / +maximal element:&] +[s0; &] +[s7; DUMP(FindMin(data));&] +[s7; DUMP(FindMax(data));&] +[s0; &] +[s17; FindMin(data) `= 6&] +[s17; FindMax(data) `= 3&] +[s0; &] +[s5; [*C@5 Min] and [*C@5 Max] return the [/ value] of minimal / maximal +element:&] +[s0; &] +[s7; DUMP(Min(data));&] +[s7; DUMP(Max(data));&] +[s0; &] +[s17; Min(data) `= 2&] +[s17; Max(data) `= 9&] +[s0; &] +[s5; If the range is empty, [*C@5 Min] and [*C@5 Max] are undefined (ASSERT +fails in debug mode), unless the value is specified as second +parameter to be used in this case:&] +[s0; &] +[s7; -|Vector empty;&] +[s7; //-|DUMP(Min(empty)); // This is undefined (fails in ASSERT)&] +[s7; -|DUMP(Min(empty, `-99999));&] +[s0; &] +[s17; Min(empty, `-99999) `= `-99999&] +[s0; &] +[s5; [*C@5 Count] returns the number of elements with specified value, +[*C@5 CountIf] the number of elements that satisfy predicate:&] +[s0; &] +[s7; DUMP(Count(data, 11));&] +[s7; DUMP(CountIf(data, `[`=`](int c) `{ return c >`= 5; `}));&] +[s0; &] +[s17; Count(data, 11) `= 0&] +[s17; CountIf(data, `[`=`](int c) `{ return c >`= 5; `}) `= 3&] +[s0; &] +[s5; [*C@5 Sum] return the sum of all elements in range:&] +[s0; &] +[s7; DUMP(Sum(data));&] +[s0; &] +[s17; Sum(data) `= 33&] +[s0; &] +[s5; Sorted containers can be searched with bisection. U`+`+ provides +usual upper / lower bound algorithms. [*C@5 FindBinary] returns +the index of element with given value or `-1 if not found:&] +[s0; &] +[s7; data `= `{ 5, 7, 9, 9, 14, 20, 23, 50 `};&] +[s7; // 0 1 2 3 4 5 6 7&] +[s7; DUMP(FindLowerBound(data, 9));&] +[s7; DUMP(FindUpperBound(data, 9));&] +[s7; DUMP(FindBinary(data, 9));&] +[s7; DUMP(FindLowerBound(data, 10));&] +[s7; DUMP(FindUpperBound(data, 10));&] +[s7; DUMP(FindBinary(data, 10));&] +[s0; &] +[s17; FindLowerBound(data, 9) `= 2&] +[s17; FindUpperBound(data, 9) `= 4&] +[s17; FindBinary(data, 9) `= 2&] +[s17; FindLowerBound(data, 10) `= 4&] +[s17; FindUpperBound(data, 10) `= 4&] +[s17; FindBinary(data, 10) `= `-1&] +[s0; &] +[s3;H4; 3.3 Sorting&] [s5; Unsurprisingly, [*C@5 Sort] function sorts a range. You can specify sorting predicate, default is [*C@5 operator<]:&] [s0; &] @@ -2082,147 +2173,8 @@ a.x < b.x; `});&] [s0; &] [s5; All sorting algorithms have they `'Stable`' variant, so there is [*C@5 StableIndexSort], [*C@5 GetStableSortOrder] etc...&] -[s22; 26. Function&] -[s5; U`+`+ [*C@5 Function] is quite similar to [*C@5 std`::function] `- -it is a function wrapper that can store/copy/invoke any callable -target. There are two important differences. First, invoking -empty [*C@5 Function] is NOP, if [*C@5 Function] has return type -[*C@5 T], it returns [*C@5 T()]. Second, [*C@5 Function] allows effective -chaining of callable targets using [*C@5 operator<<], if [*C@5 Function] -has return type, the return type of last callable appended is -used.&] -[s5; Usually, the callable target is C`+`+11 lambda:&] -[s0; &] -[s7; Function fn `= `[`](int n) `{ LOG(`"Called A`"); -return 3 `* n; `};&] -[s7; &] -[s7; LOG(`"About to call function`");&] -[s7; int n `= fn(7);&] -[s7; DUMP(n);&] -[s0; &] -[s17; About to call function&] -[s17; Called A&] -[s17; n `= 21&] -[s0; &] -[s5; If you chain another lambda into [*C@5 Function], all are called, -but the last one`'s return value is used:&] -[s0; &] -[s7; fn << `[`](int n) `{ LOG(`"Called B`"); return n `* n; `};&] -[s7; LOG(`"About to call combined function`");&] -[s7; n `= fn(7);&] -[s7; DUMP(n);&] -[s0; &] -[s17; About to call combined function&] -[s17; Called A&] -[s17; Called B&] -[s17; n `= 49&] -[s0; &] -[s5; 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 [*C@5 Function] which are -often unassigned to any action.&] -[s0; &] -[s7; fn.Clear();&] -[s7; LOG(`"About to call empty function`");&] -[s7; n `= fn(7);&] -[s7; DUMP(n);&] -[s0; &] -[s17; About to call empty function&] -[s17; n `= 0&] -[s0; &] -[s5; While using [*C@5 Function] with lambda expression is the most -common, you can use any target that has corresponding [*C@5 operator()] -defined:&] -[s0; &] -[s7; struct Functor `{&] -[s7; -|int operator()(int x) `{ LOG(`"Called Foo`"); return x % 2; -`}&] -[s7; `};&] -[s7; &] -[s7; fn `= Functor();&] -[s7; LOG(`"About to call Functor`");&] -[s7; n `= fn(7);&] -[s7; DUMP(n);&] -[s0; &] -[s17; About to call Functor&] -[s17; Called Foo&] -[s17; n `= 1&] -[s0; &] -[s5; As [*C@5 Function] with [*C@5 void] and [*C@5 bool] return types are -the most frequently used, U`+`+ defines template aliases [*C@5 Event]:&] -[s0; &] -[s7; Event<> ev `= `[`] `{ LOG(`"Event invoked`"); `};&] -[s7; &] -[s7; ev();&] -[s0; &] -[s17; Event invoked&] -[s0; &] -[s5; and [*C@5 Gate]:&] -[s0; &] -[s7; Gate gt `= `[`](int x) `{ LOG(`"Gate invoked with `" << -x); return x < 10; `};&] -[s7; &] -[s7; bool b `= gt(9);&] -[s7; DUMP(b);&] -[s7; b `= gt(10);&] -[s7; DUMP(b);&] -[s0; &] -[s17; Gate invoked with 9&] -[s17; b `= true&] -[s17; Gate invoked with 10&] -[s17; b `= false&] -[s0; &] -[s5; Using lambda to define calls to methods with more parameters -can be verbose and error`-prone. The issue can be simplified -by using [*C@5 THISFN] macro:&] -[s0; &] -[s7; struct Foo `{&] -[s7; -|void Test(int a, const String`& b) `{ LOG(`"Foo`::Test `" << -a << `", `" << b); `}&] -[s7; -|&] -[s7; -|typedef Foo CLASSNAME; // required for THISFN&] -[s7; -|&] -[s7; -|void Do() `{&] -[s7; -|-|Event fn;&] -[s7; -|-|&] -[s7; -|-|fn `= `[`=`](int a, const String`& b) `{ Test(a, b); `};&] -[s7; -|-|fn(1, `"using lambda`");&] -[s7; -|-|&] -[s7; -|-|fn `= THISFN(Test); // this is functionally equivalent, but -less verbose&] -[s7; -|-|fn(2, `"using THISFN`");&] -[s7; -|`}&] -[s7; `};&] -[s7; &] -[s7; Foo f;&] -[s7; f.Do();&] -[s0; &] -[s17; Foo`::Test 1, using lambda&] -[s17; Foo`::Test 2, using THISFN&] -[s0; &] -[s22; 27. Capturing U`+`+ containers into lambdas&] -[s5; Capturing objects with pick/clone semantics can be achieved -using [/ capture with an initializer]:&] -[s0; &] -[s7; Vector x`{ 1, 2 `};&] -[s7; Array y`{ `"one`", `"two`" `};&] -[s7; Event<> ev `= `[x `= pick(x), y `= clone(y)`] `{ DUMP(x); DUMP(y); -`};&] -[s7; &] -[s7; DUMP(x); // x is picked, so empty&] -[s7; DUMP(y); // y was cloned, so it retains original value&] -[s7; &] -[s7; LOG(`"About to invoke event`");&] -[s7; &] -[s7; ev();&] -[s0; &] -[s17; x `= `[`]&] -[s17; y `= `[one, two`]&] -[s17; About to invoke event&] -[s17; x `= `[1, 2`]&] -[s17; y `= `[one, two`]&] -[s0; &] -[s22; 28. Value&] +[s22; 4. Value&] +[s3; 4.1 Value&] [s5; Value is sort of equivalent of polymorphic data types from scripting languages like Python or JavaSript. [*C@5 Value] can represent values of concrete types, some types also have extended interoperability @@ -2255,11 +2207,11 @@ is for the most part seamless:&] [s0; &] [s17; a `= 1&] [s17; b `= 2.34&] -[s17; c `= 12/16/2016&] +[s17; c `= 12/20/2016&] [s17; d `= hello&] [s17; x `= 1&] [s17; y `= 2.34&] -[s17; z `= 12/16/2016&] +[s17; z `= 12/20/2016&] [s17; s `= hello&] [s0; &] [s5; As for primitive types, Value seamlessly works with [*C@5 int], @@ -2291,7 +2243,7 @@ as it is supported by these types):&] [s0; &] [s17; i `= 1&] [s17; j `= 2&] -[s17; k `= 12/16/2016 00:00:00&] +[s17; k `= 12/20/2016 00:00:00&] [s17; t `= hello&] [s0; &] [s5; To determine type of value stored in [*C@5 Value], you can use @@ -2325,7 +2277,7 @@ functions are defined:&] [s17; IsDateTime(c) `= true&] [s17; IsString(d) `= true&] [s0; &] -[s22; 29. [C@5 Null]&] +[s3;H4; 4.2 [C@5 Null]&] [s5; U`+`+ defines a special [*C@5 Null] constant to represent an empty value. This constant is convertible to many value types including primitive types [*C@5 double], [*C@5 int] and [*C@5 int64] (defined @@ -2348,11 +2300,11 @@ test whether a value is empty, use [*C@5 IsNull] function.&] [s17; d `= &] [s17; e > d `= true&] [s0; &] -[s5; Null is the only instance of [*C@5 Nuller] type. Assigning [*C@5 Null] -to primitive types is achieved by cast operators of [*C@5 Nuller], -other types can do it using constructor from [*C@5 Nuller].&] -[s5; As a special case, if Value contains Null, it is convertible -to any value type that can contain Null:&] +[s5; [*C@5 Null] is the only instance of [*C@5 Nuller] type. Assigning +[*C@5 Null] to primitive types is achieved by cast operators of +[*C@5 Nuller], other types can do it using constructor from [*C@5 Nuller].&] +[s5; As a special case, if [*C@5 Value] contains [*C@5 Null], it is convertible +to any value type that can contain [*C@5 Null]:&] [s0; &] [s7; Value v `= x; // x is int&] [s7; e `= v; // e is Date, but v is Null, so Null is assigned to @@ -2374,7 +2326,7 @@ coalesce (ifnull, Nvl), which returns the first non`-null argument [s0; &] [s17; Nvl(a, b, c) `= 123&] [s0; &] -[s22; 30. Client types and [C@5 Value], [C@5 RawValue], [C@5 RichValue]&] +[s3;H4; 4.3 Client types and [C@5 Value], [C@5 RawValue], [C@5 RichValue]&] [s5; There are two Value compatibility levels. The simple one, [*C@5 RawValue], has little requirements for the type used `- only copy constructor and assignment operator are required (and there are even forms @@ -2477,7 +2429,7 @@ correct type, then uses&] [s0; &] [s17; loaded `= 54321&] [s0; &] -[s22; 31. [C@5 ValueArray] and [C@5 ValueMap]&] +[s3;H4; 4.4 [C@5 ValueArray] and [C@5 ValueMap]&] [s5; [*C@5 ValueArray] is a type that represents an array of [*C@5 Value]s:&] [s0; &] [s7; ValueArray va`{1, 2, 3`};&] @@ -2742,6 +2694,506 @@ e`":5`}`}&] [s17; AsJSON(j) `= `{`"array`":`[1,`{`"key`":1`},3`],`"value`":`{`"one`":1,`"four`":4,`"thr ee`":3,`"five`":5`}`}&] [s0; &] +[s22; 5. Function and lambdas&] +[s3; 5.1 Function&] +[s5; U`+`+ [*C@5 Function] is quite similar to [*C@5 std`::function] `- +it is a function wrapper that can store/copy/invoke any callable +target. There are two important differences. First, invoking +empty [*C@5 Function] is NOP, if [*C@5 Function] has return type +[*C@5 T], it returns [*C@5 T()]. Second, [*C@5 Function] allows effective +chaining of callable targets using [*C@5 operator<<], if [*C@5 Function] +has return type, the return type of last callable appended is +used.&] +[s5; Usually, the callable target is C`+`+11 lambda:&] +[s0; &] +[s7; Function fn `= `[`](int n) `{ LOG(`"Called A`"); +return 3 `* n; `};&] +[s7; &] +[s7; LOG(`"About to call function`");&] +[s7; int n `= fn(7);&] +[s7; DUMP(n);&] +[s0; &] +[s17; About to call function&] +[s17; Called A&] +[s17; n `= 21&] +[s0; &] +[s5; If you chain another lambda into [*C@5 Function], all are called, +but the last one`'s return value is used:&] +[s0; &] +[s7; fn << `[`](int n) `{ LOG(`"Called B`"); return n `* n; `};&] +[s7; LOG(`"About to call combined function`");&] +[s7; n `= fn(7);&] +[s7; DUMP(n);&] +[s0; &] +[s17; About to call combined function&] +[s17; Called A&] +[s17; Called B&] +[s17; n `= 49&] +[s0; &] +[s5; 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 [*C@5 Function] which are +often unassigned to any action.&] +[s0; &] +[s7; fn.Clear();&] +[s7; LOG(`"About to call empty function`");&] +[s7; n `= fn(7);&] +[s7; DUMP(n);&] +[s0; &] +[s17; About to call empty function&] +[s17; n `= 0&] +[s0; &] +[s5; While using [*C@5 Function] with lambda expression is the most +common, you can use any target that has corresponding [*C@5 operator()] +defined:&] +[s0; &] +[s7; struct Functor `{&] +[s7; -|int operator()(int x) `{ LOG(`"Called Foo`"); return x % 2; +`}&] +[s7; `};&] +[s7; &] +[s7; fn `= Functor();&] +[s7; LOG(`"About to call Functor`");&] +[s7; n `= fn(7);&] +[s7; DUMP(n);&] +[s0; &] +[s17; About to call Functor&] +[s17; Called Foo&] +[s17; n `= 1&] +[s0; &] +[s5; As [*C@5 Function] with [*C@5 void] and [*C@5 bool] return types are +the most frequently used, U`+`+ defines template aliases [*C@5 Event]:&] +[s0; &] +[s7; Event<> ev `= `[`] `{ LOG(`"Event invoked`"); `};&] +[s7; &] +[s7; ev();&] +[s0; &] +[s17; Event invoked&] +[s0; &] +[s5; and [*C@5 Gate]:&] +[s0; &] +[s7; Gate gt `= `[`](int x) `{ LOG(`"Gate invoked with `" << +x); return x < 10; `};&] +[s7; &] +[s7; bool b `= gt(9);&] +[s7; DUMP(b);&] +[s7; b `= gt(10);&] +[s7; DUMP(b);&] +[s0; &] +[s17; Gate invoked with 9&] +[s17; b `= true&] +[s17; Gate invoked with 10&] +[s17; b `= false&] +[s0; &] +[s5; Using lambda to define calls to methods with more parameters +can be verbose and error`-prone. The issue can be simplified +by using [*C@5 THISFN] macro:&] +[s0; &] +[s7; struct Foo `{&] +[s7; -|void Test(int a, const String`& b) `{ LOG(`"Foo`::Test `" << +a << `", `" << b); `}&] +[s7; -|&] +[s7; -|typedef Foo CLASSNAME; // required for THISFN&] +[s7; -|&] +[s7; -|void Do() `{&] +[s7; -|-|Event fn;&] +[s7; -|-|&] +[s7; -|-|fn `= `[`=`](int a, const String`& b) `{ Test(a, b); `};&] +[s7; -|-|fn(1, `"using lambda`");&] +[s7; -|-|&] +[s7; -|-|fn `= THISFN(Test); // this is functionally equivalent, but +less verbose&] +[s7; -|-|fn(2, `"using THISFN`");&] +[s7; -|`}&] +[s7; `};&] +[s7; &] +[s7; Foo f;&] +[s7; f.Do();&] +[s0; &] +[s17; Foo`::Test 1, using lambda&] +[s17; Foo`::Test 2, using THISFN&] +[s0; &] +[s3;H4; 5.2 Capturing U`+`+ containers into lambdas&] +[s5; Capturing objects with pick/clone semantics can be achieved +using [/ capture with an initializer]:&] +[s0; &] +[s7; Vector x`{ 1, 2 `};&] +[s7; Array y`{ `"one`", `"two`" `};&] +[s7; Event<> ev `= `[x `= pick(x), y `= clone(y)`] `{ DUMP(x); DUMP(y); +`};&] +[s7; &] +[s7; DUMP(x); // x is picked, so empty&] +[s7; DUMP(y); // y was cloned, so it retains original value&] +[s7; &] +[s7; LOG(`"About to invoke event`");&] +[s7; &] +[s7; ev();&] +[s0; &] +[s17; x `= `[`]&] +[s17; y `= `[one, two`]&] +[s17; About to invoke event&] +[s17; x `= `[1, 2`]&] +[s17; y `= `[one, two`]&] +[s0; &] +[s22; 6. Multithreading&] +[s3; 6.1 [C@5 Thread]&] +[s5; Since C`+`+11, there is now a reasonable support for threads +in standard library. There are however reasons to use U`+`+ threads +instead. One of them is that U`+`+ high performance memory allocator +needs a cleanup call at the the thread exit, which is naturally +implemented into [*C@5 Upp`::Thread]. Second `'hard`' reason is +that Microsoft compiler is using Win32 API function for condition +variable that are not available for Windows XP, while U`+`+ has +alternative implementation for Windows XP, thus making executable +compatible with it.&] +[s5; Then of course we believe U`+`+ multithreading / parallel programming +support is easier to use and leads to higher performance...&] +[s5; [*C@5 Thread] class can start the thread and allows launching +thread to [*C@5 Wait] for its completion:&] +[s0; &] +[s7; Thread t;&] +[s7; t.Run(`[`] `{&] +[s7; -|for(int i `= 0; i < 10; i`+`+) `{&] +[s7; -|-|LOG(`"In the thread `" << i);&] +[s7; -|-|Sleep(100);&] +[s7; -|`}&] +[s7; -|LOG(`"Thread is ending...`");&] +[s7; `});&] +[s7; for(int i `= 0; i < 5; i`+`+) `{&] +[s7; -|LOG(`"In the main thread `" << i);&] +[s7; -|Sleep(100);&] +[s7; `}&] +[s7; LOG(`"About to wait for thread to finish`");&] +[s7; t.Wait();&] +[s7; LOG(`"Wait for thread done`");&] +[s0; &] +[s17; In the main thread 0&] +[s17; In the thread 0&] +[s17; In the thread 1&] +[s17; In the main thread 1&] +[s17; In the thread 2&] +[s17; In the main thread 2&] +[s17; In the main thread 3&] +[s17; In the thread 3&] +[s17; In the thread 4&] +[s17; In the main thread 4&] +[s17; About to wait for thread to finish&] +[s17; In the thread 5&] +[s17; In the thread 6&] +[s17; In the thread 7&] +[s17; In the thread 8&] +[s17; In the thread 9&] +[s17; Thread is ending...&] +[s17; Wait for thread done&] +[s0; &] +[s5; [*C@5 Thread] destructor calls [*C@5 Detach] method with `'disconnects`' +[*C@5 Thread] from the thread. Thread continues running.&] +[s5; [*C@5 Thread`::Start] static method launches a thread without possibility +to wait for its completion; if you need to wait, you have to +use some other method:&] +[s0; &] +[s7; bool x `= false;&] +[s7; &] +[s7; Thread`::Start(`[`&x`] `{ LOG(`"In the Started thread`"); x `= +true; `});&] +[s7; &] +[s7; LOG(`"About to wait for thread to finish`");&] +[s7; while(!x) `{ Sleep(1); `} // Do not do this in real code!&] +[s7; LOG(`"Wait for thread done`");&] +[s0; &] +[s17; About to wait for thread to finish&] +[s17; In the Started thread&] +[s17; Wait for thread done&] +[s0; &] +[s5; (method used here is horrible, but should demonstrate the point).&] +[s3;H4; 6.2 [C@5 Mutex]&] +[s5; Mutex (`"mutual exclusion`") is a well known concept in multithreaded +programming: When multiple threads write and read the same data, +the access has to be serialized using Mutex. Following invalid +code demonstrates why:&] +[s0; &] +[s7; Thread t;&] +[s7; &] +[s7; int sum `= 0;&] +[s7; t.Run(`[`&sum`] `{&] +[s7; -|for(int i `= 0; i < 1000000; i`+`+)&] +[s7; -|-|sum`+`+;&] +[s7; `});&] +[s7; &] +[s7; for(int i `= 0; i < 1000000; i`+`+)&] +[s7; -|sum`+`+;&] +[s7; &] +[s7; t.Wait();&] +[s7; DUMP(sum);&] +[s0; &] +[s17; sum `= 1061955&] +[s0; &] +[s5; While the expected value is 2000000, produced value is different. +The problem is that both thread read / modify / write [*C@5 sum] +value without any locking. Using [*C@5 Mutex] locks the [*C@5 sum] +and thus serializes access to it `- read / modify / write sequence + is now exclusive for the thread that has [*C@5 Mutex] locked, +this fixing the issue. [*C@5 Mutex] can be locked / unlocked with +[*C@5 Enter] / [*C@5 Leave] methods. Alternatively, [*C@5 Mutex`::Lock] +helper class locks [*C@5 Mutex] in constructor and unlocks it in +destructor:&] +[s0; &] +[s7; Mutex m;&] +[s7; sum `= 0;&] +[s7; t.Run(`[`&sum, `&m`] `{&] +[s7; -|for(int i `= 0; i < 1000000; i`+`+) `{&] +[s7; -|-|m.Enter();&] +[s7; -|-|sum`+`+;&] +[s7; -|-|m.Leave();&] +[s7; -|`}&] +[s7; `});&] +[s7; &] +[s7; for(int i `= 0; i < 1000000; i`+`+) `{&] +[s7; -|Mutex`::Lock `_`_(m); // Lock m till the end of scope&] +[s7; -|sum`+`+;&] +[s7; `}&] +[s7; &] +[s7; t.Wait();&] +[s7; DUMP(sum);&] +[s0; &] +[s17; sum `= 2000000&] +[s0; &] +[s3;H4; 6.3 [C@5 ConditionVariable]&] +[s5; [*C@5 ConditionVariable] in general is a synchronization primitive +used to block/awaken the thread. [*C@5 ConditionVariable] is associated +with [*C@5 Mutex] used to protect some data; in the thread that +is to be blocked, [*C@5 Mutex] has to locked; call to [*C@5 Wait] +atomically unlocks the [*C@5 Mutex] and puts the thread to waiting. +Another thread then can resume the thread by calling [*C@5 Signal], +which also causes [*C@5 Mutex] to lock again. Multiple threads +can be waiting on single [*C@5 ConditionVariable]; [*C@5 Signal] +resumes single waiting thread, [*C@5 Brodcast] resumes all waitng +threads.&] +[s0; &] +[s7; bool stop `= false;&] +[s7; BiVector data;&] +[s7; Mutex m;&] +[s7; ConditionVariable cv;&] +[s7; &] +[s7; Thread t;&] +[s7; t.Run(`[`&stop, `&data, `&m, `&cv`] `{&] +[s7; -|Mutex`::Lock `_`_(m);&] +[s7; -|for(;;) `{&] +[s7; -|-|while(data.GetCount()) `{&] +[s7; -|-|-|int q `= data.PopTail();&] +[s7; -|-|-|LOG(`"Data received: `" << q);&] +[s7; -|-|`}&] +[s7; -|-|if(stop)&] +[s7; -|-|-|break;&] +[s7; -|-|cv.Wait(m);&] +[s7; -|`}&] +[s7; `});&] +[s7; &] +[s7; for(int i `= 0; i < 10; i`+`+) `{&] +[s7; -|`{&] +[s7; -|-|Mutex`::Lock `_`_(m);&] +[s7; -|-|data.AddHead(i);&] +[s7; -|`}&] +[s7; -|cv.Signal();&] +[s7; -|Sleep(1);&] +[s7; `}&] +[s7; stop `= true;&] +[s7; cv.Signal();&] +[s7; t.Wait();&] +[s0; &] +[s17; Data received: 0&] +[s17; Data received: 1&] +[s17; Data received: 2&] +[s17; Data received: 3&] +[s17; Data received: 4&] +[s17; Data received: 5&] +[s17; Data received: 6&] +[s17; Data received: 7&] +[s17; Data received: 8&] +[s17; Data received: 9&] +[s0; &] +[s5; Important note: rarely thread can be resumed from [*C@5 Wait] even +if no other called [*C@5 Signal]. This is not a bug, but [^https`:`/`/en`.wikipedia`.org`/wiki`/Spurious`_wakeup^ d +esign decision for performance reason]. In practice it only means +that situation has to be (re)checked after resume.&] +[s3;H4; 6.4 [C@5 CoWork]&] +[s5; [*C@5 CoWork] is intented to be use when thread are used to speedup +code by distributing tasks over multiple CPU cores. [*C@5 CoWork] +spans a single set of worker threads that exist for the whole +duration of program run. [*C@5 CoWork] instances then manage assigning +jobs to these worker threads and waiting for the all work to +finish.&] +[s5; Job units to [*C@5 CoWork] are represented by [*C@5 Function] and thus can be written inline as lambdas.&] +[s5; As an example, following code reads input file by lines, splits +lines into words (this is the parallelized work) and then adds +resulting words to [*C@5 Index]:&] +[s0; &] +[s7; FileIn in(GetDataFile(`"test.txt`")); // let us open some tutorial +testing data&] +[s7; &] +[s7; Index w;&] +[s7; Mutex m; // need mutex to serialize access to w&] +[s7; &] +[s7; CoWork co;&] +[s7; while(!in.IsEof()) `{&] +[s7; -|String ln `= in.GetLine();&] +[s7; -|co `& `[ln, `&w, `&m`] `{&] +[s7; -|-|Vector h `= Split(ln, `[`](int c) `{ return IsAlpha(c) +? 0 : c; `});&] +[s7; -|-|Mutex`::Lock `_`_(m);&] +[s7; -|-|for(const auto`& s : h)&] +[s7; -|-|-|w.FindAdd(s);&] +[s7; -|`};&] +[s7; `}&] +[s7; co.Finish();&] +[s7; &] +[s7; DUMP(w);&] +[s0; &] +[s17; w `= `[Lorem, ipsum, dolor, sit, amet, consectetur, adipiscing, +elit, sed, do, eiusmod, tempor, incididunt, ut, labore, et, dolore, +magna, aliqua, Ut, enim, ad, minim, veniam, consequat, Duis, +aute, irure, in, reprehenderit, voluptate, velit, quis, nostrud, +exercitation, ullamco, laboris, nisi, aliquip, ex, ea, commodo, +esse, cillum, eu, fugiat, nulla, pariatur, Excepteur, sint, occaecat, +cupidatat, non, proident, sunt, culpa, qui, officia, deserunt, +mollit, anim, id, est, laborum`]&] +[s0; &] +[s5; Adding words to [*C@5 w] requires [*C@5 Mutex]. Alternative to this +`'result gathering`' [*C@5 Mutex] is CoWork`::FinLock. The idea +behind this is that CoWork requires an internal [*C@5 Mutex] to +serialize access to common data, so why [*C@5 FinLock] locks this +internal mutex a bit earlier, saving CPU cycles required to lock +and unlock dedicated mutex. From API contract perspective, you +can consider [*C@5 FinLock] to serialize code till the end of worker +job.&] +[s0; &] +[s7; in.Seek(0);&] +[s7; while(!in.IsEof()) `{&] +[s7; -|String ln `= in.GetLine();&] +[s7; -|co `& `[ln, `&w, `&m`] `{&] +[s7; -|-|Vector h `= Split(ln, `[`](int c) `{ return IsAlpha(c) +? 0 : c; `});&] +[s7; -|-|CoWork`::FinLock(); // replaces the mutex, locked till the +end of CoWork job&] +[s7; -|-|for(const auto`& s : h)&] +[s7; -|-|-|w.FindAdd(s);&] +[s7; -|`};&] +[s7; `}&] +[s7; co.Finish();&] +[s7; &] +[s7; DUMP(w);&] +[s0; &] +[s17; w `= `[Lorem, ipsum, dolor, sit, amet, consectetur, adipiscing, +elit, sed, do, eiusmod, tempor, incididunt, ut, labore, et, dolore, +magna, aliqua, Ut, enim, ad, minim, veniam, consequat, Duis, +aute, irure, in, reprehenderit, voluptate, velit, quis, nostrud, +exercitation, ullamco, laboris, nisi, aliquip, ex, ea, commodo, +esse, cillum, eu, fugiat, nulla, pariatur, Excepteur, sint, occaecat, +cupidatat, non, proident, sunt, culpa, qui, officia, deserunt, +mollit, anim, id, est, laborum`]&] +[s0; &] +[s5; Of course, the code performed after FinLock should not take +long, otherwise there is negative impact on all CoWork instances. +In fact, from this perspective, above code is probably past this +threshold...&] +[s3;H4; 6.5 CoPartition&] +[s5; There is some overhead associated with CoWork worker threads. +That is why e.g. performing a simple operation on the array spawning +worker thread for each element is not a good idea performance +wise:&] +[s0; &] +[s7; Vector data;&] +[s7; for(int i `= 0; i < 10000; i`+`+)&] +[s7; -|data.Add(i);&] +[s7; &] +[s7; int sum `= 0;&] +[s7; &] +[s7; CoWork co;&] +[s7; for(int i `= 0; i < data.GetCount(); i`+`+)&] +[s7; -|co `& `[i, `&sum, `&data`] `{ CoWork`::FinLock(); sum `+`= data`[i`]; +`};&] +[s7; co.Finish();&] +[s7; DUMP(sum);&] +[s0; &] +[s17; sum `= 49995000&] +[s0; &] +[s5; Above code computes the sum of all elements in the [*C@5 Vector], +using CoWorker job for each element. While producing the correct +reason, it is likely to run much slower than single`-threaded +version.&] +[s5; The solution to the problem is to split the array into small +number of larger subranges that are processed in parallel. This +is what [*C@5 CoPartition] template algorithm does:&] +[s0; &] +[s7; sum `= 0;&] +[s7; CoPartition(data, `[`&sum`](const auto`& subrange) `{&] +[s7; -|int partial`_sum `= 0;&] +[s7; -|for(const auto`& x : subrange)&] +[s7; -|-|partial`_sum `+`= x;&] +[s7; -|CoWork`::FinLock(); // available as CoPartition uses CoWork&] +[s7; -|sum `+`= partial`_sum;&] +[s7; `});&] +[s7; DUMP(sum);&] +[s0; &] +[s17; sum `= 49995000&] +[s0; &] +[s5; Note that CoPartition is still internally used, so [*C@5 CoWork`::FinLock] +is available. Instead of working on subranges, it is also possible +to use iterators:&] +[s0; &] +[s7; sum `= 0;&] +[s7; CoPartition(data.begin(), data.end(), `[`&sum`] (auto l, auto +h) `{&] +[s7; -|int partial`_sum `= 0;&] +[s7; -|while(l !`= h)&] +[s7; -|-|partial`_sum `+`= `*l`+`+;&] +[s7; -|CoWork`::FinLock(); // available as CoPartition uses CoWork&] +[s7; -|sum `+`= partial`_sum;&] +[s7; `});&] +[s7; DUMP(sum);&] +[s0; &] +[s17; sum `= 49995000&] +[s0; &] +[s5; There is no requirement on the type of iterators, so it is even +possible to use just indices:&] +[s0; &] +[s7; sum `= 0;&] +[s7; CoPartition(0, data.GetCount(), `[`&sum, `&data`] (int l, int +h) `{&] +[s7; -|int partial`_sum `= 0;&] +[s7; -|while(l !`= h)&] +[s7; -|-|partial`_sum `+`= data`[l`+`+`];&] +[s7; -|CoWork`::FinLock(); // available as CoPartition uses CoWork&] +[s7; -|sum `+`= partial`_sum;&] +[s7; `});&] +[s7; DUMP(sum);&] +[s0; &] +[s17; sum `= 49995000&] +[s0; &] +[s3;H4; 6.6 Parallel algorithms&] +[s5; U`+`+ provides a parallel versions of algorithms where it makes +sense. The naming scheme is `'Co`' prefix before the name of +algorithm designates the parallel version.&] +[s5; So the parallel version of e.g. [*C@5 FindIndex] is [*C@5 CoFindIndex], +for `'Sort`' it is `'CoSort`':&] +[s0; &] +[s7; Vector x`{ `"zero`", `"one`", `"two`", `"three`", `"four`", +`"five`" `};&] +[s7; &] +[s7; DUMP(FindIndex(x, `"two`"));&] +[s7; DUMP(CoFindIndex(x, `"two`"));&] +[s7; &] +[s7; CoSort(x);&] +[s7; DUMP(x);&] +[s0; &] +[s17; FindIndex(x, `"two`") `= 2&] +[s17; CoFindIndex(x, `"two`") `= 2&] +[s17; x `= `[five, four, one, three, two, zero`]&] +[s0; &] +[s5; Caution should be exercised when using these algorithms `- for +small datasets, they are almost certainly slower than single`-threaded +versions.&] [s0; &] [s3;%% Recommended tutorials:&] [s5;%% If you want to learn more, we have several tutorials that