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