From 4adc9cd975586de38be49460a710ab64105d7daa Mon Sep 17 00:00:00 2001 From: MarcusTL12 Date: Thu, 12 Sep 2019 16:32:16 +0200 Subject: [PATCH] Initial Commit --- Code/1.HelloWorld.jl | 7 ++ Code/10.structs1.jl | 173 +++++++++++++++++++++++++++++++++ Code/11.structs2.jl | 27 ++++++ Code/12.structs3.jl | 216 +++++++++++++++++++++++++++++++++++++++++ Code/13.fileIO.jl | 39 ++++++++ Code/14.IOBuffer.jl | 15 +++ Code/15.lambdas.jl | 35 +++++++ Code/16.Macro.jl | 25 +++++ Code/17.Broadcast.jl | 21 ++++ Code/18.Unicode.jl | 42 ++++++++ Code/19.Packages.txt | 49 ++++++++++ Code/2.functions.jl | 31 ++++++ Code/3.arithmetic.jl | 41 ++++++++ Code/4.strings.jl | 84 ++++++++++++++++ Code/5.arrays.jl | 133 +++++++++++++++++++++++++ Code/6.dicts.jl | 45 +++++++++ Code/7.sets.jl | 47 +++++++++ Code/8.controllflow.jl | 61 ++++++++++++ Code/9.scopes.jl | 59 +++++++++++ LiveCode/test.jl | 1 + 20 files changed, 1151 insertions(+) create mode 100644 Code/1.HelloWorld.jl create mode 100644 Code/10.structs1.jl create mode 100644 Code/11.structs2.jl create mode 100644 Code/12.structs3.jl create mode 100644 Code/13.fileIO.jl create mode 100644 Code/14.IOBuffer.jl create mode 100644 Code/15.lambdas.jl create mode 100644 Code/16.Macro.jl create mode 100644 Code/17.Broadcast.jl create mode 100644 Code/18.Unicode.jl create mode 100644 Code/19.Packages.txt create mode 100644 Code/2.functions.jl create mode 100644 Code/3.arithmetic.jl create mode 100644 Code/4.strings.jl create mode 100644 Code/5.arrays.jl create mode 100644 Code/6.dicts.jl create mode 100644 Code/7.sets.jl create mode 100644 Code/8.controllflow.jl create mode 100644 Code/9.scopes.jl create mode 100644 LiveCode/test.jl diff --git a/Code/1.HelloWorld.jl b/Code/1.HelloWorld.jl new file mode 100644 index 0000000..f6195a5 --- /dev/null +++ b/Code/1.HelloWorld.jl @@ -0,0 +1,7 @@ +println("Hello Julia!") + +x = 3 +y = 2 +z = x + y + +println(z) diff --git a/Code/10.structs1.jl b/Code/10.structs1.jl new file mode 100644 index 0000000..330eee1 --- /dev/null +++ b/Code/10.structs1.jl @@ -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 diff --git a/Code/11.structs2.jl b/Code/11.structs2.jl new file mode 100644 index 0000000..c93b4d0 --- /dev/null +++ b/Code/11.structs2.jl @@ -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) diff --git a/Code/12.structs3.jl b/Code/12.structs3.jl new file mode 100644 index 0000000..3cb823c --- /dev/null +++ b/Code/12.structs3.jl @@ -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 + diff --git a/Code/13.fileIO.jl b/Code/13.fileIO.jl new file mode 100644 index 0000000..ed09858 --- /dev/null +++ b/Code/13.fileIO.jl @@ -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) diff --git a/Code/14.IOBuffer.jl b/Code/14.IOBuffer.jl new file mode 100644 index 0000000..a0048ee --- /dev/null +++ b/Code/14.IOBuffer.jl @@ -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) + diff --git a/Code/15.lambdas.jl b/Code/15.lambdas.jl new file mode 100644 index 0000000..daba918 --- /dev/null +++ b/Code/15.lambdas.jl @@ -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) diff --git a/Code/16.Macro.jl b/Code/16.Macro.jl new file mode 100644 index 0000000..1cf5fb3 --- /dev/null +++ b/Code/16.Macro.jl @@ -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 + + + diff --git a/Code/17.Broadcast.jl b/Code/17.Broadcast.jl new file mode 100644 index 0000000..3fdeb5a --- /dev/null +++ b/Code/17.Broadcast.jl @@ -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 diff --git a/Code/18.Unicode.jl b/Code/18.Unicode.jl new file mode 100644 index 0000000..cbc293f --- /dev/null +++ b/Code/18.Unicode.jl @@ -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 diff --git a/Code/19.Packages.txt b/Code/19.Packages.txt new file mode 100644 index 0000000..721cb48 --- /dev/null +++ b/Code/19.Packages.txt @@ -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. diff --git a/Code/2.functions.jl b/Code/2.functions.jl new file mode 100644 index 0000000..0782592 --- /dev/null +++ b/Code/2.functions.jl @@ -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)) diff --git a/Code/3.arithmetic.jl b/Code/3.arithmetic.jl new file mode 100644 index 0000000..f2af15a --- /dev/null +++ b/Code/3.arithmetic.jl @@ -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() + diff --git a/Code/4.strings.jl b/Code/4.strings.jl new file mode 100644 index 0000000..a6e5b72 --- /dev/null +++ b/Code/4.strings.jl @@ -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) diff --git a/Code/5.arrays.jl b/Code/5.arrays.jl new file mode 100644 index 0000000..69fa40a --- /dev/null +++ b/Code/5.arrays.jl @@ -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) diff --git a/Code/6.dicts.jl b/Code/6.dicts.jl new file mode 100644 index 0000000..37c0ab1 --- /dev/null +++ b/Code/6.dicts.jl @@ -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"]) diff --git a/Code/7.sets.jl b/Code/7.sets.jl new file mode 100644 index 0000000..b189f44 --- /dev/null +++ b/Code/7.sets.jl @@ -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 + diff --git a/Code/8.controllflow.jl b/Code/8.controllflow.jl new file mode 100644 index 0000000..ebaa049 --- /dev/null +++ b/Code/8.controllflow.jl @@ -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() diff --git a/Code/9.scopes.jl b/Code/9.scopes.jl new file mode 100644 index 0000000..50a3eb5 --- /dev/null +++ b/Code/9.scopes.jl @@ -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 + diff --git a/LiveCode/test.jl b/LiveCode/test.jl new file mode 100644 index 0000000..3f4ab44 --- /dev/null +++ b/LiveCode/test.jl @@ -0,0 +1 @@ +println("Hello World!") \ No newline at end of file