1 module nxt.inplace_algorithm;
2 
3 import std.functional : unaryFun;
4 
5 import nxt.container_traits : isSet;
6 import nxt.typecons_ex : hasIndexing;
7 
8 version(unittest)
9 {
10     import std.algorithm.comparison : equal;
11     import nxt.dbgio : dbg;
12 }
13 
14 /** Returns: `r` eagerly in-place filtered on `predicate`.
15  *
16  * TODO Move to free function in array_ex.d to get @trusted access to private Array._mptr
17  */
18 C filteredInplace(alias predicate, C)(C r) @trusted // TODO remove @trusted
19 if (is(typeof(unaryFun!predicate)) &&
20     hasIndexing!C)          // TODO extend to `isArrayContainer`!C eller `isRandomAccessContainer!C`
21 {
22     import core.internal.traits : hasElaborateDestructor, Unqual;
23     import std.traits : isMutable, hasIndirections;
24     import std.range.primitives : ElementType;
25     import std.algorithm.mutation : move;
26     import nxt.traits_ex : ownsItsElements;
27 
28     alias pred = unaryFun!predicate;
29     alias E = ElementType!C;
30     alias MutableC = Unqual!C;
31 
32     static if (__traits(hasMember, r, `ptr`))
33     {
34         size_t dstIx = 0;           // destination index
35 
36         // skip leading passing elements
37         // TODO reuse .indexOf!(_ => !pred(_)) algorithm in `Array`
38         while (dstIx < r.length && pred(r.ptr[dstIx]))
39         {
40             dstIx += 1;
41         }
42 
43         // inline filtering
44         foreach (immutable srcIx; dstIx + 1 .. r.length)
45         {
46             // TODO move this into @trusted member of Array
47             if (pred(r.ptr[srcIx]))
48             {
49                 static if (isMutable!E &&
50                            !hasIndirections!E)
51                 {
52                     move(r.ptr[srcIx], r.ptr[dstIx]); // TODO reuse function in array
53                 }
54                 else static if (ownsItsElements!C)
55                 {
56                     move(r.ptr[srcIx], r.ptr[dstIx]); // TODO reuse function in array
57                 }
58                 else
59                 {
60                     static assert(0, `Cannot move elements in instance of ` ~ C.stringof);
61                 }
62                 dstIx += 1;
63             }
64             else
65             {
66                 static if (hasElaborateDestructor!E)
67                 {
68                     .destroy(e);
69                 }
70             }
71         }
72 
73         static if (__traits(hasMember, C, `shrinkTo`))
74         {
75             r.shrinkTo(dstIx);  // length will all always shrink
76         }
77         else
78         {
79             r.length = dstIx;
80         }
81     }
82 
83     return move(r);             // TODO remove move when compiler does it for us
84 }
85 
86 /** Returns: `r` eagerly in-place filtered on `predicate`.
87  */
88 C filteredInplace(alias predicate, C)(C r)
89 if (isSet!C &&
90     is(typeof(unaryFun!predicate(C.ElementType.init))))
91 {
92     import std.algorithm.mutation : move;
93     static if (__traits(hasMember, C, "remove"))
94     {
95         import std.functional : not;
96         import nxt.sso_hashmap_or_hashset : filtered;
97         return move(r).filtered!predicate(); // TODO remove move when compiler does it for us
98     }
99     else
100     {
101         C s;
102         import std.algorithm.iteration : filter;
103         foreach (e; r[].filter!predicate)
104         {
105             s.insert(e);
106         }
107         return move(s);             // TODO remove move when compiler does it for us
108     }
109 }
110 
111 /// inplace filtering on hashset
112 version(none)                   // TODO activate or remove SSOHashSet
113 @safe pure nothrow @nogc unittest
114 {
115     import std.algorithm.iteration : filter;
116     import nxt.sso_hashmap_or_hashset : SSOHashSet, byElement;
117     import nxt.digestx.fnv : FNV;
118 
119     alias X = SSOHashSet!(uint, null, FNV!(64, true));
120     enum pred = "a != 11";
121     // alias pred = (_) => _ != 1;
122 
123     // TODO activate
124     // auto x = X.withElements([11, 12].s).filteredInplace!pred.byElement;
125     // assert(x.front == 12);
126 
127     // x.popFront();
128     // assert(x.empty);
129 }
130 
131 /** Fyilter `r` eagerly in-place using `predicate`. */
132 void filterInplace(alias predicate, C)(ref C r) @trusted // TODO remove @trusted
133 if (hasIndexing!C && // TODO extend to `isArrayContainer`!C eller `isRandomAccessContainer!C`
134     is(typeof(unaryFun!predicate)))
135 {
136     import std.algorithm.mutation : move;
137     r = move(r).filteredInplace!predicate(); // TODO remove move when compiler does it for us
138 }
139 
140 @safe pure nothrow @nogc unittest
141 {
142     import std.algorithm.mutation : move;
143     import std.meta : AliasSeq;
144     import nxt.unique_range : intoUniqueRange;
145     import nxt.dynamic_array : DynamicArray;
146 
147     alias E = int;
148     foreach (C; AliasSeq!(DynamicArray// ,
149                           // TODO SortedSetUniqueArray
150                  ))
151     {
152         alias A = C!E;
153 
154         static assert(is(A == typeof(A().filteredInplace!(_ => _ & 1))));
155 
156         // empty case
157         immutable E[0] c0 = [];
158         assert(A.withCapacity(0)
159                 .filteredInplace!(_ => _ & 1)
160                 .intoUniqueRange()
161                 .equal(c0[]));
162 
163         // few elements triggers small-array optimization
164         immutable E[2] c2 = [3, 11];
165         auto a2 = A([2, 3, 11, 12].s);
166         assert(move(a2).filteredInplace!(_ => _ & 1)
167                        .intoUniqueRange()
168                        .equal(c2[]));
169 
170         // odd elements
171         immutable E[6] c6 = [3, 11, 13, 15, 17, 19];
172         auto a6 = A([3, 11, 12, 13, 14, 15, 16, 17, 18, 19].s);
173         assert(move(a6).filteredInplace!(_ => _ & 1)
174                        .intoUniqueRange()
175                        .equal(c6[]));
176 
177         // elements less than or equal to limit
178         immutable E[7] c7 = [3, 11, 12, 13, 14, 15, 16];
179         auto a7 = A([3, 11, 12, 13, 14, 15, 16, 17, 18, 19].s
180             );
181         assert(move(a7).filteredInplace!(_ => _ <= 16)
182                        .intoUniqueRange()
183                        .equal(c7[]));
184 
185         auto a3 = A([2, 4, 11].s);
186         a3.filterInplace!(_ => _ & 1);
187         assert(a3.length == 1);
188         assert(a3[0] == 11);
189     }
190 
191 }
192 
193 version(unittest)
194 {
195     import nxt.array_help : s;
196 }