{ "cells": [ { "cell_type": "markdown", "source": [ "## Overview\n", "\n", "This tutorial will give some examples of basic Julia commands and syntax.\n", "\n", "## Getting Help\n", "* Check out the official documentation for Julia: [https://docs.julialang.org/en/v1/](https://docs.julialang.org/en/v1/).\n", "* [Stack Overflow](https://stackoverflow.com) is a commonly-used resource for programming assistance.\n", "* At a code prompt or in the REPL, you can always type `?functionname` to get help." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Packages\n", "\n", "This tutorial does not require any additional packages, but you can [download or view the `Manifest.toml` here](https://raw.githubusercontent.com/vsrikrish/climate-risk-analysis/gh-pages/tutorials/notebooks/julia-basics/Manifest.toml)." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Comments\n", "Comments hide statements from the interpreter or compiler. It's a good idea to liberally comment your code so readers (including yourself!) know why your code is structured and written the way it is.\n", "Single-line comments in Julia are preceded with a `#`. Multi-line comments are preceded with `#=` and ended with `=#`" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Suppressing Output" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "You can suppress output using a semi-colon (;)." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "4+8;" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "That didn't show anything, as opposed to:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "4+8" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "## Variables\n", "Variables are names which correspond to some type of object. These names are bound to objects (and hence their values) using the `=` operator." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "x = 5" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Variables can be manipulated with standard arithmetic operators." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "4 + x" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Another advantage of Julia is the ability to use Greek letters (or other Unicode characters) as variable names. For example, type a backslash followed by the name of the Greek letter (*i.e.* `\\alpha`) followed by TAB." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "α = 3" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "You can also include subscripts or superscripts in variable names using `\\_` and `\\^`, respectively, followed by TAB. If using a Greek letter followed by a sub- or super-script, make sure you TAB following the name of the letter before the sub- or super-script. Effectively, TAB after you finish typing the name of each `\\character`." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "β₁ = 10 # The name of this variable was entered with \\beta + TAB + \\_1 + TAB" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "However, try not to overwrite predefined names! For example, you might not want to use `π` as a variable name..." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "π" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "In the grand scheme of things, overwriting `π` is not a huge deal unless you want to do some trigonometry. However, there are more important predefined functions and variables that you may want to be aware of. Always check that a variable or function name is not predefined!" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Data Types" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Each datum (importantly, *not* the variable which is bound to it) has a [data type](https://docs.julialang.org/en/v1/manual/types/). Julia types are similar to C types, in that they require not only the *type* of data (Int, Float, String, etc), but also the precision (which is related to the amount of memory allocated to the variable). Issues with precision won't be a big deal in this class, though they matter when you're concerned about performance vs. decimal accuracy of code." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "You can identify the type of a variable or expression with the `typeof()` function." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "typeof(\"This is a string.\")" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "typeof(x)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Numeric types\n", "A key distinction is between an integer type (or *Int*) and a floating-point number type (or *float*). Integers only hold whole numbers, while floating-point numbers correspond to numbers with fractional (or decimal) parts. For example, `9` is an integer, while `9.25` is a floating point number. The difference between the two has to do with the way the number is stored in memory. `9`, an integer, is handled differently in memory than `9.0`, which is a floating-point number, even though they're mathematically the same value." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "typeof(9)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "typeof(9.25)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Sometimes certain function specifications will require you to use a Float variable instead of an Int. One way to force an Int variable to be a Float is to add a decimal point at the end of the integer." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "typeof(9.)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Strings\n", "Strings hold characters, rather than numeric values. Even if a string contains what seems like a number, it is actually stored as the character representation of the digits. As a result, you cannot use arithmetic operators (for example) on this datum." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "\"5\" + 5" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "However, you can try to tell Julia to interpret a string encoding a numeric character as a numeric value using the `parse()` function. This can also be used to encode a numeric data as a string." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "parse(Int64, \"5\") + 5" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Two strings can be concatenated using `*`:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "\"Hello\" * \" \" * \"there\"" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Booleans" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Boolean variables (or *Bools*) are logical variables, that can have `true` or `false` as values." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "b = true" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Numerical comparisons, such as `==`, `!=`, or `<`, return a Bool." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "c = 9 > 11" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Bools are important for logical flows, such as if-then-else blocks or certain types of loops." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Mathematical operations\n", "Addition, subtraction, multiplication, and division work as you would expect. Just pay attention to types! The type of the output is influenced by the type of the inputs: adding or multiplying an Int by a Float will always result in a Float, even if the Float is mathematically an integer. Division is a little special: dividing an Int by another Int will still return a float, because Julia doesn't know ahead of time if the denominator is a factor of the numerator." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "3 + 5" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "3 * 2" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "3 * 2." ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "6 - 2" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "9 / 3" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Raising a base to an exponent uses `^`, not `**`." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "3^2" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Julia allows the use of updating operators to simplify updating a variable in place (in other words, using `x += 5` instead of `x = x + 5`." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "### Boolean algebra" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Logical operations can be used on variables of type `Bool`. Typical operators are `&&` (and), `||` (or), and `!` (not)." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "true && true" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "true && false" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "true || false" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "!true" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Comparisons can be chained together." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "3 < 4 || 8 == 12" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "We didn't do this above, since Julia doesn't require it, but it's easier to understand these types of compound expressions if you use parentheses to signal the order of operations. This helps with debugging!" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "(3 < 4) || (8 == 12)\n", "\n", "# Data Structures" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Data structures are containers which hold multiple values in a convenient fashion. Julia has several built-in data structures, and there are many extensions provided in additional packages." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "### Tuples" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Tuples are collections of values. Julia will pay attention to the types of these values, but they can be mixed. Tuples are also *immutable*: their values cannot be changed once they are defined." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Tuples can be defined by just separating values with commas." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_tuple = 4, 5, 6" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "To access a value, use square brackets and the desired index. **Note**: Julia indexing starts at 1, not 0!" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_tuple[1]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "As mentioned above, tuples are immutable. What happens if we try to change the value of the first element of `test_tuple`?" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_tuple[1] = 5" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Tuples also do not have to hold the same types of values." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_tuple_2 = 4, 5., 'h'\n", "typeof(test_tuple_2)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Tuples can also be defined by enclosing the values in parentheses." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_tuple_3 = (4, 5., 'h')\n", "typeof(test_tuple_3)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Arrays\n", "Arrays also hold multiple values, which can be accessed based on their index position. Arrays are commonly defined using square brackets." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_array = [1, 4, 7, 8]\n", "test_array[2]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Unlike tuples, arrays are mutable, and their contained values can be changed later." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_array[1] = 6\n", "test_array" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Arrays also can hold multiple types. Unlike tuples, this causes the array to no longer care about types at all." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_array_2 = [6, 5., 'h']\n", "typeof(test_array)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "typeof(test_array_2)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Dictionaries" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Instead of using integer indices based on position, dictionaries are indexed by keys. They are specified by passing key-value pairs to the `Dict()` method." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_dict = Dict(\"A\"=>1, \"B\"=>2)\n", "test_dict[\"B\"]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Comprehensions" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Creating a data structure with more than a handful of elements can be tedious to do by hand. If your desired array follows a certain pattern, you can create structures using a *comprehension*. Comprehensions iterate over some other data structure (such as an array) implicitly and populate the new data structure based on the specified instructions." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "[i^2 for i in 0:1:5]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "For dictionaries, make sure that you also specify the keys." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "Dict(string(i) => i^2 for i in 0:1:5)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "## Functions" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "A function is an object which accepts a tuple of arguments and maps them to a return value. In Julia, functions are defined using the following syntax." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function my_actual_function(x, y)\n", "\treturn x + y\n", "end\n", "my_actual_function(3, 5)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Functions in Julia do not require explicit use of a `return` statement. They will return the last expression evaluated in their definition. However, it's good style to explicitly `return` function outputs. This improves readability and debugging, especially when functions can return multiple expressions based on logical control flows (if-then-else blocks)." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Functions in Julia are objects, and can be treated like other objects. They can be assigned to new variables or passed as arguments to other functions." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "g = my_actual_function\n", "g(3, 5)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "function function_of_functions(f, x, y)\n", "\treturn f(x, y)\n", "end\n", "function_of_functions(g, 3, 5)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Short and Anonymous Functions" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "In addition to the long form of the function definition shown above, simple functions can be specified in more compact forms when helpful." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "This is the short form:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "h₁(x) = x^2 # make the subscript using \\_1 + \n", "h₁(4)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "This is the anonymous form:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "x->sin(x)\n", "(x->sin(x))(π/4)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Mutating Functions\n", "The convention in Julia is that functions should not modify (or *mutate*) their input data. The reason for this is to ensure that the data is preserved. Mutating functions are mainly appropriate for applications where performance needs to be optimized, and making a copy of the input data would be too memory-intensive.\n", "If you do write a mutating function in Julia, the convention is to add a `!` to its name, like `my_mutating_function!(x)`." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "### Optional arguments" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "There are two extremes with regard to function parameters which do not always need to be changed. The first is to hard-code them into the function body, which has a clear downside: when you do want to change them, the function needs to be edited directly. The other extreme is to treat them as regular arguments, passing them every time the function is called. This has the downside of potentially creating bloated function calls, particularly when there is a standard default value that makes sense for most function evaluations." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Most modern languages, including Julia, allow an alternate solution, which is to make these arguments *optional*. This involves setting a default value, which is used unless the argument is explicitly defined in a function call." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function setting_optional_arguments(x, y, c=0.5)\n", "\treturn c * (x + y)\n", "end" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "If we want to stick with the fixed value $c=0.5$, all we have to do is call `setting_optional_arguments` with the `x` and `y` arguments." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "setting_optional_arguments(3, 5)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Otherwise, we can pass a new value for `c`." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "setting_optional_arguments(3, 5, 2)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Passing data structures as arguments" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Instead of passing variables individually, it may make sense to pass a data structure, such as an array or a tuple, and then unpacking within the function definition. This is straightforward in long form: access the appropriate elements using their index." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "In short or anonymous form, there is a trick which allows the use of readable variables within the function definition." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "h₂((x,y)) = x*y # enclose the input arguments in parentheses to tell Julia to expect and unpack a tuple" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "h₂((2, 3)) # this works perfectly, as we passed in a tuple" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "h₂(2, 3) # this gives an error, as h₂ expects a single tuple, not two different numeric values" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "h₂([3, 10]) # this also works with arrays instead of tuples" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Vectorized operations" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Julia uses **dot syntax** to vectorize an operation and apply it *element-wise* across an array." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "For example, to calculate the square root of 3:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "sqrt(3)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "To calculate the square roots of every integer between 1 and 5:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "sqrt.([1, 2, 3, 4, 5])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "The same dot syntax is used for arithmetic operations over arrays, since these operations are really functions." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "[1, 2, 3, 4] .* 2" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Vectorization can be faster and is more concise to write and read than applying the same function to multiple variables or objects explicitly, so take advantage!" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "### Returning multiple values" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "You can return multiple values by separating them with a comma. This implicitly causes the function to return a tuple of values." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function return_multiple_values(x, y)\n", "\treturn x + y, x * y\n", "end\n", "\n", "return_multiple_values(3, 5)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "These values can be unpacked into multiple variables." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "n, ν = return_multiple_values(3, 5)\n", "n" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "ν" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Returning `nothing`" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Sometimes you don't want a function to return any values at all. For example, you might want a function that only prints a string to the console." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function print_some_string(x)\n", "\tprintln(\"x: $x\")\n", "\treturn nothing\n", "end\n", "print_some_string(42)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "## Printing Text Output" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "The `Text()` function returns its argument as a plain text string. Notice how this is different from evaluating a string!" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "Text(\"I'm printing a string.\")" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "`Text()` is used in this tutorial as it *returns* the string passed to it. To print directly to the console, use `println()`." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "println(\"I'm writing a string to the console.\")" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Printing Variables In a String" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "What if we want to include the value of a variable inside of a string? We do this using *string interpolation*, using `$variablename` inside of the string." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "bar = 42\n", "Text(\"Now I'm printing a variable: $bar\")" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "## Control Flows" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "One of the tricky things about learning a new programming language can be getting used to the specifics of control flow syntax. These types of flows include conditional if-then-else statements or loops." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "### Conditional Blocks" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Conditional blocks allow different pieces of code to be evaluated depending on the value of a boolean expression or variable. For example, if we wanted to compute the absolute value of a number, rather than using `abs()`:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function our_abs(x)\n", "\tif x >= 0\n", "\t\treturn x\n", "\telse\n", "\t\treturn -x\n", "\tend\n", "end\n", "\n", "our_abs(4)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "our_abs(-4)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "To nest conditional statements, use `elseif`." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function test_sign(x)\n", "\tif x > 0\n", "\t\treturn Text(\"x is positive.\")\n", "\telseif x < 0\n", "\t\treturn Text(\"x is negative.\")\n", "\telse\n", "\t\treturn Text(\"x is zero.\")\n", "\tend\n", "end\n", "\n", "test_sign(-5)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "test_sign(0)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Loops" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Loops allow expressions to be evaluated repeatedly until they are terminated. The two main types of loops are `while` loops and `for` loops." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "#### While loops" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "`while` loops continue to evaluate an expression so long as a specified boolean condition is `true`. This is useful when you don't know how many iterations it will take for the desired goal to be reached." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function compute_factorial(x)\n", "\tfactorial = 1\n", "\twhile (x > 1)\n", "\t\tfactorial *= x\n", "\t\tx -= 1\n", "\tend\n", "\treturn factorial\n", "end\n", "\n", "compute_factorial(5)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "**Warning**: While loops can easily turn into infinite loops if the condition is never meaningfully updated. Be careful, and look there if your programs are getting stuck. Also, If the expression in a `while` loop is false when the loop is reached, the loop will never be evaluated." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "#### For loops" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "`for` loops run for a finite number of iterations, based on some defined index variable." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function add_some_numbers(x)\n", "\ttotal_sum = 0 # initialize at zero since we're adding\n", "\tfor i=1:x # the counter i is updated every iteration\n", "\t\ttotal_sum += i\n", "\tend\n", "\treturn total_sum\n", "end\n", "add_some_numbers(4)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "`for` loops can also iterate over explicitly passed containers, rather than iterating over an incrementally-updated index sequence. Use the `in` keyword when defining the loop." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function add_passed_numbers(set)\n", "\ttotal_sum = 0\n", "\tfor i in set # this is the syntax we use when we want i to correspond to different container values\n", "\t\ttotal_sum += i\n", "\tend\n", "\treturn total_sum\n", "end\n", "add_passed_numbers([1, 3, 5])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "## Linear algebra" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Matrices are defined in Julia as 2d arrays. Unlike basic arrays, matrices need to contain the same data type so Julia knows what operations are allowed. When defining a matrix, use semicolons to separate rows. Row elements should not be separated by commas." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_matrix = [1 2 3; 4 5 6]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "You can also specify matrices using spaces and newlines." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_matrix_2 = [1 2 3\n", "\t\t\t\t 4 5 6]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Finally, matrices can be created using comprehensions by separating the inputs by a comma." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "[i*j for i in 1:1:5, j in 1:1:5]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Vectors are treated as 1d matrices." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "test_row_vector = [1 2 3]" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "test_col_vector = [1; 2; 3]" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Many linear algebra operations on vectors and matrices can be loaded using the `LinearAlgebra` package." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Package management" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Sometimes you might need functionality that does not exist in base Julia. Julia handles packages using the [`Pkg` package manager](https://docs.julialang.org/en/v1/stdlib/Pkg/). After finding a package which has the functions that you need, you have two options:\n", "1. Use the package management prompt in the Julia REPL (the standard Julia interface; what you get when you type `julia` in your terminal). Enter this by typing `]` at the standard green Julia prompt `julia>`. This will become a blue `pkg>`. You can then download and install new packages using `add packagename`.\n", "2. From the standard prompt, enter `using Pkg; Pkg.add(packagename)`.\n", "The `packagename` package can then be used by adding `using packagename` to the start of the script." ], "metadata": {} } ], "nbformat_minor": 3, "metadata": { "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.8.5" }, "kernelspec": { "name": "julia-1.8", "display_name": "Julia 1.8.5", "language": "julia" } }, "nbformat": 4 }