# notes

path: Environmental_and_shell_variables.md
sortable_path: Environmental_and_shell_variables.md
title: Environmental and shell variables
lead: ## Important!
checksum: 830b914250703b8e65ce54614dcc19f7a0617a12e8cda1afa3b4f3e30f49d2c3
created: 2025-10-01 13:30:58.62027315+00:00
modified: 2025-08-13 15:57:13.898635445+00:00
metadata: {"tags":["shell"]}
body:
  ## Important!

  To understand the difference between environmental and shell variables know
  that:

  - You can spawn child shells from the parent shell that is initiated when you
    first open the terminal. To do this just run `bash` or `zsh` .
  - This is a self-contained new instance of the shell. This means:
    - It **will have** access to environmental variables (since they belong to the
      parent / are global)
    - It **will not have** access to any shell variables that are defined in the
      parent.
  - **How do you get back to the ‘upper’ parent shell?** Type `exit` .
  - Note that:
    - Custom (user-created) shell variables **do not** pass down to spawned shell
      instances, nor do they pass up to the parent
    - Custom (user-created) environment variables do pass down to spawned shell
      instances but do not pass up to the parent. They are lost on `exit` .

  Q. What methods are there for keeping track of, preserving, and jumping between
  spawned instances? Is this even possible or do they die on `exit` .

  ## Questions, research

  1. If you create a variable manually I guess it won’t make it to any config
     file. How would you create a persistent var that is added to the `.bashrc`
     and thus which would be initialised on every session? Is this where the path
     comes in?
  1. What methods are there for keeping track of, preserving, and jumping between
     spawned instances? Is this even possible or do they die on `exit` ?

  ## What is the shell environment and what are environment variables?

  - Every time that you interact with the shell you do so within an
    **environment**. This is the context within which you are working and it
    determines your access to resources and the behaviour that is permitted.

  - The environment is an area that the shell builds every time that it starts a
    session. It contains variables that define system properties.

  - Every time a [shell session](Shell-sessions-e6dd743dec1d4fe3b1ee672c8f9731f6)
    spawns, a process takes place to gather and compile information that should be
    available to the shell process and its child processes. It obtains the data
    for these settings from a variety of different files and settings on the
    system.

  - The environment is represented by strings comprising key-value pairs. For
    example:

    ```bash
    KEY=value1:value2
    KEY="value with spaces":"another value with spaces"
    ```

    As the above shows, a key can have multiple related values. Each one is
    demarcated with a `:` . If the value is longer than a single word, quotation
    marks are used.

  - The keys are **variables**. They come in two types: **environmental
    variables** and **shell variables:**

    - Environmental variables are much more permanent and pertain to things like
      the user and his path (the overall session)
    - Shell variables are more changeable for instance the current working
      directory (the current program instance)

  Variables can be created via config files that run on the initialisation of the
  session or manually created via the command line in the current session

  ## What are shell variables useful for?

  Some deployment mechanisms rely on environmental variables to configure
  authentication information. This is useful because it does not require keeping
  these in files that may be seen by outside parties.

  More generally they are used for when you will need to read or alter the
  environment of your system.

  ## Viewing shell and environmental variables

  To view the settings of your current environment you can execute the `env`
  command which returns a list of the key-value pairs introduced above. Here are
  some of the more intelligible variables that are returned when I run this
  command:

  ```bash
  SHELL=/usr/bin/zsh
  DESKTOP_SESSION=plasma
  HOME=/home/thomas
  USER=thomas
  PWD=/home/thomas/repos/bash-scripting
  PATH=/home/thomas/.nvm/versions/node/v16.8.0/bin:/home/thomas/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/var/lib/snapd/snap/bin
  ```

  However if you want to target a specific viable you need to invoke `printenv`
  with the relevant key, for example:

  ```bash
  printenv SHELL
  # SHELL=/usr/bin/zsh
  ```

  Note that `env` and `printenv` do not show all the shell variables, only a
  selection. To view all the shell variables along with the environmental
  variables use `set` .

  ## Creating, exporting and deleting variable shell and environment variables

  - You set shell variables using the same syntax you would within a script file:

    ```bash
    TEST_SHELL_VAR="This is a test"
    set | grep TEST_SH
    TEST_SHELL_VAR='This is a test'

    # We can also print it with an echo, again exactly as we would with a shell script
    echo S{TEST_SHELL_VAR}
    ```

  - We can verify that it is not an environmental variable based on the fact that
    following does not return anything:

    ```bash
    printenv | grep TEST-SH
    ```

  - We can verify that this is a shell variable by spawning a new shell and
    calling it. Nothing will be returned from the child shell.

  - You can upgrade a shell variable to an environment variable with `export` :

    ```bash
    export TEST_SHELL_VAR
    # And confirm:
    printenv | grep TEST_SH
    TEST_SHELL_VAR='This is a test'
    ```

  - We can use the same syntax to create new environment variables from scratch:

    ```bash
    export NEW_ENV_VAR="A new var"
    ```

  ### Using config files to create variables

  You can also add variables to config files that run on login such as your user
  `.bashrc` / `.zshrc` . This is obviously best for when you want the variables to
  persist and be accessible within every
  [shell session](Shell-sessions-e6dd743dec1d4fe3b1ee672c8f9731f6).

  ## Important environmental and shell variables

  ### `PATH`

  A list of directories that the system will check when looking for commands. When
  a user types in a command, the system will check directories in this order for
  the executable.

  ```bash
  echo ${PATH}
  # /home/thomas/.nvm/versions/node/v16.8.0/bin:/home/thomas/.local/bin:/usr/local/sbin:/usr/local/bin:
  # /usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/var/lib/snapd/snap/bin
  ```

  For example, if you wish to use `npm` commands globally (in any directory) you
  will need to have the requisite Node executable in your path, which you can see
  above.

  ### `SHELL`

  This describes the shell that will be interpreting any commands you type in. In
  most cases, this will be bash by default, but other values can be set if you
  prefer other options.

  ```bash
  echo ${SHELL}
  # /usr/bin/zsh
  ```

  ### `USER`

  The current logged in user.

  ```bash
  echo ${USER}
  # thomas
  ```

  ### `PWD`

  The current working directory.

  ```bash
  echo ${PWD}
  # /home/thomas
  ```

raw_content:
  ---
  tags:
    - shell
  ---

  # Environmental and shell variables

  ## Important!

  To understand the difference between environmental and shell variables know
  that:

  - You can spawn child shells from the parent shell that is initiated when you
    first open the terminal. To do this just run `bash` or `zsh` .
  - This is a self-contained new instance of the shell. This means:
    - It **will have** access to environmental variables (since they belong to the
      parent / are global)
    - It **will not have** access to any shell variables that are defined in the
      parent.
  - **How do you get back to the ‘upper’ parent shell?** Type `exit` .
  - Note that:
    - Custom (user-created) shell variables **do not** pass down to spawned shell
      instances, nor do they pass up to the parent
    - Custom (user-created) environment variables do pass down to spawned shell
      instances but do not pass up to the parent. They are lost on `exit` .

  Q. What methods are there for keeping track of, preserving, and jumping between
  spawned instances? Is this even possible or do they die on `exit` .

  ## Questions, research

  1. If you create a variable manually I guess it won’t make it to any config
     file. How would you create a persistent var that is added to the `.bashrc`
     and thus which would be initialised on every session? Is this where the path
     comes in?
  1. What methods are there for keeping track of, preserving, and jumping between
     spawned instances? Is this even possible or do they die on `exit` ?

  ## What is the shell environment and what are environment variables?

  - Every time that you interact with the shell you do so within an
    **environment**. This is the context within which you are working and it
    determines your access to resources and the behaviour that is permitted.

  - The environment is an area that the shell builds every time that it starts a
    session. It contains variables that define system properties.

  - Every time a [shell session](Shell-sessions-e6dd743dec1d4fe3b1ee672c8f9731f6)
    spawns, a process takes place to gather and compile information that should be
    available to the shell process and its child processes. It obtains the data
    for these settings from a variety of different files and settings on the
    system.

  - The environment is represented by strings comprising key-value pairs. For
    example:

    ```bash
    KEY=value1:value2
    KEY="value with spaces":"another value with spaces"
    ```

    As the above shows, a key can have multiple related values. Each one is
    demarcated with a `:` . If the value is longer than a single word, quotation
    marks are used.

  - The keys are **variables**. They come in two types: **environmental
    variables** and **shell variables:**

    - Environmental variables are much more permanent and pertain to things like
      the user and his path (the overall session)
    - Shell variables are more changeable for instance the current working
      directory (the current program instance)

  Variables can be created via config files that run on the initialisation of the
  session or manually created via the command line in the current session

  ## What are shell variables useful for?

  Some deployment mechanisms rely on environmental variables to configure
  authentication information. This is useful because it does not require keeping
  these in files that may be seen by outside parties.

  More generally they are used for when you will need to read or alter the
  environment of your system.

  ## Viewing shell and environmental variables

  To view the settings of your current environment you can execute the `env`
  command which returns a list of the key-value pairs introduced above. Here are
  some of the more intelligible variables that are returned when I run this
  command:

  ```bash
  SHELL=/usr/bin/zsh
  DESKTOP_SESSION=plasma
  HOME=/home/thomas
  USER=thomas
  PWD=/home/thomas/repos/bash-scripting
  PATH=/home/thomas/.nvm/versions/node/v16.8.0/bin:/home/thomas/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/var/lib/snapd/snap/bin
  ```

  However if you want to target a specific viable you need to invoke `printenv`
  with the relevant key, for example:

  ```bash
  printenv SHELL
  # SHELL=/usr/bin/zsh
  ```

  Note that `env` and `printenv` do not show all the shell variables, only a
  selection. To view all the shell variables along with the environmental
  variables use `set` .

  ## Creating, exporting and deleting variable shell and environment variables

  - You set shell variables using the same syntax you would within a script file:

    ```bash
    TEST_SHELL_VAR="This is a test"
    set | grep TEST_SH
    TEST_SHELL_VAR='This is a test'

    # We can also print it with an echo, again exactly as we would with a shell script
    echo S{TEST_SHELL_VAR}
    ```

  - We can verify that it is not an environmental variable based on the fact that
    following does not return anything:

    ```bash
    printenv | grep TEST-SH
    ```

  - We can verify that this is a shell variable by spawning a new shell and
    calling it. Nothing will be returned from the child shell.

  - You can upgrade a shell variable to an environment variable with `export` :

    ```bash
    export TEST_SHELL_VAR
    # And confirm:
    printenv | grep TEST_SH
    TEST_SHELL_VAR='This is a test'
    ```

  - We can use the same syntax to create new environment variables from scratch:

    ```bash
    export NEW_ENV_VAR="A new var"
    ```

  ### Using config files to create variables

  You can also add variables to config files that run on login such as your user
  `.bashrc` / `.zshrc` . This is obviously best for when you want the variables to
  persist and be accessible within every
  [shell session](Shell-sessions-e6dd743dec1d4fe3b1ee672c8f9731f6).

  ## Important environmental and shell variables

  ### `PATH`

  A list of directories that the system will check when looking for commands. When
  a user types in a command, the system will check directories in this order for
  the executable.

  ```bash
  echo ${PATH}
  # /home/thomas/.nvm/versions/node/v16.8.0/bin:/home/thomas/.local/bin:/usr/local/sbin:/usr/local/bin:
  # /usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/var/lib/snapd/snap/bin
  ```

  For example, if you wish to use `npm` commands globally (in any directory) you
  will need to have the requisite Node executable in your path, which you can see
  above.

  ### `SHELL`

  This describes the shell that will be interpreting any commands you type in. In
  most cases, this will be bash by default, but other values can be set if you
  prefer other options.

  ```bash
  echo ${SHELL}
  # /usr/bin/zsh
  ```

  ### `USER`

  The current logged in user.

  ```bash
  echo ${USER}
  # thomas
  ```

  ### `PWD`

  The current working directory.

  ```bash
  echo ${PWD}
  # /home/thomas
  ```


path: Equivalent_equations.md
sortable_path: Equivalent_equations.md
title: Equivalent equations
checksum: af49b4abaf246866a8c6730aab08d07ed9c3fa439b6290eb469e80b2a9eb61be
created: 2025-10-01 13:30:58.635908932+00:00
modified: 2025-08-13 15:57:13.869952144+00:00
metadata: {"tags":["algebra"]}
lead:
  > Two equations are equivalent if they have the same
  > [solution](Algebra%20key%20terms.md#678811) set.

body:
  > Two equations are equivalent if they have the same
  > [solution](Algebra%20key%20terms.md#678811) set.

  We know from the distributive property of multiplication that the equation
  $a \cdot (b + c )$ is equivalent to $a \cdot b + a \cdot c$. If we assign values
  to the variables such that $b$ is $5$ and $c$ is $2$ we can demonstrate the
  equivalence that obtains in the case of the distributive property by showing
  that both $a \cdot (b + c )$ and $a \cdot b + a \cdot c$ have the same solution:

  $$ 2 \cdot (5 + 2) = 14 $$

  $$ 2 \cdot 5 + 2 \cdot 2 =14 $$

  When we substitute $a$ with $2$ (the solution) we arrive at a true statement
  (the assertion that arrangement of values results in $14$). Since both
  expressions have the same solution they are equivalent.

  ## Creating equivalent equations

  We can create equivalent equations by adding, subtracting, multiplying and
  dividing the _same quantity_ from both sides of the equation (i.e. either side
  of the $=$ symbol). Adding or subtracting the same quantity from both sides
  (either side of the $=$ ) of the equation results in an equivalent equation.

  ### Demonstration with addition

  $$
  x - 4 = 3
  $$

  The [solution](Algebra%20key%20terms.md#678811) to this equation is $7$

  $$
  x -
  4 (+4) = 3 (+ 4)
  $$

  Here we have added $4$ to each side of the equation. If $x = 7$ then:

  $$ 7 - 4 (+ 4) = 7 $$

  and:

  $$ 3 + 4 = 7 $$

  ### Demonstration with subtraction

  $$
  x + 4 = 9
  $$

  The [solution](Algebra%20key%20terms.md#678811) to this equation is $5$.

  $$
  x +
  4 (-4) = 9(-4)
  $$

  Here we have subtracted $4$ from each side of the equation. If $x = 5$ then:

  $$ 5 + 4 (-4) = 5 $$

  and

  $$ 9 - 4 = 5 $$

  ### Demonstration with multiplication

  $$x \cdot 2 = 10  $$ The [solution](Algebra%20key%20terms.md#678811) to this
  equation is $5$.

  $$
  (x \cdot 2) \cdot 3 = 10 \cdot 3 $$ Here we have multiplied each side of the
  equation by $3$. If $x =5$ then

  $$ (5 \cdot 2) \cdot 3 = 30$$
  $$ 10 \cdot 3 = 30$$

  ### Demonstration with division

  $$x \cdot 3 = 18  $$
  The [solution](Algebra%20key%20terms.md#678811) to this equation is $6$.
  $$\frac{x
  \cdot 3}{3} = \frac{18}{3}
  $$

  Here we have divided each side of the equation by $3$. If $x$ is 6, then

  $$
  \frac{6
  \cdot 3}{3} = 6
  $$

  $$\frac{18}{3} = 6 $$

raw_content:
  ---
  tags: [algebra]
  ---

  # Equivalent equations

  > Two equations are equivalent if they have the same
  > [solution](Algebra%20key%20terms.md#678811) set.

  We know from the distributive property of multiplication that the equation
  $a \cdot (b + c )$ is equivalent to $a \cdot b + a \cdot c$. If we assign values
  to the variables such that $b$ is $5$ and $c$ is $2$ we can demonstrate the
  equivalence that obtains in the case of the distributive property by showing
  that both $a \cdot (b + c )$ and $a \cdot b + a \cdot c$ have the same solution:

  $$ 2 \cdot (5 + 2) = 14 $$

  $$ 2 \cdot 5 + 2 \cdot 2 =14 $$

  When we substitute $a$ with $2$ (the solution) we arrive at a true statement
  (the assertion that arrangement of values results in $14$). Since both
  expressions have the same solution they are equivalent.

  ## Creating equivalent equations

  We can create equivalent equations by adding, subtracting, multiplying and
  dividing the _same quantity_ from both sides of the equation (i.e. either side
  of the $=$ symbol). Adding or subtracting the same quantity from both sides
  (either side of the $=$ ) of the equation results in an equivalent equation.

  ### Demonstration with addition

  $$
  x - 4 = 3
  $$

  The [solution](Algebra%20key%20terms.md#678811) to this equation is $7$

  $$
  x -
  4 (+4) = 3 (+ 4)
  $$

  Here we have added $4$ to each side of the equation. If $x = 7$ then:

  $$ 7 - 4 (+ 4) = 7 $$

  and:

  $$ 3 + 4 = 7 $$

  ### Demonstration with subtraction

  $$
  x + 4 = 9
  $$

  The [solution](Algebra%20key%20terms.md#678811) to this equation is $5$.

  $$
  x +
  4 (-4) = 9(-4)
  $$

  Here we have subtracted $4$ from each side of the equation. If $x = 5$ then:

  $$ 5 + 4 (-4) = 5 $$

  and

  $$ 9 - 4 = 5 $$

  ### Demonstration with multiplication

  $$x \cdot 2 = 10  $$ The [solution](Algebra%20key%20terms.md#678811) to this
  equation is $5$.

  $$
  (x \cdot 2) \cdot 3 = 10 \cdot 3 $$ Here we have multiplied each side of the
  equation by $3$. If $x =5$ then

  $$ (5 \cdot 2) \cdot 3 = 30$$
  $$ 10 \cdot 3 = 30$$

  ### Demonstration with division

  $$x \cdot 3 = 18  $$
  The [solution](Algebra%20key%20terms.md#678811) to this equation is $6$.
  $$\frac{x
  \cdot 3}{3} = \frac{18}{3}
  $$

  Here we have divided each side of the equation by $3$. If $x$ is 6, then

  $$
  \frac{6
  \cdot 3}{3} = 6
  $$

  $$\frac{18}{3} = 6 $$


path: Equivalent_fractions.md
sortable_path: Equivalent_fractions.md
title: Equivalent fractions
checksum: 477a9d84f0d6c0ba348e8797ad4b7a7463bb760f7b65b29bcf492abd41957ccc
created: 2025-10-01 13:30:58.656315503+00:00
modified: 2025-08-13 15:57:13.849305747+00:00
metadata: {"tags":["prealgebra","fractions"]}
lead:
  Two fractions are equivalent if they represent the same value. To begin with we
  can represent this visually:

body:
  Two fractions are equivalent if they represent the same value. To begin with we
  can represent this visually:

  ![equiv-fractions.png](equiv-fractions.png)

  _Each shaded area is taking up the same proportion of the whole._

  The same properties can be represented arithmetically by multiplying the
  numerator and denominator at each step by 2. Thus:

  $$
  \\frac{1 (\cdot 2)}{3 (\cdot 2)} = \frac{2}{6}
  $$

  Therefore the following rule obtains:

  > If you start with a fraction and multiply both its numerator and denominator
  > by the same value, the resulting fraction is equivalent to the original
  > fraction.

  $$
  \\frac{a}{b} = \frac{a \cdot x}{b \cdot x}
  $$

  This process works in reverse when we invert the operator and use division:

  $$
  \\frac{2 (/ 2)}{6 (/ 2)} = \frac{1}{3}
  $$

  Thus:

  > If you start with a fraction and divide both its numerator and denominator by
  > the same value, the resulting fraction is equivalent to the original fraction.

  $$
  \\frac{a}{b} = \frac{a / x}{b / x}
  $$

raw_content:
  ---
  tags:
    - prealgebra
    - fractions
  ---

  # Equivalent fractions

  Two fractions are equivalent if they represent the same value. To begin with we
  can represent this visually:

  ![equiv-fractions.png](equiv-fractions.png)

  _Each shaded area is taking up the same proportion of the whole._

  The same properties can be represented arithmetically by multiplying the
  numerator and denominator at each step by 2. Thus:

  $$
  \\frac{1 (\cdot 2)}{3 (\cdot 2)} = \frac{2}{6}
  $$

  Therefore the following rule obtains:

  > If you start with a fraction and multiply both its numerator and denominator
  > by the same value, the resulting fraction is equivalent to the original
  > fraction.

  $$
  \\frac{a}{b} = \frac{a \cdot x}{b \cdot x}
  $$

  This process works in reverse when we invert the operator and use division:

  $$
  \\frac{2 (/ 2)}{6 (/ 2)} = \frac{1}{3}
  $$

  Thus:

  > If you start with a fraction and divide both its numerator and denominator by
  > the same value, the resulting fraction is equivalent to the original fraction.

  $$
  \\frac{a}{b} = \frac{a / x}{b / x}
  $$


path: Error_handling_in_Python.md
sortable_path: Error_handling_in_Python.md
title: Error handling and exceptions in Python
lead: Errors can typically occur in the following scenarios:
checksum: 40e1c6225751211bac5aebfa58f20208c81965329365f67e0906dffdcd55d98c
created: 2025-10-01 13:30:58.659350841+00:00
modified: 2025-08-13 15:57:13.942900072+00:00
metadata: {"tags":["python"]}
body:
  Errors can typically occur in the following scenarios:

  - A file or resource that is referred to does not exist
  - A network connection fails
  - A module import error occurs

  In these scenarios you would want to allow for error handling, so that you can
  detect errors and respond appropriately.

  ## Difference between errors and exceptions.

  Python distinguises between errors and exceptions.

  - An error typically indicates a situration where something goes wrong before
    the execution of the program begins.
  - An exception arises during the execution of the program

  In contrast to a error, when an exception occurs, the program doesn't
  necessarily stop immediately. Instead, Python provides a way to handle the
  exception, allowing you to potentially recover from it, or at least, to handle
  it gracefully before the program stops.

  However if you do not implement exception handling, the program will stop
  immediately when an exception occurs, similar to a error.

  ## The Exception hierarchy

  Errors and Exceptions are objects in Python and there is no real syntactic
  distinction between the two since all errors and exceptions in herit from a base
  exception class.

  The root class is `BaseException` which all errors and exeptions extend as
  subclasses as demonstrated by this diagram:

  ![](/img/python-exception-hierarchy.png)

  ## Exception syntax

  ### Difference between `raise` and `except`:

  - `raise` is used to explicitly trigger an exception - it means that you are
    signalling that an exception condition _has_ occured in your program.
  - `except` is used in conjunction with `try`blocks to catch and handle
    exceptions. Here you are saying "I think this _might_ cause an exception, so
    let's be prepared to handle it".

  Exaple of `raise`:

  ```py
  x = -10
  if x < 0:
      raise ValueError("The value should not be negative!")
  ```

  Example of `except`:

  ```py
  try:
      result = 10 / 0
  except ZeroDivisionError:
      print("You can't divide by zero!")

  ```

  ### Scaffolding exception handling

  There is a general procedure for handling exceptions denoted by certain
  keywords:

  - `try`
    - The process you want to run
  - `except`
    - The errors that could occur. You can have multiple `except` clauses for
      different exceptions
  - `else`
    - Some code you want to run after each of the `except` clauses have run
    - It must be written after the `except` clauses
    - It runs **if and only if** no exceptions were raised
    - If `try` succeeds or an exception is thrown, `else` will not run
  - `finally`
    - What you want to run at the end of the `try, except, else` sequence

  ```py
  try
      run_calculation(7)
  except ZeroDivisionError:
      print('something')
  except IndexError:
      print('something')
  except FileNotFoundError:
      print('something')
  except Exception:
      print('something')
  else
      # Do something after the exception blocks
  finally
      # Do concluding action
  ```

  ## Custom exceptions

  You can create your own custom exceptions by creating a class that inherits from
  the `Exception` class.

  ```py
  class CustomError(Exception):
      pass

  try:
      raise CustomError("This is a custom exception!")
  except CustomError as e:
      print(f"Caught an exception: {e}")
  ```

raw_content:
  ---
  tags: [python]
  ---

  # Error handling and exceptions in Python

  Errors can typically occur in the following scenarios:

  - A file or resource that is referred to does not exist
  - A network connection fails
  - A module import error occurs

  In these scenarios you would want to allow for error handling, so that you can
  detect errors and respond appropriately.

  ## Difference between errors and exceptions.

  Python distinguises between errors and exceptions.

  - An error typically indicates a situration where something goes wrong before
    the execution of the program begins.
  - An exception arises during the execution of the program

  In contrast to a error, when an exception occurs, the program doesn't
  necessarily stop immediately. Instead, Python provides a way to handle the
  exception, allowing you to potentially recover from it, or at least, to handle
  it gracefully before the program stops.

  However if you do not implement exception handling, the program will stop
  immediately when an exception occurs, similar to a error.

  ## The Exception hierarchy

  Errors and Exceptions are objects in Python and there is no real syntactic
  distinction between the two since all errors and exceptions in herit from a base
  exception class.

  The root class is `BaseException` which all errors and exeptions extend as
  subclasses as demonstrated by this diagram:

  ![](/img/python-exception-hierarchy.png)

  ## Exception syntax

  ### Difference between `raise` and `except`:

  - `raise` is used to explicitly trigger an exception - it means that you are
    signalling that an exception condition _has_ occured in your program.
  - `except` is used in conjunction with `try`blocks to catch and handle
    exceptions. Here you are saying "I think this _might_ cause an exception, so
    let's be prepared to handle it".

  Exaple of `raise`:

  ```py
  x = -10
  if x < 0:
      raise ValueError("The value should not be negative!")
  ```

  Example of `except`:

  ```py
  try:
      result = 10 / 0
  except ZeroDivisionError:
      print("You can't divide by zero!")

  ```

  ### Scaffolding exception handling

  There is a general procedure for handling exceptions denoted by certain
  keywords:

  - `try`
    - The process you want to run
  - `except`
    - The errors that could occur. You can have multiple `except` clauses for
      different exceptions
  - `else`
    - Some code you want to run after each of the `except` clauses have run
    - It must be written after the `except` clauses
    - It runs **if and only if** no exceptions were raised
    - If `try` succeeds or an exception is thrown, `else` will not run
  - `finally`
    - What you want to run at the end of the `try, except, else` sequence

  ```py
  try
      run_calculation(7)
  except ZeroDivisionError:
      print('something')
  except IndexError:
      print('something')
  except FileNotFoundError:
      print('something')
  except Exception:
      print('something')
  else
      # Do something after the exception blocks
  finally
      # Do concluding action
  ```

  ## Custom exceptions

  You can create your own custom exceptions by creating a class that inherits from
  the `Exception` class.

  ```py
  class CustomError(Exception):
      pass

  try:
      raise CustomError("This is a custom exception!")
  except CustomError as e:
      print(f"Caught an exception: {e}")
  ```


path: Event_loop.md
sortable_path: Event_loop.md
title: The Event Loop
checksum: ea6dc580b33452599f6c93bcba43fe41057e4d101b526cc0e9dc1c7a63e7e555
created: 2025-10-01 13:30:58.661906737+00:00
modified: 2025-08-13 15:57:13.787386718+00:00
metadata: {"tags":["node-js"]}
lead:
  Node.js provides a single-threaded asynchronous architecture which is achieved
  via means of the Event Loop.

body:
  Node.js provides a single-threaded asynchronous architecture which is achieved
  via means of the Event Loop.

  ## Multi-threaded synchronous architectures

  In the context of backend, a thread as an instance of a request-response
  transaction.

  For example a request is made from the client for a resource contained in a
  database. The back-end language is an intermediary between the client machine
  and the server. It receives the request and returns the resource as a response.

  Many backend frameworks are synchronous but multithreaded. This means that a
  thread can only process one request-response cycle at a time. The thread cannot
  initiate a new cycle until it has finished with its current cycle.

  If there was only one thread, this would be inefficient and unworkable.
  Therefore the framework will be multi-threaded: multiple request-response cycles
  can be executed at once by different threads.

  ![sync-thread.svg](sync-thread.svg)

  To accomodate the ability to increase the scale of synchronous applications you
  need to be able to spawn more threads commensurate to increased demand. This
  increases the resource consumption of the framework (more cores, more memory
  etc). Moreover it is possible to reach a point where all threads are active and
  no more can be spawned. In this case there will simply be delays in the return
  of data.

  ## Node as a single-threaded asynchronous architecture

  In contrast, Node only has a single thread but it works asynchronously, not
  synchronously. Thus it has a **single-threaded asynchronous architecture**. This
  means whilst there is only a single thread it can juggle responses by
  dispatching them asynchronously. When a request is made it sends it off and
  continues with its execution and handling new requests. Once these resolve, the
  data is returned to the main thread.

  ![async.svg](async.svg)

  ## The Event Loop

  Node implements its single-threaded asynchronous architecture through the Event
  Loop.

  This is the mechanism by which Node keeps track of incoming requests and their
  fulfillment status: whether the data has been returned successfully or if there
  has been error.

  Node is continually monitoring the Event Loop in the background.

  A running Node application is a single running process. Like everything that
  happens within the OS, a process is managed by the
  [kernel](The_kernel.md) that dispatches operations to the CPU
  in a clock cycle. A thread is a sequence of code that resides within the process
  and utilises its memory pool (the amount of memory assigned by the kernel to the
  Node process). The Event Loop runs on CPU ticks: a tick is a single run of the
  Event Loop.

  ### Phases of the Event Loop

  The Event Loop comprises six phases. The Event Loop starts at the moment Node
  begins to execute your `index.js` file or any other application entry point.
  These six phases create one cycle, or loop, equal to one **tick**. A Node.js
  process exits when there is no more pending work in the Event Loop, or when
  `process.exit()` is called manually. A program only runs for as long as there
  are tasks queued in the Event Loop, or present on the
  [call stack](Call_stack.md).

  ![](/img/node-event-loop.svg)

  The phases are as follows:

  1. **Timers**
     - These are functions that execute callbacks after a set period of time. As
       in standard JavaScript there are two global timer functions: `setTimeout`
       and `setInterval`. Interestingly these are not core parts of the JavaScript
       language, they are something that are made available to JS by the
       particular browser. As Node does not run in the browser, Node has to
       provide this functionality. It does so through the core `timers` module.
     - At the beginning of this phase the Event Loop updates its own time. Then it
       checks a queue, or pool, of timers. This queue consists of all timers that
       are currently set. The Event Loop takes the timer with the shortest wait
       time and compares it with the Event Loop's current time. If the wait time
       has elapsed, then the timer's callback is queued to be called once the
       [call stack](Call_stack.md) is empty.
  2. **I/O Callbacks**
     - Once timers have been checked and scheduled, Node jumps to I/O operations.
     - Node implements a non-blocking input/output interface. This is to say,
       writing and reading to disk (files in the Node application directory) is
       implemented asynchronously. The asynchronous I/O request is recorded into
       the queue and then the call stack continues.
  3. **Idle / waiting / preparation**
     - This phase is internal to Node and is not accessible to the programmer.
     - It is primarily used for gathering informtion, and planning what needs to
       be executed during the next tick of the Event Loop
  4. **I/O polling**
     - This is the phase at which the main block of code is read and executed by
       Node.
     - During this phase the Event Loop is managing the I/O workload, calling the
       functions in the queue until the queue is empty, and calculating how long
       it should wait until moving to the next phase. All callbacks in this phase
       are called synchronously (although they return asynchronously) in the order
       that they were added to the queue, from oldest to newest.
     - This is the phase that can potentially block our application if any of
       these callbacks are slow or do not return asynchronously.
  5. **`setImmediate` callbacks**
     - This phase runs as soon as the poll phase becomes idle. If `setImmediate()`
       is scheduled within the I/O cycle it will always be executed before other
       timers regardless of how many timers are present.
     - This is your opportunity to grant precedence to certain threads within the
       Node process
  6. **Close events**
     - This phase occurs when the Event Loop is wrapping up one cycle and is ready
       to move to the next one.
     - It is an opportunity for clean-up and to guard against memory leaks.
     - This phase can be targetted via the `process.exit()` function or the close
       event of a web-socket.

  ## Event _loop_ and event _queue_

  The terms _event loop_ and _event queue_ are often used interchangeably in the
  literature but in fact they are distinct.

  The Event Loop is the Node runtime's method of execution, the queue is the stack
  of tasks that are lined up and executed by the loop. We can think of the queue
  as being the input and the loop as what acts on the input. The queue obviously
  emerges from the program we write but it is scheduled, organised and sequenced
  by the loop.

  https://blog.appsignal.com/2022/07/20/an-introduction-to-multithreading-in-nodejs.html
  https://school.geekwall.in/p/Bk2xFs1DV

raw_content:
  ---
  tags:
    - node-js
  ---

  # The Event Loop

  Node.js provides a single-threaded asynchronous architecture which is achieved
  via means of the Event Loop.

  ## Multi-threaded synchronous architectures

  In the context of backend, a thread as an instance of a request-response
  transaction.

  For example a request is made from the client for a resource contained in a
  database. The back-end language is an intermediary between the client machine
  and the server. It receives the request and returns the resource as a response.

  Many backend frameworks are synchronous but multithreaded. This means that a
  thread can only process one request-response cycle at a time. The thread cannot
  initiate a new cycle until it has finished with its current cycle.

  If there was only one thread, this would be inefficient and unworkable.
  Therefore the framework will be multi-threaded: multiple request-response cycles
  can be executed at once by different threads.

  ![sync-thread.svg](sync-thread.svg)

  To accomodate the ability to increase the scale of synchronous applications you
  need to be able to spawn more threads commensurate to increased demand. This
  increases the resource consumption of the framework (more cores, more memory
  etc). Moreover it is possible to reach a point where all threads are active and
  no more can be spawned. In this case there will simply be delays in the return
  of data.

  ## Node as a single-threaded asynchronous architecture

  In contrast, Node only has a single thread but it works asynchronously, not
  synchronously. Thus it has a **single-threaded asynchronous architecture**. This
  means whilst there is only a single thread it can juggle responses by
  dispatching them asynchronously. When a request is made it sends it off and
  continues with its execution and handling new requests. Once these resolve, the
  data is returned to the main thread.

  ![async.svg](async.svg)

  ## The Event Loop

  Node implements its single-threaded asynchronous architecture through the Event
  Loop.

  This is the mechanism by which Node keeps track of incoming requests and their
  fulfillment status: whether the data has been returned successfully or if there
  has been error.

  Node is continually monitoring the Event Loop in the background.

  A running Node application is a single running process. Like everything that
  happens within the OS, a process is managed by the
  [kernel](The_kernel.md) that dispatches operations to the CPU
  in a clock cycle. A thread is a sequence of code that resides within the process
  and utilises its memory pool (the amount of memory assigned by the kernel to the
  Node process). The Event Loop runs on CPU ticks: a tick is a single run of the
  Event Loop.

  ### Phases of the Event Loop

  The Event Loop comprises six phases. The Event Loop starts at the moment Node
  begins to execute your `index.js` file or any other application entry point.
  These six phases create one cycle, or loop, equal to one **tick**. A Node.js
  process exits when there is no more pending work in the Event Loop, or when
  `process.exit()` is called manually. A program only runs for as long as there
  are tasks queued in the Event Loop, or present on the
  [call stack](Call_stack.md).

  ![](/img/node-event-loop.svg)

  The phases are as follows:

  1. **Timers**
     - These are functions that execute callbacks after a set period of time. As
       in standard JavaScript there are two global timer functions: `setTimeout`
       and `setInterval`. Interestingly these are not core parts of the JavaScript
       language, they are something that are made available to JS by the
       particular browser. As Node does not run in the browser, Node has to
       provide this functionality. It does so through the core `timers` module.
     - At the beginning of this phase the Event Loop updates its own time. Then it
       checks a queue, or pool, of timers. This queue consists of all timers that
       are currently set. The Event Loop takes the timer with the shortest wait
       time and compares it with the Event Loop's current time. If the wait time
       has elapsed, then the timer's callback is queued to be called once the
       [call stack](Call_stack.md) is empty.
  2. **I/O Callbacks**
     - Once timers have been checked and scheduled, Node jumps to I/O operations.
     - Node implements a non-blocking input/output interface. This is to say,
       writing and reading to disk (files in the Node application directory) is
       implemented asynchronously. The asynchronous I/O request is recorded into
       the queue and then the call stack continues.
  3. **Idle / waiting / preparation**
     - This phase is internal to Node and is not accessible to the programmer.
     - It is primarily used for gathering informtion, and planning what needs to
       be executed during the next tick of the Event Loop
  4. **I/O polling**
     - This is the phase at which the main block of code is read and executed by
       Node.
     - During this phase the Event Loop is managing the I/O workload, calling the
       functions in the queue until the queue is empty, and calculating how long
       it should wait until moving to the next phase. All callbacks in this phase
       are called synchronously (although they return asynchronously) in the order
       that they were added to the queue, from oldest to newest.
     - This is the phase that can potentially block our application if any of
       these callbacks are slow or do not return asynchronously.
  5. **`setImmediate` callbacks**
     - This phase runs as soon as the poll phase becomes idle. If `setImmediate()`
       is scheduled within the I/O cycle it will always be executed before other
       timers regardless of how many timers are present.
     - This is your opportunity to grant precedence to certain threads within the
       Node process
  6. **Close events**
     - This phase occurs when the Event Loop is wrapping up one cycle and is ready
       to move to the next one.
     - It is an opportunity for clean-up and to guard against memory leaks.
     - This phase can be targetted via the `process.exit()` function or the close
       event of a web-socket.

  ## Event _loop_ and event _queue_

  The terms _event loop_ and _event queue_ are often used interchangeably in the
  literature but in fact they are distinct.

  The Event Loop is the Node runtime's method of execution, the queue is the stack
  of tasks that are lined up and executed by the loop. We can think of the queue
  as being the input and the loop as what acts on the input. The queue obviously
  emerges from the program we write but it is scheduled, organised and sequenced
  by the loop.

  https://blog.appsignal.com/2022/07/20/an-introduction-to-multithreading-in-nodejs.html
  https://school.geekwall.in/p/Bk2xFs1DV


path: Example_scenario_internet_data_transfer.md
sortable_path: Example_scenario_internet_data_transfer.md
title: Example scenario of data transfer accross the internet
lead: ![Path of a network request accross the internet](../img/network-request-layers.png)
checksum: 4d2c68558e76d803d975f06cae05c6a43a97ef0bde82be9a1c805c7206750565
created: 2025-10-01 13:30:58.671717309+00:00
modified: 2025-08-13 15:57:13.972252855+00:00
metadata: {"created":"Friday, September 06, 2024","tags":["networks","internet"]}
body:
  ![Path of a network request accross the internet](../img/network-request-layers.png)

  ![All layers of the IP](../img/all-layers-of-IP.png)

  - A client device is connected to a wireless WiFi network.
  - This network is connected to the internet via a router.
  - Elsewhere on the internet is a server also connected to the internet via
    router.
  - A user of the client device opens a web browser and requests a web page hosted
    on the server (we will assume it already knows the IP address)
  - The web browser knows HTTP - a protocol of the
    [Application Layer](Application_Layer_of_Internet_Protocol.md) - and forms an
    HTTP request intended for the destination server.
  - The browser hands off the HTTP request to the TCP/IP software stack of the
    device's [operating system](Basic_model_of_the_operating_system.md), asking
    that the data be delivered to the server on its IP address and at port 80.
  - The TCP/IP software stack on the client OS encapsulates the HTTP payload in a
    TCP segment (at the
    [Transport Layer](Transport_Layer_of_Internet_Protocol.md)), setting the
    destination port to 80 in the segment header.
  - The [Internet Layer](Internet_Layer_of_Internet_Protocol.md) software on the
    client then wraps the TCP segment in an IP packet, which includes the
    destination IP address of the server in the packet header.
  - At the [Link Layer](Link_Layer_of_Internet_Protocol.md) of the client device,
    the IP packet is encapsulated in a frame with the
    [MAC_address](MAC_addresses.md) of the local router in its header. The frame
    is wirelessly transmitted by the client device's WiFi hardware.
  - The wireless access point receives the frame and passes to the router. The
    router examines the internet layer packet to determine the destination IP
    address.
  - To reach the server, the request needs to travel through multiple routers on
    the internet and the process is replicated (looking up MAC address of the
    device and determining IP target). Eventually, the request reaches the router
    on the network where the server is connected.
  - The last router puts the packet in a frame suitable for the server's local
    network and in the frame's header is the MAC address of the server.
  - The server receives the frame and the packet is passed to the TCP/IP software
    stack. This passes the HTTP data to the process listening on TCP port 80.
  - Web server software, listening on port 80 handles the request and replies to
    the client, start ing the process again in reverse order.

raw_content:
  ---
  tags: [networks, internet]
  created: Friday, September 06, 2024
  ---

  # Example scenario of data transfer accross the internet

  ![Path of a network request accross the internet](../img/network-request-layers.png)

  ![All layers of the IP](../img/all-layers-of-IP.png)

  - A client device is connected to a wireless WiFi network.
  - This network is connected to the internet via a router.
  - Elsewhere on the internet is a server also connected to the internet via
    router.
  - A user of the client device opens a web browser and requests a web page hosted
    on the server (we will assume it already knows the IP address)
  - The web browser knows HTTP - a protocol of the
    [Application Layer](Application_Layer_of_Internet_Protocol.md) - and forms an
    HTTP request intended for the destination server.
  - The browser hands off the HTTP request to the TCP/IP software stack of the
    device's [operating system](Basic_model_of_the_operating_system.md), asking
    that the data be delivered to the server on its IP address and at port 80.
  - The TCP/IP software stack on the client OS encapsulates the HTTP payload in a
    TCP segment (at the
    [Transport Layer](Transport_Layer_of_Internet_Protocol.md)), setting the
    destination port to 80 in the segment header.
  - The [Internet Layer](Internet_Layer_of_Internet_Protocol.md) software on the
    client then wraps the TCP segment in an IP packet, which includes the
    destination IP address of the server in the packet header.
  - At the [Link Layer](Link_Layer_of_Internet_Protocol.md) of the client device,
    the IP packet is encapsulated in a frame with the
    [MAC_address](MAC_addresses.md) of the local router in its header. The frame
    is wirelessly transmitted by the client device's WiFi hardware.
  - The wireless access point receives the frame and passes to the router. The
    router examines the internet layer packet to determine the destination IP
    address.
  - To reach the server, the request needs to travel through multiple routers on
    the internet and the process is replicated (looking up MAC address of the
    device and determining IP target). Eventually, the request reaches the router
    on the network where the server is connected.
  - The last router puts the packet in a frame suitable for the server's local
    network and in the frame's header is the MAC address of the server.
  - The server receives the frame and the packet is passed to the TCP/IP software
    stack. This passes the HTTP data to the process listening on TCP port 80.
  - Web server software, listening on port 80 handles the request and replies to
    the client, start ing the process again in reverse order.


path: Expansions_and_substitutions.md
sortable_path: Expansions_and_substitutions.md
title: Expansions and substitutions
checksum: 41c5576a75897c60f757ffd28740cf9fd778651ab006da1d75cdedebcea945b0
created: 2025-10-01 13:30:58.69804896+00:00
modified: 2025-08-13 15:57:13.959351715+00:00
metadata: {"tags":["shell"]}
lead:
  Bash is weird in that parentheses, braces and brackets are used not just as
  markers for different code blocks but as the designators of commands in their
  own right. The type of bracket you use effects how your input is interpreted.

body:
  Bash is weird in that parentheses, braces and brackets are used not just as
  markers for different code blocks but as the designators of commands in their
  own right. The type of bracket you use effects how your input is interpreted.

  Below are the main forms of expansion and substitution:

  | Representation | Name                 |
  | -------------- | -------------------- |
  | `~` .          | Tilde expansion      |
  | `{..}`         | Brace expansion      |
  | `${...}`       | Parameter expansion  |
  | `$(...)`       | Command substitution |
  | `$((...))`     | Arithmetic expansion |

  ## Brace expansion: `{..}`

  Brace expansion is for changing a smaller part of a greater whole
  programmatically. This is best understood by looking at examples:

  ```
  echo c{a,o,u}t
  cat cot cut
  ```

  ```
  echo /tmp/{1..3}/file.txt
  /tmp/1/file.txt  /tmp/2/file.txt /tmp/3/file.txt
  ```

  ```bash
  echo {1..5}
  1 2 3 4 5

  echo {5..1}
  5 4 3 2 1

  echo {a...c}
  a b c
  ```

  We can also set sequences. If we wanted to count to twenty in intervals of two

  ```bash
  echo {1..20..2}
  1 3 5 7 9 11 13 15 17 19
  ```

  > Note that we type _two_ dots **not** an elipsis

  ### Example use case

  We might use brace expansion to generate sequential file names using a
  pre-defined naming scheme, eg.

  ```
  touch file_{01..12}{a..d}
  ls

  file_01a
  file_01b
  file_01c
  file_01d
  file_02a
  file_02b
  ...
  file_12d
  ```

  The syntax here basically means: for each of the elements in the first list, run
  the second list against them.

  ## Parameter expansion: `${...}`

  We use most frequently for returning the value of stored
  [variables](Variables_and_data_types_in_Bash.md).
  Techically we do not have to use the braces, we can retrieve with just `$var`
  however it's better to use them to minimise interpretation fuck-ups which happen
  a lot.

  When the braces are used, this allows us to transform the values before they are
  returned such as only returning from the 6th character: `${var:6}`.

  ## Command substition: `$(...)`

  Command substitution (circle-brackets) allows us to put the output of one
  command inside another. Bash runs the bracketed command in a
  [sub-shell](Shell_sessions.md) and then returns it
  to the main user shell.

  For example:

  ```bash
  echo "The current directory is $(pwd)."
  ```

  ## Arithemtic expansion: `$((...))`

  We use arithmetic expansion when we want to calculate numerical values

  See
  [Working with numbers in Bash](Working_with_numbers_in_Bash.md)
  for more.

raw_content:
  ---
  tags:
    - shell
  ---

  # Expansions and substitutions

  Bash is weird in that parentheses, braces and brackets are used not just as
  markers for different code blocks but as the designators of commands in their
  own right. The type of bracket you use effects how your input is interpreted.

  Below are the main forms of expansion and substitution:

  | Representation | Name                 |
  | -------------- | -------------------- |
  | `~` .          | Tilde expansion      |
  | `{..}`         | Brace expansion      |
  | `${...}`       | Parameter expansion  |
  | `$(...)`       | Command substitution |
  | `$((...))`     | Arithmetic expansion |

  ## Brace expansion: `{..}`

  Brace expansion is for changing a smaller part of a greater whole
  programmatically. This is best understood by looking at examples:

  ```
  echo c{a,o,u}t
  cat cot cut
  ```

  ```
  echo /tmp/{1..3}/file.txt
  /tmp/1/file.txt  /tmp/2/file.txt /tmp/3/file.txt
  ```

  ```bash
  echo {1..5}
  1 2 3 4 5

  echo {5..1}
  5 4 3 2 1

  echo {a...c}
  a b c
  ```

  We can also set sequences. If we wanted to count to twenty in intervals of two

  ```bash
  echo {1..20..2}
  1 3 5 7 9 11 13 15 17 19
  ```

  > Note that we type _two_ dots **not** an elipsis

  ### Example use case

  We might use brace expansion to generate sequential file names using a
  pre-defined naming scheme, eg.

  ```
  touch file_{01..12}{a..d}
  ls

  file_01a
  file_01b
  file_01c
  file_01d
  file_02a
  file_02b
  ...
  file_12d
  ```

  The syntax here basically means: for each of the elements in the first list, run
  the second list against them.

  ## Parameter expansion: `${...}`

  We use most frequently for returning the value of stored
  [variables](Variables_and_data_types_in_Bash.md).
  Techically we do not have to use the braces, we can retrieve with just `$var`
  however it's better to use them to minimise interpretation fuck-ups which happen
  a lot.

  When the braces are used, this allows us to transform the values before they are
  returned such as only returning from the 6th character: `${var:6}`.

  ## Command substition: `$(...)`

  Command substitution (circle-brackets) allows us to put the output of one
  command inside another. Bash runs the bracketed command in a
  [sub-shell](Shell_sessions.md) and then returns it
  to the main user shell.

  For example:

  ```bash
  echo "The current directory is $(pwd)."
  ```

  ## Arithemtic expansion: `$((...))`

  We use arithmetic expansion when we want to calculate numerical values

  See
  [Working with numbers in Bash](Working_with_numbers_in_Bash.md)
  for more.


path: Exponents.md
sortable_path: Exponents.md
title: Equivalent equations
lead: > Two equations are equivalent if they have the same solution set.
checksum: ed92b47092aa66865ecdaf0245394d399969417fb096a7b3f4d8d5454887833e
created: 2025-10-01 13:30:58.71334978+00:00
modified: 2025-08-13 15:57:13.914663399+00:00
metadata: {"tags":["algebra","exponents"]}
body:
  > Two equations are equivalent if they have the same solution set.

  We know from the distributive property of multiplication that the equation
  $a \cdot (b + c )$ is equivalent to $a \cdot b + a \cdot c$. If we assign values
  to the variables such that $b$ is equal to $5$ and $c$ is equal to $2$ we can
  demonstrate the equivalence that obtains in the case of the distributive
  property by showing that both $a \cdot (b + c )$ and $a \cdot b + a \cdot c$
  have the same solution:

  $$ 2 \cdot (5 + 2) = 14 $$

  $$ 2 \cdot 5 + 2 \cdot 2 =14 $$

  When we substitute $a$ with $2$ (the solution) we arrive at a true statement
  (the assertion that arrangement of values results in $14$). Since both
  expressions have the same solution they are equivalent.

  ## Creating equivalent equations

  Adding or subtracting the same quantity from both sides (either side of the $=$
  ) of the equation results in an equivalent equation.

  ### Demonstration with addition

  $$ x - 4 = 3 \\ x -4 (+ 4) = 3 (+ 4) $$

  Here we have added $4$ to each side of the equation. If $x = 7$ then:

  $$ 7 - 4 (+ 4) = 7 $$

  and:

  $$ 3 + 4 = 7 $$

  ### Demonstration with subtraction

  $$ x + 4 = 9 \\ x + 4 (-4) = 9 (-4) $$

  Here we have subtracted $4$ from each side of the equation. If $x = 5$ then:

  $$ 5 + 4 (-4) = 5 $$

  and

  $$ 9 - 4 = 5 $$

raw_content:
  ---
  tags: [algebra, exponents]
  ---

  ## Equivalent equations

  > Two equations are equivalent if they have the same solution set.

  We know from the distributive property of multiplication that the equation
  $a \cdot (b + c )$ is equivalent to $a \cdot b + a \cdot c$. If we assign values
  to the variables such that $b$ is equal to $5$ and $c$ is equal to $2$ we can
  demonstrate the equivalence that obtains in the case of the distributive
  property by showing that both $a \cdot (b + c )$ and $a \cdot b + a \cdot c$
  have the same solution:

  $$ 2 \cdot (5 + 2) = 14 $$

  $$ 2 \cdot 5 + 2 \cdot 2 =14 $$

  When we substitute $a$ with $2$ (the solution) we arrive at a true statement
  (the assertion that arrangement of values results in $14$). Since both
  expressions have the same solution they are equivalent.

  ## Creating equivalent equations

  Adding or subtracting the same quantity from both sides (either side of the $=$
  ) of the equation results in an equivalent equation.

  ### Demonstration with addition

  $$ x - 4 = 3 \\ x -4 (+ 4) = 3 (+ 4) $$

  Here we have added $4$ to each side of the equation. If $x = 7$ then:

  $$ 7 - 4 (+ 4) = 7 $$

  and:

  $$ 3 + 4 = 7 $$

  ### Demonstration with subtraction

  $$ x + 4 = 9 \\ x + 4 (-4) = 9 (-4) $$

  Here we have subtracted $4$ from each side of the equation. If $x = 5$ then:

  $$ 5 + 4 (-4) = 5 $$

  and

  $$ 9 - 4 = 5 $$


path: Expressing_whole_numbers_as_fractions.md
sortable_path: Expressing_whole_numbers_as_fractions.md
title: Expressing whole numbers as fractions
checksum: a55088c0014a974ba74e3a3bb49fdc5673ee9803c6ae04ed20b0fe14798e6b9c
created: 2025-10-01 13:30:58.716385848+00:00
modified: 2025-08-13 15:57:14.022293079+00:00
metadata: {"tags":["prealgebra","fractions"]}
lead:
  Being asked to express a natural number as a fraction seems confusing at first
  but you need to just know that for any whole number $n$, you express it as a
  fraction with $\frac{n}{1}$.

body:
  Being asked to express a natural number as a fraction seems confusing at first
  but you need to just know that for any whole number $n$, you express it as a
  fraction with $\frac{n}{1}$.

  _Express 8 as an equivalent fraction having the denominator 5_

  $$
  8 = \frac{8}{1} = \frac{8 \cdot 5}{1 \cdot 5} = \frac{40}{5}
  $$

raw_content:
  ---
  tags:
    - prealgebra
    - fractions
  ---

  # Expressing whole numbers as fractions

  Being asked to express a natural number as a fraction seems confusing at first
  but you need to just know that for any whole number $n$, you express it as a
  fraction with $\frac{n}{1}$.

  _Express 8 as an equivalent fraction having the denominator 5_

  $$
  8 = \frac{8}{1} = \frac{8 \cdot 5}{1 \cdot 5} = \frac{40}{5}
  $$


path: Factors_and_divisors.md
sortable_path: Factors_and_divisors.md
title: Factors and divisors
checksum: 752a4b5783d7f939c4fc33b221f8f64aa918ecdcb429d5b0ac41e5d58b359e16
created: 2025-10-01 13:30:58.719290365+00:00
modified: 2025-08-13 15:57:13.860711347+00:00
metadata: {"tags":["prealgebra"]}
lead:
  The terms **factor** and **divisor** are used interchangeably. They are
  different ways of expressing the same mathematical truth and this is because of
  the inverse relationship between division and multiplication.

body:
  The terms **factor** and **divisor** are used interchangeably. They are
  different ways of expressing the same mathematical truth and this is because of
  the inverse relationship between division and multiplication.

  ### Divisors

  > For a number $n$, its divisor is any number that divides $n$ evenly without
  > remainder: $$ \frac{a}{b} = 0 $$

  In this operation, $a$ is the **divisor**, $b$ is the **dividend** and $0$ is
  the **quotient**.

  ### Factors

  > For a given number $n$, its factors are any pair of numbers that when
  > multiplied together return $n$ as the product: $$ a \cdot b = n $$

  We can see the relationship consists in the fact that factors are associated
  with multiplication and divisors are associated with division: two different
  perspectives on the same number relationships.

  For example, 6 is both a factor and divisor of 18 and 24. To be precise, it is
  the greatest common divisor of these two numbers.

  As a divisor:

  $$
  \\frac{18/6}{24/6} = \frac{3}{4}
  $$

  As a factor:

  $$
  \\frac{3 \cdot 6}{4 \cdot 6} = \frac{18}{24}
  $$

  When we divide by the common divisor is acts as a divisor. When we multiply by
  the common divisor it acts as a factor. The fact that the fractions are
  [equivalent](Equivalent%20fractions.md) in both cases indicates that the
  properties are equivalent.

  ## Greatest common divisor

  > For two two integers $a, b$, $D$ is a common divisor of $a$ and $b$ if it is a
  > divisor of both. The greatest common divisor is the largest value that $D$ can
  > be whilst remaining a divisor to both $a$ and $b$.

  ### Demonstration

  _Find the greatest common divisor of $18$ and $24$_

  The divisors of 18: $$1, 2, 3, 6, 9, 18$$

  The divisors of 24: $$ 1, 2, 3, 4, 6, 8, 12, 24$$

  Thus the common divisors are: $$ 1, 2, 3, 6 $$

  The largest value in the above set is 6, thus 6 is the greatest common divisor.

  ## Heuristics for finding divisors

  1. For dividend $n$ , if $n$ ends in an even number or zero, $n$ is **divisible
     by 2**.
     1. $\frac{12}{2} = 6$
     1. $\frac{84}{2} = 42$
  1. For dividend $n$ if the sum of the digits is divisible by 3 then $n$ is
     **divisible by 3**.
     1. $\frac{72}{3} = 24$
     1. $\frac{21}{3} = 7$
  1. For a dividend $n$ if the number represented of the last two digits of $n$
     divides by 4 then $n$ is divisible by 4
     1. $\frac{324}{4} = 81$
     1. $\frac{532}{4} = 133$
  1. For a dividend $n$, if the last digit of $n$ is divisible by 0 or 5, then $n$
     is divisible by 5.
     1. $\frac{25}{5} = 5$
  1. For a dividend $n$, if $n$ is divisible by 2 and 3, then $n$ is divisible
     by 6.
     1. $\frac{12}{6} = 2$
     1. $\frac{18}{6} = 3$
  1. For a dividend $n$, if the last three digits of $n$ are divisible by 8, then
     $n$ is divisible by 8.
     1. $\frac{73024}{8} = 9128$
  1. For a dividend $n$, if the sum of the digits of $n$ is divisible by 9 then
     $n$ is divisible by 9.
     1. $\frac{117}{9} = 13$

raw_content:
  ---
  tags:
    - prealgebra
  ---

  # Factors and divisors

  The terms **factor** and **divisor** are used interchangeably. They are
  different ways of expressing the same mathematical truth and this is because of
  the inverse relationship between division and multiplication.

  ### Divisors

  > For a number $n$, its divisor is any number that divides $n$ evenly without
  > remainder: $$ \frac{a}{b} = 0 $$

  In this operation, $a$ is the **divisor**, $b$ is the **dividend** and $0$ is
  the **quotient**.

  ### Factors

  > For a given number $n$, its factors are any pair of numbers that when
  > multiplied together return $n$ as the product: $$ a \cdot b = n $$

  We can see the relationship consists in the fact that factors are associated
  with multiplication and divisors are associated with division: two different
  perspectives on the same number relationships.

  For example, 6 is both a factor and divisor of 18 and 24. To be precise, it is
  the greatest common divisor of these two numbers.

  As a divisor:

  $$
  \\frac{18/6}{24/6} = \frac{3}{4}
  $$

  As a factor:

  $$
  \\frac{3 \cdot 6}{4 \cdot 6} = \frac{18}{24}
  $$

  When we divide by the common divisor is acts as a divisor. When we multiply by
  the common divisor it acts as a factor. The fact that the fractions are
  [equivalent](Equivalent%20fractions.md) in both cases indicates that the
  properties are equivalent.

  ## Greatest common divisor

  > For two two integers $a, b$, $D$ is a common divisor of $a$ and $b$ if it is a
  > divisor of both. The greatest common divisor is the largest value that $D$ can
  > be whilst remaining a divisor to both $a$ and $b$.

  ### Demonstration

  _Find the greatest common divisor of $18$ and $24$_

  The divisors of 18: $$1, 2, 3, 6, 9, 18$$

  The divisors of 24: $$ 1, 2, 3, 4, 6, 8, 12, 24$$

  Thus the common divisors are: $$ 1, 2, 3, 6 $$

  The largest value in the above set is 6, thus 6 is the greatest common divisor.

  ## Heuristics for finding divisors

  1. For dividend $n$ , if $n$ ends in an even number or zero, $n$ is **divisible
     by 2**.
     1. $\frac{12}{2} = 6$
     1. $\frac{84}{2} = 42$
  1. For dividend $n$ if the sum of the digits is divisible by 3 then $n$ is
     **divisible by 3**.
     1. $\frac{72}{3} = 24$
     1. $\frac{21}{3} = 7$
  1. For a dividend $n$ if the number represented of the last two digits of $n$
     divides by 4 then $n$ is divisible by 4
     1. $\frac{324}{4} = 81$
     1. $\frac{532}{4} = 133$
  1. For a dividend $n$, if the last digit of $n$ is divisible by 0 or 5, then $n$
     is divisible by 5.
     1. $\frac{25}{5} = 5$
  1. For a dividend $n$, if $n$ is divisible by 2 and 3, then $n$ is divisible
     by 6.
     1. $\frac{12}{6} = 2$
     1. $\frac{18}{6} = 3$
  1. For a dividend $n$, if the last three digits of $n$ are divisible by 8, then
     $n$ is divisible by 8.
     1. $\frac{73024}{8} = 9128$
  1. For a dividend $n$, if the sum of the digits of $n$ is divisible by 9 then
     $n$ is divisible by 9.
     1. $\frac{117}{9} = 13$


path: Factory_pattern.md
sortable_path: Factory_pattern.md
title: Factory pattern
checksum: b41d57d76bf4a6832fd6d794615930f6ec3b3087892d9cddb27e1f97f6d4a538
created: 2025-10-01 13:30:58.727293092+00:00
modified: 2025-08-13 15:57:14.053182616+00:00
metadata: {"tags":["OOP","design-patterns"]}
lead:
  The factory pattern pertains to how objects are created in the course of the
  code. It is class based.

body:
  The factory pattern pertains to how objects are created in the course of the
  code. It is class based.

  In this pattern we create a class interface that delegates the responsibility of
  object instantiation to its subclasses.

  You have a high level class that sits on top of two or more lower level classes
  that are semantically related but different in function. Depending on the input
  the higher level class receives upon instantiation, it will generate one of the
  lower level classes.

  ### Advantages

  - allows for dynamic creation
    - we may not know what objects are required until runtime: the factory allows
      us to cover several eventualities
  - decoupling
    - in cases where the lower level classes combine to create something greater
      than their individual parts (see the Alligator example below), they do not
      have to know about each other.
  - maintenance
    - allows us to add/remove new object classes easily without changing a lot of
      code.

  ## Examples

  We have two types of notification: email and push. The creation of these two is
  handled by a factory class called `NotificationFactory` :

  ```jsx
  class PushNotification {
    constructor(sendTo, message) {
      this.sendTo = sendTo;
      this.message = message;
    }
  }

  class EmailNotification {
    constructor(sendTo, cc, emailContent) {
      this.sendTo = sendTo;
      this.cc = cc;
      this.emailContent = emailContent;
    }
  }
  ```

  ```jsx
  class NotificationFactory {
    createNotification(type, props) {
      switch (type) {
        case "email":
          return new EmailNotification(
            props.sendTo,
            props.cc,
            props.emailContent
          );
        case "push":
          return new PushNotification(props.sendTo, props.message);
      }
    }
  }
  ```

  We first instantiate an instance of the factory and then pass into it the
  parameters for either of the two lower level classes.

  ```jsx
  const factory = new NotificationFactory();

  // create email notification
  const emailNotification = factory.createNotification("email", {
    sendTo: "receiver@domain.com",
    cc: "test@domain.com",
    emailContent: "This is the email content to be delivered.!",
  });

  // create push notification
  const pushNotification = factory.createNotification("push", {
    sendTo: "receiver-device-id",
    message: "The push notification message",
  });
  ```

  This example doesn't use a constructor on the factory class, but we could easily
  do so. This would remove the need to call a method on the factory class when we
  want to instantiate one of the subclasses. Here is another example that does
  this with an alligator factory:

  ```jsx
  class ReptilePartFactory {
    constructor(type, props) {
      if (type === "tail") return new TailFactory(props);
      if (type === "torso") return new TorsoFactory(props);
      if (type === "head") return new HeadFactory(props);
    }
  }

  let alligator = {};
  let alligatorProps = {
    tailLength: 2.5,
    color: "green",
    snoutLenth: 1,
  };

  //gets a tail from the tail factory
  alligator.tail = new ReptilePartFactory("tail", alligatorProps);

  //gets a torso from the torso factory
  alligator.torso = new ReptilePartFactory("torso", alligatorProps);

  //gets a head from the head factory
  alligator.head = new ReptilePartFactory("head", alligatorProps);
  ```

raw_content:
  ---
  tags:
    - OOP
    - design-patterns
  ---

  # Factory pattern

  The factory pattern pertains to how objects are created in the course of the
  code. It is class based.

  In this pattern we create a class interface that delegates the responsibility of
  object instantiation to its subclasses.

  You have a high level class that sits on top of two or more lower level classes
  that are semantically related but different in function. Depending on the input
  the higher level class receives upon instantiation, it will generate one of the
  lower level classes.

  ### Advantages

  - allows for dynamic creation
    - we may not know what objects are required until runtime: the factory allows
      us to cover several eventualities
  - decoupling
    - in cases where the lower level classes combine to create something greater
      than their individual parts (see the Alligator example below), they do not
      have to know about each other.
  - maintenance
    - allows us to add/remove new object classes easily without changing a lot of
      code.

  ## Examples

  We have two types of notification: email and push. The creation of these two is
  handled by a factory class called `NotificationFactory` :

  ```jsx
  class PushNotification {
    constructor(sendTo, message) {
      this.sendTo = sendTo;
      this.message = message;
    }
  }

  class EmailNotification {
    constructor(sendTo, cc, emailContent) {
      this.sendTo = sendTo;
      this.cc = cc;
      this.emailContent = emailContent;
    }
  }
  ```

  ```jsx
  class NotificationFactory {
    createNotification(type, props) {
      switch (type) {
        case "email":
          return new EmailNotification(
            props.sendTo,
            props.cc,
            props.emailContent
          );
        case "push":
          return new PushNotification(props.sendTo, props.message);
      }
    }
  }
  ```

  We first instantiate an instance of the factory and then pass into it the
  parameters for either of the two lower level classes.

  ```jsx
  const factory = new NotificationFactory();

  // create email notification
  const emailNotification = factory.createNotification("email", {
    sendTo: "receiver@domain.com",
    cc: "test@domain.com",
    emailContent: "This is the email content to be delivered.!",
  });

  // create push notification
  const pushNotification = factory.createNotification("push", {
    sendTo: "receiver-device-id",
    message: "The push notification message",
  });
  ```

  This example doesn't use a constructor on the factory class, but we could easily
  do so. This would remove the need to call a method on the factory class when we
  want to instantiate one of the subclasses. Here is another example that does
  this with an alligator factory:

  ```jsx
  class ReptilePartFactory {
    constructor(type, props) {
      if (type === "tail") return new TailFactory(props);
      if (type === "torso") return new TorsoFactory(props);
      if (type === "head") return new HeadFactory(props);
    }
  }

  let alligator = {};
  let alligatorProps = {
    tailLength: 2.5,
    color: "green",
    snoutLenth: 1,
  };

  //gets a tail from the tail factory
  alligator.tail = new ReptilePartFactory("tail", alligatorProps);

  //gets a torso from the torso factory
  alligator.torso = new ReptilePartFactory("torso", alligatorProps);

  //gets a head from the head factory
  alligator.head = new ReptilePartFactory("head", alligatorProps);
  ```


path: Fetch_decode_execute.md
sortable_path: Fetch_decode_execute.md
title: Fetch, decode, execute, store
checksum: 0728041115227276b20f842f04d4d35daad94c2dd1f5aad1b39d3b443bef5528
created: 2025-10-01 13:30:58.730048428+00:00
modified: 2025-08-13 15:57:14.055583902+00:00
metadata: {"tags":["CPU"]}
lead:
  _Fetch, decode, execute_ is the operating cycle of the CPU. We will run through
  how this works with reference to the [CPU architecture](CPU_architecture.md).

body:
  _Fetch, decode, execute_ is the operating cycle of the CPU. We will run through
  how this works with reference to the [CPU architecture](CPU_architecture.md).

  ## Fetch

  ### Overview

  First, instructions and data are fetched from outside of the CPU chip (usually
  from the DRAM). In some cases, the data used by an instruction will be included
  with it. In other instance the instruction will reference the location where the
  data are held as an address.

  ### Specifics

  - The Program Counter register needs to keep track and sequence the different
    instructions that the CPU will work on. The first place it will look for an
    instruction is at the DRAM address `0000`, equivalent to 0 in the Program
    Counter register: the starting point. This is address therefore copied to the
    Memory Address Register for future reference.
  - This memory-storing event constitutes an instruction so it is copied to the
    Instruction Register.
  - As the first instruction has been fetched, the system reaches the end of the
    first cycle. Thus the Program counter increments by 1 to log this.
  - The next fetch cycle begins.

  ## Decode

  ### Overview

  Once the CPU has received the instruction, the CPU controller chip analyses it
  to determine which of its circuits should be used for processing.

  ### Specifics

  - Now that the instruction is fetched and stored in the RAM it needs to be
    decoded. It is therefore sent from the RAM to the Control Unit of the CPU.
    There are two parts to the instruction:
    1. The operation code ("op code"): the command that the computer will carry
       out.
    1. The operand: an address in RAM where the data will be read and written to
       as part of the execution
  - The Control Unit converts the operation code and operand into an instruction
    that is fed to the next stage in the cycle: execution. For example, for an add
    function, `add` would be the op code and `address x` and `address y` would be
    the operands, thus the instruction would be: `add` the value in `address x` to
    the value in `address y`.

  ## Execute

  Now the command will be executed. The operand is copied to the Memory Address
  Register and then passed to the Memory Data Register and the command is carried
  out by the ALU. The activities of ALU are covered in
  [CPU Architecture](CPU_architecture.md#arithmetic-logic-unit) and the notes on
  [Logic Gates](Logic_gates.md).

  ## Store

  ### Overview

  As well as telling the CPU what operation to perform, the instructions must
  specify where to store the result.

  ### Specifics

  - Data is stored differently depending on the instruction.
  - If the instruction is iterative (e.g. adding two numbers and then multiplying
    by another number), the instruction will tell the CPU to store the interim
    first value in the CPU's [registers](CPU_architecture.md#registers). As these
    are part of the CPU, the data can be retrieved more readily. If the value is
    not expected to be used again immediately, it goes to the DRAM or harddisk.

raw_content:
  ---
  tags: [CPU]
  ---

  # Fetch, decode, execute, store

  _Fetch, decode, execute_ is the operating cycle of the CPU. We will run through
  how this works with reference to the [CPU architecture](CPU_architecture.md).

  ## Fetch

  ### Overview

  First, instructions and data are fetched from outside of the CPU chip (usually
  from the DRAM). In some cases, the data used by an instruction will be included
  with it. In other instance the instruction will reference the location where the
  data are held as an address.

  ### Specifics

  - The Program Counter register needs to keep track and sequence the different
    instructions that the CPU will work on. The first place it will look for an
    instruction is at the DRAM address `0000`, equivalent to 0 in the Program
    Counter register: the starting point. This is address therefore copied to the
    Memory Address Register for future reference.
  - This memory-storing event constitutes an instruction so it is copied to the
    Instruction Register.
  - As the first instruction has been fetched, the system reaches the end of the
    first cycle. Thus the Program counter increments by 1 to log this.
  - The next fetch cycle begins.

  ## Decode

  ### Overview

  Once the CPU has received the instruction, the CPU controller chip analyses it
  to determine which of its circuits should be used for processing.

  ### Specifics

  - Now that the instruction is fetched and stored in the RAM it needs to be
    decoded. It is therefore sent from the RAM to the Control Unit of the CPU.
    There are two parts to the instruction:
    1. The operation code ("op code"): the command that the computer will carry
       out.
    1. The operand: an address in RAM where the data will be read and written to
       as part of the execution
  - The Control Unit converts the operation code and operand into an instruction
    that is fed to the next stage in the cycle: execution. For example, for an add
    function, `add` would be the op code and `address x` and `address y` would be
    the operands, thus the instruction would be: `add` the value in `address x` to
    the value in `address y`.

  ## Execute

  Now the command will be executed. The operand is copied to the Memory Address
  Register and then passed to the Memory Data Register and the command is carried
  out by the ALU. The activities of ALU are covered in
  [CPU Architecture](CPU_architecture.md#arithmetic-logic-unit) and the notes on
  [Logic Gates](Logic_gates.md).

  ## Store

  ### Overview

  As well as telling the CPU what operation to perform, the instructions must
  specify where to store the result.

  ### Specifics

  - Data is stored differently depending on the instruction.
  - If the instruction is iterative (e.g. adding two numbers and then multiplying
    by another number), the instruction will tell the CPU to store the interim
    first value in the CPU's [registers](CPU_architecture.md#registers). As these
    are part of the CPU, the data can be retrieved more readily. If the value is
    not expected to be used again immediately, it goes to the DRAM or harddisk.


path: Fetch_from_Secrets_Manager.md
sortable_path: Fetch_from_Secrets_Manager.md
title: Fetch from Secrets Manager
lead: ## TypeScript
checksum: 1868ed19af014838b8449c00ab8640193f95c5496e1b31f701fde06584f1be3d
created: 2025-10-01 13:30:58.742397143+00:00
modified: 2025-08-13 15:57:13.793432516+00:00
metadata: {"tags":["AWS"]}
body:
  ## TypeScript

  ```ts
  const getSecret = async (): Promise<SomeCredentials> => {
    const secretsManager = new AWS.SecretsManager();
    const response = await secretsManager
      .getSecretValue({ SecretId: process.env.SECRET_ARN as string })
      .promise();
    const secretValues = JSON.parse(response.SecretString as string);

    if (secretValues) {
      return {
        accessToken: secretValues.POCKET_ACCESS_TOKEN,
        consumerKey: secretValues.POCKET_CONSUMER_KEY,
      };
    } else {
      throw new Error("Failed to return Pocket credentials");
    }
  };

  type SomeCredentials = {
    accessToken: string;
    consumerKey: string;
  };
  ```

  ## Python

  ```py
  import json
  import boto3

  def get_secret():
      python_env = os.environ.get("PYTHON_ENV", "local")
      """In production, source creds from SecretsManager"""
      if python_env == "production":
          secrets_manager = boto3.client("secretsmanager")
          response = secrets_manager.get_secret_value(SecretId=os.environ["SECRET_ARN"])
          secret_values = json.loads(response["SecretString"])
          return secret_values["GOOGLE_CREDS"]
  ```

raw_content:
  ---
  tags:
    - AWS
  ---

  # Fetch from Secrets Manager

  ## TypeScript

  ```ts
  const getSecret = async (): Promise<SomeCredentials> => {
    const secretsManager = new AWS.SecretsManager();
    const response = await secretsManager
      .getSecretValue({ SecretId: process.env.SECRET_ARN as string })
      .promise();
    const secretValues = JSON.parse(response.SecretString as string);

    if (secretValues) {
      return {
        accessToken: secretValues.POCKET_ACCESS_TOKEN,
        consumerKey: secretValues.POCKET_CONSUMER_KEY,
      };
    } else {
      throw new Error("Failed to return Pocket credentials");
    }
  };

  type SomeCredentials = {
    accessToken: string;
    consumerKey: string;
  };
  ```

  ## Python

  ```py
  import json
  import boto3

  def get_secret():
      python_env = os.environ.get("PYTHON_ENV", "local")
      """In production, source creds from SecretsManager"""
      if python_env == "production":
          secrets_manager = boto3.client("secretsmanager")
          response = secrets_manager.get_secret_value(SecretId=os.environ["SECRET_ARN"])
          secret_values = json.loads(response["SecretString"])
          return secret_values["GOOGLE_CREDS"]
  ```


path: File_descriptors.md
sortable_path: File_descriptors.md
title: File descriptors
lead: File descriptors are shorthand for `stdin`, `stdout` and `stderr`:
checksum: 02cec9154ffd41bbb3e9b313053b6a4daa94b89774e296d26a9bc469c70314e9
created: 2025-10-01 13:30:58.744608361+00:00
modified: 2025-08-13 15:57:13.92057058+00:00
metadata: {"tags":["shell"]}
body:
  File descriptors are shorthand for `stdin`, `stdout` and `stderr`:

  | File descriptor | Name            | Common abbreviation |
  | --------------- | --------------- | ------------------- |
  | 0               | Standard input  | `stdin`             |
  | 1               | Standard output | `stdout`            |
  | 2               | Standard error  | `stderr`            |

  They are typically used when you want to
  [redirect](Redirection_in_Bash.md) the output of the
  standard/input /output/error somewhere, e.g a file or as input to another
  program. A classic case is `[some_command] > /dev/null 2>&1`

  They are all technically "files" which are open and which append themselves to
  every process that can run in the shell. For any command or program that you
  run, you will be able to access `0`, `1` and `2` for them. In this way they are
  similar to variables like
  [`$0`](Passing_arguments_and_options_to_Bash_scripts.md#passing-arguments)
  and
  [`$@`](Passing_arguments_and_options_to_Bash_scripts.md#passing-arguments).
  They have a universal meaning within the context of the shell runtime.

raw_content:
  ---
  tags:
    - shell
  ---

  # File descriptors

  File descriptors are shorthand for `stdin`, `stdout` and `stderr`:

  | File descriptor | Name            | Common abbreviation |
  | --------------- | --------------- | ------------------- |
  | 0               | Standard input  | `stdin`             |
  | 1               | Standard output | `stdout`            |
  | 2               | Standard error  | `stderr`            |

  They are typically used when you want to
  [redirect](Redirection_in_Bash.md) the output of the
  standard/input /output/error somewhere, e.g a file or as input to another
  program. A classic case is `[some_command] > /dev/null 2>&1`

  They are all technically "files" which are open and which append themselves to
  every process that can run in the shell. For any command or program that you
  run, you will be able to access `0`, `1` and `2` for them. In this way they are
  similar to variables like
  [`$0`](Passing_arguments_and_options_to_Bash_scripts.md#passing-arguments)
  and
  [`$@`](Passing_arguments_and_options_to_Bash_scripts.md#passing-arguments).
  They have a universal meaning within the context of the shell runtime.


path: File_operations_in_Python.md
sortable_path: File_operations_in_Python.md
title: File operations in Python
lead: Most create, delete, move etc operations invoke the inbuilt `os` module.
checksum: 48e81f9ab7a9010fd29a04828557d98a39a76c70a53132e45849ebe4348a93a2
created: 2025-10-01 13:30:58.763076552+00:00
modified: 2025-08-13 15:57:13.964173816+00:00
metadata: {"created":"Friday, October 25, 2024","tags":["python","file-system","procedural"]}
body:
  Most create, delete, move etc operations invoke the inbuilt `os` module.

  // Add directory CRUD operations in Python

  ## Renaming files (moving)

  ```py
  import os
  os.rename('original-file-name.txt', 'new-file-name.txt')
  ```

  ## Deleting files

  ```py
  import os
  os.remove('file-name.txt')
  ```

  ## Check file exists

  ```python
  import os

  file_exists = os.exists('/file/path')

  ```

raw_content:
  ---
  tags: [python, file-system, procedural]
  created: Friday, October 25, 2024
  ---

  # File operations in Python

  Most create, delete, move etc operations invoke the inbuilt `os` module.

  // Add directory CRUD operations in Python

  ## Renaming files (moving)

  ```py
  import os
  os.rename('original-file-name.txt', 'new-file-name.txt')
  ```

  ## Deleting files

  ```py
  import os
  os.remove('file-name.txt')
  ```

  ## Check file exists

  ```python
  import os

  file_exists = os.exists('/file/path')

  ```


path: File_permissions_in_Linux.md
sortable_path: File_permissions_in_Linux.md
title: File permissions in Linux
lead: ## View permissions
checksum: 8f03659b1506fd476c855eb4c88f0c0ca96c61b0422f138254da1492cbe1cb62
created: 2025-10-01 13:30:58.765854715+00:00
modified: 2025-08-13 15:57:13.918061241+00:00
metadata: {"created":"Friday, April 11, 2025","tags":["Linux","permissions"]}
body:
  ## View permissions

  ```bash
  ls -rfl
  ```

  ## What the letters mean

  ```bash
  drwxr-xr-x  2 thomas thomas  4096 Jan 21 18:00 dist
  drwxr-xr-x  2 thomas thomas  4096 Dec 29 12:50 out
  -rw-r--r--  1 thomas thomas  1108 Jan 21 17:42 README.md
  ```

  The first column of the permissions output is known as the file's _mode_. The
  sequence from left to right is as follows:

  ```
  -       - - -                 - - -                 - - -
  type    user permissions      group permissions     other permissions
  ```

  <dl>
    <dt>type</dt>
    <dd>The file type. A dash just means an ordinary file. `d` means directory </dd>
   
    <dt>user permissions</dt>
    <dd>read, write or execute. A dash means 'nothing': the permissions for that slot in the set have not be assigned</dd>

    <dt>group and other</dt>
    <dd>group is obviously what anyone belonging to the current file's user group can do. Everyone else (outside of the user and the group) is covered by the other permissions, sometimes known as 'world' permissions</dd>
  </dl>

raw_content:
  ---
  tags: [Linux, permissions]
  created: Friday, April 11, 2025
  ---

  # File permissions in Linux

  ## View permissions

  ```bash
  ls -rfl
  ```

  ## What the letters mean

  ```bash
  drwxr-xr-x  2 thomas thomas  4096 Jan 21 18:00 dist
  drwxr-xr-x  2 thomas thomas  4096 Dec 29 12:50 out
  -rw-r--r--  1 thomas thomas  1108 Jan 21 17:42 README.md
  ```

  The first column of the permissions output is known as the file's _mode_. The
  sequence from left to right is as follows:

  ```
  -       - - -                 - - -                 - - -
  type    user permissions      group permissions     other permissions
  ```

  <dl>
    <dt>type</dt>
    <dd>The file type. A dash just means an ordinary file. `d` means directory </dd>
   
    <dt>user permissions</dt>
    <dd>read, write or execute. A dash means 'nothing': the permissions for that slot in the set have not be assigned</dd>

    <dt>group and other</dt>
    <dd>group is obviously what anyone belonging to the current file's user group can do. Everyone else (outside of the user and the group) is covered by the other permissions, sometimes known as 'world' permissions</dd>
  </dl>


path: File_system_error_handling_in_Python.md
sortable_path: File_system_error_handling_in_Python.md
title: File system error handling in Python
lead:
  The [error handler](Error_handling_in_Python.md) in the case of a file not being
  found:

checksum: 4af222636234d404e53c1a463ab6687a73dea80ae94161e2ea811569c1ea9ae5
created: 2025-10-01 13:30:58.767972482+00:00
modified: 2025-08-13 15:57:14.002744797+00:00
metadata: {"created":"Friday, October 25, 2024","tags":["python","file-system","procedural"]}
body:
  The [error handler](Error_handling_in_Python.md) in the case of a file not being
  found:

  ```py
  try:
      with open('filename.txt', 'r') as file:
      contents = file.readlines()
      for line in lines:
          print(line)
  except FileNotFoundError as err:
      print("File does not exist")
      print(err)
  ```

raw_content:
  ---
  tags: [python, file-system, procedural]
  created: Friday, October 25, 2024
  ---

  # File system error handling in Python

  The [error handler](Error_handling_in_Python.md) in the case of a file not being
  found:

  ```py
  try:
      with open('filename.txt', 'r') as file:
      contents = file.readlines()
      for line in lines:
          print(line)
  except FileNotFoundError as err:
      print("File does not exist")
      print(err)
  ```


path: Filesystems.md
sortable_path: Filesystems.md
title: Filesystems
checksum: 63a227182b5bf2eb9d3fcc3a55453e60f945596d9797e15dc0c1be04b40aa3d6
created: 2025-10-01 13:30:58.773375958+00:00
modified: 2025-08-13 15:57:13.825923314+00:00
metadata: {"tags":["disks","computer-architecture","operating-systems"]}
lead:
  We cannot yet mount or interact with the partitions we have created. This is
  because we have not added a filesystem to each partition.

body:
  We cannot yet mount or interact with the partitions we have created. This is
  because we have not added a filesystem to each partition.

  > A filesytem is a form of [database](Basic_database_concepts.md); it
  > supplies the structure to transform a simple block device into the
  > sophisticated hierarchy of files and subdirectories that users can understand.

  Linux recognises many types of filesystems. The native Linux filesystem is the
  **ext4** (Fourth Extended Filesystem). Another common filesystem is **FAT**
  (File Allocation Table). Instances of this include _MSDOS_,_EXFAT_,_FAT-32_.
  They originate from Microsoft systems

  ## Creating a filesystem

  Remember we have two partitions on our external drive: `sda1` and `sda2`. We are
  going to use the `mkfs` utility to create an EXT4 system on both.

  ```bash
  mkfs -t ext4 /dev/sda1
  mkfs -t ext4 /dev/sda2
  ```

  ## Mounting a filesystem

  We can now mount our filesystems. When we mount, we must specify the following
  criteria with the request:

  - The name of the device we want to mount.
    - This will be the name or the partition. However the names (`sda` etc)
      assigned by the OS can change. In these cases and with GPT-based partitions
      you can use the UUID.
    - To see a list of devices and the corresponding filesystems and UUIDs on your
      system, you can use the **`blkid`** ('block id') program.
      ```
      /dev/nvme0n1p3: UUID="c53577b5-92ef-4a0a-9a19-e488bfdfa39c" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="e152b9f4-7ce8-e74b-94db-2731c6fce53d"
      /dev/nvme0n1p1: UUID="9920-636A" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="50592521-d386-194a-a362-bc8562ed6c82"
      /dev/nvme0n1p2: UUID="2ee6b834-0857-49dc-b8ba-a24d46d228ae" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="e08cc442-ef51-7b4f-9d55-e236c55c933c"
      /dev/sda2: UUID="abac6e2e-e3bf-40d3-a5ba-317c53eb27ce" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="4ef1b0e8-3d5b-c940-a3b1-0f85cddeca42"
      /dev/sda1: UUID="ba1e40c5-9b29-4309-a559-99bf8f68116f" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="b4983358-6036-df40-a1f8-793976f3dfb1"
      ```
  - The filesystem type (optional)
  - The **mount point**
    - This is the place within the existing filesystem where you want to mount the
      partition.
    - When you mount to a directory, this directory _becomes_ the disk you have
      mounted, you will not see it as a subdirectory within the the mount point,
      you will just see the contents of the disk itself

  ```bash
  mkdir mountpoint
  mount -t ext4 /dev/sda1 /mnt
  touch test.txt
  ```

  Our `sda1` partition is now mounted at `mountpoint`. We can go ahead and create
  files. If we now look within the graphical file manager when we click on the
  `sda1` volume, we will see the new file we have created in `mountpoint`.

  ![](/img/mount-directory.png)

  ## fstab

  In most cases you want your filesystem to mount automatically on boot and always
  to the same mount point. You can do this via the specialised `fstab` file on
  Linux systems within the `/etc/` directory.

  This is my current `fstab`:

  ```
  # <file system> <dir> <type> <options> <dump> <pass>
  # /dev/nvme0n1p2
  UUID=2ee6b834-0857-49dc-b8ba-a24d46d228ae	/         	ext4      	rw,relatime	0 1

  # /dev/nvme0n1p3
  UUID=c53577b5-92ef-4a0a-9a19-e488bfdfa39c	/home     	ext4      	rw,relatime	0 2

  /swapfile none swap sw 0 0

  ```

  It shows my root and home filesystems and my
  [swap](Swap_space.md) file. Note that we use the UUID
  to name the partition rather than its name in `/dev/`. The order of the
  parameters is as follows:

  - Device name or UUID
  - The mount point
  - The filesystem type (in the example there are two types (`ext4` and `swap`))
  - Options
  - Now largely irrelevant backup information, set to )
  - Filesystem integrity test order
    - Runs the `fsck` ('filesystem check') command against each filesystem
    - Put `1` against the root partition for this to be checked first
    - Put `0` for no checks to take place

raw_content:
  ---
  tags:
    - disks
    - computer-architecture
    - operating-systems
  ---

  # Filesystems

  We cannot yet mount or interact with the partitions we have created. This is
  because we have not added a filesystem to each partition.

  > A filesytem is a form of [database](Basic_database_concepts.md); it
  > supplies the structure to transform a simple block device into the
  > sophisticated hierarchy of files and subdirectories that users can understand.

  Linux recognises many types of filesystems. The native Linux filesystem is the
  **ext4** (Fourth Extended Filesystem). Another common filesystem is **FAT**
  (File Allocation Table). Instances of this include _MSDOS_,_EXFAT_,_FAT-32_.
  They originate from Microsoft systems

  ## Creating a filesystem

  Remember we have two partitions on our external drive: `sda1` and `sda2`. We are
  going to use the `mkfs` utility to create an EXT4 system on both.

  ```bash
  mkfs -t ext4 /dev/sda1
  mkfs -t ext4 /dev/sda2
  ```

  ## Mounting a filesystem

  We can now mount our filesystems. When we mount, we must specify the following
  criteria with the request:

  - The name of the device we want to mount.
    - This will be the name or the partition. However the names (`sda` etc)
      assigned by the OS can change. In these cases and with GPT-based partitions
      you can use the UUID.
    - To see a list of devices and the corresponding filesystems and UUIDs on your
      system, you can use the **`blkid`** ('block id') program.
      ```
      /dev/nvme0n1p3: UUID="c53577b5-92ef-4a0a-9a19-e488bfdfa39c" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="e152b9f4-7ce8-e74b-94db-2731c6fce53d"
      /dev/nvme0n1p1: UUID="9920-636A" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="50592521-d386-194a-a362-bc8562ed6c82"
      /dev/nvme0n1p2: UUID="2ee6b834-0857-49dc-b8ba-a24d46d228ae" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="e08cc442-ef51-7b4f-9d55-e236c55c933c"
      /dev/sda2: UUID="abac6e2e-e3bf-40d3-a5ba-317c53eb27ce" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="4ef1b0e8-3d5b-c940-a3b1-0f85cddeca42"
      /dev/sda1: UUID="ba1e40c5-9b29-4309-a559-99bf8f68116f" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="b4983358-6036-df40-a1f8-793976f3dfb1"
      ```
  - The filesystem type (optional)
  - The **mount point**
    - This is the place within the existing filesystem where you want to mount the
      partition.
    - When you mount to a directory, this directory _becomes_ the disk you have
      mounted, you will not see it as a subdirectory within the the mount point,
      you will just see the contents of the disk itself

  ```bash
  mkdir mountpoint
  mount -t ext4 /dev/sda1 /mnt
  touch test.txt
  ```

  Our `sda1` partition is now mounted at `mountpoint`. We can go ahead and create
  files. If we now look within the graphical file manager when we click on the
  `sda1` volume, we will see the new file we have created in `mountpoint`.

  ![](/img/mount-directory.png)

  ## fstab

  In most cases you want your filesystem to mount automatically on boot and always
  to the same mount point. You can do this via the specialised `fstab` file on
  Linux systems within the `/etc/` directory.

  This is my current `fstab`:

  ```
  # <file system> <dir> <type> <options> <dump> <pass>
  # /dev/nvme0n1p2
  UUID=2ee6b834-0857-49dc-b8ba-a24d46d228ae	/         	ext4      	rw,relatime	0 1

  # /dev/nvme0n1p3
  UUID=c53577b5-92ef-4a0a-9a19-e488bfdfa39c	/home     	ext4      	rw,relatime	0 2

  /swapfile none swap sw 0 0

  ```

  It shows my root and home filesystems and my
  [swap](Swap_space.md) file. Note that we use the UUID
  to name the partition rather than its name in `/dev/`. The order of the
  parameters is as follows:

  - Device name or UUID
  - The mount point
  - The filesystem type (in the example there are two types (`ext4` and `swap`))
  - Options
  - Now largely irrelevant backup information, set to )
  - Filesystem integrity test order
    - Runs the `fsck` ('filesystem check') command against each filesystem
    - Put `1` against the root partition for this to be checked first
    - Put `0` for no checks to take place


path: Find_Bash_command.md
sortable_path: Find_Bash_command.md
title: find
lead:
  `find` can be used both to locate files and run operations on the files it
  finds.

checksum: 2fd50ecefe4f27677ab5f5f7c6df1e043c43b02a16eb0c7a851b71f68ac6c8e0
created: 2025-10-01 13:30:58.786034991+00:00
modified: 2025-08-13 15:57:13.796663261+00:00
metadata: {"tags":["shell"]}
body:
  `find` can be used both to locate files and run operations on the files it
  finds.

  ## Main syntax

  ### No options

  Without options specified, `find` alone will return a recursive index of all the
  files in the directory from which it is run.

  ### Sub-directory

  If we pass a directory to `find` it will repeat the above process but
  specifically for that directory.

  ```bash
  $ find i3
  i3
  i3/config
  ```

  ### Filters

  We can specify flags as filters (known as 'tests' within the program).

  #### Type

  Filter by type: file or directory

  ```
  $ find -type d # return dirs only
  $ find -type f # return files only
  ```

  Within a specified directory:

  ```bash
  $ find i3 -type f
  ```

  #### Filename

  This is the most frequent use case: filter files by name with globbing.

  ```bash
  $ find -name "config"
  ./.git/config
  ./i3/config
  ```

  ```bash
  $ find -name "*.js"
  ```

  The same, but case insensitive: `iname`

  ```bash
  $ find -iname "*.JS"
  ```

  #### Path

  As above but this time includes directory names in the match. `ipath` is the
  case-insensitive version.

  ```bash
  $ find -path "utils*"
  utils.js
  utils/do-something.js
  ```

  ### Operators

  We can combine `find` commands by using logical operators: `-and`, `-or`,
  `-not`. For example:

  ```bash
  $ find -not -name "*.js" -type f
  ./app/index.html
  ./app/style.css
  ./dist/index.html
  ./dist/style.c
  ```

  Applied to a directory:

  ```bash
  find . -type -f -not -path "./.git/"
  ```

  ## Run programs against results

  Using the `exec` keyword we can run a program against the files that are
  returned from `find`.

  In this syntax we use `{}` as a placeholder for the path of the file that is
  matched. We use `;` (escaped) to indicate the end of the operation.

  ### Examples

  This script deletes the files that match the filter criteria:

  ```bash
  $ find -name "*.js" -exec rm {} \;
  ```

  This script finds all the files with the substring 'config' in their name and
  writes their file size to a file.

  ```bash
  find -name '*config*' -exec wc -c {} \; > config-sizes
  ```

raw_content:
  ---
  tags:
    - shell
  ---

  # `find`

  `find` can be used both to locate files and run operations on the files it
  finds.

  ## Main syntax

  ### No options

  Without options specified, `find` alone will return a recursive index of all the
  files in the directory from which it is run.

  ### Sub-directory

  If we pass a directory to `find` it will repeat the above process but
  specifically for that directory.

  ```bash
  $ find i3
  i3
  i3/config
  ```

  ### Filters

  We can specify flags as filters (known as 'tests' within the program).

  #### Type

  Filter by type: file or directory

  ```
  $ find -type d # return dirs only
  $ find -type f # return files only
  ```

  Within a specified directory:

  ```bash
  $ find i3 -type f
  ```

  #### Filename

  This is the most frequent use case: filter files by name with globbing.

  ```bash
  $ find -name "config"
  ./.git/config
  ./i3/config
  ```

  ```bash
  $ find -name "*.js"
  ```

  The same, but case insensitive: `iname`

  ```bash
  $ find -iname "*.JS"
  ```

  #### Path

  As above but this time includes directory names in the match. `ipath` is the
  case-insensitive version.

  ```bash
  $ find -path "utils*"
  utils.js
  utils/do-something.js
  ```

  ### Operators

  We can combine `find` commands by using logical operators: `-and`, `-or`,
  `-not`. For example:

  ```bash
  $ find -not -name "*.js" -type f
  ./app/index.html
  ./app/style.css
  ./dist/index.html
  ./dist/style.c
  ```

  Applied to a directory:

  ```bash
  find . -type -f -not -path "./.git/"
  ```

  ## Run programs against results

  Using the `exec` keyword we can run a program against the files that are
  returned from `find`.

  In this syntax we use `{}` as a placeholder for the path of the file that is
  matched. We use `;` (escaped) to indicate the end of the operation.

  ### Examples

  This script deletes the files that match the filter criteria:

  ```bash
  $ find -name "*.js" -exec rm {} \;
  ```

  This script finds all the files with the substring 'config' in their name and
  writes their file size to a file.

  ```bash
  find -name '*config*' -exec wc -c {} \; > config-sizes
  ```


path: Firewalls.md
sortable_path: Firewalls.md
title: Firewalls
checksum: 223179f3ecf6b54694f9965cf4d922379173699550e035779384ef4197a1a1bf
created: 2025-10-01 13:30:58.789039919+00:00
modified: 2025-08-13 15:57:14.091815929+00:00
metadata: {"created":"Sunday, February 09, 2025","tags":["networks","servers","firewalls"]}
lead:
  A firewall is a server mechanism that filters incoming requests for resources
  and services that it hosts.

body:
  A firewall is a server mechanism that filters incoming requests for resources
  and services that it hosts.

  Based on data about the requester, derived from the
  [TLS](./Transport_Layer_of_Internet_Protocol.md) packet headers, the firewall
  decides whether or not to grant access to the requested resource.

  It will typically comprise three actions:

  - accept
  - drop
  - reject

  Accept is obvious. To "drop" means to block the traffic without sending a reply.
  To "reject" is to block the traffic but reply with an "unreachable" error.

  An example of a firewall rule would be to drop incoming traffic from IP
  addresses of non-technical employees to the SSH port (22) but allow technical
  staff to access it.

  Network traffic is matched agains a list of firewall rules in a sequence from
  first to last. As you cannot cover all cases, you must specify a default action
  when a rule is not matched.

  Whilst firewalls are focused on incoming traffic, you can also apply rules to
  outgoing traffic. A server will permit most outgoing traffic since, after all,
  it can trust itself however you may want to stop the server connecting to known
  malicious actors.

  ## Related software

  IPTables is the standard Linux software for managing firewalls. There is also
  Uncomplicated Firewall (UWF) which attaches to IPTables and simplifies the
  process.

  Another useful software is Fail2ban which can automatically configure your
  firewall to block brute force login attempts and DDOS attacks.

  ## Difference from reverse proxies

  Whilst both reverse proxies and firewalls filter incoming traffic to a server,
  there are important differences.

  The firewall operates at the
  [Transport Layer](./Transport_Layer_of_Internet_Protocol.md) wehreas proxies
  operate at the [Application Layer](./Application_Layer_of_Internet_Protocol.md).

  The firewall is primarily concerned with security and precedes anything that
  happens with the incoming packets at the Appplication Layer. The packets first
  have to get through the firewall before the request can be routed to a specific
  service on the server.

raw_content:
  ---
  tags: [networks, servers, firewalls]
  created: Sunday, February 09, 2025
  ---

  # Firewalls

  A firewall is a server mechanism that filters incoming requests for resources
  and services that it hosts.

  Based on data about the requester, derived from the
  [TLS](./Transport_Layer_of_Internet_Protocol.md) packet headers, the firewall
  decides whether or not to grant access to the requested resource.

  It will typically comprise three actions:

  - accept
  - drop
  - reject

  Accept is obvious. To "drop" means to block the traffic without sending a reply.
  To "reject" is to block the traffic but reply with an "unreachable" error.

  An example of a firewall rule would be to drop incoming traffic from IP
  addresses of non-technical employees to the SSH port (22) but allow technical
  staff to access it.

  Network traffic is matched agains a list of firewall rules in a sequence from
  first to last. As you cannot cover all cases, you must specify a default action
  when a rule is not matched.

  Whilst firewalls are focused on incoming traffic, you can also apply rules to
  outgoing traffic. A server will permit most outgoing traffic since, after all,
  it can trust itself however you may want to stop the server connecting to known
  malicious actors.

  ## Related software

  IPTables is the standard Linux software for managing firewalls. There is also
  Uncomplicated Firewall (UWF) which attaches to IPTables and simplifies the
  process.

  Another useful software is Fail2ban which can automatically configure your
  firewall to block brute force login attempts and DDOS attacks.

  ## Difference from reverse proxies

  Whilst both reverse proxies and firewalls filter incoming traffic to a server,
  there are important differences.

  The firewall operates at the
  [Transport Layer](./Transport_Layer_of_Internet_Protocol.md) wehreas proxies
  operate at the [Application Layer](./Application_Layer_of_Internet_Protocol.md).

  The firewall is primarily concerned with security and precedes anything that
  happens with the incoming packets at the Appplication Layer. The packets first
  have to get through the firewall before the request can be routed to a specific
  service on the server.


path: Flip_flops.md
sortable_path: Flip_flops.md
title: Flip-Flops
checksum: 198fdae848e7544e8e4e8a33b32e28274dfad5f77979b255b188aa8666e898e6
created: 2025-10-01 13:30:58.807095414+00:00
modified: 2025-08-13 15:57:14.028681708+00:00
metadata: {"tags":["logic-gates","binary","memory"]}
lead:
  A flip-flop is a type of
  [latch](Latches.md) that is connected
  to a [clock signal](Clock_signals.md)
  and which executes in time with the clock's pulse. (Sometimes "latch" and
  "flip-flop" are used interchangeably but technically a latch is flip-flop
  without a clock connection.)

body:
  A flip-flop is a type of
  [latch](Latches.md) that is connected
  to a [clock signal](Clock_signals.md)
  and which executes in time with the clock's pulse. (Sometimes "latch" and
  "flip-flop" are used interchangeably but technically a latch is flip-flop
  without a clock connection.)

  ## JK Flip-Flop

  The JK Flip-Flop (the letters are meaningless) is basically an SR latch in
  functionality. It has a "set" input (_J_) and a "reset" input (_K_) and _Q_ and
  inverted-_Q_ outputs.

  Where it differs from the SR is that it the SR will change state just if the
  voltage is high (this, afterall, is all that a bit is) whereas for the JK to set
  it **must receive a clock pulse** and it will only set on the pulse. Hence in
  addition to _J_ and _K_ inputs it has a _CLK_ input for "clock".

  In addition the JK Flip-Flop has a **toggle** function. When this is executed,
  whatever _Q_ currently is will be flipped to its opposite value:
  $1 \rightarrow 0$, $0 \rightarrow 1$. The toggle executes when both _J_ and _K_
  are set to high.

  The possible state changes for the JK Flip-Flop are detailed below:

  | J   | K   | Clock | Q state                   | Operation |
  | --- | --- | ----- | ------------------------- | --------- |
  | 0   | 0   | Pulse | Maintain previous value   | Hold      |
  | 0   | 1   | Pulse | 0                         | Reset     |
  | 1   | 0   | Pulse | 1                         | Set       |
  | 1   | 1   | Pulse | Inverse of previous value | Toggle    |

  A JK Flip-Flop can execute on either the positive or negative pulse. Below are
  the diagrams for a rising and falling pulse respectively:

  ![](/img/jk-flip-flops.png)

  ## T Flip-Flops

  Another type of Flip-Flop can be created by connecting the _J_ and _K_ inputs of
  a JK Flip-Flop to each other. This reduces the possible states to two: toggle
  current state or maintain current state. Its overall logic is therefore reduced
  to just two lines of the previous state table: the state that obtains when _J_
  and _K_ are $0 0$ or $1 1$.

  Thus the state table for the T Flip-Flop is:

  | T   | Clock | Q state                   | Operation |
  | --- | ----- | ------------------------- | --------- |
  | 0   | Pulse | Maintain previous value   | Hold      |
  | 0   | Pulse | Inverse of previous value | Toggle    |

  ![](/img/t-flip-flops.png)

raw_content:
  ---
  tags:
    - logic-gates
    - binary
    - memory
  ---

  # Flip-Flops

  A flip-flop is a type of
  [latch](Latches.md) that is connected
  to a [clock signal](Clock_signals.md)
  and which executes in time with the clock's pulse. (Sometimes "latch" and
  "flip-flop" are used interchangeably but technically a latch is flip-flop
  without a clock connection.)

  ## JK Flip-Flop

  The JK Flip-Flop (the letters are meaningless) is basically an SR latch in
  functionality. It has a "set" input (_J_) and a "reset" input (_K_) and _Q_ and
  inverted-_Q_ outputs.

  Where it differs from the SR is that it the SR will change state just if the
  voltage is high (this, afterall, is all that a bit is) whereas for the JK to set
  it **must receive a clock pulse** and it will only set on the pulse. Hence in
  addition to _J_ and _K_ inputs it has a _CLK_ input for "clock".

  In addition the JK Flip-Flop has a **toggle** function. When this is executed,
  whatever _Q_ currently is will be flipped to its opposite value:
  $1 \rightarrow 0$, $0 \rightarrow 1$. The toggle executes when both _J_ and _K_
  are set to high.

  The possible state changes for the JK Flip-Flop are detailed below:

  | J   | K   | Clock | Q state                   | Operation |
  | --- | --- | ----- | ------------------------- | --------- |
  | 0   | 0   | Pulse | Maintain previous value   | Hold      |
  | 0   | 1   | Pulse | 0                         | Reset     |
  | 1   | 0   | Pulse | 1                         | Set       |
  | 1   | 1   | Pulse | Inverse of previous value | Toggle    |

  A JK Flip-Flop can execute on either the positive or negative pulse. Below are
  the diagrams for a rising and falling pulse respectively:

  ![](/img/jk-flip-flops.png)

  ## T Flip-Flops

  Another type of Flip-Flop can be created by connecting the _J_ and _K_ inputs of
  a JK Flip-Flop to each other. This reduces the possible states to two: toggle
  current state or maintain current state. Its overall logic is therefore reduced
  to just two lines of the previous state table: the state that obtains when _J_
  and _K_ are $0 0$ or $1 1$.

  Thus the state table for the T Flip-Flop is:

  | T   | Clock | Q state                   | Operation |
  | --- | ----- | ------------------------- | --------- |
  | 0   | Pulse | Maintain previous value   | Hold      |
  | 0   | Pulse | Inverse of previous value | Toggle    |

  ![](/img/t-flip-flops.png)


path: Foreign_keys_in_SQL.md
sortable_path: Foreign_keys_in_SQL.md
title: Creating views with foreign keys
checksum: 188b7858a8887ea1c737fe2bcd06c8282cbbf98697381d1aedde97f5e2469572
created: 2025-10-01 13:30:58.815341693+00:00
modified: 2025-08-13 15:57:14.079744735+00:00
metadata: {"tags":["SQL","databases"]}
lead:
  We utilise **foreign** and
  [primary keys](Primary_key.md) to create
  relationships between tables in SQL. Foreign keys link data in one table to the
  data in another table and are how we cross-reference data in SQL.

body:
  We utilise **foreign** and
  [primary keys](Primary_key.md) to create
  relationships between tables in SQL. Foreign keys link data in one table to the
  data in another table and are how we cross-reference data in SQL.

  In essence you use the primary key of one table to access data in another table.
  When one table _A_ contains the primary key of another table _B_ as a field,
  that key is "foreign" to _A_ hence the name.

  Let's say we have a `sales` table:

  | saleId | modelId | saleDate   | employeeId | price  |
  | ------ | ------- | ---------- | ---------- | ------ |
  | 1      | 44      | 2020-07-27 | tbishop    | 399.99 |
  | 2      | 22      | 2021-02-05 | tbishop    | 200.99 |

  In SQL this would be set up as follows:

  ```sql
  CREATE TABLE sales (
    saleId integer PRIMARY KEY,
    modelId integer,
    saleDate date,
    employeeId text,
    price float
  )
  ```

  For every model that is sold it is possible for the customer to return it. This
  data will be kept in another table `returns`. Every model sold will have an
  entry in `sales` but may or may not have an entry in `returns` (not every
  customer will return an item).

  We want to establish a relationship between the two tables so that if an item is
  returned we can trace it back to its original sale.

  As the `saleId` is the primary key in `sales` this means it is a unique
  identifier for each model sold. We will therefore use this in our `returns`
  table to track the sale data.

  Here's our `returns` table:

  | returnId | saleId | returnDate  | reason                                         |
  | -------- | ------ | ----------- | ---------------------------------------------- |
  | 7899     | 1      | 2020-11-218 | New device issued under warranty due to defect |
  | 6711     | 2      | 2022-09-02  | Returned gift                                  |

  In this table `saleId` is identical to `saleId` in sales. It is the primary key
  in `sales` but a foreign key in `returns`. If a device has been returned it must
  have an entry in `returns` and the `salesId` of the entry in `returns` must
  match the `salesId` in `sales`.

  This is the primary benefit of utilising foreign keys: they add a restriction.
  Entries to the table with a foreign key **must** have a value that corresponds
  with the foreign key column.

  We call this a **foreign key contraint**. More explicitly, our contraint is as
  follows:

  > Any value entered into returns.saleId must already exist in sales.salesId

  A secondary benefit is that they save us the trouble of repeating data. Without
  foreign keys we would have to input `saleId` twice in two different tables.

  We establish the foreign key reference with ther following SQL:

  ```sql
  CREATE TABLE returns (
  	returnId integer PRIMARY KEY,
  	saleId integer NOT NULL,
  	returnDate date,
  	reason text,
  	FOREIGN KEY (sale_id) REFERENCES sales(sale_id)
  	);
  ```

  A table can have more than one foreign key.

  If you delete the source of the foreign key, you also delete its references in
  tables for which it is a foreign key. This is important to remember. So if a row
  was deleted from `sales` the row in `returns` with the corresponding `saleId`
  would also be deleted.

raw_content:
  ---
  tags:
    - SQL
    - databases
  ---

  # Creating views with foreign keys

  We utilise **foreign** and
  [primary keys](Primary_key.md) to create
  relationships between tables in SQL. Foreign keys link data in one table to the
  data in another table and are how we cross-reference data in SQL.

  In essence you use the primary key of one table to access data in another table.
  When one table _A_ contains the primary key of another table _B_ as a field,
  that key is "foreign" to _A_ hence the name.

  Let's say we have a `sales` table:

  | saleId | modelId | saleDate   | employeeId | price  |
  | ------ | ------- | ---------- | ---------- | ------ |
  | 1      | 44      | 2020-07-27 | tbishop    | 399.99 |
  | 2      | 22      | 2021-02-05 | tbishop    | 200.99 |

  In SQL this would be set up as follows:

  ```sql
  CREATE TABLE sales (
    saleId integer PRIMARY KEY,
    modelId integer,
    saleDate date,
    employeeId text,
    price float
  )
  ```

  For every model that is sold it is possible for the customer to return it. This
  data will be kept in another table `returns`. Every model sold will have an
  entry in `sales` but may or may not have an entry in `returns` (not every
  customer will return an item).

  We want to establish a relationship between the two tables so that if an item is
  returned we can trace it back to its original sale.

  As the `saleId` is the primary key in `sales` this means it is a unique
  identifier for each model sold. We will therefore use this in our `returns`
  table to track the sale data.

  Here's our `returns` table:

  | returnId | saleId | returnDate  | reason                                         |
  | -------- | ------ | ----------- | ---------------------------------------------- |
  | 7899     | 1      | 2020-11-218 | New device issued under warranty due to defect |
  | 6711     | 2      | 2022-09-02  | Returned gift                                  |

  In this table `saleId` is identical to `saleId` in sales. It is the primary key
  in `sales` but a foreign key in `returns`. If a device has been returned it must
  have an entry in `returns` and the `salesId` of the entry in `returns` must
  match the `salesId` in `sales`.

  This is the primary benefit of utilising foreign keys: they add a restriction.
  Entries to the table with a foreign key **must** have a value that corresponds
  with the foreign key column.

  We call this a **foreign key contraint**. More explicitly, our contraint is as
  follows:

  > Any value entered into returns.saleId must already exist in sales.salesId

  A secondary benefit is that they save us the trouble of repeating data. Without
  foreign keys we would have to input `saleId` twice in two different tables.

  We establish the foreign key reference with ther following SQL:

  ```sql
  CREATE TABLE returns (
  	returnId integer PRIMARY KEY,
  	saleId integer NOT NULL,
  	returnDate date,
  	reason text,
  	FOREIGN KEY (sale_id) REFERENCES sales(sale_id)
  	);
  ```

  A table can have more than one foreign key.

  If you delete the source of the foreign key, you also delete its references in
  tables for which it is a foreign key. This is important to remember. So if a row
  was deleted from `sales` the row in `returns` with the corresponding `saleId`
  would also be deleted.


path: Forgejo_runner_failure_after_VPS_reboot.md
sortable_path: Forgejo_runner_failure_after_VPS_reboot.md
title: Forgejo runner failure after VPS reboot
checksum: 63faf3ea088df08b177ce3f4f2a52a3138bc6c4b1b2672c177d0601a7458924d
created: 2025-10-01 13:30:58.822119093+00:00
modified: 2025-08-13 15:57:13.84479514+00:00
metadata: {"created":"Saturday, August 09, 2025","tags":["docker","servers"]}
lead:
  If you get stuck on "Waiting" when running an automated deployment in Forgejo,
  this can be because you've rebooted the VPS and the Docker permissions have
  reset.

body:
  If you get stuck on "Waiting" when running an automated deployment in Forgejo,
  this can be because you've rebooted the VPS and the Docker permissions have
  reset.

  Error in Docker logs typically looks like:

  ```
  Error: cannot ping the docker daemon, does it running? permission denied
  while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:
  Get "http://%2Fvar%2Frun%2Fdocker.sock/_ping": dial unix /var/run/docker.sock:
  connect: permission denied
  ```

  Fix with:

  ```sh
  sudo chmod 666 /var/run/docker.sock
  docker restart forgejo-runner
  ```

raw_content:
  ---
  tags: [docker, servers]
  created: Saturday, August 09, 2025
  ---

  # Forgejo runner failure after VPS reboot

  If you get stuck on "Waiting" when running an automated deployment in Forgejo,
  this can be because you've rebooted the VPS and the Docker permissions have
  reset.

  Error in Docker logs typically looks like:

  ```
  Error: cannot ping the docker daemon, does it running? permission denied
  while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:
  Get "http://%2Fvar%2Frun%2Fdocker.sock/_ping": dial unix /var/run/docker.sock:
  connect: permission denied
  ```

  Fix with:

  ```sh
  sudo chmod 666 /var/run/docker.sock
  docker restart forgejo-runner
  ```


path: Formal_conventions.md
sortable_path: Formal_conventions.md
title: Formal conventions
lead: ## Grouping symbols
checksum: fa1ce008fb14d5df60574e86e15eb3c9b8b9c1f1af84a92134591cb053a70738
created: 2025-10-01 13:30:58.824904861+00:00
modified: 2025-08-13 15:57:13.848043253+00:00
metadata: {"tags":["prealgebra"]}
body:
  ## Grouping symbols

  We use parentheses to delimit the part of an expression we want evaluated first.
  If grouping symbols are nested, evaluate the expression in the innermost pair of
  grouping symbols first,

  ## Writing mathematical statements: placement of $=$

  We only write one equals sign per line. For example, if we are resolving
  parentheses:

  $$
  \begin{equation} \begin{split} 2 + \[3 + (4+5)\] = 2 + \[3 +9\] \\ = 2 + 12
  \\ = 14 \end{split} \end{equation}
  $$

  We call parentheses (`()`), brackets (`[]`) and braces `{}`) grouping symbols.
  When groupings (say parentheses) are used, the expression inside any pair of
  parentheses must be evaluated first.

raw_content:
  ---
  tags:
    - prealgebra
  ---

  # Formal conventions

  ## Grouping symbols

  We use parentheses to delimit the part of an expression we want evaluated first.
  If grouping symbols are nested, evaluate the expression in the innermost pair of
  grouping symbols first,

  ## Writing mathematical statements: placement of $=$

  We only write one equals sign per line. For example, if we are resolving
  parentheses:

  $$
  \begin{equation} \begin{split} 2 + \[3 + (4+5)\] = 2 + \[3 +9\] \\ = 2 + 12
  \\ = 14 \end{split} \end{equation}
  $$

  We call parentheses (`()`), brackets (`[]`) and braces `{}`) grouping symbols.
  When groupings (say parentheses) are used, the expression inside any pair of
  parentheses must be evaluated first.


path: Formal_proofs_in_propositional_logic.md
sortable_path: Formal_proofs_in_propositional_logic.md
title: Formal proofs in propositional logic
checksum: a122046abb81e7039c1b4c3d9a2e1ae493e2d3b61c43bbaefcfb61f1b46481eb
created: 2025-10-01 13:30:58.828067104+00:00
modified: 2025-08-13 15:57:13.934924206+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  When we construct a formal proof in logic we are seeking to show that a certain
  proposition is **derivable** from other propositions. We use the words
  _derivation_ and _proof_ interchangeably.

body:
  When we construct a formal proof in logic we are seeking to show that a certain
  proposition is **derivable** from other propositions. We use the words
  _derivation_ and _proof_ interchangeably.

  > A sentence $P$ is derivable in a system of propositional logic from a finite
  > set of sentences if and only if there is a derivation in this system in which
  > all and only the members of the set are **primary assumptions** and $P$ is the
  > sentence on the last line.

  We express the above symbolically as $\Gamma \vdash P$ . (Be careful not to
  confuse _derivable_ ($\vdash$) from _entails_ ($\vDash$).

  Derivability is therefore a property that a proposition can possess relative to
  a set.

  For instance to demonstrate derivability for:

  $$
  \{ \lnot F \lor D, F, D \rightarrow (G \land H) \} \vdash G \land H
  $$

  We would establish $\lnot F \lor D, F, D \rightarrow (G \land H)$ as primary
  assumptions and then, using the derivation rules of the system conclude with
  $G\land H$. Every sentence in the derivation is either a **primary assumption**
  or an **auxiliary** assumption or justified by the rules of the derivation.

  An auxiliary assumption is an assumption belonging to a sub-derivation. Primary
  assumptions belong to the main derivation. For any given derivation of the form
  $\Gamma \vdash P$ there may be a number of ways of demonstrating the derivation
  (more than one application of the rules governing the system) but one alone is
  sufficient to establish derivability.

  > We will tend to use the terms _derivation_ and _proof_ interchangeably but we
  > should note that there is a technical distinction in that a **proof is a
  > derivation in which all of the assumptions have been discharged**

  ## Constructing proofs

  We place assumptions above derivations and mark them _A_ in the right-hand
  margin. We use a shorthand for the derivation rules and also place these in the
  right-hand margin.

  We divide assumptions from derivations with a horizontal line. We number each
  line and use this to refer to the line we are applying the derivation to.
  Sub-proofs follow this structure recursively. This is known as _Fitch notation_

  _Schematically_:

  ![](/img/proofs-drawio-Page-5.drawio.png)

  _Applied example_:

  ![](/img/proofs-drawio-Page-6.drawio.png)

  ## Sub-proofs

  When a sub-proof is terminated, the assumption with which it starts is said to
  be _discharged_. It's conclusion can then be drawn upon and invoked within the
  main proof by reference to its start and end line number. However statements
  within the sub-proof cannot be referred to again from within the main proof,
  only its result.

  ## Derivation rules

  Derivation rules are
  [syntactic](Syntax_of_sentential_logic.md) rather
  than semantic. They are applied on the basis of their form rather than on the
  basis of the truth conditions of the sentences they are applied to.

  > Derivation rules can be applied without having an interpretation of the
  > symbols in mind. A derivation rule tells us that: given a group of symbols
  > with a certain structure, we can write down another group of symbols with a
  > certain structure.

  Each of the main logical connectives has an associated derivation rule. The
  binary connectives have pairs of rules, one for the introduction of the
  connective and one for its elimination.

  The main derivation rules:

  - [Negation Introduction](Negation_Introduction.md)
  - [Negation Elimination](Negation_Elimination.md)
  - [Conjunction Introduction](Conjunction_Introduction.md)
  - [Conjunction Elimination](Conditional_Elimination.md)
  - [Disjunction Introduction](Disjunction_Introduction.md)
  - [Disjunction Elimination](Disjunction_Elimination.md)
  - [Conditional Introduction](Conditional_Introduction.md)
  - [Disjunction Elimination](Disjunction_Elimination.md)
  - [Biconditional Introduction](Biconditional_Introduction.md)
  - [Biconditional Elimination](Biconditional_Elimination.md)

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Formal proofs in propositional logic

  When we construct a formal proof in logic we are seeking to show that a certain
  proposition is **derivable** from other propositions. We use the words
  _derivation_ and _proof_ interchangeably.

  > A sentence $P$ is derivable in a system of propositional logic from a finite
  > set of sentences if and only if there is a derivation in this system in which
  > all and only the members of the set are **primary assumptions** and $P$ is the
  > sentence on the last line.

  We express the above symbolically as $\Gamma \vdash P$ . (Be careful not to
  confuse _derivable_ ($\vdash$) from _entails_ ($\vDash$).

  Derivability is therefore a property that a proposition can possess relative to
  a set.

  For instance to demonstrate derivability for:

  $$
  \{ \lnot F \lor D, F, D \rightarrow (G \land H) \} \vdash G \land H
  $$

  We would establish $\lnot F \lor D, F, D \rightarrow (G \land H)$ as primary
  assumptions and then, using the derivation rules of the system conclude with
  $G\land H$. Every sentence in the derivation is either a **primary assumption**
  or an **auxiliary** assumption or justified by the rules of the derivation.

  An auxiliary assumption is an assumption belonging to a sub-derivation. Primary
  assumptions belong to the main derivation. For any given derivation of the form
  $\Gamma \vdash P$ there may be a number of ways of demonstrating the derivation
  (more than one application of the rules governing the system) but one alone is
  sufficient to establish derivability.

  > We will tend to use the terms _derivation_ and _proof_ interchangeably but we
  > should note that there is a technical distinction in that a **proof is a
  > derivation in which all of the assumptions have been discharged**

  ## Constructing proofs

  We place assumptions above derivations and mark them _A_ in the right-hand
  margin. We use a shorthand for the derivation rules and also place these in the
  right-hand margin.

  We divide assumptions from derivations with a horizontal line. We number each
  line and use this to refer to the line we are applying the derivation to.
  Sub-proofs follow this structure recursively. This is known as _Fitch notation_

  _Schematically_:

  ![](/img/proofs-drawio-Page-5.drawio.png)

  _Applied example_:

  ![](/img/proofs-drawio-Page-6.drawio.png)

  ## Sub-proofs

  When a sub-proof is terminated, the assumption with which it starts is said to
  be _discharged_. It's conclusion can then be drawn upon and invoked within the
  main proof by reference to its start and end line number. However statements
  within the sub-proof cannot be referred to again from within the main proof,
  only its result.

  ## Derivation rules

  Derivation rules are
  [syntactic](Syntax_of_sentential_logic.md) rather
  than semantic. They are applied on the basis of their form rather than on the
  basis of the truth conditions of the sentences they are applied to.

  > Derivation rules can be applied without having an interpretation of the
  > symbols in mind. A derivation rule tells us that: given a group of symbols
  > with a certain structure, we can write down another group of symbols with a
  > certain structure.

  Each of the main logical connectives has an associated derivation rule. The
  binary connectives have pairs of rules, one for the introduction of the
  connective and one for its elimination.

  The main derivation rules:

  - [Negation Introduction](Negation_Introduction.md)
  - [Negation Elimination](Negation_Elimination.md)
  - [Conjunction Introduction](Conjunction_Introduction.md)
  - [Conjunction Elimination](Conditional_Elimination.md)
  - [Disjunction Introduction](Disjunction_Introduction.md)
  - [Disjunction Elimination](Disjunction_Elimination.md)
  - [Conditional Introduction](Conditional_Introduction.md)
  - [Disjunction Elimination](Disjunction_Elimination.md)
  - [Biconditional Introduction](Biconditional_Introduction.md)
  - [Biconditional Elimination](Biconditional_Elimination.md)


path: Format_specifiers_in_C.md
sortable_path: Format_specifiers_in_C.md
title: Format specifiers in C
checksum: 23d38cde9eb660d8275d3c932f35918e2587ba0e8565d75e0d687184fab4344c
created: 2025-10-01 13:30:58.865353859+00:00
modified: 2025-08-13 15:57:13.789810491+00:00
metadata: {"created":"Thursday, February 29, 2024 | 17:41","id":"5faz2y2e","tags":["C"]}
lead:
  Format specifiers define the type of data to be printed on standard output. You
  need to use format specifiers whenever you are
  [printing-values-in-c](printing-values-in-c.md).

body:
  Format specifiers define the type of data to be printed on standard output. You
  need to use format specifiers whenever you are
  [printing-values-in-c](printing-values-in-c.md).

  | Specifier | Usage                      |
  | --------- | -------------------------- |
  | `%c`      | single character           |
  | `%s`      | string                     |
  | `%n`      | nothing                    |
  | `%d`      | integer (assuming base 10) |
  | `%f`      | float                      |
  | `%%`      | The percent sign itself    |

raw_content:
  ---
  id: 5faz2y2e
  tags:
    - C
  created: Thursday, February 29, 2024 | 17:41
  ---

  # Format specifiers in C

  Format specifiers define the type of data to be printed on standard output. You
  need to use format specifiers whenever you are
  [printing-values-in-c](printing-values-in-c.md).

  | Specifier | Usage                      |
  | --------- | -------------------------- |
  | `%c`      | single character           |
  | `%s`      | string                     |
  | `%n`      | nothing                    |
  | `%d`      | integer (assuming base 10) |
  | `%f`      | float                      |
  | `%%`      | The percent sign itself    |


path: Formatting_output_text_in_Bash.md
sortable_path: Formatting_output_text_in_Bash.md
title: Formatting output text in Bash
checksum: fac1b5b4deee65d0a974c706274a2198a56bd3dfc53a818655e33e362e7fff6d
created: 2025-10-01 13:30:58.872778743+00:00
modified: 2025-08-13 15:57:14.0399646+00:00
metadata: {"tags":["shell"]}
lead:
  We can use the `-e` flag with `echo` to have greater control over the output of
  text. `-e` allows us to use linebreaks, tabs and other formattings within a
  string.

body:
  We can use the `-e` flag with `echo` to have greater control over the output of
  text. `-e` allows us to use linebreaks, tabs and other formattings within a
  string.

  ## Output text in columns

  ```bash
  echo -e  "Name\t\tNumber"; echo -e "Thomas\t\t123";

  Name		Number
  Thomas		123
  ```

  ## Break text over several lines

  ```bash
  echo -e "This text\nbreaks over\nthree lines"

  This text
  breaks over
  three lines
  ```

  ## Colour outputs

  ![](/img/terminal_colours.png)

  ```bash
  echo -e "\033[31;40mColoured Text\033[0m"
  ```

  The coloured section is prepended by `\033[` and ended with `[0m`. The
  foreground colour is given first, and then the background colour.

  We can also change the text style. We do this by adding an extra value after the
  first square bracket:

  ```bash
  echo -e "\033[4;31;40mColoured Text\033[0m"
  ```

  This underlines the output.

  We can create a script that simplifies the construction of colour-formatted
  text, e.g

  ```bash
  ulinered="\033[4;31;40m"
  red="\033[31;40m"
  none="\033[0m"

  echo -e $ulinered"ERROR:"$none$red" Something went wrong."$none
  ```

raw_content:
  ---
  tags:
    - shell
  ---

  # Formatting output text in Bash

  We can use the `-e` flag with `echo` to have greater control over the output of
  text. `-e` allows us to use linebreaks, tabs and other formattings within a
  string.

  ## Output text in columns

  ```bash
  echo -e  "Name\t\tNumber"; echo -e "Thomas\t\t123";

  Name		Number
  Thomas		123
  ```

  ## Break text over several lines

  ```bash
  echo -e "This text\nbreaks over\nthree lines"

  This text
  breaks over
  three lines
  ```

  ## Colour outputs

  ![](/img/terminal_colours.png)

  ```bash
  echo -e "\033[31;40mColoured Text\033[0m"
  ```

  The coloured section is prepended by `\033[` and ended with `[0m`. The
  foreground colour is given first, and then the background colour.

  We can also change the text style. We do this by adding an extra value after the
  first square bracket:

  ```bash
  echo -e "\033[4;31;40mColoured Text\033[0m"
  ```

  This underlines the output.

  We can create a script that simplifies the construction of colour-formatted
  text, e.g

  ```bash
  ulinered="\033[4;31;40m"
  red="\033[31;40m"
  none="\033[0m"

  echo -e $ulinered"ERROR:"$none$red" Something went wrong."$none
  ```


path: Forms_in_React.md
sortable_path: Forms_in_React.md
title: Forms using hooks
checksum: cc03c143d64a475e9141718c7a5ba8853cc79336b8e8bfb2aec36cfac5776c82
created: 2025-10-01 13:30:58.87574306+00:00
modified: 2025-08-13 15:57:13.974778987+00:00
metadata: {"tags":["javascript","react"]}
lead:
  With hooks, form processing is exactly the same as
  [classes](Forms_in_React.md) in terms of the overall
  methodology, but the syntax is slightly different as a result of the `useState`
  hook.

body:
  With hooks, form processing is exactly the same as
  [classes](Forms_in_React.md) in terms of the overall
  methodology, but the syntax is slightly different as a result of the `useState`
  hook.

  >

  ## Basic approach

  Instead of using `this.state` and `this.setState` . We just have the `useState`
  hook. But the controlled component principle is the same. Let's say we have a
  simple email input:

  ```jsx
  const [email, setEmail] = useState("");
  ```

  As this is a form, the state change is going to be the result of user input. So
  we need to prep our form to enable this.

  ```html
  <input type="text" value="{email}" onChange="{setEmail}" />
  ```

  Now we just need to make good on the `setEmail` method we declared when we
  initialised the state:

  ```jsx
  const handleChange = (event) => {
    setEmail(event.target.value);
  };
  ```

  ### Applied example

  Here is an applied example of the above approach for a form that has three input
  fields. This component outputs the inputs as JSON on submit:

  ```jsx

  function FormHook() {
    const [email, setEmail] = useState("");
    const [phone, setPhone] = useState("");
    const [age, setAge] = useState("");
    const [formOutput, setFormOutput] = useState("Form output");

    const handleSubmit = (event) => {
      event.preventDefault();
      setFormOutput(
        JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
      );
    };
  	return (
  		<form onSubmit={handleSubmit}>
  			<input type="text" value={email} onChange={(event) => setEmail(event.target.value)}>
  			<input type="text" value={phone} onChange={(event) => setPhone(event.target.value)}>
  			<input type="number" value={age} onChange={(event) => setAge(event.target.value)}>
  			<button type="submit">Submit</button>
  		</form>
  	)
  };
  ```

  ## More complex forms

  The above is fine if you only have one form with a couple of inputs. But if you
  are managing multiple forms or forms with a complex array of inputs, you would
  need to create `useState` declaration for every single input with a custom
  `onChange` event for each one which is repetitious and not very clean.

  So instead of this, just like with class-based controlled components, we use the
  `name` HTML attribute to distinguish each input and create a generic `onChange`
  function that distinguishes each separate input by destructuring a key, value
  object using the `name`.

  ```jsx
  <form onSubmit={handleSubmit}>
  			<input type="text" name="email" value={formValues.email} onChange={handleChange}>
  			<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
  			<input type="number" name="age" value={formValues.age} onChange={handleChange}>
  			<button type="submit">Submit</button>
  </form>
  ```

  ```jsx
  const initialState = {
    email: "",
    phone: "",
    age: "",
  };

  const [formValues, setFormValues] = useState(initialState);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormValues({ ...formValues, [name]: value });
  };
  ```

  There are three parts:

  1. First we create the initial state.
  2. Next we store this initial state as the variable in the `useState`
     initialisation: `formValues` . We also provide a method `setFormValues` which
     will be used by the change handler to log the user's inputs.
  3. Finally we create the function that will log the user changes. First we use
     object destructuring on the change event to enable us to retrieve the `name`
     and `value` attributes of the HTML inputs in the component. Then we use
     spread syntax to say that for each input pair, retrieve its value, using the
     destructured `name` variable as the key.

  ### Applied example

  Below I have updated the previous context to this time reflect the new,
  abstracted logic:

  ```jsx
  function FormHookAbstracted() {
    const initialState = {
      email: "",
      phone: "",
      age: "",
    };
  	const [formValues, setFormValues] = useState(initialState);
  	const handleChange = (event) => {
      const { name, value } = event.target;
      setFormValues({ ...formValues, [name]: value });
    };
    const handleSubmit = (event) => {
      event.preventDefault();
      setFormOutput(
        JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
      );
    };
  	return (
  		<form onSubmit={handleSubmit}>
  			<input type="text" name="email" value={formValues.email} onChange={handleChange}>
  			<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
  			<input type="number" name="age" value={formValues.age} onChange={handleChange}>
  			<button type="submit">Submit</button>
  		</form>
  	)
  };
  export default FormHookAbstracted;
  ```

  Note that instead of individual variables `email` , `phone`, `age` , this
  approach returns a single object `formValues` . We could therefore access the
  individual values with e.g `[formValues.email](formvalues.email)` .

  As it is an object, it makes resetting to the original state very easy, viz:

  ```jsx
  const handleReset = () => {
    Object.values(formValues).map((x) => setFormValues(initialState));
  };
  ```

raw_content:
  ---
  tags:
    - javascript
    - react
  ---

  # Forms using hooks

  With hooks, form processing is exactly the same as
  [classes](Forms_in_React.md) in terms of the overall
  methodology, but the syntax is slightly different as a result of the `useState`
  hook.

  >

  ## Basic approach

  Instead of using `this.state` and `this.setState` . We just have the `useState`
  hook. But the controlled component principle is the same. Let's say we have a
  simple email input:

  ```jsx
  const [email, setEmail] = useState("");
  ```

  As this is a form, the state change is going to be the result of user input. So
  we need to prep our form to enable this.

  ```html
  <input type="text" value="{email}" onChange="{setEmail}" />
  ```

  Now we just need to make good on the `setEmail` method we declared when we
  initialised the state:

  ```jsx
  const handleChange = (event) => {
    setEmail(event.target.value);
  };
  ```

  ### Applied example

  Here is an applied example of the above approach for a form that has three input
  fields. This component outputs the inputs as JSON on submit:

  ```jsx

  function FormHook() {
    const [email, setEmail] = useState("");
    const [phone, setPhone] = useState("");
    const [age, setAge] = useState("");
    const [formOutput, setFormOutput] = useState("Form output");

    const handleSubmit = (event) => {
      event.preventDefault();
      setFormOutput(
        JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
      );
    };
  	return (
  		<form onSubmit={handleSubmit}>
  			<input type="text" value={email} onChange={(event) => setEmail(event.target.value)}>
  			<input type="text" value={phone} onChange={(event) => setPhone(event.target.value)}>
  			<input type="number" value={age} onChange={(event) => setAge(event.target.value)}>
  			<button type="submit">Submit</button>
  		</form>
  	)
  };
  ```

  ## More complex forms

  The above is fine if you only have one form with a couple of inputs. But if you
  are managing multiple forms or forms with a complex array of inputs, you would
  need to create `useState` declaration for every single input with a custom
  `onChange` event for each one which is repetitious and not very clean.

  So instead of this, just like with class-based controlled components, we use the
  `name` HTML attribute to distinguish each input and create a generic `onChange`
  function that distinguishes each separate input by destructuring a key, value
  object using the `name`.

  ```jsx
  <form onSubmit={handleSubmit}>
  			<input type="text" name="email" value={formValues.email} onChange={handleChange}>
  			<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
  			<input type="number" name="age" value={formValues.age} onChange={handleChange}>
  			<button type="submit">Submit</button>
  </form>
  ```

  ```jsx
  const initialState = {
    email: "",
    phone: "",
    age: "",
  };

  const [formValues, setFormValues] = useState(initialState);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormValues({ ...formValues, [name]: value });
  };
  ```

  There are three parts:

  1. First we create the initial state.
  2. Next we store this initial state as the variable in the `useState`
     initialisation: `formValues` . We also provide a method `setFormValues` which
     will be used by the change handler to log the user's inputs.
  3. Finally we create the function that will log the user changes. First we use
     object destructuring on the change event to enable us to retrieve the `name`
     and `value` attributes of the HTML inputs in the component. Then we use
     spread syntax to say that for each input pair, retrieve its value, using the
     destructured `name` variable as the key.

  ### Applied example

  Below I have updated the previous context to this time reflect the new,
  abstracted logic:

  ```jsx
  function FormHookAbstracted() {
    const initialState = {
      email: "",
      phone: "",
      age: "",
    };
  	const [formValues, setFormValues] = useState(initialState);
  	const handleChange = (event) => {
      const { name, value } = event.target;
      setFormValues({ ...formValues, [name]: value });
    };
    const handleSubmit = (event) => {
      event.preventDefault();
      setFormOutput(
        JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
      );
    };
  	return (
  		<form onSubmit={handleSubmit}>
  			<input type="text" name="email" value={formValues.email} onChange={handleChange}>
  			<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
  			<input type="number" name="age" value={formValues.age} onChange={handleChange}>
  			<button type="submit">Submit</button>
  		</form>
  	)
  };
  export default FormHookAbstracted;
  ```

  Note that instead of individual variables `email` , `phone`, `age` , this
  approach returns a single object `formValues` . We could therefore access the
  individual values with e.g `[formValues.email](formvalues.email)` .

  As it is an object, it makes resetting to the original state very easy, viz:

  ```jsx
  const handleReset = () => {
    Object.values(formValues).map((x) => setFormValues(initialState));
  };
  ```


path: Four_bit_adder.md
sortable_path: Four_bit_adder.md
title: Four-bit adder
checksum: a803c519509da2a87666647933e8f7817c59adbc65482c5dde208fb806b5bcd7
created: 2025-10-01 13:30:58.8809084+00:00
modified: 2025-08-13 15:57:13.964796423+00:00
metadata: {"tags":["logic-gates","binary"]}
lead:
  A single
  [half adder](Half_adder_and_full_adder.md#half-adder)
  and
  [full adder](Half_adder_and_full_adder.md#fufll-adder)
  allows us to calculate the sum of two 1-bit numbers, but this is not much use in
  practice. To approximate what is really happening at the circuit level in
  computers we need to be able to add bigger binary numbers. We will demonstrate
  how this can be achieved for a four-bit number (nibble) using repeated full
  adders and half adders.

body:
  A single
  [half adder](Half_adder_and_full_adder.md#half-adder)
  and
  [full adder](Half_adder_and_full_adder.md#fufll-adder)
  allows us to calculate the sum of two 1-bit numbers, but this is not much use in
  practice. To approximate what is really happening at the circuit level in
  computers we need to be able to add bigger binary numbers. We will demonstrate
  how this can be achieved for a four-bit number (nibble) using repeated full
  adders and half adders.

  We want to be able to calculate the following sum:

  ```
  0010
  0011
  ____
  0101
  ```

  We will achieve this by using three full adders and one half adder, moving from
  right to left. The half adder will be used at the beginning to calculate the
  least significant bit as it will have no carry-in. The subsequent three bits
  will all be added using a full adder.

  Let's walk through the process:

  >

  1. HA receives the bits $0$ and $1$ as inputs. It outputs $1$ as the sum bit and
     $0$ as the carry-out.
  2. FA1 receives $0$ as the carry-in bit plus $1$ and $1$ as its input. This
     means it has the following calculation to execute: $1 + 1 + 0$. This gives
     $0$ as the sum bit and $1$ as the carry-out bit.
  3. FA2 receives $1$ as the carry-in (the carry-out from FA1) plus $0$ and $0$ as
     its own inputs. $0 + 0 + 1$ gives us $1$ as the sum bit and $0$ as the
     carry-out.
  4. Finally FA3 receives $0$ as its carry-in plus $0$ and $0$ as its inputs.
     $0 + 0 + 0 = 0$, therefore its sum bit is $0$ and the overall sum of the four
     bits is $0101$. There is no carry out as there are no more bits to add.

  ## Ripple carry adder

  We notice that as the calculation proceeds, the carry bits propagate or _ripple_
  through the circuit. For this reason, a four-bit adder constructed in the manner
  of this circuit is called a **ripple carry adder**.

  There are two important consequences to note about this type of circuit:

  - Each carry bit that ripples to the next full adder introduces a small delay.
    Therefore extending the circuit to handle more bits will make the circuit
    slower overall.
  - Because the value of the bits changes through the course of the ripple, the
    output of the circuit will be innacurate until all the carry bits have had
    time to propagate.

raw_content:
  ---
  tags:
    - logic-gates
    - binary
  ---

  # Four-bit adder

  A single
  [half adder](Half_adder_and_full_adder.md#half-adder)
  and
  [full adder](Half_adder_and_full_adder.md#fufll-adder)
  allows us to calculate the sum of two 1-bit numbers, but this is not much use in
  practice. To approximate what is really happening at the circuit level in
  computers we need to be able to add bigger binary numbers. We will demonstrate
  how this can be achieved for a four-bit number (nibble) using repeated full
  adders and half adders.

  We want to be able to calculate the following sum:

  ```
  0010
  0011
  ____
  0101
  ```

  We will achieve this by using three full adders and one half adder, moving from
  right to left. The half adder will be used at the beginning to calculate the
  least significant bit as it will have no carry-in. The subsequent three bits
  will all be added using a full adder.

  Let's walk through the process:

  >

  1. HA receives the bits $0$ and $1$ as inputs. It outputs $1$ as the sum bit and
     $0$ as the carry-out.
  2. FA1 receives $0$ as the carry-in bit plus $1$ and $1$ as its input. This
     means it has the following calculation to execute: $1 + 1 + 0$. This gives
     $0$ as the sum bit and $1$ as the carry-out bit.
  3. FA2 receives $1$ as the carry-in (the carry-out from FA1) plus $0$ and $0$ as
     its own inputs. $0 + 0 + 1$ gives us $1$ as the sum bit and $0$ as the
     carry-out.
  4. Finally FA3 receives $0$ as its carry-in plus $0$ and $0$ as its inputs.
     $0 + 0 + 0 = 0$, therefore its sum bit is $0$ and the overall sum of the four
     bits is $0101$. There is no carry out as there are no more bits to add.

  ## Ripple carry adder

  We notice that as the calculation proceeds, the carry bits propagate or _ripple_
  through the circuit. For this reason, a four-bit adder constructed in the manner
  of this circuit is called a **ripple carry adder**.

  There are two important consequences to note about this type of circuit:

  - Each carry bit that ripples to the next full adder introduces a small delay.
    Therefore extending the circuit to handle more bits will make the circuit
    slower overall.
  - Because the value of the bits changes through the course of the ripple, the
    output of the circuit will be innacurate until all the carry bits have had
    time to propagate.


path: Frequency_counters.md
sortable_path: Frequency_counters.md
title: Frequency counters
lead:
  Here is a concise method for counting the number instances of each type of array
  element.

checksum: 2cf0f15b006fadb1c49fd3ea77798d83b846acd0594cf66aedc372f533b3edf2
created: 2025-10-01 13:30:58.894591552+00:00
modified: 2025-08-13 15:57:13.787950893+00:00
metadata: {"tags":["data-structures"]}
body:
  Here is a concise method for counting the number instances of each type of array
  element.

  ```js
  function frequencyCounter(...inputArr) {
    let counter = {};
    inputArr.forEach((item) => {
      // If item doesn't exist as a key in the counter, make 0 the val and add one
      // If item already exists as key in the counter, increment it by one
      counter[item] = (counter[item] || 0) + 1;
    });
    return counter;
  }
  ```

  An application of this would be checking for duplicates in an array:

  ```js
  const testArr = [1, 4, 3, 3];
  const arrayFreq = frequencyCounter(testArr);
  let count = Object.values(arrayFreq);

  if (count.some((ele) => ele > 1)) {
    return "There are duplicates";
  } else return "No duplicates";
  ```

raw_content:
  ---
  tags:
    - data-structures
  ---

  # Frequency counters

  Here is a concise method for counting the number instances of each type of array
  element.

  ```js
  function frequencyCounter(...inputArr) {
    let counter = {};
    inputArr.forEach((item) => {
      // If item doesn't exist as a key in the counter, make 0 the val and add one
      // If item already exists as key in the counter, increment it by one
      counter[item] = (counter[item] || 0) + 1;
    });
    return counter;
  }
  ```

  An application of this would be checking for duplicates in an array:

  ```js
  const testArr = [1, 4, 3, 3];
  const arrayFreq = frequencyCounter(testArr);
  let count = Object.values(arrayFreq);

  if (count.some((ele) => ele > 1)) {
    return "There are duplicates";
  } else return "No duplicates";
  ```


path: From_ARPANET_to_Internet.md
sortable_path: From_ARPANET_to_Internet.md
title: 4da22434_from_ARPANET_to_Internet
lead:
  By the mid-1970s many additional computer networks had emerged which also used
  #packet-switching.

checksum: 7e52f41b102cdf98731fa8105feb6c0d0d6384401194b8b0264211a08f8959b2
created: 2025-10-01 13:30:58.897781483+00:00
modified: 2025-08-13 15:57:13.779490745+00:00
metadata: {"created":"Sunday, October 27, 2024","tags":["internet","ARPANET"]}
body:
  By the mid-1970s many additional computer networks had emerged which also used
  #packet-switching.

  It was the success of ARPANET as a "proof of concept" for packet-switching that
  led to these additional networks being created in other countries and contexts.
  Some used different modes of transmission than the #ARPANET's phone-wire based
  system such as radio waves and satellites. However this created a problem: these
  networks were siloed from each other and a bridge was needed to connect them.

  Those working on the ARPANET steering committee (the Information Processing
  Techniques Office (IPTO) within ARPA/DARPA) began to discuss what they called a
  "concatenated network" or "CATENET" that would connect these different networks
  together. Thus emerged the idea of a _network of networks_ or _inter-network_.

  For this to work it would be necessary to construct "gateways": routing
  computers that would hand-off messages from one network to another.

  This required a new protocol because the Network Control Protocol was only
  designed to work _within_ the ARPANET with its IMPs. Unlike the IMPs, the
  gateways would not be responsible for reconstituting packets or ensuring
  reliability, this would be shifted to the hosts. The protocol was the
  [Transmission Control Protocol/Internet Protocol](Internet_Layer_of_Internet_Protocol.md).
  TCP would be responsible for packet assembly and disassembly and IP for routing.

  The "common language" of TCP/IP and the growth in networks in addition to the
  ARPANET is what formed the Internet. Different commercial groups and research
  enterprises inaugurated their own networks and, one-by-one, they adopted TCP and
  became accessible to each other.

  As the ARPANET was retired, its individual sites either became nodes within the
  Internet or they split off into sub-networks such as MILNET (which was part of
  the ARPANET retained for classified military communication). The IMPs were
  retired in favour of TCP/IP routers.

  _The ARPANET in 1973_

  ![](../img/Arpanet_map_1973.jpg)

  _The ARPANET as part of an inter-network 1977_

  ![](../img/internetworking_1977.jpg)

  _The ARPANET in 1977_

  ![](../img/Arpanet_logical_map,_march_1977.png)

  _The Internet in 1984_

  ![](../img/internet_1984.jpg)

raw_content:
  ---
  tags: [internet, ARPANET]
  created: Sunday, October 27, 2024
  ---

  # 4da22434_from_ARPANET_to_Internet

  By the mid-1970s many additional computer networks had emerged which also used
  #packet-switching.

  It was the success of ARPANET as a "proof of concept" for packet-switching that
  led to these additional networks being created in other countries and contexts.
  Some used different modes of transmission than the #ARPANET's phone-wire based
  system such as radio waves and satellites. However this created a problem: these
  networks were siloed from each other and a bridge was needed to connect them.

  Those working on the ARPANET steering committee (the Information Processing
  Techniques Office (IPTO) within ARPA/DARPA) began to discuss what they called a
  "concatenated network" or "CATENET" that would connect these different networks
  together. Thus emerged the idea of a _network of networks_ or _inter-network_.

  For this to work it would be necessary to construct "gateways": routing
  computers that would hand-off messages from one network to another.

  This required a new protocol because the Network Control Protocol was only
  designed to work _within_ the ARPANET with its IMPs. Unlike the IMPs, the
  gateways would not be responsible for reconstituting packets or ensuring
  reliability, this would be shifted to the hosts. The protocol was the
  [Transmission Control Protocol/Internet Protocol](Internet_Layer_of_Internet_Protocol.md).
  TCP would be responsible for packet assembly and disassembly and IP for routing.

  The "common language" of TCP/IP and the growth in networks in addition to the
  ARPANET is what formed the Internet. Different commercial groups and research
  enterprises inaugurated their own networks and, one-by-one, they adopted TCP and
  became accessible to each other.

  As the ARPANET was retired, its individual sites either became nodes within the
  Internet or they split off into sub-networks such as MILNET (which was part of
  the ARPANET retained for classified military communication). The IMPs were
  retired in favour of TCP/IP routers.

  _The ARPANET in 1973_

  ![](../img/Arpanet_map_1973.jpg)

  _The ARPANET as part of an inter-network 1977_

  ![](../img/internetworking_1977.jpg)

  _The ARPANET in 1977_

  ![](../img/Arpanet_logical_map,_march_1977.png)

  _The Internet in 1984_

  ![](../img/internet_1984.jpg)


path: From_free_software_to_open_source.md
sortable_path: From_free_software_to_open_source.md
title: d212acdb_from_free-software_to_open_source
checksum: 0368b86ffeff2b8dcb00806f86aea6a1d910c7356d218a7f5af2dbac9eb89344
created: 2025-10-01 13:30:58.906719304+00:00
modified: 2025-08-13 15:57:13.805609907+00:00
metadata: {"created":"Thursday, March 27, 2025","tags":["history","ecopolsoc","FOSS"]}
lead:
  For the decade or so after Stallman's "Initial Announcement" of GNU in 1983,
  free software and the philosophy behind it remained the preserve of the
  technical underground. During these years, the phrase "open source" did not
  exist.

body:
  For the decade or so after Stallman's "Initial Announcement" of GNU in 1983,
  free software and the philosophy behind it remained the preserve of the
  technical underground. During these years, the phrase "open source" did not
  exist.

  Many engineers were using and contributing to free software in their free-time
  and surreptitiously using it in the corporate context but there was little
  awareness of it amongst the business class. If they did know about GNU-Linux
  they did not see it as a viable alternative to proprietary software, due to the
  lack of licensing, customer support and the misapprehension that the product
  would be inferior.

  The turning point came when
  [Netscape](./c301a0b3-1d8_Mosaic_Netscape_and_Browser_Wars.md), in a last ditch
  effort to resist Microsoft's obliteration of their market, made their
  source-code open. This caught the attention of the corporate class and the
  additional media attention led to a greater interest amongst capitalists
  generally.

  They quickly surmised that this could be an avenue of profit maximisation:
  free-labour, no licensing costs etc. As part of this process, free software was
  rebranded as "open source", all the better to calm investors' worries about its
  socialistic flavour having misread "free" as meaning "for no cost".

  Interestingly Microsoft publicly disparaged Linux and free software generally in
  public but an employee leaked internal memoranda which showed them to be most
  concerned. They led a vigorous PR campaign of disinformation about free
  software, tying it to the emerging phenomenon of "illegal file sharing". This
  was propitious timing, as it coincided with the passing of the DMCA (Digital
  Millennium Copyright Act) in the United States which introduced punitive
  punishment for those caught distributing proprietary media and software.

  So this is really the origin of hackers' antipathy to Microsoft and it's
  interesting to reflect on this today where Microsoft has very effectively
  subsumed "open source" into itself, realising it couldn't beat it on its own
  terms. Most notably with their acquisition of GitHub.

  ## Source

  Gabriella Coleman, _Coding Freedom: The Ethics and Aesthetics of Hacking_ (2013)

raw_content:
  ---
  tags: [history, ecopolsoc, FOSS]
  created: Thursday, March 27, 2025
  ---

  # d212acdb_from_free-software_to_open_source

  For the decade or so after Stallman's "Initial Announcement" of GNU in 1983,
  free software and the philosophy behind it remained the preserve of the
  technical underground. During these years, the phrase "open source" did not
  exist.

  Many engineers were using and contributing to free software in their free-time
  and surreptitiously using it in the corporate context but there was little
  awareness of it amongst the business class. If they did know about GNU-Linux
  they did not see it as a viable alternative to proprietary software, due to the
  lack of licensing, customer support and the misapprehension that the product
  would be inferior.

  The turning point came when
  [Netscape](./c301a0b3-1d8_Mosaic_Netscape_and_Browser_Wars.md), in a last ditch
  effort to resist Microsoft's obliteration of their market, made their
  source-code open. This caught the attention of the corporate class and the
  additional media attention led to a greater interest amongst capitalists
  generally.

  They quickly surmised that this could be an avenue of profit maximisation:
  free-labour, no licensing costs etc. As part of this process, free software was
  rebranded as "open source", all the better to calm investors' worries about its
  socialistic flavour having misread "free" as meaning "for no cost".

  Interestingly Microsoft publicly disparaged Linux and free software generally in
  public but an employee leaked internal memoranda which showed them to be most
  concerned. They led a vigorous PR campaign of disinformation about free
  software, tying it to the emerging phenomenon of "illegal file sharing". This
  was propitious timing, as it coincided with the passing of the DMCA (Digital
  Millennium Copyright Act) in the United States which introduced punitive
  punishment for those caught distributing proprietary media and software.

  So this is really the origin of hackers' antipathy to Microsoft and it's
  interesting to reflect on this today where Microsoft has very effectively
  subsumed "open source" into itself, realising it couldn't beat it on its own
  terms. Most notably with their acquisition of GitHub.

  ## Source

  Gabriella Coleman, _Coding Freedom: The Ethics and Aesthetics of Hacking_ (2013)


path: Full_Node_API_example.md
sortable_path: Full_Node_API_example.md
title: Creating a RESTful API: Full example
checksum: c680971e4773cff184aed8b4d873cd6ee883270aac85577a1361b8d44a705755
created: 2025-10-01 13:30:58.916568818+00:00
modified: 2025-08-13 15:57:14.03288707+00:00
metadata: {"tags":["node-js","REST","APIs"]}
lead:
  ```js
  const express = require("express");
  const app = express(); // convention to name Express as the app
  const port = process.env.PORT || 3000;
  const Joi = require("joi");
  const helmet = require("helmet");
  const morgan = require("morgan");
  const courses = require("./routes/courses");

body:
  ```js
  const express = require("express");
  const app = express(); // convention to name Express as the app
  const port = process.env.PORT || 3000;
  const Joi = require("joi");
  const helmet = require("helmet");
  const morgan = require("morgan");
  const courses = require("./routes/courses");

  // Routes
  app.use("/api/courses", courses);

  // Middlewear
  app.use(express.json());
  app.use(helmet());

  const courses = [
    {
      id: 1,
      name: "First course",
    },
    {
      id: 2,
      name: "Second course",
    },
    {
      id: 3,
      name: "Third course",
    },
  ];

  if (app.get("env") === "development") {
    app.use(morgan("common"));
  }

  app.listen(port, () => console.log(`Listening on ${port}`));

  function validateCourse(course) {
    const schema = Joi.object({
      name: Joi.string().min(3).required(),
    });

    const { error } = schema.validate(course);
    return error;
  }

  /**
   *  Note that the following request handlers would not be stored in `index.js` they would be in their dedicated routing file.
   **/

  // Return all data from API
  courses.get("/", (req, res) => {
    res.send(courses);
  });

  // Return a specific value
  courses.get("/:id", (req, res) => {
    const course = courses.find((c) => c.id === parseInt(req.params.id));
    if (!course) res.status(404).send("A course with the given ID was not found");
    res.send(course);
  });

  // Add a new course
  courses.post("/", (req, res) => {
    const schema = Joi.object({
      name: Joi.string().min(3).required(),
    });

    const { error } = schema.validate(req.body);
    if (error)
      return error.details.map((joiErr) => res.status(400).send(joiErr.message));

    const course = {
      id: courses.length + 1,
      name: req?.body.name,
    };
    courses.push(course);
    res.send(course);
  });

  // Update a course
  courses.put("/:id", (req, res) => {
    const course = courses.find((c) => c.id === parseInt(req.params.id));

    if (!course)
      return res.status(404).send("A course with the given ID was not found");
    const { error } = validateCourse(req.body);

    if (error)
      return error.details.map((joiErr) => res.status(400).send(joiErr.message));

    course.name = req.body.name;
    res.send(course);
  });

  // Delete a course
  courses.delete("/:id", (req, res) => {
    const course = courses.find((c) => c.id === parseInt(req.params.id));
    if (!course)
      return res.status(404).send("A course with the given ID was not found");

    courses.indexOf(course);
    courses.splice(index, 1);

    res.send(course);
  });
  ```

raw_content:
  ---
  tags:
    - node-js
    - REST
    - APIs
  ---

  # Creating a RESTful API: Full example

  ```js
  const express = require("express");
  const app = express(); // convention to name Express as the app
  const port = process.env.PORT || 3000;
  const Joi = require("joi");
  const helmet = require("helmet");
  const morgan = require("morgan");
  const courses = require("./routes/courses");

  // Routes
  app.use("/api/courses", courses);

  // Middlewear
  app.use(express.json());
  app.use(helmet());

  const courses = [
    {
      id: 1,
      name: "First course",
    },
    {
      id: 2,
      name: "Second course",
    },
    {
      id: 3,
      name: "Third course",
    },
  ];

  if (app.get("env") === "development") {
    app.use(morgan("common"));
  }

  app.listen(port, () => console.log(`Listening on ${port}`));

  function validateCourse(course) {
    const schema = Joi.object({
      name: Joi.string().min(3).required(),
    });

    const { error } = schema.validate(course);
    return error;
  }

  /**
   *  Note that the following request handlers would not be stored in `index.js` they would be in their dedicated routing file.
   **/

  // Return all data from API
  courses.get("/", (req, res) => {
    res.send(courses);
  });

  // Return a specific value
  courses.get("/:id", (req, res) => {
    const course = courses.find((c) => c.id === parseInt(req.params.id));
    if (!course) res.status(404).send("A course with the given ID was not found");
    res.send(course);
  });

  // Add a new course
  courses.post("/", (req, res) => {
    const schema = Joi.object({
      name: Joi.string().min(3).required(),
    });

    const { error } = schema.validate(req.body);
    if (error)
      return error.details.map((joiErr) => res.status(400).send(joiErr.message));

    const course = {
      id: courses.length + 1,
      name: req?.body.name,
    };
    courses.push(course);
    res.send(course);
  });

  // Update a course
  courses.put("/:id", (req, res) => {
    const course = courses.find((c) => c.id === parseInt(req.params.id));

    if (!course)
      return res.status(404).send("A course with the given ID was not found");
    const { error } = validateCourse(req.body);

    if (error)
      return error.details.map((joiErr) => res.status(400).send(joiErr.message));

    course.name = req.body.name;
    res.send(course);
  });

  // Delete a course
  courses.delete("/:id", (req, res) => {
    const course = courses.find((c) => c.id === parseInt(req.params.id));
    if (!course)
      return res.status(404).send("A course with the given ID was not found");

    courses.indexOf(course);
    courses.splice(index, 1);

    res.send(course);
  });
  ```


path: Function_overloads.md
sortable_path: Function_overloads.md
title: Function overloads
checksum: 415bd2066b1c9503246da7a8771ee4ad0b2417e36b28bac5a4ff3ddf1b95fcb9
created: 2025-10-01 13:30:58.919189473+00:00
modified: 2025-08-13 15:57:13.782416212+00:00
metadata: {"tags":["typescript"]}
lead:
  Function overloading is not a feature of JavaScript but something close to it
  can be achieved with TypeScript. It proceeds by defining multiple function types
  (defined above the function) that may serve as the actual function's parameters.
  Then with the actual function, you leave the changeable parameters open as
  optional unions and/or `unknown` :

body:
  Function overloading is not a feature of JavaScript but something close to it
  can be achieved with TypeScript. It proceeds by defining multiple function types
  (defined above the function) that may serve as the actual function's parameters.
  Then with the actual function, you leave the changeable parameters open as
  optional unions and/or `unknown` :

  ```ts
  // First oveload type:
  function logSearch(term: string, options?: string): void;

  // Second overload type:
  function logSearch(term: string, options?: number): void;

  // Implementation:
  function logSearch(term: string, p2?: unknown) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearch("apples", "braeburn");
  logSearch("bananas", 3);
  ```

  ```ts
  // First overload type:
  function logSearchUnion(term: string, options?: string): void;

  // Second overload type:
  function logSearchUnion(term: string, options?: number): void;

  // Implementation:
  function logSearchUnion(term: string, p2?: string | number) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearchUnion("melon", "honey-dew");
  logSearchUnion("oranges", 4);
  ```

raw_content:
  ---
  tags:
    - typescript
  ---

  # Function overloads

  Function overloading is not a feature of JavaScript but something close to it
  can be achieved with TypeScript. It proceeds by defining multiple function types
  (defined above the function) that may serve as the actual function's parameters.
  Then with the actual function, you leave the changeable parameters open as
  optional unions and/or `unknown` :

  ```ts
  // First oveload type:
  function logSearch(term: string, options?: string): void;

  // Second overload type:
  function logSearch(term: string, options?: number): void;

  // Implementation:
  function logSearch(term: string, p2?: unknown) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearch("apples", "braeburn");
  logSearch("bananas", 3);
  ```

  ```ts
  // First overload type:
  function logSearchUnion(term: string, options?: string): void;

  // Second overload type:
  function logSearchUnion(term: string, options?: number): void;

  // Implementation:
  function logSearchUnion(term: string, p2?: string | number) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearchUnion("melon", "honey-dew");
  logSearchUnion("oranges", 4);
  ```


path: Functions.md
sortable_path: Functions.md
title: Functions
checksum: d07fc49bfba92f05596275674fd922ea512e4efde44e593414c7127dfe821e8e
created: 2025-10-01 13:30:58.922245969+00:00
modified: 2025-08-13 15:57:13.919310084+00:00
metadata: {"tags":["typescript","react"]}
lead:
  Continuing from the other examples of React Typescript, we could do standard
  listing function, like:

body:
  Continuing from the other examples of React Typescript, we could do standard
  listing function, like:

  ```tsx
  <ul>
    {people.map((person) => {
      return <li>{person.name}</li>;
    })}
  </ul>
  ```

  But it's neater to do it with a function defined within the `List` component:

  ```tsx
  const renderList = (): JSX.Element[] => {
    return people.map((person) => {
      return (
        <li>
          <div>{person.name}</div>
          <div>{person.age}</div>
        </li>
      );
    });
  };
  ```

  And then change the eariler list to a function invocation:

  ```tsx
      <ul>{renderList()}<ul>
  ```

raw_content:
  ---
  tags:
    - typescript
    - react
  ---

  # Functions

  Continuing from the other examples of React Typescript, we could do standard
  listing function, like:

  ```tsx
  <ul>
    {people.map((person) => {
      return <li>{person.name}</li>;
    })}
  </ul>
  ```

  But it's neater to do it with a function defined within the `List` component:

  ```tsx
  const renderList = (): JSX.Element[] => {
    return people.map((person) => {
      return (
        <li>
          <div>{person.name}</div>
          <div>{person.age}</div>
        </li>
      );
    });
  };
  ```

  And then change the eariler list to a function invocation:

  ```tsx
      <ul>{renderList()}<ul>
  ```


path: Functions_in_Bash.md
sortable_path: Functions_in_Bash.md
title: Functions in Bash
checksum: 3984707a0e0c9ed1d007c02686e9631ce8ab705d64cd3d3eb9aa0d360909906f
created: 2025-10-01 13:30:58.926046974+00:00
modified: 2025-08-13 15:57:13.932928532+00:00
metadata: {"tags":["shell"]}
lead:
  We don't name function parameters in the function declaration. Instead we have
  an implied index of arguments: `$1, $2, $3,...`. When the function is called,
  the first value after the function name becomes `$1` by default, then the
  subsequent arguments.

body:
  We don't name function parameters in the function declaration. Instead we have
  an implied index of arguments: `$1, $2, $3,...`. When the function is called,
  the first value after the function name becomes `$1` by default, then the
  subsequent arguments.

  ```bash
  function expandRange() {
      declare -a expandedRange=()
      for (( i=$1; i<=$2; i++ )); do
          expandedRange+=($i)
      done
      echo "${expandedRange[@]}"
  }
  ```

  ```bash
  expandedRange=$(expandRange 1 4)
  echo $expandedRange
  # 1 2 3 4
  ```

  ## Get all arguments as an array

  We can access all the arguments passed to a function using the `$@` syntax we
  encountered before when
  [passing arguments to scripts](Passing_arguments_to_Bash_scripts.md). (Here a
  function is a kind of script in miniature so the process is the same.)

  ```sh
  function numberThings() {
    i=1
    for f in "$@"; do
      echo $i: "$f"
      (( i++ ))
    done
  }
  ```

  ## Local variables

  ```bash
  var1="I'm variable 1"

  function myfunction() {
    var2="I'm variable 2"
    local var3="I'm variable 3"
  }

  myfunction
  echo $var1
  echo $var2
  echo $var3

  # I'm variable 1
  # I'm variable 2
  ```

  > The convention is to put functions at the top of the script, after the shebang
  > and after the global variables

raw_content:
  ---
  tags:
    - shell
  ---

  # Functions in Bash

  We don't name function parameters in the function declaration. Instead we have
  an implied index of arguments: `$1, $2, $3,...`. When the function is called,
  the first value after the function name becomes `$1` by default, then the
  subsequent arguments.

  ```bash
  function expandRange() {
      declare -a expandedRange=()
      for (( i=$1; i<=$2; i++ )); do
          expandedRange+=($i)
      done
      echo "${expandedRange[@]}"
  }
  ```

  ```bash
  expandedRange=$(expandRange 1 4)
  echo $expandedRange
  # 1 2 3 4
  ```

  ## Get all arguments as an array

  We can access all the arguments passed to a function using the `$@` syntax we
  encountered before when
  [passing arguments to scripts](Passing_arguments_to_Bash_scripts.md). (Here a
  function is a kind of script in miniature so the process is the same.)

  ```sh
  function numberThings() {
    i=1
    for f in "$@"; do
      echo $i: "$f"
      (( i++ ))
    done
  }
  ```

  ## Local variables

  ```bash
  var1="I'm variable 1"

  function myfunction() {
    var2="I'm variable 2"
    local var3="I'm variable 3"
  }

  myfunction
  echo $var1
  echo $var2
  echo $var3

  # I'm variable 1
  # I'm variable 2
  ```

  > The convention is to put functions at the top of the script, after the shebang
  > and after the global variables


path: Functions_in_Python.md
sortable_path: Functions_in_Python.md
title: Functions in Python
checksum: 02169f9893dea8f188e6c3ea80dca601bb3edef2f5ad05b6f5cf5875b8760e09
created: 2025-10-01 13:30:58.936121478+00:00
modified: 2025-08-13 15:57:14.009988882+00:00
metadata: {"tags":["python"]}
lead:
  - Convention is to leave a double line-break after a function definition (but
    not with nested functions - here, a single linebreak is sufficient)
  - Scope within functions is demarcated by indents, as everything in Python
  - We use a docstring _within_ the function body, to document our function. This
    text will then show up in Intellisense etc.

body:
  - Convention is to leave a double line-break after a function definition (but
    not with nested functions - here, a single linebreak is sufficient)
  - Scope within functions is demarcated by indents, as everything in Python
  - We use a docstring _within_ the function body, to document our function. This
    text will then show up in Intellisense etc.

  ## Basic examples

  ```py

  # No params, no return
  def print_msg():
      """ A function that prints hello world """
      print('Hello World!')

  print_msg()
  # Hello World!

  print(type(print_msg))
  # <class 'function'>

  # Params, no return
  def print_my_msg(msg):
      """ A simple function to print a message """
      print(msg)

  print_my_msg('Good day')
  # Good day

  # Params and return
  def square(n):
      return n * n

  print(square(2))
  # 4

  result = square(4)
  print(result)

  # 16

  ```

  ## Default parameters

  ```py
  def greeter(name, message='Live Long and Prosper'):
      print('Welcome', name, '-', message)

  greeter('Eloise')

  # Welcome Eloise - Live Long and Prosper
  ```

  ## Optional parameters

  ````py
  def func_with_optional(non_optional, optional_param=None):
    if optional_param is not None:
      # Do something with specific value
    else:
      # Run standard process


  ## Function with arbitrary parameter list

  ```python
  def greeter(*args):
      for name in args:
          print('Welcome', name)

  greeter('John', 'Denise', 'Phoebe', 'Adam', 'Gryff', 'Natalia')

  """
  Welcome John
  Welcome Denise
  Welcome Phoebe
  Welcome Adam
  Welcome Gryff
  Welcome Natalia
  """
  ````

  ## Scoping

  Function variables are locally scoped by default.

  They can access variables that are outer to them and can redefine them within
  their own scope _and_ within the global scope using the keywords `global` and
  `nonlocal`.

  Below a global variable is accessed and changed but only internally within a
  function scope

  ```py
  max = 100
  print('initial value of max:', max)

  def print_max():
      global max
      max = max + 1
      print('inside function:', max)


  print_max()
  print('outside function:', max)

  """
  initial value of max: 100
  inside function: 101
  outside function: 101
  """
  ```

  Below a higher-scoped variable is redefined from within the lower scope:

  ```py
  def myfunc1():
    x = "John"
    def myfunc2():
      nonlocal x
      x = "hello"
    myfunc2()
    return x

  print(myfunc1())

  # hello
  ```

  We cannot however redefine a global variable from a function scope permanently.
  It will remain whatever it is in global scope, after the function has run.

raw_content:
  ---
  tags: [python]
  ---

  # Functions in Python

  - Convention is to leave a double line-break after a function definition (but
    not with nested functions - here, a single linebreak is sufficient)
  - Scope within functions is demarcated by indents, as everything in Python
  - We use a docstring _within_ the function body, to document our function. This
    text will then show up in Intellisense etc.

  ## Basic examples

  ```py

  # No params, no return
  def print_msg():
      """ A function that prints hello world """
      print('Hello World!')

  print_msg()
  # Hello World!

  print(type(print_msg))
  # <class 'function'>

  # Params, no return
  def print_my_msg(msg):
      """ A simple function to print a message """
      print(msg)

  print_my_msg('Good day')
  # Good day

  # Params and return
  def square(n):
      return n * n

  print(square(2))
  # 4

  result = square(4)
  print(result)

  # 16

  ```

  ## Default parameters

  ```py
  def greeter(name, message='Live Long and Prosper'):
      print('Welcome', name, '-', message)

  greeter('Eloise')

  # Welcome Eloise - Live Long and Prosper
  ```

  ## Optional parameters

  ````py
  def func_with_optional(non_optional, optional_param=None):
    if optional_param is not None:
      # Do something with specific value
    else:
      # Run standard process


  ## Function with arbitrary parameter list

  ```python
  def greeter(*args):
      for name in args:
          print('Welcome', name)

  greeter('John', 'Denise', 'Phoebe', 'Adam', 'Gryff', 'Natalia')

  """
  Welcome John
  Welcome Denise
  Welcome Phoebe
  Welcome Adam
  Welcome Gryff
  Welcome Natalia
  """
  ````

  ## Scoping

  Function variables are locally scoped by default.

  They can access variables that are outer to them and can redefine them within
  their own scope _and_ within the global scope using the keywords `global` and
  `nonlocal`.

  Below a global variable is accessed and changed but only internally within a
  function scope

  ```py
  max = 100
  print('initial value of max:', max)

  def print_max():
      global max
      max = max + 1
      print('inside function:', max)


  print_max()
  print('outside function:', max)

  """
  initial value of max: 100
  inside function: 101
  outside function: 101
  """
  ```

  Below a higher-scoped variable is redefined from within the lower scope:

  ```py
  def myfunc1():
    x = "John"
    def myfunc2():
      nonlocal x
      x = "hello"
    myfunc2()
    return x

  print(myfunc1())

  # hello
  ```

  We cannot however redefine a global variable from a function scope permanently.
  It will remain whatever it is in global scope, after the function has run.


path: Fundamental_Theorem_of_Arithmetic.md
sortable_path: Fundamental_Theorem_of_Arithmetic.md
title: Fundamental Theorem of Arithmetic
checksum: 6adaf24b44c95ae548c33892d9c8a671dcdc6b0701da1fe8ed6b98ffb9fdcf85
created: 2025-10-01 13:30:58.939392039+00:00
modified: 2025-08-13 15:57:13.917366918+00:00
metadata: {"tags":["prealgebra","theorems"]}
lead:
  > Every integer greater than one is either a prime number itself or is product
  > of a unique combination of primes.

body:
  > Every integer greater than one is either a prime number itself or is product
  > of a unique combination of primes.

  This is also known as the **Unique Factorisation Theorem**.

  'Unique' means that there is not more than one way to derive the whole number.
  Once you reduce the factorisation to primes, there can only be one set of
  numbers that results in the target number.

  For example, $24$ has the following factors: ${12, 24}$ and $6, 4$ but these are
  composite numbers. The unique factorisation combination for 24 is $2, 2, 3$.

raw_content:
  ---
  tags:
    - prealgebra
    - theorems
  ---

  # Fundamental Theorem of Arithmetic

  > Every integer greater than one is either a prime number itself or is product
  > of a unique combination of primes.

  This is also known as the **Unique Factorisation Theorem**.

  'Unique' means that there is not more than one way to derive the whole number.
  Once you reduce the factorisation to primes, there can only be one set of
  numbers that results in the target number.

  For example, $24$ has the following factors: ${12, 24}$ and $6, 4$ but these are
  composite numbers. The unique factorisation combination for 24 is $2, 2, 3$.


path: Further_examples_of_TS_generics.md
sortable_path: Further_examples_of_TS_generics.md
title: Further examples of generics in TypeScript
lead: ## Basic function
checksum: 02599d71a25bee261d052b3ee96b7d8605f670235fcf4d65b46eeac1344375fc
created: 2025-10-01 13:30:58.942204441+00:00
modified: 2025-08-13 15:57:13.980363895+00:00
metadata: {"tags":["typescript"]}
body:
  ## Basic function

  In the code below we have a simple JavaScript function that receives a value and
  an an array as parameters. It returns a new array comprising the original array
  plus the additional value:

  ```js
  function generateArray(existingArray, newValue) {
    return [...existingArray, newValue];
  }
  ```

  Imagine that we want to ensure that each of the parameters share the same data
  type. In other words: if the function is passed a string array, the second
  parameter must also be a string. For example, it should not be the case that you
  can append a string to an array of numbers.

  Now imagine that we don't know in advance what type the value or array will be,
  we just know that the data types of the parameters must match.

  In converting the function to TypeScript, one way of overcoming our lack of
  foreknowledge would be to deploy `any`. This way it doesn't matter which types
  are passed to the function:

  ```ts
  function generateArray(existingArray: any[], newValue: any): any[] {
    return [...existingArray, newValue];
  }
  ```

  But this is no solution at all. The problem — as always with `any` — is that it
  strips our function of any type checks whatsoever and would therefore invite
  calls of form: `generateArray([1,2,3], 'lorem')`.

  Enter generics:

  ```ts
  function generateArray<T>(existingArr: T[], newValue: T): T[] {
    return [...existingArr, newValue];
  }
  ```

  Now, whilst we haven't asserted ahead of time which types will be used,
  whichever types we do pass in, must match. The function header is saying:

  - both arguments must be of the same type (represented by `T`)
  - the function will return an array of this same `T` type.

  If I then tried to run the function with unmatched types (for example
  `generateArray([1,2,3,4], true)` ) TypeScript would raise the following error:

  ```
  Argument of type 'boolean' is not assignable to parameter of type 'number'
  ```

  Note that even though the function in question does not express any preference
  for number types, given that our first parameter is a number, TypeScript knows
  that the second parameter must also be a number.

  > In the generic function we have used `T` as our placeholder for a generic type
  > as this is the convention. However there is no compunction to do so. We could
  > have used any letter or string, providing that the string is not a reserved
  > term.

  ### More advanced function

  This example demonstrates how we can use generics to reduce repetition when
  writing functions and is also a more realistic use case.

  Let's say we have two types or interfaces:

  ```tsx
  type VideoFormatUrls = {
    format720p: URL;
    format1080p: URL;
  };
  ```

  ```tsx
  type SubtitleFormatUrls = {
    english: URL;
    german: URL;
  };
  ```

  An example of an object matching these type definitions:

  ```tsx
  const videoFormats: VideoFormatUrls = {
  	format720p: https://www.format720p.co.uk,
  	format1080p: https://www.format1080p.co.uk
  }
  ```

  Imagine we wanted to be able to check whether a given film is available in a
  certain video format. We could write a function like this:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  Now imagine that we need to do the same thing with subtitles, but given that
  `isFormatAvailable()` is typed to the `VideoFormatUrls` type we would get an
  error if we used this function for subtitles. But we also don't want to write a
  near identical function typed to `SubtitleFormatUrls` to subtitles just to
  ensure adequate type safety.

  Alternatively we could use a union type, for example:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls | SubtitleFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  But this quickly becomes unwieldy if we, for the sake of argument have a great
  many URL types that we want the function to utilise.

  This is where generics become super helpful. Here is how we would rewrite the
  function as a generic:

  ```tsx
  function isAvailable<Formats>(obj: Formats, key: string): key is keyof Formats {
    return key in obj;
  }
  ```

  We could then explicitly type our calls of this function, viz:

  ```tsx
  isFormatAvailable<SubtitleFormatUrls>(subtitles, "english");
  ```

raw_content:
  ---
  tags:
    - typescript
  ---

  # Further examples of generics in TypeScript

  ## Basic function

  In the code below we have a simple JavaScript function that receives a value and
  an an array as parameters. It returns a new array comprising the original array
  plus the additional value:

  ```js
  function generateArray(existingArray, newValue) {
    return [...existingArray, newValue];
  }
  ```

  Imagine that we want to ensure that each of the parameters share the same data
  type. In other words: if the function is passed a string array, the second
  parameter must also be a string. For example, it should not be the case that you
  can append a string to an array of numbers.

  Now imagine that we don't know in advance what type the value or array will be,
  we just know that the data types of the parameters must match.

  In converting the function to TypeScript, one way of overcoming our lack of
  foreknowledge would be to deploy `any`. This way it doesn't matter which types
  are passed to the function:

  ```ts
  function generateArray(existingArray: any[], newValue: any): any[] {
    return [...existingArray, newValue];
  }
  ```

  But this is no solution at all. The problem — as always with `any` — is that it
  strips our function of any type checks whatsoever and would therefore invite
  calls of form: `generateArray([1,2,3], 'lorem')`.

  Enter generics:

  ```ts
  function generateArray<T>(existingArr: T[], newValue: T): T[] {
    return [...existingArr, newValue];
  }
  ```

  Now, whilst we haven't asserted ahead of time which types will be used,
  whichever types we do pass in, must match. The function header is saying:

  - both arguments must be of the same type (represented by `T`)
  - the function will return an array of this same `T` type.

  If I then tried to run the function with unmatched types (for example
  `generateArray([1,2,3,4], true)` ) TypeScript would raise the following error:

  ```
  Argument of type 'boolean' is not assignable to parameter of type 'number'
  ```

  Note that even though the function in question does not express any preference
  for number types, given that our first parameter is a number, TypeScript knows
  that the second parameter must also be a number.

  > In the generic function we have used `T` as our placeholder for a generic type
  > as this is the convention. However there is no compunction to do so. We could
  > have used any letter or string, providing that the string is not a reserved
  > term.

  ### More advanced function

  This example demonstrates how we can use generics to reduce repetition when
  writing functions and is also a more realistic use case.

  Let's say we have two types or interfaces:

  ```tsx
  type VideoFormatUrls = {
    format720p: URL;
    format1080p: URL;
  };
  ```

  ```tsx
  type SubtitleFormatUrls = {
    english: URL;
    german: URL;
  };
  ```

  An example of an object matching these type definitions:

  ```tsx
  const videoFormats: VideoFormatUrls = {
  	format720p: https://www.format720p.co.uk,
  	format1080p: https://www.format1080p.co.uk
  }
  ```

  Imagine we wanted to be able to check whether a given film is available in a
  certain video format. We could write a function like this:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  Now imagine that we need to do the same thing with subtitles, but given that
  `isFormatAvailable()` is typed to the `VideoFormatUrls` type we would get an
  error if we used this function for subtitles. But we also don't want to write a
  near identical function typed to `SubtitleFormatUrls` to subtitles just to
  ensure adequate type safety.

  Alternatively we could use a union type, for example:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls | SubtitleFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  But this quickly becomes unwieldy if we, for the sake of argument have a great
  many URL types that we want the function to utilise.

  This is where generics become super helpful. Here is how we would rewrite the
  function as a generic:

  ```tsx
  function isAvailable<Formats>(obj: Formats, key: string): key is keyof Formats {
    return key in obj;
  }
  ```

  We could then explicitly type our calls of this function, viz:

  ```tsx
  isFormatAvailable<SubtitleFormatUrls>(subtitles, "english");
  ```


path: GPU_versus_CPU.md
sortable_path: GPU_versus_CPU.md
title: GPU versus CPU
checksum: 132c91acd7f4f95fe91448d7b07df376dc5f8028fee99951dcb411a17cec74ae
created: 2025-10-01 13:30:58.945008875+00:00
modified: 2025-08-13 15:57:13.853049401+00:00
metadata: {"created":"Sunday, September 22, 2024","tags":["computer-architecture"]}
lead:
  - GPU stands for _Graphics Processing Unit_. Originally designed for rendering
    graphics and video but now used for other applications of parallel computing
    eg. machine learning.

body:
  - GPU stands for _Graphics Processing Unit_. Originally designed for rendering
    graphics and video but now used for other applications of parallel computing
    eg. machine learning.

  - The standard CPU uses sequential processing whereas the GPU is designed for
    parallel processing, specialising in performing many simple calculations
    simultaneously.

  - Harnessing the GPU requires specialised programming approaches.

  - Most standard laptops and desktops (if they are not designed for
    graphically-intensive tasks) incorporate graphics processing within the CPU.
    The CPU chip is on the same chip as the CPU chip. This is called _integrated_
    graphics processing.

  - Some laptops, especially those marketed for gaming or professional work, have
    a separate GPU chip. This is a more powerful graphics processor, separate from
    the CPU.

raw_content:
  ---
  tags: [computer-architecture]
  created: Sunday, September 22, 2024
  ---

  # GPU versus CPU

  - GPU stands for _Graphics Processing Unit_. Originally designed for rendering
    graphics and video but now used for other applications of parallel computing
    eg. machine learning.

  - The standard CPU uses sequential processing whereas the GPU is designed for
    parallel processing, specialising in performing many simple calculations
    simultaneously.

  - Harnessing the GPU requires specialised programming approaches.

  - Most standard laptops and desktops (if they are not designed for
    graphically-intensive tasks) incorporate graphics processing within the CPU.
    The CPU chip is on the same chip as the CPU chip. This is called _integrated_
    graphics processing.

  - Some laptops, especially those marketed for gaming or professional work, have
    a separate GPU chip. This is a more powerful graphics processor, separate from
    the CPU.


path: Generics_in_TypeScript.md
sortable_path: Generics_in_TypeScript.md
title: Generics
checksum: 74484845b015dd880dd9a5e01c698353617bd818b86e244c6946d64ab002a41c
created: 2025-10-01 13:30:58.94863317+00:00
modified: 2025-08-13 15:57:13.800098017+00:00
metadata: {"tags":["typescript"]}
lead:
  Generics are a powerful feature in TypeScript that enables you to write reusable
  and flexible code while maintaining strong typing. With generics, you can create
  functions, classes, and interfaces that work with various types while preserving
  type information.

body:
  Generics are a powerful feature in TypeScript that enables you to write reusable
  and flexible code while maintaining strong typing. With generics, you can create
  functions, classes, and interfaces that work with various types while preserving
  type information.

  The main purpose of generics is to allow developers to write code that can
  **operate on different data types without knowing the specific type
  beforehand**. This helps to keep the code DRY and maintainable.

  ## Basic usage

  ### Functions

  ```ts
  function identity<T>(arg: T): T {
    return arg;
  }
  ```

  Here, `T` is a generic type variable. The `identity` function is a generic
  function that takes an argument of type `T` and returns a value of type `T`.

  To use this generic function you can either explicitly provide the type within
  the angle brackets, or let TypeScript infer the type based on the value passed:

  ```ts
  // Explicitly specifying the type
  let output1 = identity<string>("hello");

  // TypeScript infers the type as `number`
  let output2 = identity(42);
  ```

  #### Restricting the available types

  In the previous example any type could be used with the `identity` function. The
  only constraint that we place on usage is that the types must be consistent: if
  we pass a string as an argument, then a string must be returned.

  However we can add further restrictions on types by using the `extend` keyword,
  combined with an interface.

  ```ts
  interface Lengthwise {
    length: number;
  }

  function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
  }
  ```

  In this example the `logLength` generic function is limited to types that
  implement the `Lengthwise` interface. So: any argument that is passed to
  `logLength` and any value that is returned by it must match the shape of
  `Lengthwise`, having the `length` property.

  ## Interfaces and classes

  Generics can also be used profitably when working with stricter OOP constructs.

  ```ts
  interface KeyValuePair<K, V> {
    key: K;
    value: V;
  }
  ```

  The `KeyValuePair` interface has two generic type parameters: K for the key and
  V for the value. The interface defines an object shape that must possess the
  properties `key` and `value`, e.g:

  ```ts
  {
    key: 2,
    value: 'something'
  }
  ```

  In this form the generic specifies that whatever types are used for the key and
  the pair must be used consistently, e.g. this would be wrong:

  ```ts
  {
    key: 'age',
    value: 32
  }
  ```

  The following class uses the `KeyValuePair` interface:

  ```ts
  class Storage<K, V> {
    private items: KeyValuePair<K, V>[] = [];

    add(item: KeyValuePair<K, V>): void {
      this.items.push(item);
    }

    getByKey(key: K): V | undefined {
      const foundItem = this.items.find((item) => item.key === key);
      return foundItem ? foundItem.value : undefined;
    }

    getAll(): KeyValuePair<K, V>[] {
      return this.items;
    }
  }
  ```

  This class stores objects that match the `KeyValuesPair` interface in an array
  and provides and add and list method for accessing/returning them.

  The `add` method takes an item of type `KeyValuePair<K, V>` and adds it to the
  `items` array. The `getByKey` method takes a key of type `K` and returns the
  value of type `V` associated with that key or undefined if the key is not found.
  The `getAll` method returns all stored key-value pairs.

  Here is an example of instantiating the class:

  ```ts
  const storage = new Storage<number, string>();

  storage.add({ key: 1, value: "one" });
  storage.add({ key: 2, value: "two" });

  const value = storage.getByKey(1);
  const allItems = storage.getAll();

  console.log(value);
  // value: "one"

  console.log(allItems);

  // allItems: [
  //   { key: 1, value: "one" },
  //   { key: 2, value: "two" }
  // ]
  ```

  ## Real examples

  ### GraphQL client for query and mutation requests over `fetch`

  ```ts
  type GraphQlResult<T> = {
    data: T;
    errors?: Array<{
      message: string;
      locations: Array<{ line: number; column: number }>;
      path: Array<string | number>;
    }>;
  };

  export class GraphQlClient {
    private endpoint: string;

    constructor(endpoint: string) {
      this.endpoint = endpoint;
    }
    async request<T>(
      query: string,
      variables?: Record<string, unknown>
    ): Promise<T> {
      try {
        const response = await fetch(this.endpoint, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ query, variables }),
        });

        if (!response.ok) {
          throw new Error(
            `Network error: ${response.status} - ${response.statusText}`
          );
        }

        const result: GraphQlResult<T> = await response.json();

        if (result.errors) {
          throw new Error(`GraphQL error: ${JSON.stringify(result.errors)}`);
        }

        return result.data;
      } catch (error) {
        console.error(error);
        throw error;
      }
    }
  }
  ```

  ### VSCode extension TreeView generator

  In VSCode a TreeView is a list of values that may have nested values, like a
  directory. The following generic is a helper function that generates a TreeView
  based on a given class that is passed in as an argument, along with the class's
  constructor values (`args` in the example). It also calls a method `refresh` on
  each instance of the class.

  ```ts
  function createTreeView<
    T extends IndexHyperlinksProvider | IndexMetadataProvider,
    U extends LinkTypes | MetadataTypes
  >(
    viewType: string,
    ProviderClass: new (...args: any[]) => T,
    type: U,
    activeEditor?: string | undefined,
    ...args: ConstructorParameters<typeof ProviderClass>
  ): T {
    const view = new ProviderClass(...args, type);
    if (view instanceof IndexHyperlinksProvider) {
      view.refresh(activeEditor, type as LinkTypes);
    } else if (view instanceof IndexMetadataProvider) {
      view.refreshIndex();
    }
    vscode.window.registerTreeDataProvider(viewType, view);
    return view;
  }
  ```

raw_content:
  ---
  tags:
    - typescript
  ---

  # Generics

  Generics are a powerful feature in TypeScript that enables you to write reusable
  and flexible code while maintaining strong typing. With generics, you can create
  functions, classes, and interfaces that work with various types while preserving
  type information.

  The main purpose of generics is to allow developers to write code that can
  **operate on different data types without knowing the specific type
  beforehand**. This helps to keep the code DRY and maintainable.

  ## Basic usage

  ### Functions

  ```ts
  function identity<T>(arg: T): T {
    return arg;
  }
  ```

  Here, `T` is a generic type variable. The `identity` function is a generic
  function that takes an argument of type `T` and returns a value of type `T`.

  To use this generic function you can either explicitly provide the type within
  the angle brackets, or let TypeScript infer the type based on the value passed:

  ```ts
  // Explicitly specifying the type
  let output1 = identity<string>("hello");

  // TypeScript infers the type as `number`
  let output2 = identity(42);
  ```

  #### Restricting the available types

  In the previous example any type could be used with the `identity` function. The
  only constraint that we place on usage is that the types must be consistent: if
  we pass a string as an argument, then a string must be returned.

  However we can add further restrictions on types by using the `extend` keyword,
  combined with an interface.

  ```ts
  interface Lengthwise {
    length: number;
  }

  function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
  }
  ```

  In this example the `logLength` generic function is limited to types that
  implement the `Lengthwise` interface. So: any argument that is passed to
  `logLength` and any value that is returned by it must match the shape of
  `Lengthwise`, having the `length` property.

  ## Interfaces and classes

  Generics can also be used profitably when working with stricter OOP constructs.

  ```ts
  interface KeyValuePair<K, V> {
    key: K;
    value: V;
  }
  ```

  The `KeyValuePair` interface has two generic type parameters: K for the key and
  V for the value. The interface defines an object shape that must possess the
  properties `key` and `value`, e.g:

  ```ts
  {
    key: 2,
    value: 'something'
  }
  ```

  In this form the generic specifies that whatever types are used for the key and
  the pair must be used consistently, e.g. this would be wrong:

  ```ts
  {
    key: 'age',
    value: 32
  }
  ```

  The following class uses the `KeyValuePair` interface:

  ```ts
  class Storage<K, V> {
    private items: KeyValuePair<K, V>[] = [];

    add(item: KeyValuePair<K, V>): void {
      this.items.push(item);
    }

    getByKey(key: K): V | undefined {
      const foundItem = this.items.find((item) => item.key === key);
      return foundItem ? foundItem.value : undefined;
    }

    getAll(): KeyValuePair<K, V>[] {
      return this.items;
    }
  }
  ```

  This class stores objects that match the `KeyValuesPair` interface in an array
  and provides and add and list method for accessing/returning them.

  The `add` method takes an item of type `KeyValuePair<K, V>` and adds it to the
  `items` array. The `getByKey` method takes a key of type `K` and returns the
  value of type `V` associated with that key or undefined if the key is not found.
  The `getAll` method returns all stored key-value pairs.

  Here is an example of instantiating the class:

  ```ts
  const storage = new Storage<number, string>();

  storage.add({ key: 1, value: "one" });
  storage.add({ key: 2, value: "two" });

  const value = storage.getByKey(1);
  const allItems = storage.getAll();

  console.log(value);
  // value: "one"

  console.log(allItems);

  // allItems: [
  //   { key: 1, value: "one" },
  //   { key: 2, value: "two" }
  // ]
  ```

  ## Real examples

  ### GraphQL client for query and mutation requests over `fetch`

  ```ts
  type GraphQlResult<T> = {
    data: T;
    errors?: Array<{
      message: string;
      locations: Array<{ line: number; column: number }>;
      path: Array<string | number>;
    }>;
  };

  export class GraphQlClient {
    private endpoint: string;

    constructor(endpoint: string) {
      this.endpoint = endpoint;
    }
    async request<T>(
      query: string,
      variables?: Record<string, unknown>
    ): Promise<T> {
      try {
        const response = await fetch(this.endpoint, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ query, variables }),
        });

        if (!response.ok) {
          throw new Error(
            `Network error: ${response.status} - ${response.statusText}`
          );
        }

        const result: GraphQlResult<T> = await response.json();

        if (result.errors) {
          throw new Error(`GraphQL error: ${JSON.stringify(result.errors)}`);
        }

        return result.data;
      } catch (error) {
        console.error(error);
        throw error;
      }
    }
  }
  ```

  ### VSCode extension TreeView generator

  In VSCode a TreeView is a list of values that may have nested values, like a
  directory. The following generic is a helper function that generates a TreeView
  based on a given class that is passed in as an argument, along with the class's
  constructor values (`args` in the example). It also calls a method `refresh` on
  each instance of the class.

  ```ts
  function createTreeView<
    T extends IndexHyperlinksProvider | IndexMetadataProvider,
    U extends LinkTypes | MetadataTypes
  >(
    viewType: string,
    ProviderClass: new (...args: any[]) => T,
    type: U,
    activeEditor?: string | undefined,
    ...args: ConstructorParameters<typeof ProviderClass>
  ): T {
    const view = new ProviderClass(...args, type);
    if (view instanceof IndexHyperlinksProvider) {
      view.refresh(activeEditor, type as LinkTypes);
    } else if (view instanceof IndexMetadataProvider) {
      view.refreshIndex();
    }
    vscode.window.registerTreeDataProvider(viewType, view);
    return view;
  }
  ```


path: Git_access_over_HTTPS_with_credentials.md
sortable_path: Git_access_over_HTTPS_with_credentials.md
title: Git access with HTTPS and credentials
checksum: 219e5d816fb3f7440ee67faae49231968636ae225a29c9b5bfaccd2b4a18360c
created: 2025-10-01 13:30:58.951752479+00:00
modified: 2025-08-13 15:57:13.802429737+00:00
metadata: {"created":"Sunday, April 13, 2025","tags":["git"]}
lead:
  If not using SSH, use HTTPS. However in order to combine this with 2FA it is
  necessary to use an access token as the pass, not your actual password.

body:
  If not using SSH, use HTTPS. However in order to combine this with 2FA it is
  necessary to use an access token as the pass, not your actual password.

  Create an access token in your remote Git client (GitHub, Forgejo).

  Then, clear any existing credentials with:

  ```sh
  git config --global --unset credential.helper
  ```

  Set Git config to use store mode:

  ```sh
  git config --global credential.helper store
  ```

  Do a push or pull in a repo to prompt authentication. Put in username but
  credential as password. You should be able to login.

  Confirm the credential has been added by viewing the credential file at the root
  of your home directory:

  ```sh
  cat ${HOME}/.git-credentials
  ```

raw_content:
  ---
  tags: [git]
  created: Sunday, April 13, 2025
  ---

  # Git access with HTTPS and credentials

  If not using SSH, use HTTPS. However in order to combine this with 2FA it is
  necessary to use an access token as the pass, not your actual password.

  Create an access token in your remote Git client (GitHub, Forgejo).

  Then, clear any existing credentials with:

  ```sh
  git config --global --unset credential.helper
  ```

  Set Git config to use store mode:

  ```sh
  git config --global credential.helper store
  ```

  Do a push or pull in a repo to prompt authentication. Put in username but
  credential as password. You should be able to login.

  Confirm the credential has been added by viewing the credential file at the root
  of your home directory:

  ```sh
  cat ${HOME}/.git-credentials
  ```


path: Git_bisect.md
sortable_path: Git_bisect.md
title: Bisect
lead:
  Bisect can be used to identify commits that introduce a bug or regression in our
  code.

checksum: 582180e21ae5adc1089dce7b0205d74e9345d25d43bb03b7a0d473da0f4a3774
created: 2025-10-01 13:30:58.955212338+00:00
modified: 2025-08-13 15:57:13.842026917+00:00
metadata: {"tags":["git"]}
body:
  Bisect can be used to identify commits that introduce a bug or regression in our
  code.

  It is most useful for when we know there is a problem in the code and we know
  there was a point in the past where the bug did not exist. We can compare the
  two points and try to identify where things went wrong.

  We mark the last good revision and the first bad revision. Bisect will the reset
  the code to the midpoint between the good and bad versions and let you test it.
  You mark that as a good or bad version and then bisect repeats the process.

  ![](/img/git-bisect.svg)

  ## Procedure

  ```
  git bisect start

  git bisect good <SHA, tag, branch>

  git bisect bad <SHA, tag, branch>
  ```

raw_content:
  ---
  tags: [git]
  ---

  # Bisect

  Bisect can be used to identify commits that introduce a bug or regression in our
  code.

  It is most useful for when we know there is a problem in the code and we know
  there was a point in the past where the bug did not exist. We can compare the
  two points and try to identify where things went wrong.

  We mark the last good revision and the first bad revision. Bisect will the reset
  the code to the midpoint between the good and bad versions and let you test it.
  You mark that as a good or bad version and then bisect repeats the process.

  ![](/img/git-bisect.svg)

  ## Procedure

  ```
  git bisect start

  git bisect good <SHA, tag, branch>

  git bisect bad <SHA, tag, branch>
  ```


path: Git_rebasing.md
sortable_path: Git_rebasing.md
title: Rebasing
checksum: be09e17373363834833d5deb64d2732b4c33199b79b14ab0e6dfd63676670f80
created: 2025-10-01 13:30:58.958539178+00:00
modified: 2025-08-13 15:57:13.76183949+00:00
metadata: {"tags":["git"]}
lead:
  Rebasing is a way to integrate changes from one branch into another. In this
  regarding it is like merging a branch B into another branch A. However rebasing
  differs from normal merging in the way in which it modifies the Git history.

body:
  Rebasing is a way to integrate changes from one branch into another. In this
  regarding it is like merging a branch B into another branch A. However rebasing
  differs from normal merging in the way in which it modifies the Git history.

  In a normal merge of branch B into branch A, Git creates a new commit that
  combines the changes of branches B and A. This is known as the merge commit and
  is evident from the following automatic commit message that is generated:

  ```
  Merge branch B of github.com:thomasabishop/remote-repository into A
  ```

  ![](/img/normal-merge-again.svg)

  In this scenario the merge commit has two or more parent commits each
  representing the history of the merged branches. The resulting history of A will
  include the commits of B. Basically the two histories are combined.

  This would give us a history that looks like the following, with different
  colours for the separate SHAs of each merged branch:

  ![](/img/combined-merge-hist.svg)

  If we were to create a rebase branch of A from B, there would be a new singular
  history without distinguishing multiple parents that combines the commits of A
  and B. The rebased branch's commits are recreated with new commit IDs and new
  timestamps, based on the current state of the branch that you are rebasing onto.
  This is obviously potentially destructive because it does not preserve the
  respective branch's separate history. It rewrites history as a continuous stream
  of commits in a single branch.

  When a rebase is applied, it will put the diverging B commits at the tip of A
  like so:

  ![](/img/rebase-tip-chage.svg)

  And then rebrand the previous A commits to be continuous with B presenting a
  flat and linear Git history like the following:

  ![](/img/single-git-history-rebase.svg)

  ## Benefits, use-cases

  > The purpose of rebasing is to ensure that the commit history of a project is
  > as linear and simple as possible, by incorporating changes from one branch
  > into another, as if the changes had been made on the other branch all along.

  A common use-case is with feature branches as it makes the features fit more
  seamlessly with the `main` or `develop` branch.

  It is also a technique that can be used to integrate recent commits without
  merging.

  When to use one over the other:

  - **Merge** to allow commits to stand out or to be clearly grouped
  - **Merge** to bring large feature branches back into main
  - **Rebase** to add minor commits in a main branch into a feature branch
  - **Rebase** when you need to move commits from one branch to another

  So in terms of my personal workflow, I should be using

  - Merge when I want to bring a feature into develop
  - Merge when I want to bring develop into main for a release
  - Merge any time the feature branch is already public and being used by others.
  - Rebase to capture small changes in the parent branch
  - Rebase when I have branched from a branch because a refactor or something has
    become more complex than originally anticipated.

  ## Syntax

  For example if we are working on a feature branch off of `main`, we would run
  the following from this feature branch:

  ```
  git rebase main
  ```

  This will move the feature branch to the tip of `main`.

  If this completes successfully, we would then go on to merge the feature with
  main.

  If conflicts occur, the rebase will halt. You fix the conflicts and then run:

  ```
  git rebase --continue
  ```

  Or if you don't want to resolve the conflict (not advised):

  ```
  git rebase --skip
  ```

  Or stop the whole process:

  ```
  git rebase --abort
  ```

  ### Isolate the point at which one branch diverges from another

  This can be useful to check before you create a rebase:

  ```
  git merge-base main feature/some-feature

  c33acc84f06fcb94e0e87d9adb240c038da6d71c
  ```

  ## Golden rule of rebasing

  When we rebase we remove the additional merge commit but we are changing
  history. The commits are still there but the SHAs are changed. For this reason
  **you should not rebase a public branch**, otherwise you will mess up your
  collaborators history. You should do your rebasing locally and then make a pull
  request.

  ## Difference from cherry-picking

  The main difference between the two approaches is that
  [cherry-picking](Cherry_picking_a_branch.md) is a more selective
  process, where you can pick and choose specific commits that you want to include
  in another branch. This can be useful when you only want to apply specific
  changes or fixes from one branch to another, without including all the changes
  made in the original branch.

  On the other hand, rebasing is a more comprehensive process that can include all
  the changes from one branch to another, but it modifies the commit history,
  making it more linear and easier to understand. Unlike rebasing, cherry-picking
  does not modify the commit history of either branch.

  ## Squashing commits

  This is a handy feature of rebasing. It allows you to combine multiple commits
  into one. This is useful when you have been doing a lot of trial and error to
  resolve a bug and you have lots of small commits that are not that important in
  themselves.

  We use the interactive rebase tool for this.

  Checkout the branch where the commit you want to squash is located.

  Then run:

  ```
  git rebase -i HEAD~n
  ```

  Where `n` is the number of commits you want to squash starting from the most
  recent. This will open an interactive rebase window, listing the commits. You
  can then use the keywords to decide what you want to do with them. In our case
  this will be `s` for squash.

  ![](/img/git-interactive-rebase.png)

  ### Example

  I made a small fix commit because something went wrong during build. I do not
  want this commit to be in the history because it is so minor. Instead I want to
  incorporate these changes into the last public commit.

  Trivial commit is `xy` Previous commit is `aa`

  ```
  git rebase -i HEAD~2
  ```

  This displays:

  ```
  pick xy
  pick aa
  ```

  Change this to:

  ```
  pick xy
  squash aa
  ```

  Now `xy == aa`

  ### Another example

  Here we will rebase the interim commits into the last feature commit

  ```sh
  git rebase -i HEAD~10
  ```

  ```
  pick a691a7667 feature (shared-nav) add active app check
  pick 74a07fdb1 interim: add logging
  pick 9af4e2652 interim: more logging
  pick 51b36f667 interim: use href not origin
  pick 33f161198 interim : try catch handler
  pick 41c144ddf interim: remove didLoadCheck
  pick ebd1c9f24 interim: fix flash star
  pick 77af452e7 interim: rm redundant active app check
  pick 79a9b737a iterim: test ternary
  pick a90d23223 interim: further tidying
  ```

  ```
  pick a691a7667 feature (shared-nav) add active app check
  squash 74a07fdb1 interim: add logging
  squash 9af4e2652 interim: more logging
  squash 51b36f667 interim: use href not origin
  squash 33f161198 interim : try catch handler
  squash 41c144ddf interim: remove didLoadCheck
  squash ebd1c9f24 interim: fix flash star
  squash 77af452e7 interim: rm redundant active app check
  squash 79a9b737a iterim: test ternary
  squash a90d23223 interim: further tidying
  ```

  ## `git pull rebase`

  We can use this command to combine a rebase with a pull. This way we silently
  update our local version of a branch with the remote, without adding a merge
  commit.

  ```
  git pull --rebase
  ```

  Add `--rebase=preserve` to keep merges that have been done _locally_. It is
  generally a good idea to do this.

raw_content:
  ---
  tags:
    - git
  ---

  # Rebasing

  Rebasing is a way to integrate changes from one branch into another. In this
  regarding it is like merging a branch B into another branch A. However rebasing
  differs from normal merging in the way in which it modifies the Git history.

  In a normal merge of branch B into branch A, Git creates a new commit that
  combines the changes of branches B and A. This is known as the merge commit and
  is evident from the following automatic commit message that is generated:

  ```
  Merge branch B of github.com:thomasabishop/remote-repository into A
  ```

  ![](/img/normal-merge-again.svg)

  In this scenario the merge commit has two or more parent commits each
  representing the history of the merged branches. The resulting history of A will
  include the commits of B. Basically the two histories are combined.

  This would give us a history that looks like the following, with different
  colours for the separate SHAs of each merged branch:

  ![](/img/combined-merge-hist.svg)

  If we were to create a rebase branch of A from B, there would be a new singular
  history without distinguishing multiple parents that combines the commits of A
  and B. The rebased branch's commits are recreated with new commit IDs and new
  timestamps, based on the current state of the branch that you are rebasing onto.
  This is obviously potentially destructive because it does not preserve the
  respective branch's separate history. It rewrites history as a continuous stream
  of commits in a single branch.

  When a rebase is applied, it will put the diverging B commits at the tip of A
  like so:

  ![](/img/rebase-tip-chage.svg)

  And then rebrand the previous A commits to be continuous with B presenting a
  flat and linear Git history like the following:

  ![](/img/single-git-history-rebase.svg)

  ## Benefits, use-cases

  > The purpose of rebasing is to ensure that the commit history of a project is
  > as linear and simple as possible, by incorporating changes from one branch
  > into another, as if the changes had been made on the other branch all along.

  A common use-case is with feature branches as it makes the features fit more
  seamlessly with the `main` or `develop` branch.

  It is also a technique that can be used to integrate recent commits without
  merging.

  When to use one over the other:

  - **Merge** to allow commits to stand out or to be clearly grouped
  - **Merge** to bring large feature branches back into main
  - **Rebase** to add minor commits in a main branch into a feature branch
  - **Rebase** when you need to move commits from one branch to another

  So in terms of my personal workflow, I should be using

  - Merge when I want to bring a feature into develop
  - Merge when I want to bring develop into main for a release
  - Merge any time the feature branch is already public and being used by others.
  - Rebase to capture small changes in the parent branch
  - Rebase when I have branched from a branch because a refactor or something has
    become more complex than originally anticipated.

  ## Syntax

  For example if we are working on a feature branch off of `main`, we would run
  the following from this feature branch:

  ```
  git rebase main
  ```

  This will move the feature branch to the tip of `main`.

  If this completes successfully, we would then go on to merge the feature with
  main.

  If conflicts occur, the rebase will halt. You fix the conflicts and then run:

  ```
  git rebase --continue
  ```

  Or if you don't want to resolve the conflict (not advised):

  ```
  git rebase --skip
  ```

  Or stop the whole process:

  ```
  git rebase --abort
  ```

  ### Isolate the point at which one branch diverges from another

  This can be useful to check before you create a rebase:

  ```
  git merge-base main feature/some-feature

  c33acc84f06fcb94e0e87d9adb240c038da6d71c
  ```

  ## Golden rule of rebasing

  When we rebase we remove the additional merge commit but we are changing
  history. The commits are still there but the SHAs are changed. For this reason
  **you should not rebase a public branch**, otherwise you will mess up your
  collaborators history. You should do your rebasing locally and then make a pull
  request.

  ## Difference from cherry-picking

  The main difference between the two approaches is that
  [cherry-picking](Cherry_picking_a_branch.md) is a more selective
  process, where you can pick and choose specific commits that you want to include
  in another branch. This can be useful when you only want to apply specific
  changes or fixes from one branch to another, without including all the changes
  made in the original branch.

  On the other hand, rebasing is a more comprehensive process that can include all
  the changes from one branch to another, but it modifies the commit history,
  making it more linear and easier to understand. Unlike rebasing, cherry-picking
  does not modify the commit history of either branch.

  ## Squashing commits

  This is a handy feature of rebasing. It allows you to combine multiple commits
  into one. This is useful when you have been doing a lot of trial and error to
  resolve a bug and you have lots of small commits that are not that important in
  themselves.

  We use the interactive rebase tool for this.

  Checkout the branch where the commit you want to squash is located.

  Then run:

  ```
  git rebase -i HEAD~n
  ```

  Where `n` is the number of commits you want to squash starting from the most
  recent. This will open an interactive rebase window, listing the commits. You
  can then use the keywords to decide what you want to do with them. In our case
  this will be `s` for squash.

  ![](/img/git-interactive-rebase.png)

  ### Example

  I made a small fix commit because something went wrong during build. I do not
  want this commit to be in the history because it is so minor. Instead I want to
  incorporate these changes into the last public commit.

  Trivial commit is `xy` Previous commit is `aa`

  ```
  git rebase -i HEAD~2
  ```

  This displays:

  ```
  pick xy
  pick aa
  ```

  Change this to:

  ```
  pick xy
  squash aa
  ```

  Now `xy == aa`

  ### Another example

  Here we will rebase the interim commits into the last feature commit

  ```sh
  git rebase -i HEAD~10
  ```

  ```
  pick a691a7667 feature (shared-nav) add active app check
  pick 74a07fdb1 interim: add logging
  pick 9af4e2652 interim: more logging
  pick 51b36f667 interim: use href not origin
  pick 33f161198 interim : try catch handler
  pick 41c144ddf interim: remove didLoadCheck
  pick ebd1c9f24 interim: fix flash star
  pick 77af452e7 interim: rm redundant active app check
  pick 79a9b737a iterim: test ternary
  pick a90d23223 interim: further tidying
  ```

  ```
  pick a691a7667 feature (shared-nav) add active app check
  squash 74a07fdb1 interim: add logging
  squash 9af4e2652 interim: more logging
  squash 51b36f667 interim: use href not origin
  squash 33f161198 interim : try catch handler
  squash 41c144ddf interim: remove didLoadCheck
  squash ebd1c9f24 interim: fix flash star
  squash 77af452e7 interim: rm redundant active app check
  squash 79a9b737a iterim: test ternary
  squash a90d23223 interim: further tidying
  ```

  ## `git pull rebase`

  We can use this command to combine a rebase with a pull. This way we silently
  update our local version of a branch with the remote, without adding a merge
  commit.

  ```
  git pull --rebase
  ```

  Add `--rebase=preserve` to keep merges that have been done _locally_. It is
  generally a good idea to do this.


path: Global_object_in_NodeJS.md
sortable_path: Global_object_in_NodeJS.md
title: Global object
checksum: a833275912428951e9737156c72959670ed03b91fdbb78dac2aeb1cf52690ac3
created: 2025-10-01 13:30:58.967057941+00:00
modified: 2025-08-13 15:57:13.955361839+00:00
metadata: {"tags":["node-js"]}
lead:
  > In Node every function and variable should be scoped to a module. We should
  > not define functions and variables within the global scope.

body:
  > In Node every function and variable should be scoped to a module. We should
  > not define functions and variables within the global scope.

  - In Node the equivalent to the browser's `Window` object is `global`. The
    properties and methods that belong to this method are available anywhere in a
    program.

  - Just as we can technically write `Window.console.log()`, we can write
    `global.console.log()` however in both cases it is more sane to use the
    shorthand.

  - However if we declare a variable in this scope in browser-based JavaScript,
    this variable becomes accessible via the `Window` object and thus is
    accessible in global scope. The same is not true for Node. If you declare a
    variable at this level it will return undefined.

  - This is because of Node's modular nature. If you were to define a function
    `foo` in a module and then also define it in the global scope, when you call
    `foo`, the Node interpreter would not know which function to call. Hence it
    chooses not to recognise the global `foo`, returning undefined.

raw_content:
  ---
  tags:
    - node-js
  ---

  # Global object

  > In Node every function and variable should be scoped to a module. We should
  > not define functions and variables within the global scope.

  - In Node the equivalent to the browser's `Window` object is `global`. The
    properties and methods that belong to this method are available anywhere in a
    program.

  - Just as we can technically write `Window.console.log()`, we can write
    `global.console.log()` however in both cases it is more sane to use the
    shorthand.

  - However if we declare a variable in this scope in browser-based JavaScript,
    this variable becomes accessible via the `Window` object and thus is
    accessible in global scope. The same is not true for Node. If you declare a
    variable at this level it will return undefined.

  - This is because of Node's modular nature. If you were to define a function
    `foo` in a module and then also define it in the global scope, when you call
    `foo`, the Node interpreter would not know which function to call. Hence it
    chooses not to recognise the global `foo`, returning undefined.


path: Grep.md
sortable_path: Grep.md
title: Grep
lead: ## Purpose of `grep`
checksum: be1a28cd33fe7976d80627887f90d029ed84591c7e294bc75165973cc3f70c59
created: 2025-10-01 13:30:58.970140607+00:00
modified: 2025-08-13 15:57:14.09465378+00:00
metadata: {"tags":["shell"]}
body:
  ## Purpose of `grep`

  `grep` stands for “global regular expression print”. It allows you to search
  plain text data sets for strings which match a regular expression or pattern.

  ## Syntax

  ### Schematic

  ```bash
  grep [options] [pattern] [source file] > [output file]
  ```

  Note that above we redirect the file matches to a new file. You don't have to do
  this. If you omit the redirection, `grep` will output to standard output.

  ### Applied

  ```bash
  grep -i -n "banana" fruits.txt > banana.txt
  ```

  The above example searches, using regex, for strings matching the pattern
  “banana” in the file `fruits.txt` regardless of the character case (`-i` ensures
  this) and outputs its findings to the file `banana.txt`, with the line number
  where the match occurs appended to the output (`-n` takes care of this).

  Note that for simplicity, you can chain optional values together, i.e. the
  options in the above example could be input as `-in`.

  ## Useful options

  - ignore case: `i`
  - count matches instead of returning actual match: `-c`
  - precede each match with the line number where it occurs: `-n`
  - invert the match (show everything that doesn't match the expression): `-v`
  - search entire directories recursively: `-r`
  - list file names where matches occur (in the scenario of a recursive match):
    `-l`

  ## `ripgrep`

  `ripgrep` is generally faster however it does not come as default with Unix and
  only works recursively, i.e. it is designed to find strings within files within
  multiple directories not just single files or piped streams.

  It also respects `.gitignore` files that it finds within directories by default
  and `node_modules` which is really handy.

  Most of the standard `grep` options transfer over.

raw_content:
  ---
  tags:
    - shell
  ---

  # Grep

  ## Purpose of `grep`

  `grep` stands for “global regular expression print”. It allows you to search
  plain text data sets for strings which match a regular expression or pattern.

  ## Syntax

  ### Schematic

  ```bash
  grep [options] [pattern] [source file] > [output file]
  ```

  Note that above we redirect the file matches to a new file. You don't have to do
  this. If you omit the redirection, `grep` will output to standard output.

  ### Applied

  ```bash
  grep -i -n "banana" fruits.txt > banana.txt
  ```

  The above example searches, using regex, for strings matching the pattern
  “banana” in the file `fruits.txt` regardless of the character case (`-i` ensures
  this) and outputs its findings to the file `banana.txt`, with the line number
  where the match occurs appended to the output (`-n` takes care of this).

  Note that for simplicity, you can chain optional values together, i.e. the
  options in the above example could be input as `-in`.

  ## Useful options

  - ignore case: `i`
  - count matches instead of returning actual match: `-c`
  - precede each match with the line number where it occurs: `-n`
  - invert the match (show everything that doesn't match the expression): `-v`
  - search entire directories recursively: `-r`
  - list file names where matches occur (in the scenario of a recursive match):
    `-l`

  ## `ripgrep`

  `ripgrep` is generally faster however it does not come as default with Unix and
  only works recursively, i.e. it is designed to find strings within files within
  multiple directories not just single files or piped streams.

  It also respects `.gitignore` files that it finds within directories by default
  and `node_modules` which is really handy.

  Most of the standard `grep` options transfer over.


path: Ground.md
sortable_path: Ground.md
title: Ground
checksum: 06d6a82b347c9527a50fa40afccff167a415ff9ab7bfed33ff87c58dc8ae473d
created: 2025-10-01 13:30:58.973742561+00:00
modified: 2025-08-13 15:57:13.979722933+00:00
metadata: {"tags":["physics","electricity"]}
lead:
  The term 'ground' is used as a general reference for 'zero potential': the point
  in an electrical circuit where there is no potential difference between the
  positive and negative terminals of the voltage source.

body:
  The term 'ground' is used as a general reference for 'zero potential': the point
  in an electrical circuit where there is no potential difference between the
  positive and negative terminals of the voltage source.

  As a consequence of the lack of potential, ground is a point at which no current
  can be generated.

  The term originates from domestic electrical systems where the circuit box is
  literally attached to the ground (represented by the green and yellow ribboned
  wires in plug sockets and light fixtures etc) where current is discharged to
  prevent appliances emitting electrical shocks.

  In circuit diagrams with simple DC current, ground is taken to be the negative
  terminal of the voltage source.

  The symbol for ground in circuit diagrams:

  <img src="/home/thomas/repos/computer_science/img/ground-symbol.png" width="300px">

raw_content:
  ---
  tags: [physics, electricity]
  ---

  # Ground

  The term 'ground' is used as a general reference for 'zero potential': the point
  in an electrical circuit where there is no potential difference between the
  positive and negative terminals of the voltage source.

  As a consequence of the lack of potential, ground is a point at which no current
  can be generated.

  The term originates from domestic electrical systems where the circuit box is
  literally attached to the ground (represented by the green and yellow ribboned
  wires in plug sockets and light fixtures etc) where current is discharged to
  prevent appliances emitting electrical shocks.

  In circuit diagrams with simple DC current, ground is taken to be the negative
  terminal of the voltage source.

  The symbol for ground in circuit diagrams:

  <img src="/home/thomas/repos/computer_science/img/ground-symbol.png" width="300px">


path: Guide_to_YAML.md
sortable_path: Guide_to_YAML.md
title: Guide to YAML
checksum: 22c8571439ba88961c2f29fa85f550ded8ffcbe85324bac952d3ee59bf419992
created: 2025-10-01 13:30:58.978157864+00:00
modified: 2025-08-13 15:57:13.981541385+00:00
metadata: {"tags":["yaml"]}
lead:
  - Data description language
  - Alternative to XML and JSON
  - Easy to read by humans and process by code

body:
  - Data description language
  - Alternative to XML and JSON
  - Easy to read by humans and process by code

  ## Main rule

  > Never use tabs for indentation, use two spaces

  ## Available data types

  - strings
  - numbers
  - booleans
  - arrays
  - maps (key, value pairs)

  ### Example

  ````yaml
  # Basic scalar data types
  age: 51
  greeting: Hello, world
  length: 182.3
  happy: true
  ``

  ```yaml
  # array
  pets:
    - cat
    - dog

  # nested array
  pets:
    - cat
    - dog
      - staffy
      - pitty
  ````

  ```yaml
  # map
  languages:
    - java: advanced
    - python: average
    - javascript: basic
  ```

  ### Strings

  - Don't have to use quotes unless string contains reserved YAML characters
    (dashes, colons etc)
  - Can use single or double quotes

  ### Multi-line text

  Two methods:

  ```yaml
  # Literal block preserves new line characters
  literal_block: |
    Lorem ipsum dolar sit avcc lorem dolar 
    Blah blah
    Blah
  ```

  ```yaml
  folded_block: >
    Each different line will be folded back on itself so you don't have to keep
    scrolling to the right
  ```

raw_content:
  ---
  tags:
    - yaml
  ---

  # Guide to YAML

  - Data description language
  - Alternative to XML and JSON
  - Easy to read by humans and process by code

  ## Main rule

  > Never use tabs for indentation, use two spaces

  ## Available data types

  - strings
  - numbers
  - booleans
  - arrays
  - maps (key, value pairs)

  ### Example

  ````yaml
  # Basic scalar data types
  age: 51
  greeting: Hello, world
  length: 182.3
  happy: true
  ``

  ```yaml
  # array
  pets:
    - cat
    - dog

  # nested array
  pets:
    - cat
    - dog
      - staffy
      - pitty
  ````

  ```yaml
  # map
  languages:
    - java: advanced
    - python: average
    - javascript: basic
  ```

  ### Strings

  - Don't have to use quotes unless string contains reserved YAML characters
    (dashes, colons etc)
  - Can use single or double quotes

  ### Multi-line text

  Two methods:

  ```yaml
  # Literal block preserves new line characters
  literal_block: |
    Lorem ipsum dolar sit avcc lorem dolar 
    Blah blah
    Blah
  ```

  ```yaml
  folded_block: >
    Each different line will be folded back on itself so you don't have to keep
    scrolling to the right
  ```


path: HTTPS.md
sortable_path: HTTPS.md
title: HTTPS
checksum: acc6bc1db39682003becdecd7d512d72398168b76cdbb1bc254e1c867012aa51
created: 2025-10-01 13:30:58.982535217+00:00
modified: 2025-08-13 15:57:14.044599644+00:00
metadata: {"created":"Friday, December 14, 2024","tags":["world-wide-web","internet","encryption","servers"]}
lead:
  The problem with standard, unencrypted HTTP requests is that the data can be
  captured in transit and be observed or modified by malicious actors.

body:
  The problem with standard, unencrypted HTTP requests is that the data can be
  captured in transit and be observed or modified by malicious actors.

  HTTPS is the solution. It encrypts the entirety of the HTTP request and response
  (URL, headers, body).

  It utilises two types of encryption to send messages securely:

  - symmetric encryption
    - a single shared key between client and server to encrypt the messages sent
      between them
  - asymmetric encryption
    - two keys are used: a [public key](./bbdcb54f_public_key_cryptography.md) to
      encrypt the data and a private key to decrypt it. The public key can be
      shared freely so anyone can encrypt and send data to a peer but only the
      peer can receive and decrypt it

  > Symmetric encryption applies once the server has been authenticated and its
  > public key has been shared with the client. Asymmetric encryption applies at
  > the initiation phase only.

  The encryption protocol used is TLS - the **Transport Layer Security Protocol**.
  Previously SSL (Secure Sockets Layer) was used but has been deprecated over
  security concerns.

  The initial handshake process takes place entirely within the Network Layer,
  over TCP. Only once encryption is secured is HTTPS activated at the
  [Application Layer](./Application_Layer_of_Internet_Protocol.md)

  ## How communication over HTTPs works

  ![Server handshake diagram](../img/https-handshake.png)

  - An initial handshake is shared between client and server, agreeing that
    communication will be encrypted. This is public. "Client hello" and "Server
    hello". Some random numbers are exchanged at this point
  - The server sends a digital [certificate](./Certificate_authorities.md) key The
    client checks that the certificate is valid
  - If valid, the client encrypts a string of bytes using the public key and sends
    it to the server. This is called the "premaster secret"
  - This is decrypted by the server
  - The premaster secret, along with the random bytes from the handshake is then
    used to compute a shared secret key (symmetric) which is used to encrypt all
    subsequent messages for the duration of the session

  ## Caching

  Once symmetric exchange is achieved the TLS handshake is complete and lasts
  typically until either terminates it.

  However, some caching does occur on both the server and browser side. Both use
  session IDs to enact an abbreviated handshake such as when the session cache
  expires. This way, certificate validation and the asymmetric exchange can be
  skipped. These tickets are shared as part of the original exchange and the
  client can present them to the server as proof they already authenticated when
  they come back later.

raw_content:
  ---
  tags: [world-wide-web, internet, encryption, servers]
  created: Friday, December 14, 2024
  ---

  # HTTPS

  The problem with standard, unencrypted HTTP requests is that the data can be
  captured in transit and be observed or modified by malicious actors.

  HTTPS is the solution. It encrypts the entirety of the HTTP request and response
  (URL, headers, body).

  It utilises two types of encryption to send messages securely:

  - symmetric encryption
    - a single shared key between client and server to encrypt the messages sent
      between them
  - asymmetric encryption
    - two keys are used: a [public key](./bbdcb54f_public_key_cryptography.md) to
      encrypt the data and a private key to decrypt it. The public key can be
      shared freely so anyone can encrypt and send data to a peer but only the
      peer can receive and decrypt it

  > Symmetric encryption applies once the server has been authenticated and its
  > public key has been shared with the client. Asymmetric encryption applies at
  > the initiation phase only.

  The encryption protocol used is TLS - the **Transport Layer Security Protocol**.
  Previously SSL (Secure Sockets Layer) was used but has been deprecated over
  security concerns.

  The initial handshake process takes place entirely within the Network Layer,
  over TCP. Only once encryption is secured is HTTPS activated at the
  [Application Layer](./Application_Layer_of_Internet_Protocol.md)

  ## How communication over HTTPs works

  ![Server handshake diagram](../img/https-handshake.png)

  - An initial handshake is shared between client and server, agreeing that
    communication will be encrypted. This is public. "Client hello" and "Server
    hello". Some random numbers are exchanged at this point
  - The server sends a digital [certificate](./Certificate_authorities.md) key The
    client checks that the certificate is valid
  - If valid, the client encrypts a string of bytes using the public key and sends
    it to the server. This is called the "premaster secret"
  - This is decrypted by the server
  - The premaster secret, along with the random bytes from the handshake is then
    used to compute a shared secret key (symmetric) which is used to encrypt all
    subsequent messages for the duration of the session

  ## Caching

  Once symmetric exchange is achieved the TLS handshake is complete and lasts
  typically until either terminates it.

  However, some caching does occur on both the server and browser side. Both use
  session IDs to enact an abbreviated handshake such as when the session cache
  expires. This way, certificate validation and the asymmetric exchange can be
  skipped. These tickets are shared as part of the original exchange and the
  client can present them to the server as proof they already authenticated when
  they come back later.


path: HTTP_request_types.md
sortable_path: HTTP_request_types.md
title: HTTP request types
lead: ## GET
body:
  ## GET

  - Get data

  ## POST

  - Create data

  ## PUT

  - Update data

  ## DELETE

  - Remove data

checksum: e2e8f9de5eeea50e69c7e5a4b92f8917d48f0ca9b47c70024d8ded12ec067f65
created: 2025-10-01 13:30:58.997772671+00:00
modified: 2025-08-13 15:57:13.804990632+00:00
metadata: {"tags":["REST"]}
raw_content:
  ---
  tags:
    - REST
  ---

  # HTTP request types

  ## GET

  - Get data

  ## POST

  - Create data

  ## PUT

  - Update data

  ## DELETE

  - Remove data


path: Half_adder_and_full_adder.md
sortable_path: Half_adder_and_full_adder.md
title: The half adder and full adder
lead: ## Binary arithmetic
checksum: 6c57575017ae451e8a8b357b8e8a6c8cdab2de4305b514ab62eaa9bfeb654556
created: 2025-10-01 13:30:59.001428299+00:00
modified: 2025-08-13 15:57:13.921892712+00:00
metadata: {"tags":["logic-gates","binary"]}
body:
  ## Binary arithmetic

  The half adder and full adder are components of digital circuits that enable us
  to carry out binary addition. Using adders and half adders we can add two binary
  numbers together. Adders are a type of [integrated circuit]() comprising certain
  [logic gates](Logic_gates.md) where the arrangement allows for the
  representation of the addition of bits.

  ### Example addition

  Consider the following binary addition:

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  How does the calculation proceed? Moving from the right-most column:

  - $1 + 0 = 1$ hence the sum of the first column (the binary 1 coumn) is $1$ with
    no carry
  - Moving to the second column (the binary 2 column), $1 + 1 = 2$ however we do
    not have the digit "2" in binary, therefore we must bump the place value by
    one place and carry this sum as a $1$ leaving a zero to mark the place value
  - Thus the third column (the binary 4 column) has the calculation:
    $1 + 0 + 0 = 1$ with no carry.

  ### Bits

  From the previous example we can identified the following repeated actions:

  - For calculations of the form $1 + 0$ or $0 + 0$ there will be no carried digit
  - For calculations of the form $1 + 1$ there will be a carried digit

  In the context of adders the digits are bits. We distinguish three core bits by
  their role in the calculation: the carry, the **least significant bit** and the
  **most significant bit**. The least significant bit is the right-most digit in
  the calculation. The most significant bit is the leftmost.

  The two types of adders are distinguished by which bits of the calculation they
  operate on. The half adder focuses on the least significant bit whereas the full
  adder focuses on the most significant bit.

  ## Half adder

  The half adder receives two bits (A and B) which are to be added together. It
  outputs this value as the **sum bit**. If there is a bit to be carried to the
  next column in the binary calculation this will be output as the **carry-out**
  bit.

  | A                            | B                             | S           | C_out             |
  | ---------------------------- | ----------------------------- | ----------- | ----------------- |
  | The first number to be added | The second number to be added | The sum bit | The carry-out bit |

  The diagram below shows the circuit representation of a half-adder and an
  example calculation. This calculation matches the ones column of the earlier
  binary addition example: $0011 + 0010$.

  ![](/img/half-adder-new.png)

  ### Implementation with logic gates

  If we think about it, the possible inputs and outputs of a half adder are highly
  circumscribed:

  - If the sum exceeds $1$, the sum bit will be $0$ and the carry-out bit will be
    $1$
  - In all other cases the carry-out bit will be $0$. These other cases are when
    the sum bit is either $0$ or $1$, e.g: $1 + 0$ or $0 + 0$.

  We can represent this with a simple truth-table:

  | A   | B   | S   | C_out |
  | --- | --- | --- | ----- |
  | 0   | 0   | 0   | 0     |
  | 0   | 1   | 1   | 0     |
  | 1   | 0   | 1   | 0     |
  | 1   | 1   | 0   | 1     |

  We can see that the sum bit column replicates the truth-conditions of
  [XOR](Logic_gates.md#xor-gate):

  | P   | Q   | P V Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | T     |
  | F   | T   | T     |
  | F   | F   | F     |

  And the carry-out bit replicates the truth conditions of
  [AND](Logic_gates.md#and-gate):

  | P   | Q   | P & Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | F     |
  | F   | T   | F     |
  | F   | F   | T     |

  It is therefore possible to implement a half-adder with just these two logic
  gates:

  ![](/img/half-adder-gates-three.png)

  The digital circuit above has the same inputs and outputs as the half adder
  diagram above.

  <iframe src="https://circuitverse.org/simulator/embed/half-adder-67b14a9f-d1ed-4240-ab19-1d753f18a40d" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

  ## Full adder

  As the half adder only calculates the least significant bit, it is not
  sufficient by itself to complete a binary addition; it cannot account for
  movements in binary place value. To carry out full calculations it must be
  supplemented with the full adder.

  The full adder takes in three inputs and has two outputs. It is identical to the
  half-adder apart from the fact that one of its inputs is **carry-in**. This is
  equivalent to the value that is designated as **carry-out** in a half adder. It
  is an incoming value that is the product of a previous operation that resulted
  in a carry-out. This is added together with the full adders own two inputs (A
  and B) and like the half adder, generates a sum bit and a carry-out bit.

  | A                            | B                             | C_in                     | S                      | C_out                        |
  | ---------------------------- | ----------------------------- | ------------------------ | ---------------------- | ---------------------------- |
  | The first number to be added | The second number to be added | The incoming carried bit | The sum bit (A+B+C_in) | The carry-out bit (A+B+C_in) |

  ![](/img/full-adder-new.png)

  The diagram above is equivalent to the calculation taking place in the fours
  column. It has received a carry from the twos column ($1 + 1$ results in $1$ as
  a carry) and then adds this together with its own inputs ($0$ and $0$).

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  ### Implementation with logic gates

  When it comes to implementing the full adder with logic gates we can allow
  ourselves greater abstraction as we already have the half adder to work with. We
  don't need to create a half adder from scratch, we can reuse it.

  We are adding three bits: $1$, $0$ and $0$. This can be achieved with two half
  adders:

  - One half adder (HA1) for the sum of $0 + 0$
  - One half adder (HA2) for the sum of $(0 + 0) + 1$ (the previous sum plus the
    third bit)

  The sum of HA1 ($0 + 0$) is passed in to the B input on HA2 and the $1$ is
  passed in as the A input of HA2. This gives us $1$ as the sum bit of HA2.

  At this point we have completed the addition and have successfully added the
  three bits: $1$, $0$, and $0$ to get $1$ as ths sum. However we also have to
  account for the fact that the addition may result in its own carry-out bit. What
  if the inputs were $1, 1, 0$ for example?

  <iframe src="https://circuitverse.org/simulator/embed/full-adder-674f8ddf-c5aa-42fa-9c0f-4349ffabe723" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="700" allowFullScreen></iframe>

raw_content:
  ---
  tags: [logic-gates, binary]
  ---

  # The half adder and full adder

  ## Binary arithmetic

  The half adder and full adder are components of digital circuits that enable us
  to carry out binary addition. Using adders and half adders we can add two binary
  numbers together. Adders are a type of [integrated circuit]() comprising certain
  [logic gates](Logic_gates.md) where the arrangement allows for the
  representation of the addition of bits.

  ### Example addition

  Consider the following binary addition:

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  How does the calculation proceed? Moving from the right-most column:

  - $1 + 0 = 1$ hence the sum of the first column (the binary 1 coumn) is $1$ with
    no carry
  - Moving to the second column (the binary 2 column), $1 + 1 = 2$ however we do
    not have the digit "2" in binary, therefore we must bump the place value by
    one place and carry this sum as a $1$ leaving a zero to mark the place value
  - Thus the third column (the binary 4 column) has the calculation:
    $1 + 0 + 0 = 1$ with no carry.

  ### Bits

  From the previous example we can identified the following repeated actions:

  - For calculations of the form $1 + 0$ or $0 + 0$ there will be no carried digit
  - For calculations of the form $1 + 1$ there will be a carried digit

  In the context of adders the digits are bits. We distinguish three core bits by
  their role in the calculation: the carry, the **least significant bit** and the
  **most significant bit**. The least significant bit is the right-most digit in
  the calculation. The most significant bit is the leftmost.

  The two types of adders are distinguished by which bits of the calculation they
  operate on. The half adder focuses on the least significant bit whereas the full
  adder focuses on the most significant bit.

  ## Half adder

  The half adder receives two bits (A and B) which are to be added together. It
  outputs this value as the **sum bit**. If there is a bit to be carried to the
  next column in the binary calculation this will be output as the **carry-out**
  bit.

  | A                            | B                             | S           | C_out             |
  | ---------------------------- | ----------------------------- | ----------- | ----------------- |
  | The first number to be added | The second number to be added | The sum bit | The carry-out bit |

  The diagram below shows the circuit representation of a half-adder and an
  example calculation. This calculation matches the ones column of the earlier
  binary addition example: $0011 + 0010$.

  ![](/img/half-adder-new.png)

  ### Implementation with logic gates

  If we think about it, the possible inputs and outputs of a half adder are highly
  circumscribed:

  - If the sum exceeds $1$, the sum bit will be $0$ and the carry-out bit will be
    $1$
  - In all other cases the carry-out bit will be $0$. These other cases are when
    the sum bit is either $0$ or $1$, e.g: $1 + 0$ or $0 + 0$.

  We can represent this with a simple truth-table:

  | A   | B   | S   | C_out |
  | --- | --- | --- | ----- |
  | 0   | 0   | 0   | 0     |
  | 0   | 1   | 1   | 0     |
  | 1   | 0   | 1   | 0     |
  | 1   | 1   | 0   | 1     |

  We can see that the sum bit column replicates the truth-conditions of
  [XOR](Logic_gates.md#xor-gate):

  | P   | Q   | P V Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | T     |
  | F   | T   | T     |
  | F   | F   | F     |

  And the carry-out bit replicates the truth conditions of
  [AND](Logic_gates.md#and-gate):

  | P   | Q   | P & Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | F     |
  | F   | T   | F     |
  | F   | F   | T     |

  It is therefore possible to implement a half-adder with just these two logic
  gates:

  ![](/img/half-adder-gates-three.png)

  The digital circuit above has the same inputs and outputs as the half adder
  diagram above.

  <iframe src="https://circuitverse.org/simulator/embed/half-adder-67b14a9f-d1ed-4240-ab19-1d753f18a40d" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

  ## Full adder

  As the half adder only calculates the least significant bit, it is not
  sufficient by itself to complete a binary addition; it cannot account for
  movements in binary place value. To carry out full calculations it must be
  supplemented with the full adder.

  The full adder takes in three inputs and has two outputs. It is identical to the
  half-adder apart from the fact that one of its inputs is **carry-in**. This is
  equivalent to the value that is designated as **carry-out** in a half adder. It
  is an incoming value that is the product of a previous operation that resulted
  in a carry-out. This is added together with the full adders own two inputs (A
  and B) and like the half adder, generates a sum bit and a carry-out bit.

  | A                            | B                             | C_in                     | S                      | C_out                        |
  | ---------------------------- | ----------------------------- | ------------------------ | ---------------------- | ---------------------------- |
  | The first number to be added | The second number to be added | The incoming carried bit | The sum bit (A+B+C_in) | The carry-out bit (A+B+C_in) |

  ![](/img/full-adder-new.png)

  The diagram above is equivalent to the calculation taking place in the fours
  column. It has received a carry from the twos column ($1 + 1$ results in $1$ as
  a carry) and then adds this together with its own inputs ($0$ and $0$).

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  ### Implementation with logic gates

  When it comes to implementing the full adder with logic gates we can allow
  ourselves greater abstraction as we already have the half adder to work with. We
  don't need to create a half adder from scratch, we can reuse it.

  We are adding three bits: $1$, $0$ and $0$. This can be achieved with two half
  adders:

  - One half adder (HA1) for the sum of $0 + 0$
  - One half adder (HA2) for the sum of $(0 + 0) + 1$ (the previous sum plus the
    third bit)

  The sum of HA1 ($0 + 0$) is passed in to the B input on HA2 and the $1$ is
  passed in as the A input of HA2. This gives us $1$ as the sum bit of HA2.

  At this point we have completed the addition and have successfully added the
  three bits: $1$, $0$, and $0$ to get $1$ as ths sum. However we also have to
  account for the fact that the addition may result in its own carry-out bit. What
  if the inputs were $1, 1, 0$ for example?

  <iframe src="https://circuitverse.org/simulator/embed/full-adder-674f8ddf-c5aa-42fa-9c0f-4349ffabe723" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="700" allowFullScreen></iframe>


path: Handle_multiple_endpoints_single_lambda.md
sortable_path: Handle_multiple_endpoints_single_lambda.md
title: Handle multiple API Gateway endpoints in a single lambda
lead:
  With the following config we have a single lambda (`/query-waktime`) with
  multiple endpoints:

checksum: 64aa42198251e03ef3600ba126849e631e989fd286ce097a316ad6fe91a04379
created: 2025-10-01 13:30:59.016955887+00:00
modified: 2025-08-13 15:57:13.993947206+00:00
metadata: {"tags":["AWS","aws-lambda"]}
body:
  With the following config we have a single lambda (`/query-waktime`) with
  multiple endpoints:

  ```yml
  Resources:
    QueryWakatimeFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: src/query-wakatime/
        Handler: index.handler
        Events:
          MainMetrics:
            Type: Api
            Properties:
              Path: /query-wakatime/main-metrics
              Method: get
              Auth:
                Authorizer: AWS_IAM
          Durations:
            Type: Api
            Properties:
              Path: /query-wakatime/durations
              Method: get
              Auth:
                Authorizer: AWS_IAM
          TodayOnly:
            Type: Api
            Properties:
              Path: /query-wakatime/today-only
              Method: get
              Auth:
                Authorizer: AWS_IAM
  ```

  Our endpoints are as follows:

  - `/query-wakatime/main-metrics`
  - `/query-wakatime/durations`
  - `/query-wakatime/today-only`

  Each returns a different dataset from an external API. The lambda function makes
  a different request to the API based on the endpoint it receives and the time
  period that is passed as a query string parameter, e.g.
  `/query-wakatime?range=last_7_days`.

  Below is a template for concisely handling requests to the different endpoints
  in the single lambda function wihout excessive repetition.

  ```js
  const getEndpoint = (path: string, timePeriod?: string | undefined) => {
    const basePath = "/users/current";
    switch (path) {
      case "/query-wakatime/main-metrics":
        return `${basePath}/stats/${timePeriod}`;
      case "/query-wakatime/durations":
        return `${basePath}/summaries?range=${timePeriod}`;
      case "/query-wakatime/today-only":
        return `${basePath}/status_bar/today`;
    }
  };

  export const handler = async (event) => {
    const fetchClient = new FetchClient("https://wakatime.com/api/v1/");
    const path = event.path;
    const timePeriod = event.queryStringParameters?.timePeriod;
    const endpoint = getEndpoint(path, timePeriod);

    const init: RequestInit = {
      headers: {
        Authorization: `Basic ${encodedApiKey}`,
      },
    };

    if (!endpoint) {
      throw new Error("Endpoint is undefined")
    }

   let data = await fetchClient.get(endpoint, init)
   let dataJson = (await data.json()) as string

   response = {
      statusCode: 200,
      body: JSON.stringify(dataJson),
      headers: responseHeaders,
      }
  };



  ```

raw_content:
  ---
  tags:
    - AWS
    - aws-lambda
  ---

  # Handle multiple API Gateway endpoints in a single lambda

  With the following config we have a single lambda (`/query-waktime`) with
  multiple endpoints:

  ```yml
  Resources:
    QueryWakatimeFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: src/query-wakatime/
        Handler: index.handler
        Events:
          MainMetrics:
            Type: Api
            Properties:
              Path: /query-wakatime/main-metrics
              Method: get
              Auth:
                Authorizer: AWS_IAM
          Durations:
            Type: Api
            Properties:
              Path: /query-wakatime/durations
              Method: get
              Auth:
                Authorizer: AWS_IAM
          TodayOnly:
            Type: Api
            Properties:
              Path: /query-wakatime/today-only
              Method: get
              Auth:
                Authorizer: AWS_IAM
  ```

  Our endpoints are as follows:

  - `/query-wakatime/main-metrics`
  - `/query-wakatime/durations`
  - `/query-wakatime/today-only`

  Each returns a different dataset from an external API. The lambda function makes
  a different request to the API based on the endpoint it receives and the time
  period that is passed as a query string parameter, e.g.
  `/query-wakatime?range=last_7_days`.

  Below is a template for concisely handling requests to the different endpoints
  in the single lambda function wihout excessive repetition.

  ```js
  const getEndpoint = (path: string, timePeriod?: string | undefined) => {
    const basePath = "/users/current";
    switch (path) {
      case "/query-wakatime/main-metrics":
        return `${basePath}/stats/${timePeriod}`;
      case "/query-wakatime/durations":
        return `${basePath}/summaries?range=${timePeriod}`;
      case "/query-wakatime/today-only":
        return `${basePath}/status_bar/today`;
    }
  };

  export const handler = async (event) => {
    const fetchClient = new FetchClient("https://wakatime.com/api/v1/");
    const path = event.path;
    const timePeriod = event.queryStringParameters?.timePeriod;
    const endpoint = getEndpoint(path, timePeriod);

    const init: RequestInit = {
      headers: {
        Authorization: `Basic ${encodedApiKey}`,
      },
    };

    if (!endpoint) {
      throw new Error("Endpoint is undefined")
    }

   let data = await fetchClient.get(endpoint, init)
   let dataJson = (await data.json()) as string

   response = {
      statusCode: 200,
      body: JSON.stringify(dataJson),
      headers: responseHeaders,
      }
  };



  ```


path: Handling_negative_fractions.md
sortable_path: Handling_negative_fractions.md
title: Negative fractions
checksum: 134790bda657057e507a4087eb4d4ce42197cdcf7f3eb41586397ff6af015de4
created: 2025-10-01 13:30:59.019912995+00:00
modified: 2025-08-13 15:57:14.021041607+00:00
metadata: {"tags":["prealgebra","fractions"]}
lead:
  To work with negative fractions we draw on the
  [Rules for operations on like and unlike terms](Rules%20for%20operations%20on%20like%20and%20unlike%20terms.md).

body:
  To work with negative fractions we draw on the
  [Rules for operations on like and unlike terms](Rules%20for%20operations%20on%20like%20and%20unlike%20terms.md).

  ## Fractions with unlike terms

  - A fraction is just one number divided by another. $\frac{5}{10}$ is just ten
    divided by 5.

  - A positive integer divided by a negative or vice versa will always result in a
    negative. Thus $\frac{5}{-15}$ is equal to $-3$.

  - We can therefore express the whole fraction as a negative:

    $$
    -	\frac{5}{15}
    $$

  - Or we could apply the negative symbol to the numerator. It would stand for the
    same value:
    $$
    \\frac{-5}{15}
    $$

  Therefore:

  > Let $a,b$ be any integers. The following three fractions are
  > [equivalent](Equivalent%20fractions.md):
  > $$\frac{-5}{15}, \frac{5}{-15}, - \frac{5}{15}$$

  ## Fractions with like terms

  - In cases where both the numerator and denominator are both negative, the value
    that the fraction represents will be positive overall. This is because the
    quotient of a negative integer divided by a negative integer will always be
    positive.

  - Thus: $$ \frac{- 12xy^2}{ - 18xy^2} = \frac{12xy^2}{18xy^2}$$

raw_content:
  ---
  tags:
    - prealgebra
    - fractions
  ---

  # Negative fractions

  To work with negative fractions we draw on the
  [Rules for operations on like and unlike terms](Rules%20for%20operations%20on%20like%20and%20unlike%20terms.md).

  ## Fractions with unlike terms

  - A fraction is just one number divided by another. $\frac{5}{10}$ is just ten
    divided by 5.

  - A positive integer divided by a negative or vice versa will always result in a
    negative. Thus $\frac{5}{-15}$ is equal to $-3$.

  - We can therefore express the whole fraction as a negative:

    $$
    -	\frac{5}{15}
    $$

  - Or we could apply the negative symbol to the numerator. It would stand for the
    same value:
    $$
    \\frac{-5}{15}
    $$

  Therefore:

  > Let $a,b$ be any integers. The following three fractions are
  > [equivalent](Equivalent%20fractions.md):
  > $$\frac{-5}{15}, \frac{5}{-15}, - \frac{5}{15}$$

  ## Fractions with like terms

  - In cases where both the numerator and denominator are both negative, the value
    that the fraction represents will be positive overall. This is because the
    quotient of a negative integer divided by a negative integer will always be
    positive.

  - Thus: $$ \frac{- 12xy^2}{ - 18xy^2} = \frac{12xy^2}{18xy^2}$$


path: Hardware_Description_Language.md
sortable_path: Hardware_Description_Language.md
title: Hardware Description Language
checksum: 165dfe31b4879e4bb4e7cb751f68c3732809d918fb7c78529bc82727f3eadbcb
created: 2025-10-01 13:30:59.034537924+00:00
modified: 2025-08-13 15:57:13.780078177+00:00
metadata: {"tags":["nand-to-tetris"]}
lead:
  An HDL is a declarative programming language used to describe the behaviour or
  structure of
  [digital circuits](Integrated_circuits.md).
  They are used to simulate the circuit and check its response.

body:
  An HDL is a declarative programming language used to describe the behaviour or
  structure of
  [digital circuits](Integrated_circuits.md).
  They are used to simulate the circuit and check its response.

  The hardware designer specifies a chip's logic by writing an HDL program which
  is then rigorously tested. At this stage, a
  [hardware simulator](Hardware_simulation.md) takes the
  HDL program as input and creates a software representation of the chip logic.
  The designer can instruct the simulator to test the virtual chip on various sets
  of inputs. This is done to check the chip's functionality but also to benchmark
  a variety of other parameters such as speed of computation and energy
  consumption.

  There are many HDLs but the most popular are VHDL ("very high speed
  integrated-circuit description language") and Verilog.

  ## Usage in _NAND to Tetris_

  We won't use an actual HDL language, instead we will use a simplified toy
  language called HDL that is simple enough that when it is used with a simulator,
  we can learn the main facets of chip design. Its syntax is very similar to VHDL.

  ## Demonstration of HDL program

  ### Boolean function to enact

  We will create an HDL program for an XOR gate that is implemented through the
  following arrangement of NOT, AND, and OR gates:

  ![](/img/xor-hdl.png)

  ### HDL file (`Xor.hdl`):

  Here is our HDL file:

  ```
  /* Xor gate
     If a!=b out=1 else out=0
  */
  CHIP Xor {
    IN a, b;
    OUT out;
    PARTS:
    Not (in=a, out=nota);
    Not (in=b, out=notb);
    And (a=a, b=notb, out=w1);
    And (a=nota, b=b, out=w2);
    Or  (a=w1, b=w2, out=out)
  }
  ```

  #### Interface (`CHIP, IN, OUT`)

  At the top level of the HDL program, the `CHIP` name and `IN`/`OUT` declaration
  is the _interface_ of the chip. Here we specify our naming convention for the
  `IN` and `OUT` values which we will refer to in the implementation declaration
  in `PARTS`.

  #### Implementation (`PARTS`)

  Everything under the `PARTS` section is the chip _implementation_. We can draw
  on composite gates in the `PARTS` declaration (e.g. `Not`, `And`, `Or`). The
  convention is to work from left to right when transcribing from a digital
  circuit diagram

  #### Pins

  In an HDL program we distinguish internal pins along with the standard
  [input and output pins](Integrated_circuits.md).
  At the level of the interface, we are concerned only with input and output pins
  (in the example program these are `a`, `b` and `out`). It is at the level of the
  implementation that internal pins are encountered. In the example these are the
  connections between, e.g. the AND and NOT gates such as
  `And (a=a, b-notb, out=w1)`. This means the AND gate is receiving through its
  `a` pin the input `a` value and through its `b` pin the value of `b` inverted by
  a NOT. `out` is the value that is computed based on the input pins of `a` and
  `b`.

  ### Test file (`Xor.tst`)

  Along with the HDL file we also create a test file. This runs the chip against
  the inputs we supply, these will typically be equivalent to the (left-hand)
  truth-values column in a truth table which is the same as the parameters passed
  to a [Boolean function](Boolean_functions.md), for
  example:

  ```vhdl
  load Xor.hdl
  output-list a, b, out;
  set a 0, set b 0, eval, output;
  set a 0, set b 1, eval, output;
  set a 1, set b 0, eval, output;
  set a 1, set b =, eval, output;
  ```

  ### Output file (`Xor.out`)

  When the test file is run against the HDL file it will generate an output file.
  This is effectively the result of the unit test. And will take the form of a
  truth table:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```

raw_content:
  ---
  tags: [nand-to-tetris]
  ---

  # Hardware Description Language

  An HDL is a declarative programming language used to describe the behaviour or
  structure of
  [digital circuits](Integrated_circuits.md).
  They are used to simulate the circuit and check its response.

  The hardware designer specifies a chip's logic by writing an HDL program which
  is then rigorously tested. At this stage, a
  [hardware simulator](Hardware_simulation.md) takes the
  HDL program as input and creates a software representation of the chip logic.
  The designer can instruct the simulator to test the virtual chip on various sets
  of inputs. This is done to check the chip's functionality but also to benchmark
  a variety of other parameters such as speed of computation and energy
  consumption.

  There are many HDLs but the most popular are VHDL ("very high speed
  integrated-circuit description language") and Verilog.

  ## Usage in _NAND to Tetris_

  We won't use an actual HDL language, instead we will use a simplified toy
  language called HDL that is simple enough that when it is used with a simulator,
  we can learn the main facets of chip design. Its syntax is very similar to VHDL.

  ## Demonstration of HDL program

  ### Boolean function to enact

  We will create an HDL program for an XOR gate that is implemented through the
  following arrangement of NOT, AND, and OR gates:

  ![](/img/xor-hdl.png)

  ### HDL file (`Xor.hdl`):

  Here is our HDL file:

  ```
  /* Xor gate
     If a!=b out=1 else out=0
  */
  CHIP Xor {
    IN a, b;
    OUT out;
    PARTS:
    Not (in=a, out=nota);
    Not (in=b, out=notb);
    And (a=a, b=notb, out=w1);
    And (a=nota, b=b, out=w2);
    Or  (a=w1, b=w2, out=out)
  }
  ```

  #### Interface (`CHIP, IN, OUT`)

  At the top level of the HDL program, the `CHIP` name and `IN`/`OUT` declaration
  is the _interface_ of the chip. Here we specify our naming convention for the
  `IN` and `OUT` values which we will refer to in the implementation declaration
  in `PARTS`.

  #### Implementation (`PARTS`)

  Everything under the `PARTS` section is the chip _implementation_. We can draw
  on composite gates in the `PARTS` declaration (e.g. `Not`, `And`, `Or`). The
  convention is to work from left to right when transcribing from a digital
  circuit diagram

  #### Pins

  In an HDL program we distinguish internal pins along with the standard
  [input and output pins](Integrated_circuits.md).
  At the level of the interface, we are concerned only with input and output pins
  (in the example program these are `a`, `b` and `out`). It is at the level of the
  implementation that internal pins are encountered. In the example these are the
  connections between, e.g. the AND and NOT gates such as
  `And (a=a, b-notb, out=w1)`. This means the AND gate is receiving through its
  `a` pin the input `a` value and through its `b` pin the value of `b` inverted by
  a NOT. `out` is the value that is computed based on the input pins of `a` and
  `b`.

  ### Test file (`Xor.tst`)

  Along with the HDL file we also create a test file. This runs the chip against
  the inputs we supply, these will typically be equivalent to the (left-hand)
  truth-values column in a truth table which is the same as the parameters passed
  to a [Boolean function](Boolean_functions.md), for
  example:

  ```vhdl
  load Xor.hdl
  output-list a, b, out;
  set a 0, set b 0, eval, output;
  set a 0, set b 1, eval, output;
  set a 1, set b 0, eval, output;
  set a 1, set b =, eval, output;
  ```

  ### Output file (`Xor.out`)

  When the test file is run against the HDL file it will generate an output file.
  This is effectively the result of the unit test. And will take the form of a
  truth table:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```


path: Hardware_abstraction_and_modularity.md
sortable_path: Hardware_abstraction_and_modularity.md
title: Hardware abstraction and modularity
checksum: 6d101cf97794548ab23f8a70cf416e70d8642d888d2ba82ecbbe32ac1327bc4a
created: 2025-10-01 13:30:59.054509919+00:00
modified: 2025-08-13 15:57:13.738921674+00:00
metadata: {"tags":["nand-to-tetris"]}
lead:
  In computer architecture we deal with complexity by breaking the system into
  **modules**. For each module we distinguish **_abstraction_** from
  **_implementation_**.

body:
  In computer architecture we deal with complexity by breaking the system into
  **modules**. For each module we distinguish **_abstraction_** from
  **_implementation_**.

  <dl>
    <dt>abstraction</dt>
    <dd>what the module does</dd>
    <dt>implementation</dt>
    <dd>how it does it</dd>
  </dl>

  When using a module as a building block you are to focus exclusively on the
  module's abstraction, ignoring completely its implementation details.

  > The abstraction-implementation paradigm helps developers manage complexity and
  > maintain sanity: by dividing an overwhelming system into well-defined modules
  > we create manageable chunks of implementation work and localize error
  > detection and correction.

  [N.Nisan, S.Schoken. 2021. **The Elements of Computing Systems** (Second
  Edition)]

  The design of the diagram below emphasises the role of abstraction and
  modularity in the movement from transistors to chips:

  ![](/img/hardware-abstraction-hierarchy.png)

raw_content:
  ---
  tags: [nand-to-tetris]
  ---

  # Hardware abstraction and modularity

  In computer architecture we deal with complexity by breaking the system into
  **modules**. For each module we distinguish **_abstraction_** from
  **_implementation_**.

  <dl>
    <dt>abstraction</dt>
    <dd>what the module does</dd>
    <dt>implementation</dt>
    <dd>how it does it</dd>
  </dl>

  When using a module as a building block you are to focus exclusively on the
  module's abstraction, ignoring completely its implementation details.

  > The abstraction-implementation paradigm helps developers manage complexity and
  > maintain sanity: by dividing an overwhelming system into well-defined modules
  > we create manageable chunks of implementation work and localize error
  > detection and correction.

  [N.Nisan, S.Schoken. 2021. **The Elements of Computing Systems** (Second
  Edition)]

  The design of the diagram below emphasises the role of abstraction and
  modularity in the movement from transistors to chips:

  ![](/img/hardware-abstraction-hierarchy.png)


path: Hardware_simulation.md
sortable_path: Hardware_simulation.md
title: Hardware simulation
checksum: 81eaf5ae524f90bc704e6b23c5218acb3c5324da1ad875beec4ae07caba430d7
created: 2025-10-01 13:30:59.057797083+00:00
modified: 2025-08-13 15:57:14.044927442+00:00
metadata: {"tags":["nand-to-tetris"]}
lead:
  In order to test our
  [HDL](Hardware_Description_Language.md) files we load
  them into the hardware simulator program. We will demonstrate this with the
  following XOR implementation:

body:
  In order to test our
  [HDL](Hardware_Description_Language.md) files we load
  them into the hardware simulator program. We will demonstrate this with the
  following XOR implementation:

  ![](/img/xor-addition-four.png)

  There are several simulation options:

  - interactive
  - script-based (where we load a test script into the simulator along with the
    HDL file
  - comparative (running the HDL program against our intended output specified in
    the `.cmp` file)

  The use-cases for each mode are based on the complexity of the chip you are
  evaluating. For a small chip, interactive and script-based testing would be
  sufficient but for much larger components like an ALU a comparative approach
  would be more manageable and efficient.

  ## Interactive

  The image below shows a basic interactive usage of the simulator. We have
  uploaded the `Xor.hdl` file into the simulator and changed the input pins to
  `a=1, b=0` and clicked the calculator icon (representing "evaluation"). This
  then shows the output and internal pin values for these inputs.

  <img src="/home/thomas/repos/computer_science/img/hardware-sim-basic.png" >

  ## Script-based

  This time we have clicked the script icon to load `Xor.tst`. This loads the test
  script into the main GUI panel on the left. We can step through each line of the
  test file and we will see the pin values update in accordance with the test.

  When this is run it automatically generates an output file in the source
  directory at `Xor.out`. This can be viewed within the simulator via the 'View'
  drop down.

  ![](/img/hd-sim-test.png)

  ## Comparison-based

  With a comparison-based approach to chip testing we run a comparison against the
  `.out` file that the simulator generates when running the HDL program against a
  `.cmp` comparison file that we provide. Both are simply truth-tables. For XOR if
  the program matched the comparison specification both `Xor.out` and `Xor.cmp`
  would look like the following:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```

  You don't have to do anything to apply the comparison since the compare file
  will already be loaded as part of the test file's set up:

  ```vhdl
  load Xor.hdl
  output file Xor.out
  compare-to Xor.cmp
  output-list a, b, out;
  set ...
  ```

raw_content:
  ---
  tags: [nand-to-tetris]
  ---

  # Hardware simulation

  In order to test our
  [HDL](Hardware_Description_Language.md) files we load
  them into the hardware simulator program. We will demonstrate this with the
  following XOR implementation:

  ![](/img/xor-addition-four.png)

  There are several simulation options:

  - interactive
  - script-based (where we load a test script into the simulator along with the
    HDL file
  - comparative (running the HDL program against our intended output specified in
    the `.cmp` file)

  The use-cases for each mode are based on the complexity of the chip you are
  evaluating. For a small chip, interactive and script-based testing would be
  sufficient but for much larger components like an ALU a comparative approach
  would be more manageable and efficient.

  ## Interactive

  The image below shows a basic interactive usage of the simulator. We have
  uploaded the `Xor.hdl` file into the simulator and changed the input pins to
  `a=1, b=0` and clicked the calculator icon (representing "evaluation"). This
  then shows the output and internal pin values for these inputs.

  <img src="/home/thomas/repos/computer_science/img/hardware-sim-basic.png" >

  ## Script-based

  This time we have clicked the script icon to load `Xor.tst`. This loads the test
  script into the main GUI panel on the left. We can step through each line of the
  test file and we will see the pin values update in accordance with the test.

  When this is run it automatically generates an output file in the source
  directory at `Xor.out`. This can be viewed within the simulator via the 'View'
  drop down.

  ![](/img/hd-sim-test.png)

  ## Comparison-based

  With a comparison-based approach to chip testing we run a comparison against the
  `.out` file that the simulator generates when running the HDL program against a
  `.cmp` comparison file that we provide. Both are simply truth-tables. For XOR if
  the program matched the comparison specification both `Xor.out` and `Xor.cmp`
  would look like the following:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```

  You don't have to do anything to apply the comparison since the compare file
  will already be loaded as part of the test file's set up:

  ```vhdl
  load Xor.hdl
  output file Xor.out
  compare-to Xor.cmp
  output-list a, b, out;
  set ...
  ```


path: Headless_Raspi_network_setup.md
sortable_path: Headless_Raspi_network_setup.md
title: Headless network setup
checksum: 1130cb421dc289d445aaedfbcfa79cac0079753aa0c992958503737758883f4b
created: 2025-10-01 13:30:59.064450985+00:00
modified: 2025-08-13 15:57:14.037489349+00:00
lead:
  If you are using a headless system and you aren't using an ethernet connection,
  you face a chicken-egg issue of how to connect the device to WiFi via `ssh` when
  the device isn't yet on the network.

body:
  If you are using a headless system and you aren't using an ethernet connection,
  you face a chicken-egg issue of how to connect the device to WiFi via `ssh` when
  the device isn't yet on the network.

  You can get around this by saving a file called `wp_supplicant.conf` to the
  `/boot` directory of the device. This specifies the SSID name and password and
  will be used to auto-connect when the device boots.

  For example:

  ```
  country=GB
  ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
  update_config=1
  network={
       ssid="SKYVL7XP"
       scan_ssid=1
       psk="WIFI_PASSWORD"
       key_mgmt=WPA-PSK
  }
  ```

  ## Related notes

raw_content:
  ---
  id: hb10
  tags:
    - networks
    - Linux
    - raspberry-pi
    - procedural
  created: Wednesday, June 12, 2024
  ---

  # Headless network setup

  If you are using a headless system and you aren't using an ethernet connection,
  you face a chicken-egg issue of how to connect the device to WiFi via `ssh` when
  the device isn't yet on the network.

  You can get around this by saving a file called `wp_supplicant.conf` to the
  `/boot` directory of the device. This specifies the SSID name and password and
  will be used to auto-connect when the device boots.

  For example:

  ```
  country=GB
  ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
  update_config=1
  network={
       ssid="SKYVL7XP"
       scan_ssid=1
       psk="WIFI_PASSWORD"
       key_mgmt=WPA-PSK
  }
  ```

  ## Related notes

metadata:
  {"created":"Wednesday, June 12, 2024","id":"hb10","tags":["networks","Linux","raspberry-pi","procedural"]}


path: Heap_memory.md
sortable_path: Heap_memory.md
title: Heap memory
lead:
  Along with [[Stack_memory|Stack memory]], programs make use of _heap memory_
  during runtime.

checksum: f599daed3b0ffc815cbf770feb48f87ca0f44f85901555625c85c9884270a479
created: 2025-10-01 13:30:59.067599599+00:00
modified: 2025-08-13 15:57:14.089521773+00:00
metadata: {"created":"Saturday, April 20, 2024","id":"18bl","tags":["memory","C"]}
body:
  Along with [[Stack_memory|Stack memory]], programs make use of _heap memory_
  during runtime.

  Heap memory does not use a standardised data structure and can be accessed from
  any point within the program.

  Whereas stack memory with it's LIFO structure has memory management built-in
  when programs allocate memory from the heap they must manually deallocate it
  when it is no longer required. This process of "freeing memory" is known as
  _garbage collection_. In a language like C, this is the explicit concern of the
  programmer and is not abstracted away. Failure to properly manage garbage
  collection is what causes [[Memory_leaks]].

  Heap memory is used in combination with the stack since a given heap memory
  allocation address is stored as a stack variable during runtime. It points to
  the heap memory address whilst not being that memory itself.

  Here is an example of managing heap memory allocation in C:

  ```c
  void * data;
  data = malloc(512)
  ```

  The first line assigns a special _pointer_ variable (indicated by `void *`
  rather than `int` or `str`) . This is a variable only holds a memory address.
  The `malloc` method requests 512 bytes that it wants to assign to the `data`
  variable. It will return the address of the first byte in the newly allocated
  memory. `data` will then refer to the address on the stack that holds the
  address allocation on the heap.

  ## Related notes

raw_content:
  ---
  id: 18bl
  tags: [memory, C]
  created: Saturday, April 20, 2024
  ---

  # Heap memory

  Along with [[Stack_memory|Stack memory]], programs make use of _heap memory_
  during runtime.

  Heap memory does not use a standardised data structure and can be accessed from
  any point within the program.

  Whereas stack memory with it's LIFO structure has memory management built-in
  when programs allocate memory from the heap they must manually deallocate it
  when it is no longer required. This process of "freeing memory" is known as
  _garbage collection_. In a language like C, this is the explicit concern of the
  programmer and is not abstracted away. Failure to properly manage garbage
  collection is what causes [[Memory_leaks]].

  Heap memory is used in combination with the stack since a given heap memory
  allocation address is stored as a stack variable during runtime. It points to
  the heap memory address whilst not being that memory itself.

  Here is an example of managing heap memory allocation in C:

  ```c
  void * data;
  data = malloc(512)
  ```

  The first line assigns a special _pointer_ variable (indicated by `void *`
  rather than `int` or `str`) . This is a variable only holds a memory address.
  The `malloc` method requests 512 bytes that it wants to assign to the `data`
  variable. It will return the address of the first byte in the newly allocated
  memory. `data` will then refer to the address on the stack that holds the
  address allocation on the heap.

  ## Related notes


path: Hexadecimal_number_system.md
sortable_path: Hexadecimal_number_system.md
title: Hexadecimal number system
checksum: 964ff70772f2ed1f0a21e567fb11783b90624a99c3327a8257a9b75bf82fa805
created: 2025-10-01 13:30:59.086717583+00:00
modified: 2025-08-13 15:57:13.890597017+00:00
metadata: {"tags":["number-systems","computer-architecture"]}
lead:
  Hexadecimal is the other main number system used in computing. It works in
  tandem with the [binary number system](Binary_number_system.md) and provides an
  easier and more accessible means of working with long sequences of binary
  numbers.

body:
  Hexadecimal is the other main number system used in computing. It works in
  tandem with the [binary number system](Binary_number_system.md) and provides an
  easier and more accessible means of working with long sequences of binary
  numbers.

  ## Hexadecimal place value

  Unlike denary which uses base ten and binary which uses base two, hexadecimal
  uses base 16 as its place value.

  > Each place in a hexadecimal number represents a power of 16 and each place can
  > be one of 16 symbols.

  ## Hexadecimal values

  The table below shows the symbols comprising hexadecimal alongside their denary
  and binary equivalents:

  | Hexadecimal | Decimal | Binary |
  | ----------- | ------- | ------ |
  | 0           | 0       | 0000   |
  | 1           | 1       | 0001   |
  | 2           | 2       | 0010   |
  | 3           | 3       | 0011   |
  | 4           | 4       | 0100   |
  | 5           | 5       | 0101   |
  | 6           | 6       | 0110   |
  | 7           | 7       | 0111   |
  | 8           | 8       | 1000   |
  | 9           | 9       | 1001   |
  | A           | 10      | 1010   |
  | B           | 11      | 1011   |
  | C           | 12      | 1100   |
  | D           | 13      | 1101   |
  | E           | 14      | 1110   |
  | F           | 15      | 1111   |

  This table shows the raw value of each hexadecimal place value:

  | $16^{3}$ | $16^{2}$ | $16^{1}$ | $16^{0}$ |
  | -------- | -------- | -------- | -------- |
  | 4096     | 256      | 16       | 1        |

  ## Hexadecimal prefix

  The custom is to prefix a hexadecimal number with `0x` to indicate that the
  number is hexadecimal.

  ## Converting hexadecimal numbers

  Using the previous table we can convert hexadecimal values to decimal.

  For example we can convert `1A5` as follows, working from right to left:

  $(5 \cdot 1 = 5) + (A \cdot 16 = 160) + (1 \cdot 256 = 256) = 421$

  The process is quite easy: we get the n from $16^{n}$ based on the position of
  the digit and then multiply this by the value of the symbol (1,2,...F):

  $$
    16^{n} \cdot 1,2,...F
  $$

  As applied to `0x1A5`:

  | $16^{3}$               | $16^{2}$              | $16^{1}$                   | $16^{0}$            |
  | ---------------------- | --------------------- | -------------------------- | ------------------- |
  | $1\cdot 16^{3} = 4096$ | $1\cdot 16^{2} = 256$ | $A (10)\cdot 16^{1} = 160$ | $5\cdot 16^{0} = 5$ |

  Another example for `0xF00F`:

  $(15 \cdot 4096 = 61440) + (0 \cdot 256 = 0) + (0 \cdot 16 = 0) + (15 \cdot 1 = 15) = 61455$

  ## Using hexadecimal to simplify binary numbers

  Whilst computers themselves do not use the hexadecimal number system (everything
  is binary), hexadecimal offers advantages for humans who must work with binary:

  1. It is much easier to read a hexadecimal number than long sequences of binary
     numbers
  2. It is easier to quickly convert binary numbers to hexadecimal than to convert
     binary numbers to decimal

  Look at the following equivalences

  | Number system   | Example 1           | Example 2           |
  | --------------- | ------------------- | ------------------- |
  | **Binary**      | 1111 0000 0000 1111 | 1000 1000 1000 0001 |
  | **Hexadecimal** | F00F                | 8881                |
  | **Decimal**     | 61,455              | 34,945              |

  It is obvious that a pattern is maintained between the hexadecimal and binary
  numbers and that this pattern is obscured by the decimal conversion. In the
  first example the binary half-byte `1111` is matched by the hexadecimal `F00F`.

  Mathematically comparing hex `F` and binary `1111`:

  $$
    \textsf{1111} = ((1 \cdot 2^{3}) + (1 \cdot 2^{2}) + (1 \cdot 2^{1}) + (1 \cdot 2^{0})) \\
    = 8 + 4 + 2 + 1 \\
    = 15
  $$

  $$
    \textsf{F} = 15 \cdot 16^{0}  \\
    = 15
  $$

  ![](/img/hexadecimal-to-bytes.svg)

  > Every four bits (or half byte) in binary corresponds to one symbol in
  > hexadecimal. Therefore **a byte can be easily represented with two hexadecimal
  > symbols, a 16-bit number can be represented with four hex symbols, a 32-bit
  > number can represented with eight hex symbols and so on.**

raw_content:
  ---
  tags:
    - number-systems
    - computer-architecture
  ---

  # Hexadecimal number system

  Hexadecimal is the other main number system used in computing. It works in
  tandem with the [binary number system](Binary_number_system.md) and provides an
  easier and more accessible means of working with long sequences of binary
  numbers.

  ## Hexadecimal place value

  Unlike denary which uses base ten and binary which uses base two, hexadecimal
  uses base 16 as its place value.

  > Each place in a hexadecimal number represents a power of 16 and each place can
  > be one of 16 symbols.

  ## Hexadecimal values

  The table below shows the symbols comprising hexadecimal alongside their denary
  and binary equivalents:

  | Hexadecimal | Decimal | Binary |
  | ----------- | ------- | ------ |
  | 0           | 0       | 0000   |
  | 1           | 1       | 0001   |
  | 2           | 2       | 0010   |
  | 3           | 3       | 0011   |
  | 4           | 4       | 0100   |
  | 5           | 5       | 0101   |
  | 6           | 6       | 0110   |
  | 7           | 7       | 0111   |
  | 8           | 8       | 1000   |
  | 9           | 9       | 1001   |
  | A           | 10      | 1010   |
  | B           | 11      | 1011   |
  | C           | 12      | 1100   |
  | D           | 13      | 1101   |
  | E           | 14      | 1110   |
  | F           | 15      | 1111   |

  This table shows the raw value of each hexadecimal place value:

  | $16^{3}$ | $16^{2}$ | $16^{1}$ | $16^{0}$ |
  | -------- | -------- | -------- | -------- |
  | 4096     | 256      | 16       | 1        |

  ## Hexadecimal prefix

  The custom is to prefix a hexadecimal number with `0x` to indicate that the
  number is hexadecimal.

  ## Converting hexadecimal numbers

  Using the previous table we can convert hexadecimal values to decimal.

  For example we can convert `1A5` as follows, working from right to left:

  $(5 \cdot 1 = 5) + (A \cdot 16 = 160) + (1 \cdot 256 = 256) = 421$

  The process is quite easy: we get the n from $16^{n}$ based on the position of
  the digit and then multiply this by the value of the symbol (1,2,...F):

  $$
    16^{n} \cdot 1,2,...F
  $$

  As applied to `0x1A5`:

  | $16^{3}$               | $16^{2}$              | $16^{1}$                   | $16^{0}$            |
  | ---------------------- | --------------------- | -------------------------- | ------------------- |
  | $1\cdot 16^{3} = 4096$ | $1\cdot 16^{2} = 256$ | $A (10)\cdot 16^{1} = 160$ | $5\cdot 16^{0} = 5$ |

  Another example for `0xF00F`:

  $(15 \cdot 4096 = 61440) + (0 \cdot 256 = 0) + (0 \cdot 16 = 0) + (15 \cdot 1 = 15) = 61455$

  ## Using hexadecimal to simplify binary numbers

  Whilst computers themselves do not use the hexadecimal number system (everything
  is binary), hexadecimal offers advantages for humans who must work with binary:

  1. It is much easier to read a hexadecimal number than long sequences of binary
     numbers
  2. It is easier to quickly convert binary numbers to hexadecimal than to convert
     binary numbers to decimal

  Look at the following equivalences

  | Number system   | Example 1           | Example 2           |
  | --------------- | ------------------- | ------------------- |
  | **Binary**      | 1111 0000 0000 1111 | 1000 1000 1000 0001 |
  | **Hexadecimal** | F00F                | 8881                |
  | **Decimal**     | 61,455              | 34,945              |

  It is obvious that a pattern is maintained between the hexadecimal and binary
  numbers and that this pattern is obscured by the decimal conversion. In the
  first example the binary half-byte `1111` is matched by the hexadecimal `F00F`.

  Mathematically comparing hex `F` and binary `1111`:

  $$
    \textsf{1111} = ((1 \cdot 2^{3}) + (1 \cdot 2^{2}) + (1 \cdot 2^{1}) + (1 \cdot 2^{0})) \\
    = 8 + 4 + 2 + 1 \\
    = 15
  $$

  $$
    \textsf{F} = 15 \cdot 16^{0}  \\
    = 15
  $$

  ![](/img/hexadecimal-to-bytes.svg)

  > Every four bits (or half byte) in binary corresponds to one symbol in
  > hexadecimal. Therefore **a byte can be easily represented with two hexadecimal
  > symbols, a 16-bit number can be represented with four hex symbols, a 32-bit
  > number can represented with eight hex symbols and so on.**


path: Host_protocols_of_ARPANET.md
sortable_path: Host_protocols_of_ARPANET.md
title: 653c0b1d_host_protocols_of_ARPANET
checksum: b73ca9d5f96d7c34a742f9769cd75e1c91208f05f5a176784e63462be7724e76
created: 2025-10-01 13:30:59.093413747+00:00
modified: 2025-08-13 15:57:13.757653479+00:00
metadata: {"created":"Wednesday, October 23, 2024","tags":["ARPANET","computer-history","networks"]}
lead:
  The responsibility for enabling communication between nodes fell to the hosts,
  not the [IMPs](e470bf3d_IMPs_in_the_ARPANET.md). At the lowest level (equivalent
  to the [link layer](Link_Layer_of_Internet_Protocol.md) in the subsequent
  internet?), this consisted in moving raw bits between hosts regardless of what
  specific type of data they encoded.

body:
  The responsibility for enabling communication between nodes fell to the hosts,
  not the [IMPs](e470bf3d_IMPs_in_the_ARPANET.md). At the lowest level (equivalent
  to the [link layer](Link_Layer_of_Internet_Protocol.md) in the subsequent
  internet?), this consisted in moving raw bits between hosts regardless of what
  specific type of data they encoded.

  In the book (_Where Wizards Stay Up Late_), there is a great description by
  analogy:

  > Some water out of the tap is used for making coffee, some for washing dishes,
  > some for bathing. The pipe and the faucet do not care about this; they convey
  > the water regardless.

raw_content:
  ---
  tags: [ARPANET, computer-history, networks]
  created: Wednesday, October 23, 2024
  ---

  # 653c0b1d_host_protocols_of_ARPANET

  The responsibility for enabling communication between nodes fell to the hosts,
  not the [IMPs](e470bf3d_IMPs_in_the_ARPANET.md). At the lowest level (equivalent
  to the [link layer](Link_Layer_of_Internet_Protocol.md) in the subsequent
  internet?), this consisted in moving raw bits between hosts regardless of what
  specific type of data they encoded.

  In the book (_Where Wizards Stay Up Late_), there is a great description by
  analogy:

  > Some water out of the tap is used for making coffee, some for washing dishes,
  > some for bathing. The pipe and the faucet do not care about this; they convey
  > the water regardless.


path: How_tunneling_works_with_VPNs.md
sortable_path: How_tunneling_works_with_VPNs.md
title: How tunnelling works with VPNs
checksum: 8edbd757700fba99b1345a687410ffe2df63fc3afd0e95052c89417e1feae7cd
created: 2025-10-01 13:30:59.108929834+00:00
modified: 2025-08-13 15:57:14.042879132+00:00
metadata: {"created":"Monday, February 24, 2025","tags":["networks","VPN"]}
lead:
  A VPN is a good example of a [network tunnel](./Network_tunnels.md) and the
  technique of packet wrapping / encapsulation to bypass network restrictions.

body:
  A VPN is a good example of a [network tunnel](./Network_tunnels.md) and the
  technique of packet wrapping / encapsulation to bypass network restrictions.

  When a user connects to a VPN, their device creates a connection to the VPN
  server. All subsequent traffic is routed first through the tunnel, rather than
  directly to the target host IP.

  When the data arrives at the VPN server, the VPN wraps the packets inside new
  packets that have the VPN server's addressing information. The nested packets
  are encrypted before they are encapsulated and the tunnel they arrive through is
  also encrypted end-to-end. Thus there is - in theory - no way to know where the
  original packets come from.

  When the data returns from the target host to the VPN server that requested it,
  the VPN server decrypts the data and forwards it back through the secure tunnel
  to the original user's device.

  Whilst connected to the VPN, the user's IP appears to be identical to the IP of
  the VPN server. This is because the outer packet header has the VPN's addressing
  information, not the source IP's (this is encrypted within the payload). As the
  target host only sees the VPN's headers, it appears as if the VPN user's IP is
  the same as the VPN's.

raw_content:
  ---
  tags: [networks, VPN]
  created: Monday, February 24, 2025
  ---

  # How tunnelling works with VPNs

  A VPN is a good example of a [network tunnel](./Network_tunnels.md) and the
  technique of packet wrapping / encapsulation to bypass network restrictions.

  When a user connects to a VPN, their device creates a connection to the VPN
  server. All subsequent traffic is routed first through the tunnel, rather than
  directly to the target host IP.

  When the data arrives at the VPN server, the VPN wraps the packets inside new
  packets that have the VPN server's addressing information. The nested packets
  are encrypted before they are encapsulated and the tunnel they arrive through is
  also encrypted end-to-end. Thus there is - in theory - no way to know where the
  original packets come from.

  When the data returns from the target host to the VPN server that requested it,
  the VPN server decrypts the data and forwards it back through the secure tunnel
  to the original user's device.

  Whilst connected to the VPN, the user's IP appears to be identical to the IP of
  the VPN server. This is because the outer packet header has the VPN's addressing
  information, not the source IP's (this is encrypted within the payload). As the
  target host only sees the VPN's headers, it appears as if the VPN user's IP is
  the same as the VPN's.


path: ICMP_Protocol.md
sortable_path: ICMP_Protocol.md
title: ICMP Protocol
checksum: 81995cf8b107982fa597a658a7ae4ae2532b03dd8636940b6b4f9dd4da5d5693
created: 2025-10-01 13:30:59.116412955+00:00
modified: 2025-08-13 15:57:13.810406675+00:00
metadata: {"created":"Monday, January 13, 2025","tags":["networks"]}
lead:
  The Internet Control Message Protocol operates alongside the
  [Internet Protocol](Internet_Layer_of_Internet_Protocol.md) at the
  [Transport Layer](Transport_Layer_of_Internet_Protocol.md).

body:
  The Internet Control Message Protocol operates alongside the
  [Internet Protocol](Internet_Layer_of_Internet_Protocol.md) at the
  [Transport Layer](Transport_Layer_of_Internet_Protocol.md).

  It doesn't carry any actual user data and thus there is no
  [Application Layer](Application_Layer_of_Internet_Protocol.md) processes above
  it. It's usage is primarily for diagnostics and debugging. When network problems
  occur, ICMP generates messages to report errors back to the source.

  Probably the most familiar tools related to ICMP is [Ping](Ping.md).

raw_content:
  ---
  tags: [networks]
  created: Monday, January 13, 2025
  ---

  # ICMP Protocol

  The Internet Control Message Protocol operates alongside the
  [Internet Protocol](Internet_Layer_of_Internet_Protocol.md) at the
  [Transport Layer](Transport_Layer_of_Internet_Protocol.md).

  It doesn't carry any actual user data and thus there is no
  [Application Layer](Application_Layer_of_Internet_Protocol.md) processes above
  it. It's usage is primarily for diagnostics and debugging. When network problems
  occur, ICMP generates messages to report errors back to the source.

  Probably the most familiar tools related to ICMP is [Ping](Ping.md).


path: IMPs_in_the_ARPANET.md
sortable_path: IMPs_in_the_ARPANET.md
title: e470bf3d_IMPs_in_the_ARPANET
checksum: 8e2cc9c8714a45239d8cb1752f4f5ed10ded442706db964d1a40286f7fafed8f
created: 2025-10-01 13:30:59.14063997+00:00
modified: 2025-08-13 15:57:14.077355366+00:00
metadata: {"created":"Friday, October 18, 2024","tags":["ARPANET","networks","computer-history"]}
lead:
  Within the #ARPANET, IMPs (Information Message Processors) were nodes
  distinguished from [hosts](Network_hosts.md). They were computers designed to
  receive and route the the packets from one host to another, described as
  "adaptive routing".

body:
  Within the #ARPANET, IMPs (Information Message Processors) were nodes
  distinguished from [hosts](Network_hosts.md). They were computers designed to
  receive and route the the packets from one host to another, described as
  "adaptive routing".

  Their job was to:

  - packetize or reassemble the data from the host
  - find the most efficient pathway from one host to another
  - store packets in their buffers whilst attending to other requests
  - retry the transmission a set number of times in the case of error or data
    corruption
  - confirm successful transmission

  These responsibilities required the IMP to "know" the location and addresses of
  other IMPs and hosts on the [network](Network_fundamentals.md) and their
  proximity to them.

  From the point of view of the host user, the existence of the IMPs would be
  hidden. Hosts would connect to eachother transparently - it would appear as if
  you were directly connecting to the other host rather than an IMP.

  Thus an IMP was a subnetwork within the broader ARPANET: the IMP would be the
  core of this subnetwork and the links to its direct hosts, the periperhy.
  Initially, in its full extent, the ARPANET comprised a network of these
  subnetworks.

  ![IMP diagram](../img/IMP-network.png)

  IMPs were later known as 'gateways' and were the precursors to what we today
  call routers.

raw_content:
  ---
  tags: [ARPANET, networks, computer-history]
  created: Friday, October 18, 2024
  ---

  # e470bf3d_IMPs_in_the_ARPANET

  Within the #ARPANET, IMPs (Information Message Processors) were nodes
  distinguished from [hosts](Network_hosts.md). They were computers designed to
  receive and route the the packets from one host to another, described as
  "adaptive routing".

  Their job was to:

  - packetize or reassemble the data from the host
  - find the most efficient pathway from one host to another
  - store packets in their buffers whilst attending to other requests
  - retry the transmission a set number of times in the case of error or data
    corruption
  - confirm successful transmission

  These responsibilities required the IMP to "know" the location and addresses of
  other IMPs and hosts on the [network](Network_fundamentals.md) and their
  proximity to them.

  From the point of view of the host user, the existence of the IMPs would be
  hidden. Hosts would connect to eachother transparently - it would appear as if
  you were directly connecting to the other host rather than an IMP.

  Thus an IMP was a subnetwork within the broader ARPANET: the IMP would be the
  core of this subnetwork and the links to its direct hosts, the periperhy.
  Initially, in its full extent, the ARPANET comprised a network of these
  subnetworks.

  ![IMP diagram](../img/IMP-network.png)

  IMPs were later known as 'gateways' and were the precursors to what we today
  call routers.


path: IP_addresses.md
sortable_path: IP_addresses.md
title: IP addresses
lead: ## IPv4 vs IPv6
checksum: 0f01198098c3d02c6940f70369dd6d8e152f046585adc2f39f3fc788135e834a
created: 2025-10-01 13:30:59.154325284+00:00
modified: 2025-08-13 15:57:13.774777664+00:00
metadata: {"created":"Friday, August 16, 2024","tags":["networks","internet"]}
body:
  ## IPv4 vs IPv6

  IP addresses come in two formats and bit-lengths depending on which version of
  the [Internet Protocol](Internet_Layer_of_Internet_Protocol.md) is being used.

  - IPv4 addresses are 32-bits in length
  - IPv6 addresses are 128-bits in length

  The larger the bit-length, the more unique addresses that can be generated. Thus
  IPv6 can create more addresses that IPv4.

  This entry focuses on IPv4 addresses.

  ## Anatomy of an IPv4 address

  Each of the 32 bits are divided into four groups which are each 8 bits in
  length. Each group therefore comprises an octet and is separated by dots.

  The decimal number system, rather than binary is used to represent the values.
  This means each binary octet is three decimal digits in length.

  The 32 bits comprise two major groupings

  - the **network prefix**
  - the **host**

  ![Different sections of IP address](../img/ip-address-anatomy.png)

  Devices connected to the same network share the same network prefix. They are
  said to be on the same **subnet**. However each device will have a unique value
  for the host octet.

  (This account is somewhat idealised. The length of the prefix does not always
  align with the octet boundary. For instance, the prefix could be 25 bits in
  length, rather than 24. In this case it would "steal" one bit from the host
  section making it 23 bits in length.)

  Any changes to the length of the network prefix change the number of hosts
  available on the subnet. If the network prefix is longer, the number of unique
  hosts is reduced. If the network prefix is shorted, the number of unique hosts
  is increased.

  ## Identifying the network and host groupings

  Given that the network prefix and host identifier do not always stick to set
  boundaries, we require a way of distinguishing the two values. There are two
  dominant methods.

  ### CIDR notation

  This stands for _Classless Inter-Domain Routing_. An example:

  > 192.168.1.23/24

  The value after the forward slash is the number of bits that specify the network
  address. In the example, it is the standard 24-bits. Thus 192.168.1.23 stands
  for the network.

  ### Subnet masking

  The subnet mask is another number _in addition to_ the network address. It is a
  kind of superimposed map on top of the address.

  Here is an example of a subnet mask:

  > 11111111.11111111.11111111.00000000

  The denary form would be as follows (as 255 is the maximal decimal number that
  can be represented with a single 8-bit number and 0 is the smallest):

  > 255.255.255.0

  In the binary form, the 1 values represent the bits which designate the network
  address and the 0 values represent the bits that designate the host.

  In the example above this corresponds to the idealised 32-bit:8-bit ratio of
  CIDR /24.

  There is a clever consequence of the subnet mask: if you apply a bitwise AND
  operator against the IP address and mask (both in their binary form) you can
  determine whether two addresses are on the same network.

  To compare the IP address 192.168.1.23 against 192.168.1.100 to demonstrate:

  ```
  192.168.1.23:
  IP:     11000000.10101000.00000001.00010111
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000001.00000000
  ___________________________________

  192.168.1.100:
  IP:     11000000.10101000.00000001.00010111
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000001.00000000
  ```

  After applying the bitwise AND logic we see that the result is identical for
  both IPs indicating they are on the same network (share the same network
  prefix), whilst the host value is "masked".

  Here is a scenario where the subnet mask indicates that two IPs are not on the
  shared network:

  ```
  192.168.1.23:
  IP:     11000000.10101000.00000001.00010111
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000001.00000000
  ___________________________________

  192.168.2.1
  IP:     11000000.10101000.00000010.00000001
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000010.00000000

  ```

  255.255.255.0 is not the only possible subnet mask. There are masks
  corresponding to ratios other than CIDR /24. For example:

  - 255.0.0.0 (/8)
  - 255.255.0.0 (/8)
  - 255.255.255.192 (/26)
  - 255.255.255.240 (/28)

  As noted previously, the different ratios will obviously affect the number of
  unique hosts available on the subnet.

  ## Determining the range of hosts: practical example

  Let's say we have the following IP address expressed in CIDR: 192.168.0.133/27.
  In binary this would be:

  ```
  110000000.10101000.00000000.100--00101
  ```

  The `--` indicates the demarcation point between the network prefix and the bits
  designated for the host. 32 - 27 leaves us 5 bits for our range of hosts. This
  gives us 2^5 = 32 unique host values.

  In fact it will actually be 30 values since the first value (`00000`) will be
  used to identify the network itself and the last value (`11111`) will be the
  broadcast address - the address used when a message needs to be sent to all
  hosts on the network.

  An actual host address in this range is included in the example: `00101`.

raw_content:
  ---
  tags: [networks, internet]
  created: Friday, August 16, 2024
  ---

  # IP addresses

  ## IPv4 vs IPv6

  IP addresses come in two formats and bit-lengths depending on which version of
  the [Internet Protocol](Internet_Layer_of_Internet_Protocol.md) is being used.

  - IPv4 addresses are 32-bits in length
  - IPv6 addresses are 128-bits in length

  The larger the bit-length, the more unique addresses that can be generated. Thus
  IPv6 can create more addresses that IPv4.

  This entry focuses on IPv4 addresses.

  ## Anatomy of an IPv4 address

  Each of the 32 bits are divided into four groups which are each 8 bits in
  length. Each group therefore comprises an octet and is separated by dots.

  The decimal number system, rather than binary is used to represent the values.
  This means each binary octet is three decimal digits in length.

  The 32 bits comprise two major groupings

  - the **network prefix**
  - the **host**

  ![Different sections of IP address](../img/ip-address-anatomy.png)

  Devices connected to the same network share the same network prefix. They are
  said to be on the same **subnet**. However each device will have a unique value
  for the host octet.

  (This account is somewhat idealised. The length of the prefix does not always
  align with the octet boundary. For instance, the prefix could be 25 bits in
  length, rather than 24. In this case it would "steal" one bit from the host
  section making it 23 bits in length.)

  Any changes to the length of the network prefix change the number of hosts
  available on the subnet. If the network prefix is longer, the number of unique
  hosts is reduced. If the network prefix is shorted, the number of unique hosts
  is increased.

  ## Identifying the network and host groupings

  Given that the network prefix and host identifier do not always stick to set
  boundaries, we require a way of distinguishing the two values. There are two
  dominant methods.

  ### CIDR notation

  This stands for _Classless Inter-Domain Routing_. An example:

  > 192.168.1.23/24

  The value after the forward slash is the number of bits that specify the network
  address. In the example, it is the standard 24-bits. Thus 192.168.1.23 stands
  for the network.

  ### Subnet masking

  The subnet mask is another number _in addition to_ the network address. It is a
  kind of superimposed map on top of the address.

  Here is an example of a subnet mask:

  > 11111111.11111111.11111111.00000000

  The denary form would be as follows (as 255 is the maximal decimal number that
  can be represented with a single 8-bit number and 0 is the smallest):

  > 255.255.255.0

  In the binary form, the 1 values represent the bits which designate the network
  address and the 0 values represent the bits that designate the host.

  In the example above this corresponds to the idealised 32-bit:8-bit ratio of
  CIDR /24.

  There is a clever consequence of the subnet mask: if you apply a bitwise AND
  operator against the IP address and mask (both in their binary form) you can
  determine whether two addresses are on the same network.

  To compare the IP address 192.168.1.23 against 192.168.1.100 to demonstrate:

  ```
  192.168.1.23:
  IP:     11000000.10101000.00000001.00010111
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000001.00000000
  ___________________________________

  192.168.1.100:
  IP:     11000000.10101000.00000001.00010111
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000001.00000000
  ```

  After applying the bitwise AND logic we see that the result is identical for
  both IPs indicating they are on the same network (share the same network
  prefix), whilst the host value is "masked".

  Here is a scenario where the subnet mask indicates that two IPs are not on the
  shared network:

  ```
  192.168.1.23:
  IP:     11000000.10101000.00000001.00010111
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000001.00000000
  ___________________________________

  192.168.2.1
  IP:     11000000.10101000.00000010.00000001
  Mask:   11111111.11111111.11111111.00000000
  Result: 11000000.10101000.00000010.00000000

  ```

  255.255.255.0 is not the only possible subnet mask. There are masks
  corresponding to ratios other than CIDR /24. For example:

  - 255.0.0.0 (/8)
  - 255.255.0.0 (/8)
  - 255.255.255.192 (/26)
  - 255.255.255.240 (/28)

  As noted previously, the different ratios will obviously affect the number of
  unique hosts available on the subnet.

  ## Determining the range of hosts: practical example

  Let's say we have the following IP address expressed in CIDR: 192.168.0.133/27.
  In binary this would be:

  ```
  110000000.10101000.00000000.100--00101
  ```

  The `--` indicates the demarcation point between the network prefix and the bits
  designated for the host. 32 - 27 leaves us 5 bits for our range of hosts. This
  gives us 2^5 = 32 unique host values.

  In fact it will actually be 30 values since the first value (`00000`) will be
  used to identify the network itself and the last value (`11111`) will be the
  broadcast address - the address used when a message needs to be sent to all
  hosts on the network.

  An actual host address in this range is included in the example: `00101`.


path: Identify_merged_branches.md
sortable_path: Identify_merged_branches.md
title: Identify merged branches
lead:
  ```sh
  # View merged
  git branch --merged

checksum: 4276aeff9c2f164c65c35fb617983aa2739633e1617b080b74ae32b35baacc1a
created: 2025-10-01 13:30:59.165700664+00:00
modified: 2025-08-13 15:57:14.07564539+00:00
metadata: {"tags":["git"]}
body:
  ```sh
  # View merged
  git branch --merged

  # View merged remote
  git branch -r --merged

  # View unmerged
  git branch --no-merged

  # View unmerged remote

  git branch -r --no-merged
  ```

  The above cases run the merge check from the point of view of HEAD as this is
  the default.

  But we can also run it from any branch.

  ```
  git branch --merged non_head_branch
  ```

  ## Demonstration

  We have the following branches

  ```
  git branch

  * main
  key_feature
  another_feature
  ```

  The branches other than `main`, have not been merged:

  ```
  git branch --merged

  * main
  ```

  Just to confirm:

  ```
  git branch --no-merged

  key_feature
  another_feature
  ```

raw_content:
  ---
  tags: [git]
  ---

  # Identify merged branches

  ```sh
  # View merged
  git branch --merged

  # View merged remote
  git branch -r --merged

  # View unmerged
  git branch --no-merged

  # View unmerged remote

  git branch -r --no-merged
  ```

  The above cases run the merge check from the point of view of HEAD as this is
  the default.

  But we can also run it from any branch.

  ```
  git branch --merged non_head_branch
  ```

  ## Demonstration

  We have the following branches

  ```
  git branch

  * main
  key_feature
  another_feature
  ```

  The branches other than `main`, have not been merged:

  ```
  git branch --merged

  * main
  ```

  Just to confirm:

  ```
  git branch --no-merged

  key_feature
  another_feature
  ```


path: Importing_MongoDB_data.md
sortable_path: Importing_MongoDB_data.md
title: Importing data to MongoDB
checksum: 677b579e14fd2d752e80d93d1b76f3f2d113a7454f3689917dc8a11c9b4564cd
created: 2025-10-01 13:30:59.168596987+00:00
modified: 2025-08-13 15:57:13.806190953+00:00
metadata: {"tags":["mongo-db","node-js","databases"]}
lead:
  We can use the command line to import data into a Mongo database. We can also
  combine the importing of data with the creation of a database:

body:
  We can use the command line to import data into a Mongo database. We can also
  combine the importing of data with the creation of a database:

  ```bash
  mongoimport --db [preexisting_or_new_database name] --collection [collection_name] --file sourcedata.json --jsonArray
  ```

  We use `--jsonArray` to indicate the format of the data we are importing.

  Once this has executed, the data will be visible in Compass.

raw_content:
  ---
  tags:
    - mongo-db
    - node-js
    - databases
  ---

  # Importing data to MongoDB

  We can use the command line to import data into a Mongo database. We can also
  combine the importing of data with the creation of a database:

  ```bash
  mongoimport --db [preexisting_or_new_database name] --collection [collection_name] --file sourcedata.json --jsonArray
  ```

  We use `--jsonArray` to indicate the format of the data we are importing.

  Once this has executed, the data will be visible in Compass.


path: Increasing_fractions_to_their_highest_terms.md
sortable_path: Increasing_fractions_to_their_highest_terms.md
title: Increasing fractions to their highest terms
checksum: 29fe4d70e55797777968ba4e5bf1a14774a0ffe5cad62973beab7c005bc209f3
created: 2025-10-01 13:30:59.171730679+00:00
modified: 2025-08-13 15:57:13.853711058+00:00
metadata: {"tags":["prealgebra","fractions"]}
lead:
  Given the equivalence between factors and divisors we can increase fractions to
  higher terms in a very similar way to when we reduce fractions. In the latter
  case we are dividing by divisors to reduce. In the former, we are multiplying by
  factors to increase.

body:
  Given the equivalence between factors and divisors we can increase fractions to
  higher terms in a very similar way to when we reduce fractions. In the latter
  case we are dividing by divisors to reduce. In the former, we are multiplying by
  factors to increase.

  > Whenever we increase a fraction, the resultant fraction will always be
  > [equivalent](Equivalent%20fractions.md) to the fraction we started with.

  ## Demonstration

  _Express $\frac{3}{4}$ as an equivalent fraction having the denominator 20_

  $$
  \\frac{3 \cdot 4}{5 \cdot 4} = \frac{12}{20}
  $$

  _Express $\frac{2}{3}$ as an equivalent fraction having the denominator 21_

  $$
  \\frac{2 \cdot 7}{3 \cdot 7} = \frac{14}{21}
  $$

  ## Increasing fractions with variables to higher terms

  _Express $\frac{2}{9}$ as an equivalent fraction having the denominator 18a_

  In these cases, just append the variable to the factor:

  $$
  \\frac{2 \cdot 2a}{9 \cdot 2a} = \frac{4a}{18a}
  $$

raw_content:
  ---
  tags:
    - prealgebra
    - fractions
  ---

  # Increasing fractions to their highest terms

  Given the equivalence between factors and divisors we can increase fractions to
  higher terms in a very similar way to when we reduce fractions. In the latter
  case we are dividing by divisors to reduce. In the former, we are multiplying by
  factors to increase.

  > Whenever we increase a fraction, the resultant fraction will always be
  > [equivalent](Equivalent%20fractions.md) to the fraction we started with.

  ## Demonstration

  _Express $\frac{3}{4}$ as an equivalent fraction having the denominator 20_

  $$
  \\frac{3 \cdot 4}{5 \cdot 4} = \frac{12}{20}
  $$

  _Express $\frac{2}{3}$ as an equivalent fraction having the denominator 21_

  $$
  \\frac{2 \cdot 7}{3 \cdot 7} = \frac{14}{21}
  $$

  ## Increasing fractions with variables to higher terms

  _Express $\frac{2}{9}$ as an equivalent fraction having the denominator 18a_

  In these cases, just append the variable to the factor:

  $$
  \\frac{2 \cdot 2a}{9 \cdot 2a} = \frac{4a}{18a}
  $$


path: Insert_data_into_SQL_table.md
sortable_path: Insert_data_into_SQL_table.md
title: Insert data into table with SQL INSERT statement
lead: _A table named `sales`:_
checksum: c665728b91472161cc4b0d63c8626aec625bba9226159c46e3f801e7b51242e0
created: 2025-10-01 13:30:59.180646856+00:00
modified: 2025-08-13 15:57:13.972871573+00:00
metadata: {"tags":["SQL","databases"]}
body:
  _A table named `sales`:_

  | saleId | modelId | date       | salesPerson |
  | ------ | ------- | ---------- | ----------- |
  | 223    | 2       | 2020-01-09 | tgomay      |
  | 321    | 3       | 2020-03-14 | snedgamon   |
  | 491    | 4       | 2021-08-06 | tricksahoy  |

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01','mhogan');
  ```

  > If you intend to miss out a value, you shouldn't leave it blank, you should
  > instead use `NULL` :

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01', NULL);
  ```

  > The approach above works well if the order of entry matches the order of the
  > table fields. If this is not the case you can specify the order as part of the
  > `INSERT` statement:

  ```sql
  INSERT INTO sales**(employee_id, sale_id, model_id, sale_date)**
  VALUES ('mhogan', 1, 11, '2020-01-01',);
  ```

raw_content:
  ---
  tags:
    - SQL
    - databases
  ---

  # Insert data into table with SQL `INSERT` statement

  _A table named `sales`:_

  | saleId | modelId | date       | salesPerson |
  | ------ | ------- | ---------- | ----------- |
  | 223    | 2       | 2020-01-09 | tgomay      |
  | 321    | 3       | 2020-03-14 | snedgamon   |
  | 491    | 4       | 2021-08-06 | tricksahoy  |

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01','mhogan');
  ```

  > If you intend to miss out a value, you shouldn't leave it blank, you should
  > instead use `NULL` :

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01', NULL);
  ```

  > The approach above works well if the order of entry matches the order of the
  > table fields. If this is not the case you can specify the order as part of the
  > `INSERT` statement:

  ```sql
  INSERT INTO sales**(employee_id, sale_id, model_id, sale_date)**
  VALUES ('mhogan', 1, 11, '2020-01-01',);
  ```


path: Instruction_set_architectures.md
sortable_path: Instruction_set_architectures.md
title: Instruction Set Architectures
lead: ## Summary
checksum: 38ccb65b129d4cbcb060765e9e148bd9527299c2ec783167fa140f86880ee1db
created: 2025-10-01 13:30:59.184336252+00:00
modified: 2025-08-13 15:57:13.987299586+00:00
metadata: {"tags":["CPU"]}
body:
  ## Summary

  - Computers with different hardware execute machine code instructions (e.g
    _add_) in different ways. They have different _instruction set architectures_
    (ISAs).

  - A family of CPUs that implement the core instructions of the
    [[Arithmetic_Logic_Unit]] in the same way share an ISA. Any software built for
    that ISA will work on any CPU that implements the ISA

  - The two main CPU architectures are ARM and x86

  ## Architecture is logical not physical

  The "architecture" of an ISA is logical rather than physical, i.e processors
  that share an ISA may work very differently on the hardware level whilst still
  adhering to the same instruction set.

  ## ARM and x86

  The two main processor architectures are ARM and x86. x86 processors were first
  introduced by Intel but are also made by AMD.

  ### x86

  - First introduced by Intel but also made by AMD.

  - Name stems from Intel's internal naming conventions - every new processor they
    introduced would have a letter designation ending with "86". Later they would
    adopt names such as "Pentium", "Celeron" etc. Regardless, these processors
    still fall under the "x86" family.

  - Over time, new instructions have been added to the x86 architecture but they
    all maintain backwards compatibility with preceding generations.

  - There have been different, successive generations of x86 corresponding to
    their [word-size](Signed_and_unsigned_numbers.md): 16-bit, 32-bit, 64-bit.
    Word size here just means how many bits the processor can work with at a time.
    A 34-bit processor can operate on values that are 32 bits in length. This
    means it has 32bit [[CPU_architecture#registers|registers]], a 32-bit data
    [[Bus|bus]], a 32-bit address bus and so on.

  - AMD introduced the first 64-bit processor, not Intel but they later caught up.
    A 64-bit x86 processor is often referred to as _x64_ or _x86-64_.

  ### ARM

  - x86 processors dominate the personal computer and server space

  - On movile devices ARM ISA predominates

  - The ARM company develops the architecture and standard which it licenses it to
    other companies to implement.

  - Preferred on mobile because they have reduced power consumption and lower
    costs compared to x86.

  - The processor is on a single chip that contains the CPU as well as memory and
    other hardware.

raw_content:
  ---
  tags: [CPU]
  ---

  # Instruction Set Architectures

  ## Summary

  - Computers with different hardware execute machine code instructions (e.g
    _add_) in different ways. They have different _instruction set architectures_
    (ISAs).

  - A family of CPUs that implement the core instructions of the
    [[Arithmetic_Logic_Unit]] in the same way share an ISA. Any software built for
    that ISA will work on any CPU that implements the ISA

  - The two main CPU architectures are ARM and x86

  ## Architecture is logical not physical

  The "architecture" of an ISA is logical rather than physical, i.e processors
  that share an ISA may work very differently on the hardware level whilst still
  adhering to the same instruction set.

  ## ARM and x86

  The two main processor architectures are ARM and x86. x86 processors were first
  introduced by Intel but are also made by AMD.

  ### x86

  - First introduced by Intel but also made by AMD.

  - Name stems from Intel's internal naming conventions - every new processor they
    introduced would have a letter designation ending with "86". Later they would
    adopt names such as "Pentium", "Celeron" etc. Regardless, these processors
    still fall under the "x86" family.

  - Over time, new instructions have been added to the x86 architecture but they
    all maintain backwards compatibility with preceding generations.

  - There have been different, successive generations of x86 corresponding to
    their [word-size](Signed_and_unsigned_numbers.md): 16-bit, 32-bit, 64-bit.
    Word size here just means how many bits the processor can work with at a time.
    A 34-bit processor can operate on values that are 32 bits in length. This
    means it has 32bit [[CPU_architecture#registers|registers]], a 32-bit data
    [[Bus|bus]], a 32-bit address bus and so on.

  - AMD introduced the first 64-bit processor, not Intel but they later caught up.
    A 64-bit x86 processor is often referred to as _x64_ or _x86-64_.

  ### ARM

  - x86 processors dominate the personal computer and server space

  - On movile devices ARM ISA predominates

  - The ARM company develops the architecture and standard which it licenses it to
    other companies to implement.

  - Preferred on mobile because they have reduced power consumption and lower
    costs compared to x86.

  - The processor is on a single chip that contains the CPU as well as memory and
    other hardware.


path: Integrated_circuits.md
sortable_path: Integrated_circuits.md
title: Integrated circuits
checksum: 0dc9e1f489b2886878f382b4fe89e0ac58430d1e5c1c38a850e9bbe4f5c96aab
created: 2025-10-01 13:30:59.203658758+00:00
modified: 2025-08-13 15:57:14.074546059+00:00
metadata: {"tags":["logic-gates","electronics"]}
lead:
  An integrated circuit (IC) is a single unit that comprises several logic gates
  designed for the easy construction of [digital circuits](Digital_circuits.md).
  The terms "integrated circuit" and "chip" are often used interchangeably.

body:
  An integrated circuit (IC) is a single unit that comprises several logic gates
  designed for the easy construction of [digital circuits](Digital_circuits.md).
  The terms "integrated circuit" and "chip" are often used interchangeably.

  An IC puts the gates on a single piece of silicon that has electrical contact
  points called pins. There are two types of pins on an IC: input pins and output
  pins. Input pins are used to receive signals from other components in the
  circuit, while output pins are used to send signals to other components. The
  function of a pin is determined by the connection it has to the rest of the
  circuit and the type of signal it is intended to transmit or receive.

  The type we will look at are called **dual in-line packages** (DIPs). They are
  rectangular wth two parallel rows of pins. The pins make it easy to connect DIPs
  to a breadboard.

  _An integrated circuit and its use on a breadboard:_

  ![](../img/integrated-circuit.jpeg)

  ![](../img/breadboard-DIP.jpg)

raw_content:
  ---
  tags:
    - logic-gates
    - electronics
  ---

  # Integrated circuits

  An integrated circuit (IC) is a single unit that comprises several logic gates
  designed for the easy construction of [digital circuits](Digital_circuits.md).
  The terms "integrated circuit" and "chip" are often used interchangeably.

  An IC puts the gates on a single piece of silicon that has electrical contact
  points called pins. There are two types of pins on an IC: input pins and output
  pins. Input pins are used to receive signals from other components in the
  circuit, while output pins are used to send signals to other components. The
  function of a pin is determined by the connection it has to the rest of the
  circuit and the type of signal it is intended to transmit or receive.

  The type we will look at are called **dual in-line packages** (DIPs). They are
  rectangular wth two parallel rows of pins. The pins make it easy to connect DIPs
  to a breadboard.

  _An integrated circuit and its use on a breadboard:_

  ![](../img/integrated-circuit.jpeg)

  ![](../img/breadboard-DIP.jpg)


path: Interactive_staging.md
sortable_path: Interactive_staging.md
title: Interactive staging
checksum: fdef4d4ff9933facb66f0b9ca70df3e69d95d9376ecb3748f72eb15d9a91a064
created: 2025-10-01 13:30:59.223087017+00:00
modified: 2025-08-13 15:57:13.758264736+00:00
metadata: {"tags":["git"]}
lead:
  With interactive staging we can stage sub-portions of files rather than the
  whole file. This makes commits much more targetted and atomic.

body:
  With interactive staging we can stage sub-portions of files rather than the
  whole file. This makes commits much more targetted and atomic.

  ## Basic use

  We enter interactive mode with:

  ```
  git add -i
  ```

  This opens an interface:

  ![](/img/git-interactive-mode-2.png)

  We select 2 and it lets us stage by number. If I enter 1, it will stage the
  first change.

  If we then go into 3 for revert, we can undo what we have staged.

  > Note that untracked files are not automatically added to the changes. We have
  > to manually add them with 4 ("add untracked").

  > Interactive mode doesn't commit anything, it is only for staging.

  ## Working with patches and hunks

  There's not much utility in interactive mode if you are commiting whole files.
  You can just use `git add [file]` and not bother with it.

  But it is useful for staging sub-portions of a file, which are called **hunks**.

  We access these via **patch mode**:

  ![](/img/git-patch-mode.png)

  ### Splitting hunks

  Git will try to suggest the best hunks it can but these will sometimes not
  reflect the changes you want. Sometimes they will reflect multiple changes
  rather than a single change.

  So long as there is an unchanged line between the different changes that it has
  selected as a hunk you can split the hunk further. You just hit `s` in patch
  mode, and it will repeatedly split the hunk each time until you get what you
  want.

  This is still an automated process and you may not find that the splitting
  results in a hunk you want to commit. In this case you can use manual splitting
  mode. We enter this with `e`.

  This will oped up Vim for the manual work to be done:

  ![](/img/git-manual-hunk.png)

  We use the diff symbols `+`, `-` and space to do this.

  ### Accessing patch mode directly

  If we just want to go straight to patch mode, without anything else:

  ```
  git add -p
  ```

  We can also work with patches in the following commands:

  ```
  git stash -p
  git reset -p
  git restore -p
  git commit -p
  ```

raw_content:
  ---
  tags: [git]
  ---

  # Interactive staging

  With interactive staging we can stage sub-portions of files rather than the
  whole file. This makes commits much more targetted and atomic.

  ## Basic use

  We enter interactive mode with:

  ```
  git add -i
  ```

  This opens an interface:

  ![](/img/git-interactive-mode-2.png)

  We select 2 and it lets us stage by number. If I enter 1, it will stage the
  first change.

  If we then go into 3 for revert, we can undo what we have staged.

  > Note that untracked files are not automatically added to the changes. We have
  > to manually add them with 4 ("add untracked").

  > Interactive mode doesn't commit anything, it is only for staging.

  ## Working with patches and hunks

  There's not much utility in interactive mode if you are commiting whole files.
  You can just use `git add [file]` and not bother with it.

  But it is useful for staging sub-portions of a file, which are called **hunks**.

  We access these via **patch mode**:

  ![](/img/git-patch-mode.png)

  ### Splitting hunks

  Git will try to suggest the best hunks it can but these will sometimes not
  reflect the changes you want. Sometimes they will reflect multiple changes
  rather than a single change.

  So long as there is an unchanged line between the different changes that it has
  selected as a hunk you can split the hunk further. You just hit `s` in patch
  mode, and it will repeatedly split the hunk each time until you get what you
  want.

  This is still an automated process and you may not find that the splitting
  results in a hunk you want to commit. In this case you can use manual splitting
  mode. We enter this with `e`.

  This will oped up Vim for the manual work to be done:

  ![](/img/git-manual-hunk.png)

  We use the diff symbols `+`, `-` and space to do this.

  ### Accessing patch mode directly

  If we just want to go straight to patch mode, without anything else:

  ```
  git add -p
  ```

  We can also work with patches in the following commands:

  ```
  git stash -p
  git reset -p
  git restore -p
  git commit -p
  ```


path: Internet_Layer_of_Internet_Protocol.md
sortable_path: Internet_Layer_of_Internet_Protocol.md
title: Internet Layer of the Internet Protocol Suite
checksum: fd97eae4f1441c51acff3d52fc5d636be1e7c59d68b435c45a6495faca4fc21e
created: 2025-10-01 13:30:59.2268719+00:00
modified: 2025-08-13 15:57:14.045831507+00:00
metadata: {"created":"Thursday, August 15, 2024","tags":["internet","networks"]}
lead:
  The Internet Layer is concerned with establishing the basis for the connection
  and communication between hosts accross the internet.

body:
  The Internet Layer is concerned with establishing the basis for the connection
  and communication between hosts accross the internet.

  In order for the data to get from a machine on one local network to a machine on
  another local network, each machine must have a way of identifying and
  interfacing with the other.

  The Internet Layer utilises the **Internet Protocol** to determine how devices
  will be publicly identified to devices on other networks and how different
  devices on the same local network will be distinguished from outside of this
  network. This is achieved through
  [Internet Protocol (IP) addresses](./IP_addresses.md).

  ## Packets

  Data is sent accross the internet between hosts as _packets_. These packets,
  when sent and received within the
  [Link Layer](Link_Layer_of_Internet_Protocol.md) on the local network, are
  enclosed within a Link Layer frame (in its payload section):

  ![Internet Layer packet](../img/internet-layer-packet-revised.png)

  Like a frame, an IP packet has a header and a payload. The payload comprises the
  data between sent between hosts. The header contains a source IP address and a
  destination IP address and also specifies which version of the Internet Protocol
  is being used.

  // Need to find out more about packets work.

raw_content:
  ---
  tags:
    - internet
    - networks
  created: Thursday, August 15, 2024
  ---

  # Internet Layer of the Internet Protocol Suite

  The Internet Layer is concerned with establishing the basis for the connection
  and communication between hosts accross the internet.

  In order for the data to get from a machine on one local network to a machine on
  another local network, each machine must have a way of identifying and
  interfacing with the other.

  The Internet Layer utilises the **Internet Protocol** to determine how devices
  will be publicly identified to devices on other networks and how different
  devices on the same local network will be distinguished from outside of this
  network. This is achieved through
  [Internet Protocol (IP) addresses](./IP_addresses.md).

  ## Packets

  Data is sent accross the internet between hosts as _packets_. These packets,
  when sent and received within the
  [Link Layer](Link_Layer_of_Internet_Protocol.md) on the local network, are
  enclosed within a Link Layer frame (in its payload section):

  ![Internet Layer packet](../img/internet-layer-packet-revised.png)

  Like a frame, an IP packet has a header and a payload. The payload comprises the
  data between sent between hosts. The header contains a source IP address and a
  destination IP address and also specifies which version of the Internet Protocol
  is being used.

  // Need to find out more about packets work.


path: Internet_fundamentals.md
sortable_path: Internet_fundamentals.md
title: Internet fundamentals
checksum: 74fbd8a154de3ccf6b6fc4bba7e3b09e2c7c260d38dd2e21d11b735908d767e2
created: 2025-10-01 13:30:59.239564754+00:00
modified: 2025-08-13 15:57:13.812918916+00:00
metadata: {"created":"Saturday, August 03, 2024","tags":["networks"]}
lead:
  The internet is a _[network](Network_fundamentals.md) of networks_. A globally
  connected set of computer networks that all use a suite of common
  [protocols](Network_protocols.md).

body:
  The internet is a _[network](Network_fundamentals.md) of networks_. A globally
  connected set of computer networks that all use a suite of common
  [protocols](Network_protocols.md).

  The protocols governing the communication between networks on the internet are
  specified in the **Internet Protocol Suite**.

  This combines two protocols:

  - **TCP**
    - The Transmission Control Protocol
  - **IP**
    - The Internet Protocol

  Hence the Internet Protocol Suite is also known as "TCP/IP".

  The TCP/IP is a network stack comprising four layers, each with its own
  protocols. From the top:

  ![TCP/IP stack diagram](../img/TCP_IP.png)

  - The [Link Layer](Link_Layer_of_Internet_Protocol.md) concerns communication
    between devices on the same local network. The transfer of information once it
    has been received from the internet or its transfer between devices before it
    sent out into the internet. Ethernet and WiFi are two core protocols utilised
    at this layer.

  - The [Internet Layer](Internet_Layer_of_Internet_Protocol.md) is concerned with
    communication accross networks. It is responsible for identifying hosts on the
    inter-network by their addresses and routing data from network to network.

  - The [Transport Layer](Transport_Layer_of_Internet_Protocol.md) creates a
    commmunication channel between two hosts using ports and sockets.

  - The [Application Layer](Application_Layer_of_Internet_Protocol.md) concerned
    with software that utilises information received and sent over the internet,
    for instance web pages and emails. This is the highest level of abstraction
    for which all the lower levels serve as "plumbing". Examples of protocols
    operative at the Application Layer include HTTP and SSH.

  See:
  [Example_scenario_internet_data_transfer](Example_scenario_internet_data_transfer.md)

raw_content:
  ---
  tags: [networks]
  created: Saturday, August 03, 2024
  ---

  # Internet fundamentals

  The internet is a _[network](Network_fundamentals.md) of networks_. A globally
  connected set of computer networks that all use a suite of common
  [protocols](Network_protocols.md).

  The protocols governing the communication between networks on the internet are
  specified in the **Internet Protocol Suite**.

  This combines two protocols:

  - **TCP**
    - The Transmission Control Protocol
  - **IP**
    - The Internet Protocol

  Hence the Internet Protocol Suite is also known as "TCP/IP".

  The TCP/IP is a network stack comprising four layers, each with its own
  protocols. From the top:

  ![TCP/IP stack diagram](../img/TCP_IP.png)

  - The [Link Layer](Link_Layer_of_Internet_Protocol.md) concerns communication
    between devices on the same local network. The transfer of information once it
    has been received from the internet or its transfer between devices before it
    sent out into the internet. Ethernet and WiFi are two core protocols utilised
    at this layer.

  - The [Internet Layer](Internet_Layer_of_Internet_Protocol.md) is concerned with
    communication accross networks. It is responsible for identifying hosts on the
    inter-network by their addresses and routing data from network to network.

  - The [Transport Layer](Transport_Layer_of_Internet_Protocol.md) creates a
    commmunication channel between two hosts using ports and sockets.

  - The [Application Layer](Application_Layer_of_Internet_Protocol.md) concerned
    with software that utilises information received and sent over the internet,
    for instance web pages and emails. This is the highest level of abstraction
    for which all the lower levels serve as "plumbing". Examples of protocols
    operative at the Application Layer include HTTP and SSH.

  See:
  [Example_scenario_internet_data_transfer](Example_scenario_internet_data_transfer.md)


path: Intersection_types_in_TypeScript.md
sortable_path: Intersection_types_in_TypeScript.md
title: Intersection types
checksum: f5d651d47122ae6fa8ecc204ac6dad3d06c12d1531df51dbfcbaafb1aaea5b91
created: 2025-10-01 13:30:59.277305704+00:00
modified: 2025-08-13 15:57:13.895482732+00:00
metadata: {"tags":["typescript"]}
lead:
  Like union types, intersection types allow us to combine the properties of
  existing primitive and custom types to make composites.

body:
  Like union types, intersection types allow us to combine the properties of
  existing primitive and custom types to make composites.

  ### Creating an intersection

  Say you have the following three types:

  ```ts
  type IConcert = {
    date: number;
    location: string;
    band: string;
  };

  type IConference = {
    date: number;
    location: string;
    keynoteSpeaker: string;
  };

  type IMeeting = {
    date: number;
    location: string;
    agenda: string[];
  };
  ```

  Each type corresponds to a different event and has two properties in common with
  the other events: `date` and `location`.

  Rather than repeat these properties for each individual type, we could create a
  general `IEvent` type and apply this as an intersection to each of the
  individual event types:

  ```ts
  // The common event type
  type IEvent = {
    date: number;
    location: string;
  };
  ```

  ```ts
  // The intersection

  type IConcert = IEvent & {
    band: string;
  };

  type IConference = IEvent & {
    keynoteSpeaker: string;
  };
  ```

  > In the case of interfaces, instead of using `&` we would write
  > `interface IConcert extends IEvent`

  ### Alternative method

  There is another equivalent way of intersecting the types. We could first define
  `IConcert` and `IConference` as their own types (without `extends` or `&`) and
  then intersect with, e.g., type IConcert = SelfStandingConcertType & IEvent.

  ### Benefits of intersections

  - It is more concise and we do not repeat ourselves needlessly
  - Common properties can be modelled **in one place** which makes updates and
    changes much easier: we do not have to change multiple type declarations, just
    change the 'master' type that is intersected with the variants.
  - It is more readable: we can see which properties are unique to the type
    without having to parse repeated properties.

  ## Set theory: understanding errors

  With intersections we literally use the & operator which discloses that the
  logic is equivalent to logical AND: a member of the intersection must have the
  properties of both types. In the case of primitive values this is impossible or,
  more accurately, it results in an empty set because, for example, `string` and
  `boolean` do not share any common values. This means you will get an error if
  you assign an intersection type to a value that does not have all the features
  of each custom type in the intersection.

raw_content:
  ---
  tags:
    - typescript
  ---

  # Intersection types

  Like union types, intersection types allow us to combine the properties of
  existing primitive and custom types to make composites.

  ### Creating an intersection

  Say you have the following three types:

  ```ts
  type IConcert = {
    date: number;
    location: string;
    band: string;
  };

  type IConference = {
    date: number;
    location: string;
    keynoteSpeaker: string;
  };

  type IMeeting = {
    date: number;
    location: string;
    agenda: string[];
  };
  ```

  Each type corresponds to a different event and has two properties in common with
  the other events: `date` and `location`.

  Rather than repeat these properties for each individual type, we could create a
  general `IEvent` type and apply this as an intersection to each of the
  individual event types:

  ```ts
  // The common event type
  type IEvent = {
    date: number;
    location: string;
  };
  ```

  ```ts
  // The intersection

  type IConcert = IEvent & {
    band: string;
  };

  type IConference = IEvent & {
    keynoteSpeaker: string;
  };
  ```

  > In the case of interfaces, instead of using `&` we would write
  > `interface IConcert extends IEvent`

  ### Alternative method

  There is another equivalent way of intersecting the types. We could first define
  `IConcert` and `IConference` as their own types (without `extends` or `&`) and
  then intersect with, e.g., type IConcert = SelfStandingConcertType & IEvent.

  ### Benefits of intersections

  - It is more concise and we do not repeat ourselves needlessly
  - Common properties can be modelled **in one place** which makes updates and
    changes much easier: we do not have to change multiple type declarations, just
    change the 'master' type that is intersected with the variants.
  - It is more readable: we can see which properties are unique to the type
    without having to parse repeated properties.

  ## Set theory: understanding errors

  With intersections we literally use the & operator which discloses that the
  logic is equivalent to logical AND: a member of the intersection must have the
  properties of both types. In the case of primitive values this is impossible or,
  more accurately, it results in an empty set because, for example, `string` and
  `boolean` do not share any common values. This means you will get an error if
  you assign an intersection type to a value that does not have all the features
  of each custom type in the intersection.


path: Intro_to_the_Unix_shell.md
sortable_path: Intro_to_the_Unix_shell.md
title: Introduction to the Unix shell
lead: ## Unix based systems
checksum: 729368e67cdd533f552430cf7186e4e791cbdefc0b45b3909482857a0fba8ada
created: 2025-10-01 13:30:59.28094382+00:00
modified: 2025-08-13 15:57:13.958190281+00:00
metadata: {"tags":["shell"]}
body:
  ## Unix based systems

  Many operating systems are based on the UNIX software architecture. macOS/OSX
  (Darwin) and GNU/Linux are two very popular examples. Most web servers run a
  version of Linux as their native OS thus a knowledge of command line for UNIX
  systems is invaluable for web developers. Windows systems are not UNIX based so
  everything that is written here is not applicable to the Windows command line
  program Command Prompt although there are obvious conceptual overlaps.

  UNIX was initially developed by AT&T and is still owned by them. GNU (the basis
  for Linux) is a recursive acronym for ’GNU is not UNIX’. Functionally it is the
  same as Unix however, it just doesn’t contain the proprietary code.

  ## Key terms

  ### Kernal

  The kernel is the central part of an operating system. It manages the operations
  of the computer and the hardware; most notably memory and CPU time. There are
  two types of kernels: a **micro-kernel**, which only contains basic
  functionality; and a **monolithic kernel**, which contains many device drivers.

  ### Shell

  A shell is a user interface for access to an operating system’s services. In
  general, operating system shells use either a command-line interface or GUI,
  depending on a computer’s role and particular operation. It is named a shell
  because it is the outermost layer around the operating system kernel

  ### Bash

  Bash is a Unix shell and command language written by Brian Fox for the GNU
  Project as a free software replacement for the Bourne shell. First released in
  1989, it has been distributed widely as the default login shell for most Linux
  distributions and Apple’s macOS.

  ## What is the command line?

  Command line is both a general computing concept and a specific utility. In its
  general sense, engaging with a computer via command line means inputting
  abbreviated written commands into a console or terminal window on a computer.
  The computer then runs these commands on the basis of the instructions you have
  given.

  You can get the computer to ‘do things’ (make files, connect to networks etc)
  via command line and you can use it to check the status of systems within your
  computer.

  This mode of engagement contrasts with the engagement facilitated by modern
  graphical user interfaces (GUIs) where the user navigates via a combination of
  keyboard and mouse inputs and interacts with windows whose functionality is
  usually self-explanatory and therefore more user-friendly. Command line predates
  GUIs but it is considered a more efficient and expansive means of interacting
  with the whole of your computer especially if you are a developer or programmer.

  In the more specific sense, the ‘command line’ refers to the location in the
  console window where you input your commands. The command line consists in a
  user name and the current directory you are in followed by a dollar sign and a
  cursor waiting for input.

  ## Basic orientation

  When you are using command line you are always situated somewhere within a file
  system. Typically this will be your own user files. directory structure By
  default you are always in your home directory, this is represented by the tilda
  symbol: `~`. This is why you see the tilda symbol as part of the command line.
  In command line, we do not use the term ‘folders’, instead they are called
  directories. (I will follow this convention from hereon.) You do not have to
  worry too much about remembering the names of specific folders and files you can
  always ask the computer to display them to you by using `ls` argument or `pwd`.

  ## Command line syntax

  The syntax of the language used to input commands is analogous to the grammar of
  natural languages. We have a verb that is operative on an object/noun and which
  can be modified through adverbs. The syntax of a command sequence is as follows:

  1. Command (verb): what we want to do
  1. Option (adverb): modifying the command - always starts with a hyphen
  1. Argument (noun/object): what we want our command to operate on

  We will see that not all commands require arguments, but this is the general
  structure

raw_content:
  ---
  tags:
    - shell
  ---

  # Introduction to the Unix shell

  ## Unix based systems

  Many operating systems are based on the UNIX software architecture. macOS/OSX
  (Darwin) and GNU/Linux are two very popular examples. Most web servers run a
  version of Linux as their native OS thus a knowledge of command line for UNIX
  systems is invaluable for web developers. Windows systems are not UNIX based so
  everything that is written here is not applicable to the Windows command line
  program Command Prompt although there are obvious conceptual overlaps.

  UNIX was initially developed by AT&T and is still owned by them. GNU (the basis
  for Linux) is a recursive acronym for ’GNU is not UNIX’. Functionally it is the
  same as Unix however, it just doesn’t contain the proprietary code.

  ## Key terms

  ### Kernal

  The kernel is the central part of an operating system. It manages the operations
  of the computer and the hardware; most notably memory and CPU time. There are
  two types of kernels: a **micro-kernel**, which only contains basic
  functionality; and a **monolithic kernel**, which contains many device drivers.

  ### Shell

  A shell is a user interface for access to an operating system’s services. In
  general, operating system shells use either a command-line interface or GUI,
  depending on a computer’s role and particular operation. It is named a shell
  because it is the outermost layer around the operating system kernel

  ### Bash

  Bash is a Unix shell and command language written by Brian Fox for the GNU
  Project as a free software replacement for the Bourne shell. First released in
  1989, it has been distributed widely as the default login shell for most Linux
  distributions and Apple’s macOS.

  ## What is the command line?

  Command line is both a general computing concept and a specific utility. In its
  general sense, engaging with a computer via command line means inputting
  abbreviated written commands into a console or terminal window on a computer.
  The computer then runs these commands on the basis of the instructions you have
  given.

  You can get the computer to ‘do things’ (make files, connect to networks etc)
  via command line and you can use it to check the status of systems within your
  computer.

  This mode of engagement contrasts with the engagement facilitated by modern
  graphical user interfaces (GUIs) where the user navigates via a combination of
  keyboard and mouse inputs and interacts with windows whose functionality is
  usually self-explanatory and therefore more user-friendly. Command line predates
  GUIs but it is considered a more efficient and expansive means of interacting
  with the whole of your computer especially if you are a developer or programmer.

  In the more specific sense, the ‘command line’ refers to the location in the
  console window where you input your commands. The command line consists in a
  user name and the current directory you are in followed by a dollar sign and a
  cursor waiting for input.

  ## Basic orientation

  When you are using command line you are always situated somewhere within a file
  system. Typically this will be your own user files. directory structure By
  default you are always in your home directory, this is represented by the tilda
  symbol: `~`. This is why you see the tilda symbol as part of the command line.
  In command line, we do not use the term ‘folders’, instead they are called
  directories. (I will follow this convention from hereon.) You do not have to
  worry too much about remembering the names of specific folders and files you can
  always ask the computer to display them to you by using `ls` argument or `pwd`.

  ## Command line syntax

  The syntax of the language used to input commands is analogous to the grammar of
  natural languages. We have a verb that is operative on an object/noun and which
  can be modified through adverbs. The syntax of a command sequence is as follows:

  1. Command (verb): what we want to do
  1. Option (adverb): modifying the command - always starts with a hyphen
  1. Argument (noun/object): what we want our command to operate on

  We will see that not all commands require arguments, but this is the general
  structure


path: Inversion_of_operators.md
sortable_path: Inversion_of_operators.md
title: Inversion of operators
lead: ## Addition, subtraction
checksum: 1a1ebe83392d9fc836df388f84a67c4305490a99f31da6c5b6c3df299d15b685
created: 2025-10-01 13:30:59.285195121+00:00
modified: 2025-08-13 15:57:13.812302626+00:00
metadata: {"tags":["prealgebra"]}
body:
  ## Addition, subtraction

  Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6

  $$

  Subtraction is the inverse of addition: $$(x + a) - a = x$$ $$ (3 + 2) - 2 = 3$$

  Division is the inverse of multiplication $$ \frac{a \cdot x}{a} = x$$
  $$ \frac{6
  \cdot 3}{6} = 3
  $$

  Multiplication is the inverse of division $$ a \cdot \frac{x}{a} = x$$

  $$
  2 \cdot
  \frac{8}{2} = 8
  $$

raw_content:
  ---
  tags:
    - prealgebra
  ---

  # Inversion of operators

  ## Addition, subtraction

  Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6

  $$

  Subtraction is the inverse of addition: $$(x + a) - a = x$$ $$ (3 + 2) - 2 = 3$$

  Division is the inverse of multiplication $$ \frac{a \cdot x}{a} = x$$
  $$ \frac{6
  \cdot 3}{6} = 3
  $$

  Multiplication is the inverse of division $$ a \cdot \frac{x}{a} = x$$

  $$
  2 \cdot
  \frac{8}{2} = 8
  $$


path: Invoking_the_shell_in_Python.md
sortable_path: Invoking_the_shell_in_Python.md
title: Invoking the shell in Python
lead:
  ```py
  import subprocess

checksum: e86663be11247bed326351c2daf35c5a26ad5a2c1b9fb0e2d898f860bba9d678
created: 2025-10-01 13:30:59.289033998+00:00
modified: 2025-08-13 15:57:13.951535119+00:00
metadata: {"created":"Monday, April 29, 2024","id":"cfr4","tags":["python","shell"]}
body:
  ```py
  import subprocess

  try:
      process = subprocess.run(
          ["ls", "-la"],
          shell=True,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
      )
      return process.stdout

  except subprocess.CalledProcessError as e:
          return e.stderr.strip()
  ```

raw_content:
  ---
  id: cfr4
  tags: [python, shell]
  created: Monday, April 29, 2024
  ---

  # Invoking the shell in Python

  ```py
  import subprocess

  try:
      process = subprocess.run(
          ["ls", "-la"],
          shell=True,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
      )
      return process.stdout

  except subprocess.CalledProcessError as e:
          return e.stderr.strip()
  ```


path: Iterating_in_React.md
sortable_path: Iterating_in_React.md
title: Iterating through data
checksum: a417bdd27a8eaa5408dcb27982df7ef2466161fa88eeacb315e39b99b39ad4d2
created: 2025-10-01 13:30:59.292041509+00:00
modified: 2025-08-13 15:57:13.938562237+00:00
metadata: {"tags":["javascript","react"]}
lead:
  As React follows the **functional programming paradigm** and state versioning,
  looping is not the recommended way of engineering repeated processes since it
  presupposes pre-existing state. Instead React relies on the functional methods
  of modern JavaScript such as `map` , `filter` , `reduce` to output the values of
  iterative data structures.

body:
  As React follows the **functional programming paradigm** and state versioning,
  looping is not the recommended way of engineering repeated processes since it
  presupposes pre-existing state. Instead React relies on the functional methods
  of modern JavaScript such as `map` , `filter` , `reduce` to output the values of
  iterative data structures.

  Instead of mutating the state of a reference type, these methods are
  'non-destructive': they create a new array that meets the requirements set in
  the particular method. These newly created arrays and objects therefore form the
  basis of the state versioning, because each new array that is engendered, is a
  new state configuration.

  ## The `key` attribute

  The `key` attribute is a special React attribute that should be added to the
  elements of any listed content (i.e numbered lists, unordered lists, `select`
  options etc; basically anything rendered that comes about through mapping arrays
  and objects).

  This enables React to monitor the state of the list: which items have changed,
  been added, or removed.

  The best way to pick a key is to use a string that uniquely identifies a list
  item among its siblings.

  For instance, using our earlier `numbers` array, assuming each number is unique,
  we could use the number itself as the key:

  ```jsx
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) => (
    <li key={number.toString()}> {number}</li>
  ));
  ```

  > Keys have to be unique to the individual element and not shared by any other
  > sibling. But they do not need to be globally unique. You could use the same
  > `key` name in another array list.

  > Keys should be string types, not other data types. You can use the
  > `.toString()` method to ensure this is the case.

  If you can't find a unique basis for naming a series of keys, you can use the
  `index` value of the `Array.map` function. This should only be used if the order
  of the items doesn't change. An example:

  ```jsx
  class List extends React.Component {
    render() {
      const demoArr = ["cat", "dog", "mouse"];
      return (
          <ul>
            {demoArr.map((x, index) => (
              <li key={index}>{x}</li>
            ))}
          </ul>
        </div>
      );
    }
  }
  ```

  ## Demonstration

  ### Source data

  ```jsx
  const blogData = [
    {
      id: 1,
      date: "2020-11-15",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
    {
      id: 2,
      date: "2020-08-11",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
  ];
  ```

  ### Child component

  ```jsx
  export default function BlogPreviewCard(props) {
    return (
      <Card>
        <CardMedia image={props.image} />
        <CardContent>
          <Typography>{props.date}</Typography>
          <Typography>{props.title}</Typography>
          <Typography>{props.shortByline}</Typography>
        </CardContent>
        <CardActions>
          <Button>Read</Button>
        </CardActions>
      </Card>
    );
  }
  ```

  ### Iteration

  ```jsx
  // This uses the Material-UI grid component

  import BlogPreviewCard from "../components/BlogPreviewCard"
  import blogData from "../data/blogData

  export default function BlogPreviewSet() {
    return (
      <Grid>
        {blogDummyData.map((data, index) => (
          <Grid key={index}>
            <BlogPreviewCard key={index} {...data} />
          </Grid>
        ))}
      </Grid>
    )
  }
  ```

raw_content:
  ---
  tags:
    - javascript
    - react
  ---

  # Iterating through data

  As React follows the **functional programming paradigm** and state versioning,
  looping is not the recommended way of engineering repeated processes since it
  presupposes pre-existing state. Instead React relies on the functional methods
  of modern JavaScript such as `map` , `filter` , `reduce` to output the values of
  iterative data structures.

  Instead of mutating the state of a reference type, these methods are
  'non-destructive': they create a new array that meets the requirements set in
  the particular method. These newly created arrays and objects therefore form the
  basis of the state versioning, because each new array that is engendered, is a
  new state configuration.

  ## The `key` attribute

  The `key` attribute is a special React attribute that should be added to the
  elements of any listed content (i.e numbered lists, unordered lists, `select`
  options etc; basically anything rendered that comes about through mapping arrays
  and objects).

  This enables React to monitor the state of the list: which items have changed,
  been added, or removed.

  The best way to pick a key is to use a string that uniquely identifies a list
  item among its siblings.

  For instance, using our earlier `numbers` array, assuming each number is unique,
  we could use the number itself as the key:

  ```jsx
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) => (
    <li key={number.toString()}> {number}</li>
  ));
  ```

  > Keys have to be unique to the individual element and not shared by any other
  > sibling. But they do not need to be globally unique. You could use the same
  > `key` name in another array list.

  > Keys should be string types, not other data types. You can use the
  > `.toString()` method to ensure this is the case.

  If you can't find a unique basis for naming a series of keys, you can use the
  `index` value of the `Array.map` function. This should only be used if the order
  of the items doesn't change. An example:

  ```jsx
  class List extends React.Component {
    render() {
      const demoArr = ["cat", "dog", "mouse"];
      return (
          <ul>
            {demoArr.map((x, index) => (
              <li key={index}>{x}</li>
            ))}
          </ul>
        </div>
      );
    }
  }
  ```

  ## Demonstration

  ### Source data

  ```jsx
  const blogData = [
    {
      id: 1,
      date: "2020-11-15",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
    {
      id: 2,
      date: "2020-08-11",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
  ];
  ```

  ### Child component

  ```jsx
  export default function BlogPreviewCard(props) {
    return (
      <Card>
        <CardMedia image={props.image} />
        <CardContent>
          <Typography>{props.date}</Typography>
          <Typography>{props.title}</Typography>
          <Typography>{props.shortByline}</Typography>
        </CardContent>
        <CardActions>
          <Button>Read</Button>
        </CardActions>
      </Card>
    );
  }
  ```

  ### Iteration

  ```jsx
  // This uses the Material-UI grid component

  import BlogPreviewCard from "../components/BlogPreviewCard"
  import blogData from "../data/blogData

  export default function BlogPreviewSet() {
    return (
      <Grid>
        {blogDummyData.map((data, index) => (
          <Grid key={index}>
            <BlogPreviewCard key={index} {...data} />
          </Grid>
        ))}
      </Grid>
    )
  }
  ```


path: Iterating_through_complex_data_structures_in_Python.md
sortable_path: Iterating_through_complex_data_structures_in_Python.md
title: Iterating through complex data structures in Python
lead: // TODO: sections :
checksum: ec3567e218e2db403c03023983dc935a585c61867d668b1797e5faf97b08aee0
created: 2025-10-01 13:30:59.296332356+00:00
modified: 2025-08-13 15:57:13.863271797+00:00
metadata: {"tags":["python"]}
body:
  // TODO: sections :

  - Looping, mapping, filtering lists
  - Looping, mapping, filtering dictionaries
  - As above with multi-dimensional lists
  - As above with list of dictionaries

  ## Complex data structures

  In JavaScript when we return data from an API we tend to use an array of objects
  as the canonical form of a repeating iterable, e.g:

  ```js
  const data = [
    {
      name: "Thomas",
      age: 35,
    },
    {
      name: "Joe",
      age: 75,
    },
  ];
  ```

  In Python there are two common ways to handle similar data structures:

  - A list of lists:
    ```py
    data = [
      ["Thomas", 35],
      ["Joe", 75]
    ]
    ```
  - A list of dictionaries:
    ```py
    data = [
       {"name": "Thomas", "age": 35},
       {"name": "Joe", "age": 75},
    ]
    ```

  ## Multi-dimensional lists (list of lists)

  ### Sorting by common property

  Assuming the sub-lists have an identical structure, you can
  [sort](Sorting_lists_in_Python.md) them by
  a common property by passing a
  [lambda function](Lambdas_in_Python.md) to
  the `key` value of `sorted()` and `.sort()`.

  For example, to sort the following list of lists by the second `age` property:

  ```python
  people = [["Alice", 30], ["Bob", 25], ["Clare", 35], ["Dave", 28]]
  ```

  Using `sorted()`:

  ```py
  sorted_people = sorted(people, key=lambda x: x[1])
  print(sorted_people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  Using `.sort`:

  ```py
  people.sort(key=lambda x: x[1])
  print(people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  If you want to sort by name instead, you could change the lambda function to
  `lambda x: x[0]`:

  ```python
  # Using sorted()
  sorted_people = sorted(people, key=lambda x: x[0])
  print(sorted_people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]

  # Using .sort()
  people.sort(key=lambda x: x[0])
  print(people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]
  ```

  ### Updating an inner value within a multidimensional list

  In the following example we have a list of the following structure:

  ```py
  data = [
      ["1688582410", "Article One"],
      ["1688647447", "Article Two"],
      ["1689023491", "Article Three"],
  ]
  ```

  Below we use
  [map](Map_and_filter_in_Python.md) and a
  [lambda function](Lambdas_in_Python.md) to
  convert the first element of each iner list from a Unix timestamp to a readable
  string:

  ```py
   readable_date = list(map(lambda i: [convert_timestamp(i[0])] + i[1:], date))
  ```

  We could also use
  [list comprehension](List_comprehension_in_Python.md)
  to achieve the same outcome:

  ```py
  readable_date = [[convert_timestamp(i[0])] + i[1:] for i in data]
  ```

  ### Filter elements in a multidimensional list

  Say we have the following data structure:

  ```py
  name_age = [ ["Anthony", 16], ["Christopher", 22], ["James", 6] ]
  ```

  We can return only the people who are older than 18 with the following `filter`
  function and `lambda`:

  ```py
  filtered_ages = list(filter(lambda person: person[1] > 18, name_age))
  ```

  ### Remove duplicate entries from multidimensional list

  If we are just working with a normal list, a quick way to remove duplicates is
  just:

  ```py
  our_list = [9, 9, 3, 2]
  unique = list(set(our_list))
  ```

  This won't work with a list of lists because lists are not hashable.

  There are different approaches. Let's say we have the following multidimensional
  list:

  ```py
  dummy_data = [
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
      ["1688582410", "Article One", "Lorem ipsum...", "https://example.com"],
      ["1688647447", "Article Two", "Lorem ipsum...", "https://example.com"],
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
  ]
  ```

  Here is one method:

  ```py
  unique = []
  seen = set()
  for element in dummy_data:
      element_to_check = element[0]
      if element_to_check not in seen:
          unique.append(element)
          seen.add(element_to_check)
  print(unique)
  ```

  We designate a unique property in the inner lists and then store a unique record
  of each. If the seen set doesn't have the incoming element, we add it to the
  unique array.

  It's more efficient to use a dictionary because it allows us to do this in 0(1)
  time rather than 0(n):

  ```py
  unique_dict = {x[0]: x for x in dummy_data}
  unique_data = list(unique_dict.values())
  ```

  This approach leverages the fact that a dictionary cannot have duplicate keys:
  if you try to insert an element with a key that already exists, the new value
  will simply overwrite the old value for that key.

  Accordingly, we create a dictionary which uses the unique key in each list as
  the key of each dictionary entry via
  [dictionary comprehension](List_comprehension_in_Python.md#dictionary-comprehension)
  that loops through each value in the inner lists of the multidimensional array.
  We then parse the values of the dictionary into a list.

  ## List of dictionaries

raw_content:
  ---
  tags: [python]
  ---

  # Iterating through complex data structures in Python

  // TODO: sections :

  - Looping, mapping, filtering lists
  - Looping, mapping, filtering dictionaries
  - As above with multi-dimensional lists
  - As above with list of dictionaries

  ## Complex data structures

  In JavaScript when we return data from an API we tend to use an array of objects
  as the canonical form of a repeating iterable, e.g:

  ```js
  const data = [
    {
      name: "Thomas",
      age: 35,
    },
    {
      name: "Joe",
      age: 75,
    },
  ];
  ```

  In Python there are two common ways to handle similar data structures:

  - A list of lists:
    ```py
    data = [
      ["Thomas", 35],
      ["Joe", 75]
    ]
    ```
  - A list of dictionaries:
    ```py
    data = [
       {"name": "Thomas", "age": 35},
       {"name": "Joe", "age": 75},
    ]
    ```

  ## Multi-dimensional lists (list of lists)

  ### Sorting by common property

  Assuming the sub-lists have an identical structure, you can
  [sort](Sorting_lists_in_Python.md) them by
  a common property by passing a
  [lambda function](Lambdas_in_Python.md) to
  the `key` value of `sorted()` and `.sort()`.

  For example, to sort the following list of lists by the second `age` property:

  ```python
  people = [["Alice", 30], ["Bob", 25], ["Clare", 35], ["Dave", 28]]
  ```

  Using `sorted()`:

  ```py
  sorted_people = sorted(people, key=lambda x: x[1])
  print(sorted_people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  Using `.sort`:

  ```py
  people.sort(key=lambda x: x[1])
  print(people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  If you want to sort by name instead, you could change the lambda function to
  `lambda x: x[0]`:

  ```python
  # Using sorted()
  sorted_people = sorted(people, key=lambda x: x[0])
  print(sorted_people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]

  # Using .sort()
  people.sort(key=lambda x: x[0])
  print(people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]
  ```

  ### Updating an inner value within a multidimensional list

  In the following example we have a list of the following structure:

  ```py
  data = [
      ["1688582410", "Article One"],
      ["1688647447", "Article Two"],
      ["1689023491", "Article Three"],
  ]
  ```

  Below we use
  [map](Map_and_filter_in_Python.md) and a
  [lambda function](Lambdas_in_Python.md) to
  convert the first element of each iner list from a Unix timestamp to a readable
  string:

  ```py
   readable_date = list(map(lambda i: [convert_timestamp(i[0])] + i[1:], date))
  ```

  We could also use
  [list comprehension](List_comprehension_in_Python.md)
  to achieve the same outcome:

  ```py
  readable_date = [[convert_timestamp(i[0])] + i[1:] for i in data]
  ```

  ### Filter elements in a multidimensional list

  Say we have the following data structure:

  ```py
  name_age = [ ["Anthony", 16], ["Christopher", 22], ["James", 6] ]
  ```

  We can return only the people who are older than 18 with the following `filter`
  function and `lambda`:

  ```py
  filtered_ages = list(filter(lambda person: person[1] > 18, name_age))
  ```

  ### Remove duplicate entries from multidimensional list

  If we are just working with a normal list, a quick way to remove duplicates is
  just:

  ```py
  our_list = [9, 9, 3, 2]
  unique = list(set(our_list))
  ```

  This won't work with a list of lists because lists are not hashable.

  There are different approaches. Let's say we have the following multidimensional
  list:

  ```py
  dummy_data = [
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
      ["1688582410", "Article One", "Lorem ipsum...", "https://example.com"],
      ["1688647447", "Article Two", "Lorem ipsum...", "https://example.com"],
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
  ]
  ```

  Here is one method:

  ```py
  unique = []
  seen = set()
  for element in dummy_data:
      element_to_check = element[0]
      if element_to_check not in seen:
          unique.append(element)
          seen.add(element_to_check)
  print(unique)
  ```

  We designate a unique property in the inner lists and then store a unique record
  of each. If the seen set doesn't have the incoming element, we add it to the
  unique array.

  It's more efficient to use a dictionary because it allows us to do this in 0(1)
  time rather than 0(n):

  ```py
  unique_dict = {x[0]: x for x in dummy_data}
  unique_data = list(unique_dict.values())
  ```

  This approach leverages the fact that a dictionary cannot have duplicate keys:
  if you try to insert an element with a key that already exists, the new value
  will simply overwrite the old value for that key.

  Accordingly, we create a dictionary which uses the unique key in each list as
  the key of each dictionary entry via
  [dictionary comprehension](List_comprehension_in_Python.md#dictionary-comprehension)
  that loops through each value in the inner lists of the multidimensional array.
  We then parse the values of the dictionary into a list.

  ## List of dictionaries


path: JavaScript_module_types.md
sortable_path: JavaScript_module_types.md
title: JavaScript module types
checksum: 32ee0dc1c2405758a7981a08a63258512ef33f621d465370885dd3a057a40eff
created: 2025-10-01 13:30:59.334395741+00:00
modified: 2025-08-13 15:57:14.083722292+00:00
metadata: {"created":"Sunday, November 24, 2024","tags":["javascript"]}
lead:
  The following info is useful when you wish to use modern JavaScript tooling
  outside of a build system.

body:
  The following info is useful when you wish to use modern JavaScript tooling
  outside of a build system.

  For instance I wanted to use a bundled React application in a static website not
  created using NPM.

  Great account by
  [Julia Evans](https://jvns.ca/blog/2024/11/18/how-to-import-a-javascript-library/).

  In essence there are three main module formats:

  - CommonJS

    - Native Node JS module system using `require()` and `module.exports()`

  - ESModules

    - The defacto standard that works in both the browser and Node.js

  - AMD

    - A browser-friendly version of CommonJS since superseded by ESModules

  Then there is UMD, _Universal Module Definition_. This is a wrapper that
  combines compatibility for multiple module systems and works with AMD, CommonJS,
  as well as global variables.

raw_content:
  ---
  tags: [javascript]
  created: Sunday, November 24, 2024
  ---

  # JavaScript module types

  The following info is useful when you wish to use modern JavaScript tooling
  outside of a build system.

  For instance I wanted to use a bundled React application in a static website not
  created using NPM.

  Great account by
  [Julia Evans](https://jvns.ca/blog/2024/11/18/how-to-import-a-javascript-library/).

  In essence there are three main module formats:

  - CommonJS

    - Native Node JS module system using `require()` and `module.exports()`

  - ESModules

    - The defacto standard that works in both the browser and Node.js

  - AMD

    - A browser-friendly version of CommonJS since superseded by ESModules

  Then there is UMD, _Universal Module Definition_. This is a wrapper that
  combines compatibility for multiple module systems and works with AMD, CommonJS,
  as well as global variables.


path: Joins_in_SQL.md
sortable_path: Joins_in_SQL.md
title: Joins in SQL
checksum: 6a774fe4238d77ff4c2738c5352ec7e5df7b5f564a96348deaa82f6c931f261f
created: 2025-10-01 13:30:59.338429207+00:00
modified: 2025-08-13 15:57:13.999846029+00:00
metadata: {"tags":["SQL","databases"]}
lead:
  <!-- Once a relationship has been created using primary and foreign keys (as detailed in the previous section), you are able to combine and integrate data from the different tables. This is known as performing **joins.** -->

body:
  <!-- Once a relationship has been created using primary and foreign keys (as detailed in the previous section), you are able to combine and integrate data from the different tables. This is known as performing **joins.** -->

  We perform joins in SQL when we combine and integrate data from multiple tables.
  In order for a join to be possible, **the tables you want to join must have rows
  that share the same fields**.

  The output will be represented as a table but it is virtual, not an actual
  table. (If you wish to actually create a table or a view off the back of a join
  operation you should combine the join with the
  [`CREATE TABLE`](Create_an_SQL_table.md) command etc.)

  ## Inner joins

  > An inner join combines rows that have matching values in two or more tables.
  > This join is based on the existence of a common field between the tables and
  > is used to retrieve data that appears in both tables.

  The following is a good scenario for creating an inner join:

  > We want to create a list of the name of all computers that have been sold and
  > when they were sold.

  We will be working from the following tables.

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |

  The data points we need are not provided in a single table. `sales` gives us the
  `saleDate` but not the `modelName`. In order to meet our requirements we must
  perform a join that combines `modelName` from the `model` table and `saleDate`
  from the `sale` table to give us a table like the following:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |

  We can do this by utilising the field they have in common: `modelId`.

  ### Implementation

  The general syntax for creating an inner join from two tables that share a
  common field is as follows:

  ```sql
  SELECT column_name(s)
  FROM table1
  INNER JOIN table2
  ON table1.columnName = table2.columnName
  ```

  So basically, look at `table1` and `table2` return the rows where the fields for
  `table1` and `table2` match, where these fields are the ones specified in the
  `SELECT`.

  Applied to our example:

  ```sql
  SELECT model.modelName, sales.saleDate -- Name the fields
  FROM model -- Specify the first table
  INNER JOIN sales -- Specify the second table
  ON model.model_id = sales.model_id; -- Specify the match criteria
  ```

  > It is important to note that the match must _be_ a match. Both tables must
  > have a row with the same value for `modelId` for the join to be applied. If
  > there is a `modelId` of X in `sales` but no corresponding X for `modelId` in
  > `model`, nothing will be returned for that row.

  We can represent the logical relationship that obtains between the `sales` and
  `model` tables as follows:

  ![](/img/sql-inner-join.png)

  ## Outer joins

  Outer joins are joins that return matched values **and unmatched** values from
  either or both tables.

  To explain outer joins we need to expand our data set:

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |
  | 4      |         | 2020-03-01 | msmelk     | 699.99  |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |
  | 66      | Google Chromebook    |

  > We will assume that `sales` is our left table and `model` our right.

  ### Left outer join

  A left outer join returns all records from the left or first table and the
  matching records from the right or second table. If there are not matches on the
  right then only the left data is returned.

  In the context of our dataset, in the _sales_ database we have a row where there
  is a sale with no corresponding `modelId`. If we performed a left outer join
  this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |

  The logical relationship sustained between `sales` and `model` by a left inner
  join is represented in the following diagram:

  ![](/img/sql-left-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  LEFT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  LEFT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Right outer join

  This is the inverse of the right outer join. A right outer join returns all
  records from the right or second table and the matching records from the left or
  first table. If there are not matches on the left then only the right data is
  returned.

  In the context of our dataset, in the _model_ database we have a row where there
  is a `modelId` without a corresponding entry in the `sales` table. If we
  performed a right outer join this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  | Google Chromebook    |            |

  The logical relationship sustained between `sales` and `model` by a right inner
  join is represented in the following diagram:

  ![](/img/sql-right-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  RIGHT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  RIGHT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Full outer join

  The full outer join returns all rows when there is a match in either the left or
  the right table. So, for table A and table B the full join returns all rows in A
  and all rows in B including those where there is a match between A and B. This
  is the broadest type of join and can often result in large data sets.

  In the context of our dataset it would result in the following table being
  generated:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |
  | Google Chromebook    |            |

  Represented by the following diagram:

  ![](/img/sql-full-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  FULL OUTER JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  FULL OUTER JOIN model
  ON sales.modelId = model.modelId
  ```

  This type of join is used when you want to discern when there is _not_ a match
  between two fields across tables. For example: imagine that you wanted a list of
  computers that had never been sold. In this case, you would be interested in
  rows where there is a `model_id` without a corresponding `sales_id` .

  In SQL this would be achieved with:

  ```sql
  SELECT model.name, sales.sale_date
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id;
  ```

  Note that this would return all the model names but where there isn't a sale
  data, `NULL` would be returned. This is an **important distinction :** the outer
  join method doesn't just return the rows with a `NULL` value for `sale_date` as
  we might expect. It returns all models along with those that have not been sold.
  This is because it is oriented to the "left" table, equivalent to the table in
  the SQL that we cited first with the `on` keyword.

  > A left outer join returns all the records from the left (model) table and
  > those that match in the right (sales) table. Where there are no matching
  > records in the right (sales) table, a `NULL` value is returned.

  A **right outer join**, often referred to as a right join, is the opposite of a
  left outer; it returns all the records from the right table and those that match
  in the left table. In our scenario this would be all the models that had a
  `sale_date` including models that didn't have a `sale_date` , i.e which returned
  `NULL`

  Finally, a **full outer join** returns all the records from both tables, and
  where a record cannot be matched, a NULL value is returned. So this would mean
  there could be `NULL`s in both fields of the returned rows.

  We can combine multiple types of join in the same SQL query:

  ```sql
  SELECT model.name, sales.sale_date, manufacturer.url
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id
  INNER JOIN manufacturer on model.manufacturer_id = manufacturer.manufacturer_id;
  ```

raw_content:
  ---
  tags:
    - SQL
    - databases
  ---

  # Joins in SQL

  <!-- Once a relationship has been created using primary and foreign keys (as detailed in the previous section), you are able to combine and integrate data from the different tables. This is known as performing **joins.** -->

  We perform joins in SQL when we combine and integrate data from multiple tables.
  In order for a join to be possible, **the tables you want to join must have rows
  that share the same fields**.

  The output will be represented as a table but it is virtual, not an actual
  table. (If you wish to actually create a table or a view off the back of a join
  operation you should combine the join with the
  [`CREATE TABLE`](Create_an_SQL_table.md) command etc.)

  ## Inner joins

  > An inner join combines rows that have matching values in two or more tables.
  > This join is based on the existence of a common field between the tables and
  > is used to retrieve data that appears in both tables.

  The following is a good scenario for creating an inner join:

  > We want to create a list of the name of all computers that have been sold and
  > when they were sold.

  We will be working from the following tables.

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |

  The data points we need are not provided in a single table. `sales` gives us the
  `saleDate` but not the `modelName`. In order to meet our requirements we must
  perform a join that combines `modelName` from the `model` table and `saleDate`
  from the `sale` table to give us a table like the following:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |

  We can do this by utilising the field they have in common: `modelId`.

  ### Implementation

  The general syntax for creating an inner join from two tables that share a
  common field is as follows:

  ```sql
  SELECT column_name(s)
  FROM table1
  INNER JOIN table2
  ON table1.columnName = table2.columnName
  ```

  So basically, look at `table1` and `table2` return the rows where the fields for
  `table1` and `table2` match, where these fields are the ones specified in the
  `SELECT`.

  Applied to our example:

  ```sql
  SELECT model.modelName, sales.saleDate -- Name the fields
  FROM model -- Specify the first table
  INNER JOIN sales -- Specify the second table
  ON model.model_id = sales.model_id; -- Specify the match criteria
  ```

  > It is important to note that the match must _be_ a match. Both tables must
  > have a row with the same value for `modelId` for the join to be applied. If
  > there is a `modelId` of X in `sales` but no corresponding X for `modelId` in
  > `model`, nothing will be returned for that row.

  We can represent the logical relationship that obtains between the `sales` and
  `model` tables as follows:

  ![](/img/sql-inner-join.png)

  ## Outer joins

  Outer joins are joins that return matched values **and unmatched** values from
  either or both tables.

  To explain outer joins we need to expand our data set:

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |
  | 4      |         | 2020-03-01 | msmelk     | 699.99  |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |
  | 66      | Google Chromebook    |

  > We will assume that `sales` is our left table and `model` our right.

  ### Left outer join

  A left outer join returns all records from the left or first table and the
  matching records from the right or second table. If there are not matches on the
  right then only the left data is returned.

  In the context of our dataset, in the _sales_ database we have a row where there
  is a sale with no corresponding `modelId`. If we performed a left outer join
  this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |

  The logical relationship sustained between `sales` and `model` by a left inner
  join is represented in the following diagram:

  ![](/img/sql-left-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  LEFT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  LEFT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Right outer join

  This is the inverse of the right outer join. A right outer join returns all
  records from the right or second table and the matching records from the left or
  first table. If there are not matches on the left then only the right data is
  returned.

  In the context of our dataset, in the _model_ database we have a row where there
  is a `modelId` without a corresponding entry in the `sales` table. If we
  performed a right outer join this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  | Google Chromebook    |            |

  The logical relationship sustained between `sales` and `model` by a right inner
  join is represented in the following diagram:

  ![](/img/sql-right-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  RIGHT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  RIGHT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Full outer join

  The full outer join returns all rows when there is a match in either the left or
  the right table. So, for table A and table B the full join returns all rows in A
  and all rows in B including those where there is a match between A and B. This
  is the broadest type of join and can often result in large data sets.

  In the context of our dataset it would result in the following table being
  generated:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |
  | Google Chromebook    |            |

  Represented by the following diagram:

  ![](/img/sql-full-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  FULL OUTER JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  FULL OUTER JOIN model
  ON sales.modelId = model.modelId
  ```

  This type of join is used when you want to discern when there is _not_ a match
  between two fields across tables. For example: imagine that you wanted a list of
  computers that had never been sold. In this case, you would be interested in
  rows where there is a `model_id` without a corresponding `sales_id` .

  In SQL this would be achieved with:

  ```sql
  SELECT model.name, sales.sale_date
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id;
  ```

  Note that this would return all the model names but where there isn't a sale
  data, `NULL` would be returned. This is an **important distinction :** the outer
  join method doesn't just return the rows with a `NULL` value for `sale_date` as
  we might expect. It returns all models along with those that have not been sold.
  This is because it is oriented to the "left" table, equivalent to the table in
  the SQL that we cited first with the `on` keyword.

  > A left outer join returns all the records from the left (model) table and
  > those that match in the right (sales) table. Where there are no matching
  > records in the right (sales) table, a `NULL` value is returned.

  A **right outer join**, often referred to as a right join, is the opposite of a
  left outer; it returns all the records from the right table and those that match
  in the left table. In our scenario this would be all the models that had a
  `sale_date` including models that didn't have a `sale_date` , i.e which returned
  `NULL`

  Finally, a **full outer join** returns all the records from both tables, and
  where a record cannot be matched, a NULL value is returned. So this would mean
  there could be `NULL`s in both fields of the returned rows.

  We can combine multiple types of join in the same SQL query:

  ```sql
  SELECT model.name, sales.sale_date, manufacturer.url
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id
  INNER JOIN manufacturer on model.manufacturer_id = manufacturer.manufacturer_id;
  ```


