{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Julia Basics\n",
        "\n",
        "## Overview\n",
        "\n",
        "This tutorial will give some examples of basic Julia commands and\n",
        "syntax. It is written for students who have taken an introductory\n",
        "scientific programming course in Python, and highlights key differences\n",
        "between the two languages.\n",
        "\n",
        "## Key Differences from Python\n",
        "\n",
        "If you are coming from Python, here are the most important differences\n",
        "to keep in mind as you read.\n",
        "\n",
        "### 1-based indexing\n",
        "\n",
        "Julia indexes arrays starting at 1, not 0. `arr[1]` is the first\n",
        "element. This is consistent with MATLAB, R, and Fortran, but different\n",
        "from Python and C.\n",
        "\n",
        "### `true` and `false` are lowercase\n",
        "\n",
        "Python uses `True` and `False`. Julia uses `true` and `false`.\n",
        "\n",
        "### No classes — functions are the unit of abstraction\n",
        "\n",
        "Python organizes code with classes and methods (`obj.method()`). Julia\n",
        "uses **functions** and **multiple dispatch**: you define functions that\n",
        "behave differently depending on the types of *all* their arguments.\n",
        "There is no `self` and no method-calling dot. Instead of `arr.sort()`,\n",
        "you write `sort(arr)`.\n",
        "\n",
        "### Explicit broadcasting with `.`\n",
        "\n",
        "In NumPy, `np.sqrt(arr)` automatically applies to every element. In\n",
        "Julia, you must opt in to element-wise application with a dot:\n",
        "`sqrt.(arr)`. The dot is the **broadcast operator** and is one of the\n",
        "most distinctive parts of Julia syntax.\n",
        "\n",
        "### Scope rules\n",
        "\n",
        "Variables defined inside a loop or `if` block in a **function** or\n",
        "**script** are local to that block by default. This is stricter than\n",
        "Python. If you define `x` inside a `for` loop, `x` won’t exist after the\n",
        "loop ends (unless you declare it with `local` or assign to it in an\n",
        "outer scope first).\n",
        "\n",
        "### `using`, `import`, and `include`\n",
        "\n",
        "- `using PackageName` loads a package and brings its exported names into\n",
        "  scope (like `from package import *`).\n",
        "- `import PackageName` loads a package but requires qualification\n",
        "  (`PackageName.func()`).\n",
        "- `include(\"filename.jl\")` is completely different: it runs a source\n",
        "  file as if you had typed it at the REPL. It does **not** create a\n",
        "  module or namespace.\n",
        "\n",
        "### Strings concatenate with `*`, not `+`\n",
        "\n",
        "Julia reserves `+` for numeric addition only. To join strings, use `*`\n",
        "or the `string()` function.\n",
        "\n",
        "## The Julia REPL\n",
        "\n",
        "Julia’s interactive environment (the **REPL**, for Read-Eval-Print-Loop)\n",
        "has several modes beyond just typing code:\n",
        "\n",
        "| Mode    | How to enter | Prompt   | Purpose                        |\n",
        "|---------|--------------|----------|--------------------------------|\n",
        "| Julia   | (default)    | `julia>` | Evaluate Julia expressions     |\n",
        "| Help    | `?`          | `help?>` | Look up function documentation |\n",
        "| Package | `]`          | `pkg>`   | Add/remove/update packages     |\n",
        "| Shell   | `;`          | `shell>` | Run shell commands             |\n",
        "\n",
        "For example, to break `10` into its digits:"
      ],
      "id": "2c52184e-931d-47bc-b256-3c041d123c14"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "2-element Vector{Int64}:\n",
              " 0\n",
              " 1"
            ]
          }
        }
      ],
      "source": [
        "# Type ? at the julia> prompt, then type digits(10)\n",
        "digits(10)"
      ],
      "id": "2"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To add a package:"
      ],
      "id": "65e215dc-07d4-4a4b-bd9d-e49d627b0e7c"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Type ] at the julia> prompt, then: add Plots"
      ],
      "id": "4"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To list files in the current directory:"
      ],
      "id": "a1393a7d-5638-41a7-a4f7-91e7eeee2aab"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Type ; at the julia> prompt, then: ls"
      ],
      "id": "6"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "As a result, you can install packages, read documentation, and manage\n",
        "files without leaving Julia.\n",
        "\n",
        "## Getting Help\n",
        "\n",
        "- Check out the official documentation for Julia:\n",
        "  <https://docs.julialang.org/en/v1/>.\n",
        "- [Stack Overflow](https://stackoverflow.com) is a commonly-used\n",
        "  resource for programming assistance.\n",
        "- At a code prompt or in the REPL, you can always type `?functionname`\n",
        "  to get help.\n",
        "\n",
        "## Comments\n",
        "\n",
        "Comments hide statements from the interpreter or compiler. It’s a good\n",
        "idea to liberally comment your code so readers (including yourself!)\n",
        "know why your code is structured and written the way it is. Good\n",
        "comments focus on the *why* of the code logic and structure, not the\n",
        "*what* or the *how*. The *what* and *how* should be clear from the code\n",
        "itself.\n",
        "\n",
        "Single-line comments in Julia are preceded with a `#`. Multi-line\n",
        "comments are preceded with `#=` and ended with `=#`\n",
        "\n",
        "## Suppressing Output\n",
        "\n",
        "You can suppress output using a semi-colon (`;`)."
      ],
      "id": "6e4a8e6e-1f41-4f8e-8033-034480ccd4b7"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "4+8;"
      ],
      "id": "8"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "That didn’t show anything, as opposed to:"
      ],
      "id": "bf6c8b84-1408-4f9f-be53-78499630325f"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "12"
            ]
          }
        }
      ],
      "source": [
        "4+8"
      ],
      "id": "10"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Suppressing output is usually unnecessary but can be useful when you are\n",
        "doing a multi-step calculation and don’t want to see the intermediate\n",
        "results or when the output of a command is extremely large.\n",
        "\n",
        "### `begin...end` Blocks\n",
        "\n",
        "Sometimes you need to group multiple expressions together — for example,\n",
        "to suppress the output of a multi-line computation. Use a `begin...end`\n",
        "block:"
      ],
      "id": "8f9f42ce-1b7f-4f29-b7e6-e20f109499ae"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "x = begin\n",
        "    a = 4 + 8\n",
        "    b = a * 2\n",
        "    a + b\n",
        "end;"
      ],
      "id": "12"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The block returns the value of its last expression (in this case, into\n",
        "`x`, which has a value of `a + b`). `begin...end` is the equivalent of\n",
        "wrapping expressions in parentheses in Python when you need multiple\n",
        "statements in a position that expects one.\n",
        "\n",
        "Note that `a` and `b` will not exist outside of the `begin...end` block,\n",
        "because they are local to that block. If you want to use them later, you\n",
        "need to define them outside of the block.\n",
        "\n",
        "## Variables\n",
        "\n",
        "Variables are names which correspond to some type of object. These names\n",
        "are bound to objects (and hence their values) using the `=` operator."
      ],
      "id": "017d3838-ffc6-45b1-a1e9-d682db90fb3d"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "5"
            ]
          }
        }
      ],
      "source": [
        "x = 5"
      ],
      "id": "14"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Variables can be manipulated with standard arithmetic operators."
      ],
      "id": "542a262d-fe25-4ac9-9583-0a81f04b1f58"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "9"
            ]
          }
        }
      ],
      "source": [
        "4 + x"
      ],
      "id": "16"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Another advantage of Julia is the ability to use Greek letters (or other\n",
        "Unicode characters) as variable names. For example, type a backslash\n",
        "followed by the name of the Greek letter (*i.e.* `\\alpha`) followed by\n",
        "TAB."
      ],
      "id": "ae394cd2-5491-460f-9daf-78ebcd79d450"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "3"
            ]
          }
        }
      ],
      "source": [
        "α = 3"
      ],
      "id": "18"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "You can also include subscripts or superscripts in variable names using\n",
        "`\\_` and `\\^`, respectively, followed by TAB. If using a Greek letter\n",
        "followed by a sub- or super-script, make sure you TAB following the name\n",
        "of the letter before the sub- or super-script. Effectively, TAB after\n",
        "you finish typing the name of each `\\character`."
      ],
      "id": "05e63127-1d9d-442e-b950-b043344bab2a"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "10"
            ]
          }
        }
      ],
      "source": [
        "β₁ = 10 # The name of this variable was entered with \\beta + TAB + \\_1 + TAB"
      ],
      "id": "20"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "However, try not to overwrite predefined names! For example, you might\n",
        "not want to use `π` as a variable name…"
      ],
      "id": "3233eded-c324-45c0-807a-f0ba36c07e99"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "π = 3.1415926535897..."
            ]
          }
        }
      ],
      "source": [
        "π"
      ],
      "id": "22"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In the grand scheme of things, overwriting `π` is not a huge deal unless\n",
        "you want to do some trigonometry. However, there are more important\n",
        "predefined functions and variables that you may want to be aware of.\n",
        "Always check that a variable or function name is not predefined!\n",
        "\n",
        "## Data Types\n",
        "\n",
        "Every value in Julia has a **type**. Types determine what operations are\n",
        "valid on a value and how it is stored in memory. Julia’s type system is\n",
        "**dynamic**: you don’t have to declare types for variables (unlike C),\n",
        "but the compiler infers them automatically. You can optionally annotate\n",
        "types to improve performance or enforce constraints.\n",
        "\n",
        "This is different from Python’s duck typing: in Python, any object that\n",
        "has a `.sort()` method can be sorted. In Julia, `sort()` dispatches to a\n",
        "specific implementation based on the concrete type of the argument. This\n",
        "gives Julia C-like performance without C-like verbosity.\n",
        "\n",
        "You can identify the type of a variable or expression with the\n",
        "`typeof()` function."
      ],
      "id": "b801a0e3-2a17-4007-abce-2f9778dc3193"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "String"
            ]
          }
        }
      ],
      "source": [
        "typeof(\"This is a string.\")"
      ],
      "id": "24"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Int64"
            ]
          }
        }
      ],
      "source": [
        "typeof(x)"
      ],
      "id": "26"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Numeric Types\n",
        "\n",
        "A key distinction is between an integer type (or *Int*) and a\n",
        "floating-point number type (or *float*). Integers only hold whole\n",
        "numbers, while floating-point numbers correspond to numbers with\n",
        "fractional (or decimal) parts. For example, `9` is an integer, while\n",
        "`9.25` is a floating point number. The difference between the two has to\n",
        "do with the way the number is stored in memory. `9`, an integer, is\n",
        "handled differently in memory than `9.0`, which is a floating-point\n",
        "number, even though they’re mathematically the same value."
      ],
      "id": "555aa2be-5380-4fa3-bb76-56e5cc7bd10a"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Int64"
            ]
          }
        }
      ],
      "source": [
        "typeof(9)"
      ],
      "id": "28"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Float64"
            ]
          }
        }
      ],
      "source": [
        "typeof(9.25)"
      ],
      "id": "30"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Sometimes certain function specifications will require you to use a\n",
        "Float variable instead of an Int. One way to force an Int variable to be\n",
        "a Float is to add a decimal point at the end of the integer."
      ],
      "id": "32861c6b-6221-4c73-a11e-d66ec7ffbe8b"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Float64"
            ]
          }
        }
      ],
      "source": [
        "typeof(9.0)"
      ],
      "id": "32"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Strings\n",
        "\n",
        "Strings hold characters, rather than numeric values. Even if a string\n",
        "contains what seems like a number, it is actually stored as the\n",
        "character representation of the digits. As a result, you cannot use\n",
        "arithmetic operators (for example) on this datum."
      ],
      "id": "832c0cec-1f07-47d3-aefb-f187ba420377"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "\"5\" + 5"
      ],
      "id": "34"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "However, you can try to tell Julia to interpret a string encoding a\n",
        "numeric character as a numeric value using the `parse()` function. This\n",
        "can also be used to encode a numeric data as a string."
      ],
      "id": "2c7d2e40-808a-4138-8f2e-e36994ff8380"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "10"
            ]
          }
        }
      ],
      "source": [
        "parse(Int64, \"5\") + 5"
      ],
      "id": "36"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Two strings can be concatenated using `*`:"
      ],
      "id": "876ebc10-2139-45d7-bc43-7c0a1fb69c3c"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "\"Hello there\""
            ]
          }
        }
      ],
      "source": [
        "\"Hello\" * \" \" * \"there\""
      ],
      "id": "38"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Ranges\n",
        "\n",
        "A **range** represents a sequence of numbers without storing all of them\n",
        "in memory. Ranges are created with the colon operator `:` and are the\n",
        "standard way to express iteration bounds or evenly-spaced sequences."
      ],
      "id": "3781ebe4-6ea7-4ed2-975d-7bde906d1257"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "1.0:0.5:3.0"
            ]
          }
        }
      ],
      "source": [
        "r = 1:5           # a UnitRange: numbers 1 through 5\n",
        "s = 1:0.5:3       # a StepRange: 1.0, 1.5, 2.0, 2.5, 3.0"
      ],
      "id": "40"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Ranges are **lazy** — `1:1000000` takes almost no memory, just like\n",
        "Python’s `range(1, 1000001)`. But unlike Python’s `range`, you can use\n",
        "Julia ranges directly in arithmetic and they work as you’d expect\n",
        "(broadcasting aside). To materialize a range into an array, use\n",
        "`collect(r)`.\n",
        "\n",
        "### `nothing`, `missing`, and `NaN`\n",
        "\n",
        "Julia distinguishes three kinds of “absent” or “invalid” values, which\n",
        "is important for data analysis:\n",
        "\n",
        "| Value | Meaning | Use case |\n",
        "|--------------------|-------------------------|----------------------------|\n",
        "| `nothing` | No value / void return | The return value of `println`; a function that has nothing useful to return |\n",
        "| `missing` | Data is absent (from `Missings.jl`) | A missing survey response; propagates through calculations (`missing + 1` → `missing`) |\n",
        "| `NaN` | Not a Number (Float64) | The result of `0/0`; propagates differently from `missing` — `NaN + 1` is still `NaN` |\n",
        "\n",
        "For this course, you’ll encounter `missing` most often when loading\n",
        "real-world datasets with gaps. Use `skipmissing(data)` to filter out\n",
        "missing values, or `coalesce.(data, 0.0)` to replace them with a\n",
        "default.\n",
        "\n",
        "### Named Tuples\n",
        "\n",
        "A `NamedTuple` is like a tuple where each element has a name. They are\n",
        "lightweight, immutable, and very useful for passing structured options\n",
        "to functions."
      ],
      "id": "58c023fb-d93b-43fe-9f34-21eec5a7aaef"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "95"
            ]
          }
        }
      ],
      "source": [
        "nt = (name=\"Alice\", score=95, passed=true)\n",
        "nt.name      # access by name\n",
        "nt[2]        # or by position"
      ],
      "id": "42"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Named tuples are the Julia equivalent of Python’s simple data classes or\n",
        "lightweight dictionaries. Many Julia functions (especially in plotting)\n",
        "accept named tuples for keyword-style options.\n",
        "\n",
        "### Booleans\n",
        "\n",
        "Boolean variables (or *Bools*) are logical variables, that can have\n",
        "`true` or `false` as values. (Note: these are lowercase — Python uses\n",
        "`True`/`False`.)"
      ],
      "id": "cd1b6e7e-d4d2-46cc-9374-c1cb325a4907"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "true"
            ]
          }
        }
      ],
      "source": [
        "b = true"
      ],
      "id": "44"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Numerical comparisons, such as `==`, `!=`, or `<`, return a Bool."
      ],
      "id": "01cca0cc-ac2b-46ac-8bba-89572b82edbd"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "false"
            ]
          }
        }
      ],
      "source": [
        "c = 9 > 11"
      ],
      "id": "46"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Bools are important for logical flows, such as if-then-else blocks or\n",
        "certain types of loops.\n",
        "\n",
        "## Mathematical Operations\n",
        "\n",
        "Addition, subtraction, multiplication, and division work as you would\n",
        "expect. Just pay attention to types! The type of the output is\n",
        "influenced by the type of the inputs: adding or multiplying an Int by a\n",
        "Float will always result in a Float, even if the Float is mathematically\n",
        "an integer. Division is a little special: dividing an Int by another Int\n",
        "will still return a float, because Julia doesn’t know ahead of time if\n",
        "the denominator is a factor of the numerator."
      ],
      "id": "07c3f665-4724-427b-8536-9153de1db506"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "8"
            ]
          }
        }
      ],
      "source": [
        "3 + 5"
      ],
      "id": "48"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "6"
            ]
          }
        }
      ],
      "source": [
        "3 * 2"
      ],
      "id": "50"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "6.0"
            ]
          }
        }
      ],
      "source": [
        "3 * 2."
      ],
      "id": "52"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4"
            ]
          }
        }
      ],
      "source": [
        "6 - 2"
      ],
      "id": "54"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "3.0"
            ]
          }
        }
      ],
      "source": [
        "9 / 3"
      ],
      "id": "56"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Raising a base to an exponent uses `^`, not `**`."
      ],
      "id": "c36e6de3-1891-41d5-a52f-8a586530c91e"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "9"
            ]
          }
        }
      ],
      "source": [
        "3^2"
      ],
      "id": "58"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Julia allows the use of updating operators to simplify updating a\n",
        "variable in place (in other words, using `x += 5` instead of\n",
        "`x = x + 5`.\n",
        "\n",
        "## Boolean Algebra\n",
        "\n",
        "Logical operations can be used on variables of type `Bool`. Typical\n",
        "operators are `&&` (and), `||` (or), and `!` (not)."
      ],
      "id": "3aea7516-f3d0-4be1-8177-ee9beddb0f9b"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "true"
            ]
          }
        }
      ],
      "source": [
        "true && true"
      ],
      "id": "60"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "false"
            ]
          }
        }
      ],
      "source": [
        "true && false"
      ],
      "id": "62"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "true"
            ]
          }
        }
      ],
      "source": [
        "true || false"
      ],
      "id": "64"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "false"
            ]
          }
        }
      ],
      "source": [
        "!true"
      ],
      "id": "66"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Comparisons can be chained together."
      ],
      "id": "27bac156-76ad-4a42-b199-f4df98c16896"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "true"
            ]
          }
        }
      ],
      "source": [
        "3 < 4 || 8 == 12"
      ],
      "id": "68"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We didn’t do this above, since Julia doesn’t require it, but it’s easier\n",
        "to understand these types of compound expressions if you use parentheses\n",
        "to signal the order of operations. This helps with debugging!"
      ],
      "id": "d311acb0-57dd-4e70-b6d9-a9315a87a9e4"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "true"
            ]
          }
        }
      ],
      "source": [
        "(3 < 4) || (8 == 12)"
      ],
      "id": "70"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Data Structures\n",
        "\n",
        "Data structures are containers which hold multiple values in a\n",
        "convenient fashion. Julia has several built-in data structures, and\n",
        "there are many extensions provided in additional packages.\n",
        "\n",
        "### Tuples\n",
        "\n",
        "Tuples are collections of values. Julia will pay attention to the types\n",
        "of these values, but they can be mixed. Tuples are also *immutable*:\n",
        "their values cannot be changed once they are defined.\n",
        "\n",
        "Tuples can be defined by just separating values with commas."
      ],
      "id": "8639bbab-8e8e-40c8-862d-c0bcfbbd4796"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "(4, 5, 6)"
            ]
          }
        }
      ],
      "source": [
        "test_tuple = 4, 5, 6"
      ],
      "id": "72"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To access a value, use square brackets and the desired index.\n",
        "\n",
        "> **Note**\n",
        ">\n",
        "> Julia indexing starts at 1, not 0!"
      ],
      "id": "90558687-5317-4a4f-a0c3-fa888778d5d7"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4"
            ]
          }
        }
      ],
      "source": [
        "test_tuple[1]"
      ],
      "id": "74"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "As mentioned above, tuples are immutable. What happens if we try to\n",
        "change the value of the first element of `test_tuple`?"
      ],
      "id": "7dc9257f-9183-4b5c-b7c9-b66848e0469c"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "test_tuple[1] = 5"
      ],
      "id": "76"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Tuples also do not have to hold the same types of values."
      ],
      "id": "f62ace51-8df3-45f1-ade9-25e0d3914fa4"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Tuple{Int64, Float64, Char}"
            ]
          }
        }
      ],
      "source": [
        "test_tuple_2 = 4, 5.0, 'h'\n",
        "typeof(test_tuple_2)"
      ],
      "id": "78"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Tuples can also be defined by enclosing the values in parentheses."
      ],
      "id": "1dc3978d-3309-468d-9a7a-e795267a3848"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Tuple{Int64, Float64, Char}"
            ]
          }
        }
      ],
      "source": [
        "test_tuple_3 = (4, 5.0, 'h')\n",
        "typeof(test_tuple_3)"
      ],
      "id": "80"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Arrays\n",
        "\n",
        "Arrays also hold multiple values, which can be accessed based on their\n",
        "index position. Arrays are commonly defined using square brackets."
      ],
      "id": "f19e98e3-2fe0-480c-99cf-f15e0a8945ea"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4"
            ]
          }
        }
      ],
      "source": [
        "test_array = [1, 4, 7, 8]\n",
        "test_array[2]"
      ],
      "id": "82"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Unlike tuples, arrays are mutable, and their contained values can be\n",
        "changed later."
      ],
      "id": "1cd989e6-3ae4-4730-ac92-5279b254bc0e"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4-element Vector{Int64}:\n",
              " 6\n",
              " 4\n",
              " 7\n",
              " 8"
            ]
          }
        }
      ],
      "source": [
        "test_array[1] = 6\n",
        "test_array"
      ],
      "id": "84"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Arrays also can hold multiple types. Unlike tuples, this causes the\n",
        "array to no longer care about types at all."
      ],
      "id": "b387598e-6bc2-4b67-9de0-16d2189c5bd0"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/html": [
              "<pre>Vector{Any}<span class=\"ansi-bright-black-fg\"> (alias for </span><span class=\"ansi-bright-black-fg\">Array{Any, 1}</span><span class=\"ansi-bright-black-fg\">)</span></pre>"
            ]
          }
        }
      ],
      "source": [
        "test_array_2 = [6, 5.0, 'h']\n",
        "typeof(test_array_2)"
      ],
      "id": "86"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Compare this with `test_array`:"
      ],
      "id": "f93f804b-5cf8-4b7b-9878-e0d47f805241"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/html": [
              "<pre>Vector{Int64}<span class=\"ansi-bright-black-fg\"> (alias for </span><span class=\"ansi-bright-black-fg\">Array{Int64, 1}</span><span class=\"ansi-bright-black-fg\">)</span></pre>"
            ]
          }
        }
      ],
      "source": [
        "typeof(test_array)"
      ],
      "id": "88"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Dictionaries\n",
        "\n",
        "Instead of using integer indices based on position, dictionaries are\n",
        "indexed by keys. They are specified by passing key-value pairs to the\n",
        "`Dict()` method."
      ],
      "id": "ff745a11-2a70-471c-8334-291dce718f74"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "2"
            ]
          }
        }
      ],
      "source": [
        "test_dict = Dict(\"A\"=>1, \"B\"=>2)\n",
        "test_dict[\"B\"]"
      ],
      "id": "90"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Comprehensions\n",
        "\n",
        "Creating a data structure with more than a handful of elements can be\n",
        "tedious to do by hand. If your desired array follows a certain pattern,\n",
        "you can create structures using a *comprehension*. Comprehensions\n",
        "iterate over some other data structure (such as an array) implicitly and\n",
        "populate the new data structure based on the specified instructions."
      ],
      "id": "1791d1a5-9486-43c3-83b3-ca37ed96080f"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "6-element Vector{Int64}:\n",
              "  0\n",
              "  1\n",
              "  4\n",
              "  9\n",
              " 16\n",
              " 25"
            ]
          }
        }
      ],
      "source": [
        "[i^2 for i in 0:1:5]"
      ],
      "id": "92"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "For dictionaries, make sure that you also specify the keys."
      ],
      "id": "03068195-ac28-45bd-9f5f-13cdb6c122b5"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Dict{String, Int64} with 6 entries:\n",
              "  \"4\" => 16\n",
              "  \"1\" => 1\n",
              "  \"5\" => 25\n",
              "  \"0\" => 0\n",
              "  \"2\" => 4\n",
              "  \"3\" => 9"
            ]
          }
        }
      ],
      "source": [
        "Dict(string(i) => i^2 for i in 0:1:5)"
      ],
      "id": "94"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Comprehensions are powerful and are extremely useful for simple tasks.\n",
        "However, they can be difficult to read if they are too long or\n",
        "complicated, such as if multiple commands are required to populate the\n",
        "data structure. In these cases, it is better to use a loop. **Always\n",
        "prioritize readability over conciseness.**\n",
        "\n",
        "### `map`, `filter`, and `reduce`\n",
        "\n",
        "Comprehensions are the most idiomatic way to transform collections in\n",
        "Julia, but you can also use `map`, `filter`, and `reduce` — these work\n",
        "similarly to Python:"
      ],
      "id": "f87fd1aa-0beb-4729-90a5-4d617437d631"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "5-element Vector{Int64}:\n",
              "  1\n",
              "  4\n",
              "  9\n",
              " 16\n",
              " 25"
            ]
          }
        }
      ],
      "source": [
        "map(x -> x^2, 1:5)        # like Python's map(lambda x: x**2, range(1, 6))"
      ],
      "id": "96"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "2-element Vector{Int64}:\n",
              " 4\n",
              " 5"
            ]
          }
        }
      ],
      "source": [
        "filter(x -> x > 3, 1:5)   # keep elements where the condition is true"
      ],
      "id": "98"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "15"
            ]
          }
        }
      ],
      "source": [
        "reduce(+, 1:5)            # sum all elements — equivalent to sum(1:5)"
      ],
      "id": "100"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "A useful pattern: `mapreduce` combines the two in one pass:"
      ],
      "id": "7d3d2859-6fbc-4ceb-b397-a468908801b8"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "55"
            ]
          }
        }
      ],
      "source": [
        "mapreduce(x -> x^2, +, 1:5)  # sum of squares, without creating an intermediate array"
      ],
      "id": "102"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Functions\n",
        "\n",
        "A function is an object which accepts a tuple of arguments and maps them\n",
        "to a return value. In Julia, functions are defined using the following\n",
        "syntax."
      ],
      "id": "e250adfb-9fca-4e41-a950-e3922df65364"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "8"
            ]
          }
        }
      ],
      "source": [
        "function my_actual_function(x, y)\n",
        "    return x + y\n",
        "end\n",
        "my_actual_function(3, 5)"
      ],
      "id": "104"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Functions in Julia do not require explicit use of a `return` statement.\n",
        "They will return the last expression evaluated in their definition.\n",
        "However, it’s good style to explicitly `return` function outputs. This\n",
        "improves readability and debugging, especially when functions can return\n",
        "multiple expressions based on logical control flows (if-then-else\n",
        "blocks).\n",
        "\n",
        "Functions in Julia are objects, and can be treated like other objects.\n",
        "They can be assigned to new variables or passed as arguments to other\n",
        "functions."
      ],
      "id": "7a96ec10-063f-4ceb-8ef4-151b8d73f8f3"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "8"
            ]
          }
        }
      ],
      "source": [
        "g = my_actual_function\n",
        "g(3, 5)"
      ],
      "id": "106"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "8"
            ]
          }
        }
      ],
      "source": [
        "function function_of_functions(f, x, y)\n",
        "    return f(x, y)\n",
        "end\n",
        "function_of_functions(g, 3, 5)"
      ],
      "id": "108"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "> **Tip**\n",
        ">\n",
        "> Try to structure your code using functions as often as possible to\n",
        "> reduce bugs and simplify debugging! If you find yourself copying and\n",
        "> pasting code, or writing the same code multiple times, consider\n",
        "> writing a function instead. If there’s a mistake in that code, you\n",
        "> only have to find it and fix it once.\n",
        ">\n",
        "> This will also make your code more readable and easier to follow, as\n",
        "> function names should be chosen to make it clear what that chunk of\n",
        "> code is doing.\n",
        "\n",
        "### Short and Anonymous Functions\n",
        "\n",
        "In addition to the long form of the function definition shown above,\n",
        "simple functions can be specified in more compact forms when helpful.\n",
        "\n",
        "This is the short form:"
      ],
      "id": "acd8308d-34b6-4cf3-9fb6-65145481b9dc"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "16"
            ]
          }
        }
      ],
      "source": [
        "h₁(x) = x^2 # make the subscript using \\_1 + <TAB>\n",
        "h₁(4)"
      ],
      "id": "110"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "This is the anonymous form:"
      ],
      "id": "927bb34b-33ee-4470-ac92-59cd691988b7"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "0.7071067811865475"
            ]
          }
        }
      ],
      "source": [
        "x -> sin(x)\n",
        "(x -> sin(x))(π/4)"
      ],
      "id": "112"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Mutating Functions\n",
        "\n",
        "The convention in Julia is that functions should not modify (or\n",
        "*mutate*) their input data. The reason for this is to ensure that the\n",
        "data are preserved. Mutating functions are mainly appropriate for\n",
        "applications where performance needs to be optimized, and making a copy\n",
        "of the input data would be too memory-intensive.\n",
        "\n",
        "If you do write a mutating function in Julia, the convention is to add a\n",
        "`!` to its name, like `my_mutating_function!(x)`. This makes it clear to\n",
        "anyone reading or using your code that they should expect `x` to be\n",
        "modified, rather than the output of `my_mutating_function` to be stored\n",
        "in a different variable.\n",
        "\n",
        "### Optional Arguments\n",
        "\n",
        "There are two extremes with regard to function parameters which do not\n",
        "always need to be changed.\n",
        "\n",
        "1.  The first is to hard-code them into the function body, which has a\n",
        "    clear downside: when you do want to change them, the function needs\n",
        "    to be edited directly.\n",
        "2.  The second is to treat them as regular arguments, passing them every\n",
        "    time the function is called. This has the downside of potentially\n",
        "    creating bloated function calls, particularly when there is a\n",
        "    standard default value that makes sense for most function\n",
        "    evaluations.\n",
        "\n",
        "Most modern languages, including Julia, allow an alternate solution,\n",
        "which is to make these arguments *optional*. This involves setting a\n",
        "default value, which is used unless the argument is explicitly defined\n",
        "in a function call."
      ],
      "id": "6767aff4-b7a0-4bde-ae53-bc5a561d1c5e"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "setting_optional_arguments (generic function with 2 methods)"
            ]
          }
        }
      ],
      "source": [
        "function setting_optional_arguments(x, y, c=0.5)\n",
        "    return c * (x + y)\n",
        "end"
      ],
      "id": "114"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "If we want to stick with the fixed value $c=0.5$, all we have to do is\n",
        "call `setting_optional_arguments` with the `x` and `y` arguments."
      ],
      "id": "8cb3eb21-f4ea-4fac-8098-3f7bd2e17caa"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4.0"
            ]
          }
        }
      ],
      "source": [
        "setting_optional_arguments(3, 5)"
      ],
      "id": "116"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Otherwise, we can pass a new value for `c`."
      ],
      "id": "fb92747a-f3f5-46d8-8f3a-69cf1dbea0e5"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "16"
            ]
          }
        }
      ],
      "source": [
        "setting_optional_arguments(3, 5, 2)"
      ],
      "id": "118"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Passing Data Structures as Arguments\n",
        "\n",
        "Instead of passing variables individually, it may make sense to pass a\n",
        "data structure, such as an array or a tuple, and then unpacking within\n",
        "the function definition. This is straightforward in long form: access\n",
        "the appropriate elements using their index.\n",
        "\n",
        "In short or anonymous form, there is a trick which allows the use of\n",
        "readable variables within the function definition."
      ],
      "id": "c7906b79-94d4-466d-aaf8-d21608b75f91"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "h₂ (generic function with 1 method)"
            ]
          }
        }
      ],
      "source": [
        "h₂((x,y)) = x*y # enclose the input arguments in parentheses to tell Julia to expect and unpack a tuple"
      ],
      "id": "120"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "6"
            ]
          }
        }
      ],
      "source": [
        "h₂((2, 3)) # this works perfectly, as we passed in a tuple"
      ],
      "id": "122"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "h₂(2, 3) # this gives an error, as h₂ expects a single tuple, not two different numeric values"
      ],
      "id": "124"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "30"
            ]
          }
        }
      ],
      "source": [
        "h₂([3, 10]) # this also works with arrays instead of tuples"
      ],
      "id": "126"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Dot Syntax (Broadcasting)\n",
        "\n",
        "Julia uses **dot syntax** to **broadcast** an operation element-wise\n",
        "across an array. This is the Julia equivalent of NumPy’s automatic\n",
        "vectorization, but it’s **explicit** — you add a `.` wherever you want\n",
        "element-wise behavior.\n",
        "\n",
        "> **Why Broadcasting?**\n",
        ">\n",
        "> Julia requires you to *opt-in* to broadcasting because it allows you\n",
        "> to write code that is clear and has expected behavior: if you do not\n",
        "> want a function to be used on a array, a function that lacks\n",
        "> broadcasting will throw an error, alerting a user that they are using\n",
        "> the function incorrectly. This is a common source of bugs in Python\n",
        "> (or R), where functions are often written to accept both scalars and\n",
        "> arrays, but the behavior is not always what the user expects. And with\n",
        "> multiple dispatch, if you want scalars and arrays to handled\n",
        "> differently, you can use two different methods for the same function\n",
        "> name, each with the appropriate logic.\n",
        "\n",
        "> **Python / NumPy vs. Julia**\n",
        ">\n",
        "> | Python (NumPy)                    | Julia          |\n",
        "> |-----------------------------------|----------------|\n",
        "> | `np.sqrt(arr)`                    | `sqrt.(arr)`   |\n",
        "> | `arr * 2`                         | `arr .* 2`     |\n",
        "> | `arr1 + arr2`                     | `arr1 .+ arr2` |\n",
        "> | `np.sin(arr)`                     | `sin.(arr)`    |\n",
        "> | `f(arr)` where f is your function | `f.(arr)`      |\n",
        ">\n",
        "> The dot fuses: `sin.(cos.(x))` is equivalent to a single loop and as\n",
        "> fast as writing it by hand.\n",
        "\n",
        "For example, to calculate the square root of 3:"
      ],
      "id": "02edf9c1-559c-45a9-abb6-c52ee1f72d28"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "1.7320508075688772"
            ]
          }
        }
      ],
      "source": [
        "sqrt(3)"
      ],
      "id": "128"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To calculate the square roots of every integer between 1 and 5:"
      ],
      "id": "1e106071-5fed-453b-8ce3-5fa6ea44b331"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "5-element Vector{Float64}:\n",
              " 1.0\n",
              " 1.4142135623730951\n",
              " 1.7320508075688772\n",
              " 2.0\n",
              " 2.23606797749979"
            ]
          }
        }
      ],
      "source": [
        "sqrt.([1, 2, 3, 4, 5])"
      ],
      "id": "130"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The same dot syntax works for arithmetic:"
      ],
      "id": "9e20fc97-9956-4cda-b0e8-5c45e4e008af"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4-element Vector{Int64}:\n",
              " 2\n",
              " 4\n",
              " 6\n",
              " 8"
            ]
          }
        }
      ],
      "source": [
        "[1, 2, 3, 4] .* 2"
      ],
      "id": "132"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "And for your own functions:"
      ],
      "id": "cfebeca1-290a-40e0-9595-ee55c87db162"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "3-element Vector{Int64}:\n",
              "  2\n",
              "  5\n",
              " 10"
            ]
          }
        }
      ],
      "source": [
        "f(x) = x^2 + 1\n",
        "f.([1, 2, 3])"
      ],
      "id": "134"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "You can also use the `@.` macro to broadcast an entire expression at\n",
        "once:"
      ],
      "id": "d23d3885-5772-4ce4-9f07-9643a5772607"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "3-element Vector{Float64}:\n",
              "  0.5143952585235492\n",
              " -0.4042391538522658\n",
              " -0.8360218615377305"
            ]
          }
        }
      ],
      "source": [
        "@. sin(cos([1, 2, 3]))  # equivalent to sin.(cos.([1, 2, 3]))"
      ],
      "id": "136"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Broadcasting is faster than writing a loop by hand (Julia fuses the\n",
        "operations) and is the standard way to write vectorized code. Use it\n",
        "liberally!\n",
        "\n",
        "### The Pipe Operator `|>`\n",
        "\n",
        "Julia has a **pipe operator** `|>` that chains function calls\n",
        "left-to-right:"
      ],
      "id": "9bfb780f-222b-4e85-80fb-168980285959"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "6"
            ]
          }
        }
      ],
      "source": [
        "[1, 2, 3, 4, 5] |> x -> filter(iseven, x) |> sum  # sum of even numbers"
      ],
      "id": "138"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "This is equivalent to `sum(filter(iseven, [1,2,3,4,5]))` but avoids\n",
        "deeply nested parentheses. Pipelines are especially common in\n",
        "data-analysis workflows with `DataFrames.jl`.\n",
        "\n",
        "### Returning Multiple Values\n",
        "\n",
        "You can return multiple values by separating them with a comma. This\n",
        "implicitly causes the function to return a tuple of values."
      ],
      "id": "6a28fba5-9c9d-494a-abd2-d8809988f8cf"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "(8, 15)"
            ]
          }
        }
      ],
      "source": [
        "function return_multiple_values(x, y)\n",
        "    return x + y, x * y\n",
        "end\n",
        "return_multiple_values(3, 5)"
      ],
      "id": "140"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "These values can be unpacked into multiple variables."
      ],
      "id": "e70fcd38-f924-4e0e-a842-48f39e1df797"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "8"
            ]
          }
        }
      ],
      "source": [
        "n, ν = return_multiple_values(3, 5)\n",
        "n"
      ],
      "id": "142"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "15"
            ]
          }
        }
      ],
      "source": [
        "ν"
      ],
      "id": "144"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Returning `nothing`\n",
        "\n",
        "Sometimes you don’t want a function to return any values at all. For\n",
        "example, you might want a function that only prints a string to the\n",
        "console."
      ],
      "id": "4664dadc-96e6-4e28-8f01-a04e24598814"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "x: 42"
          ]
        }
      ],
      "source": [
        "function print_some_string(x)\n",
        "    println(\"x: $x\")\n",
        "    return nothing\n",
        "end\n",
        "print_some_string(42)"
      ],
      "id": "146"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Printing Text Output\n",
        "\n",
        "The `Text()` function returns its argument as a plain text string.\n",
        "Notice how this is different from evaluating a string!"
      ],
      "id": "064170a4-4f7f-4fb2-8f5c-760758f7bb2e"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "I'm printing a string."
            ]
          }
        }
      ],
      "source": [
        "Text(\"I'm printing a string.\")"
      ],
      "id": "148"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "`Text()` is used in this tutorial as it *returns* the string passed to\n",
        "it. To print directly to the console, use `println()`."
      ],
      "id": "195be80c-1794-4d4b-a2fb-032967dc0791"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "I'm writing a string to the console."
          ]
        }
      ],
      "source": [
        "println(\"I'm writing a string to the console.\")"
      ],
      "id": "150"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Printing Variables In a String\n",
        "\n",
        "What if we want to include the value of a variable inside of a string?\n",
        "We do this using *string interpolation*, using `$variablename` inside of\n",
        "the string."
      ],
      "id": "3d376e07-9847-4f49-a80f-6753aae8314f"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "Now I'm printing a variable: 42"
            ]
          }
        }
      ],
      "source": [
        "bar = 42\n",
        "Text(\"Now I'm printing a variable: $bar\")"
      ],
      "id": "152"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Control Flows\n",
        "\n",
        "One of the tricky things about learning a new programming language can\n",
        "be getting used to the specifics of control flow syntax. These types of\n",
        "flows include conditional if-then-else statements or loops.\n",
        "\n",
        "### Conditional Blocks\n",
        "\n",
        "Conditional blocks allow different pieces of code to be evaluated\n",
        "depending on the value of a boolean expression or variable. For example,\n",
        "if we wanted to compute the absolute value of a number, rather than\n",
        "using `abs()`:"
      ],
      "id": "8f1b7a5a-2e98-4d60-bc92-3fd4d3c1811e"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "our_abs (generic function with 1 method)"
            ]
          }
        }
      ],
      "source": [
        "function our_abs(x)\n",
        "    if x >= 0\n",
        "        return x\n",
        "    else\n",
        "        return -x\n",
        "    end\n",
        "end"
      ],
      "id": "154"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4"
            ]
          }
        }
      ],
      "source": [
        "our_abs(4)"
      ],
      "id": "156"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "4"
            ]
          }
        }
      ],
      "source": [
        "our_abs(-4)"
      ],
      "id": "158"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To nest conditional statements, use `elseif`."
      ],
      "id": "c2f44ed4-4710-4150-8dcb-5f64d8cc9cbd"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "test_sign (generic function with 1 method)"
            ]
          }
        }
      ],
      "source": [
        "function test_sign(x)\n",
        "    if x > 0\n",
        "        return Text(\"x is positive.\")\n",
        "    elseif x < 0\n",
        "        return Text(\"x is negative.\")\n",
        "    else\n",
        "        return Text(\"x is zero.\")\n",
        "    end\n",
        "end"
      ],
      "id": "160"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "x is negative."
            ]
          }
        }
      ],
      "source": [
        "test_sign(-5)"
      ],
      "id": "162"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "x is zero."
            ]
          }
        }
      ],
      "source": [
        "test_sign(0)"
      ],
      "id": "164"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Loops\n",
        "\n",
        "Loops allow expressions to be evaluated repeatedly until they are\n",
        "terminated. The two main types of loops are `while` loops and `for`\n",
        "loops.\n",
        "\n",
        "#### While loops\n",
        "\n",
        "`while` loops continue to evaluate an expression so long as a specified\n",
        "boolean condition is `true`. This is useful when you don’t know how many\n",
        "iterations it will take for the desired goal to be reached."
      ],
      "id": "6912d471-a137-40a3-8bbb-27772e3d87a3"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "120"
            ]
          }
        }
      ],
      "source": [
        "function compute_factorial(x)\n",
        "    factorial = 1\n",
        "    while (x > 1)\n",
        "        factorial *= x\n",
        "        x -= 1\n",
        "    end\n",
        "    return factorial\n",
        "end\n",
        "compute_factorial(5)"
      ],
      "id": "166"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "> **Warning**\n",
        ">\n",
        "> While loops can easily turn into infinite loops if the condition is\n",
        "> never meaningfully updated. Be careful, and look there if your\n",
        "> programs are getting stuck. Also, if the expression in a `while` loop\n",
        "> is false when the loop is reached, the loop will never be evaluated.\n",
        "\n",
        "#### For loops\n",
        "\n",
        "`for` loops run for a finite number of iterations, based on some defined\n",
        "index variable."
      ],
      "id": "c5a0e387-6e59-4461-bfa9-60b935728c86"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "10"
            ]
          }
        }
      ],
      "source": [
        "function add_some_numbers(x)\n",
        "    total_sum = 0 # initialize at zero since we're adding\n",
        "    for i=1:x # the counter i is updated every iteration\n",
        "        total_sum += i\n",
        "    end\n",
        "    return total_sum\n",
        "end\n",
        "add_some_numbers(4)"
      ],
      "id": "168"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "`for` loops can also iterate over explicitly passed containers, rather\n",
        "than iterating over an incrementally-updated index sequence. Use the\n",
        "`in` keyword when defining the loop."
      ],
      "id": "4d35206e-fafd-4e70-8968-4fb451e2cbd6"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "9"
            ]
          }
        }
      ],
      "source": [
        "function add_passed_numbers(set)\n",
        "    total_sum = 0\n",
        "    for i in set # this is the syntax we use when we want i to correspond to different container values\n",
        "        total_sum += i\n",
        "    end\n",
        "    return total_sum\n",
        "end\n",
        "add_passed_numbers([1, 3, 5])"
      ],
      "id": "170"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Iteration Helpers: `enumerate`, `zip`, and `pairs`\n",
        "\n",
        "These functions work similarly to their Python counterparts:"
      ],
      "id": "796a2346-f13d-4609-aaf5-e68321a47c49"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Item 1 is a\n",
            "Item 2 is b\n",
            "Item 3 is c"
          ]
        }
      ],
      "source": [
        "# enumerate gives (index, value) pairs (but index starts at 1!)\n",
        "for (i, val) in enumerate([\"a\", \"b\", \"c\"])\n",
        "    println(\"Item $i is $val\")\n",
        "end"
      ],
      "id": "172"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1 + 4 = 5\n",
            "2 + 5 = 7\n",
            "3 + 6 = 9"
          ]
        }
      ],
      "source": [
        "# zip iterates over multiple collections in lockstep\n",
        "for (x, y) in zip([1, 2, 3], [4, 5, 6])\n",
        "    println(\"$x + $y = $(x + y)\")\n",
        "end"
      ],
      "id": "174"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "B: 2\n",
            "A: 1"
          ]
        }
      ],
      "source": [
        "# pairs iterates over (key, value) pairs for dictionaries\n",
        "d = Dict(\"A\"=>1, \"B\"=>2)\n",
        "for (k, v) in pairs(d)\n",
        "    println(\"$k: $v\")\n",
        "end"
      ],
      "id": "176"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Multiple Dispatch\n",
        "\n",
        "This is Julia’s most important concept. A **generic function** is a\n",
        "function that can have many **methods**, each specialized for different\n",
        "combinations of argument types."
      ],
      "id": "41633ca8-5fd9-41c0-83f9-65f2d85a5e9b"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "\"\\\"hello\\\" is a string\""
            ]
          }
        }
      ],
      "source": [
        "# A generic function with two methods\n",
        "function describe(x::Int)\n",
        "    return \"$x is an integer\"\n",
        "end\n",
        "\n",
        "function describe(x::String)\n",
        "    return \"\\\"$x\\\" is a string\"\n",
        "end\n",
        "\n",
        "describe(42)\n",
        "describe(\"hello\")"
      ],
      "id": "178"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Julia calls this **multiple dispatch** because the specific method is\n",
        "chosen based on the types of **all** arguments, not just the first one\n",
        "(unlike Python’s single dispatch in traditional OOP).\n",
        "\n",
        "> **Type Annotations**\n",
        ">\n",
        "> To take advantage of multiple dispatch (and to write fast code),\n",
        "> annotate arguments with their expected types, such as the `::Int` and\n",
        "> `::String` annotations above. If you omit them, the compiler does not\n",
        "> know what types to expect, and as a result will generate slower\n",
        "> generic code and will not know how to choose the appropriate method.\n",
        ">\n",
        "> You can also annotate return types, but this is less common.\n",
        "\n",
        "> **Note**\n",
        ">\n",
        "> In Python, you might write `obj.method()` where `obj`’s class\n",
        "> determines which `method` runs. In Julia, you write `method(obj)` and\n",
        "> the types of all arguments collectively determine the implementation.\n",
        "> This is why Julia doesn’t need classes — dispatch replaces\n",
        "> inheritance.\n",
        "\n",
        "## Linear Algebra\n",
        "\n",
        "Matrices are defined in Julia as 2d arrays. Unlike basic arrays,\n",
        "matrices need to contain the same data type so Julia knows what\n",
        "operations are allowed. When defining a matrix, use semicolons to\n",
        "separate rows. Row elements should not be separated by commas."
      ],
      "id": "939d82c9-e129-4877-86c0-509ef29a04dd"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "2×3 Matrix{Int64}:\n",
              " 1  2  3\n",
              " 4  5  6"
            ]
          }
        }
      ],
      "source": [
        "test_matrix = [1 2 3; 4 5 6]"
      ],
      "id": "180"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "You can also specify matrices using spaces and newlines."
      ],
      "id": "7dab6306-47b6-4570-bbbd-8ac4500c8713"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "2×3 Matrix{Int64}:\n",
              " 1  2  3\n",
              " 4  5  6"
            ]
          }
        }
      ],
      "source": [
        "test_matrix_2 = [1 2 3\n",
        "                 4 5 6]"
      ],
      "id": "182"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally, matrices can be created using comprehensions by separating the\n",
        "inputs by a comma."
      ],
      "id": "7f415295-5a22-45d4-bc5e-4aab3efe257f"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "5×5 Matrix{Int64}:\n",
              " 1   2   3   4   5\n",
              " 2   4   6   8  10\n",
              " 3   6   9  12  15\n",
              " 4   8  12  16  20\n",
              " 5  10  15  20  25"
            ]
          }
        }
      ],
      "source": [
        "[i*j for i in 1:1:5, j in 1:1:5]"
      ],
      "id": "184"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Vectors are treated as 1d matrices."
      ],
      "id": "929a73a4-e810-477c-8a99-45f7a81ce39e"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "1×3 Matrix{Int64}:\n",
              " 1  2  3"
            ]
          }
        }
      ],
      "source": [
        "test_row_vector = [1 2 3]"
      ],
      "id": "186"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "3-element Vector{Int64}:\n",
              " 1\n",
              " 2\n",
              " 3"
            ]
          }
        }
      ],
      "source": [
        "test_col_vector = [1; 2; 3]"
      ],
      "id": "188"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Many linear algebra operations on vectors and matrices can be loaded\n",
        "using the `LinearAlgebra` package.\n",
        "\n",
        "## Working with Files\n",
        "\n",
        "### Reading CSV files\n",
        "\n",
        "The `CSV.jl` package (which needs to be installed separately into an\n",
        "environment; I will include this by default for assignments where file\n",
        "I/O is needed) reads comma-separated files into a `DataFrame` (this will\n",
        "result in an error because we don’t have the actual file):"
      ],
      "id": "490d90dc-9a04-44b4-8af8-81cd8da77b72"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "WARNING: using DataFrames.describe in module Notebook conflicts with an existing identifier."
          ]
        },
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/html": [
              "</div>"
            ]
          }
        }
      ],
      "source": [
        "using CSV, DataFrames\n",
        "df = CSV.read(\"data/myfile.csv\", DataFrame)"
      ],
      "id": "190"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Writing Files\n",
        "\n",
        "``` julia\n",
        "# Write a DataFrame df back to CSV\n",
        "CSV.write(\"output.csv\", df)\n",
        "```\n",
        "\n",
        "### Plain Text I/O\n",
        "\n",
        "For plain text files, use `read()` (or more often, `readlines()`) and\n",
        "`write()`:"
      ],
      "id": "a7d75bdf-8029-4d0a-b56a-8611ed76e0d6"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "\"Experimental run: Trial A\\nDate: 2024-01-15\\nTemperature: 22.5 C\\nPressure: 101.3 kPa\\nConcentration: 0.45 mol/L\\n\""
            ]
          }
        }
      ],
      "source": [
        "text = read(\"data/myfile.txt\", String)           # read entire file as a string"
      ],
      "id": "192"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "5-element Vector{String}:\n",
              " \"Experimental run: Trial A\"\n",
              " \"Date: 2024-01-15\"\n",
              " \"Temperature: 22.5 C\"\n",
              " \"Pressure: 101.3 kPa\"\n",
              " \"Concentration: 0.45 mol/L\""
            ]
          }
        }
      ],
      "source": [
        "lines = readlines(\"data/myfile.txt\")             # read as array of lines"
      ],
      "id": "194"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "``` julia\n",
        "open(\"output.txt\", \"w\") do io\n",
        "    write(io, \"Hello, world!\")\n",
        "end\n",
        "```\n",
        "\n",
        "### `DelimitedFiles` (standard library)\n",
        "\n",
        "For simple numeric matrices, use `readdlm` from the standard library:"
      ],
      "id": "9553b16e-d753-4510-8d46-2578fe17caa8"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "3×3 Matrix{Float64}:\n",
              " 1.0  2.0  3.0\n",
              " 4.0  5.0  6.0\n",
              " 7.0  8.0  9.0"
            ]
          }
        }
      ],
      "source": [
        "using DelimitedFiles\n",
        "data = readdlm(\"data/data.txt\")   # reads into a numeric matrix"
      ],
      "id": "196"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "`readdlm()` will also work with other delimiters, such as tabs or\n",
        "spaces, by passing a second argument:\n",
        "\n",
        "``` julia\n",
        "data = readdlm(\"data/data.txt\", '\\t')   # reads into a numeric matrix with tab delimiter\n",
        "```\n",
        "\n",
        "## Error Handling\n",
        "\n",
        "Julia’s approach to error handling is for you to try a command, and if\n",
        "it fails, to catch the error and handle it gracefully. This is similar\n",
        "to Python.\n",
        "\n",
        "Use `try`/`catch` blocks to handle errors gracefully:"
      ],
      "id": "b8afcf7f-4a23-4d99-adb1-949d51eca1e5"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Caught a DomainError: sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x))."
          ]
        },
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "NaN"
            ]
          }
        }
      ],
      "source": [
        "result = try\n",
        "    sqrt(-1.0)\n",
        "catch e\n",
        "    println(\"Caught a \", typeof(e), \": \", e.msg)\n",
        "    NaN\n",
        "end\n",
        "result"
      ],
      "id": "198"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "For more specific handling, catch particular exception types (you can\n",
        "*e.g.* explain the error in plainer language than the default error\n",
        "message):"
      ],
      "id": "259e07aa-7866-41a3-8478-12461c8cf101"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Cannot take sqrt of a negative real: DomainError(-1.0, \"sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).\")"
          ]
        }
      ],
      "source": [
        "try\n",
        "    sqrt(-1.0)\n",
        "catch e\n",
        "    if e isa DomainError\n",
        "        println(\"Cannot take sqrt of a negative real: $e\")\n",
        "    else\n",
        "        rethrow()   # if it's another kind of error, let it propagate\n",
        "    end\n",
        "end"
      ],
      "id": "200"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Performance Tips\n",
        "\n",
        "Julia can match C speeds, but only if you avoid a few common pitfalls.\n",
        "Here are the most important ones:\n",
        "\n",
        "1.  **Put performance-critical code inside functions.** Code at global\n",
        "    scope is slower because the compiler can’t assume types are stable.\n",
        "\n",
        "2.  **Avoid changing variable types.** If `x` starts as an `Int` and\n",
        "    later becomes a `Float64`, the compiler must generate slower generic\n",
        "    code. This is called **type instability**.\n",
        "\n",
        "3.  **Pre-allocate arrays.** Instead of `push!`-ing one element at a\n",
        "    time, create an array of the right size with `zeros(n)` or\n",
        "    `Vector{Float64}(undef, n)` and fill it.\n",
        "\n",
        "You can check your code’s performance with `@time`:"
      ],
      "id": "8eebee48-9795-4921-8578-c61aaa70ec17"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "  0.000000 seconds"
          ]
        },
        {
          "output_type": "display_data",
          "metadata": {},
          "data": {
            "text/plain": [
              "5000000050000000"
            ]
          }
        }
      ],
      "source": [
        "function slow_sum(n)\n",
        "    s = 0\n",
        "    for i in 1:n\n",
        "        s += i\n",
        "    end\n",
        "    return s\n",
        "end\n",
        "@time slow_sum(10^8)"
      ],
      "id": "202"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The first run includes compilation time (Julia compiles functions the\n",
        "first time they’re called). Run it twice to see the steady-state speed.\n",
        "\n",
        "A lack of memory pre-allocation is a common source of slowdowns. Here is\n",
        "a concrete comparison. Both functions compute the squares of `1:n`, but\n",
        "the first grows the array incrementally while the second allocates ahead\n",
        "of time:"
      ],
      "id": "5f02aa62-4ade-499f-b32d-92b8ca76664d"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "  0.002391 seconds (24 allocations: 17.477 MiB)\n",
            "  0.010301 seconds (24 allocations: 17.477 MiB, 79.11% gc time)"
          ]
        }
      ],
      "source": [
        "function squares_push(n)\n",
        "    result = Float64[]           # empty array\n",
        "    for i in 1:n\n",
        "        push!(result, i^2)       # reallocates on every append\n",
        "    end\n",
        "    return result\n",
        "end\n",
        "\n",
        "function squares_prealloc(n)\n",
        "    result = Vector{Float64}(undef, n)  # allocate once\n",
        "    for i in 1:n\n",
        "        result[i] = i^2                  # write to existing slot\n",
        "    end\n",
        "    return result\n",
        "end\n",
        "\n",
        "@time squares_push(10^6);\n",
        "@time squares_push(10^6);      # second call: steady-state speed"
      ],
      "id": "204"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "  0.001239 seconds (3 allocations: 7.641 MiB)\n",
            "  0.001192 seconds (3 allocations: 7.641 MiB)"
          ]
        }
      ],
      "source": [
        "@time squares_prealloc(10^6);\n",
        "@time squares_prealloc(10^6);  # second call: steady-state speed"
      ],
      "id": "206"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The pre-allocated version is typically 3–10× faster and allocates far\n",
        "less memory. The difference grows with `n`.\n",
        "\n",
        "### `include()`: Running Script Files\n",
        "\n",
        "In Python, `import file` loads a module. In Julia, `include(\"file.jl\")`\n",
        "**runs** a source file as if you typed its contents into the REPL. No\n",
        "module or namespace is created — everything defined in the file becomes\n",
        "available in your current scope. This is the standard way to load helper\n",
        "code or split a large project across multiple files."
      ],
      "id": "2ddfa7fb-d304-410e-9430-2e886e6b7f20"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "include(\"myfunctions.jl\")  # evaluates the file contents in the current scope"
      ],
      "id": "208"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Package management\n",
        "\n",
        "Sometimes you might need functionality that does not exist in base\n",
        "Julia. Julia handles packages using the [`Pkg` package\n",
        "manager](https://docs.julialang.org/en/v1/stdlib/Pkg/). After finding a\n",
        "package which has the functions that you need, you have two options:\n",
        "\n",
        "1.  Use the package management prompt in the Julia REPL (the standard\n",
        "    Julia interface; what you get when you type `julia` in your\n",
        "    terminal). Enter this by typing `]` at the standard green Julia\n",
        "    prompt `julia>`. This will become a blue `pkg>`. You can then add\n",
        "    new packages using `add packagename`.\n",
        "2.  From the standard prompt, enter `import Pkg; Pkg.add(packagename)`.\n",
        "    The `packagename` package can then be used by adding\n",
        "    `using packagename` to the start of the script."
      ],
      "id": "b1ecbedf-6c51-4b0f-8b7f-4a99afca2874"
    }
  ],
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernel_info": {
      "name": "julia"
    },
    "kernelspec": {
      "name": "julia",
      "display_name": "Julia",
      "language": "julia"
    },
    "language_info": {
      "name": "julia",
      "codemirror_mode": "julia",
      "version": "1.11.5"
    }
  }
}