1 /**
2     Compacts a range
3 */
4 module ddash.algorithm.compact;
5 
6 ///
7 unittest {
8     import optional: no, some;
9     import ddash.utils: isFalsey;
10 
11     // compact falsy values
12     assert([0, 1, 2, 0, 3].compact!isFalsey.equal([1, 2, 3]));
13 
14     // compact empty arrays
15     assert([[1], [], [2]].compact!isFalsey.equal([[1], [2]]));
16 
17     // compact optionals
18     assert([some(2), no!int].compact!isFalsey.equal([some(2)]));
19 
20     class C {
21         int i;
22         this(int i) { this.i = i; }
23     }
24 
25     import std.algorithm: map;
26     auto arr = [new C(1), null, new C(2), null];
27     assert(arr.compact.map!"a.i".equal([1, 2]));
28 
29     struct A {
30         int x;
31     }
32 
33     // compact by a object member
34     assert([A(7), A(0)].compactBy!("x", isFalsey).equal([A(7)]));
35 
36     // compact an associative array
37     auto aa = ["a": 1, "b": 0, "c": 2];
38     assert(aa.compactValues!isFalsey == ["a": 1, "c": 2]);
39 }
40 
41 import ddash.common;
42 
43 /**
44     Compacts a range
45 
46     Params:
47         pred = a unary predicate that returns true if value should be compacted
48         range = an input range
49 
50     Returns:
51         compacted range
52 
53     Since:
54         0.0.1
55 */
56 auto compact(alias pred = null, Range)(Range range) if (from!"std.range".isInputRange!Range) {
57     return compactBase!("", pred)(range);
58 }
59 
60 ///
61 unittest {
62     import optional: no, some;
63     import ddash.utils: isFalsey;
64     assert([0, 1, 2, 0, 3].compact!(isFalsey).equal([1, 2, 3]));
65     assert([[1], [], [2]].compact!isFalsey.equal([[1], [2]]));
66     assert([some(2), no!int].compact!isFalsey.equal([some(2)]));
67 
68     class C {
69         int i;
70         this(int i) { this.i = i; }
71     }
72 
73     import std.algorithm: map;
74     auto arr = [new C(1), null, new C(2), null];
75     assert(arr.compact.map!(a => a.i).equal([1, 2]));
76 }
77 
78 /**
79     Compacts a range by a publicly visible member variable or property of `ElemntType!Range`
80 
81     Params:
82         member = which member in `ElementType!Range` to compact by
83         pred = a unary predicate that returns true if value should be compacted
84         range = an input range
85 
86     Returns:
87         compacted range
88 
89     Since:
90         0.0.1
91 */
92 auto compactBy(string member, alias pred = null, Range)(Range range) if (from!"std.range".isInputRange!Range) {
93     return compactBase!(member, pred)(range);
94 }
95 
96 private auto compactBase(string member, alias pred = null, Range)(Range range) if (from!"std.range".isInputRange!Range) {
97     import std.algorithm: filter;
98     import bolts: isNullType, isUnaryOver;
99     import ddash.common.valueby;
100     import std.range: ElementType;
101 
102     alias E = ElementType!Range;
103 
104     static if (isNullType!pred)
105     {
106         import bolts: isNullable;
107         static assert(
108             isNullable!E,
109             "Cannot compact non-nullable type `" ~ E.stringod ~ "'",
110         );
111         alias fun = (a) => valueBy!member(a) !is null;
112     }
113     else static if (isUnaryOver!(pred, typeof(valueBy!member(E.init))))
114     {
115         alias fun = a => !pred(valueBy!member(a));
116     }
117     else
118     {
119         static assert(0, "predicate must either be null or bool function(" ~ E.stringof ~ ")");
120     }
121 
122     return range.filter!fun;
123 }
124 
125 ///
126 unittest {
127     import ddash.utils: isFalsey;
128     struct A {
129         int x;
130         private int y;
131     }
132     assert([A(3, 2), A(0, 1)].compactBy!("x", isFalsey).equal([A(3, 2)]));
133     assert(!__traits(compiles, [A(3, 2)].compactBy!("y", isFalsey)));
134     assert(!__traits(compiles, [A(3, 2)].compactBy!("z", isFalsey)));
135     assert(!__traits(compiles, [A(3, 2)].compactBy!("", isFalsey)));
136 }
137 
138 /**
139     Compacts an associative array by its values
140 
141     Params:
142         pred = a unary predicate that returns true if value should be compacted
143         aa = compacted associated array
144 
145     Returns:
146         compacted associtive array
147 
148     Since:
149         0.0.1
150 */
151 auto compactValues(alias pred = null, T, U)(T[U] aa) {
152     import std.array: byPair, assocArray;
153     return aa
154         .byPair
155         .compactBy!("value", pred)
156         .assocArray;
157 }
158 
159 ///
160 unittest {
161     import ddash.utils: isFalsey;
162     auto aa = ["a": 1, "b": 0, "c": 2];
163     assert(aa.compactValues!isFalsey == ["a": 1, "c": 2]);
164 }
165 
166 /**
167     Compacts a list of values
168 
169     Params:
170         values = list of values that share a common type
171 
172     Returns:
173         Compacted array of values cast to common type T
174 
175     Since:
176         0.0.1
177 */
178 auto compact(alias pred = null, Values...)(Values values) if (!is(from!"std.traits".CommonType!Values == void)) {
179     import ddash.algorithm: concat;
180     return concat(values)
181         .compactBase!("", pred);
182 }
183 
184 ///
185 unittest {
186     import ddash.utils: isFalsey;
187     auto a = compact!isFalsey(1, 0, 2, 0, 3);
188     auto b = compact!isFalsey(1, 0, 2.0, 0, 3);
189 
190     assert(a.equal([1, 2, 3]));
191     assert(b.equal([1, 2, 3]));
192 
193     static assert(is(typeof(a.array) == int[]));
194     static assert(is(typeof(b.array) == double[]));
195 }