Vector: Difference between revisions
(Tcl implementation added) |
(→{{header|jq}}: tweak to work with jq 1.4; some generalizations for vectors of any dimension) |
||
Line 48: | Line 48: | ||
=={{header|jq}}== |
=={{header|jq}}== |
||
{{works with|jq|1.4}} |
|||
In the following, the vector [x,y] is represented by the JSON array [x,y]. |
In the following, the vector [x,y] is represented by the JSON array [x,y]. |
||
For generality, the pointwise operations (multiply, divide, negate) |
|||
will work with conformal arrays of any dimension, and |
|||
sum/0 accepts any number of same-dimensional vectors. |
|||
<lang jq>def polar(r; angle): |
<lang jq>def polar(r; angle): |
||
[ r*(angle|cos), r*(angle|sin) ]; |
[ r*(angle|cos), r*(angle|sin) ]; |
||
# If your jq allows multi-arity functions, you may wish to uncomment the following line: |
|||
def polar(r): [r, 0]; |
# def polar(r): [r, 0]; |
||
def |
def polar2vector: polar(.[0]; .[1]); |
||
def vector(x; y): |
def vector(x; y): |
||
if (x|type) == "number" and (y|type) == "number" then [x,y] |
if (x|type) == "number" and (y|type) == "number" then [x,y] |
||
else "TypeError" |
else error("TypeError") |
||
end; |
end; |
||
# Input: an array of vectors to be added |
# Input: an array of same-dimensional vectors of any dimension to be added |
||
def sum: |
def sum: |
||
def sum2: .[0] as $a | .[1] as $b | reduce range(0;$a|length) as $i ($a; .[$i] += $b[$i]); |
|||
if length <= 1 then . |
|||
else reduce .[1:][] as $v (.[0] ; [., $v]|sum2) |
|||
end; |
|||
def |
def multiply(scalar): [ .[] * scalar ]; |
||
⚫ | |||
def minus(v): [., (v|negate)] | sum; |
def minus(v): [., (v|negate)] | sum; |
||
⚫ | |||
[ scalar * .[0], scalar * .[1] ]; |
|||
def divide(scalar): |
def divide(scalar): |
||
if scalar == 0 then "division of a vector by 0 is not supported" |
if scalar == 0 then error("division of a vector by 0 is not supported") |
||
else [ .[ |
else [ .[] / scalar ] |
||
end; |
end; |
||
Line 91: | Line 99: | ||
def topolar: [r, angle];</lang> |
def topolar: [r, angle];</lang> |
||
'''Examples |
'''Examples''' |
||
<lang jq>def |
<lang jq>def examples: |
||
def pi: 1 | atan * 4; |
|||
[1,1] as $v |
[1,1] as $v |
||
Line 105: | Line 114: | ||
"w * 5 is \($w | multiply(5))", |
"w * 5 is \($w | multiply(5))", |
||
"w / 2 is \($w | divide(2))", |
"w / 2 is \($w | divide(2))", |
||
"v|topolar is \($v|topolar)", |
"v|topolar is \($v|topolar)", |
||
"w|topolar is \($w|topolar)", |
"w|topolar is \($w|topolar)", |
||
"z = polar(1; pi/2) is \($z)", |
"z = polar(1; pi/2) is \($z)", |
||
"z|topolar is \($z|topolar)", |
"z|topolar is \($z|topolar)", |
||
"z2 = polar(-2; pi/4) is \($z2)", |
"z2 = polar(-2; pi/4) is \($z2)", |
||
"z2|topolar is \($z2|topolar)", |
"z2|topolar is \($z2|topolar)", |
||
"z2|topolar|polar is \($z2|topolar| |
"z2|topolar|polar is \($z2|topolar|polar2vector)" ; |
||
examples</lang> |
|||
{{out}} |
{{out}} |
||
<lang sh>$ jq -r -n -f vector.jq |
<lang sh>$ jq -r -n -f vector.jq |
Revision as of 17:38, 19 June 2015
Implement a Vector class (or a set of functions) that models a Physical Vector. You should implement the four basic operations and a 'pretty print' function. Your Vector may be initialized in any reasonable way.
- Start and end points and direction - Angular coefficient and value (length)
The four operations to be implemented are:
- Vector+Vector addition
- Vector-Vector subtraction
- Vector*scalar multiplication
- Vector/scalar division
J
These are primitive (built in) operations in J:
<lang J> 5 7+2 3 7 10
5 7-2 3
3 4
5 7*11
55 77
5 7%2
2.5 3.5</lang>
A few things here might be worth noting:
J treats a sequences of space separated numbers as a single word, this is analogous to how languages which support a "string" data type support treating strings with spaces in them as single words. Put differently: '5 7' is a sequence of three characters but 5 7 (without the quotes) is a sequence of two numbers.
J uses the percent sign to represent division. This is a visual pun with the "division sign" or "obelus" which has been used to represent the division operation for hundreds of years.
In J, a single number (or single character) is special. It's not a treated as a sequence except in contexts where you explicitly declare it to be one (for example, by prefixing it with a comma). (If it were treated as a sequence the above 5 7*11
and 5 7%2
operations would have been errors, because of the vector length mis-match.)
It's perhaps also worth noting that J allows you to specify complex numbers using polar coordinates, and complex numbers can be converted to vectors using the special token (+.) - for example:
<lang J> 2ad45 1.41421j1.41421
+. 2ad45
1.41421 1.41421
2ar0.785398
1.41421j1.41421
+. 2ar0.785398
1.41421 1.41421</lang>
In the construction of these numeric constants, ad
is followed by an angle in degrees while ar
is followed by an angle in radians. This practice of embedding letters in a numeric constant is analogous to the use of exponential notation when describing some floating point numbers.
jq
In the following, the vector [x,y] is represented by the JSON array [x,y].
For generality, the pointwise operations (multiply, divide, negate) will work with conformal arrays of any dimension, and sum/0 accepts any number of same-dimensional vectors. <lang jq>def polar(r; angle):
[ r*(angle|cos), r*(angle|sin) ];
- If your jq allows multi-arity functions, you may wish to uncomment the following line:
- def polar(r): [r, 0];
def polar2vector: polar(.[0]; .[1]);
def vector(x; y):
if (x|type) == "number" and (y|type) == "number" then [x,y] else error("TypeError") end;
- Input: an array of same-dimensional vectors of any dimension to be added
def sum:
def sum2: .[0] as $a | .[1] as $b | reduce range(0;$a|length) as $i ($a; .[$i] += $b[$i]); if length <= 1 then . else reduce .[1:][] as $v (.[0] ; [., $v]|sum2) end;
def multiply(scalar): [ .[] * scalar ];
def negate: multiply(-1);
def minus(v): [., (v|negate)] | sum;
def divide(scalar):
if scalar == 0 then error("division of a vector by 0 is not supported") else [ .[] / scalar ] end;
def r: (.[0] | .*.) + (.[1] | .*.) | sqrt;
def atan2:
def pi: 1 | atan * 4; def sign: if . < 0 then -1 elif . > 0 then 1 else 0 end; .[0] as $x | .[1] as $y | if $x == 0 then $y | sign * pi / 2 else ($y / $x) | if $x > 0 then atan elif . > 0 then atan - pi else atan + pi end end;
def angle: atan2;
def topolar: [r, angle];</lang>
Examples <lang jq>def examples:
def pi: 1 | atan * 4;
[1,1] as $v | [3,4] as $w | polar(1; pi/2) as $z | polar(-2; pi/4) as $z2 | "v is \($v)", " w is \($w)", "v + w is \([$v, $w] | sum)", "v - w is \( $v |minus($w))", " - v is \( $v|negate )", "w * 5 is \($w | multiply(5))", "w / 2 is \($w | divide(2))", "v|topolar is \($v|topolar)", "w|topolar is \($w|topolar)", "z = polar(1; pi/2) is \($z)", "z|topolar is \($z|topolar)", "z2 = polar(-2; pi/4) is \($z2)", "z2|topolar is \($z2|topolar)", "z2|topolar|polar is \($z2|topolar|polar2vector)" ;
examples</lang>
- Output:
<lang sh>$ jq -r -n -f vector.jq v is [1,1]
w is [3,4]
v + w is [4,5] v - w is [-2,-3]
- v is [-1,-1]
w * 5 is [15,20] w / 2 is [1.5,2] v|topolar is [1.4142135623730951,0.7853981633974483] w|topolar is [5,0.9272952180016122] z = polar(1; pi/2) is [6.123233995736766e-17,1] z|topolar is [1,1.5707963267948966] z2 = polar(-2; pi/4) is [-1.4142135623730951,-1.414213562373095] z2|topolar is [2,-2.356194490192345] z2|topolar|polar is [-1.414213562373095,-1.4142135623730951]</lang>
ooRexx
<lang oorexx>v=.vector~new(12,-3); Say "v=.vector~new(12,-3) =>" v~print v~ab(1,1,6,4); Say "v~ab(1,1,6,4) =>" v~print v~al(45,2); Say "v~al(45,2) =>" v~print w=v~'+'(v); Say "w=v~'+'(v) =>" w~print x=v~'-'(w); Say "x=v~'-'(w) =>" x~print y=x~'*'(3); Say "y=x~'*'(3) =>" y~print z=x~'/'(0.1); Say "z=x~'/'(0.1) =>" z~print
- class vector
- attribute x
- attribute y
- method init
Use Arg a,b self~x=a self~y=b
- method ab /* set vector from point (a,b) to point (c,d) */
Use Arg a,b,c,d self~x=c-a self~y=d-b
- method al /* set vector given angle a and length l */
Use Arg a,l self~x=l*rxCalccos(a) self~y=l*rxCalcsin(a)
- method '+' /* add: Return sum of self and argument */
Use Arg v x=self~x+v~x y=self~y+v~y res=.vector~new(x,y) Return res
- method '-' /* subtract: Return difference of self and argument */
Use Arg v x=self~x-v~x y=self~y-v~y res=.vector~new(x,y) Return res
- method '*' /* multiply: Return self multiplied by t */
Use Arg t x=self~x*t y=self~y*t res=.vector~new(x,y) Return res
- method '/' /* divide: Return self divided by t */
Use Arg t x=self~x/t y=self~y/t res=.vector~new(x,y) Return res
- method print /* prettyprint a vector */
return '['self~x','self~y']'
- requires rxMath Library</lang>
- Output:
v=.vector~new(12,-3) => [12,-3] v~ab(1,1,6,4) => [5,3] v~al(45,2) => [1.41421356,1.41421356] w=v~'+'(v) => [2.82842712,2.82842712] x=v~'-'(w) => [-1.41421356,-1.41421356] y=x~'*'(3) => [-4.24264068,-4.24264068] z=x~'/'(0.1) => [-14.1421356,-14.1421356]
Perl 6
<lang perl6>class Vector {
has Real $.x; has Real $.y;
multi submethod BUILD (:$!x!, :$!y!) { * } multi submethod BUILD (:$length!, :$angle!) { $!x = $length * cos $angle; $!y = $length * sin $angle; } multi submethod BUILD (:from([$x1, $y1])!, :to([$x2, $y2])!) { $!x = $x2 - $x1; $!y = $y2 - $y1; } method length { sqrt $.x ** 2 + $.y ** 2 } method angle { atan2 $.y, $.x } method add ($v) { Vector.new(x => $.x + $v.x, y => $.y + $v.y) } method subtract ($v) { Vector.new(x => $.x - $v.x, y => $.y - $v.y) } method multiply ($n) { Vector.new(x => $.x * $n, y => $.y * $n ) } method divide ($n) { Vector.new(x => $.x / $n, y => $.y / $n ) } method gist { "vec[$.x, $.y]" }
}
multi infix:<+> (Vector $v, Vector $w) is export { $v.add: $w } multi infix:<-> (Vector $v, Vector $w) is export { $v.subtract: $w } multi prefix:<-> (Vector $v) is export { $v.multiply: -1 } multi infix:<*> (Vector $v, $n) is export { $v.multiply: $n } multi infix:</> (Vector $v, $n) is export { $v.divide: $n }
- [ Usage example: ]#####
say my $u = Vector.new(x => 3, y => 4); #: vec[3, 4] say my $v = Vector.new(from => [1, 0], to => [2, 3]); #: vec[1, 3] say my $w = Vector.new(length => 1, angle => pi/4); #: vec[0.707106781186548, 0.707106781186547]
say $u.length; #: 5 say $u.angle * 180/pi; #: 53.130102354156
say $u + $v; #: vec[4, 7] say $u - $v; #: vec[2, 1] say -$u; #: vec[-3, -4] say $u * 10; #: vec[30, 40] say $u / 2; #: vec[1.5, 2]</lang>
PL/I
<lang pli>*process source attributes xref or(!);
vectors: Proc Options(main); Dcl (v,w,x,y,z) Dec Float(9) Complex; real(v)=12; imag(v)=-3; Put Edit(pp(v))(Skip,a); real(v)=6-1; imag(v)=4-1; Put Edit(pp(v))(Skip,a); real(v)=2*cosd(45); imag(v)=2*sind(45); Put Edit(pp(v))(Skip,a);
w=v+v; Put Edit(pp(w))(Skip,a); x=v-w; Put Edit(pp(x))(Skip,a); y=x*3; Put Edit(pp(y))(Skip,a); z=x/.1; Put Edit(pp(z))(Skip,a);
pp: Proc(c) Returns(Char(50) Var); Dcl c Dec Float(9) Complex; Dcl res Char(50) Var; Put String(res) Edit('[',real(c),',',imag(c),']') (3(a,f(9,5))); Return(res); End; End;</lang>
- Output:
[ 12.00000, -3.00000] [ 5.00000, 3.00000] [ 1.41421, 1.41421] [ 2.82843, 2.82843] [ -1.41421, -1.41421] [ -4.24264, -4.24264] [-14.14214,-14.14214]
Python
Implements a Vector Class that is initialized with origin, angular coefficient and value.
<lang python>class Vector:
def __init__(self,m,value): self.m = m self.value = value self.angle = math.degrees(math.atan(self.m)) self.x = self.value * math.sin(math.radians(self.angle)) self.y = self.value * math.cos(math.radians(self.angle))
def __add__(self,vector): """ >>> Vector(1,10) + Vector(1,2) Vector: - Angular coefficient: 1.0 - Angle: 45.0 degrees - Value: 12.0 - X component: 8.49 - Y component: 8.49 """ final_x = self.x + vector.x final_y = self.y + vector.y final_value = pytagoras(final_x,final_y) final_m = final_y / final_x return Vector(final_m,final_value)
def __neg__(self): return Vector(self.m,-self.value)
def __sub__(self,vector): return self + (- vector) def __mul__(self,scalar): """ >>> Vector(4,5) * 2 Vector: - Angular coefficient: 4 - Angle: 75.96 degrees - Value: 10 - X component: 9.7 - Y component: 2.43
""" return Vector(self.m,self.value*scalar)
def __div__(self,scalar): return self * (1 / scalar) def __repr__(self): """ Returns a nicely formatted list of the properties of the Vector.
>>> Vector(1,10) Vector: - Angular coefficient: 1 - Angle: 45.0 degrees - Value: 10 - X component: 7.07 - Y component: 7.07 """ return """Vector: - Angular coefficient: {} - Angle: {} degrees - Value: {} - X component: {} - Y component: {}""".format(self.m.__round__(2), self.angle.__round__(2), self.value.__round__(2), self.x.__round__(2), self.y.__round__(2))</lang>
Racket
We store internally only the x, y
components and calculate the norm, angle and slope on demand. We have two constructors one with (x,y)
and another with (slope, norm)
.
We use fl*
and fl/
to try to get the most sensible result for vertical vectors.
<lang Racket>#lang racket
(require racket/flonum)
(define (rad->deg x) (fl* 180. (fl/ (exact->inexact x) pi)))
- Custom printer
- no shared internal structures
(define (vec-print v port mode)
(write-string "Vec:\n" port) (write-string (format " -Slope: ~a\n" (vec-slope v)) port) (write-string (format " -Angle(deg): ~a\n" (rad->deg (vec-angle v))) port) (write-string (format " -Norm: ~a\n" (vec-norm v)) port) (write-string (format " -X: ~a\n" (vec-x v)) port) (write-string (format " -Y: ~a\n" (vec-y v)) port))
(struct vec (x y)
#:methods gen:custom-write [(define write-proc vec-print)])
- Alternative constructor
(define (vec/slope-norm s n)
(vec (* n (/ 1 (sqrt (+ 1 (sqr s))))) (* n (/ s (sqrt (+ 1 (sqr s)))))))
- Properties
(define (vec-norm v)
(sqrt (+ (sqr (vec-x v)) (sqr (vec-y v)))))
(define (vec-slope v)
(fl/ (exact->inexact (vec-y v)) (exact->inexact (vec-x v))))
(define (vec-angle v)
(atan (vec-y v) (vec-x v)))
- Operations
(define (vec+ v w)
(vec (+ (vec-x v) (vec-x w)) (+ (vec-y v) (vec-y w))))
(define (vec- v w)
(vec (- (vec-x v) (vec-x w)) (- (vec-y v) (vec-y w))))
(define (vec*e v l)
(vec (* (vec-x v) l) (* (vec-y v) l)))
(define (vec/e v l)
(vec (/ (vec-x v) l) (/ (vec-y v) l)))</lang>
Tests <lang Racket>(vec/slope-norm 1 10)
(vec/slope-norm 0 10)
(vec 3 4)
(vec 0 10)
(vec 10 0)
(vec+ (vec/slope-norm 1 10) (vec/slope-norm 1 2))
(vec*e (vec/slope-norm 4 5) 2)</lang>
- Output:
Vec: -Slope: 1.0 -Angle(deg): 45.0 -Norm: 10.0 -X: 7.071067811865475 -Y: 7.071067811865475 Vec: -Slope: 0.0 -Angle(deg): 0.0 -Norm: 10 -X: 10 -Y: 0 Vec: -Slope: 1.3333333333333333 -Angle(deg): 53.13010235415597 -Norm: 5 -X: 3 -Y: 4 Vec: -Slope: +inf.0 -Angle(deg): 90.0 -Norm: 10 -X: 0 -Y: 10 Vec: -Slope: 0.0 -Angle(deg): 0.0 -Norm: 10 -X: 10 -Y: 0 Vec: -Slope: 1.0 -Angle(deg): 45.0 -Norm: 11.999999999999998 -X: 8.48528137423857 -Y: 8.48528137423857 Vec: -Slope: 4.0 -Angle(deg): 75.96375653207353 -Norm: 10.000000000000002 -X: 2.42535625036333 -Y: 9.70142500145332
Ruby
<lang ruby>class Vector
def self.polar(r, angle=0) new(r*Math.cos(angle), r*Math.sin(angle)) end attr_reader :x, :y def initialize(x, y) raise TypeError unless x.is_a?(Numeric) and y.is_a?(Numeric) @x, @y = x, y end def +(other) raise TypeError if self.class != other.class self.class.new(@x + other.x, @y + other.y) end def -@; self.class.new(-@x, -@y) end def -(other) self + (-other) end def *(scalar) raise TypeError unless scalar.is_a?(Numeric) self.class.new(@x * scalar, @y * scalar) end def /(scalar) raise TypeError unless scalar.is_a?(Numeric) and scalar.nonzero? self.class.new(@x / scalar, @y / scalar) end def r; @r ||= Math.hypot(@x, @y) end def angle; @angle ||= Math.atan2(@y, @x) end def polar; [r, angle] end def rect; [@x, @y] end def to_s; "#{self.class}#{[@x, @y]}" end alias inspect to_s
end
p v = Vector.new(1,1) #=> Vector[1, 1] p w = Vector.new(3,4) #=> Vector[3, 4] p v + w #=> Vector[4, 5] p v - w #=> Vector[-2, -3] p -v #=> Vector[-1, -1] p w * 5 #=> Vector[15, 20] p w / 2.0 #=> Vector[1.5, 2.0] p w.x #=> 3 p w.y #=> 4 p v.polar #=> [1.4142135623730951, 0.7853981633974483] p w.polar #=> [5.0, 0.9272952180016122] p z = Vector.polar(1, Math::PI/2) #=> Vector[6.123031769111886e-17, 1.0] p z.rect #=> [6.123031769111886e-17, 1.0] p z.polar #=> [1.0, 1.5707963267948966] p z = Vector.polar(-2, Math::PI/4) #=> Vector[-1.4142135623730951, -1.414213562373095] p z.polar #=> [2.0, -2.356194490192345]</lang>
Tcl
Good artists steal .. code .. from the great RS on Tcl'ers wiki. Seriously, this is a neat little procedure:
<lang Tcl>namespace path ::tcl::mathop proc vec {op a b} {
if {[llength $a] == 1 && [llength $b] == 1} { $op $a $b } elseif {[llength $a]==1} { lmap i $b {vec $op $a $i} } elseif {[llength $b]==1} { lmap i $a {vec $op $i $b} } elseif {[llength $a] == [llength $b]} { lmap i $a j $b {vec $op $i $j} } else {error "length mismatch [llength $a] != [llength $b]"}
}
proc polar {r t} {
list [expr {$r * cos($t)}] [expr {$r * sin($t)}]
}
proc check {cmd res} {
set r [uplevel 1 $cmd] if {$r eq $res} { puts "Ok! $cmd \t = $res" } else { puts "ERROR: $cmd = $r \t expected $res" }
}
check {vec + {5 7} {2 3}} {7 10} check {vec - {5 7} {2 3}} {3 4} check {vec * {5 7} 11} {55 77} check {vec / {5 7} 2.0} {2.5 3.5} check {polar 2 0.785398} {1.41421 1.41421}</lang>
The tests are taken from J's example:
- Output:
Ok! vec + {5 7} {2 3} = 7 10 Ok! vec - {5 7} {2 3} = 3 4 Ok! vec * {5 7} 11 = 55 77 Ok! vec / {5 7} 2.0 = 2.5 3.5 ERROR: polar 2 0.785398 = 1.4142137934519636 1.4142133312941887 expected 1.41421 1.41421
the polar calculation gives more than 6 digits of precision, and tests our error handling ;-).