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 }