1 module dataframes.columns;
2 
3 import std.traits;
4 import std.array;
5 
6 import dataframes.helpers;
7 
8 /**
9  * DataFrame Column to store all the elements of
10  * the column.
11  */
12 struct Column(T)
13 {
14     T[] values;
15     int shiftBy;
16     size_t currentFrontIndex = 0;
17     size_t currentBackIndex;
18 
19     this(T[] values)
20     {
21         this.values = values;
22         currentBackIndex = values.length;
23     }
24 
25     /**
26      * Get the length of Items in the column
27      */
28     size_t length()
29     {
30         return values.length;
31     }
32 
33     /**
34      * Assign the Column data at once.
35      */
36     void opAssign(T[] rhs)
37     {
38         // Length is set by the dataframe so insert data
39         if (values.length != rhs.length)
40             throw new DataFrameException("All arrays must be of the same length");
41 
42         values = rhs;
43     }
44 
45     void opAssign(Column!T rhs)
46     {
47         // Length is set by the dataframe so insert data
48         if (values.length != rhs.length)
49             throw new DataFrameException("All arrays must be of the same length");
50 
51         values = rhs.values;
52     }
53 
54     void opOpAssign(string op : "~")(T rhs)
55     {
56         values ~= rhs;
57     }
58 
59     size_t opDollar(size_t pos)()
60     {
61         return values.length;
62     }
63 
64     auto opSlice()(size_t start, size_t end)
65     {
66         return values[start .. end];
67     }
68 
69     T opIndex(size_t idx)
70     {
71         auto newIdx = idx - this.shiftBy;
72         if (shiftBy != 0 && (newIdx < 0 || newIdx >= values.length)) return T.init;
73 
74         return values[newIdx];
75     }
76 
77     // TODO: Support all Operations?
78     // +	-	*	/	%	^^	&
79     // |	^	<<	>>	>>>	~	in
80     static if (isNumeric!T)
81     {
82         auto opBinary(string op, T2)(T2 rhs)
83         {
84             static if (isNumeric!T2)
85                 alias OutputType = CommonType!(T, T2);
86             else
87                 alias OutputType = CommonType!(T, typeof(rhs.values[0]));
88 
89             Column!OutputType output;
90             foreach(idx, d; values)
91             {
92                 static if (isNumeric!T2)
93                     auto rhsValue = rhs;
94                 else
95                     auto rhsValue = rhs[idx];
96 
97                 switch(op)
98                 {
99                 case "+":
100                     output.values ~= d + rhsValue;
101                     break;
102                 case "/":
103                     output.values ~= d / rhsValue;
104                     break;
105                 case "-":
106                     output.values ~= d - rhsValue;
107                     break;
108                 case "*":
109                     output.values ~= d * rhsValue;
110                     break;
111                 default:
112                     assert(0, "Operator " ~ op ~ " not implemented");
113                 }
114             }
115             return output;
116         }
117     }
118 
119     T front()
120     {
121         return this[currentFrontIndex];
122     }
123 
124     void popFront()
125     {
126         currentFrontIndex++;
127     }
128 
129     bool empty()
130     {
131         return currentFrontIndex >= this.values.length || currentBackIndex < 0;
132     }
133 
134     T back()
135     {
136         return this[currentBackIndex];
137     }
138 
139     void popBack()
140     {
141         currentBackIndex--;
142     }
143 
144     Column!T shift(int value)
145     {
146         Column!T newCol;
147         this.shiftBy = value;
148         return this;
149     }
150 
151     bool opEquals(Column!T rhs)
152     {
153         import std.math.operations : isClose;
154         import std.range : enumerate;
155         import std.math : isNaN;
156 
157         if (this.length != rhs.length) return false;
158 
159         foreach(idx, _value; this.values.enumerate)
160         {
161             static if (isFloatingPoint!T) {
162                 if ((this[idx].isNaN && !rhs[idx].isNaN) || (!this[idx].isNaN && rhs[idx].isNaN)) return false;
163                 else continue;
164             }
165 
166             if (!isClose(this[idx], rhs[idx])) return false;
167         }
168         return true;
169     }
170 }
171 
172 unittest
173 {
174     auto data = Column!double([100.0, 200.0, 300.0]);
175     assert(data.length == 3);
176 
177     auto c1 = Column!int([10, 20]);
178     auto c2 = Column!int([10, 20]);
179     assert(c1 + c2 == Column!int([20, 40]));
180 
181     auto price = Column!double([25, 30]);
182     auto quantity = Column!int([10, 5]);
183     auto unitPrice = price / quantity;
184 
185     assert(unitPrice == Column!double([2.5, 6]));
186 
187     auto c3 = Column!double([100, 200, 300]);
188     auto c4 = Column!double([10, 20, 30]);
189 
190     assert(c3+c4.shift(1) == Column!double([double.init, 210, 320]));
191     assert(c3+c4.shift(-1) == Column!double([120, 230, double.init]));
192 
193     import std.algorithm.iteration : sum, filter;
194     import std.math : isNaN;
195 
196     assert(c3.shift(1).filter!(n => !n.isNaN).sum == 300);
197     assert(c3.shift(-1).filter!(n => !n.isNaN).sum == 500);
198 
199     auto col1 = Column!int([10, 20, 30, 40, 50]);
200     auto avgCol = (col1 + col1.shift(1)) / 2;
201     assert(avgCol == Column!int([5, 15, 25, 35, 45]));
202 }