mistermonke
mistermonke

Reputation: 41

How do I define a type as a vector of another defined type?

So what I want to do is something like: Define a type Point that is constructed from floats passed by the user. This is supposed to store the coordinates of a point. struct Point x::Float64 y::Float64 end This works well enough.

Now, I want to define a line segment that is defined as a vector of Point types which accepts two points from the user and creates a vector of 1000 points in between and assigns it to the line and constructs it. ''' s=Point(1,1) t=Point(8,9)

struct Line::Vector{Point}
    s::Point
    t::Point
    Line(s,t)=new([Point(s.x+i*(t.x-s.x)/1000,s.y+i*(t.y-s.y)/1000) for i=0:1000])
end

''' I can't quite get it to work, trying variations of the code above. Could someone please guide me in the right direction?

Upvotes: 4

Views: 132

Answers (2)

cbk
cbk

Reputation: 4370

The answer is actually simpler than you might think!

struct Point
    x::Float64
    y::Float64
end

struct Line
    points::Vector{Point}
end

Line(s::Point, t::Point) = Line([Point(s.x+i*(t.x-s.x)/1000,s.y+i*(t.y-s.y)/1000) for i=0:1000]) # Aside: this is a function. You could also define it as a long-form function.

Then, for example

julia> s = Point(1,1)
Point(1.0, 1.0)

julia> t = Point(8,9)
Point(8.0, 9.0)

julia> Line(s,t)
Line(Point[Point(1.0, 1.0), Point(1.007, 1.008), Point(1.014, 1.016), Point(1.021, 1.024), Point(1.028, 1.032), Point(1.035, 1.04), Point(1.042, 1.048), Point(1.049, 1.056), Point(1.056, 1.064), Point(1.063, 1.072)  …  Point(7.937, 8.928), Point(7.944, 8.936), Point(7.951, 8.943999999999999), Point(7.958, 8.952), Point(7.965, 8.96), Point(7.972, 8.968), Point(7.979, 8.975999999999999), Point(7.986, 8.984), Point(7.993, 8.992), Point(8.0, 9.0)])

If you want to get the s and t back, then you can just grab them from the array of points. In fact, one "Julian" way to do this would be just to use multiple dispatch and add methods for the Line type to first and last, e.g.

julia> import Base.first

julia> first(l::Line) = first(l.points)
first (generic function with 42 methods)

julia> import Base.last

julia> last(l::Line) = last(l.points)
last (generic function with 36 methods)

julia> first(l)
Point(1.0, 1.0)

julia> last(l)
Point(8.0, 9.0)

Now if you want to make this more general, to allow points of any type rather than just Float64, then you could simply write

struct Point{T}
    x::T
    y::T
end

struct Line{T}
    points::Vector{Point{T}}
end

instead, to make these structs parametric.

Upvotes: 1

Przemyslaw Szufel
Przemyslaw Szufel

Reputation: 42264

In some implementations you will want your Line to behave as an actual Vector in that case you need to inherit on the AbstractVector type:

struct Point 
  x::Float64
  y::Float64
end

struct Line <: AbstractVector{Point}
  s::Point
  t::Point
  points::Vector{Point}
  Line(s::Point,t::Point) = 
    new(s,t,Point.(range(s.x,t.x,length=100),range(s.y,t.y,length=100)))
end

Base.size(line::Line) = (length(line.points), )
Base.getindex(line::Line, ix) = line.points[ix]

I have slighlty shortened the code to generate points. Moreover, you can see this required defining some basic methods (you might also want to add setindex if you want to mutate the Vector).

Now let us see how it works and how Line behaves as a Vector:

julia> line = Line(Point(0,1), Point(1,3));
100-element Line:
 Point(0.0, 1.0)
 Point(0.010101010101010102, 1.02020202020202)
 Point(0.020202020202020204, 1.0404040404040404)
 ⋮
 Point(0.9797979797979798, 2.95959595959596)
 Point(0.98989898989899, 2.9797979797979797)
 Point(1.0, 3.0)

julia> line[1], line[2], line[end]
(Point(0.0, 1.0), Point(0.010101010101010102, 1.02020202020202), Point(1.0, 3.0))

With this implementation first and last work as well (similarly to the other answer) so you might consider removing s and t from the struct (depends on your code):

julia> first(line), last(line)
(Point(0.0, 1.0), Point(1.0, 3.0))

Upvotes: 3

Related Questions