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