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 }