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