1 // Written in the D programming language
2 /**
3  * Implementation of vector as a list of values.
4  * Features:
5  * $(OL
6  *      $(LI All vector actions is checked at compile time)
7  *      $(LI Vector contains only it's data, and nothing additional)
8  *      $(LI Almost all vector actions is `pure` and `nothrow`)
9  *      $(LI Vector can have one- and multiletter accessors)
10  * )
11  * 
12  * Usage:
13  * Create vector as in documentation below and treat it like a simple array (if
14  * you want to take some data from vector or put it in) or number (if you need
15  * to do some mathematical actions with vector).
16  * 
17  * Vector can have accessor - a named property method returning one of vector
18  * element. Accessor can be two types:
19  * $(OL
20  *      $(LI One-letter accessor. To create vector with this type you should
21  *           create vector like that:
22  * 
23  *           auto vec = Vector!(int, 2, "xy")(10, 20);
24  * 
25  *           Then you can call vector accessor:
26  * 
27  *           assert (vec.x == 10);
28  *      )
29  *      $(LI Multiletter accessor. To create vector with this type you should
30  *           create vector like that:
31  *           
32  *           auto vec = Vector!(int, 2, "col,row")(10, 20);
33  *           
34  *           Accessor delimiter in the accessors string should be ",".
35  *           Then you can call vector accessor:
36  * 
37  *           assert (vec.col == 10);
38  *      )
39  * )
40  * 
41  * Vector also has Orientation - a string that defines vector orientation:
42  * `horizontal` or `vertical`. It is needed only when vector is converted to
43  * matrix and by default is `horizontal`.
44  * 
45  * Copyright:
46  *      Copyright Vlad Rindevich, 2014.
47  * 
48  * License:
49  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
50  * 
51  * Authors:
52  *      Vlad Rindevich (rindevich.vs@gmail.com).
53  */
54 module mathed.types.vector;
55 
56 public import mathed.utils.traits : isVector;
57 
58 private 
59 {
60     import mathed.types.matrix : Matrix, DefaultInit, isMatrix, Matrix1i, Matrix3i;
61 
62     import std.traits : isNumeric;
63     import std.array : appender, split;
64     import std.conv : to;
65     import std.string : format;
66 }
67 
68 alias Vector!2 Vector2f;
69 alias Vector!3 Vector3f;
70 alias Vector!4 Vector4f;
71 
72 alias Vector!(2, int) Vector2i;
73 alias Vector!(3, int) Vector3i;
74 alias Vector!(4, int) Vector4i;
75 
76 alias Vector!(2, float, "xy")  Planef;
77 alias Vector!(3, float, "xyz") Stereof;
78 
79 alias Vector!(2, int, "xy")  Planei;
80 alias Vector!(3, int, "xyz") Stereoi;
81 
82 /**
83  * Main vector interface.
84  */
85 struct Vector (size_t Size, Type = float, string Accessors = "", 
86                         string Orientation = "horizontal")
87     if (Size > 0)
88 {
89     static assert (isAcceptableSize!(Accessors, Size),
90                    format (INACCEPTABLE_SIZE, CountAccessors (Accessors)));
91 
92     static assert (isAcceptableType!Orientation,
93                    format (INACCEPTABLE_TYPE, Orientation));
94 
95     private
96     {
97         alias Vector!(Size, Type, Accessors, Orientation) Self;
98         
99         /*
100          * Vector core array.
101          */
102         static if (isNumeric!Type)
103             Type[Size] data = mixin (DefaultInit (Size));
104         else
105             Type[Size] data;
106 
107         // Gets the `Accessors` string
108         alias Accessors accessors;
109 
110         // Gets vector orientation
111         alias Orientation orientation;
112     }
113 
114     /// Gets vector size (quantity of it's elements).
115     alias Size size;
116 
117     /// Gets type of vector data
118     alias Type type;
119 
120     unittest
121     {
122         auto v = Vector3i (1, 2, 3);
123         assert (v.size == 3);
124     }
125 
126 @trusted:
127 
128     /**
129      * Vector default constructor. 
130      */
131     this (Type[Size] values...) pure nothrow
132     {
133         set (values);
134     }
135 
136     static if (Accessors != "")
137         mixin (AttrAccessor (Accessors, Size));
138 
139     ///
140     unittest
141     {
142         // Creating a vector with accessors
143         auto v = Vector!(3, int, "xyz")(1, 2, 3);
144 
145         // Testing accessor methods.
146         assert (v.x == 1);
147         assert (v.y == 2);
148         assert (v.z == 3);
149     }
150 
151     /**
152      * Sets all vector values in one action.
153      */
154     void set (Type[Size] values...) pure nothrow
155     {
156         foreach (size_t index, ref element; data)
157             element = values[index];
158     }
159 
160     ///
161     unittest
162     {
163         // Creating vector
164         auto v = Vector3i (1, 2, 3);
165 
166         // Change data in one action
167         v.set (3, 2, 1);
168         assert (v[0] == 3 && v[1] == 2 && v[2] == 1);
169     }
170 
171     /**
172      * Stringifies vector.
173      */
174     string toString () { return data.to!string (); } 
175 
176     unittest
177     {
178         auto v = Vector3i (1, 2, 3);
179         auto str = v.to!string ();
180         assert (str == "[1, 2, 3]");
181     }
182 
183     /**
184      * Gets vector element.
185      */
186     ref auto opIndex (size_t element) pure nothrow
187     {
188         return data[element];
189     }
190 
191     unittest
192     {
193         auto v = Vector3i (1, 2, 3);
194         assert (v[1] == 2);
195     }
196 
197     /**
198      * Iterates vector.
199      */
200     int opApply (int delegate (ref Type) foreach_)
201     {
202         int result;
203         
204         foreach (size_t index, ref element; data)
205         {
206             result = foreach_ (element);
207             if (result) break;
208         }
209         
210         return result;
211     }
212 
213     /// ditto
214     int opApply (int delegate (ref size_t, ref Type) foreach_)
215     {
216         int result;
217         
218         foreach (size_t index, ref element; data)
219         {
220             result = foreach_ (index, element);
221             if (result) break;
222         }
223         
224         return result;
225     }
226 
227     /**
228      * Inverses vector sign.
229      */
230     auto opUnary(string op)() pure nothrow
231         if( op == "-" )
232     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
233     body
234     {
235         return opBinary!"*" (-1);
236     }
237 
238     /**
239      * Processes vector addition and subtraction.
240      */ 
241     Self opBinary (string op, SumType, alias string SumAccessors)
242                   (in Vector!(Size, SumType, SumAccessors, Orientation) summand) pure nothrow
243         if ((op == "+" || op == "-") && is (SumType : Type))
244     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
245     body
246     {
247         Self newVector;
248         foreach (size_t index, ref element; newVector.data)
249             mixin ("element = data[index] " ~ op ~ " summand.data[index];");
250         return newVector;
251     }
252 
253     /// ditto
254     void opOpAssign (string op, SumType, alias string SumAccessors)
255                     (in Vector!(Size, SumType, SumAccessors, Orientation) summand) pure nothrow
256         if ((op == "+" || op == "-") && is (SumType : Type))
257     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
258     body
259     {
260         this = opBinary!op (summand);
261     }
262 
263     unittest
264     {
265         auto v = Vector3i (1, 2, 3);
266         auto w = Vector3i (3, 2, 1);
267         assert (v + w == Vector3i (4, 4, 4));
268     }
269 
270     /**
271      * Processes vector multiplication and division with number.
272      */
273     Self opBinary (string op, Number)(in Number num) pure nothrow
274         if ((op == "*" || op == "/") && isNumeric!Number)
275     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
276     body
277     {
278         Self newVector;
279         foreach (size_t index, ref element; newVector.data)
280             mixin ("element = data[index] " ~ op ~ " num;");
281         return newVector;
282     }
283 
284     /// ditto
285     Self opBinaryRight (string op, Number)(in Number num) pure nothrow
286         if ((op == "*" || op == "/") && isNumeric!Number)
287     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
288     body
289     {
290         return opBinary!op (num);
291     }
292 
293     /// ditto
294     void opOpAssign (string op, Number)(in Number num) pure nothrow
295         if ((op == "*" || op == "/") && isNumeric!Number)
296     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
297     body
298     {
299         this = opBinary!op (num);
300     }
301 
302     unittest
303     {
304         auto v = Vector3i (1, 2, 3);
305         assert (v * 2 == Vector3i (2, 4, 6));
306         v *= 2;
307         assert (v == Vector3i (2, 4, 6));
308     }
309 
310     /**
311      * Processes vector multiplication with another vector. Due to mathematical
312      * restrictions that vector can be multiplied or divided only by
313      * perpendicular vector, both vectors will be converted to matrix and
314      * then processed.
315      */
316     auto opBinary (string op, Factor)(Factor factor) pure nothrow
317         if (op == "*" && isVector!Factor)
318     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
319     body
320     {
321         return this.toMatrix () * factor.toMatrix ();
322     }
323 
324     unittest
325     {
326         auto v = Vector3i (1, 2, 3);
327         auto w = Vector!(3, int, "", "vertical") (1, 2, 3);
328         assert (v * w == Matrix1i (14));
329 
330         auto v2 = Vector!(3, int, "", "vertical") (1, 2, 3);
331         auto w2 = Vector3i (1, 2, 3);
332 
333         auto equalMatrix = Matrix3i
334         (
335             1, 2, 3,
336             2, 4, 6,
337             3, 6, 9
338         );
339 
340         assert (v2 * w2 == equalMatrix);
341     }
342 
343     /**
344      * Processes casting current vector to a new type of vector. Parametres that
345      * can be cange are: vector type, accessors and orientation.
346      */
347     NewType opCast (NewType)() pure nothrow
348         if (isVector!NewType && NewType.size == Size
349             && (is (NewType.type : Type) || is (Type : NewType.type)))
350     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
351     body
352     {
353         NewType newVector;
354         
355         foreach (size_t index, ref element; newVector.data)
356             element = cast(NewType.type) data[index];
357         
358         return newVector;
359     }
360 
361     /// ditto
362     Vector!(Size, NewType, NewAccessors, NewOrientation)
363     castTo (NewType, 
364             string NewAccessors = Accessors,
365             string NewOrientation = Orientation)()
366         if (is (NewType : Type) || is (Type : NewType))
367     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
368     body
369     {
370         return cast(Vector!(Size, NewType, NewAccessors, NewOrientation)) this;
371     }
372 
373     unittest
374     {
375         auto v = Vector!(4, int)(1, 2, 3, 4);
376         auto w = cast(Vector!4) v;
377         auto x = v.castTo!double ();
378 
379         assert (is (w.type == float));
380         assert (is (x.type == double));
381 
382         assert (is (typeof (w[0]) == float));
383         assert (is (typeof (x[0]) == double));
384 
385         assert (isVector!w);
386         assert (isVector!x);
387     }
388 
389     /**
390      * Casts vector to a string.
391      */
392     string opCast (NewType)()
393         if (is (NewType == string))
394     {
395         return toString ();
396     }
397 
398     /**
399      * Converts vector to one-lined or one-columned matrix depending on
400      * Orientation.
401      */
402     auto toMatrix () pure nothrow
403     {
404         static if (Orientation == "vertical")
405             return Matrix!(Size, 1, Type)(data);
406         else
407             return Matrix!(1, Size, Type)(data);
408     }
409 
410     /**
411      * Transposes vector.
412      */
413     @property auto t () pure nothrow
414     {
415         static if (Orientation == "vertical")
416             return Vector!(Size, Type, Accessors, "horizontal")(data);
417         else
418             return Vector!(Size, Type, Accessors, "vertical")(data);
419     }
420 
421     unittest
422     {
423         auto v = Vector3i (1, 2, 3);
424         assert (v.t.toMatrix ().lines == 3);
425     }
426 }
427 
428 private:
429 
430 // Compile-time vector accessors counting.
431 size_t CountAccessors (string Accessors, size_t Size) pure @trusted
432 {
433     if (Size == 1)
434         return Accessors.length == 0 ? 0 : 1;
435     else if (!Accessors.hasSymbol (','))
436         return Accessors.length;
437     else
438         return Accessors.split (',').length;
439 }
440 
441 // Compile-time test for char existence in the string.
442 bool hasSymbol (string str, char sym) pure nothrow @trusted
443 {
444     bool has;
445 
446     foreach (ref letter; str)
447         if (letter == sym)
448             has = true;
449 
450     return has;
451 }
452 
453 // Compile-time generation of accessor methods.
454 string AttrAccessor (string Accessors, size_t Size) pure @trusted
455 {
456     string result;
457     string code = q{
458         @property ref Type %1$s () pure nothrow @trusted
459         {
460             return data[%2$s];
461         }
462     };
463 
464     if (Size == 1 )
465         result ~= format (code, Accessors, 0);
466     else if (Size > 1 && !Accessors.hasSymbol (','))
467         foreach (size_t index, accessor; Accessors)
468             result ~= format (code, accessor, index);
469     else
470     {
471         string[] AccessorsList;
472 
473         if (Accessors.hasSymbol (','))
474             AccessorsList = Accessors.split (',');
475 
476         foreach (size_t index, accessor; AccessorsList)
477             result ~= format (code, accessor, index);
478     }
479     
480     return result;
481 }
482 
483 pure @trusted template isAcceptableSize (string Accessors, size_t Size)
484 {
485     enum isAcceptableSize = CountAccessors (Accessors, Size) == 0 
486                             || CountAccessors (Accessors, Size) == Size;
487 }
488 
489 pure nothrow @trusted template isAcceptableType (string Type)
490 {
491     enum isAcceptableType = Type == "horizontal" 
492                             || Type == "vertical";
493 }
494 
495 enum 
496 {
497     NOT_NUMERIC_FORBIDDEN = "Impossible to apply mathematical action to "
498                             ~ "not-numeric vector",
499     INACCEPTABLE_SIZE = "Attribute accessors should be equal to vector size by "
500                         ~ "quantity or be empty string, not %s",
501     INACCEPTABLE_TYPE = "Orientation should be `horizontal` or `vertical`, not %s"
502 }