Initial Commit
This commit is contained in:
parent
3eff6439e4
commit
4adc9cd975
|
@ -0,0 +1,7 @@
|
||||||
|
println("Hello Julia!")
|
||||||
|
|
||||||
|
x = 3
|
||||||
|
y = 2
|
||||||
|
z = x + y
|
||||||
|
|
||||||
|
println(z)
|
|
@ -0,0 +1,173 @@
|
||||||
|
|
||||||
|
# User defined types are made with the struct keyword. Member variables are
|
||||||
|
# listed in the struct body as follows.
|
||||||
|
# Keep in mind that julia does not support redefining of types by default,
|
||||||
|
# so whenever you change anything in the struct (field names/types, etc.)
|
||||||
|
# you have to restart the REPL.
|
||||||
|
# Alternatively you could look into the package Revise.jl which solves
|
||||||
|
# some of these problems.
|
||||||
|
struct TestType
|
||||||
|
x
|
||||||
|
y
|
||||||
|
z
|
||||||
|
end
|
||||||
|
|
||||||
|
# Every struct gets a default constructor that is the typename taking
|
||||||
|
# in all the member variables in order.
|
||||||
|
t = TestType(1, 2, 3)
|
||||||
|
|
||||||
|
# Every struct also gets a default print function that shows the variable
|
||||||
|
# in style of the constructor.
|
||||||
|
@show t
|
||||||
|
|
||||||
|
# Accessing the member variables of the struct is done simply by just
|
||||||
|
# writing variable_name.field_name
|
||||||
|
# every field is always public as julia does not implement any heavy
|
||||||
|
# object oriented design.
|
||||||
|
@show t.x
|
||||||
|
|
||||||
|
# This line is illegal since struct are immutable by default.
|
||||||
|
# t.y = 5
|
||||||
|
|
||||||
|
# Field mutability can be achieved by adding the keyword "mutable" in front.
|
||||||
|
# However for simple structs, like this xyz-point like structure, it is
|
||||||
|
# much more efficient if you can avoid making it mutable. This has to do
|
||||||
|
# with how mutable struct are allocated differently then immutable ones.
|
||||||
|
# Read more in the "Types" section of the julia documentation.
|
||||||
|
mutable struct TestType2
|
||||||
|
x::Float64
|
||||||
|
y::Float64
|
||||||
|
z::Float64
|
||||||
|
end
|
||||||
|
|
||||||
|
# Also as illustrated above, the fields in a struct can be strictly typed.
|
||||||
|
# This is usually a good idea since it makes the struct take a constant
|
||||||
|
# amount of space, and removes a lot of type bookkeping.
|
||||||
|
|
||||||
|
t2 = TestType2(1.68, 3.14, 2.71)
|
||||||
|
|
||||||
|
# With this mutable struct, the variable now behaves more like a general
|
||||||
|
# container (Array/vector) so you can change the fields.
|
||||||
|
t2.y = 1.23
|
||||||
|
@show t2
|
||||||
|
|
||||||
|
# You can also make parametric types, kind of like templates in C++.
|
||||||
|
# This essentially creates a new type for each new T, so it alleviates the
|
||||||
|
# problem of keeping track of the types of the individual fields.
|
||||||
|
struct TestType3{T}
|
||||||
|
x::T
|
||||||
|
y::T
|
||||||
|
z::T
|
||||||
|
end
|
||||||
|
|
||||||
|
# This now constructs a TestType3{Int64}
|
||||||
|
t3 = TestType3(1, 2, 3)
|
||||||
|
@show t3
|
||||||
|
|
||||||
|
|
||||||
|
# This will be the example struct throughout the rest of the file.
|
||||||
|
struct Polar{T<:Real}
|
||||||
|
r::T
|
||||||
|
θ::T
|
||||||
|
|
||||||
|
# You might want to make your own constructors for a type.
|
||||||
|
# These are typically made inside the struct body since this overrides
|
||||||
|
# the creation of the default constructor described above.
|
||||||
|
function Polar(r::T, θ::T) where T <: Real
|
||||||
|
# Doing some constructy stuff
|
||||||
|
println("Creating Polar number (r = $r, θ = $θ)")
|
||||||
|
|
||||||
|
# The actual variable is created by calling the "new()" function
|
||||||
|
# which acts as the default constructor. This however is just
|
||||||
|
# accessible to functions defined inside the struct body.
|
||||||
|
new{T}(r, θ)
|
||||||
|
end
|
||||||
|
|
||||||
|
# You might want to implement multiple different constructors.
|
||||||
|
# This one is short and simple so it can be created with the inline syntax.
|
||||||
|
# The zero(T) function finds the apropriate "zero" value
|
||||||
|
# for the given type T. That way no implicit conversions have to be done.
|
||||||
|
Polar{T}() where T <: Real = new{T}(zero(T), zero(T))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Not all constructors have to be defined inside the struct body, but here
|
||||||
|
# outside the body we no longer have access to the "new()" function since
|
||||||
|
# the compiler doesn't have any way to infer which type "new" would refer
|
||||||
|
# to out here. So instead we have to use one of the constructors we defined
|
||||||
|
# inside the struct.
|
||||||
|
# This constructor is very similar to the Polar{T}() function but instead
|
||||||
|
# of writing p = Polar{Int}() to take the type in as a template argument
|
||||||
|
# you would pass the type in as an actual argument; p = Polar(Int)
|
||||||
|
Polar(::Type{T}) where T <: Real = Polar{T}()
|
||||||
|
|
||||||
|
|
||||||
|
# Constructing with the first constructor
|
||||||
|
p = Polar(3.14, 2.71)
|
||||||
|
@show p
|
||||||
|
|
||||||
|
# Constructing with the empty constructor
|
||||||
|
p = Polar{Int}()
|
||||||
|
@show p
|
||||||
|
|
||||||
|
# Constructing with the constructor taking the type as an argument
|
||||||
|
p = Polar(Float16)
|
||||||
|
@show p
|
||||||
|
|
||||||
|
|
||||||
|
# You might want to overload som basic operators and functions on your
|
||||||
|
# type. One common thing to want to override is how your variables are printed.
|
||||||
|
# Since there are so many different ways of converting a variable to text
|
||||||
|
# (print, println, @printf, string, @show, display, etc.)
|
||||||
|
# it can be difficult to find out which function is actually responsible
|
||||||
|
# for converting to text.
|
||||||
|
# This function turns out to be the Base.show() function.
|
||||||
|
# It takes in an IO stream object and and the variable you want to show.
|
||||||
|
|
||||||
|
# Since the funtion is from the Base module
|
||||||
|
# we have to override Base.show specifically
|
||||||
|
# Also it is very important to actually specify the type of the variable here
|
||||||
|
# so that we are actually specifying the show function for our type only
|
||||||
|
# and not for any general type.
|
||||||
|
function Base.show(io::IO, p::Polar)
|
||||||
|
# Say we want to print our polar number as r * e^(i θ) style string
|
||||||
|
show(io, p.r) # non strings we want to recursively show
|
||||||
|
write(io, " ⋅ ℯ^(i ⋅ ") # strings we want to write directly with write
|
||||||
|
show(io, p.θ)
|
||||||
|
write(io, ")")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now our polar numbers are printed as we specified
|
||||||
|
p = Polar(3.14, 2.71)
|
||||||
|
@show p
|
||||||
|
println(p)
|
||||||
|
display(p)
|
||||||
|
|
||||||
|
# Even converting to a string is now done with our show functon
|
||||||
|
s = string(p)
|
||||||
|
@show s
|
||||||
|
|
||||||
|
|
||||||
|
# Overloading operators is even simpler as every infix operator is just a
|
||||||
|
# function; a + b is equivalent to +(a, b) (for all you lisp lovers)
|
||||||
|
|
||||||
|
# For some reason we cannot write the Base.* inline for infix operators
|
||||||
|
import Base.*
|
||||||
|
*(p1::Polar{T}, p2::Polar{T}) where T = Polar(p1.r * p2.r, p1.θ + p2.θ)
|
||||||
|
|
||||||
|
# Multiplication between different types might also be useful
|
||||||
|
*(x::T, p::Polar{T}) where T = Polar(x * p.r, p.θ)
|
||||||
|
# Implementing the reverse mulitplication such that it becomes commutative
|
||||||
|
*(p::Polar{T}, x::T) where T = x * p
|
||||||
|
|
||||||
|
p1 = Polar(2.0, 15.0)
|
||||||
|
p2 = Polar(3.0, 30.0)
|
||||||
|
p3 = p1 * p2
|
||||||
|
@show p3
|
||||||
|
|
||||||
|
# The in-place arithmetic operators (+=, -=, *=, /=, etc.) are not actual
|
||||||
|
# operators but rather just an alias for a = a * b so they need not be
|
||||||
|
# implemented seperately. If the memory copying is a problem and you
|
||||||
|
# really have to do it in-place the way to do that would be to make some
|
||||||
|
# sort of mult!(p, x) function.
|
||||||
|
p3 *= 0.5
|
||||||
|
@show p3
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
# This is just a super simple example to illustrate the "call" operator.
|
||||||
|
|
||||||
|
# Simple polynomial type. The Polynomials.jl package is basically a more
|
||||||
|
# complete version of this example.
|
||||||
|
struct Poly{T}
|
||||||
|
coeffs::Vector{T}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Could implement a whole bunch of operators (+, -, *, /, show, etc.)
|
||||||
|
# but that can be left as an exercise for the reader :3
|
||||||
|
# Also the Polynomials.jl package have implemented most of those.
|
||||||
|
|
||||||
|
# Here, the whole point of this example; This function basically makes
|
||||||
|
# the variable, p, act as a polynomial function on x.
|
||||||
|
function (p::Poly{T})(x::T) where T <: Number
|
||||||
|
ret = zero(T)
|
||||||
|
for i in 1 : length(p.coeffs)
|
||||||
|
ret += p.coeffs[i] * x^(i - 1)
|
||||||
|
end
|
||||||
|
ret
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now any variable of type Poly can be used as if it was a function
|
||||||
|
# This gives some really clean syntax :)
|
||||||
|
p = Poly([0.0, -3.0, 0.0, 1.0]) # p = 0 - 3 x + 0 x^2 + 1 x^3
|
||||||
|
@show p(3.0)
|
|
@ -0,0 +1,216 @@
|
||||||
|
|
||||||
|
# This is a more complete example of a linked list implementation, but exists
|
||||||
|
# mainly to illustrate how to implement indexing and iteration.
|
||||||
|
|
||||||
|
# okay, this is the dirtiest hack I have ever done in julia.
|
||||||
|
# Julia is really finicky when it comes to redefining structs.
|
||||||
|
# Usually it isn't a problem if you haven't changed anything,
|
||||||
|
# but because of the recursive inclusion in the Node_ struct it was really
|
||||||
|
# unhappy, so this basically runs this if block the first time
|
||||||
|
# and not any subsequent times.
|
||||||
|
if !isdefined(@__MODULE__, :__first__)
|
||||||
|
__first__ = true
|
||||||
|
|
||||||
|
# The node struct that actually holds the values
|
||||||
|
mutable struct Node_{T}
|
||||||
|
data::T
|
||||||
|
prev::Union{Node_{T}, Nothing}
|
||||||
|
next::Union{Node_{T}, Nothing}
|
||||||
|
end
|
||||||
|
|
||||||
|
# An alias for a type union such that we can set a node to the value nothing
|
||||||
|
# when we want to indicate the end of the list.
|
||||||
|
const Node{T} = Union{Node_{T}, Nothing}
|
||||||
|
|
||||||
|
# Since we made the alias for Node, we make this wrapper for the constructor
|
||||||
|
# of the actual Node_ object. There are probably better ways to do this
|
||||||
|
# but this works fine.
|
||||||
|
function make_node(data::T, prev::Node{T}, next::Node{T}) where T
|
||||||
|
Node_{T}(data, prev, next)
|
||||||
|
end
|
||||||
|
|
||||||
|
# The wrapper struct for the nodes. Most of the methods will be implemented
|
||||||
|
# on this type.
|
||||||
|
# It is also specified as a subtype of the abstract type AbstractArray.
|
||||||
|
# This signalises that it will mostly act like an array
|
||||||
|
# and makes it possible to pass a LinkedList into a function that is specified
|
||||||
|
# to take in objects of type AbstractArray. This gives a bunch of functions
|
||||||
|
# that needs to be implemented, and some that can be overridden. All of these
|
||||||
|
# are listed under the "Intefaces" section in the julia docs.
|
||||||
|
# Here we will implement the required methods and the methods for iteration.
|
||||||
|
mutable struct LinkedList{T} <: AbstractArray{T, 1}
|
||||||
|
head::Node{T}
|
||||||
|
tail::Node{T}
|
||||||
|
items::Int # Keeping track of how many items for quick length checking
|
||||||
|
|
||||||
|
# Implementing a default constructor
|
||||||
|
LinkedList{T}() where T = new{T}(nothing, nothing, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # End of the dirty hack
|
||||||
|
|
||||||
|
# Constructor to create a list from a collection of elements
|
||||||
|
function LinkedList(elems::AbstractArray{T}) where T
|
||||||
|
l = LinkedList{T}()
|
||||||
|
for e in elems
|
||||||
|
push!(l, e)
|
||||||
|
end
|
||||||
|
l
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Not required for AbstractArray, but makes sence to implement
|
||||||
|
function Base.push!(l::LinkedList{T}, v::T) where T
|
||||||
|
n_node = make_node(v, nothing, nothing)
|
||||||
|
|
||||||
|
if l.head === nothing
|
||||||
|
l.tail = l.head = n_node
|
||||||
|
else
|
||||||
|
l.tail.next = n_node
|
||||||
|
n_node.prev = l.tail
|
||||||
|
l.tail = n_node
|
||||||
|
# n_node.next = l.head # Uncomment this one if you feel brave ;)
|
||||||
|
end
|
||||||
|
l.items += 1
|
||||||
|
l
|
||||||
|
end
|
||||||
|
|
||||||
|
# One of the reqired functions for AbstractArray. size returns a tuple
|
||||||
|
# of all the dimensions, but this is a 1d collection so it only contains
|
||||||
|
# one element.
|
||||||
|
Base.size(l::LinkedList) = (l.items, )
|
||||||
|
|
||||||
|
# Implements the iteration function. This makes it possible to for example
|
||||||
|
# write something like "for element in list" to iterate through the list.
|
||||||
|
# The function returns a 2-tuple where the first element is the next iteration
|
||||||
|
# item and the second element is the next state. The state is some variable
|
||||||
|
# that determines the next element and state. When the function returns
|
||||||
|
# nothing, the loop is done.
|
||||||
|
function Base.iterate(l::LinkedList, state=l.head)
|
||||||
|
if state === nothing
|
||||||
|
nothing # If the state is nothing, we return nothing
|
||||||
|
else
|
||||||
|
(state.data, state.next) # Else the next item/state is returned
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Implements a simple show function for nicer printing. The default show
|
||||||
|
# function is ridiculous when you have circular/recursive containment in
|
||||||
|
# your structs, so it is necessary to implement a nicer version.
|
||||||
|
function Base.show(io::IO, l::LinkedList{T}) where T
|
||||||
|
show(io, l.items)
|
||||||
|
write(io, "-element LinkedList{")
|
||||||
|
show(io, T)
|
||||||
|
write(io, "}:\n ")
|
||||||
|
first = true
|
||||||
|
for elem in l
|
||||||
|
if !first
|
||||||
|
write(io, " → ")
|
||||||
|
end
|
||||||
|
first = false
|
||||||
|
show(io, elem)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# A utility function for both the getindex and setindex! functions
|
||||||
|
function findnode(l::LinkedList, i::Int)
|
||||||
|
if !(1 <= i <= length(l))
|
||||||
|
throw(BoundsError(l, i)) # Throwing an error if out of bounds
|
||||||
|
end
|
||||||
|
curnode = l.head
|
||||||
|
for j in 1 : i - 1
|
||||||
|
curnode = curnode.next
|
||||||
|
end
|
||||||
|
curnode
|
||||||
|
end
|
||||||
|
|
||||||
|
# Another required function for AbstractArray.
|
||||||
|
# This function makes it possible to read the element at a certain index
|
||||||
|
# with the syntax l[i]
|
||||||
|
Base.getindex(l::LinkedList, i::Int) = findnode(l, i).data
|
||||||
|
|
||||||
|
# The complimentary function for seting elements at a given index
|
||||||
|
# with corresponding syntax l[i] = v
|
||||||
|
Base.setindex!(l::LinkedList{T}, v::T, i::Int) where T = findnode(l, i).data = v
|
||||||
|
|
||||||
|
|
||||||
|
# Practial to override the copy function. In this case it it really
|
||||||
|
# simple to set up because of the way we made the constructor.
|
||||||
|
Base.copy(l::LinkedList) = LinkedList(l)
|
||||||
|
|
||||||
|
# Implementing the append function as well. Not necessary but practical to have.
|
||||||
|
function Base.append!(l::LinkedList{T}, l2::AbstractArray{T}) where T
|
||||||
|
for elem in l2
|
||||||
|
push!(l, elem)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Implementing insert! for a linked list is quite useful
|
||||||
|
function Base.insert!(l::LinkedList{T}, i::Int, v::T) where T
|
||||||
|
node = findnode(l, i)
|
||||||
|
n_node = make_node(v, node, node.next)
|
||||||
|
if node.next !== nothing
|
||||||
|
node.next.prev = n_node
|
||||||
|
end
|
||||||
|
node.next = n_node
|
||||||
|
l
|
||||||
|
end
|
||||||
|
|
||||||
|
# Same goes for deleteat!
|
||||||
|
function Base.deleteat!(l::LinkedList, i::Int)
|
||||||
|
node = findnode(l, i)
|
||||||
|
if node.prev !== nothing
|
||||||
|
node.prev.next = node.next
|
||||||
|
end
|
||||||
|
if node.next !== nothing
|
||||||
|
node.next.prev = node.prev
|
||||||
|
end
|
||||||
|
l
|
||||||
|
end
|
||||||
|
|
||||||
|
# It can be useful to reverse a list
|
||||||
|
function Base.reverse!(l::LinkedList)
|
||||||
|
cur_node = l.head
|
||||||
|
for i = 1 : length(l)
|
||||||
|
cur_node.prev, cur_node.next = cur_node.next, cur_node.prev
|
||||||
|
cur_node = cur_node.prev
|
||||||
|
end
|
||||||
|
l.head, l.tail = l.tail, l.head
|
||||||
|
l
|
||||||
|
end
|
||||||
|
|
||||||
|
# It can also be useful to make a reversed copy of a list
|
||||||
|
Base.reverse(l::LinkedList) = reverse!(copy(l))
|
||||||
|
|
||||||
|
|
||||||
|
# Some test code:
|
||||||
|
|
||||||
|
# Creating a linked list
|
||||||
|
l = LinkedList(1 : 5)
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# showing the third element
|
||||||
|
@show l[3]
|
||||||
|
|
||||||
|
# setting the third element
|
||||||
|
l[3] = 7
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# inserting the value 8 between 2nd and 3rd node
|
||||||
|
insert!(l, 2, 8)
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# deleting the second node
|
||||||
|
deleteat!(l, 2)
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# since all required functions for AbstractArray have been implemented
|
||||||
|
# standard functions, such as sort!, should now work. However if performance
|
||||||
|
# is important you should probably implement your own version of functions
|
||||||
|
# so that it is done more efficiently for the collection in question.
|
||||||
|
# for example the sort function here will do a lot of index operations
|
||||||
|
# and since finding a node in a linked list is O(n) complexity and
|
||||||
|
# sort! is O(n log n) list operations, the whole runtime becomes O(n^2 log n).
|
||||||
|
sort!(l)
|
||||||
|
@show l
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
# File IO is really similar to how it would be done in python and C.
|
||||||
|
|
||||||
|
io = open("test.txt", "w") # Opens test.txt for writing
|
||||||
|
write(io, "Hello World!") # writes a string to it
|
||||||
|
close(io) # closes the file
|
||||||
|
|
||||||
|
# However the more appropriate way to do it would be more similar to
|
||||||
|
# Python's "with" statement. This encloses the file handling in a code block
|
||||||
|
# and automatically closes the filestream and does cleanup if anything goes
|
||||||
|
# wrong.
|
||||||
|
open("test.txt", "w") do io
|
||||||
|
write(io, "Hello\nWorld!")
|
||||||
|
end
|
||||||
|
|
||||||
|
# The do-syntax here is really just syntactical sugar for giving the
|
||||||
|
# open() function a function as an argument
|
||||||
|
|
||||||
|
# The following syntax is effectively what the do-syntax above does.
|
||||||
|
# Create a function taking in a single argument with the file-handling code
|
||||||
|
function f(io)
|
||||||
|
write(io, "Hello\nWorld!")
|
||||||
|
end
|
||||||
|
|
||||||
|
# call the open function with this function as the first parameter
|
||||||
|
open(f, "test.txt", "w")
|
||||||
|
|
||||||
|
# This makes for some really clean and safe file handling syntax.
|
||||||
|
s = open("test.txt", "r") do io
|
||||||
|
collect(eachline(io))
|
||||||
|
end
|
||||||
|
display(s)
|
||||||
|
|
||||||
|
# for simple functions as the one above it is possible to just pass
|
||||||
|
# the the function directly in without using the do-syntax
|
||||||
|
# for a really compact one line function call
|
||||||
|
# The ∘ symbol is function composition (from mathematics)
|
||||||
|
s = open(collect ∘ eachline, "test.txt", "r")
|
||||||
|
display(s)
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
# IOBuffers are like writing to a file in memory. They are really efficient
|
||||||
|
# when creating strings and work like any other IO object we've worked with.
|
||||||
|
io = IOBuffer()
|
||||||
|
|
||||||
|
a = 42
|
||||||
|
write(io, "i = ")
|
||||||
|
print(io, a)
|
||||||
|
|
||||||
|
# To retrieve the written data from the buffer we call take!(). This returns
|
||||||
|
# a Vector{UInt8} so to interpret it as a string we call String()
|
||||||
|
s = String(take!(io))
|
||||||
|
|
||||||
|
println(s)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
# A lambda function is made with an arrow from the arguments
|
||||||
|
# to the function body
|
||||||
|
f = x -> 2x^2
|
||||||
|
@show f(2)
|
||||||
|
|
||||||
|
# If the function takes more then one argument, parenthesis are required
|
||||||
|
g = (x, y) -> x * y
|
||||||
|
@show g(2, 3)
|
||||||
|
|
||||||
|
# This also holds for functions taking no arguments
|
||||||
|
p = () -> Float64(π)
|
||||||
|
@show p()
|
||||||
|
|
||||||
|
# Function currying is possible, so if anyone wants to do lambda calculus
|
||||||
|
# feel free.
|
||||||
|
h = x -> y -> x / y
|
||||||
|
@show h(12)(4)
|
||||||
|
|
||||||
|
# If a function has to be written over multiple lines, this can be done
|
||||||
|
# with a "begin" block.
|
||||||
|
fib = n -> begin
|
||||||
|
a, b = 0, 1
|
||||||
|
for i in 1 : n
|
||||||
|
a, b = b, a + b
|
||||||
|
end
|
||||||
|
a
|
||||||
|
end
|
||||||
|
@show fib.(0:6)
|
||||||
|
|
||||||
|
# Lambdas can be useful when passing a function as an argument
|
||||||
|
# without wanting to give it a name. Here is an example using a lambda
|
||||||
|
# instead of using the do-syntax for file handling
|
||||||
|
s = open(io -> String(read(io)), "test.txt", "r")
|
||||||
|
println(s)
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
# Julia comes with a lot of useful macros. Youv'e already seen the @show macro.
|
||||||
|
x = 5
|
||||||
|
@show x
|
||||||
|
|
||||||
|
# Macros are a small part of julias huge metaprogramming system.
|
||||||
|
# There exists a bunch of useful macros, both in julia itself, and in
|
||||||
|
# different packages. A macro is basically a function that takes in a list
|
||||||
|
# of arguments and generates a code block at compiletime. It is possible
|
||||||
|
# to create your own macros, but that takes some reading up on the
|
||||||
|
# metaprogramming section in the julia docs.
|
||||||
|
|
||||||
|
|
||||||
|
# Probably one of my most used macros is the @time macro
|
||||||
|
reallyslowfunction() = sleep(2)
|
||||||
|
@time reallyslowfunction()
|
||||||
|
|
||||||
|
# If you want to time a whole code block this is done with a "begin" block
|
||||||
|
@time begin
|
||||||
|
# Slow code
|
||||||
|
sleep(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
# The broadcast operator (.) can be quite useful when dealing with arrays.
|
||||||
|
# Basically whenever an operator or function is used, you can just write
|
||||||
|
# a dot (.) before the operator/function to make it act elementwise.
|
||||||
|
|
||||||
|
# Here we elementwise add 3 to the array
|
||||||
|
a = [1, 2, 3, 4]
|
||||||
|
a .+= 3
|
||||||
|
@show a
|
||||||
|
|
||||||
|
# Here we elementwise multiply a by 2 and then elementwise add a and b.
|
||||||
|
b = a .* 2
|
||||||
|
@show b
|
||||||
|
c = a .+ b
|
||||||
|
@show c
|
||||||
|
|
||||||
|
# To elementwise use more general functions the dot symbol is placed between
|
||||||
|
# the function name and parentheses.
|
||||||
|
f(x) = 2x^2
|
||||||
|
d = f.(c)
|
||||||
|
@show d
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
# Julia uses unicode characters quite heavily. Mostly it is possible to avoid
|
||||||
|
# using unicode completely, but it can make code look quite clean.
|
||||||
|
# The unicode autocompletion for vscode mostly works, but can be a bit
|
||||||
|
# unreliable, but the julia REPL (console) is also useful for writing unicode.
|
||||||
|
# There is a whole section in the Julia docs dedicated to how to input different
|
||||||
|
# unicode characters, so just search "Unicode Input" in the docs.
|
||||||
|
|
||||||
|
# We've seen a lot of different unicode symbols, here are some more useful ones.
|
||||||
|
|
||||||
|
# The infix operator for boolean xor is the ⊻ (\veebar, \xor) symbol.
|
||||||
|
b = true ⊻ false
|
||||||
|
|
||||||
|
# In the LinearAlgebra package the ⋅ (\cdot) symbol is overloaded as the
|
||||||
|
# scalar product of vectors.
|
||||||
|
using LinearAlgebra
|
||||||
|
a = [1, 2] ⋅ [2, 1]
|
||||||
|
@show a
|
||||||
|
|
||||||
|
# Where you would write "in" you could probably use either ∈ (\n) or ∉ (\notin)
|
||||||
|
|
||||||
|
# You can use ∈ for iteration
|
||||||
|
for i ∈ 1 : 5
|
||||||
|
# dostuff
|
||||||
|
end
|
||||||
|
|
||||||
|
# It can also be used for checking if an element is in a collection
|
||||||
|
3 ∈ [1, 2, 3, 4]
|
||||||
|
# And the notin symbol can be used to check if something isn't in the collection
|
||||||
|
2 ∉ [1, 2, 3, 4]
|
||||||
|
|
||||||
|
# The mathematical constants π (\pi) and ℯ (\euler) are defined as irrationals
|
||||||
|
# that can be cast to a numeric type and they will be calculated to the
|
||||||
|
# required precision.
|
||||||
|
|
||||||
|
# This calculates pi the the precision of a Float64
|
||||||
|
p = Float64(π)
|
||||||
|
@show p
|
||||||
|
|
||||||
|
# This will calculate the fraction closest to ℯ using Int16.
|
||||||
|
e = Rational{Int16}(ℯ)
|
||||||
|
@show e
|
|
@ -0,0 +1,49 @@
|
||||||
|
|
||||||
|
Therer are a lot of different useful packages for julia. All official
|
||||||
|
Packages can be found at https://juliaobserver.com/packages (I think)
|
||||||
|
|
||||||
|
Here are a couple of good packages i use a lot.
|
||||||
|
|
||||||
|
IJulia:
|
||||||
|
This package is required for using Julia in jupyter.
|
||||||
|
|
||||||
|
LinearAlgebra:
|
||||||
|
This package comes with julia without any need for installation and includes
|
||||||
|
a lot of linear algebra functionality, comparable to numpy.linalg.
|
||||||
|
|
||||||
|
Statistics:
|
||||||
|
Distributions, mean, standard deviations, etc...
|
||||||
|
|
||||||
|
Plots:
|
||||||
|
Creates nice plots. Has support for multiple backends (including pyplot).
|
||||||
|
|
||||||
|
PyCall:
|
||||||
|
makes it possible to import python packages into julia and write python code
|
||||||
|
in julia files. Anytime there exists a useful python package and no julia
|
||||||
|
equivalent, this package saves lives.
|
||||||
|
|
||||||
|
Primes:
|
||||||
|
I do not know what magic they put into making this package, but I have never
|
||||||
|
seen a so fast funtion for checking if a number is prime ever.
|
||||||
|
The package is useful for working with prime numbers.
|
||||||
|
|
||||||
|
Memoize:
|
||||||
|
A useful package for automatically memoizing a function. For people taking
|
||||||
|
algorithms and datastructures, this will make more sence later in the course.
|
||||||
|
|
||||||
|
Images:
|
||||||
|
Useful to handle images. Terrible slow to load the package, but once loaded
|
||||||
|
it is quite fast and very featureful.
|
||||||
|
|
||||||
|
SymPy:
|
||||||
|
A wrapper for pythons sympy package which is arguably much better than
|
||||||
|
sympy itself.
|
||||||
|
|
||||||
|
Printf:
|
||||||
|
Wrapper for the C- printf style functions for fast string formating.
|
||||||
|
|
||||||
|
CSV:
|
||||||
|
Package for reading/writing .csv (comma seperated values) files.
|
||||||
|
|
||||||
|
JSON:
|
||||||
|
Package for reading/writing .json files.
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
# Standard function definition
|
||||||
|
function f(x, y)
|
||||||
|
return x + y
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function implicitly returns the value of the last line
|
||||||
|
# of the function so no return keyword is required
|
||||||
|
function g(x, y)
|
||||||
|
x - y
|
||||||
|
end
|
||||||
|
|
||||||
|
# Functions can be defined one one line like this.
|
||||||
|
# This is just a shorthand and is compiled identically to the ones above
|
||||||
|
h(x, y) = x * y
|
||||||
|
|
||||||
|
|
||||||
|
# Can enforce types on function arguments and return value
|
||||||
|
# This makes it possible to create multiple functions
|
||||||
|
# with the same name but different types; These are called methods
|
||||||
|
f(x::Float64, y::Float64)::Float64 = x / y
|
||||||
|
|
||||||
|
|
||||||
|
# Runs the generic method of the function at the top of the file
|
||||||
|
println(f(2, 3))
|
||||||
|
|
||||||
|
# Runs the specific method defined for two Float64.
|
||||||
|
println(f(2.2, 3.2))
|
||||||
|
|
||||||
|
# Also falls back the the generic method as input is (::Float64, ::Int64)
|
||||||
|
println(f(1.6, 3))
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
|
||||||
|
# Also possible to enforce types on local variables
|
||||||
|
|
||||||
|
function main()
|
||||||
|
# Int is an alias for Int32 or Int64 depending on your installation
|
||||||
|
# 32 bit vs 64 bit
|
||||||
|
a::Int = 14
|
||||||
|
b::Int = 7
|
||||||
|
|
||||||
|
# Enforces the type of c for the rest of the scope
|
||||||
|
c::Int = a + b
|
||||||
|
@show typeof(c) c # The @show macro is basically a debug print
|
||||||
|
|
||||||
|
# Normal division of integers returns Float64
|
||||||
|
# Assignment to c tries to convert result to the
|
||||||
|
# type of c as the type is enforced.
|
||||||
|
# If unable to convert to Int it throws an appropriate exception
|
||||||
|
c = a / b
|
||||||
|
@show typeof(c) c
|
||||||
|
|
||||||
|
# // is rational division. This returns a rational number
|
||||||
|
c = a // b
|
||||||
|
@show typeof(c) c
|
||||||
|
|
||||||
|
|
||||||
|
# Integer division is done by div, fld or cld (floor divide/ceil divide)
|
||||||
|
# div rounds towards zero (1.5 -> 1, -2.7 -> -2)
|
||||||
|
# floor rounds downwards (1.5 -> 1, -2.7 -> -3)
|
||||||
|
# ceil rounds upwards (1.5 -> 2, -2.7 -> -2)
|
||||||
|
c = div(a, b)
|
||||||
|
c = fld(a, b)
|
||||||
|
c = cld(a, b)
|
||||||
|
|
||||||
|
# Exponentiation is done with the ^ symbol as in most calculator programs
|
||||||
|
c = a^b
|
||||||
|
@show c
|
||||||
|
end
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
|
||||||
|
# Strings are made with double quotes ""
|
||||||
|
# Single quotes '' are reserved for single characters (i.e. c/c++)
|
||||||
|
s = "Hello world"
|
||||||
|
println(s)
|
||||||
|
|
||||||
|
|
||||||
|
# When converting to a string the function string (with lower case s)
|
||||||
|
# is used.
|
||||||
|
a = 3.14
|
||||||
|
s = string(a)
|
||||||
|
|
||||||
|
println(s) # Prints 3.14
|
||||||
|
|
||||||
|
|
||||||
|
# The length of a string can be found with the length function
|
||||||
|
# similar to len in python
|
||||||
|
|
||||||
|
@show length(s)
|
||||||
|
|
||||||
|
|
||||||
|
# The function String (with upper case S) is used for more direct
|
||||||
|
# interpretation of data as a string
|
||||||
|
|
||||||
|
a = UInt8[65, 66, 67] # ASCII codes for "ABC"
|
||||||
|
|
||||||
|
println(String(a)) # Prints ABC
|
||||||
|
println(string(a)) # Prints UInt8[0x41, 0x42, 0x43]
|
||||||
|
|
||||||
|
|
||||||
|
# When converting from string to some numeric value, the parse function
|
||||||
|
# is used. This function takes in the type to try to parse to as well
|
||||||
|
# as the string to parse.
|
||||||
|
s = "128"
|
||||||
|
a = parse(Int, s)
|
||||||
|
|
||||||
|
@show a
|
||||||
|
|
||||||
|
s = "3.14"
|
||||||
|
a = parse(Float64, s)
|
||||||
|
|
||||||
|
@show a
|
||||||
|
|
||||||
|
|
||||||
|
# Easy inline string formating can be done with the $ (eval) symbol
|
||||||
|
a, b = 3, 5
|
||||||
|
|
||||||
|
s = "a, b = $a, $b" # generates string "a, b = 3, 5"
|
||||||
|
println(s)
|
||||||
|
|
||||||
|
s = "a + b = $(a + b)" # "a + b = 8"
|
||||||
|
println(s)
|
||||||
|
|
||||||
|
# However it is usually faster (performance wise) to just pass in
|
||||||
|
# multiple arguments to f.exs. println
|
||||||
|
println("a + b = ", a + b)
|
||||||
|
|
||||||
|
|
||||||
|
# The raw string macro is very useful when copying raw text and not
|
||||||
|
# wanting to worry about special characters doing special stuff
|
||||||
|
# (i.e. \n for new line). A common use for this is filepaths
|
||||||
|
|
||||||
|
filepath = raw"C:\Users\somefolder\somefile.txt"
|
||||||
|
@show filepath
|
||||||
|
|
||||||
|
# String concatenation is, somewhat weirdly, done with the * sign
|
||||||
|
# instead of the + sign. Julia's justification for this is that
|
||||||
|
# addition (+) is reserved for commutative operations (a + b = b + a)
|
||||||
|
# and since string concatenation is not commutative it gets the multiplication
|
||||||
|
# symbol instead.
|
||||||
|
s = "foo" * "bar"
|
||||||
|
println(s)
|
||||||
|
|
||||||
|
# This by extension means that if you want to repeat a string n times
|
||||||
|
# this is done with the exponentiation (^) sign
|
||||||
|
s = "foo"^5
|
||||||
|
println(s)
|
||||||
|
|
||||||
|
# To get input from the console this can be done with the readline() function
|
||||||
|
# However getting console input when running in vscode with F5/shift+enter
|
||||||
|
# seems to crash for some reason, so avoid console input if thats how you
|
||||||
|
# run the program
|
||||||
|
# s = readline()
|
||||||
|
# println(s)
|
|
@ -0,0 +1,133 @@
|
||||||
|
|
||||||
|
# Lists/Arrays/Vectors work very similarly to python
|
||||||
|
|
||||||
|
# A list can be constructed from a set of elements with [] brackets
|
||||||
|
l = [1, 2, 3, 4]
|
||||||
|
|
||||||
|
@show typeof(l)
|
||||||
|
|
||||||
|
# Indexing the array can be done with [] also.
|
||||||
|
# Keep in mind, arrays in julia are 1-indexed by default
|
||||||
|
@show l[2]
|
||||||
|
|
||||||
|
# The type of elements in the array can be enforced by writing
|
||||||
|
# the type directly in front of the brackets
|
||||||
|
l = Int32[1, 2, 3, 4]
|
||||||
|
|
||||||
|
@show typeof(l)
|
||||||
|
|
||||||
|
# An array will try to implicitly convert data to have a single
|
||||||
|
# element data type
|
||||||
|
l = [1, 2.3, 3//2]
|
||||||
|
|
||||||
|
@show l
|
||||||
|
@show typeof(l)
|
||||||
|
|
||||||
|
# However if this is not possible, the list will get the type Any.
|
||||||
|
# Arrays with multiple types are much more inefficient because of
|
||||||
|
# type bookkeeping.
|
||||||
|
l = [1, 2.3, 3//2, "hello"]
|
||||||
|
|
||||||
|
@show l
|
||||||
|
@show typeof(l)
|
||||||
|
|
||||||
|
# Julia natively supports multidimensional arrays, such as matrices, tensors
|
||||||
|
# etc. with a lot of linear algebra built into the language.
|
||||||
|
m = [
|
||||||
|
1 2 3;
|
||||||
|
4 5 6;
|
||||||
|
7 8 9
|
||||||
|
]
|
||||||
|
|
||||||
|
display(m) # display() is a pretty-print function that is nice for showing
|
||||||
|
# matrices and other containers
|
||||||
|
@show typeof(m)
|
||||||
|
|
||||||
|
# Creating an "empty" array can be done in a couple different ways
|
||||||
|
|
||||||
|
# Creating an array with no elements is usually done with empty brackets []
|
||||||
|
l = []
|
||||||
|
# or with a specific type
|
||||||
|
l = Float32[]
|
||||||
|
|
||||||
|
# Creating an uninitialized array with a set amount of elements
|
||||||
|
# can be achieved by calling the Array constructor
|
||||||
|
|
||||||
|
l = Array{Int, 1}(undef, 100) # Creating a 1d array of 100 undefined elements
|
||||||
|
|
||||||
|
# when working with 1d arrays it is usually better to use the alias Vector
|
||||||
|
# for 1d array. Vector{T} is an alias for Array{T, 1}
|
||||||
|
|
||||||
|
l = Vector{Int}(undef, 100) # Does the exactly the same as line above
|
||||||
|
|
||||||
|
# however it is usually cleaner and safer to use the function zeros() or
|
||||||
|
# ones() to initialize the memory.
|
||||||
|
l = zeros(Int, 100) # creates an array of 100 zeros of type Int
|
||||||
|
l = ones(Int, 100) # same for ones
|
||||||
|
|
||||||
|
# Values can be added to the back of an array with the push! function.
|
||||||
|
l = [1, 2, 3, 4]
|
||||||
|
push!(l, 5)
|
||||||
|
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# Reserving space in the buffer can be done with sizehint!
|
||||||
|
# That way pushing values to the array doesn't cause so many reallocations
|
||||||
|
|
||||||
|
sizehint!(l, 100) # reserving space for 100 elements
|
||||||
|
|
||||||
|
# Pushing to the front can be done with pushfirst!
|
||||||
|
pushfirst!(l, 0)
|
||||||
|
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# Do not confuse push! with append!. Where append in python works as push!
|
||||||
|
# here, append! in julia concatenates a whole array to the back.
|
||||||
|
append!(l, [6, 7, 8, 9])
|
||||||
|
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# Deleting an element at a specific index is done by deleteat!
|
||||||
|
# Keep in mind that deleting elements from an array is often slow
|
||||||
|
# as data has to be moved. In julia it is fast to delete elements near
|
||||||
|
# either end of the array as the shortest end is the one moved to fill
|
||||||
|
# the space.
|
||||||
|
deleteat!(l, 3)
|
||||||
|
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# As in python, list can be created with a list comprehension
|
||||||
|
l = [i^2 for i in 1 : 5] # creates square numbers from 1 to 25
|
||||||
|
@show l
|
||||||
|
|
||||||
|
# As Julia supports multidimensional arrays, it also supports
|
||||||
|
# multidimensional list comprehensions
|
||||||
|
l = [x * y for y in 1 : 3, x in 1 : 4]
|
||||||
|
display(l)
|
||||||
|
|
||||||
|
# It is possible to view an array through a wrapper, making it differently
|
||||||
|
# organized without copying data, f.exs. the transpose of a matrix or
|
||||||
|
# viewing a multidimensional array as the raw memory as a 1d array.
|
||||||
|
# There are many different types of views in multiple different packages.
|
||||||
|
# search the documentation to see more
|
||||||
|
|
||||||
|
l2 = view(l, 3:-1:1, 1 : 4) # Flipping the matrix upside down
|
||||||
|
display(l2)
|
||||||
|
|
||||||
|
l2 = transpose(l) # transpose is a wrapper for a PermutedDimsArray
|
||||||
|
l2 = PermutedDimsArray(l, (2, 1)) # flips x and y dimensions
|
||||||
|
display(l2)
|
||||||
|
|
||||||
|
|
||||||
|
# Random numbers can be created in many different ways. Here are a fiew
|
||||||
|
# easy ones.
|
||||||
|
|
||||||
|
a = rand(Int64) # Create a random Int64
|
||||||
|
a = rand(Float64) # Create random Float64 in the range 0 : 1
|
||||||
|
a = rand(1 : 10) # Create a random Int in the given range
|
||||||
|
l = rand(1 : 10, 3, 4) # Create a random 3x4 matrix with entries in 1 : 10
|
||||||
|
display(l)
|
||||||
|
|
||||||
|
using Random
|
||||||
|
rand!(l, -100 : 100) # Fill l with random entries from -100 : 100
|
||||||
|
display(l)
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
# Dicts (dictionaries/maps) are not as simple as in python, resembling
|
||||||
|
# more the way modern c++ does it
|
||||||
|
|
||||||
|
# Dicts are usually created from an array of "Pair" types (First => Last)
|
||||||
|
# The type of the dict keys and values are automatically infered.
|
||||||
|
d = Dict([
|
||||||
|
"foo" => 3,
|
||||||
|
"bar" => 7
|
||||||
|
])
|
||||||
|
display(d)
|
||||||
|
|
||||||
|
# A dict can also be initialized as an array of two tuples. This works
|
||||||
|
# identically to the example above
|
||||||
|
d = Dict([
|
||||||
|
("foo", 2.71),
|
||||||
|
("bar", 3.14)
|
||||||
|
])
|
||||||
|
display(d)
|
||||||
|
|
||||||
|
# Values can be pushed to the dict similarly to an array.
|
||||||
|
# Keep in mind that creating an empty dict like this gives the type
|
||||||
|
# Dict{Any, Any} which might be somewhat slower because of having
|
||||||
|
# to work for general types
|
||||||
|
d = Dict()
|
||||||
|
push!(d, "foo" => 1//2)
|
||||||
|
push!(d, "bar" => 22//7)
|
||||||
|
display(d)
|
||||||
|
|
||||||
|
# The problem above can be mitigated by infering the types manually
|
||||||
|
d = Dict{String, Rational{Int}}()
|
||||||
|
push!(d, "foo" => 1//2)
|
||||||
|
push!(d, "bar" => 22//7)
|
||||||
|
display(d)
|
||||||
|
|
||||||
|
# A key can be deleted from a dict with delete!()
|
||||||
|
delete!(d, "foo")
|
||||||
|
display(d)
|
||||||
|
|
||||||
|
# To check if a certain key exists in the dict, the haskey function is used
|
||||||
|
println(haskey(d, "foo")) # false
|
||||||
|
println(haskey(d, "bar")) # true
|
||||||
|
|
||||||
|
# Indexing a dict is done with brackets like for any array
|
||||||
|
println(d["bar"])
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
# Sets are created from lists of elements and duplicates are removed
|
||||||
|
s = Set(['a', 'b', 'c', 'b'])
|
||||||
|
display(s)
|
||||||
|
@show 'b' in s
|
||||||
|
|
||||||
|
# As with other collections, elements can be added with push!
|
||||||
|
push!(s, 'd')
|
||||||
|
display(s)
|
||||||
|
|
||||||
|
# And elements are removed with delete!
|
||||||
|
delete!(s, 'b')
|
||||||
|
display(s)
|
||||||
|
|
||||||
|
# An empty set can be made just like an empty dict, but again this forces
|
||||||
|
# it to accept elements of type Any, which can be an minor slowdown
|
||||||
|
s = Set()
|
||||||
|
|
||||||
|
# This is of course fixed by infering the type when creating the dict
|
||||||
|
s = Set{Int}()
|
||||||
|
push!(s, 2)
|
||||||
|
display(s)
|
||||||
|
|
||||||
|
# Usual set operations like union, intersect, difference and checking if
|
||||||
|
# one is a subset of another, are all implemented, many with
|
||||||
|
# unicode infix operators
|
||||||
|
s1 = Set([1, 2, 4, 8, 16])
|
||||||
|
s2 = Set([2, 3, 5, 16])
|
||||||
|
|
||||||
|
@show union(s1, s2) # Takes the set union
|
||||||
|
@show s1 ∪ s2 # Equvialent to the line above (\cup for the unicode macro)
|
||||||
|
|
||||||
|
@show intersect(s1, s2) # Set intesect
|
||||||
|
@show s1 ∩ s2 # Unicode version (\cap)
|
||||||
|
|
||||||
|
@show setdiff(s1, s2) # Set difference. No unicode replacement for this one
|
||||||
|
@show symdiff(s1, s2) # symetric difference. No unicode here either
|
||||||
|
|
||||||
|
union!(s1, s2) # Equvialent to s1 = s1 ∪ s2
|
||||||
|
@show s1
|
||||||
|
|
||||||
|
intersect!(s1, s2) # Equvialent to s1 = s1 ∩ s2
|
||||||
|
@show s1
|
||||||
|
|
||||||
|
# Check if s1 is a subset of s2. (\subseteq)
|
||||||
|
@show s1 ⊆ s2
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
a = 3
|
||||||
|
b = 5
|
||||||
|
|
||||||
|
# if statements are made very similarly to other languages like python
|
||||||
|
if a < b
|
||||||
|
println("<")
|
||||||
|
elseif a > b
|
||||||
|
println(">")
|
||||||
|
else
|
||||||
|
println("=")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
i = 10
|
||||||
|
|
||||||
|
# While loops are made similarly to if statements
|
||||||
|
while i >= 0
|
||||||
|
print(i, ' ')
|
||||||
|
global i -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
println()
|
||||||
|
|
||||||
|
l = []
|
||||||
|
|
||||||
|
# A typical range based for loop is made similarly to python
|
||||||
|
# with matlab style range syntax
|
||||||
|
for i in 1 : 10
|
||||||
|
push!(l, i^2)
|
||||||
|
end
|
||||||
|
println(l)
|
||||||
|
|
||||||
|
# A for-each style for loop is also similar to python
|
||||||
|
for i in l
|
||||||
|
print(i, ' ')
|
||||||
|
end
|
||||||
|
println()
|
||||||
|
|
||||||
|
# Reverse ranges can be achieved by specifying the steplength as the
|
||||||
|
# middle argument in the range
|
||||||
|
for i in length(l) : -1 : 1
|
||||||
|
print(l[i], ' ')
|
||||||
|
end
|
||||||
|
println()
|
||||||
|
|
||||||
|
# A reverse for-each loop can be achieved a couple different ways.
|
||||||
|
# Perhaps the cleanest way is to just create the reverse array and
|
||||||
|
# iterating over that. This however copies the whole array to a new
|
||||||
|
# reversed one, so it is a bit memory inefficient.
|
||||||
|
for i in reverse(l)
|
||||||
|
print(i, ' ')
|
||||||
|
end
|
||||||
|
println()
|
||||||
|
|
||||||
|
# This problem can be mitigated by using an array view that views the
|
||||||
|
# array in reverse. This is fine, but it doesn't look as clean anymore.
|
||||||
|
for i in view(l, length(l) : -1 : 1)
|
||||||
|
print(i, ' ')
|
||||||
|
end
|
||||||
|
println()
|
|
@ -0,0 +1,59 @@
|
||||||
|
# The global scope in julia behaves weirdly. This section shows how to
|
||||||
|
# cicumvent the issues that arise, but the bottom line here should be
|
||||||
|
# to avoid the global scope as much as humanly possible.
|
||||||
|
# Changing globals require ugly explicit syntax, globals cannot be
|
||||||
|
# strongly typed, and, in general, globals are significantly slower
|
||||||
|
# up to several orders of magnitude in some cases.
|
||||||
|
|
||||||
|
a = 5
|
||||||
|
|
||||||
|
# This works
|
||||||
|
if a > 2
|
||||||
|
println(a)
|
||||||
|
a += 3
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
for i in 1 : 3
|
||||||
|
println(a) # This works
|
||||||
|
# a -= 1 # This does not work
|
||||||
|
end
|
||||||
|
|
||||||
|
# Declaring a to be global fixes this problem
|
||||||
|
for i in 1 : 3
|
||||||
|
global a
|
||||||
|
println(a)
|
||||||
|
a -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# None of this is a problem in functions, so do everything in functions
|
||||||
|
function main()
|
||||||
|
a = 7
|
||||||
|
for i in 1 : 3
|
||||||
|
a -= 1
|
||||||
|
end
|
||||||
|
println(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
# An inline function body can be created with a "let" block.
|
||||||
|
# This creates a nameless function that is run immediately.
|
||||||
|
let a = 10
|
||||||
|
for i in 1 : 3
|
||||||
|
a -= 1
|
||||||
|
end
|
||||||
|
println(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# A "begin" block is similar to a let block, but it does not enforce
|
||||||
|
# a new scope, so this code is still in the global scope
|
||||||
|
begin
|
||||||
|
a = 12
|
||||||
|
for i in 1 : 3
|
||||||
|
global a -= 1
|
||||||
|
end
|
||||||
|
println(a)
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
println("Hello World!")
|
Loading…
Reference in New Issue