# notes

path: zk/Hexadecimal_number_system.md
sortable_path: zkHexadecimal_number_system.md
title: Hexadecimal number system
checksum: 142383e619077fbab7ffd1fea51bba66be637ab65f95f0a1542c3101798bb564
created: 2024-03-30 09:03:19.740848011+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["number-systems"]}
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]
  ---

  # 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: zk/IO_in_Python.md
sortable_path: zkIO_in_Python.md
title: IO in Python
lead: ## The open() object
checksum: 10fe2480c7405bdff032ec32bfa2b332069ce1a05f24246026df7a1841f434fb
created: 2024-03-30 09:03:19.746239686+00:00
modified: 2024-05-31 11:55:02.785274678+00:00
metadata: {"tags":["python"]}
body:
  ## The open() object

  The built-in `open()` function creates a **file object** that allows us to read,
  write and append to files.

  The general syntax is as follows:

  ```py
  file_object = open(<file_name>, <access_mode>)
  ```

  `<file_name>` is obviously a path to the file you want to read, create or
  modify. The `<access_mode>` denotes the mode in which to open the file. The most
  frequently used are:

  - `r`
    - read
  - `w`
    - write
  - `a`
    - append

  ### All access modes

  In addition we have the following access modes

  ## Reading files

  Once a file object has been intialised with `open()` there are several ways in
  which the content can be read:

  | Read method | Behaviour                                                                                                                                |
  | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
  | `read`      | Return the entire contents of the file as a single string.                                                                               |
  | `readline`  | Read the contents of a file a line at a time. You would combine this with a loop so that you can do something with each individual line. |
  | `readlines` | Return a list of all the lines in a file. Each line will be an element in the list.                                                      |

  ### Read

  `read` reads the entire contents of a file and returns it as a single string.

  `read()` reads the entire file into memory at once, so it may not be suitable
  for very large files that exceed the available memory of your system. In such
  cases, you can use the `readline()` method to read the file line by line
  instead.

  The basic syntax is as follows:

  ```py
  # Open the file for reading
  file = open('filename.txt', 'r')

  # Read the entire contents of the file
  contents = file.read()

  # We could also limit the read to a number of characters:
  contents = file.read(100)

  # Close the file
  file.close()
  ```

  Once we have the file contents stored, we can then interact with it. The
  standard way of doing this is to parse it line by line. For example, say we were
  reading a CSV:

  ```py
  lines = content.split(',')
  for line in lines:
      if line: # if the line is not empty
          # do something with line
  ```

  ### Readline

  > The readline() method in Python is used to read a single line from a file. It
  > is typically used when you want to process a file line by line, rather than
  > reading the entire file into memory at once.

  `readline()` returns the line including the newline character at the end of the
  line, so you may need to strip this character off using the strip() method
  before processing the line further.

  ```python
  # Open the file for reading
  file = open('filename.txt', 'r')

  # Loop through the file, reading one line at a time
  line = file.readline()
  while line:
      # Strip off the newline character
      line = line.strip()

      # Do something with the line
      print(line)

      # Read the next line
      line = file.readline()

  # Close the file
  file.close()
  ```

  ### Readlines

  The `readlines()` method is used to read all the lines of a file and return them
  as a list of strings, where each element of the list is a line from the file.

  ```py
  # Open the file for reading
  file = open('filename.txt', 'r')

  # Read all the lines of the file and store them in a list
  lines = file.readlines()

  # Loop through the lines and print them to the console
  for line in lines:
      print(line)

  # Close the file
  file.close()
  ```

  ### Error handling

  Obviously file access can raise errors - typically when the file you want to
  access does not exist (i.e. a `FileNotFoundError`
  [exception](Error_handling_in_Python.md)). We can manage this scenario with
  [exception handlers](Error_handling_in_Python.md):

  ```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)
  ```

  ## Close and "with as"

  You notice that once we have finished with our I/O operation, we must call
  `file.close()` to terminate the process. This removes the reference to the file
  from memory.

  A more pythonic and concise way of reading files and closing them is to use
  `with...as` syntax. When this phrasing is used, a self-contained context is
  created for the I/O operation that closes the file automatically.

  ```py
  with open('filename.txt', 'r') as file:
      contents = file.read()
      print(contents)
  ```

  ## Writing to files

  Again we create a file object with `open()` and this time use the `write`
  method:

  ```py
  # Open file in write mode
  file = open("example.txt", "w")

  # Write some text to the file
  file.write("Hello, this is an example text written using Python.")

  # Close the file
  file.close()
  ```

  > Note that in the above example, if the file does not already exist, it will
  > create it. If it does exist, it will overwrite its contents with the new data.
  > So we use `write` to create new files as well as to write to existing files.

  ## Renaming and deleting files

  We have to use another built-in module to rename and delete files: `os`.

  To rename an existing file:

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

  To delete a file:

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

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

  # IO in Python

  ## The open() object

  The built-in `open()` function creates a **file object** that allows us to read,
  write and append to files.

  The general syntax is as follows:

  ```py
  file_object = open(<file_name>, <access_mode>)
  ```

  `<file_name>` is obviously a path to the file you want to read, create or
  modify. The `<access_mode>` denotes the mode in which to open the file. The most
  frequently used are:

  - `r`
    - read
  - `w`
    - write
  - `a`
    - append

  ### All access modes

  In addition we have the following access modes

  ## Reading files

  Once a file object has been intialised with `open()` there are several ways in
  which the content can be read:

  | Read method | Behaviour                                                                                                                                |
  | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
  | `read`      | Return the entire contents of the file as a single string.                                                                               |
  | `readline`  | Read the contents of a file a line at a time. You would combine this with a loop so that you can do something with each individual line. |
  | `readlines` | Return a list of all the lines in a file. Each line will be an element in the list.                                                      |

  ### Read

  `read` reads the entire contents of a file and returns it as a single string.

  `read()` reads the entire file into memory at once, so it may not be suitable
  for very large files that exceed the available memory of your system. In such
  cases, you can use the `readline()` method to read the file line by line
  instead.

  The basic syntax is as follows:

  ```py
  # Open the file for reading
  file = open('filename.txt', 'r')

  # Read the entire contents of the file
  contents = file.read()

  # We could also limit the read to a number of characters:
  contents = file.read(100)

  # Close the file
  file.close()
  ```

  Once we have the file contents stored, we can then interact with it. The
  standard way of doing this is to parse it line by line. For example, say we were
  reading a CSV:

  ```py
  lines = content.split(',')
  for line in lines:
      if line: # if the line is not empty
          # do something with line
  ```

  ### Readline

  > The readline() method in Python is used to read a single line from a file. It
  > is typically used when you want to process a file line by line, rather than
  > reading the entire file into memory at once.

  `readline()` returns the line including the newline character at the end of the
  line, so you may need to strip this character off using the strip() method
  before processing the line further.

  ```python
  # Open the file for reading
  file = open('filename.txt', 'r')

  # Loop through the file, reading one line at a time
  line = file.readline()
  while line:
      # Strip off the newline character
      line = line.strip()

      # Do something with the line
      print(line)

      # Read the next line
      line = file.readline()

  # Close the file
  file.close()
  ```

  ### Readlines

  The `readlines()` method is used to read all the lines of a file and return them
  as a list of strings, where each element of the list is a line from the file.

  ```py
  # Open the file for reading
  file = open('filename.txt', 'r')

  # Read all the lines of the file and store them in a list
  lines = file.readlines()

  # Loop through the lines and print them to the console
  for line in lines:
      print(line)

  # Close the file
  file.close()
  ```

  ### Error handling

  Obviously file access can raise errors - typically when the file you want to
  access does not exist (i.e. a `FileNotFoundError`
  [exception](Error_handling_in_Python.md)). We can manage this scenario with
  [exception handlers](Error_handling_in_Python.md):

  ```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)
  ```

  ## Close and "with as"

  You notice that once we have finished with our I/O operation, we must call
  `file.close()` to terminate the process. This removes the reference to the file
  from memory.

  A more pythonic and concise way of reading files and closing them is to use
  `with...as` syntax. When this phrasing is used, a self-contained context is
  created for the I/O operation that closes the file automatically.

  ```py
  with open('filename.txt', 'r') as file:
      contents = file.read()
      print(contents)
  ```

  ## Writing to files

  Again we create a file object with `open()` and this time use the `write`
  method:

  ```py
  # Open file in write mode
  file = open("example.txt", "w")

  # Write some text to the file
  file.write("Hello, this is an example text written using Python.")

  # Close the file
  file.close()
  ```

  > Note that in the above example, if the file does not already exist, it will
  > create it. If it does exist, it will overwrite its contents with the new data.
  > So we use `write` to create new files as well as to write to existing files.

  ## Renaming and deleting files

  We have to use another built-in module to rename and delete files: `os`.

  To rename an existing file:

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

  To delete a file:

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


path: zk/Identify_merged_branches.md
sortable_path: zkIdentify_merged_branches.md
title: Identify merged branches
lead:
  ```sh
  # View merged
  git branch --merged

checksum: 4276aeff9c2f164c65c35fb617983aa2739633e1617b080b74ae32b35baacc1a
created: 2024-03-30 09:03:19.756286126+00:00
modified: 2024-03-27 10:45:52.503525368+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: zk/Increasing_fractions_to_their_highest_terms.md
sortable_path: zkIncreasing_fractions_to_their_highest_terms.md
title: Demonstration
lead: _Express $\frac{3}{4}$ as an equivalent fraction having the denominator 20_
checksum: 8478dbec4697a42d8a0300f8f8c1b9cd322f9929519fc437c36fe3a875f3f5f3
created: 2024-03-30 09:03:19.762808771+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","fractions"]}
body:
  _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
  ---

  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: zk/Insert_data_into_SQL_table.md
sortable_path: zkInsert_data_into_SQL_table.md
title: Insert data into table with SQL INSERT statement
lead: _A table named `sales`:_
checksum: c665728b91472161cc4b0d63c8626aec625bba9226159c46e3f801e7b51242e0
created: 2024-03-30 09:03:19.772063797+00:00
modified: 2024-06-15 09:46:40.662102255+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: zk/Instruction_set_architectures.md
sortable_path: zkInstruction_set_architectures.md
title: Instruction Set Architectures
lead: ## Summary
checksum: 38ccb65b129d4cbcb060765e9e148bd9527299c2ec783167fa140f86880ee1db
created: 2024-03-30 09:03:19.774620306+00:00
modified: 2024-04-09 06:25:57.380871694+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: zk/Integrated_circuits.md
sortable_path: zkIntegrated_circuits.md
title: Integrated circuits
checksum: 73ebcb419feaa64000026c9642ef49530fa55a95c552243d76711cc350533d4d
created: 2024-03-30 09:03:19.796542348+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["logic-gates"]}
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 align="left" width="200" src="/home/thomas/repos/computer_science/img/integrated-circuit.jpeg">
  <img  width="200" src="/home/thomas/repos/computer_science/img/breadboard-DIP.jpg">

raw_content:
  ---
  tags: [logic-gates]
  ---

  # 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 align="left" width="200" src="/home/thomas/repos/computer_science/img/integrated-circuit.jpeg">
  <img  width="200" src="/home/thomas/repos/computer_science/img/breadboard-DIP.jpg">


path: zk/Interactive_staging.md
sortable_path: zkInteractive_staging.md
title: Interactive staging
checksum: fdef4d4ff9933facb66f0b9ca70df3e69d95d9376ecb3748f72eb15d9a91a064
created: 2024-03-30 09:03:19.801911632+00:00
modified: 2024-03-27 10:45:52.503525368+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: zk/Intersection_types.md
sortable_path: zkIntersection_types.md
title: Intersection types
checksum: f5d651d47122ae6fa8ecc204ac6dad3d06c12d1531df51dbfcbaafb1aaea5b91
created: 2024-03-30 09:03:19.805183635+00:00
modified: 2024-03-27 10:45:52.503525368+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: zk/Intro_to_the_Unix_shell.md
sortable_path: zkIntro_to_the_Unix_shell.md
title: Unix based systems
checksum: 5ae0adb0aa72338a198a1ace4ecc38eb42cf5515fb938b953f3a447a1497fd93
created: 2024-03-30 09:03:19.808151053+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
lead:
  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.

body:
  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
  ---

  ## 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: zk/Inversion_of_operators.md
sortable_path: zkInversion_of_operators.md
title: Addition, subtraction
lead:
  Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6
  $$

checksum: b0619f98d78539336feb4a54fe6c92f7b523edb763e77e3ef30a0130d98ae458
created: 2024-03-30 09:03:19.825906309+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra"]}
body:
  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
  ---

  ## 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: zk/Iterating_through_complex_data_structures_in_Python.md
sortable_path: zkIterating_through_complex_data_structures_in_Python.md
title: Iterating through complex data structures in Python
lead: // TODO: sections :
checksum: 98d8eba3f0d6f72ac9997837f53a665a43a5ce07f0d5dc1ab46a6bc28d06c031
created: 2024-03-30 09:03:19.83326494+00:00
modified: 2024-03-27 10:45:52.503525368+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_etc.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_etc.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_etc.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_etc.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: zk/Joins_in_SQL.md
sortable_path: zkJoins_in_SQL.md
title: Joins in SQL
checksum: 6a774fe4238d77ff4c2738c5352ec7e5df7b5f564a96348deaa82f6c931f261f
created: 2024-03-30 09:03:19.864689426+00:00
modified: 2024-06-15 09:46:15.381586675+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;
  ```


path: zk/Key_characteristics_of_GraphQL.md
sortable_path: zkKey_characteristics_of_GraphQL.md
title: Key characteristics of GraphQL
checksum: 2543b0fa29da25818b2937b2dece5d500bff598b9faa001df16e086ee5f0fd17
created: 2024-03-30 09:03:19.874620569+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["graphql","APIs"]}
lead:
  > In a nutshell, GraphQL is all about optimizing data communication between a
  > client and a server. This includes the client asking for needed data and
  > communicating that need to the server, and the server preparing a fulfillment
  > for that need and communicating the fulfillment back to the client. GraphQL
  > allows clients to ask for the exact data they need and makes it easier for
  > servers to aggregate data from multiple data storage resources.

body:
  > In a nutshell, GraphQL is all about optimizing data communication between a
  > client and a server. This includes the client asking for needed data and
  > communicating that need to the server, and the server preparing a fulfillment
  > for that need and communicating the fulfillment back to the client. GraphQL
  > allows clients to ask for the exact data they need and makes it easier for
  > servers to aggregate data from multiple data storage resources.

  - GraphQL is a **query language** from which you can build APIs. It exists as an
    abstraction layer between a client that issues requests and a backend
    comprising any number of databases that responds to queries.

  - Crucially, with applications that utilise GraphQL, both the backend and the
    frontend use the same GraphQL syntax. They speak the same language. This means
    that neither needs to take account of the way in which the other is
    implemented. For example, a client could request data from both a relational
    database and an object database without knowing there is any difference from
    its point of view. Conversely, client requests could be made through
    JavaScript or Python and the structure of the request received by the backend
    would be the same.
  - The main benefit is its interoperability and its network economy when compared
    to REST APIs: you ask only for what you want and you get only that; there is
    no surplus data, increasing efficiency, reducing the number of network
    requests and reducing the size of the payload.

  > The best way to think of GraphQL architecturally is to think of it as a
  > different technique for instantiating the _uniform interface_, _client/server
  > decoupling_, and _layered-system architecture_ contraints of traditional
  > [REST APIs](RESTful_APIs.md#rest). However, instead of making
  > GET or POST requests to a REST endpoint URL and passing parameters, you send a
  > structured query object.

  ## Architecture

  From the point of view of the frontend, GraphQL is a query language that may be
  used via wrappers for a given framework or programming language (such as Apollo)
  or directly simply by sending requests over HTTPS to a single URL.

  From the point of view of the backend, GraphQL is a **runtime** that provides a
  structure for servers to describe the data to be exposed in their APIs. We call
  this structure the **schema**. For example, the GraphQL server might translate
  the query into SQL statements for a relational database then take what the
  storage engine responds with, translate it into JSON and send it back to the
  client application.

  > The backend implementation of GraphQL is known as the **GraphQL server**. This
  > server is distinct from any of the physical servers that the backend may rely
  > on. The client queries the GraphQL server.

  Client requests are sent over HTTPS and the data is typically returned in the
  form of JSON:

  ![](/img/graphql-architecture.png)

  ## Implementation overview

  ### Queries, mutations, subscriptions

  Queries can be used for **both** reading and modifying data.

  - When you need to read data with GraphQL you use **queries**
  - When you need to modify data, you use **mutations**

  So, for comparison, a query would be akin to a `READ` instruction in SQL or a
  `GET` request send to a REST API. A mutation would be akin to `WRITE` then
  `READ` instruction in SQL or a `POST` request to a REST API.

  There is a third request type called a **subscription**. This is used for
  real-time data monitoring requests, like a data stream, similar to a continuous
  `READ` process. Mutations typically trigger events that can be subscribed to.

  ### Frontend

  Below is an example of a request that would be made to a GraphQL server from the
  frontend:

  ```graphql
  query {
    employee(id: 42) {
      name
      email
    }
  }
  ```

  ### Backend: structure and behaviour

  We define the structure of a GraphQL API on the backend through the schema. A
  schema is strongly typed and is basically a graph of fields that have types, e.g

  ```graphql
  type Employee(id: Int!) {
    name: String!
    email: String!
  }
  ```

  `!` stands for required. In addition to the standard primitive data-types you
  can also have custom types.

  We implement the behaviour of the API through functions called **resolver
  functions**. Each field in a GraphQL schema is backed by a resolver function. A
  resolver function defines what data to fetch for its field.

  > A resolver function represents the instructions on how and where to access raw
  > data. For example, a resolver function might issue a SQL statement to a
  > relational database, read a file’s data directly from the operating system, or
  > update some cached data in a document database. A resolver function is
  > directly related to a field in a GraphQL request, and it can represent a
  > single primitive value, an object, or a list of values or objects.

  ### GraphQL endpoint

  The GraphQL server exposes a single endpoint as a URL. This is in contrast to a
  REST API where there are multiple endpoints, each corresponding to a different
  resource. With GraphQL, the request is not specified by the URL and the payload
  but rather in the GraphQL string that you send to the endpoint, like the
  `employee` query above.

  ## Benefits

  ### Single requests

  With a REST API if you require multiple resources you have to make multiple
  requests to different endpoints and then once they resolve, synthesise them on
  the client side. With GraphQL you can send a single request that encompasses
  each individual resource:

  The REST scenario:

  ![](/img/REST_request-load.png)

  The GraphQL scenario:

  ![](/img/graphQL_request_load.png)

  ### Abstraction of multiple services

  ![](/img/graphql_multiple_resources.png)

  ### Stops overfetching

  In a strict REST API you must request all resources from a given resource and
  then filter on the client. With GraphQL you request only the resource you need
  at the client-level.

  ### Single endpoint

  In GraphQL you have a single endpoint and it always remains the same. This makes
  updates easier to manage, since you don't need to broadcast new endpoints as the
  API grows. It also simplifies frontend parsing, since you don't need to account
  for, or interpolate different endpoints in your request URL.

raw_content:
  ---
  tags: [graphql, APIs]
  ---

  # Key characteristics of GraphQL

  > In a nutshell, GraphQL is all about optimizing data communication between a
  > client and a server. This includes the client asking for needed data and
  > communicating that need to the server, and the server preparing a fulfillment
  > for that need and communicating the fulfillment back to the client. GraphQL
  > allows clients to ask for the exact data they need and makes it easier for
  > servers to aggregate data from multiple data storage resources.

  - GraphQL is a **query language** from which you can build APIs. It exists as an
    abstraction layer between a client that issues requests and a backend
    comprising any number of databases that responds to queries.

  - Crucially, with applications that utilise GraphQL, both the backend and the
    frontend use the same GraphQL syntax. They speak the same language. This means
    that neither needs to take account of the way in which the other is
    implemented. For example, a client could request data from both a relational
    database and an object database without knowing there is any difference from
    its point of view. Conversely, client requests could be made through
    JavaScript or Python and the structure of the request received by the backend
    would be the same.
  - The main benefit is its interoperability and its network economy when compared
    to REST APIs: you ask only for what you want and you get only that; there is
    no surplus data, increasing efficiency, reducing the number of network
    requests and reducing the size of the payload.

  > The best way to think of GraphQL architecturally is to think of it as a
  > different technique for instantiating the _uniform interface_, _client/server
  > decoupling_, and _layered-system architecture_ contraints of traditional
  > [REST APIs](RESTful_APIs.md#rest). However, instead of making
  > GET or POST requests to a REST endpoint URL and passing parameters, you send a
  > structured query object.

  ## Architecture

  From the point of view of the frontend, GraphQL is a query language that may be
  used via wrappers for a given framework or programming language (such as Apollo)
  or directly simply by sending requests over HTTPS to a single URL.

  From the point of view of the backend, GraphQL is a **runtime** that provides a
  structure for servers to describe the data to be exposed in their APIs. We call
  this structure the **schema**. For example, the GraphQL server might translate
  the query into SQL statements for a relational database then take what the
  storage engine responds with, translate it into JSON and send it back to the
  client application.

  > The backend implementation of GraphQL is known as the **GraphQL server**. This
  > server is distinct from any of the physical servers that the backend may rely
  > on. The client queries the GraphQL server.

  Client requests are sent over HTTPS and the data is typically returned in the
  form of JSON:

  ![](/img/graphql-architecture.png)

  ## Implementation overview

  ### Queries, mutations, subscriptions

  Queries can be used for **both** reading and modifying data.

  - When you need to read data with GraphQL you use **queries**
  - When you need to modify data, you use **mutations**

  So, for comparison, a query would be akin to a `READ` instruction in SQL or a
  `GET` request send to a REST API. A mutation would be akin to `WRITE` then
  `READ` instruction in SQL or a `POST` request to a REST API.

  There is a third request type called a **subscription**. This is used for
  real-time data monitoring requests, like a data stream, similar to a continuous
  `READ` process. Mutations typically trigger events that can be subscribed to.

  ### Frontend

  Below is an example of a request that would be made to a GraphQL server from the
  frontend:

  ```graphql
  query {
    employee(id: 42) {
      name
      email
    }
  }
  ```

  ### Backend: structure and behaviour

  We define the structure of a GraphQL API on the backend through the schema. A
  schema is strongly typed and is basically a graph of fields that have types, e.g

  ```graphql
  type Employee(id: Int!) {
    name: String!
    email: String!
  }
  ```

  `!` stands for required. In addition to the standard primitive data-types you
  can also have custom types.

  We implement the behaviour of the API through functions called **resolver
  functions**. Each field in a GraphQL schema is backed by a resolver function. A
  resolver function defines what data to fetch for its field.

  > A resolver function represents the instructions on how and where to access raw
  > data. For example, a resolver function might issue a SQL statement to a
  > relational database, read a file’s data directly from the operating system, or
  > update some cached data in a document database. A resolver function is
  > directly related to a field in a GraphQL request, and it can represent a
  > single primitive value, an object, or a list of values or objects.

  ### GraphQL endpoint

  The GraphQL server exposes a single endpoint as a URL. This is in contrast to a
  REST API where there are multiple endpoints, each corresponding to a different
  resource. With GraphQL, the request is not specified by the URL and the payload
  but rather in the GraphQL string that you send to the endpoint, like the
  `employee` query above.

  ## Benefits

  ### Single requests

  With a REST API if you require multiple resources you have to make multiple
  requests to different endpoints and then once they resolve, synthesise them on
  the client side. With GraphQL you can send a single request that encompasses
  each individual resource:

  The REST scenario:

  ![](/img/REST_request-load.png)

  The GraphQL scenario:

  ![](/img/graphQL_request_load.png)

  ### Abstraction of multiple services

  ![](/img/graphql_multiple_resources.png)

  ### Stops overfetching

  In a strict REST API you must request all resources from a given resource and
  then filter on the client. With GraphQL you request only the resource you need
  at the client-level.

  ### Single endpoint

  In GraphQL you have a single endpoint and it always remains the same. This makes
  updates easier to manage, since you don't need to broadcast new endpoints as the
  API grows. It also simplifies frontend parsing, since you don't need to account
  for, or interpolate different endpoints in your request URL.


path: zk/Killing_processes.md
sortable_path: zkKilling_processes.md
title: Kill a process running on a port
lead: For example a local server.
checksum: b5ea10421ba4328ef688d7defa2a2ffc8b0f93e5c9023f4479d0a7fffb44de47
created: 2024-03-30 09:03:19.881853829+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
body:
  For example a local server.

  ```bash
  sudo lsof -t -i:8000
  # List files and proces ID (-t) and internet connections (-i) on port number

  $ 7890
  # Gives you process id

  sudo kill -9 7890
  # Kill the process that is running there
  ```

  ## Alterntive

  ```sh
  ss -ltnp | grep ':8000'
  ```

  Returns:

  ```
  LISTEN 0      511            [::1]:8000          [::]:*    users:(("node",pid=10493,fd=34))
  ```

  Kill with:

  ```
  kill 10493
  ```

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

  ## Kill a process running on a port

  For example a local server.

  ```bash
  sudo lsof -t -i:8000
  # List files and proces ID (-t) and internet connections (-i) on port number

  $ 7890
  # Gives you process id

  sudo kill -9 7890
  # Kill the process that is running there
  ```

  ## Alterntive

  ```sh
  ss -ltnp | grep ':8000'
  ```

  Returns:

  ```
  LISTEN 0      511            [::1]:8000          [::]:*    users:(("node",pid=10493,fd=34))
  ```

  Kill with:

  ```
  kill 10493
  ```


path: zk/LEDs.md
sortable_path: zkLEDs.md
title: LEDs
checksum: 5ea72dfcc5045b77a80da58d83950badd6f7e43a34f90ba747145cd7c74b2947
created: 2024-03-30 09:03:19.885194386+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["electricity","circuits"]}
lead:
  LED' stands for **Light Emitting Diode**, a [circuit]() component that emits
  light. The symbol for an LED is displayed below:

body:
  LED' stands for **Light Emitting Diode**, a [circuit]() component that emits
  light. The symbol for an LED is displayed below:

  ![](/img/diode-led.png)

  A **diode** is a special kind of component that only permits current to flow
  through it in one direction. To achieve this it has very low resistance in one
  direction to allow current flow and high resistance in the other direction to
  impede current flow. This feature of diodes is clearly represented in the
  generic diode circuit symbol:

  ![](/img/diode.png)

  An LED diode lights up when the right amount of current flows through it. A
  standard LED has a maximum current of 20mA. An appropriate
  [resistor](Resistance.md#resistors)
  must therefore be added to the circuit to ensure the current doesn't exeedd this
  amount.

raw_content:
  ---
  tags: [electricity, circuits]
  ---

  # LEDs

  LED' stands for **Light Emitting Diode**, a [circuit]() component that emits
  light. The symbol for an LED is displayed below:

  ![](/img/diode-led.png)

  A **diode** is a special kind of component that only permits current to flow
  through it in one direction. To achieve this it has very low resistance in one
  direction to allow current flow and high resistance in the other direction to
  impede current flow. This feature of diodes is clearly represented in the
  generic diode circuit symbol:

  ![](/img/diode.png)

  An LED diode lights up when the right amount of current flows through it. A
  standard LED has a maximum current of 20mA. An appropriate
  [resistor](Resistance.md#resistors)
  must therefore be added to the circuit to ensure the current doesn't exeedd this
  amount.


path: zk/Lambda_directory_structure.md
sortable_path: zkLambda_directory_structure.md
title: Lambda directory structure
lead: I am using the following structure for my lambdas:
checksum: ad5f4d0b310ea8571d53bfc1292ec0252ad85b3f5e65a265f70e0c8f63779a3b
created: 2024-03-30 09:03:19.89308174+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["AWS"]}
body:
  I am using the following structure for my lambdas:

  ```
  lambda-name/
      events/
      function-name/
          src/
              index.js
          tests/
          local-env.json
          production-env.json
          package.json
      template.yaml
      README.md
  ```

raw_content:
  ---
  tags: [AWS]
  ---

  # Lambda directory structure

  I am using the following structure for my lambdas:

  ```
  lambda-name/
      events/
      function-name/
          src/
              index.js
          tests/
          local-env.json
          production-env.json
          package.json
      template.yaml
      README.md
  ```


path: zk/Lambda_handler_function.md
sortable_path: zkLambda_handler_function.md
title: AWS Lambda handler function
lead: Every Lambda function begins the same way with a handler function. In NodeJS:
checksum: db7083124ec79bc49cc7e3f28756a97c48e7d950d679c34bcb546b92a6982771
created: 2024-03-30 09:03:19.896420506+00:00
modified: 2024-06-15 09:50:14.019667391+00:00
metadata: {"tags":["AWS","aws-lambda"]}
body:
  Every Lambda function begins the same way with a handler function. In NodeJS:

  ```js
  module.exports.handler = function(event, context, callback){...}
  ```

  The handler is the entry point for the Lambda. It is a function that Lambda can
  call to start the execution of your code when an event triggers your Lambda
  function.

  You may put your whole code in the body of the handler, or you may call other
  modules and keep the handler clean and focused on bootstrapping.

  The `module.exports` part is just the standard way of exporting a module in
  Node. `handler` is a property that is being exported.

  You always have access to the parameters `event`, `context` and `callback`.

  - `event`
    - The triggering event of the Lambda function stored as an object. The
      object's contents will be different depending on the trigger. It could be
      any AWS service that serves a trigger (S3, DynamoDB, API Gateway etc).
    - For example, if the triggering function is a request to API Gateway, the
      `event` object will contain information about the HTTP request such as
      headers, path, and the request body.
  - `context`
    - contains information about the runtime environment and the current execution
      of the Lambda function. The context object provides properties and methods
      that you can use to interact with the AWS Lambda service and the current
      invocation.
  - `callback`
    - an optional parameter that represents a callback function provided by AWS
      Lambda. The callback function allows you to signal the completion of your
      Lambda function's execution and return a response or an error. The callback
      function accepts two arguments: error and result. If the first argument is
      not null, AWS Lambda treats it as an error, and the Lambda function
      invocation fails. If the first argument is null, AWS Lambda treats the
      second argument as the result and considers the invocation successful. Using
      the callback function is more common in non-async handler functions. When
      you use async functions, you can return a response or throw an error
      directly, without using the callback function.

  ### Full example:

  In JavaScript:

  ```js
  module.exports.handler = async (event, context) => {
    // Your logic to handle the incoming event
    // For example, you can call other AWS services, process the event data, etc.

    // Return a response
    return {
      statusCode: 200,
      body: JSON.stringify({
        message: "Your Lambda function has successfully executed!",
      }),
    };
  };
  ```

  In Python:

  ```py
  import json

  def handler(event, context):
      # Your logic to handle the incoming event
      # For example, you can call other AWS services, process the event data, etc.

      # Return a response
      response = {
          'statusCode': 200,
          'body': json.dumps({
              'message': 'Your Lambda function has successfully executed!'
          })
      }
      return response
  ```

raw_content:
  ---
  tags:
    - AWS
    - aws-lambda
  ---

  # AWS Lambda handler function

  Every Lambda function begins the same way with a handler function. In NodeJS:

  ```js
  module.exports.handler = function(event, context, callback){...}
  ```

  The handler is the entry point for the Lambda. It is a function that Lambda can
  call to start the execution of your code when an event triggers your Lambda
  function.

  You may put your whole code in the body of the handler, or you may call other
  modules and keep the handler clean and focused on bootstrapping.

  The `module.exports` part is just the standard way of exporting a module in
  Node. `handler` is a property that is being exported.

  You always have access to the parameters `event`, `context` and `callback`.

  - `event`
    - The triggering event of the Lambda function stored as an object. The
      object's contents will be different depending on the trigger. It could be
      any AWS service that serves a trigger (S3, DynamoDB, API Gateway etc).
    - For example, if the triggering function is a request to API Gateway, the
      `event` object will contain information about the HTTP request such as
      headers, path, and the request body.
  - `context`
    - contains information about the runtime environment and the current execution
      of the Lambda function. The context object provides properties and methods
      that you can use to interact with the AWS Lambda service and the current
      invocation.
  - `callback`
    - an optional parameter that represents a callback function provided by AWS
      Lambda. The callback function allows you to signal the completion of your
      Lambda function's execution and return a response or an error. The callback
      function accepts two arguments: error and result. If the first argument is
      not null, AWS Lambda treats it as an error, and the Lambda function
      invocation fails. If the first argument is null, AWS Lambda treats the
      second argument as the result and considers the invocation successful. Using
      the callback function is more common in non-async handler functions. When
      you use async functions, you can return a response or throw an error
      directly, without using the callback function.

  ### Full example:

  In JavaScript:

  ```js
  module.exports.handler = async (event, context) => {
    // Your logic to handle the incoming event
    // For example, you can call other AWS services, process the event data, etc.

    // Return a response
    return {
      statusCode: 200,
      body: JSON.stringify({
        message: "Your Lambda function has successfully executed!",
      }),
    };
  };
  ```

  In Python:

  ```py
  import json

  def handler(event, context):
      # Your logic to handle the incoming event
      # For example, you can call other AWS services, process the event data, etc.

      # Return a response
      response = {
          'statusCode': 200,
          'body': json.dumps({
              'message': 'Your Lambda function has successfully executed!'
          })
      }
      return response
  ```


path: zk/Lambda_programming_model.md
sortable_path: zkLambda_programming_model.md
title: AWS Lambda programming model
lead: The overall architecure consists in the following three processes:
checksum: fc67720166bcee6eceb573ee829de652e833340b4ad16163bb96ba51ee4ca7ad
created: 2024-03-30 09:03:19.899938286+00:00
modified: 2024-06-15 09:50:16.459714731+00:00
metadata: {"tags":["AWS","aws-lambda"]}
body:
  The overall architecure consists in the following three processes:

  1. Triggers
  2. Handler function
  3. Code

  ## Triggers

  See [AWS Lambda triggers](Lambda_triggers.md)

  ## Handler function

  See
  [AWS Lambda handler function](Lambda_handler_function.md)

  ## Code

  This is just the stuff that runs in the body of the handler or that is called
  from the handler body.

raw_content:
  ---
  tags:
    - AWS
    - aws-lambda
  ---

  # AWS Lambda programming model

  The overall architecure consists in the following three processes:

  1. Triggers
  2. Handler function
  3. Code

  ## Triggers

  See [AWS Lambda triggers](Lambda_triggers.md)

  ## Handler function

  See
  [AWS Lambda handler function](Lambda_handler_function.md)

  ## Code

  This is just the stuff that runs in the body of the handler or that is called
  from the handler body.


path: zk/Lambda_triggers.md
sortable_path: zkLambda_triggers.md
title: AWS Lambda triggers
lead: Here are some common trigger patterns
checksum: 3586771db03bd5e8ea9e70f94d4be5e2b0015c1b5c44765942f08868468cd483
created: 2024-03-30 09:03:19.91022386+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["AWS","aws-lambda"]}
body:
  Here are some common trigger patterns

  ### API Gateway trigger

  - an HTTP request is sent to AWS API Gateway
  - API Gateway triggers a Lambda function to execute

  ### DynamoDB table change trigger

  - a record in a DynamoDB table is modified
  - DynamoDB triggers a lambda function to execute

  ### S3 trigger

  - a new file is created in S3
  - S3 triggers a Lambda function to execute

  ### SQS trigger

  - a new message is added to an [SQS](AWS_Messaging_services.md#sqs)
    queue
  - SQS triggers a Lambda function to execute

raw_content:
  ---
  tags: [AWS, aws-lambda]
  ---

  # AWS Lambda triggers

  Here are some common trigger patterns

  ### API Gateway trigger

  - an HTTP request is sent to AWS API Gateway
  - API Gateway triggers a Lambda function to execute

  ### DynamoDB table change trigger

  - a record in a DynamoDB table is modified
  - DynamoDB triggers a lambda function to execute

  ### S3 trigger

  - a new file is created in S3
  - S3 triggers a Lambda function to execute

  ### SQS trigger

  - a new message is added to an [SQS](AWS_Messaging_services.md#sqs)
    queue
  - SQS triggers a Lambda function to execute


path: zk/Lambdas_in_Python.md
sortable_path: zkLambdas_in_Python.md
title: Lambdas in Python
checksum: 30d573d0442eaf659020ffbcd309ef1210d56188aabb9c71cd9dc0fd9d7fdc48
created: 2024-03-30 09:03:19.919939041+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
lead:
  In Python, anonymous functions like arrow-functions in JavaScript (`() => {}`)
  are immediately invoked and unnamed. They are called lambdas.

body:
  In Python, anonymous functions like arrow-functions in JavaScript (`() => {}`)
  are immediately invoked and unnamed. They are called lambdas.

  Whilst they are unnamed, just like JS, the value they return can be stored in a
  variable. They do not require the `return` keyword.

  They are most often used unnamed with the functional methods
  [map, filter](Map_and_filter_in_Python.md)
  and reduce.

  Here is the two syntaxes side by side:

  ```js
  // JavaScript

  const double = (x) => x * x;
  ```

  ```py
  # Python

  double = lambda x: x * x
  ```

  Here is a lambda with multiple parameters:

  ```py
  func = lambda x, y, z: x + y + z
  print(func(2, 3, 4))
  # 9
  ```

  > Lambdas obviously enshrine functional programming paradigms. Therefore they
  > should be pure functions, not mutating values or issueing side effects. For
  > example, it would be improper (though syntactically well-formed) to use a
  > lambda to `print` something

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

  # Lambdas in Python

  In Python, anonymous functions like arrow-functions in JavaScript (`() => {}`)
  are immediately invoked and unnamed. They are called lambdas.

  Whilst they are unnamed, just like JS, the value they return can be stored in a
  variable. They do not require the `return` keyword.

  They are most often used unnamed with the functional methods
  [map, filter](Map_and_filter_in_Python.md)
  and reduce.

  Here is the two syntaxes side by side:

  ```js
  // JavaScript

  const double = (x) => x * x;
  ```

  ```py
  # Python

  double = lambda x: x * x
  ```

  Here is a lambda with multiple parameters:

  ```py
  func = lambda x, y, z: x + y + z
  print(func(2, 3, 4))
  # 9
  ```

  > Lambdas obviously enshrine functional programming paradigms. Therefore they
  > should be pure functions, not mutating values or issueing side effects. For
  > example, it would be improper (though syntactically well-formed) to use a
  > lambda to `print` something


path: zk/Latches.md
sortable_path: zkLatches.md
title: Latches
checksum: 456a5efe4f003860290d55f3eacc357ef5b2f103f0fb03419c1bb61e014f3cc0
created: 2024-03-30 09:03:19.929290597+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["logic-gates","binary","memory"]}
lead:
  The **combinatorial digital circuits** we have looked at so far have been
  non-sequential. The outcome is a function of its immediate set of inputs and
  everything happens at once: there is no means of storing state for future use.
  In other words there is no _[memory](Memory.md)_.

body:
  The **combinatorial digital circuits** we have looked at so far have been
  non-sequential. The outcome is a function of its immediate set of inputs and
  everything happens at once: there is no means of storing state for future use.
  In other words there is no _[memory](Memory.md)_.

  In contrast, the output of a **sequential digital circuit** depends not only on
  its present set of inputs but also on past inputs to the circuit. It has some
  knowledge of its own previous state through the existence of memory. This can be
  implemented via components that allow for the **storage and retrieval of binary
  data**.

  ## What is a latch?

  A latch is a circuit component that wor

  The **SR Latch** (for "set/reset") has two inputs: _S_ (for "set") and _R_ (for
  "reset") and one output, _Q_. _Q_ stands for the bit that is remembered. (There
  is also _not-Q_ which is the opposite of whatever _Q_ is currently set to.)

  The SR Latch goes through the following state changes:

  - When _S_ is set to 1, output _Q_ becomes 1 also
  - When _S_ goes to 0, _Q_ remains 1
  - When _R_ is set to 1, the memory bit is cleared and _Q_ becomes 0.
  - _Q_ remains at 0 even if _R_ goes back to 0

  This is represented more clearly in the table below:

  | S   | R   | Q   | Operation     |
  | --- | --- | --- | ------------- |
  | 1   | 1   | X   | Invalid, null |

  The most succinct account of a latch:

  > A latch is a 1-bit memory device that has a state _Q_ of either 1 or 0. The
  > _S_ input sets _Q_ to 1 and the _R_ input resets _Q_ to 0.

  _The representation of an SR Latch in a digital circuit diagram_:

  ![](/img/sr_latch_diagram.png)

  ## Creating a latch circuit

  The circuit diagram latch symbol obviously encapsulates more complex
  functionality that occurs at the sub-circuit level. We will demonstrate how this
  functionality can be achieved with two
  [NOR](Logic_gates.md#nor-gate) gates.

  The two gates are in a **cross-coupled configuration**. This basically means
  that the wires are crossed back on themselves such that the output of one is
  also an input of the other at a single stage in the sequence.

  The circuit is created as follows:

  ![](/img/sr_latch_logic_circuit.png)

  Interactive version:

  <iframe src="https://circuitverse.org/simulator/embed/nor-latch-0869192c-7d7b-4161-b13f-3f72c1bce8e9" 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>

  <br />
  Let's talk through the logic at each state change:

  - When _S_ is set to 1, output _Q_ becomes 1 also
    - N1 is receiving 1 from S and 0 from R by way of N2. This is the inversion of
      OR so TF equals F. Thus N1 is outputting 0.
    - Thus N2 is receiving 0 from N1 as its top input and 0 from R as its bottom
      input. It is therefore outputting 1 because with NOR, FF equals T. For this
      reason, _Q_ is 1 because is directly connected to _R_.
  - When _S_ goes to 0, _Q_ remains 1
    - N2 is receiving 0 from N1 as the top input and 0 from R as the bottom input
      hence the overall input is FF which means N2 is outputting T and Q remains 1
    - N1 is outputting 0 because it is receiving 0 as its top input and 1 from its
      bottom input
  - When _R_ is set to 1, the memory bit is cleared and _Q_ becomes 0.
    - N2 is receiving 1 from R as its bottom input and 1 from the output of N1 as
      its top input. Therefore it is outputting TT which in NOR evaluates to F
      hence Q is 0
    - N1 is outputting 1 because it is receiving 0 from S as its top input and 0
      from its bottom input coming from N2. FF equals T in NOR therefore 1 is
      outputting 1
  - _Q_ remains at 0 even if _R_ goes back to 0
    - N2 is receiving 0 as its bottom input from R and 1 as its top input from N1.
      TF equals F in NOR hence the output of N2 is 0 and Q remains 0
    - N1 is outputting 1 because it is receiving 0 as its top input from S and 0
      as its bottom input from N2. FF equals T in NOR hence N1 is 1

raw_content:
  ---
  tags: [logic-gates, binary, memory]
  ---

  # Latches

  The **combinatorial digital circuits** we have looked at so far have been
  non-sequential. The outcome is a function of its immediate set of inputs and
  everything happens at once: there is no means of storing state for future use.
  In other words there is no _[memory](Memory.md)_.

  In contrast, the output of a **sequential digital circuit** depends not only on
  its present set of inputs but also on past inputs to the circuit. It has some
  knowledge of its own previous state through the existence of memory. This can be
  implemented via components that allow for the **storage and retrieval of binary
  data**.

  ## What is a latch?

  A latch is a circuit component that wor

  The **SR Latch** (for "set/reset") has two inputs: _S_ (for "set") and _R_ (for
  "reset") and one output, _Q_. _Q_ stands for the bit that is remembered. (There
  is also _not-Q_ which is the opposite of whatever _Q_ is currently set to.)

  The SR Latch goes through the following state changes:

  - When _S_ is set to 1, output _Q_ becomes 1 also
  - When _S_ goes to 0, _Q_ remains 1
  - When _R_ is set to 1, the memory bit is cleared and _Q_ becomes 0.
  - _Q_ remains at 0 even if _R_ goes back to 0

  This is represented more clearly in the table below:

  | S   | R   | Q   | Operation     |
  | --- | --- | --- | ------------- |
  | 1   | 1   | X   | Invalid, null |

  The most succinct account of a latch:

  > A latch is a 1-bit memory device that has a state _Q_ of either 1 or 0. The
  > _S_ input sets _Q_ to 1 and the _R_ input resets _Q_ to 0.

  _The representation of an SR Latch in a digital circuit diagram_:

  ![](/img/sr_latch_diagram.png)

  ## Creating a latch circuit

  The circuit diagram latch symbol obviously encapsulates more complex
  functionality that occurs at the sub-circuit level. We will demonstrate how this
  functionality can be achieved with two
  [NOR](Logic_gates.md#nor-gate) gates.

  The two gates are in a **cross-coupled configuration**. This basically means
  that the wires are crossed back on themselves such that the output of one is
  also an input of the other at a single stage in the sequence.

  The circuit is created as follows:

  ![](/img/sr_latch_logic_circuit.png)

  Interactive version:

  <iframe src="https://circuitverse.org/simulator/embed/nor-latch-0869192c-7d7b-4161-b13f-3f72c1bce8e9" 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>

  <br />
  Let's talk through the logic at each state change:

  - When _S_ is set to 1, output _Q_ becomes 1 also
    - N1 is receiving 1 from S and 0 from R by way of N2. This is the inversion of
      OR so TF equals F. Thus N1 is outputting 0.
    - Thus N2 is receiving 0 from N1 as its top input and 0 from R as its bottom
      input. It is therefore outputting 1 because with NOR, FF equals T. For this
      reason, _Q_ is 1 because is directly connected to _R_.
  - When _S_ goes to 0, _Q_ remains 1
    - N2 is receiving 0 from N1 as the top input and 0 from R as the bottom input
      hence the overall input is FF which means N2 is outputting T and Q remains 1
    - N1 is outputting 0 because it is receiving 0 as its top input and 1 from its
      bottom input
  - When _R_ is set to 1, the memory bit is cleared and _Q_ becomes 0.
    - N2 is receiving 1 from R as its bottom input and 1 from the output of N1 as
      its top input. Therefore it is outputting TT which in NOR evaluates to F
      hence Q is 0
    - N1 is outputting 1 because it is receiving 0 from S as its top input and 0
      from its bottom input coming from N2. FF equals T in NOR therefore 1 is
      outputting 1
  - _Q_ remains at 0 even if _R_ goes back to 0
    - N2 is receiving 0 as its bottom input from R and 1 as its top input from N1.
      TF equals F in NOR hence the output of N2 is 0 and Q remains 0
    - N1 is outputting 1 because it is receiving 0 as its top input from S and 0
      as its bottom input from N2. FF equals T in NOR hence N1 is 1


path: zk/Law_of_Non_Contradiction.md
sortable_path: zkLaw_of_Non_Contradiction.md
title: Law of Non-Contradiction
lead: > A proposition cannot be true and false at the same time.
body:
  > A proposition cannot be true and false at the same time.

  $$
  \lnot (P \land \lnot P)
  $$

checksum: ddf0f94542234a0692e65d40c5bc7349d5e4783a9033ede76e874c531e7e2c0a
created: 2024-03-30 09:03:19.941153028+00:00
modified: 2024-06-16 16:52:15.016167468+00:00
metadata: {"tags":["propositional-logic","logic"]}
raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Law of Non-Contradiction

  > A proposition cannot be true and false at the same time.

  $$
  \lnot (P \land  \lnot P)
  $$


path: zk/Law_of_the_Excluded_Middle.md
sortable_path: zkLaw_of_the_Excluded_Middle.md
title: Law of the Excluded Middle
lead:
  > Every proposition has to be either true or false. There can be no middle
  > ground.

checksum: a53ec515846745f28a9a89390a50710c2ca90f7f1a1180acfa550231761cc6b5
created: 2024-03-30 09:03:19.943841479+00:00
modified: 2024-06-16 16:52:18.949255609+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  > Every proposition has to be either true or false. There can be no middle
  > ground.

  $$
  P \lor \sim P
  $$

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

  # Law of the Excluded Middle

  > Every proposition has to be either true or false. There can be no middle
  > ground.

  $$
  P \lor \sim P
  $$


path: zk/Lifecycle_methods.md
sortable_path: zkLifecycle_methods.md
title: Lifecycle methods
checksum: f28d1ec8c52599af3685974a5c17a22f2d0042b296ee9ba49ed379592fa024b7
created: 2024-03-30 09:03:19.947024537+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript","react"]}
lead:
  Lifecycles are familiar from nature: something is born, it grows and reaches
  maturity and then dies and the process continues as what dies is broken down
  into constituent parts which form the basis of new life. And so on and so on.

body:
  Lifecycles are familiar from nature: something is born, it grows and reaches
  maturity and then dies and the process continues as what dies is broken down
  into constituent parts which form the basis of new life. And so on and so on.

  The same metaphor can be applied to React components relative to the document
  object model:

  - Components are 'born' (created and mounted on the DOM)
  - They 'grow' by changing and updating their state
  - They 'die' by being unmounted from the DOM

  ## Specific lifecycle phases

  The outline above corresponds to specific methods that execute when React code
  runs:

  1. `componentWillMount()` : this method is called just before the `render()`
     method is called
  2. `componentDidMount()` : this method is called after the component is rendered
     in the DOM

  The two methods above only occur once in a lifecycle.

  The 'updating' phase is achieved with the following methods. This is when the
  data of the component, in the form of state and props updates in response to
  user interaction. We call this process 're-rendering':

  3. `shouldComponentUpdate()` : determines whether component should update

  4. `componentWillUpdate()` : called before re-rerendering occurs

  5. `componentDidUpdate()` : called after re-rendering occurs

  The final phase is unmounting: when the component is removed from the DOM:

  6. `componentWillUnmount()`

  ![React component lifecycle](react-lifecycle.png)

  ## Side-effects: why lifecycle phases matter

  > React is built based on the principles of functional programming. In this
  > paradigm, a side-effect (something that is generally discouraged) is any
  > process that is not the direct result of a function executing or anything that
  > is not covered by the `return` keyword.

  Many of the methods detailed in the previous section will rarely be used in
  React development since they pertain to the inner engineering of the library,
  rather than its application in custom contexts. However sometimes you do need
  access to these methods, depending on the background context of what you are
  trying to achieving. This context could include external API and database
  requests or **anything that does not directly pertain to the rendering of the
  component on the screen**.

  React groups these processes under the heading of **side-effects.**

  ### Examples of side-effects and where in the lifecycle they should run

  - `componentWillMount` : It's a good idea to run set-up scripts at this point,
    such as connecting to and authenticating access to a database. This process
    runs before the `render` method is called so it is an optimal point to
    establish these connections
    - You should not however make data requests at this point since data is
      typically returned asynchronously. You cannot therefore be sure that the
      data will be returned before mounting occurs.
  - `componentDidMount` : This is the time to do things that can't occur without
    the DOM:
    - retrieve data
    - add event listeners
    - set timers using `setTimeout` or `setInterval`
  - Updating methods (`shouldComponentUpdate`, `componentWillUpdate` etc.)
    - As these precede or take place concurrently with the rerendering process,
      this phase is a good point to retrieve new data or run necessary external
      processes that are required by the re-rendering.

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

  # Lifecycle methods

  Lifecycles are familiar from nature: something is born, it grows and reaches
  maturity and then dies and the process continues as what dies is broken down
  into constituent parts which form the basis of new life. And so on and so on.

  The same metaphor can be applied to React components relative to the document
  object model:

  - Components are 'born' (created and mounted on the DOM)
  - They 'grow' by changing and updating their state
  - They 'die' by being unmounted from the DOM

  ## Specific lifecycle phases

  The outline above corresponds to specific methods that execute when React code
  runs:

  1. `componentWillMount()` : this method is called just before the `render()`
     method is called
  2. `componentDidMount()` : this method is called after the component is rendered
     in the DOM

  The two methods above only occur once in a lifecycle.

  The 'updating' phase is achieved with the following methods. This is when the
  data of the component, in the form of state and props updates in response to
  user interaction. We call this process 're-rendering':

  3. `shouldComponentUpdate()` : determines whether component should update

  4. `componentWillUpdate()` : called before re-rerendering occurs

  5. `componentDidUpdate()` : called after re-rendering occurs

  The final phase is unmounting: when the component is removed from the DOM:

  6. `componentWillUnmount()`

  ![React component lifecycle](react-lifecycle.png)

  ## Side-effects: why lifecycle phases matter

  > React is built based on the principles of functional programming. In this
  > paradigm, a side-effect (something that is generally discouraged) is any
  > process that is not the direct result of a function executing or anything that
  > is not covered by the `return` keyword.

  Many of the methods detailed in the previous section will rarely be used in
  React development since they pertain to the inner engineering of the library,
  rather than its application in custom contexts. However sometimes you do need
  access to these methods, depending on the background context of what you are
  trying to achieving. This context could include external API and database
  requests or **anything that does not directly pertain to the rendering of the
  component on the screen**.

  React groups these processes under the heading of **side-effects.**

  ### Examples of side-effects and where in the lifecycle they should run

  - `componentWillMount` : It's a good idea to run set-up scripts at this point,
    such as connecting to and authenticating access to a database. This process
    runs before the `render` method is called so it is an optimal point to
    establish these connections
    - You should not however make data requests at this point since data is
      typically returned asynchronously. You cannot therefore be sure that the
      data will be returned before mounting occurs.
  - `componentDidMount` : This is the time to do things that can't occur without
    the DOM:
    - retrieve data
    - add event listeners
    - set timers using `setTimeout` or `setInterval`
  - Updating methods (`shouldComponentUpdate`, `componentWillUpdate` etc.)
    - As these precede or take place concurrently with the rerendering process,
      this phase is a good point to retrieve new data or run necessary external
      processes that are required by the re-rendering.


path: zk/List_comprehension_etc.md
sortable_path: zkList_comprehension_etc.md
title: List comprehension
checksum: 47f1ec37b472ca033ebbdfae22ba2e8c7ac0b0943d70dcb158fa782b62e3f28f
created: 2024-03-30 09:03:19.949984257+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
lead:
  List comprehension is a syntactic shorthand for applying a function to each
  element in a list without explicitly using loop syntax.

body:
  List comprehension is a syntactic shorthand for applying a function to each
  element in a list without explicitly using loop syntax.

  Since its introduction to the language, the same functionality has become
  achievable by using functional methods like
  [`map` and `filter`](Map_and_filter_in_Python.md),
  utilising [lambdas](Lambdas_in_Python.md)
  however list comprehension is often more straightforward and easier to read.

  ## Syntax

  Here is a basic example which applies `+ 1` to each integer in a list:

  ```python
  values = [1, 2, 4, 6, 8, 9]

  new_values = [i + 1 for i in values]
  print('new_values', new_values)
  # new_values [2, 3, 5, 7, 9, 10]
  ```

  The basic syntax is as follows:

  ```
  new_list  = [expression for each member in an iterable]
  ```

  - The _expression_ can be the member itself, a call to a method, or any other
    valid expression that returns a value. In the example above, the expression
    `i + i` adds one to each member value.

  - The _member_ is the object or value in the list or iterable. In the example
    above, the member value is _i_.

  - The _iterable_ is a list, set, dictionary or any other object that can return
    its elements one at a time. In the example above, the iterable is each value
    in `values`.

  This is a much more condensed way of achieving the same outcome with a
  traditional loop:

  ```py
  values = [1, 2, 4, 6, 8, 9]
  new_list = []
  for i in values:
    values.append(i+1)
  ```

  ### Another example

  In the following example, we apply list comprehension with a `in range` loop
  structure:

  ```py
  new_list = [i * i for i in range(10) ]
  print(new_list)
  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

  ```

  ### Adding a condition

  We can apply a conditional to a comprehension:

  ```py
  new_list = [item + 1 for item in values if item % 2 == 0]

  print('new_list:', new_list)
  # new_list: [3, 5, 7, 9]
  ```

  ### Filters

  By applying a condition (and no execution to each element) we effectively create
  a filter:

  ```py
  numbers = [1, 2, 3, 4, 4, 4]
  numbers_filtered = [i for i in numbers if i > 2]
  print(numbers_filtered)
  # [3, 4, 5]
  ```

  For comparison, the same outcome could be achieved with a filter and lambda:

  ```py
  with_filter = list(filter(lambda x: x > 2, numbers))
  print(with_filter)
  # [3, 4, 5]
  ```

  ## Set comprehension

  We can also apply comprehension to sets. The syntax is practically identical but
  the difference is the resultant data structure will not contain duplicates.

  ```py
  numbers = [1, 2, 3, 4, 4, 4]
  unique =  {i for i in numbers}
  print(unique)
  # {1,2,3,4}
  ```

  ## Dictionary comprehension

  ```py
  squares = {i: i * i for i in range(5)}
  print(squares)
  {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
  ```

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

  # List comprehension

  List comprehension is a syntactic shorthand for applying a function to each
  element in a list without explicitly using loop syntax.

  Since its introduction to the language, the same functionality has become
  achievable by using functional methods like
  [`map` and `filter`](Map_and_filter_in_Python.md),
  utilising [lambdas](Lambdas_in_Python.md)
  however list comprehension is often more straightforward and easier to read.

  ## Syntax

  Here is a basic example which applies `+ 1` to each integer in a list:

  ```python
  values = [1, 2, 4, 6, 8, 9]

  new_values = [i + 1 for i in values]
  print('new_values', new_values)
  # new_values [2, 3, 5, 7, 9, 10]
  ```

  The basic syntax is as follows:

  ```
  new_list  = [expression for each member in an iterable]
  ```

  - The _expression_ can be the member itself, a call to a method, or any other
    valid expression that returns a value. In the example above, the expression
    `i + i` adds one to each member value.

  - The _member_ is the object or value in the list or iterable. In the example
    above, the member value is _i_.

  - The _iterable_ is a list, set, dictionary or any other object that can return
    its elements one at a time. In the example above, the iterable is each value
    in `values`.

  This is a much more condensed way of achieving the same outcome with a
  traditional loop:

  ```py
  values = [1, 2, 4, 6, 8, 9]
  new_list = []
  for i in values:
    values.append(i+1)
  ```

  ### Another example

  In the following example, we apply list comprehension with a `in range` loop
  structure:

  ```py
  new_list = [i * i for i in range(10) ]
  print(new_list)
  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

  ```

  ### Adding a condition

  We can apply a conditional to a comprehension:

  ```py
  new_list = [item + 1 for item in values if item % 2 == 0]

  print('new_list:', new_list)
  # new_list: [3, 5, 7, 9]
  ```

  ### Filters

  By applying a condition (and no execution to each element) we effectively create
  a filter:

  ```py
  numbers = [1, 2, 3, 4, 4, 4]
  numbers_filtered = [i for i in numbers if i > 2]
  print(numbers_filtered)
  # [3, 4, 5]
  ```

  For comparison, the same outcome could be achieved with a filter and lambda:

  ```py
  with_filter = list(filter(lambda x: x > 2, numbers))
  print(with_filter)
  # [3, 4, 5]
  ```

  ## Set comprehension

  We can also apply comprehension to sets. The syntax is practically identical but
  the difference is the resultant data structure will not contain duplicates.

  ```py
  numbers = [1, 2, 3, 4, 4, 4]
  unique =  {i for i in numbers}
  print(unique)
  # {1,2,3,4}
  ```

  ## Dictionary comprehension

  ```py
  squares = {i: i * i for i in range(5)}
  print(squares)
  {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
  ```


path: zk/Listing_and_navigating_directories.md
sortable_path: zkListing_and_navigating_directories.md
title: Listing options
checksum: 803ef6362f8b7aa068c1db16391ce443c34c98ee7a6b3069ba7451ac46ddc991
created: 2024-03-30 09:03:19.961412341+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
lead:
  Obviously we know that in order to list the files and sub-directories in our
  current directory we use `ls` but here are some of the most useful of the
  different modifiers:

body:
  Obviously we know that in order to list the files and sub-directories in our
  current directory we use `ls` but here are some of the most useful of the
  different modifiers:

  - `**ls -a**`
    - list and include hidden dot files
  - `**ls -l**`
    - list with user permissions, file-size and date-modified (most detailed)
  - `**ls ***`
    - list organised by folders one level deep

  ## Navigation shorthands

  - `cd -`
    - Return to the directory you were last in
  - `!!`
    - Repeat the last command

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

  ## Listing options

  Obviously we know that in order to list the files and sub-directories in our
  current directory we use `ls` but here are some of the most useful of the
  different modifiers:

  - `**ls -a**`
    - list and include hidden dot files
  - `**ls -l**`
    - list with user permissions, file-size and date-modified (most detailed)
  - `**ls ***`
    - list organised by folders one level deep

  ## Navigation shorthands

  - `cd -`
    - Return to the directory you were last in
  - `!!`
    - Repeat the last command


path: zk/Lists_and_arrays.md
sortable_path: zkLists_and_arrays.md
title: Arrays in Bash
lead: We define an array as follows:
checksum: 7cf79a6168baac907db1fdf26fa379a4a32194f7c097b458f844b5e54445dd1c
created: 2024-03-30 09:03:19.964422213+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell","data-structures"]}
body:
  We define an array as follows:

  ```bash
  words=(here are some words)
  ```

  We can also explicitly define an array using `declare` :

  ```bash
  declare -a words=("element1" "element2" "element3")
  ```

  ## Index notation

  We access specific array elements by their index using the same braces style we
  use with variables:

  ```bash
  echo "${words[2]}"

  # element3
  ```

  ## Iterating through arrays

  ```bash
  for i in "${words[@]}"
  do
     echo "$i"
     # or do whatever with individual element of the array
  done
  # element1 element2 element3
  ```

  Note that `@` here is a special symbol standing for all the members of the
  `words` array.

  ## Sorting arrays

  ### Sorting an integer array highests to lowest

  ```bash
  sorted_array=($(echo "${array[@]}" | tr " " "\n" | sort -nr))
  ```

  Where `array` is the name of the original array. The sorted array will be stored
  in the `sorted_array` array.

  The `sort` command sorts the input in reverse numerical order (`-n` for
  numerical sort and `-r` for reverse sort). The `tr` command is used to convert
  the spaces in the array to newline characters so that each element is sorted on
  a separate line.

  ## Pushing, appending to an array

  We use the `+=` shorthand to add elements to a preexisting array:

  ```bash
  preExistingArray=(1 2 3)
  preExistingArray+=(4)
  echo preExistingArray
  # 1 2 3 4

  ```

  > Note that we have to put the item we want to addend within array brackets

  ## Check if array empty

  ```sh
  an_array=()

  if [ -z "${another_array[@]}" ]; then
      echo "Array is empty"
  else
      echo "Array is not empty"
  fi
  ```

  Here we pass all the elements of the array to a
  [test](Test_values_in_Bash.md) condition which
  tests for an empty string.

  > NB: This will not immediately work in the context of a function. See below.

  ## Weirdness with functions

  When you pass an array as an argument to a
  [function](Functions_in_Bash.md) it will not
  immediately be understood to be an array.

  When we use `$1` to individuate the first function argument this is read as
  string. So if you parsed an array argument as `$1`, any logic you have in the
  function will work on the assumption that the argument is a string, not an
  array.

  To get round this we have to effectively _redeclare_ the argument as an array
  before running any array logic. We do this through a **nameref** (a reference to
  a variable). The nameref resolves to the value of the variable it references.
  This allows you to indirectly manipulate the value of the original variable
  through the nameref.

  A nameref is created with the `-n` flag. The following function uses this method
  to check if an array is empty:

  ```sh
  function array_empty() {
      declare -n arr=$1
      if [ ${#arr[@]} -gt 0 ]; then
          echo "array is not empty"
      else
          echo "array is empty"
      fi
  }

  my_array=()
  array_empty "my_array"
  ```

  You'll notice when we invoke the function we pass the array as a string, this
  facilitates the nameref operation in the function.

  ## Associational arrays / maps

  With Bash 4 we gained an additional array-like data structure that is key-value
  based and similar to maps in other languages.

  ```bash
  declare -A rock=(["win"]="scissors" ["lose"]="paper")
  ```

  We would then individuate a value with `"${rock[win]}"`

  ## Lists as implicit string arrays

  When we use the term **list** in bash, we are not actually referring to a
  specific type of data structure. Instead a **list variable** is really just a
  normal variable wrapped in quote marks that has strings separated by spaces.
  Despite the fact that this is not an actual iterative data structure, we are
  still able to loop through variables of this type.

  ```bash
  A_STR_LIST="cat dog hamster"
  AN_INT_LIST="1 2 3"
  ```

  To iterate through a list variable, we can use a for loop:

  ```bash
  for ele in $A_STR_LIST; do
      echo $ele
  done
  ```

  We are leveraging this aspect of Bash when we
  [loop through each character in a string](Strings_in_bash.md#loop-through-each-character-in-a-string).

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

  # Arrays in Bash

  We define an array as follows:

  ```bash
  words=(here are some words)
  ```

  We can also explicitly define an array using `declare` :

  ```bash
  declare -a words=("element1" "element2" "element3")
  ```

  ## Index notation

  We access specific array elements by their index using the same braces style we
  use with variables:

  ```bash
  echo "${words[2]}"

  # element3
  ```

  ## Iterating through arrays

  ```bash
  for i in "${words[@]}"
  do
     echo "$i"
     # or do whatever with individual element of the array
  done
  # element1 element2 element3
  ```

  Note that `@` here is a special symbol standing for all the members of the
  `words` array.

  ## Sorting arrays

  ### Sorting an integer array highests to lowest

  ```bash
  sorted_array=($(echo "${array[@]}" | tr " " "\n" | sort -nr))
  ```

  Where `array` is the name of the original array. The sorted array will be stored
  in the `sorted_array` array.

  The `sort` command sorts the input in reverse numerical order (`-n` for
  numerical sort and `-r` for reverse sort). The `tr` command is used to convert
  the spaces in the array to newline characters so that each element is sorted on
  a separate line.

  ## Pushing, appending to an array

  We use the `+=` shorthand to add elements to a preexisting array:

  ```bash
  preExistingArray=(1 2 3)
  preExistingArray+=(4)
  echo preExistingArray
  # 1 2 3 4

  ```

  > Note that we have to put the item we want to addend within array brackets

  ## Check if array empty

  ```sh
  an_array=()

  if [ -z "${another_array[@]}" ]; then
      echo "Array is empty"
  else
      echo "Array is not empty"
  fi
  ```

  Here we pass all the elements of the array to a
  [test](Test_values_in_Bash.md) condition which
  tests for an empty string.

  > NB: This will not immediately work in the context of a function. See below.

  ## Weirdness with functions

  When you pass an array as an argument to a
  [function](Functions_in_Bash.md) it will not
  immediately be understood to be an array.

  When we use `$1` to individuate the first function argument this is read as
  string. So if you parsed an array argument as `$1`, any logic you have in the
  function will work on the assumption that the argument is a string, not an
  array.

  To get round this we have to effectively _redeclare_ the argument as an array
  before running any array logic. We do this through a **nameref** (a reference to
  a variable). The nameref resolves to the value of the variable it references.
  This allows you to indirectly manipulate the value of the original variable
  through the nameref.

  A nameref is created with the `-n` flag. The following function uses this method
  to check if an array is empty:

  ```sh
  function array_empty() {
      declare -n arr=$1
      if [ ${#arr[@]} -gt 0 ]; then
          echo "array is not empty"
      else
          echo "array is empty"
      fi
  }

  my_array=()
  array_empty "my_array"
  ```

  You'll notice when we invoke the function we pass the array as a string, this
  facilitates the nameref operation in the function.

  ## Associational arrays / maps

  With Bash 4 we gained an additional array-like data structure that is key-value
  based and similar to maps in other languages.

  ```bash
  declare -A rock=(["win"]="scissors" ["lose"]="paper")
  ```

  We would then individuate a value with `"${rock[win]}"`

  ## Lists as implicit string arrays

  When we use the term **list** in bash, we are not actually referring to a
  specific type of data structure. Instead a **list variable** is really just a
  normal variable wrapped in quote marks that has strings separated by spaces.
  Despite the fact that this is not an actual iterative data structure, we are
  still able to loop through variables of this type.

  ```bash
  A_STR_LIST="cat dog hamster"
  AN_INT_LIST="1 2 3"
  ```

  To iterate through a list variable, we can use a for loop:

  ```bash
  for ele in $A_STR_LIST; do
      echo $ele
  done
  ```

  We are leveraging this aspect of Bash when we
  [loop through each character in a string](Strings_in_bash.md#loop-through-each-character-in-a-string).


path: zk/Lists_in_Python.md
sortable_path: zkLists_in_Python.md
title: Lists in Python
lead: Lists are the equivalent of a simple array in JavaScript.
checksum: 9e4e9326f23ad94585bf743e274e0c7164a599eea09914d92019bc054f780d5e
created: 2024-03-30 09:03:19.980140137+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python","data-structures"]}
body:
  Lists are the equivalent of a simple array in JavaScript.

  Lists have the following properties:

  - They are **ordered**
  - They are **mutable** and can be modified
  - They **allow duplicate** members
  - They are **indexed**
  - You can increase/decrease their length by adding/removing new members

  > Lists are denoted with `[...]`

  ## Duplicating lists

  When we want to duplicate a list, we can't just reassign the list to a new
  variable and expect this to be a copy.

  If we edit this "copy" it will update the original list since it copies the
  pointer and will therefore point to the same address in memory. Instead we have
  to use the List `copy()` function which returns a new list and doesn't modify
  the original list.

  Relatedly, we should distinguish those List methods and functions which create a
  new list (non-destructive) and those that mutate the existing list.

  ## Basic usage

  ```python
  # Defining a list
  list1 = ['John', 'Paul', 'George', 'Ringo']
  list2 = [4]

  # Empty list
  list3 = []  # empty list
  list3 = list()  # Also empty list

  # Nested list
  list5 = [[2, 3], [6, 8]]
  ```

  ## Slicing

  ```python
  list1 = ['John', 'Paul', 'George', 'Ringo']

  print(list1[1])
  print(list1[-1])
  print(list1[1:3])
  print(list1[:3])
  print(list1[1:])

  """
  'John'
  'Ringo'
  ['Paul', 'George']
  ['John', 'Paul', 'George']
  ['Paul', 'George', 'Ringo']
  """
  ```

  ## Adding additional values to existing list

  ### Single value

  ```py
  list1 = ['John', 'Paul', 'George', 'Ringo']

  # Add single element to the end of a list
  list1.append('Pete')
  # ['John', 'Paul', 'George', 'Ringo', 'Pete']
  ```

  ### Multiple values

  ```py
  list1.extend(['Albert', 'Bob'])
  list1 += ['Ginger', 'Sporty']
  # ['John', 'Paul', 'George', 'Ringo', 'Pete', 'Albert', 'Bob', 'Ginger', 'Sporty']
  ```

  ### Specific index

  ```python

  ## Insert at specific index

  a_list = ['Adele', 'Madonna', 'Cher']
  a_list.insert(1, 'Paloma')
  print(a_list)

  # ['Adele', 'Paloma', 'Madonna', 'Cher']
  ```

  ## Removing elements

  We distinguish `del` from `remove` when removing elements from lists:

  - `del` requires an index value
  - `remove` requires a value reference (i.e. the name of the element rather than
    its index)

  `del` is simple deletion whereas `remove` searches the list. Therefore `del` is
  more efficient.

  ### `del`

  ```py
  my_list = [10, 20, 30, 40, 50]
  del my_list[1]
  print(my_list)  # Output will be [10, 30, 40, 50]
  ```

  We can remove multiple items at once via a slice:

  ```py
  my_list = [10, 20, 30, 40, 50]

  # Delete the elements from index 1 to 3 (inclusive of start index and exclusive of end index)
  del my_list[1:4]

  # Print the updated list
  print(my_list)  # Output will be [10, 50]
  ```

  ### `remove()`

  ```py
  my_list = [10, 20, 30, 40, 50]

  # Remove the element with value 30
  my_list.remove(30)

  # Print the updated list
  print(my_list)  # Output will be [10, 20, 40, 50]
  ```

  > If the value is not found in the list,
  > ` remove()`` will raise a ValueError. To avoid this, you can check whether the value exists in the list before calling  `remove()`

  ### pop()

  Remove and return the element removed

  ```python
  list6 = ['Once', 'Upon', 'a', 'Time']
  print(list6.pop(2))
  # a

  ```

  ## Return index of a list element

  ```python
  list7 = [2, 3, 6, 8]
  print(list7.index(8))
  # 3

  list6 = ['Once', 'Upon', 'a', 'Time']
  print(list6.index('a'))
  # 2
  ```

  ## Nesting lists

  ```python
  l1 = [1, 43.5, 'Phoebe', True]
  l2 = ['apple', 'orange', 31]
  root_list = ['John', l1, l2, 'Denise']
  print(root_list)
  # ['John', [1, 43.5, 'Phoebe', True], ['apple', 'orange', 31], 'Denise']
  ```

  ## Merging (flattening) lists

  ```py
  list1 = [1, 2, 3]
  list2 = [4, 5, 6]

  merged_list = list1 + list2
  print(merged_list)  # Output: [1, 2, 3, 4, 5, 6]
  ```

  ## See also

  [Sorting lists in Python](Sorting_lists_in_Python.md)

raw_content:
  ---
  tags: [python, data-structures]
  ---

  # Lists in Python

  Lists are the equivalent of a simple array in JavaScript.

  Lists have the following properties:

  - They are **ordered**
  - They are **mutable** and can be modified
  - They **allow duplicate** members
  - They are **indexed**
  - You can increase/decrease their length by adding/removing new members

  > Lists are denoted with `[...]`

  ## Duplicating lists

  When we want to duplicate a list, we can't just reassign the list to a new
  variable and expect this to be a copy.

  If we edit this "copy" it will update the original list since it copies the
  pointer and will therefore point to the same address in memory. Instead we have
  to use the List `copy()` function which returns a new list and doesn't modify
  the original list.

  Relatedly, we should distinguish those List methods and functions which create a
  new list (non-destructive) and those that mutate the existing list.

  ## Basic usage

  ```python
  # Defining a list
  list1 = ['John', 'Paul', 'George', 'Ringo']
  list2 = [4]

  # Empty list
  list3 = []  # empty list
  list3 = list()  # Also empty list

  # Nested list
  list5 = [[2, 3], [6, 8]]
  ```

  ## Slicing

  ```python
  list1 = ['John', 'Paul', 'George', 'Ringo']

  print(list1[1])
  print(list1[-1])
  print(list1[1:3])
  print(list1[:3])
  print(list1[1:])

  """
  'John'
  'Ringo'
  ['Paul', 'George']
  ['John', 'Paul', 'George']
  ['Paul', 'George', 'Ringo']
  """
  ```

  ## Adding additional values to existing list

  ### Single value

  ```py
  list1 = ['John', 'Paul', 'George', 'Ringo']

  # Add single element to the end of a list
  list1.append('Pete')
  # ['John', 'Paul', 'George', 'Ringo', 'Pete']
  ```

  ### Multiple values

  ```py
  list1.extend(['Albert', 'Bob'])
  list1 += ['Ginger', 'Sporty']
  # ['John', 'Paul', 'George', 'Ringo', 'Pete', 'Albert', 'Bob', 'Ginger', 'Sporty']
  ```

  ### Specific index

  ```python

  ## Insert at specific index

  a_list = ['Adele', 'Madonna', 'Cher']
  a_list.insert(1, 'Paloma')
  print(a_list)

  # ['Adele', 'Paloma', 'Madonna', 'Cher']
  ```

  ## Removing elements

  We distinguish `del` from `remove` when removing elements from lists:

  - `del` requires an index value
  - `remove` requires a value reference (i.e. the name of the element rather than
    its index)

  `del` is simple deletion whereas `remove` searches the list. Therefore `del` is
  more efficient.

  ### `del`

  ```py
  my_list = [10, 20, 30, 40, 50]
  del my_list[1]
  print(my_list)  # Output will be [10, 30, 40, 50]
  ```

  We can remove multiple items at once via a slice:

  ```py
  my_list = [10, 20, 30, 40, 50]

  # Delete the elements from index 1 to 3 (inclusive of start index and exclusive of end index)
  del my_list[1:4]

  # Print the updated list
  print(my_list)  # Output will be [10, 50]
  ```

  ### `remove()`

  ```py
  my_list = [10, 20, 30, 40, 50]

  # Remove the element with value 30
  my_list.remove(30)

  # Print the updated list
  print(my_list)  # Output will be [10, 20, 40, 50]
  ```

  > If the value is not found in the list,
  > ` remove()`` will raise a ValueError. To avoid this, you can check whether the value exists in the list before calling  `remove()`

  ### pop()

  Remove and return the element removed

  ```python
  list6 = ['Once', 'Upon', 'a', 'Time']
  print(list6.pop(2))
  # a

  ```

  ## Return index of a list element

  ```python
  list7 = [2, 3, 6, 8]
  print(list7.index(8))
  # 3

  list6 = ['Once', 'Upon', 'a', 'Time']
  print(list6.index('a'))
  # 2
  ```

  ## Nesting lists

  ```python
  l1 = [1, 43.5, 'Phoebe', True]
  l2 = ['apple', 'orange', 31]
  root_list = ['John', l1, l2, 'Denise']
  print(root_list)
  # ['John', [1, 43.5, 'Phoebe', True], ['apple', 'orange', 31], 'Denise']
  ```

  ## Merging (flattening) lists

  ```py
  list1 = [1, 2, 3]
  list2 = [4, 5, 6]

  merged_list = list1 + list2
  print(merged_list)  # Output: [1, 2, 3, 4, 5, 6]
  ```

  ## See also

  [Sorting lists in Python](Sorting_lists_in_Python.md)


path: zk/Local_AWS_development_with_SAM.md
sortable_path: zkLocal_AWS_development_with_SAM.md
title: Local AWS development with SAM
checksum: 125550b5c17fa1ce41fcfd41f42c41f618882dfec80122c235a838aadee59d73
created: 2024-03-30 09:03:19.990530127+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["AWS","docker"]}
lead:
  We can run a local instance of our SAM stack for a given application without
  sending requests to the cloud. This is implemented through Docker.

body:
  We can run a local instance of our SAM stack for a given application without
  sending requests to the cloud. This is implemented through Docker.

  The SAM CLI handles all the Docker-related tasks, such as pulling the required
  Lambda runtime images, creating containers, mounting your code and dependencies,
  and running your Lambda functions inside those containers.

  ## Basic set up

  Enter your project directory.

  First build your SAM application:

  ```sh
  sam build
  ```

  We then run:

  ```sh
  sam local start-api
  ```

  If you have an API Gateway endpoint that you want to call over the local server.
  You will be able to call it after executing the above.

  This will be indicated by:

  ![](/img/local-sam-docker.png)

  If we want to invoke the function directly we use:

  ```sh
   sam local invoke [FunctionName]
  ```

  ## Using environment variables

  If you have an API key or database credentials, you are going to typically want
  to use different values dependent on environment.

  Even if the values are the same accross environments, it's a good idea to not
  call a secret when working locally since this request is billable.

  In the example below I show how to set up environment variables for an API key
  locally and in production.

  ## Create secret

  Go to AWS SecretsManager and add the API key as a secret. This will be sourced
  in production.

  ## Create local ENV file

  > These must be in JSON to work with SAM:

  ### Local env

  ```json
  // local-env.json
  {
    "FunctionName": {
      "API_KEY": "xxx-yyy-xxx",
      "NODE_ENV": "development"
    }
  }
  ```

  We save these to the root of the given function's directory not at the global
  repo level.

  > Be sure to add this to `.gitignore` so that it does not become public

  ### Update `template.yaml`

  Every environment variable you intend to use, must exist in the `template.yaml`,
  otherwise it will not be sourced at runtime:

  ```yaml
  ...
  Resources:
    Properties:
      Environment:
        Variables:
          SECRET_ARN: "arn:aws:secretsmanager:eu-west-2:885135949562:secret:wakatime-api-key-X9oF3v",
          NODE_ENV: production
          API_KEY:
  ...
  ```

  > We go ahead and populate the values for production. But we leave the variable
  > we use in development blank, since we don't want it committed and we will
  > source it at the SAM invocation. It still needs to exist though.

  ### Pass in the environment variable at invocation:

  ```sh
  sam local start-api --env-vars /home/thomas/repos/lambdas/wakatime-api/get-coding-stats/local-env.json
  ```

  In production, the variables required will be automatically sourced from the
  `template.yaml`

  ### Create handler within the Lambda itself

  You are obviously going to need to distinguish between the different deployments
  when the Lambda executes. Here is an example helper function:

  ```ts
  import * as AWS from "aws-sdk";

  const secretsManager = new AWS.SecretsManager();

  async function getApiKey(): Promise<string> {
    if (process.env.NODE_ENV === "production") {
      const response = await secretsManager
        .getSecretValue({ SecretId: process.env.SECRET_ARN as string })
        .promise();
      const secretValues = JSON.parse(response.SecretString as string);
      return secretValues.API_KEY;
    } else {
      return process.env.API_KEY as string;
    }
  }
  ```

raw_content:
  ---
  tags: [AWS, docker]
  ---

  # Local AWS development with SAM

  We can run a local instance of our SAM stack for a given application without
  sending requests to the cloud. This is implemented through Docker.

  The SAM CLI handles all the Docker-related tasks, such as pulling the required
  Lambda runtime images, creating containers, mounting your code and dependencies,
  and running your Lambda functions inside those containers.

  ## Basic set up

  Enter your project directory.

  First build your SAM application:

  ```sh
  sam build
  ```

  We then run:

  ```sh
  sam local start-api
  ```

  If you have an API Gateway endpoint that you want to call over the local server.
  You will be able to call it after executing the above.

  This will be indicated by:

  ![](/img/local-sam-docker.png)

  If we want to invoke the function directly we use:

  ```sh
   sam local invoke [FunctionName]
  ```

  ## Using environment variables

  If you have an API key or database credentials, you are going to typically want
  to use different values dependent on environment.

  Even if the values are the same accross environments, it's a good idea to not
  call a secret when working locally since this request is billable.

  In the example below I show how to set up environment variables for an API key
  locally and in production.

  ## Create secret

  Go to AWS SecretsManager and add the API key as a secret. This will be sourced
  in production.

  ## Create local ENV file

  > These must be in JSON to work with SAM:

  ### Local env

  ```json
  // local-env.json
  {
    "FunctionName": {
      "API_KEY": "xxx-yyy-xxx",
      "NODE_ENV": "development"
    }
  }
  ```

  We save these to the root of the given function's directory not at the global
  repo level.

  > Be sure to add this to `.gitignore` so that it does not become public

  ### Update `template.yaml`

  Every environment variable you intend to use, must exist in the `template.yaml`,
  otherwise it will not be sourced at runtime:

  ```yaml
  ...
  Resources:
    Properties:
      Environment:
        Variables:
          SECRET_ARN: "arn:aws:secretsmanager:eu-west-2:885135949562:secret:wakatime-api-key-X9oF3v",
          NODE_ENV: production
          API_KEY:
  ...
  ```

  > We go ahead and populate the values for production. But we leave the variable
  > we use in development blank, since we don't want it committed and we will
  > source it at the SAM invocation. It still needs to exist though.

  ### Pass in the environment variable at invocation:

  ```sh
  sam local start-api --env-vars /home/thomas/repos/lambdas/wakatime-api/get-coding-stats/local-env.json
  ```

  In production, the variables required will be automatically sourced from the
  `template.yaml`

  ### Create handler within the Lambda itself

  You are obviously going to need to distinguish between the different deployments
  when the Lambda executes. Here is an example helper function:

  ```ts
  import * as AWS from "aws-sdk";

  const secretsManager = new AWS.SecretsManager();

  async function getApiKey(): Promise<string> {
    if (process.env.NODE_ENV === "production") {
      const response = await secretsManager
        .getSecretValue({ SecretId: process.env.SECRET_ARN as string })
        .promise();
      const secretValues = JSON.parse(response.SecretString as string);
      return secretValues.API_KEY;
    } else {
      return process.env.API_KEY as string;
    }
  }
  ```


path: zk/Logarithms.md
sortable_path: zkLogarithms.md
title: Using exponents to calculate logarithms
checksum: 741db4c876f0fe9332002a963e75702af62c7cd91a1d7e03790b8b372d0b720c
created: 2024-03-30 09:03:19.994950595+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["algebra"]}
lead:
  This approach becomes rapidly difficult when working with larger numbers. It's
  not as obvious what $\log \_{5} 625$ would be using this method. For this
  reason, we use exponents which are intimately related to logarithms.

body:
  This approach becomes rapidly difficult when working with larger numbers. It's
  not as obvious what $\log \_{5} 625$ would be using this method. For this
  reason, we use exponents which are intimately related to logarithms.

  A logarithm can be expressed identically using exponents for example:

  $$
  \log \_{3} 9 = 2 \leftrightarrow 3^2 = 9


  $$

  By carrying out the conversion in stages, we can work out the answer to the
  question a logarithm poses.

  Let's work out $\log \_{2} 8$ using this method.

  1. First we add a variable (x) to the expression on the right hand:

     $$
     \log \_{2} 8 \leftrightarrow x


     $$

  1. Next we take the base of the logarithm and combine it with x as an exponent.
     Now our formula looks like this:

     $$
     \log \_{2} 8 \leftrightarrow 2^x


     $$

  1. Next we add an equals and the number that is left from the logarithm (8):

  $$
  \log \_{2} 8 \leftrightarrow 2^x = 8


  $$

  Then the problem is reduced to: how many times do you need to multiply two by
  itself to get 8? The answer is 3 : 2 x 2 x 2 or 2 p3. Hence we have the balanced
  equation:

  $$
  \log \_{2} 8 \leftrightarrow 2^3 = 8


  $$

  ## Common base values

  Often times a base won't be specified in a log expression. For example:

  $$
  \log1000


  $$

  This is just a shorthand and it means that the base value is ten, i.e that the
  logarithm is written in denary (base 10). So the above actually means:

  $$
  \log \_{10} 1000 = 3


  $$

  This is referred to as the **common logarithm**

  Another frequent base is Euler's number (approx. 2.71828) known as the **natural
  logarithm**

  An example

  $$
  \log \_{e} 7.389 = 2


  $$

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

  Most simply a logarithm is a way of answering the question:

  > How many of one number do we need to get another number. How many of x do we
  > need to get y

  More formally:

  > x raised to what power gives me y

  Below is an example of a logarithm:

  $$
  \log \_{3} 9


  $$

  We read it:

  > log base 3 of 9

  And it means:

  > 3 raised to what power gives me 9?

  In this case the answer is easy: $3^2$ gives me nine, which is to say: three
  multiplied by itself.

  ## Using exponents to calculate logarithms

  This approach becomes rapidly difficult when working with larger numbers. It's
  not as obvious what $\log \_{5} 625$ would be using this method. For this
  reason, we use exponents which are intimately related to logarithms.

  A logarithm can be expressed identically using exponents for example:

  $$
  \log \_{3} 9 = 2 \leftrightarrow 3^2 = 9


  $$

  By carrying out the conversion in stages, we can work out the answer to the
  question a logarithm poses.

  Let's work out $\log \_{2} 8$ using this method.

  1. First we add a variable (x) to the expression on the right hand:

     $$
     \log \_{2} 8 \leftrightarrow x


     $$

  1. Next we take the base of the logarithm and combine it with x as an exponent.
     Now our formula looks like this:

     $$
     \log \_{2} 8 \leftrightarrow 2^x


     $$

  1. Next we add an equals and the number that is left from the logarithm (8):

  $$
  \log \_{2} 8 \leftrightarrow 2^x = 8


  $$

  Then the problem is reduced to: how many times do you need to multiply two by
  itself to get 8? The answer is 3 : 2 x 2 x 2 or 2 p3. Hence we have the balanced
  equation:

  $$
  \log \_{2} 8 \leftrightarrow 2^3 = 8


  $$

  ## Common base values

  Often times a base won't be specified in a log expression. For example:

  $$
  \log1000


  $$

  This is just a shorthand and it means that the base value is ten, i.e that the
  logarithm is written in denary (base 10). So the above actually means:

  $$
  \log \_{10} 1000 = 3


  $$

  This is referred to as the **common logarithm**

  Another frequent base is Euler's number (approx. 2.71828) known as the **natural
  logarithm**

  An example

  $$
  \log \_{e} 7.389 = 2


  $$


path: zk/Logic_gates.md
sortable_path: zkLogic_gates.md
title: Logic gates
checksum: 2690421e13abf67dfa546b560e0b6867deaed7409216467fccebb8a9e82ff62e
created: 2024-03-30 09:03:20.002454066+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["logic-gates","binary"]}
lead:
  > [A logic gate consists in] three connections where there may or may not be
  > some electricity. Two of those connections are places where electricity may be
  > put into the device, and the third connection is a place where electricity may
  > come out of the device.

body:
  > [A logic gate consists in] three connections where there may or may not be
  > some electricity. Two of those connections are places where electricity may be
  > put into the device, and the third connection is a place where electricity may
  > come out of the device.

  [J.C. Scott. 2009. **But How Do It Know? The Basics of Computers for Everyone**,
  21]

  Logic gates are the basic building blocks of digital computing. **A logic gate
  is an electrical circuit that has one or more than one input and only one
  output.** The input and output points of the gate are
  [pins](Integrated_circuits.md) The
  input controls the output and the logic determining which types of input
  (on/off) lead to specific outputs (on/off) is isomorphic with the
  truth-conditions of the
  [Boolean connectives](Truth-functional_connectives.md) specifiable in
  terms of [truth tables](Truth-tables.md).

  Physically, what 'travels through' the gates is electrical current and what
  constitutes the 'gate' is a
  [transistor](Transistors.md)
  responding to the current. Going up a level of abstraction, the current/ charge
  is identified with a
  [bit](Binary_units_of_measurement.md#binary-units-of-measurement).
  It is bits that go into the gate and bits which come out: binary information
  that may be either 1 or 0.

  ## Elementary and composite gates

  We distinguish elementary from composite logic gates. An elementary gate is a
  single gate embodying a single logical connective. It cannot be reduced any
  lower as a logical abstraction. A composite gate is a gate made up of more than
  one elemen>tary gate and/or other composite gates.

  An example of a composite gate would be a three-way AND. An AND with three
  inputs rather than the standard two that furnish the elementary AND gate. This
  gate would output 1 when all three gates have the value 1 and 0 otherwise.
  [Adders](Half_adder_and_full_adder.md)
  and [latche>s](Latches.md) whilst
  being
  [integrated circuits](Integrated_circuits.md)
  are also, technically speaking, composite gates.

  ## Gate interface / gate implementation

  The gate _interface_ is an abstraction that the enables the user to think of the
  gate simply in terms of inputs and outputs, without being conc>erned with the
  technical details of how this is achieved. How it is achieved is the gate
  _implementation_.

  We can demonstrate this with the earlier example of a three-way AND. The diagram
  below represents the gate as an interface:

  // Add: Interface diagram

  Whereas this diagram presents the implementation of the gate: it shows the
  specific combination of gates which creates the enables the behaviour
  represented in the interface diagram.

  // Add: Implementation diagram

  > Importantly, a single interface may be implemented in a variety of ways. There
  > is a one-to-many relationship at work here. From the point of view of the user
  > interface these differences should not be detectable. This is another example
  > of
  > [hardware abstraction](Hardware_abstraction_and_modularity.md)

  ## NOT gate

  > The NOT gate inverts the value of whatever input it receives

  ### Truth conditions

  | $P$ | $\lnot P$ |
  | --- | --------- |
  | 1   | 0         |
  | 0   | 1         |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/not-gate-aeb5f9e5-9f58-4883-b8e5-d70f6d023185?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginhe

  ### Truth conditions

  tion) truth functional connective

  ### Symbol

  ![](/img/and-gate-new-2.png)

  ### Truth conditions

  ### Truth conditions

  | 1 | 0 | 0 | | 0 | 0 | 0 | | 0 | 0 | 0 |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/and-gate-b6937338-e83f-474b-af79-854b39c151a3?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  ## NAND gate

  > The NAND gate inverts the truth conditions of AND.

  ### Symbol

  ![](/img/nand-gate-new.png)

  ### Truth conditions

  | $P$ | $Q$ | $\lnot(P \land Q)$ |
  | --- | --- | ------------------ |
  | 1   | 1   | 0                  |
  | 1   | 0   | 0                  |
  | 0   | 1   | 0                  |
  | 0   | 0   | 1                  |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/nand-gate-60613ab9-9562-445e-9883-c4ea1920e206?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  NAND is a **universal logic gate**: equipped with just a NAND we can represent
  every other possible logical condition. In practice with circuits, it is more
  efficient to use specific dedicated gates (i.e OR, AND, NOT etc) for the other
  Boolean connectives but in principle the same output can be achieved through
  NANDs alone.

  ## OR gate

  > The OR gate represents the truth conditions of the
  > [disjunction](Truth-functional_connectives### Truth
  > conditions.md#disjunction) truth functional connective

  ### Symbol

  ![](/img/or-gate-new.png)

  ### Truth condition

  ### Truth conditions

  s

  | $P$ | $Q$ | $P \lor Q$ |
  | --- | --- | ---------- |
  | 1   | 1   | 1          |
  | 1   | 0   | 1          |
  | 0   | 1   | 1          |
  | 0   | 0   | 0          |

  ### Interactive circui

  ### Truth conditions

  t

  <iframe src="https://circuitverse.org/simulator/embed/or-gate-087e4933-7963-482d-b4bf-9e130ef05706?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  ## XOR gate

  > The OR gate represents the truth conditions of the exclusive OR

  ### Symbol

  ![](/img/xor-gate-new.png)

  ### Truth conditions

  | $P$ | $Q$ | $\lnot(P \Leftrightarrow Q)$ |
  | --- | --- | ---------------------------- |
  | 1   | 1   | 0                            |
  | 1   | 0   | 1                            |
  | 0   | 1   | 1                            |
  | 0   | 0   | 0                            |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/xor-gate-a240131e-a9c3-4240-a52b-e14412fdd654?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  ## NOR gate

  > The NOR gate inverts the function of an OR gate

  ### Symbol

  ![](/img/nor-gate-new.png)

  ### Truth conditions

  | $P$ | $Q$ | $P \lor Q$ |
  | --- | --- | ---------- |
  | 1   | 1   | 0          |
  | 1   | 0   | 0          |
  | 0   | 1   | 0          |
  | 0   | 0   | 1          |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/nor-gate-ac7946b4-f5d0-4c87-afd1-a2e92326d006?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

raw_content:
  ---
  tags: [logic-gates, binary]
  ---

  # Logic gates

  > [A logic gate consists in] three connections where there may or may not be
  > some electricity. Two of those connections are places where electricity may be
  > put into the device, and the third connection is a place where electricity may
  > come out of the device.

  [J.C. Scott. 2009. **But How Do It Know? The Basics of Computers for Everyone**,
  21]

  Logic gates are the basic building blocks of digital computing. **A logic gate
  is an electrical circuit that has one or more than one input and only one
  output.** The input and output points of the gate are
  [pins](Integrated_circuits.md) The
  input controls the output and the logic determining which types of input
  (on/off) lead to specific outputs (on/off) is isomorphic with the
  truth-conditions of the
  [Boolean connectives](Truth-functional_connectives.md) specifiable in
  terms of [truth tables](Truth-tables.md).

  Physically, what 'travels through' the gates is electrical current and what
  constitutes the 'gate' is a
  [transistor](Transistors.md)
  responding to the current. Going up a level of abstraction, the current/ charge
  is identified with a
  [bit](Binary_units_of_measurement.md#binary-units-of-measurement).
  It is bits that go into the gate and bits which come out: binary information
  that may be either 1 or 0.

  ## Elementary and composite gates

  We distinguish elementary from composite logic gates. An elementary gate is a
  single gate embodying a single logical connective. It cannot be reduced any
  lower as a logical abstraction. A composite gate is a gate made up of more than
  one elemen>tary gate and/or other composite gates.

  An example of a composite gate would be a three-way AND. An AND with three
  inputs rather than the standard two that furnish the elementary AND gate. This
  gate would output 1 when all three gates have the value 1 and 0 otherwise.
  [Adders](Half_adder_and_full_adder.md)
  and [latche>s](Latches.md) whilst
  being
  [integrated circuits](Integrated_circuits.md)
  are also, technically speaking, composite gates.

  ## Gate interface / gate implementation

  The gate _interface_ is an abstraction that the enables the user to think of the
  gate simply in terms of inputs and outputs, without being conc>erned with the
  technical details of how this is achieved. How it is achieved is the gate
  _implementation_.

  We can demonstrate this with the earlier example of a three-way AND. The diagram
  below represents the gate as an interface:

  // Add: Interface diagram

  Whereas this diagram presents the implementation of the gate: it shows the
  specific combination of gates which creates the enables the behaviour
  represented in the interface diagram.

  // Add: Implementation diagram

  > Importantly, a single interface may be implemented in a variety of ways. There
  > is a one-to-many relationship at work here. From the point of view of the user
  > interface these differences should not be detectable. This is another example
  > of
  > [hardware abstraction](Hardware_abstraction_and_modularity.md)

  ## NOT gate

  > The NOT gate inverts the value of whatever input it receives

  ### Truth conditions

  | $P$ | $\lnot P$ |
  | --- | --------- |
  | 1   | 0         |
  | 0   | 1         |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/not-gate-aeb5f9e5-9f58-4883-b8e5-d70f6d023185?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginhe

  ### Truth conditions

  tion) truth functional connective

  ### Symbol

  ![](/img/and-gate-new-2.png)

  ### Truth conditions

  ### Truth conditions

  | 1 | 0 | 0 | | 0 | 0 | 0 | | 0 | 0 | 0 |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/and-gate-b6937338-e83f-474b-af79-854b39c151a3?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  ## NAND gate

  > The NAND gate inverts the truth conditions of AND.

  ### Symbol

  ![](/img/nand-gate-new.png)

  ### Truth conditions

  | $P$ | $Q$ | $\lnot(P \land Q)$ |
  | --- | --- | ------------------ |
  | 1   | 1   | 0                  |
  | 1   | 0   | 0                  |
  | 0   | 1   | 0                  |
  | 0   | 0   | 1                  |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/nand-gate-60613ab9-9562-445e-9883-c4ea1920e206?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  NAND is a **universal logic gate**: equipped with just a NAND we can represent
  every other possible logical condition. In practice with circuits, it is more
  efficient to use specific dedicated gates (i.e OR, AND, NOT etc) for the other
  Boolean connectives but in principle the same output can be achieved through
  NANDs alone.

  ## OR gate

  > The OR gate represents the truth conditions of the
  > [disjunction](Truth-functional_connectives### Truth
  > conditions.md#disjunction) truth functional connective

  ### Symbol

  ![](/img/or-gate-new.png)

  ### Truth condition

  ### Truth conditions

  s

  | $P$ | $Q$ | $P \lor Q$ |
  | --- | --- | ---------- |
  | 1   | 1   | 1          |
  | 1   | 0   | 1          |
  | 0   | 1   | 1          |
  | 0   | 0   | 0          |

  ### Interactive circui

  ### Truth conditions

  t

  <iframe src="https://circuitverse.org/simulator/embed/or-gate-087e4933-7963-482d-b4bf-9e130ef05706?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  ## XOR gate

  > The OR gate represents the truth conditions of the exclusive OR

  ### Symbol

  ![](/img/xor-gate-new.png)

  ### Truth conditions

  | $P$ | $Q$ | $\lnot(P \Leftrightarrow Q)$ |
  | --- | --- | ---------------------------- |
  | 1   | 1   | 0                            |
  | 1   | 0   | 1                            |
  | 0   | 1   | 1                            |
  | 0   | 0   | 0                            |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/xor-gate-a240131e-a9c3-4240-a52b-e14412fdd654?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>

  ## NOR gate

  > The NOR gate inverts the function of an OR gate

  ### Symbol

  ![](/img/nor-gate-new.png)

  ### Truth conditions

  | $P$ | $Q$ | $P \lor Q$ |
  | --- | --- | ---------- |
  | 1   | 1   | 0          |
  | 1   | 0   | 0          |
  | 0   | 1   | 0          |
  | 0   | 0   | 1          |

  ### Interactive circuit

  <iframe src="https://circuitverse.org/simulator/embed/nor-gate-ac7946b4-f5d0-4c87-afd1-a2e92326d006?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="250" width="500" allowFullScreen></iframe>


path: zk/Logical_consistency.md
sortable_path: zkLogical_consistency.md
title: Logical consistency
lead: ## Informal definition
checksum: 25b31fea3e617c20721052b3fb39a4b363a687e9183bbcf5cc6abab51e4219b6
created: 2024-03-30 09:03:20.041343934+00:00
modified: 2024-06-16 16:52:23.802295591+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  ## Informal definition

  A set of propositions is consistent if and only if **it is possible for all the
  members of the set to be true at the same time**. A set of propositions is
  inconsistent if and only if it is not consistent.

  ### Demonstration

  The following set of propositions form an inconsistent set:

  1. Anyone who takes astrology seriously is a lunatic.
  2. Alice is my sister and no sister of mine has a lunatic for a husband.
  3. David is Alice's husband and he read's the horoscope column every morning.
  4. Anyone who reads the horoscope column every morning takes astrology
     seriously.

  The set is inconsistent because not all of them can be true. If (1), (3), (4)
  are true, (2) cannot be. If (2), (3),(4) are true, (1) cannot be.

  ## Formal definition

  > A finite set of propositions $\Gamma$ is truth-functionally consistent if and
  > only if there is at least one truth-assignment in which all propositions of
  > $\Gamma$ are true.

  ### Informal expression

  ```
  The book is blue or the book is brown
  The book is brown
  ```

  ### Formal expression

  $$
  \{P \lor Q, Q\}
  $$

  ### Truth table

  $ \{P, Q\} $ form a consistent set because there is at least one assignment when
  both propositions are true. In fact there are two (corresponding to each
  disjunct) but one is sufficient.

  | $P$ | $Q$ | $ P \lor Q $ | $Q$ |
  | --- | --- | ------------ | --- |
  | T   | T   | T            | T   |
  | T   | F   | T            | F   |
  | F   | T   | T            | T   |
  | F   | F   | F            | F   |

  ## Derivation

  > In terms of logical derivation, a finite $\Gamma$ of propositions is
  > **inconsistent** in a system of derivation for propositional logic if and only
  > if a proposition of the form $P \& \lnot P$ is derivable from $\Gamma$. It is
  > **consistent** just if this is not the case.

  In other terms, if you can derive a contradiction from the set, the set is
  logically inconsistent.

  A
  [contradiction](Logical_truth_and_falsity.md#logical-falsity)
  has very important consequences for reasoning because if a set of propositions
  is inconsistent, any other proposition is derivable from it.

  ![](/img/derivation_from_contradiction.png)

  _A demonstration of the the consequences of deriving a contradiction in a
  sequence of reasoning._

  Here we want to derive some proposition $Q$. If we can derive a contradiction
  from its negation as an assumption then, by the
  [negation elimination](Negation_Elimination.md)) rule, we can
  assert $Q$. This is why contradictions should be avoided in arguments, they
  'prove' everything which, by association, undermines any particular premise you
  are trying to assert.

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

  # Logical consistency

  ## Informal definition

  A set of propositions is consistent if and only if **it is possible for all the
  members of the set to be true at the same time**. A set of propositions is
  inconsistent if and only if it is not consistent.

  ### Demonstration

  The following set of propositions form an inconsistent set:

  1. Anyone who takes astrology seriously is a lunatic.
  2. Alice is my sister and no sister of mine has a lunatic for a husband.
  3. David is Alice's husband and he read's the horoscope column every morning.
  4. Anyone who reads the horoscope column every morning takes astrology
     seriously.

  The set is inconsistent because not all of them can be true. If (1), (3), (4)
  are true, (2) cannot be. If (2), (3),(4) are true, (1) cannot be.

  ## Formal definition

  > A finite set of propositions $\Gamma$ is truth-functionally consistent if and
  > only if there is at least one truth-assignment in which all propositions of
  > $\Gamma$ are true.

  ### Informal expression

  ```
  The book is blue or the book is brown
  The book is brown
  ```

  ### Formal expression

  $$
  \{P \lor Q, Q\}
  $$

  ### Truth table

  $ \{P, Q\} $ form a consistent set because there is at least one assignment when
  both propositions are true. In fact there are two (corresponding to each
  disjunct) but one is sufficient.

  | $P$ | $Q$ | $ P \lor Q $ | $Q$ |
  | --- | --- | ------------ | --- |
  | T   | T   | T            | T   |
  | T   | F   | T            | F   |
  | F   | T   | T            | T   |
  | F   | F   | F            | F   |

  ## Derivation

  > In terms of logical derivation, a finite $\Gamma$ of propositions is
  > **inconsistent** in a system of derivation for propositional logic if and only
  > if a proposition of the form $P \& \lnot P$ is derivable from $\Gamma$. It is
  > **consistent** just if this is not the case.

  In other terms, if you can derive a contradiction from the set, the set is
  logically inconsistent.

  A
  [contradiction](Logical_truth_and_falsity.md#logical-falsity)
  has very important consequences for reasoning because if a set of propositions
  is inconsistent, any other proposition is derivable from it.

  ![](/img/derivation_from_contradiction.png)

  _A demonstration of the the consequences of deriving a contradiction in a
  sequence of reasoning._

  Here we want to derive some proposition $Q$. If we can derive a contradiction
  from its negation as an assumption then, by the
  [negation elimination](Negation_Elimination.md)) rule, we can
  assert $Q$. This is why contradictions should be avoided in arguments, they
  'prove' everything which, by association, undermines any particular premise you
  are trying to assert.


path: zk/Logical_equivalence.md
sortable_path: zkLogical_equivalence.md
title: Logical equivalence
checksum: b08ec98b4bd43e93cacd533e9b9d0847dc5ae2454cd8d160eed3a4b7a635fb90
created: 2024-03-30 09:03:20.055697681+00:00
modified: 2024-06-16 16:52:27.765396756+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  > Two sentences, P and Q, are truth-functionally equivalent if and only if there
  > is no truth assignment in which P is true and Q is false

body:
  > Two sentences, P and Q, are truth-functionally equivalent if and only if there
  > is no truth assignment in which P is true and Q is false

  ### Informal expression

  P: If it is raining then the pavement will be wet.

  Q: The pavement is not wet unless it is raining.

  ### Formal expression

  $$
  (P \rightarrow Q) \longleftrightarrow (\lnot P \lor Q)
  $$

  ### Truth-tables

  | $P$ | $Q$ | $ P \rightarrow Q $ | $ \lnot P \lor Q$ |
  | --- | --- | ------------------- | ----------------- |
  | T   | T   | T                   | T                 |
  | T   | F   | T                   | F                 |
  | F   | T   | T                   | T                 |
  | F   | F   | F                   | T                 |

  ### Derivation

  > Propositions $P$ and $Q$ are equivalent in a system of
  > [derivation](Formal%20proofs%20in%20propositional%20logic.md) for
  > propositional logic if $Q$ is derivable from $P$ and $P$ is derivable from
  > $Q$.

  Note that the property of equivalence stated in terms of derivablity above is
  identical to the derivation rule for the
  [material biconditional](Biconditional_Introduction.md):

  ![bi-intro.png](bi-intro.png)

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

  # Logical equivalence

  > Two sentences, P and Q, are truth-functionally equivalent if and only if there
  > is no truth assignment in which P is true and Q is false

  ### Informal expression

  P: If it is raining then the pavement will be wet.

  Q: The pavement is not wet unless it is raining.

  ### Formal expression

  $$
  (P \rightarrow Q) \longleftrightarrow (\lnot P \lor Q)
  $$

  ### Truth-tables

  | $P$ | $Q$ | $ P \rightarrow Q $ | $ \lnot P \lor Q$ |
  | --- | --- | ------------------- | ----------------- |
  | T   | T   | T                   | T                 |
  | T   | F   | T                   | F                 |
  | F   | T   | T                   | T                 |
  | F   | F   | F                   | T                 |

  ### Derivation

  > Propositions $P$ and $Q$ are equivalent in a system of
  > [derivation](Formal%20proofs%20in%20propositional%20logic.md) for
  > propositional logic if $Q$ is derivable from $P$ and $P$ is derivable from
  > $Q$.

  Note that the property of equivalence stated in terms of derivablity above is
  identical to the derivation rule for the
  [material biconditional](Biconditional_Introduction.md):

  ![bi-intro.png](bi-intro.png)


path: zk/Logical_indeterminacy.md
sortable_path: zkLogical_indeterminacy.md
title: Logical indeterminacy
checksum: 32e3fa137e65eff2c6f39ea7a5fca9d67f8a160dfbe8ad1551a0c8995a001af3
created: 2024-03-30 09:03:20.068819073+00:00
modified: 2024-06-16 16:52:32.458463622+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  The vast majority of propositions in natural and formal logical languages are
  **neither
  [logically true](Logical_truth_and_falsity.md#logical-truth)
  or
  [logically false](Logical_truth_and_falsity.md#logical-falsity)**.
  This makes sense because propositions of this form are all either tautologies or
  contradictions and as such do not express information about the state of events
  in the world. We call propositions that are neither logically true or logically
  false, **logically indeterminate** propositions.

body:
  The vast majority of propositions in natural and formal logical languages are
  **neither
  [logically true](Logical_truth_and_falsity.md#logical-truth)
  or
  [logically false](Logical_truth_and_falsity.md#logical-falsity)**.
  This makes sense because propositions of this form are all either tautologies or
  contradictions and as such do not express information about the state of events
  in the world. We call propositions that are neither logically true or logically
  false, **logically indeterminate** propositions.

  ## Informal definition

  A proposition is logically indeterminate if it is neither logically true or
  logically false. This is to say: it can be both [consistently](Consistency.md)
  asserted and consistently denied.

  For example the proposition:

  ```
  It is raining.
  ```

  May be true or false thus it can it both be asserted and denied quite
  consistently. It is true if it actually is raining and false if it actually is
  not raining. There is no logical contradiction implied by saying it is raining
  when it isn't raining, this assertion is simply false. There is a contradiction
  in saying that both states obtain. Thus the proposition:

  ```
  It is raining and it is not raining.
  ```

  Cannot be consistently asserted as there is no possibility of the proposition
  being true. It is either raining or it isn't raining. Given the law for
  conjunction, both conjuncts must be true for the proposition as a whole to be
  true. But in the case of this proposition if one conjunct is true, the other
  must be false and vice versa, hence it is not possible for the proposition to be
  true at all. It can _only_ be false.

  Contrariwise the proposition:

  ```
  It is raining or it is not raining.
  ```

  Cannot be consistently denied as there is no possibility of it being false. It
  is either raining or not raining. Given the law for disjunction, either disjunct
  can be true to make the proposition as a whole true. Given that it is either
  raining or not raining in either scenario, the proposition as a whole will be
  true. Therefore there is no possibility of it being false, it can _only_ be
  true.

  ## Formal definition

  > A proposition P is truth-functionally indeterminate if and only if it is
  > neither truth-functionally true or truth-functionally false. should be avoided
  > in arguments, they 'prove' everything whi

  ```
  P
  ```

  ### Truth-table

  | $P$ | $P$ |
  | --- | --- |
  | T   | T   |
  | F   | F   |

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

  # Logical indeterminacy

  The vast majority of propositions in natural and formal logical languages are
  **neither
  [logically true](Logical_truth_and_falsity.md#logical-truth)
  or
  [logically false](Logical_truth_and_falsity.md#logical-falsity)**.
  This makes sense because propositions of this form are all either tautologies or
  contradictions and as such do not express information about the state of events
  in the world. We call propositions that are neither logically true or logically
  false, **logically indeterminate** propositions.

  ## Informal definition

  A proposition is logically indeterminate if it is neither logically true or
  logically false. This is to say: it can be both [consistently](Consistency.md)
  asserted and consistently denied.

  For example the proposition:

  ```
  It is raining.
  ```

  May be true or false thus it can it both be asserted and denied quite
  consistently. It is true if it actually is raining and false if it actually is
  not raining. There is no logical contradiction implied by saying it is raining
  when it isn't raining, this assertion is simply false. There is a contradiction
  in saying that both states obtain. Thus the proposition:

  ```
  It is raining and it is not raining.
  ```

  Cannot be consistently asserted as there is no possibility of the proposition
  being true. It is either raining or it isn't raining. Given the law for
  conjunction, both conjuncts must be true for the proposition as a whole to be
  true. But in the case of this proposition if one conjunct is true, the other
  must be false and vice versa, hence it is not possible for the proposition to be
  true at all. It can _only_ be false.

  Contrariwise the proposition:

  ```
  It is raining or it is not raining.
  ```

  Cannot be consistently denied as there is no possibility of it being false. It
  is either raining or not raining. Given the law for disjunction, either disjunct
  can be true to make the proposition as a whole true. Given that it is either
  raining or not raining in either scenario, the proposition as a whole will be
  true. Therefore there is no possibility of it being false, it can _only_ be
  true.

  ## Formal definition

  > A proposition P is truth-functionally indeterminate if and only if it is
  > neither truth-functionally true or truth-functionally false. should be avoided
  > in arguments, they 'prove' everything whi

  ```
  P
  ```

  ### Truth-table

  | $P$ | $P$ |
  | --- | --- |
  | T   | T   |
  | F   | F   |


path: zk/Logical_possibility_and_necessity.md
sortable_path: zkLogical_possibility_and_necessity.md
title: Logical possibility and necessity
lead: ## Logical possibility
checksum: f2b8d371f555e5b90f5e19beba80d8f0b6a488c50cc44d3ee4aca2bab05158c9
created: 2024-03-30 09:03:20.088733205+00:00
modified: 2024-06-16 16:52:38.408138711+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  ## Logical possibility

  In distinguishing the properties of
  [logical consistency](Logical_consistency.md) and
  [validity](Validity_and_entailment.md) we make tacit use
  of the notion of **possibility**. This is because when we consider the validity
  of an argument we are assessing truth-conditions and this consists in asking
  ourselves what could or could not be the case: were it such that _P_, then it
  would be the case that _Q_. It is important to understand what possibility means
  in the context of logic and how it differs from what we might mean ordinarily
  when we use the term.

  It is evident from the case of arguments that are valid but not sound that logic
  operates with a specialised notion of possibility. For example it has to be the
  case that the proposition _Every woman can levitate_ is logically possible since
  the following argument is valid:

  ```
  1.  P: Janice is a woman.
  2.  P: Every woman can levitate.
  3.  C: Janice can levitate.
  ```

  But we know of course that women cannot levitate. When we assert that this is
  impossible we are relying on a stronger notion of possibility than logical
  possibility. It follows that the concept of possibility can have different
  degrees. The scope of the concept of possibility has been the concern of
  logicians and philosophers since at least the time of Plato and numerous
  different formulations exist. The notion that we mostly work with unreflectively
  in everyday life is nomological possibility. This means ‘governed by the
  application of laws’ where these laws pertain to our current understanding of
  the natural world as determined by physics. Levitation is therefore
  nomologically impossible but logically possible.

  If logical possibility is not constrained by the laws of physics does it place
  any restrictions on what is possible? Logic applies a single restriction, the
  law of non-contradiction: a proposition cannot both be true and false at once.
  The following propositions are examples of a contradictory propositions.

  Some examples of contradictions:

  - There is a dog that is not a dog
  - Today is Tuesday and today is not Tuesday
  - The cat that is dead is alive

  From this we can derive the following property of logical possibility:

  > A proposition is logically possible just if it does not imply a contradiction.

  ## Logical necessity

  A proposition is _logically necessary_ if it is true in every logically possible
  circumstance which is to say: true on every possible truth functional
  assignment. Necessity and
  [logical truth](Logical_truth_and_falsity.md#logical-truth)
  are therefore synonyms: anything that is logically true (a tautology) is true by
  necessity (could not be otherwise.)

  Further, every logical truth is logically possible but not everything that is
  logically possible is logically true. It is possible that it is raining but this
  is not logically necessary - it could be otherwise, i.e not raining. However it
  is not possible that it could be both raining and not raining.

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

  # Logical possibility and necessity

  ## Logical possibility

  In distinguishing the properties of
  [logical consistency](Logical_consistency.md) and
  [validity](Validity_and_entailment.md) we make tacit use
  of the notion of **possibility**. This is because when we consider the validity
  of an argument we are assessing truth-conditions and this consists in asking
  ourselves what could or could not be the case: were it such that _P_, then it
  would be the case that _Q_. It is important to understand what possibility means
  in the context of logic and how it differs from what we might mean ordinarily
  when we use the term.

  It is evident from the case of arguments that are valid but not sound that logic
  operates with a specialised notion of possibility. For example it has to be the
  case that the proposition _Every woman can levitate_ is logically possible since
  the following argument is valid:

  ```
  1.  P: Janice is a woman.
  2.  P: Every woman can levitate.
  3.  C: Janice can levitate.
  ```

  But we know of course that women cannot levitate. When we assert that this is
  impossible we are relying on a stronger notion of possibility than logical
  possibility. It follows that the concept of possibility can have different
  degrees. The scope of the concept of possibility has been the concern of
  logicians and philosophers since at least the time of Plato and numerous
  different formulations exist. The notion that we mostly work with unreflectively
  in everyday life is nomological possibility. This means ‘governed by the
  application of laws’ where these laws pertain to our current understanding of
  the natural world as determined by physics. Levitation is therefore
  nomologically impossible but logically possible.

  If logical possibility is not constrained by the laws of physics does it place
  any restrictions on what is possible? Logic applies a single restriction, the
  law of non-contradiction: a proposition cannot both be true and false at once.
  The following propositions are examples of a contradictory propositions.

  Some examples of contradictions:

  - There is a dog that is not a dog
  - Today is Tuesday and today is not Tuesday
  - The cat that is dead is alive

  From this we can derive the following property of logical possibility:

  > A proposition is logically possible just if it does not imply a contradiction.

  ## Logical necessity

  A proposition is _logically necessary_ if it is true in every logically possible
  circumstance which is to say: true on every possible truth functional
  assignment. Necessity and
  [logical truth](Logical_truth_and_falsity.md#logical-truth)
  are therefore synonyms: anything that is logically true (a tautology) is true by
  necessity (could not be otherwise.)

  Further, every logical truth is logically possible but not everything that is
  logically possible is logically true. It is possible that it is raining but this
  is not logically necessary - it could be otherwise, i.e not raining. However it
  is not possible that it could be both raining and not raining.


path: zk/Logical_truth_and_falsity.md
sortable_path: zkLogical_truth_and_falsity.md
title: Logical truth and falsity
lead: We say of certain propositions that they are logically true or logically false.
checksum: 4a99a064e5afe470ad3eb6ae5bb245501ff8c76df7292f13e67109dd80525769
created: 2024-03-30 09:03:20.106473653+00:00
modified: 2024-06-16 16:52:42.537921525+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  We say of certain propositions that they are logically true or logically false.

  ## Logical falsity

  ### Informal definition

  A proposition is logically false if and only if **it is not possible for the
  proposition to be true**. The proposition itself cannot be consistently
  asserted.

  **Demonstration**

  ```
  There is a country that is not a country.
  Apples are fruits and apples are not fruits
  ```

  Neither proposition can be true because the truth of the first clause is
  contradicted by the second. By the principle of
  [consistency](Logical_consistency.md), it is not
  possible for both clauses to be true at once therefore the proposition, overall
  has the truth value of false.

  ```
  It is raining and it is not raining.
  ```

  ### Formal definition

  > A proposition P is truth-functionally false if and only if P is false on every
  > truth-value assignment

  ### Formal expression

  $$ P \land \lnot P $$

  ### Truth-table

  | $P$ | $P \land \lnot P$ |
  | --- | ----------------- |
  | T   | F                 |
  | T   | F                 |

  ## Logical truth

  ### Informal definition

  A proposition is logically true if and only if it is not possible for the
  proposition to be false. The proposition itself cannot be consistently denied.

  **Demonstration**

  ```
  A rose is a rose.
  Today is Tuesday unless today is not Tuesday.
  ```

  Regardless of any facts obtaining in the world, these propositions cannot be
  false.should be avoided in arguments, they 'prove' everything whi

  As with logically false propositions, logical truth can also apply to compound
  propositions:

  ```
  A rose is a rose and a shoe is a shoe
  ```

  ### Formal definition

  > A proposition P is truth-functionally true if and only if P is true on every
  > truth-value assignment

  $$ P \lor \lnot P$$

  ### Truth-table

  | $P$ | $P \lor \lnot P$ |
  | --- | ---------------- |
  | T   | T                |
  | F   | T                |

  ### Consequences

  The existence of logically false and logically true propositions affects the
  validity and soundness of arguments in which they are used. These are
  technicalities that have philosophically interesting consequences.

  - If an argument contains premises which are logically false than this argument
    will perforce be valid. This is because one cannot consistently assert the
    premises and deny the conclusion which is the definition of
    [validity](Validity_and_entailment.md). However the
    _reason_ why one cannot consistently assert the premises and deny the
    conclusions is because one cannot consistently assert the premises - they
    conflict with each other. Furthermore as the argument contains false premises,
    it cannot be sound.

    ```
    (P1) Russia is a country.
    (P2) Russia is not a country
    (P3) All countries have languages.
    ____________________________________________
    (C) Russian is a language.
    ```

  - Any argument with a logically true conclusion is valid. Because the conclusion
    cannot be consistently denied it follows that we cannot consistently assert
    the premises _and_ deny the conclusion. Whether or not the argument is sound
    remains an open question however. If the premises happen to be true then the
    argument will be sound on the strength of the conclusion being logically true
    but if the premises are false it will be unsound regardless of the truth of
    the conclusion.

    ```
    (P1) Horses have legs.
    (P2) Animals with legs can move.
    ____________________________________________
    (C) A horse is a horse
    ```

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

  # Logical truth and falsity

  We say of certain propositions that they are logically true or logically false.

  ## Logical falsity

  ### Informal definition

  A proposition is logically false if and only if **it is not possible for the
  proposition to be true**. The proposition itself cannot be consistently
  asserted.

  **Demonstration**

  ```
  There is a country that is not a country.
  Apples are fruits and apples are not fruits
  ```

  Neither proposition can be true because the truth of the first clause is
  contradicted by the second. By the principle of
  [consistency](Logical_consistency.md), it is not
  possible for both clauses to be true at once therefore the proposition, overall
  has the truth value of false.

  ```
  It is raining and it is not raining.
  ```

  ### Formal definition

  > A proposition P is truth-functionally false if and only if P is false on every
  > truth-value assignment

  ### Formal expression

  $$ P \land \lnot P $$

  ### Truth-table

  | $P$ | $P \land \lnot P$ |
  | --- | ----------------- |
  | T   | F                 |
  | T   | F                 |

  ## Logical truth

  ### Informal definition

  A proposition is logically true if and only if it is not possible for the
  proposition to be false. The proposition itself cannot be consistently denied.

  **Demonstration**

  ```
  A rose is a rose.
  Today is Tuesday unless today is not Tuesday.
  ```

  Regardless of any facts obtaining in the world, these propositions cannot be
  false.should be avoided in arguments, they 'prove' everything whi

  As with logically false propositions, logical truth can also apply to compound
  propositions:

  ```
  A rose is a rose and a shoe is a shoe
  ```

  ### Formal definition

  > A proposition P is truth-functionally true if and only if P is true on every
  > truth-value assignment

  $$ P \lor \lnot P$$

  ### Truth-table

  | $P$ | $P \lor \lnot P$ |
  | --- | ---------------- |
  | T   | T                |
  | F   | T                |

  ### Consequences

  The existence of logically false and logically true propositions affects the
  validity and soundness of arguments in which they are used. These are
  technicalities that have philosophically interesting consequences.

  - If an argument contains premises which are logically false than this argument
    will perforce be valid. This is because one cannot consistently assert the
    premises and deny the conclusion which is the definition of
    [validity](Validity_and_entailment.md). However the
    _reason_ why one cannot consistently assert the premises and deny the
    conclusions is because one cannot consistently assert the premises - they
    conflict with each other. Furthermore as the argument contains false premises,
    it cannot be sound.

    ```
    (P1) Russia is a country.
    (P2) Russia is not a country
    (P3) All countries have languages.
    ____________________________________________
    (C) Russian is a language.
    ```

  - Any argument with a logically true conclusion is valid. Because the conclusion
    cannot be consistently denied it follows that we cannot consistently assert
    the premises _and_ deny the conclusion. Whether or not the argument is sound
    remains an open question however. If the premises happen to be true then the
    argument will be sound on the strength of the conclusion being logically true
    but if the premises are false it will be unsound regardless of the truth of
    the conclusion.

    ```
    (P1) Horses have legs.
    (P2) Animals with legs can move.
    ____________________________________________
    (C) A horse is a horse
    ```


path: zk/Loops_in_Python.md
sortable_path: zkLoops_in_Python.md
title: Loops in Python
lead: ## For
checksum: acdd623ce724d6b2166e14d9743e7552851ba4c5d1f478ddc4ce8fca6f15ce44
created: 2024-03-30 09:03:20.118761964+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  ## For

  There are three main types of `for` loop in Python and they all use the
  `for...in` clause:

  1. Traditional `for` loop with range:

     ```py
     for i in range(0, 5):
         print(i)
     ```

     This is equivalent to the following in JavaScript:

     ```js
     for (let i = 0; i <= 5: i++) {
         console.log(i)
     }
     ```

  2. `for` loop with iterable:

     ```py
     fruits = ["apple", "banana", "cherry"]
     for (fruit in fruits):
         print(fruit)
     ```

  3. `for` loop with `enumerate()` function: Iterate over an iterable whilst also
     keeping track of the index of the current element:
     ```py
     for index, fruit in enumerate(fruits):
         print(f"Index {index}: {fruit}")
     ```

  ### Loop in increments greater than 1

  The following loop increments by 2 on each iteration

  ```py
  for i in range(0, 10, 2):
      print(i, ' ', end='')
  print()

  # 0  2  4  6  8
  ```

  ### Break and continue

  #### Break

  ```py
  num = int(input('Enter a number from 1-6 to check for: '))
  for i in range(0, 6):
      if i == num:
          break
      print(i, ' ')
  ```

  #### Continue

  ```py
  for i in range(0, 10):
      print(i, ' ', end='')
      if i % 2 == 1:
          continue
      print('hey its an even number')
      print('we love even numbers')
  print('Done')

  """
  0  hey its an even number
  we love even numbers
  1  2  hey its an even number
  we love even numbers
  3  4  hey its an even number
  we love even numbers
  5  6  hey its an even number
  we love even numbers
  7  8  hey its an even number
  we love even numbers
  9  Done
  """
  ```

  ## While

  ```python
  count = 0
  print('Starting')
  while count < 10:
      print(count, '', end='')
      count += 1

  print()  # not part of the while loop
  print('Done')

  """
  Starting
  0 1 2 3 4 5 6 7 8 9
  Done
  """
  ```

  > There are no `do while` loops in Python

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

  # Loops in Python

  ## For

  There are three main types of `for` loop in Python and they all use the
  `for...in` clause:

  1. Traditional `for` loop with range:

     ```py
     for i in range(0, 5):
         print(i)
     ```

     This is equivalent to the following in JavaScript:

     ```js
     for (let i = 0; i <= 5: i++) {
         console.log(i)
     }
     ```

  2. `for` loop with iterable:

     ```py
     fruits = ["apple", "banana", "cherry"]
     for (fruit in fruits):
         print(fruit)
     ```

  3. `for` loop with `enumerate()` function: Iterate over an iterable whilst also
     keeping track of the index of the current element:
     ```py
     for index, fruit in enumerate(fruits):
         print(f"Index {index}: {fruit}")
     ```

  ### Loop in increments greater than 1

  The following loop increments by 2 on each iteration

  ```py
  for i in range(0, 10, 2):
      print(i, ' ', end='')
  print()

  # 0  2  4  6  8
  ```

  ### Break and continue

  #### Break

  ```py
  num = int(input('Enter a number from 1-6 to check for: '))
  for i in range(0, 6):
      if i == num:
          break
      print(i, ' ')
  ```

  #### Continue

  ```py
  for i in range(0, 10):
      print(i, ' ', end='')
      if i % 2 == 1:
          continue
      print('hey its an even number')
      print('we love even numbers')
  print('Done')

  """
  0  hey its an even number
  we love even numbers
  1  2  hey its an even number
  we love even numbers
  3  4  hey its an even number
  we love even numbers
  5  6  hey its an even number
  we love even numbers
  7  8  hey its an even number
  we love even numbers
  9  Done
  """
  ```

  ## While

  ```python
  count = 0
  print('Starting')
  while count < 10:
      print(count, '', end='')
      count += 1

  print()  # not part of the while loop
  print('Done')

  """
  Starting
  0 1 2 3 4 5 6 7 8 9
  Done
  """
  ```

  > There are no `do while` loops in Python


path: zk/Loops_in_bash.md
sortable_path: zkLoops_in_bash.md
title: Loops in Bash
lead: ## Traditional for loop
checksum: 021d8bf50cddf71c62034ac838af2c11526a8a1ce9f3debec29c67a6f8150ef6
created: 2024-03-30 09:03:20.122591455+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
body:
  ## Traditional for loop

  ```bash
  for (( i=0; i<=5;i++  )); do
    echo $i
  done

  # 1 2 3 4 5
  ```

  ## `for..in`: loop through an array

  ```bash
  for element in "${arr[@]}"
  do
      echo "$element"
  done
  ```

  ## While loop

  > `while` loops execute while a condition is true (0)

  We can use a `while` loop as a condition in two senses:

  - execute while a given condition obtains
  - expand and execute a given command as long as the final command in the `while'
    for the command has an exit status of zero (i.e. truthy)

  Here is an exampe of using `while` in the former case:

  ```sh
  declare -i n=0
  while (( n<10 ))
  do
    echo "n:$n"
    (( n++ ))
  done
  ```

  Here is an example of using `while` in the latter case:

  ```sh
  while read line;
  do
      # something
  done < "$file_to_read"
  ```

  ## Until loop

  > `until` loops execute until a condition is false (1)

  ```sh
  declare -i m=0
  until (( m==10 )): do
    echo "m:$m"
    (( m++ ))
  done
  ```

  This gives us the same output as `n` with the while loop but here it runs so
  long as `m==10` is false. As soon as `m` is equal to 100, the condition becomes
  true and hence the loop stops.

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

  # Loops in Bash

  ## Traditional for loop

  ```bash
  for (( i=0; i<=5;i++  )); do
    echo $i
  done

  # 1 2 3 4 5
  ```

  ## `for..in`: loop through an array

  ```bash
  for element in "${arr[@]}"
  do
      echo "$element"
  done
  ```

  ## While loop

  > `while` loops execute while a condition is true (0)

  We can use a `while` loop as a condition in two senses:

  - execute while a given condition obtains
  - expand and execute a given command as long as the final command in the `while'
    for the command has an exit status of zero (i.e. truthy)

  Here is an exampe of using `while` in the former case:

  ```sh
  declare -i n=0
  while (( n<10 ))
  do
    echo "n:$n"
    (( n++ ))
  done
  ```

  Here is an example of using `while` in the latter case:

  ```sh
  while read line;
  do
      # something
  done < "$file_to_read"
  ```

  ## Until loop

  > `until` loops execute until a condition is false (1)

  ```sh
  declare -i m=0
  until (( m==10 )): do
    echo "m:$m"
    (( m++ ))
  done
  ```

  This gives us the same output as `n` with the while loop but here it runs so
  long as `m==10` is false. As soon as `m` is equal to 100, the condition becomes
  true and hence the loop stops.


path: zk/Machine_code.md
sortable_path: zkMachine_code.md
title: Machine_code
lead: # Machine code
checksum: 44047dbf1cd0ccdae2cb7733a887580bbe1bb8ebc35d4884ba175290cd6987ba
created: 2024-03-30 09:03:20.126143504+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"created":"Monday, March 11, 2024","id":"iqh8","tags":["binary","CPU"],"title":"Machine_code"}
body:
  # Machine code

  No matter how a program was originally written, it eventually needs to execute
  on a [[CPU_architecture|CPU]] as a series of machine language instructions.

  ## Example machine instruction

  The following machine instruction is an instruction that would be understood by
  an [[Instruction_set_architectures| ARM ISA]]. It moves the number `4` into the
  `r7` register:

  ```
  11100011101000000111000000000100
  ```

  This 32-bit line of binary has a series of instructions embedded within it. We
  partition each part of the sequence below, from left to right, mapping it to the
  instruction:

  | Binary sequence | Instruction          | Action                                   |
  | --------------- | -------------------- | ---------------------------------------- |
  | 1110            | condition            | always execute (unconditional)           |
  | 00              | --                   | --                                       |
  | 1               | immediate            | value is in the last 8 bits              |
  | 1101            | opcode               | `mov` : move the value                   |
  | 0               | --                   | --                                       |
  | 0000            | --                   | --                                       |
  | 0111            | destination register | destination is 0111 which means `r7`     |
  | 0000            | --                   | --                                       |
  | 00000100        | immediate value      | the binary representation of decimal '4' |

  - The blank zeros are also instructions, they are just not used in this
    instruction

  - The condition sequence tells us the instruction should run in all conditions
    not only under certain circumstances

  - The immediate bit (`1`) tells us whether the the value we are accessing is
    contained within the instruction or whether it is stored in a register. In
    this scenario `1` means the value is in the instruction. If it were `0`, the
    register where the value is located would be specified elsewhere in the
    instruction (in one of the currently blank sequences).

  - The opcode correspoons to `mov` the value.

  - THe destination register details where the value should be moved to (`r7`)

  - Finally, the immediate value is equivalent to decimal `4`

  ## Difficulty

  Binary sequences are hard to understand, even if converted to the
  [[Hexadecimal_number_system]]. We have a better way of managing operations on
  the machine level: [assembly](Assembly.md)

raw_content:
  ---
  id: iqh8
  title: Machine_code
  tags: [binary, CPU]
  created: Monday, March 11, 2024
  ---

  # Machine code

  No matter how a program was originally written, it eventually needs to execute
  on a [[CPU_architecture|CPU]] as a series of machine language instructions.

  ## Example machine instruction

  The following machine instruction is an instruction that would be understood by
  an [[Instruction_set_architectures| ARM ISA]]. It moves the number `4` into the
  `r7` register:

  ```
  11100011101000000111000000000100
  ```

  This 32-bit line of binary has a series of instructions embedded within it. We
  partition each part of the sequence below, from left to right, mapping it to the
  instruction:

  | Binary sequence | Instruction          | Action                                   |
  | --------------- | -------------------- | ---------------------------------------- |
  | 1110            | condition            | always execute (unconditional)           |
  | 00              | --                   | --                                       |
  | 1               | immediate            | value is in the last 8 bits              |
  | 1101            | opcode               | `mov` : move the value                   |
  | 0               | --                   | --                                       |
  | 0000            | --                   | --                                       |
  | 0111            | destination register | destination is 0111 which means `r7`     |
  | 0000            | --                   | --                                       |
  | 00000100        | immediate value      | the binary representation of decimal '4' |

  - The blank zeros are also instructions, they are just not used in this
    instruction

  - The condition sequence tells us the instruction should run in all conditions
    not only under certain circumstances

  - The immediate bit (`1`) tells us whether the the value we are accessing is
    contained within the instruction or whether it is stored in a register. In
    this scenario `1` means the value is in the instruction. If it were `0`, the
    register where the value is located would be specified elsewhere in the
    instruction (in one of the currently blank sequences).

  - The opcode correspoons to `mov` the value.

  - THe destination register details where the value should be moved to (`r7`)

  - Finally, the immediate value is equivalent to decimal `4`

  ## Difficulty

  Binary sequences are hard to understand, even if converted to the
  [[Hexadecimal_number_system]]. We have a better way of managing operations on
  the machine level: [assembly](Assembly.md)


path: zk/Managing_environments.md
sortable_path: zkManaging_environments.md
title: Managing environments
lead: With a full-scale Node application you will typically run three environments:
checksum: 036a86541e04215403232e121dc96a105197f73db8d435c9bf4d6f02d68aa280
created: 2024-03-30 09:03:20.146421913+00:00
modified: 2024-06-15 09:50:22.093157284+00:00
metadata: {"tags":["node-js"]}
body:
  With a full-scale Node application you will typically run three environments:

  - Development
  - Testing
  - Production

  ## Accessing the current environment

  To determine the current environment we can use the variable
  **`process.env.NODE_ENV`** from the
  [global process object](Process_object.md).
  This works universally regardless of the kind of Node app we are building.

  If you have not manually set up your environments, **`process.env.NODE_ENV`**
  will return `undefined`.

  ### Setting the Node environment

  #### For the session

  `NODE_ENV` is a bash
  [environment variable](Environmental_and_shell_variables.md)
  like any other. So we can set it in the normal way:

  ```bash
  export NODE_ENV=production
  ```

  ### In Express

  In Express, there is a built in method for retrieving the current envrionment:
  `app.get('env')`. Express will default to the development environment.

  <p style="color:red">! How to keep Express and Node environment in sync?</p>

  ## Configuring environments

  We use the third-party [Config](node-config)
  package to manage different configurations based on the environment.

  Once installed we set up a dedicated config directory with a structure as
  follows:

  ```
  config/
      default.json
      development.json
      production.json
  ```

  For example:

  ```json
  // default.json
  {
    "name": "My Express app"
  }
  ```

  Then to utilise config variables:

  ```js
  const config = require("config");
  console.log("Application name:" + config.get("name"));
  ```

  If we toggled the different environments, we would see different outputs from
  the above code (assuming we had different config files in `/config` with
  different names).

  ### Sensitive config data

  We will need to store passwords, API keys and other kinds of authentication data
  for our application. We obviously shouldn't store this data openly in our config
  files since it would be made public.

  We can do so securely by utilising
  [environmental variables](Environmental_and_shell_variables.md)
  alongside the config pacakage.

  We create a file called `custom-environment-variables` (must be called this) and
  map a property to an environmental environment we have already set.

  Let's create an environmental variable for a password:

  ```bash
  export APP_PASSWORD='mypassword123'
  ```

  Then in our custom variable file:

  ```json
  {
    "password": "APP_PASSWORD"
  }
  ```

  We can then safely reference this value in the course of our normal code:

  ```js
  console.log(config.get("password"));
  ```

  <p style="color:red">! But how would this be achieved in a production server?</p>

  <p style="color:red">! And how could we do this programmatically at the start of a local development session without manually setting each environment variable in the terminal?</p>

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

  # Managing environments

  With a full-scale Node application you will typically run three environments:

  - Development
  - Testing
  - Production

  ## Accessing the current environment

  To determine the current environment we can use the variable
  **`process.env.NODE_ENV`** from the
  [global process object](Process_object.md).
  This works universally regardless of the kind of Node app we are building.

  If you have not manually set up your environments, **`process.env.NODE_ENV`**
  will return `undefined`.

  ### Setting the Node environment

  #### For the session

  `NODE_ENV` is a bash
  [environment variable](Environmental_and_shell_variables.md)
  like any other. So we can set it in the normal way:

  ```bash
  export NODE_ENV=production
  ```

  ### In Express

  In Express, there is a built in method for retrieving the current envrionment:
  `app.get('env')`. Express will default to the development environment.

  <p style="color:red">! How to keep Express and Node environment in sync?</p>

  ## Configuring environments

  We use the third-party [Config](node-config)
  package to manage different configurations based on the environment.

  Once installed we set up a dedicated config directory with a structure as
  follows:

  ```
  config/
      default.json
      development.json
      production.json
  ```

  For example:

  ```json
  // default.json
  {
    "name": "My Express app"
  }
  ```

  Then to utilise config variables:

  ```js
  const config = require("config");
  console.log("Application name:" + config.get("name"));
  ```

  If we toggled the different environments, we would see different outputs from
  the above code (assuming we had different config files in `/config` with
  different names).

  ### Sensitive config data

  We will need to store passwords, API keys and other kinds of authentication data
  for our application. We obviously shouldn't store this data openly in our config
  files since it would be made public.

  We can do so securely by utilising
  [environmental variables](Environmental_and_shell_variables.md)
  alongside the config pacakage.

  We create a file called `custom-environment-variables` (must be called this) and
  map a property to an environmental environment we have already set.

  Let's create an environmental variable for a password:

  ```bash
  export APP_PASSWORD='mypassword123'
  ```

  Then in our custom variable file:

  ```json
  {
    "password": "APP_PASSWORD"
  }
  ```

  We can then safely reference this value in the course of our normal code:

  ```js
  console.log(config.get("password"));
  ```

  <p style="color:red">! But how would this be achieved in a production server?</p>

  <p style="color:red">! And how could we do this programmatically at the start of a local development session without manually setting each environment variable in the terminal?</p>


path: zk/Map_and_filter_in_Python.md
sortable_path: zkMap_and_filter_in_Python.md
title: Map and filter in Python
lead: ## Map
checksum: 657b2624c0782eaa5f2b92a064f41d79185fc42eef1be5b4c8ab202f87745c76
created: 2024-03-30 09:03:20.165674202+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  ## Map

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  new_data = map(lambda i: i + 10, data)
  print(new_data)
  ```

  We can also pass-in a function rather than use a lambda:

  ```py
  def add_one(i):
      return i + 1

  x = list(map(addOne, data))
  # [2, 4, 6, 3, 8, 5, 11]
  # necessary to add `list` to get some output
  ```

  ### Map returns an object

  When you run `map` against a list it returns a map object not a list. This can
  present problems if you are not aware of it since it is not an entity that you
  can loop through.

  The following map removes whitespaces from elements in a list:

  ```py
    lines = map(lambda x: x.strip(), lines)
  ```

  In order to be able to run list-like operations against the resulting `lines`
  object we would need to convert it to a list:

  ```py
    lines = list(map(lambda x: x.strip(), lines))
  ```

  ## Filter

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  d1 = list(filter(lambda i: i % 2 == 0, data))
  print(d1)
  #  [2, 4, 10]

  def is_even(i):
      return i % 2 == 0


  # Filter for even numbers using a named function
  d2 = list(filter(is_even, data))

  # [2, 4, 10]
  ```

  ## Chaining

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]
  new_data = list(map(lambda i: i + 10, filter(is_even, data)))
  print(new_data)
  # new_data: [12, 14, 20]
  ```

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

  # Map and filter in Python

  ## Map

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  new_data = map(lambda i: i + 10, data)
  print(new_data)
  ```

  We can also pass-in a function rather than use a lambda:

  ```py
  def add_one(i):
      return i + 1

  x = list(map(addOne, data))
  # [2, 4, 6, 3, 8, 5, 11]
  # necessary to add `list` to get some output
  ```

  ### Map returns an object

  When you run `map` against a list it returns a map object not a list. This can
  present problems if you are not aware of it since it is not an entity that you
  can loop through.

  The following map removes whitespaces from elements in a list:

  ```py
    lines = map(lambda x: x.strip(), lines)
  ```

  In order to be able to run list-like operations against the resulting `lines`
  object we would need to convert it to a list:

  ```py
    lines = list(map(lambda x: x.strip(), lines))
  ```

  ## Filter

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  d1 = list(filter(lambda i: i % 2 == 0, data))
  print(d1)
  #  [2, 4, 10]

  def is_even(i):
      return i % 2 == 0


  # Filter for even numbers using a named function
  d2 = list(filter(is_even, data))

  # [2, 4, 10]
  ```

  ## Chaining

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]
  new_data = list(map(lambda i: i + 10, filter(is_even, data)))
  print(new_data)
  # new_data: [12, 14, 20]
  ```


path: zk/Map_and_filter_to_reduce.md
sortable_path: zkMap_and_filter_to_reduce.md
title: Expressing combined map and filter operations to a single reduce
lead: ## `map` + `filter`
checksum: c55ae1785e85ae7113094c8c7f48a7190f3e0eb2cec9022f07c79701ce3fea0a
created: 2024-03-30 09:03:20.169127095+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript"]}
body:
  ## `map` + `filter`

  The following combines a `map` and a `filter` to square a list of numbers and
  then filter the ones that are greater than or equal to 10:

  ```js
  const arr = [1, 2, 3, 4];

  const filteredSquaredNumbers = arr.map((x) => x * x).filter((y) => y >= 10);
  ```

  Using `reduce` we can combine these operations more concisely:

  ```js
  const filteredSquaredNumbers = arr.reduce((acc, x) => {
    const squared = x * x;
    if (squared >= 10) {
      acc.push(squared);
    }
    return acc;
  }, []);
  ```

  ## `filter` + `map`

  Here we use a filter and then a map to filter the even numbers and then double
  them:

  ```js
  const arr = [1, 2, 3, 4];

  const evenDoubled = arr.filter((x) => x % 2 === 0).map((y) => y * 2);
  ```

  Using `reduce` :

  ```js
  const evenDoubled = arr.reduce((acc, x) => {
    if (x % 2 === 0) {
      acc.push(x * 2);
    }
    return acc;
  }, []);
  ```

  > The empty array [] is the initial value of the accumulator. As the callback
  > function processes each element of the array, it can add or modify the
  > accumulator based on the desired logic. By providing an empty array as the
  > initial value for the accumulator, we ensure that the results will be
  > collected in a new array without modifying the original array.

raw_content:
  ---
  tags: [javascript]
  ---

  # Expressing combined map and filter operations to a single reduce

  ## `map` + `filter`

  The following combines a `map` and a `filter` to square a list of numbers and
  then filter the ones that are greater than or equal to 10:

  ```js
  const arr = [1, 2, 3, 4];

  const filteredSquaredNumbers = arr.map((x) => x * x).filter((y) => y >= 10);
  ```

  Using `reduce` we can combine these operations more concisely:

  ```js
  const filteredSquaredNumbers = arr.reduce((acc, x) => {
    const squared = x * x;
    if (squared >= 10) {
      acc.push(squared);
    }
    return acc;
  }, []);
  ```

  ## `filter` + `map`

  Here we use a filter and then a map to filter the even numbers and then double
  them:

  ```js
  const arr = [1, 2, 3, 4];

  const evenDoubled = arr.filter((x) => x % 2 === 0).map((y) => y * 2);
  ```

  Using `reduce` :

  ```js
  const evenDoubled = arr.reduce((acc, x) => {
    if (x % 2 === 0) {
      acc.push(x * 2);
    }
    return acc;
  }, []);
  ```

  > The empty array [] is the initial value of the accumulator. As the callback
  > function processes each element of the array, it can add or modify the
  > accumulator based on the desired logic. By providing an empty array as the
  > initial value for the accumulator, we ensure that the results will be
  > collected in a new array without modifying the original array.


path: zk/Mapped_types.md
sortable_path: zkMapped_types.md
title: Mapped types in TypeScript
lead:
  A mapped type is a way to create new types based on existing ones by
  transforming their properties

checksum: ccabedb3387b7583fb0265e8e625b02b3cfe9ddc04875240fe8f29b61a4fab8e
created: 2024-03-30 09:03:20.172244522+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
body:
  A mapped type is a way to create new types based on existing ones by
  transforming their properties

  As it works on a single type and typically narrows or limits the properties it
  works in a way that is opposite to a [union]() or [intersection]() type.

  > Mapped types are an example of when `type` is preferable to `interface`, since
  > you cannot generate mapped types as interfaces, although you may generate a
  > mapped type from an interface.

  For the demonstrations we will use the following type as the example that we
  will map from:

  ```ts
  type Person = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```

  ## Main varieties of mapped types

  ### Read only

  Creates a type with all properties of the given type set to `readonly`:

  ```ts
  type Readonly<T> = {
    readonly [P in keyof T]: T[P];
  };

  type ReadonlyPerson = Readonly<Person>;
  ```

  This is equivalent to:

  ```ts
  type ReadonlyPerson = {
    readonly name: string;
    readonly age: number;
    readonly city: string;
    readonly country: string;
  };
  ```

  ### Partial

  Creates a type with all properties of the given type set to optional.

  ```ts
  type Partial<T> = {
    [P in keyof T]?: T[P];
  };

  type PartialPerson = Partial<Person>;
  ```

  This is equivalent to:

  ```ts
  type PartialPerson = {
    name?: string;
    age?: number;
    city?: string;
    country?: string;
  };
  ```

  ### Pick

  This is useful when you want to create a new type based on a subset of
  properties from an existing type.

  ```ts
  type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
  };

  type PersonNameAndAge = Pick<Person, "name" | "age">;
  ```

  This is equivalent to:

  ```ts
  type PersonNameAndAge = {
    name: string;
    age: number;
  };
  ```

  ### Record

  Creates a type with keys of the given type and values of the specified type. It
  is a way of shoehorning keys from an existing type with new values.

  Basic syntax:

  ```ts
  Record<Keys, ValueType>;
  ```

  Example:

  ```ts
  type UserRole = "admin" | "user" | "guest";

  // Create a Record type to store user role descriptions
  type UserRoleDescriptions = Record<UserRole, string>;

  // Create an object that implements the UserRoleDescriptions type
  const roleDescriptions: UserRoleDescriptions = {
    admin: "Has full access to the system",
    user: "Can access limited resources",
    guest: "Can view public resources only",
  };
  ```

  Here is a simpler example

  ```ts
  type Person = {
    name: string;
    nationality: string;
  };

  type PersonInstance = Record<string, Person>;
  const thomasInstance: PersonInstance = {
    thomas: {
      name: "Thomas",
      nationality: "British",
    },
  };
  ```

  ### Exclude

  Creates a type by excluding specific properties from the given type.

  ```ts
  type Exclude<T, U> = T extends U ? never : T;

  type KeysWithoutAge = Exclude<keyof Person, "age">;
  ```

  This is equivalent to:

  ```ts
  type KeysWithoutAge = "name" | "city" | "country";
  ```

  ### Extract

  Creates a type by extracting specific properties from the given type. Basically
  the opposite operation to Exclude.

  ```ts
  type Extract<T, U> = T extends U ? T : never;

  type NameKey = Extract<keyof Person, "name">;
  ```

  This is equivalent to:

  ```ts
  type NameKey = "name";
  ```

  ### Non-nullable

  Creates a type by removing null and undefined from the given type.

  ```ts
  type NonNullable<T> = T extends null | undefined ? never : T;

  type NullablePerson = {
    name: string | null;
    age: number | null;
    city: string | null;
    country: string | null;
  };

  type NonNullablePerson = NonNullable<NullablePerson>;
  ```

  Equivalent to:

  ```ts
  type NonNullablePerson = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```

raw_content:
  ---
  tags:
    - typescript
  ---

  # Mapped types in TypeScript

  A mapped type is a way to create new types based on existing ones by
  transforming their properties

  As it works on a single type and typically narrows or limits the properties it
  works in a way that is opposite to a [union]() or [intersection]() type.

  > Mapped types are an example of when `type` is preferable to `interface`, since
  > you cannot generate mapped types as interfaces, although you may generate a
  > mapped type from an interface.

  For the demonstrations we will use the following type as the example that we
  will map from:

  ```ts
  type Person = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```

  ## Main varieties of mapped types

  ### Read only

  Creates a type with all properties of the given type set to `readonly`:

  ```ts
  type Readonly<T> = {
    readonly [P in keyof T]: T[P];
  };

  type ReadonlyPerson = Readonly<Person>;
  ```

  This is equivalent to:

  ```ts
  type ReadonlyPerson = {
    readonly name: string;
    readonly age: number;
    readonly city: string;
    readonly country: string;
  };
  ```

  ### Partial

  Creates a type with all properties of the given type set to optional.

  ```ts
  type Partial<T> = {
    [P in keyof T]?: T[P];
  };

  type PartialPerson = Partial<Person>;
  ```

  This is equivalent to:

  ```ts
  type PartialPerson = {
    name?: string;
    age?: number;
    city?: string;
    country?: string;
  };
  ```

  ### Pick

  This is useful when you want to create a new type based on a subset of
  properties from an existing type.

  ```ts
  type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
  };

  type PersonNameAndAge = Pick<Person, "name" | "age">;
  ```

  This is equivalent to:

  ```ts
  type PersonNameAndAge = {
    name: string;
    age: number;
  };
  ```

  ### Record

  Creates a type with keys of the given type and values of the specified type. It
  is a way of shoehorning keys from an existing type with new values.

  Basic syntax:

  ```ts
  Record<Keys, ValueType>;
  ```

  Example:

  ```ts
  type UserRole = "admin" | "user" | "guest";

  // Create a Record type to store user role descriptions
  type UserRoleDescriptions = Record<UserRole, string>;

  // Create an object that implements the UserRoleDescriptions type
  const roleDescriptions: UserRoleDescriptions = {
    admin: "Has full access to the system",
    user: "Can access limited resources",
    guest: "Can view public resources only",
  };
  ```

  Here is a simpler example

  ```ts
  type Person = {
    name: string;
    nationality: string;
  };

  type PersonInstance = Record<string, Person>;
  const thomasInstance: PersonInstance = {
    thomas: {
      name: "Thomas",
      nationality: "British",
    },
  };
  ```

  ### Exclude

  Creates a type by excluding specific properties from the given type.

  ```ts
  type Exclude<T, U> = T extends U ? never : T;

  type KeysWithoutAge = Exclude<keyof Person, "age">;
  ```

  This is equivalent to:

  ```ts
  type KeysWithoutAge = "name" | "city" | "country";
  ```

  ### Extract

  Creates a type by extracting specific properties from the given type. Basically
  the opposite operation to Exclude.

  ```ts
  type Extract<T, U> = T extends U ? T : never;

  type NameKey = Extract<keyof Person, "name">;
  ```

  This is equivalent to:

  ```ts
  type NameKey = "name";
  ```

  ### Non-nullable

  Creates a type by removing null and undefined from the given type.

  ```ts
  type NonNullable<T> = T extends null | undefined ? never : T;

  type NullablePerson = {
    name: string | null;
    age: number | null;
    city: string | null;
    country: string | null;
  };

  type NonNullablePerson = NonNullable<NullablePerson>;
  ```

  Equivalent to:

  ```ts
  type NonNullablePerson = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```


path: zk/Maps_and_sets.md
sortable_path: zkMaps_and_sets.md
title: Maps and Sets
checksum: 391de8d7c7b97236ff25a0ce78fc54bd629b9d1010dd5c1ed91b6de808339bac
created: 2024-03-30 09:03:20.176351677+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript"]}
lead:
  > Both `Map` and `Set` are new data structures introduced with ES6. They combine
  > the properties of the pre-existing data structures: arrays and objects and
  > offer advantages in certain non-typical use cases.

body:
  > Both `Map` and `Set` are new data structures introduced with ES6. They combine
  > the properties of the pre-existing data structures: arrays and objects and
  > offer advantages in certain non-typical use cases.

  ## Maps

  ### Concepts

  A map (not to be confused with the array map method) introduces something like
  the associative array model to JavaScript. Prior to ES6 the closest thing JS had
  to this was an object.

  Maps are like objects in that their structure consists in key/map pairs but like
  arrays in that the order in which the entries are placed is retained and can be
  referred to throughout the lifecycle of the Map. Also, the length of the Map is
  a known and easily returnable quantity, with objects we have to do some
  wrangling with `Object.keys` and `Object.entries` to retrieve meta data about
  the object itself. Moreover, with an object the order in which the keys are
  stored is not recorded.

  At the same time Maps are different from arrays because the index of an array
  element is not unique, it will always be a number of the form `[0], [1], [2]...`
  .

  Finally, the key can be any data type not just a string. This is an important
  characteristic of maps because although you can use numbers or booleans as the
  keys in an object, they are not actually numbers, they are converted to strings
  at execution. With Maps, whatever data type you assign as a key, retains its
  type.

  Maps

  - Have key/value pair structure
  - Have length property
  - Order of entries and insertions is maintained
  - Can use any data type as a key
  - Disallow duplicate keys

  ### Creating a Map and adding entries

  ```jsx
  const map = new Map();

  // We then pass in key/value pairs using `set`:
  map.set("firstName", "Luke");
  map.set("lastName", "Skywalker");
  map.set("occupation", "Jedi Knight");
  ```

  There is an alternative method for defining a map:

  ```jsx
  const map = new Map([
    ["firstName", "Luke"],
    ["lastName", "Skywalker"],
    ["occupation", "Jedi Knight"],
  ]);
  ```

  Internally, this data will be represented as follows:

  ```
  Map(3)
  0: {"firstName" => "Luke"}
  1: {"lastName" => "Skywalker"}
  2: {"occupation" => "Jedi Knight"}
  ```

  ### Working with map elements

  ```jsx
  // check for the existence of an item in a map by searching for existence of key
  map.has("firstName"); // return boolean

  // return all keys in a Map as iterator
  map.keys(); // MapIterator {'firstName', 'lastName', 'occupation'}

  // return all values as iterator
  map.values();

  // return array of key/value pairs
  map.entries(); // {"firstName" => "Luke", "lastName" => "Skywalker", "occupation" => "Jedi Knight"}
  // retrieve an item from a Map
  map.get("lastName"); // Skywalker

  // delete an item from a map by key, returns boolean as confirmation
  map.delete("occupation"); // true

  //return the length of a map (just like array.length)
  map.size; // 3
  // clear all values
  map.clear();
  ```

  ### Looping

  ```jsx
  // Map has its own .forEach method, like with arrays. Hewre they are side by side

  // Map
  Map.prototype.forEach((value, key, map) = () => {})

  // Array
  Array.prototype.forEach((item, index, array) = () => {})

  // You can also use a for of loop
  // Destructure the key and value out of the Map item
  for (const [key, value] of map) {
    // Log the keys and values of the Map with for...of
    console.log(`${key}: ${value}`)
  }
  ```

  ### Interfacing between Maps and other reference types

  Given that we can create a map with both object and array-style syntax, its easy
  to convert maps to and from the more established data structures.

  It's easy to convert an object to a map:

  ```jsx
  const luke = {
    firstName: "Luke",
    lastName: "Skywalker",
    occupation: "Jedi Knight",
  };

  const map = new Map(Object.entries(luke));
  ```

  And a map to an object:

  ```jsx
  const obj = Object.fromEntries(map);
  ```

  A map to an array:

  ```jsx
  const arr = Array.from(map);
  ```

  We saw earlier that a Map is a multidimensional array, so any multidimensional
  array can be used in the Map constructor invocation to turn it into a Map.

  ## Sets

  A set is special kind of array, an atypical supplement to arrays. It is
  basically an array of unique values. If you try to add a duplicate entry to a
  set it will be ignored.

  ```jsx
  const set = new Set();
  set.add("Beethoven");
  set.add("Bach");

  // Or:
  const set = new Set(["Beethoven", "Bach"]);

  // Convert array to set (note, this will remove duplicates on initialisation)
  const arr = [...set];
  ```

  We can use `keys()`, `values()`, and `entries()` with sets but obviously, `keys`
  and `values` will be identical.

raw_content:
  ---
  tags: [javascript]
  ---

  # Maps and Sets

  > Both `Map` and `Set` are new data structures introduced with ES6. They combine
  > the properties of the pre-existing data structures: arrays and objects and
  > offer advantages in certain non-typical use cases.

  ## Maps

  ### Concepts

  A map (not to be confused with the array map method) introduces something like
  the associative array model to JavaScript. Prior to ES6 the closest thing JS had
  to this was an object.

  Maps are like objects in that their structure consists in key/map pairs but like
  arrays in that the order in which the entries are placed is retained and can be
  referred to throughout the lifecycle of the Map. Also, the length of the Map is
  a known and easily returnable quantity, with objects we have to do some
  wrangling with `Object.keys` and `Object.entries` to retrieve meta data about
  the object itself. Moreover, with an object the order in which the keys are
  stored is not recorded.

  At the same time Maps are different from arrays because the index of an array
  element is not unique, it will always be a number of the form `[0], [1], [2]...`
  .

  Finally, the key can be any data type not just a string. This is an important
  characteristic of maps because although you can use numbers or booleans as the
  keys in an object, they are not actually numbers, they are converted to strings
  at execution. With Maps, whatever data type you assign as a key, retains its
  type.

  Maps

  - Have key/value pair structure
  - Have length property
  - Order of entries and insertions is maintained
  - Can use any data type as a key
  - Disallow duplicate keys

  ### Creating a Map and adding entries

  ```jsx
  const map = new Map();

  // We then pass in key/value pairs using `set`:
  map.set("firstName", "Luke");
  map.set("lastName", "Skywalker");
  map.set("occupation", "Jedi Knight");
  ```

  There is an alternative method for defining a map:

  ```jsx
  const map = new Map([
    ["firstName", "Luke"],
    ["lastName", "Skywalker"],
    ["occupation", "Jedi Knight"],
  ]);
  ```

  Internally, this data will be represented as follows:

  ```
  Map(3)
  0: {"firstName" => "Luke"}
  1: {"lastName" => "Skywalker"}
  2: {"occupation" => "Jedi Knight"}
  ```

  ### Working with map elements

  ```jsx
  // check for the existence of an item in a map by searching for existence of key
  map.has("firstName"); // return boolean

  // return all keys in a Map as iterator
  map.keys(); // MapIterator {'firstName', 'lastName', 'occupation'}

  // return all values as iterator
  map.values();

  // return array of key/value pairs
  map.entries(); // {"firstName" => "Luke", "lastName" => "Skywalker", "occupation" => "Jedi Knight"}
  // retrieve an item from a Map
  map.get("lastName"); // Skywalker

  // delete an item from a map by key, returns boolean as confirmation
  map.delete("occupation"); // true

  //return the length of a map (just like array.length)
  map.size; // 3
  // clear all values
  map.clear();
  ```

  ### Looping

  ```jsx
  // Map has its own .forEach method, like with arrays. Hewre they are side by side

  // Map
  Map.prototype.forEach((value, key, map) = () => {})

  // Array
  Array.prototype.forEach((item, index, array) = () => {})

  // You can also use a for of loop
  // Destructure the key and value out of the Map item
  for (const [key, value] of map) {
    // Log the keys and values of the Map with for...of
    console.log(`${key}: ${value}`)
  }
  ```

  ### Interfacing between Maps and other reference types

  Given that we can create a map with both object and array-style syntax, its easy
  to convert maps to and from the more established data structures.

  It's easy to convert an object to a map:

  ```jsx
  const luke = {
    firstName: "Luke",
    lastName: "Skywalker",
    occupation: "Jedi Knight",
  };

  const map = new Map(Object.entries(luke));
  ```

  And a map to an object:

  ```jsx
  const obj = Object.fromEntries(map);
  ```

  A map to an array:

  ```jsx
  const arr = Array.from(map);
  ```

  We saw earlier that a Map is a multidimensional array, so any multidimensional
  array can be used in the Map constructor invocation to turn it into a Map.

  ## Sets

  A set is special kind of array, an atypical supplement to arrays. It is
  basically an array of unique values. If you try to add a duplicate entry to a
  set it will be ignored.

  ```jsx
  const set = new Set();
  set.add("Beethoven");
  set.add("Bach");

  // Or:
  const set = new Set(["Beethoven", "Bach"]);

  // Convert array to set (note, this will remove duplicates on initialisation)
  const arr = [...set];
  ```

  We can use `keys()`, `values()`, and `entries()` with sets but obviously, `keys`
  and `values` will be identical.


path: zk/Match_statements_in_Python.md
sortable_path: zkMatch_statements_in_Python.md
title: Match statements in Python
lead: > A `match` statement is the equivalent of a switch or case statement in Python
checksum: 4eca8abdc2e26149bf6e163394aa1e01aaef771d47d653884dac8ec213313c40
created: 2024-03-30 09:03:20.181893761+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  > A `match` statement is the equivalent of a switch or case statement in Python

  ```python
  command = input("What are you doing next? ")

  match command:
      case "quit":
          print("Goodbye!")
      case "look":
          print("Looking out")
      case "up" | "down":
          print("up or down")
      case _:
          print("The default")

  """
  What are you doing next? up
  up or down
  """

  match command.split():
      case ["go", "left"]:
          print("go left")
      case ["go", ("fast" | "slow")]:
          print("go fast or slow")

  point = (3, 3)
  match point:
      case (x, y) if x == y:
          print(f"The point is located on the diagonal Y=X at {x}.")
      case (x, y):
          print(f"Point is not on the diagonal.")
  """
  The point is located on the diagonal Y=X at 3.
  """
  ```

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

  # Match statements in Python

  > A `match` statement is the equivalent of a switch or case statement in Python

  ```python
  command = input("What are you doing next? ")

  match command:
      case "quit":
          print("Goodbye!")
      case "look":
          print("Looking out")
      case "up" | "down":
          print("up or down")
      case _:
          print("The default")

  """
  What are you doing next? up
  up or down
  """

  match command.split():
      case ["go", "left"]:
          print("go left")
      case ["go", ("fast" | "slow")]:
          print("go fast or slow")

  point = (3, 3)
  match point:
      case (x, y) if x == y:
          print(f"The point is located on the diagonal Y=X at {x}.")
      case (x, y):
          print(f"Point is not on the diagonal.")
  """
  The point is located on the diagonal Y=X at 3.
  """
  ```


path: zk/Matter_and_atoms.md
sortable_path: zkMatter_and_atoms.md
title: Matter and atoms
lead: ## Matter, elements, compounds and mixtures
checksum: 3f5628d93b77e4acadfd3a283c09d933482db0d3e12f6dda0d3487836d3b4174
created: 2024-03-30 09:03:20.185694027+00:00
modified: 2024-05-09 06:45:47.437065198+00:00
metadata: {"tags":["physics"]}
body:
  ## Matter, elements, compounds and mixtures

  - Matter is anything that occupies space and has weight. It may be found in any
    one of three states: liquid, solid, or gas.
  - Matter can be classed in terms of **elements**, **compounds** and
    **mixtures**.
    - An element is a substance that cannot be reduced to a simpler substance by
      chemical means.
    - The chemical combination of two or more elements is a compound. Compounds
      _can_ be separated or reduced by chemical means but they cannot be
      physically separated.
    - A _physical_ (note not _chemical_) combination of elements and compounds is
      a mixture.
    - For example "air" is a mixture which is made up of oxygen, nitrogen, carbon
      dioxide, and other gases.

  ## Atoms and molecules

  - Elements are made up of **atoms**.
  - A **molecule** is a type of compound where the atoms of two types of elements
    have joined together. For example sodium and chlorine are two elements that
    can join to form sodium chloride. We call this **chemical bonding**.

  ## Atomic particles
  ![](/img/atom-diagram.svg)



  - There are three particles comprising atoms: **protons**, **neutrons** and
    **electrons**.

  - Protons and neutrons comprise the nucleus of the atom. A proton is positively
    charged. A neutron has no charge.

  - Electrons orbit around the nucleus in concentric rings. Electrons are
    negatively charged. Because of this protons and electrons attract each other.

  ### Atomic number

  - The number of protons in the nucleus is called the element's **atomic**
    number.
  - We use the atomic number of elements to distinguish one element from another.

  ### Atomic weight

  - The atomic weight of an atom is its mass.
  - The mass is determined by the number of protons and neutrons in the nucleus.
  - An electron $\frac{1}{1845}$ the mass of a proton. Its mass is so neglible
    that it makes no significant difference to the atomic weight.

raw_content:
  ---
  tags: [physics]
  ---

  # Matter and atoms

  ## Matter, elements, compounds and mixtures

  - Matter is anything that occupies space and has weight. It may be found in any
    one of three states: liquid, solid, or gas.
  - Matter can be classed in terms of **elements**, **compounds** and
    **mixtures**.
    - An element is a substance that cannot be reduced to a simpler substance by
      chemical means.
    - The chemical combination of two or more elements is a compound. Compounds
      _can_ be separated or reduced by chemical means but they cannot be
      physically separated.
    - A _physical_ (note not _chemical_) combination of elements and compounds is
      a mixture.
    - For example "air" is a mixture which is made up of oxygen, nitrogen, carbon
      dioxide, and other gases.

  ## Atoms and molecules

  - Elements are made up of **atoms**.
  - A **molecule** is a type of compound where the atoms of two types of elements
    have joined together. For example sodium and chlorine are two elements that
    can join to form sodium chloride. We call this **chemical bonding**.

  ## Atomic particles
  ![](/img/atom-diagram.svg)



  - There are three particles comprising atoms: **protons**, **neutrons** and
    **electrons**.

  - Protons and neutrons comprise the nucleus of the atom. A proton is positively
    charged. A neutron has no charge.

  - Electrons orbit around the nucleus in concentric rings. Electrons are
    negatively charged. Because of this protons and electrons attract each other.

  ### Atomic number

  - The number of protons in the nucleus is called the element's **atomic**
    number.
  - We use the atomic number of elements to distinguish one element from another.

  ### Atomic weight

  - The atomic weight of an atom is its mass.
  - The mass is determined by the number of protons and neutrons in the nucleus.
  - An electron $\frac{1}{1845}$ the mass of a proton. Its mass is so neglible
    that it makes no significant difference to the atomic weight.


path: zk/Memory.md
sortable_path: zkMemory.md
title: Memory
lead: ## Why do we need memory?
checksum: 970391d3e1e1ada268e6add602bf3aea5608e73902d1c3aca2bfb344b2829180
created: 2024-03-30 09:03:20.199851706+00:00
modified: 2024-06-15 10:24:26.003478772+00:00
metadata: {"tags":["memory","computer-architecture"]}
body:
  ## Why do we need memory?

  > A CPU is just an operator on memory. It reads its instructions and data from
  > the memory and writes back out to the memory. (Ward 2021)

  When a [CPU](CPU_architecture.md) executes a program,
  it needs a place to store the program's **instructions** and **related data**.
  This is the role of memory.

  ## What is memory?

  The data that comprises a program is a series of bits. The basic unit of memory
  storage is a **memory cell**: a circuit that can store a single bit.

  ## Memory types

  There are two types of memory: SRAM and DRAM. Both types of RAM memory are
  _volatile_ : the memory is only retained whilst the computer has a power supply
  and is wiped when the computer is rebooted. This contrasts with the memory of
  the harddisk which is non-volatile and is retained after a reboot.

  Programs that are executing are loaded into memory because the chips that
  comprise memory can read and store data much faster than the harddisk. It would
  be possible to run a program from the harddisk but it would be 500 - 1000 times
  slower than memory.

  #### DRAM

  DRAM uses capacitors to create the memory cell:

  > a **capacitor** is an electronic component that stores electrical energy in an
  > electrical field. A device which can accumulate and release electrical charge.

  In a DRAM cell, each bit of data is stored as a charge in a capacitor. The
  presence of charge represents a '1' bit and the absence of charge represents a
  '0' bit. Each of these cells is paired with a
  [transistor](Transistors.md) that
  controls the reading and writing of data.

  However capacitors lose
  [charge](Current.md) over time due
  to leaks. As a result DRAM is memory that needs to be refreshed (recharged)
  frequently. For this reason and because it only uses one transistor and
  capacitor per bit, DRAM is the less expensive form of volatile memory.

  #### SRAM

  SRAM (Static Random Access Memory) is also volatile memory but its electronical
  implementation is different. Unlike DRAM it doesn't use capacitors. Consequently
  the transistors do not leak or need to be refreshed, hence why SRAM is _static_
  and DRAM is _dynamic_.

  SRAM uses [flip flops](Flip_flops.md)
  to store the bits. It also uses multiple transistors per bit. This makes it
  faster than DRAM but more expensive. DRAM is at least ten times slower than
  SRAM.

  ## The role of memory in computation

  The following steps outline the way in which memory interacts with the processor
  during computational cycles, once the
  [bootstrapping](Boot_process.md) process has completed and
  the OS kernel is itself loaded into memory.

  1. A file is loaded from the harddisk into memory.
  2. The instruction at the first address is sent to the CPU, travelling accross
     the data bus part of the [system bus](Bus.md).
  3. The CPU processes this instruction and then sends a request accross the
     address bus part of the system bus for the next instruction to the memory
     controller within the
     [chipset](Chipset_and_controllers.md).
  4. The chipset finds where this instruction is stored within the
     [DRAM](Memory.md#dram) and issues a request to
     have it read out and send to the CPU over the data bus.

  > This is a simplified account; it is not the case that only single requests are
  > passed back and forth. This would be inefficient and time-wasting. The kernel
  > sends to the CPU not just the first instruction in the requested file but also
  > a number of instructions that immediately follow it.

  ![](/img/memory-flow.svg)

  Every part of the above process - the journey accross the bus, the lookup in the
  controller, the operations on the DRAM, the journey back accross the bus - takes
  multiple CPU clock cycles.

  ## CPU register and cache memory

  As partly indicated in the diagram above, the CPU has its own memory in the form
  of registers and cache memory.

  Registers are a form of memory that are positioned on the same chip as the CPU.
  They are very fast but can only store a small amount of data. They are used to
  store the results of calculations and the addresses of the next instructions to
  be processed.

  The cache is SRAM memory that is separate from the DRAM memory which comprises
  the main memory. It exists in order to boost perfomance when executing the
  read/request cycles of the steps detailed above.

  There are two types of cache memory:

  - L1 cache
    - Situated on the CPU chip itself
    - Fastest to access but stores less
  - L2 cache
    - Situated outside of the CPU on its own chip
    - Slower to access than L1 but can store more data

  The L1 cache is the fastest since the data has less distance to travel when
  moving to and from the CPU. This said, the L2 cache is still very fast when
  compared to the main memory, both because it is SRAM rather than DRAM and
  because it is closer to the processor than the main memory.

  Cache controllers use complex algorithms to determine what should go into the
  cache to facilitate the best performance, but generally they work on the
  principle that what has been previously used by the CPU will be requested again
  soon. If the CPU has just asked for an instruction at memory location 555 it's
  very likely that it will next ask for the one at 556, and after that the one at
  557 and so on. The cache's controller circuits therefore go ahead and fetch
  these from slow DRAM to fast SRAM.

  ## The memory hierarchy

  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

  ![](/img/Memory-Hierarchy.jpg)

  ## Memory addresses

  > Computers assign numeric addresses to bytes of memory and the CPU can read or
  > write to those addresses

  We can think of the internals of RAM as grids of memory cells.

  Each single-bit cell in the grid can be identified using two dimensional
  coordinates, like in a graph. The coordinates are the location of that cell in
  the grid.

  Handling one bit at a time isn't very efficient so RAM accesses **multiple
  grids** of 1-bit memory cells in parallel. This allows for reads or writes of
  multiple bits at once, such as a whole byte.

  The location of a set of bits in memory is known as a **memory address**.

  ### Demonstration

  Let's imagine we have a computer system that can address up to 64KB of memory
  and our system is byte addressable. This means there are
  $64 \cdot 1024 = 65,536$ bytes of memory because 1KB = 1024 bytes.

  We therefore have 65,536 addresses and each address can store one byte. So our
  addresses go from 0 to 65, 535.

  We now need to consider how many bits we need to uniquely represent an address
  on this system.

  What does this mean? Although there are approximately 64 thousand bytes of
  memory, to refer to each byte we can't just use 1, 2, 3... because computers use
  binary numbers. We need a binary number to refer to a given byte in the the 64KB
  of memory. The question we are asking is: how long does this binary number need
  to be to be able to represent each of the 64 thousand bytes?

  1 bit can represent two addresses: 0 and 1. 2 bits can represent four addresses:
  00, 01, 10, 11. The formula is as follows: number of addresses = $2^n$ where $n$
  is the number of bits.

  We need to reverse this formula to find out how many bits we need to represent a
  given number of addresses. We can do this with a
  [logarithm](Logarithms.md).

  We can reverse the formula as follows: number of bits = $\log_2$(number of
  addresses).

  In our case we have 65,536 addresses so we need $\log_2(65,536)$ bits to
  represent each address. This is approximately 16 bits. Thus a 16 bit memory
  address is needed to address 65, 546 bytes.

  Using memory addresses we end up with tables like the following:

  | Memory address   | Data             |
  | ---------------- | ---------------- |
  | 0000000000000000 | 1010101010101010 |
  | 0000000000000001 | 0010001001001011 |
  | 0000000000000010 | 0010001001001010 |

  This is hard to parse so we can instead use
  [hexadecimal numbers](Hexadecimal_number_system.md)
  to represent the addresses:

  | Memory address (as hex) | Data (as binary) |
  | ----------------------- | ---------------- |
  | 0x0000                  | 1010101010101010 |
  | 0x0001                  | 0010001001001011 |
  | 0x0002                  | 0010001001001010 |

  By itself, the the data is meaningless but we know from
  [binary encoding](Binary_encoding.md) that the
  binary data will correspond to some meaningful data, such as a character or a
  colour, depending on the encoding scheme used. The above table could correspond
  to the characters for 'A', 'B' and 'C' in the ASCII encoding scheme:

  | Memory address (as hex) | Data (as binary) | Data (as ASCII) |
  | ----------------------- | ---------------- | --------------- |
  | 0x0000                  | 1010101010101010 | A               |
  | 0x0001                  | 0010001001001011 | B               |
  | 0x0002                  | 0010001001001010 | C               |

raw_content:
  ---
  tags:
    - memory
    - computer-architecture
  ---

  # Memory

  ## Why do we need memory?

  > A CPU is just an operator on memory. It reads its instructions and data from
  > the memory and writes back out to the memory. (Ward 2021)

  When a [CPU](CPU_architecture.md) executes a program,
  it needs a place to store the program's **instructions** and **related data**.
  This is the role of memory.

  ## What is memory?

  The data that comprises a program is a series of bits. The basic unit of memory
  storage is a **memory cell**: a circuit that can store a single bit.

  ## Memory types

  There are two types of memory: SRAM and DRAM. Both types of RAM memory are
  _volatile_ : the memory is only retained whilst the computer has a power supply
  and is wiped when the computer is rebooted. This contrasts with the memory of
  the harddisk which is non-volatile and is retained after a reboot.

  Programs that are executing are loaded into memory because the chips that
  comprise memory can read and store data much faster than the harddisk. It would
  be possible to run a program from the harddisk but it would be 500 - 1000 times
  slower than memory.

  #### DRAM

  DRAM uses capacitors to create the memory cell:

  > a **capacitor** is an electronic component that stores electrical energy in an
  > electrical field. A device which can accumulate and release electrical charge.

  In a DRAM cell, each bit of data is stored as a charge in a capacitor. The
  presence of charge represents a '1' bit and the absence of charge represents a
  '0' bit. Each of these cells is paired with a
  [transistor](Transistors.md) that
  controls the reading and writing of data.

  However capacitors lose
  [charge](Current.md) over time due
  to leaks. As a result DRAM is memory that needs to be refreshed (recharged)
  frequently. For this reason and because it only uses one transistor and
  capacitor per bit, DRAM is the less expensive form of volatile memory.

  #### SRAM

  SRAM (Static Random Access Memory) is also volatile memory but its electronical
  implementation is different. Unlike DRAM it doesn't use capacitors. Consequently
  the transistors do not leak or need to be refreshed, hence why SRAM is _static_
  and DRAM is _dynamic_.

  SRAM uses [flip flops](Flip_flops.md)
  to store the bits. It also uses multiple transistors per bit. This makes it
  faster than DRAM but more expensive. DRAM is at least ten times slower than
  SRAM.

  ## The role of memory in computation

  The following steps outline the way in which memory interacts with the processor
  during computational cycles, once the
  [bootstrapping](Boot_process.md) process has completed and
  the OS kernel is itself loaded into memory.

  1. A file is loaded from the harddisk into memory.
  2. The instruction at the first address is sent to the CPU, travelling accross
     the data bus part of the [system bus](Bus.md).
  3. The CPU processes this instruction and then sends a request accross the
     address bus part of the system bus for the next instruction to the memory
     controller within the
     [chipset](Chipset_and_controllers.md).
  4. The chipset finds where this instruction is stored within the
     [DRAM](Memory.md#dram) and issues a request to
     have it read out and send to the CPU over the data bus.

  > This is a simplified account; it is not the case that only single requests are
  > passed back and forth. This would be inefficient and time-wasting. The kernel
  > sends to the CPU not just the first instruction in the requested file but also
  > a number of instructions that immediately follow it.

  ![](/img/memory-flow.svg)

  Every part of the above process - the journey accross the bus, the lookup in the
  controller, the operations on the DRAM, the journey back accross the bus - takes
  multiple CPU clock cycles.

  ## CPU register and cache memory

  As partly indicated in the diagram above, the CPU has its own memory in the form
  of registers and cache memory.

  Registers are a form of memory that are positioned on the same chip as the CPU.
  They are very fast but can only store a small amount of data. They are used to
  store the results of calculations and the addresses of the next instructions to
  be processed.

  The cache is SRAM memory that is separate from the DRAM memory which comprises
  the main memory. It exists in order to boost perfomance when executing the
  read/request cycles of the steps detailed above.

  There are two types of cache memory:

  - L1 cache
    - Situated on the CPU chip itself
    - Fastest to access but stores less
  - L2 cache
    - Situated outside of the CPU on its own chip
    - Slower to access than L1 but can store more data

  The L1 cache is the fastest since the data has less distance to travel when
  moving to and from the CPU. This said, the L2 cache is still very fast when
  compared to the main memory, both because it is SRAM rather than DRAM and
  because it is closer to the processor than the main memory.

  Cache controllers use complex algorithms to determine what should go into the
  cache to facilitate the best performance, but generally they work on the
  principle that what has been previously used by the CPU will be requested again
  soon. If the CPU has just asked for an instruction at memory location 555 it's
  very likely that it will next ask for the one at 556, and after that the one at
  557 and so on. The cache's controller circuits therefore go ahead and fetch
  these from slow DRAM to fast SRAM.

  ## The memory hierarchy

  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

  ![](/img/Memory-Hierarchy.jpg)

  ## Memory addresses

  > Computers assign numeric addresses to bytes of memory and the CPU can read or
  > write to those addresses

  We can think of the internals of RAM as grids of memory cells.

  Each single-bit cell in the grid can be identified using two dimensional
  coordinates, like in a graph. The coordinates are the location of that cell in
  the grid.

  Handling one bit at a time isn't very efficient so RAM accesses **multiple
  grids** of 1-bit memory cells in parallel. This allows for reads or writes of
  multiple bits at once, such as a whole byte.

  The location of a set of bits in memory is known as a **memory address**.

  ### Demonstration

  Let's imagine we have a computer system that can address up to 64KB of memory
  and our system is byte addressable. This means there are
  $64 \cdot 1024 = 65,536$ bytes of memory because 1KB = 1024 bytes.

  We therefore have 65,536 addresses and each address can store one byte. So our
  addresses go from 0 to 65, 535.

  We now need to consider how many bits we need to uniquely represent an address
  on this system.

  What does this mean? Although there are approximately 64 thousand bytes of
  memory, to refer to each byte we can't just use 1, 2, 3... because computers use
  binary numbers. We need a binary number to refer to a given byte in the the 64KB
  of memory. The question we are asking is: how long does this binary number need
  to be to be able to represent each of the 64 thousand bytes?

  1 bit can represent two addresses: 0 and 1. 2 bits can represent four addresses:
  00, 01, 10, 11. The formula is as follows: number of addresses = $2^n$ where $n$
  is the number of bits.

  We need to reverse this formula to find out how many bits we need to represent a
  given number of addresses. We can do this with a
  [logarithm](Logarithms.md).

  We can reverse the formula as follows: number of bits = $\log_2$(number of
  addresses).

  In our case we have 65,536 addresses so we need $\log_2(65,536)$ bits to
  represent each address. This is approximately 16 bits. Thus a 16 bit memory
  address is needed to address 65, 546 bytes.

  Using memory addresses we end up with tables like the following:

  | Memory address   | Data             |
  | ---------------- | ---------------- |
  | 0000000000000000 | 1010101010101010 |
  | 0000000000000001 | 0010001001001011 |
  | 0000000000000010 | 0010001001001010 |

  This is hard to parse so we can instead use
  [hexadecimal numbers](Hexadecimal_number_system.md)
  to represent the addresses:

  | Memory address (as hex) | Data (as binary) |
  | ----------------------- | ---------------- |
  | 0x0000                  | 1010101010101010 |
  | 0x0001                  | 0010001001001011 |
  | 0x0002                  | 0010001001001010 |

  By itself, the the data is meaningless but we know from
  [binary encoding](Binary_encoding.md) that the
  binary data will correspond to some meaningful data, such as a character or a
  colour, depending on the encoding scheme used. The above table could correspond
  to the characters for 'A', 'B' and 'C' in the ASCII encoding scheme:

  | Memory address (as hex) | Data (as binary) | Data (as ASCII) |
  | ----------------------- | ---------------- | --------------- |
  | 0x0000                  | 1010101010101010 | A               |
  | 0x0001                  | 0010001001001011 | B               |
  | 0x0002                  | 0010001001001010 | C               |


path: zk/Memory_leaks.md
sortable_path: zkMemory_leaks.md
title: Memory leaks
checksum: a4d5c6a8df018b82e31f5ba1746250ef20d36dca5a43b5a7d7e425cf5ab4db22
created: 2024-03-30 09:03:20.234256158+00:00
modified: 2024-04-16 06:25:37.254359454+00:00
metadata: {"tags":["memory"]}
lead:
  A memory leak occurs when you program memory and forget to delete it once you
  are finished. The consequences of memory leak is that it reduces the performance
  of the computer by reducing the amount of available memory. Eventually, in the
  worst case, too much of the available memory may become allocated and all or
  part of the system or device stops working correctly, the application fails, or
  the system slows down vastly.

body:
  A memory leak occurs when you program memory and forget to delete it once you
  are finished. The consequences of memory leak is that it reduces the performance
  of the computer by reducing the amount of available memory. Eventually, in the
  worst case, too much of the available memory may become allocated and all or
  part of the system or device stops working correctly, the application fails, or
  the system slows down vastly.

raw_content:
  ---
  tags: [memory]
  ---

  # Memory leaks

  A memory leak occurs when you program memory and forget to delete it once you
  are finished. The consequences of memory leak is that it reduces the performance
  of the computer by reducing the amount of available memory. Eventually, in the
  worst case, too much of the available memory may become allocated and all or
  part of the system or device stops working correctly, the application fails, or
  the system slows down vastly.


path: zk/Messaging_services.md
sortable_path: zkMessaging_services.md
title: AWS Messaging services
lead: ## SQS
checksum: eb8a75e05ac26b869762b86506c063c553af24181b6b7f0d27f7d451127bc5b8
created: 2024-03-30 09:03:20.238576676+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["AWS"]}
body:
  ## SQS

  > SQS: Simple Queue Service

  SQS is a service that allows you to send, store and receive messages between
  apps and software components built in AWS, with automatic encryption. It helps
  with decoupling and scaling.

  As the name indicates, its operating mode is that of a
  [queue](Queue.md) data structure offering first-in, first-out
  and other queue implementations.

  An example application of this would be to set up an SQS queue that receives
  messages and triggers a lambda whenever a new message is added.

  ## SNS

  > SNS: Simple Notification Service

  Similar to SQS but the focus is on notifications rather than messages, i.e
  events that fire when something specific happens, not just a message-send event.
  It can be used for passing notifications between applications or to persons
  through SMS, text, push notifications and email.

  **SNS pushes messages out to subscribers while SQS stores the messages until
  someone reads them.**

raw_content:
  ---
  tags: [AWS]
  ---

  # AWS Messaging services

  ## SQS

  > SQS: Simple Queue Service

  SQS is a service that allows you to send, store and receive messages between
  apps and software components built in AWS, with automatic encryption. It helps
  with decoupling and scaling.

  As the name indicates, its operating mode is that of a
  [queue](Queue.md) data structure offering first-in, first-out
  and other queue implementations.

  An example application of this would be to set up an SQS queue that receives
  messages and triggers a lambda whenever a new message is added.

  ## SNS

  > SNS: Simple Notification Service

  Similar to SQS but the focus is on notifications rather than messages, i.e
  events that fire when something specific happens, not just a message-send event.
  It can be used for passing notifications between applications or to persons
  through SMS, text, push notifications and email.

  **SNS pushes messages out to subscribers while SQS stores the messages until
  someone reads them.**


path: zk/Middleware.md
sortable_path: zkMiddleware.md
title: Middleware
lead: ## What is middleware?
checksum: 002a76c3aa2e50a04717aa85d4ca9d4c68c985d756e36af5ea4ba11770e17712
created: 2024-03-30 09:03:20.246689953+00:00
modified: 2024-06-15 09:50:24.15986399+00:00
metadata: {"tags":["node-js","middleware"]}
body:
  ## What is middleware?

  - Anything that terminates the `req, res` cycle counts as middleware. It is
    basically anything that acts as an intermediary once the request is received
    but before the resource is sent. A good example would be the
    `app.use(express.json()` or `app.use(bodyParser.json)` functions we call in
    order to be able to parse JSON that is sent from the client.
  - You will most likely have multiple middleware functions running at once. We
    call this intermediary part of the cycle the **request processing pipeline**.
  - Generally all middleware will be added as a property on the Express `app`
    instance with the `app.use(...)` syntax.

  ## Creating custom middleware functions

  ### Basic schema

  ```js
  app.use((req, res, next) => {
    // do some middleware
    next();
  });
  ```

  ### `next`

  The `next` parameter is key, it allows Express to move onto the next middleware
  function once the custom middleware executes. Without it, the request processing
  pipeline will get blocked.

  Middleware functions are basically asynchronous requests and as such they use a
  similar syntax as Promises (e.g `then`) for sequencing processes.

  ### Example of sequence

  ```js
  app.use((req, res, next) => {
    console.log("Do process A...");
    next();
  });

  app.use((req, res, next) => {
    console.log("Do process B...");
    next();
  });
  ```

  > It makes more sense of course to define our middleware within a function and
  > then pass it as an argument to `app.use()`

  ## Including middleware based on environment

  With a full-scale Node application you will typically run three environments:

  - Development
  - Testing
  - Production

  We will not want to run certain types of middleware in all environments. For
  example, it would be costly to run logging in the app's production environment.
  It would make more sense to run this only in development.

  ### Accessing current Node environment

  We can control which middleware we run via the Node envrionment variables:
  `process.env` (see for instance [ports](Ports.md)).

  We could set
  [Morgan](Morgan.md) to run
  only in development with:

  ```js
  if (app.get("env") === "development") {
    app.use(morgan("common"));
    console.log("Morgan enabled");
  }
  ```

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

  # Middleware

  ## What is middleware?

  - Anything that terminates the `req, res` cycle counts as middleware. It is
    basically anything that acts as an intermediary once the request is received
    but before the resource is sent. A good example would be the
    `app.use(express.json()` or `app.use(bodyParser.json)` functions we call in
    order to be able to parse JSON that is sent from the client.
  - You will most likely have multiple middleware functions running at once. We
    call this intermediary part of the cycle the **request processing pipeline**.
  - Generally all middleware will be added as a property on the Express `app`
    instance with the `app.use(...)` syntax.

  ## Creating custom middleware functions

  ### Basic schema

  ```js
  app.use((req, res, next) => {
    // do some middleware
    next();
  });
  ```

  ### `next`

  The `next` parameter is key, it allows Express to move onto the next middleware
  function once the custom middleware executes. Without it, the request processing
  pipeline will get blocked.

  Middleware functions are basically asynchronous requests and as such they use a
  similar syntax as Promises (e.g `then`) for sequencing processes.

  ### Example of sequence

  ```js
  app.use((req, res, next) => {
    console.log("Do process A...");
    next();
  });

  app.use((req, res, next) => {
    console.log("Do process B...");
    next();
  });
  ```

  > It makes more sense of course to define our middleware within a function and
  > then pass it as an argument to `app.use()`

  ## Including middleware based on environment

  With a full-scale Node application you will typically run three environments:

  - Development
  - Testing
  - Production

  We will not want to run certain types of middleware in all environments. For
  example, it would be costly to run logging in the app's production environment.
  It would make more sense to run this only in development.

  ### Accessing current Node environment

  We can control which middleware we run via the Node envrionment variables:
  `process.env` (see for instance [ports](Ports.md)).

  We could set
  [Morgan](Morgan.md) to run
  only in development with:

  ```js
  if (app.get("env") === "development") {
    app.use(morgan("common"));
    console.log("Morgan enabled");
  }
  ```


path: zk/Mixed_and_improper_fractions.md
sortable_path: zkMixed_and_improper_fractions.md
title: Mixed and improper fractions
lead: ## Improper fractions
checksum: 591035861210f541355bffec2836d38c935d09f90152f5328479bf5fca7cd409
created: 2024-03-30 09:03:20.260272834+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","fractions"]}
body:
  ## Improper fractions

  - Proper fraction:
    - The numerator is smaller than the denominator
    - E.g. $\frac{2}{3}$, $-\frac{5}{10}$
  - Improper fraction
    - The numerator is greater than the denominator
    - E.g. $\frac{3}{2}$, $-\frac{5}{4}$

  ## Mixed fractions

  A mixed fraction is part whole number, part fraction. For example:
  $5 \frac{3}{4}$.

  It means, in effect: $5 + \frac{3}{4}$

  ## Converting mixed fractions into improper fractions

  Mixed fractions are hard to calculate with. We need some way to convert them to
  fractions. We can do this by converting them to improper fractions.

  With the example $4 \frac{7}{8}$, we know this means $4 + \frac{7}{8}$. We need
  to express the amount 4 in terms of eighths. It would be 4 lots of $\frac{8}{8}$
  given that 4 is a whole number not a fractional amount. Thus the process would
  be:

  $$
      \frac{8}{8} + \frac{8}{8} + \frac{8}{8} + \frac{8}{8} + \frac{7}{8}
  $$

  But we know that when we
  [add fractions with a common denominator](Add_Subtract_Fractions.md#adding-subracting-fractions-with-common-denominators),
  we only add the numerators, not the denominators. Thus the calculation would
  actually be:

  $$
  \frac{8 + 8 + 8 + 8 + 7}{8} = \frac{39}{8}
  $$

  Addition helps to explain the concepts underlying the procedure but it is more
  efficient to use multiplication.

  The procedure is as follows:

  1. Multiply the whole number part by the denominator
  2. Add the numerator
  3. Place the result over the denominator

  Thus:

  $$
      4 \frac{7}{8} = \frac{(4 \cdot 8) + 7 }{8}
  $$

  ## Converting improper fractions into mixed fractions

  It is quite obvious how to reverse the process and turn an improper fraction
  into a mixed fraction.

  Take $\frac{27}{5}$. We work out how many times the numerator is divisible by
  the denominator and make that the whole number. The remainder is then left as
  the fractional part.

  $$
  \begin{split}
  \frac{27}{5} = 27 \div 5  \\
  = 5 r 2 \\
  = 5 \frac{2}{5}
  \end{split}
  $$

  ## Multiplying and dividing by mixed fractions

  Now that we know how to convert mixed fractions into improper fractions, it is
  straight forward to multiply and divide with them. We convert the mixed fraction
  into an improper fraction and then divide and multiply as we would with a proper
  fraction.

  ### Demonstration of multiplication

  Calculate $-2\frac{1}{12} \cdot 2 \frac{4}{5}$:

  1. First convert each mixed fraction into an improper fraction:
     $$
       \begin{split}
       -2\frac{1}{12} = -2 \cdot -12  \\
       = 24 + 1 \\
       = - \frac{25}{12}
       \end{split}
     $$

  $$
  \begin{split}
    2 \frac{4}{5} =2 \cdot 5 \\
    = 10 + 4 \\
    = \frac{14}{5}
  \end{split}
  $$

  2. Then carry out the multiplication
     [using factorization](Multiplying_fractions.md#prime-factorisation-in-place):

     $$
       \begin{split}
       - \frac{25}{12} \cdot \frac{14}{5} = \\
      - \frac{(5 \cdot 5) \cdot (7 \cdot 2)}{(3 \cdot 2 \cdot 2) \cdot (5)} = - \frac{5 \cdot 7 }{2 \cdot 3} \\
       \end{split}
     $$

  3. Then simplify:

     $$
       - \frac{35}{6}
     $$

  4. Finally, convert back into a mixed fraction:

  $$
    \begin{split}
    - \frac{35}{6} = -35 \div 6 \\
    -  5 r 5 = \\
    - 5 \frac{5}{6}
    \end{split}
  $$

  ## Demonstration of division

  Again we convert the mixed fraction into an improper fraction and then follow
  the requisite rule, in the case of division this is to
  [invert and multiply](Dividing_fractions.md#formal-specification-of-how-to-divide-fractions').

  Calculate $-4 \frac{4}{5} \div 5 \frac{3}{5}$.

  1. Convert to improper fraction:
     $$
        \begin{split}
          -4 \cdot 5 = \\
      -20
     \end{split}
     $$

  ## Adding and subtracting mixed fractions

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

  # Mixed and improper fractions

  ## Improper fractions

  - Proper fraction:
    - The numerator is smaller than the denominator
    - E.g. $\frac{2}{3}$, $-\frac{5}{10}$
  - Improper fraction
    - The numerator is greater than the denominator
    - E.g. $\frac{3}{2}$, $-\frac{5}{4}$

  ## Mixed fractions

  A mixed fraction is part whole number, part fraction. For example:
  $5 \frac{3}{4}$.

  It means, in effect: $5 + \frac{3}{4}$

  ## Converting mixed fractions into improper fractions

  Mixed fractions are hard to calculate with. We need some way to convert them to
  fractions. We can do this by converting them to improper fractions.

  With the example $4 \frac{7}{8}$, we know this means $4 + \frac{7}{8}$. We need
  to express the amount 4 in terms of eighths. It would be 4 lots of $\frac{8}{8}$
  given that 4 is a whole number not a fractional amount. Thus the process would
  be:

  $$
      \frac{8}{8} + \frac{8}{8} + \frac{8}{8} + \frac{8}{8} + \frac{7}{8}
  $$

  But we know that when we
  [add fractions with a common denominator](Add_Subtract_Fractions.md#adding-subracting-fractions-with-common-denominators),
  we only add the numerators, not the denominators. Thus the calculation would
  actually be:

  $$
  \frac{8 + 8 + 8 + 8 + 7}{8} = \frac{39}{8}
  $$

  Addition helps to explain the concepts underlying the procedure but it is more
  efficient to use multiplication.

  The procedure is as follows:

  1. Multiply the whole number part by the denominator
  2. Add the numerator
  3. Place the result over the denominator

  Thus:

  $$
      4 \frac{7}{8} = \frac{(4 \cdot 8) + 7 }{8}
  $$

  ## Converting improper fractions into mixed fractions

  It is quite obvious how to reverse the process and turn an improper fraction
  into a mixed fraction.

  Take $\frac{27}{5}$. We work out how many times the numerator is divisible by
  the denominator and make that the whole number. The remainder is then left as
  the fractional part.

  $$
  \begin{split}
  \frac{27}{5} = 27 \div 5  \\
  = 5 r 2 \\
  = 5 \frac{2}{5}
  \end{split}
  $$

  ## Multiplying and dividing by mixed fractions

  Now that we know how to convert mixed fractions into improper fractions, it is
  straight forward to multiply and divide with them. We convert the mixed fraction
  into an improper fraction and then divide and multiply as we would with a proper
  fraction.

  ### Demonstration of multiplication

  Calculate $-2\frac{1}{12} \cdot 2 \frac{4}{5}$:

  1. First convert each mixed fraction into an improper fraction:
     $$
       \begin{split}
       -2\frac{1}{12} = -2 \cdot -12  \\
       = 24 + 1 \\
       = - \frac{25}{12}
       \end{split}
     $$

  $$
  \begin{split}
    2 \frac{4}{5} =2 \cdot 5 \\
    = 10 + 4 \\
    = \frac{14}{5}
  \end{split}
  $$

  2. Then carry out the multiplication
     [using factorization](Multiplying_fractions.md#prime-factorisation-in-place):

     $$
       \begin{split}
       - \frac{25}{12} \cdot \frac{14}{5} = \\
      - \frac{(5 \cdot 5) \cdot (7 \cdot 2)}{(3 \cdot 2 \cdot 2) \cdot (5)} = - \frac{5 \cdot 7 }{2 \cdot 3} \\
       \end{split}
     $$

  3. Then simplify:

     $$
       - \frac{35}{6}
     $$

  4. Finally, convert back into a mixed fraction:

  $$
    \begin{split}
    - \frac{35}{6} = -35 \div 6 \\
    -  5 r 5 = \\
    - 5 \frac{5}{6}
    \end{split}
  $$

  ## Demonstration of division

  Again we convert the mixed fraction into an improper fraction and then follow
  the requisite rule, in the case of division this is to
  [invert and multiply](Dividing_fractions.md#formal-specification-of-how-to-divide-fractions').

  Calculate $-4 \frac{4}{5} \div 5 \frac{3}{5}$.

  1. Convert to improper fraction:
     $$
        \begin{split}
          -4 \cdot 5 = \\
      -20
     \end{split}
     $$

  ## Adding and subtracting mixed fractions


path: zk/Module_pattern.md
sortable_path: zkModule_pattern.md
title: Module pattern
lead: > Come back and compare this with learning from Node.js
checksum: f4959dd608fbcc87ccb71faa82ff8328afae0c2dab725672ea3b929835d50d48
created: 2024-03-30 09:03:20.283471991+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["OOP"]}
body:
  > Come back and compare this with learning from Node.js

  With the module design pattern we create encapsulation: the variables and
  functions (as methods) are kept private inside the module and cannot be
  overwritten. This design pattern is familiar from Node.js development: every
  package you import into your project and download from NPM is a module.

  Generally you will create the module as a class, import it and then instantiate
  a new instance. However for private development, you could just as well use an
  object and duplicate it with `Object.create`.

  ## Example

  Here is an example of a simple module that returns the age of a person.

  ```js
  export default class Age {
    constructor(name, birthYear) {
      this.name = name;
      this.birthYear = birthYear;
    }
    currentYear() {
      return new Date().getFullYear();
    }
    get age() {
      return this.currentYear() - this.birthYear;
    }
  }
  ```

  ```js
  const martha = new Age("Martha", 1997);
  console.log(martha.age); // 24
  ```

  ## Controlling access

  In the example above, `aValue` could be edited in instantiations of the class.
  Given that modules should not be overwrittable, you could make it a private
  property on the class. The benefit of getters and setters is that they dictate
  what can be modified and retrieved from the module. So if you use `get` and
  `set` you can prevent overwrites.

  ## Object modules

  If you want to use an object instead of a class, you have to take greater care
  to ensure that the objects are not overwritable. **Also you cannot use the `#`
  modifier to make properties private.**

  - Use getters and setters for updating and retrieving values
  - Use `Object.seal` to prevent changes to the parent object
  - Instead of using getters and setters, for individual properties you can set
    `writable` to be `false` for properties that you don't want changed

  There are examples of each in the following:

  ```jsx
  export const age = {
    name: "",
    birthYear: new Number(),
    currentYear() {
      return new Date().getFullYear();
    },
    get age() {
      return this.currentYear() - this.birthYear;
    },
  };

  Object.seal(age);
  Object.defineProperty(age, "aValue", {
    value: 6,
    writable: false,
  });
  ```

raw_content:
  ---
  tags: [OOP]
  ---

  # Module pattern

  > Come back and compare this with learning from Node.js

  With the module design pattern we create encapsulation: the variables and
  functions (as methods) are kept private inside the module and cannot be
  overwritten. This design pattern is familiar from Node.js development: every
  package you import into your project and download from NPM is a module.

  Generally you will create the module as a class, import it and then instantiate
  a new instance. However for private development, you could just as well use an
  object and duplicate it with `Object.create`.

  ## Example

  Here is an example of a simple module that returns the age of a person.

  ```js
  export default class Age {
    constructor(name, birthYear) {
      this.name = name;
      this.birthYear = birthYear;
    }
    currentYear() {
      return new Date().getFullYear();
    }
    get age() {
      return this.currentYear() - this.birthYear;
    }
  }
  ```

  ```js
  const martha = new Age("Martha", 1997);
  console.log(martha.age); // 24
  ```

  ## Controlling access

  In the example above, `aValue` could be edited in instantiations of the class.
  Given that modules should not be overwrittable, you could make it a private
  property on the class. The benefit of getters and setters is that they dictate
  what can be modified and retrieved from the module. So if you use `get` and
  `set` you can prevent overwrites.

  ## Object modules

  If you want to use an object instead of a class, you have to take greater care
  to ensure that the objects are not overwritable. **Also you cannot use the `#`
  modifier to make properties private.**

  - Use getters and setters for updating and retrieving values
  - Use `Object.seal` to prevent changes to the parent object
  - Instead of using getters and setters, for individual properties you can set
    `writable` to be `false` for properties that you don't want changed

  There are examples of each in the following:

  ```jsx
  export const age = {
    name: "",
    birthYear: new Number(),
    currentYear() {
      return new Date().getFullYear();
    },
    get age() {
      return this.currentYear() - this.birthYear;
    },
  };

  Object.seal(age);
  Object.defineProperty(age, "aValue", {
    value: 6,
    writable: false,
  });
  ```


path: zk/Module_wrapping_at_runtime.md
sortable_path: zkModule_wrapping_at_runtime.md
title: The Module Wrapper Function
checksum: 63fd54a4e690500bc25b04db13c74285d55753f70c36fd240a467d7b78eb432e
created: 2024-03-30 09:03:20.287849942+00:00
modified: 2024-06-15 09:50:26.603244644+00:00
metadata: {"tags":["node-js"]}
lead:
  When Node runs each of our module files are wrapped within an
  immediately-invoked function expression that has the following parameters:

body:
  When Node runs each of our module files are wrapped within an
  immediately-invoked function expression that has the following parameters:

  ```js
  (function (exports, require, module, __filename, __dirname))

  ```

  This is called the **module wrapper function**

  Note that one of these parameters is the
  [module object](Modules.md#structure-of-a-module).

  Within any module we can access these parameters: you can think of them as
  metadata about the module itself. `__filename` and `__dirname` are particularly
  useful when writing to files and modifying directories.

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

  ## The Module Wrapper Function

  When Node runs each of our module files are wrapped within an
  immediately-invoked function expression that has the following parameters:

  ```js
  (function (exports, require, module, __filename, __dirname))

  ```

  This is called the **module wrapper function**

  Note that one of these parameters is the
  [module object](Modules.md#structure-of-a-module).

  Within any module we can access these parameters: you can think of them as
  metadata about the module itself. `__filename` and `__dirname` are particularly
  useful when writing to files and modifying directories.


path: zk/Modules.md
sortable_path: zkModules.md
title: Modules
checksum: 9d03b5ccc104e11c3ef76f35571497cf375083499eb5f3b5f78548785b8d21f0
created: 2024-03-30 09:03:20.294999712+00:00
modified: 2024-06-15 09:50:29.696637857+00:00
metadata: {"tags":["node-js"]}
lead:
  > Modules are partitioned files where we define our variables and functions.
  > Values defined in modules are scoped to that specific module, constituting a
  > unique name space. This avoids name clashes in large programs.

body:
  > Modules are partitioned files where we define our variables and functions.
  > Values defined in modules are scoped to that specific module, constituting a
  > unique name space. This avoids name clashes in large programs.

  - Every file in a Node application is considered a module.

  - The variables and methods in a module are equivalent to `private` properties
    and methods in object-oriented programming.

  - If you wish to use a function or variable defined in a module outside of its
    modular container you need to explicitly export it and make it public.

  ## Structure of a module

  Node keeps an internal record of the properties of a module. To see this we can
  log the property `module` to the console.

  ```js
  // index.js
  console.log(module);
  ```

  This gives us:

  ```plaintext
  Module {
   id: '.',
   path: '/home/thomas/repos/node-learning',
   exports: {},
   filename: '/home/thomas/repos/node-learning/index.js',
   loaded: false,
   children: [],
   paths: [
     '/home/thomas/repos/node-learning/node_modules',
     '/home/thomas/repos/node_modules',
     '/home/thomas/node_modules',
     '/home/node_modules',
     '/node_modules'
   ]
  }
  ```

  ## Exports

  - Whenever we export a property or method from a module we are directly
    targeting the `exports` property of the module object.
  - Once we add exports to a file they will be displayed under that property of
    the module object.
  - We can export the entire module itself as the export (typically used when the
    module is a single function or class) or individual properties.

  ### Exporting a whole module

  _The example below is a module file that consists in a single function_

  ```js
  module.exports = function (...params) {
    // function body
  };
  ```

  Note the module is unnamed. We would name it when we import:

  ```js
  const myFunction = require("./filenme");
  ```

  ### Exporting sub-components from a module

  In the example below we export a variable and function from the same module.
  Note only those values prefixed with `exports` are exported.

  ```js
  exports.myFunc = (...params) => {
    // function bod[]()y
  };

  exports.aVar = 321.3;

  var nonExportedVar = true;
  ```

  This time the exports are already name so we would import with the following:

  ```js
  const { myFunc, aVar } = require("./filename");
  ```

  We can also do the exporting at the bottom when the individual components are
  named:

  ```js
  const myNamedFunc = (val) => {
    return val + 1;
  };

  function anotherNamedFunc(val) {
    return val * 2;
  }

  // This time we export at the bottom
  exports.myNamedFunc = myNamedFunc;
  exports.differentName = anotherNamedFunc; // We can use different names

  // Or we could export them together
  module.exports = { myNamedFunc, anotherNamedFunc };
  ```

  The import is the same:

  ```js
  const { myNamedFunc, anotherNamedFunc } = require("./modules/multiExports");
  ```

  ## Structuring modules

  The techniques above are useful to know but generally you would want to enforce
  a stricter structure than a mix of exported and private values in the one file.
  The best way to do this is with a single default export.

  Here the thing exported could be a composite function or an object that
  basically acts like a class with methods and properties.

  _Export a composite single function_

  ```js

  module.exports = () => {
  	foo() {...}
  	bar() {...}
  }
  ```

  _Export an object_

  ```js

  module.exports = {
  	foo : () => {...},
      bar: () => {...}
  }
  ```

  **Both of these structures would be referred to in the same way when importing
  and using them.**

  Or you could export an actual class as the default. This is practically the same
  as the two above other than that you would have to use `new` to initiate an
  instance of the class.

  ```js
  export default class {
    foo() {}
    bar() {}
  }
  ```

  Every method and property within the export will be public by default, whether
  it is an object, class or function. If you wanted to keep certain
  methods/properties private, the best approach is to define them as variables and
  functions within the module file but outside of the `export` block.

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

  # Modules

  > Modules are partitioned files where we define our variables and functions.
  > Values defined in modules are scoped to that specific module, constituting a
  > unique name space. This avoids name clashes in large programs.

  - Every file in a Node application is considered a module.

  - The variables and methods in a module are equivalent to `private` properties
    and methods in object-oriented programming.

  - If you wish to use a function or variable defined in a module outside of its
    modular container you need to explicitly export it and make it public.

  ## Structure of a module

  Node keeps an internal record of the properties of a module. To see this we can
  log the property `module` to the console.

  ```js
  // index.js
  console.log(module);
  ```

  This gives us:

  ```plaintext
  Module {
   id: '.',
   path: '/home/thomas/repos/node-learning',
   exports: {},
   filename: '/home/thomas/repos/node-learning/index.js',
   loaded: false,
   children: [],
   paths: [
     '/home/thomas/repos/node-learning/node_modules',
     '/home/thomas/repos/node_modules',
     '/home/thomas/node_modules',
     '/home/node_modules',
     '/node_modules'
   ]
  }
  ```

  ## Exports

  - Whenever we export a property or method from a module we are directly
    targeting the `exports` property of the module object.
  - Once we add exports to a file they will be displayed under that property of
    the module object.
  - We can export the entire module itself as the export (typically used when the
    module is a single function or class) or individual properties.

  ### Exporting a whole module

  _The example below is a module file that consists in a single function_

  ```js
  module.exports = function (...params) {
    // function body
  };
  ```

  Note the module is unnamed. We would name it when we import:

  ```js
  const myFunction = require("./filenme");
  ```

  ### Exporting sub-components from a module

  In the example below we export a variable and function from the same module.
  Note only those values prefixed with `exports` are exported.

  ```js
  exports.myFunc = (...params) => {
    // function bod[]()y
  };

  exports.aVar = 321.3;

  var nonExportedVar = true;
  ```

  This time the exports are already name so we would import with the following:

  ```js
  const { myFunc, aVar } = require("./filename");
  ```

  We can also do the exporting at the bottom when the individual components are
  named:

  ```js
  const myNamedFunc = (val) => {
    return val + 1;
  };

  function anotherNamedFunc(val) {
    return val * 2;
  }

  // This time we export at the bottom
  exports.myNamedFunc = myNamedFunc;
  exports.differentName = anotherNamedFunc; // We can use different names

  // Or we could export them together
  module.exports = { myNamedFunc, anotherNamedFunc };
  ```

  The import is the same:

  ```js
  const { myNamedFunc, anotherNamedFunc } = require("./modules/multiExports");
  ```

  ## Structuring modules

  The techniques above are useful to know but generally you would want to enforce
  a stricter structure than a mix of exported and private values in the one file.
  The best way to do this is with a single default export.

  Here the thing exported could be a composite function or an object that
  basically acts like a class with methods and properties.

  _Export a composite single function_

  ```js

  module.exports = () => {
  	foo() {...}
  	bar() {...}
  }
  ```

  _Export an object_

  ```js

  module.exports = {
  	foo : () => {...},
      bar: () => {...}
  }
  ```

  **Both of these structures would be referred to in the same way when importing
  and using them.**

  Or you could export an actual class as the default. This is practically the same
  as the two above other than that you would have to use `new` to initiate an
  instance of the class.

  ```js
  export default class {
    foo() {}
    bar() {}
  }
  ```

  Every method and property within the export will be public by default, whether
  it is an object, class or function. If you wanted to keep certain
  methods/properties private, the best approach is to define them as variables and
  functions within the module file but outside of the `export` block.


path: zk/Monitoring_processes_and_resources.md
sortable_path: zkMonitoring_processes_and_resources.md
title: Monitoring processes and resources
lead: ## General purpose diagnostic programs (memory, CPU, I/O)
checksum: 2a3fa5696919c2037c3107e4859bdacbd21f06cde5c16b5076e10f89a56cbe5a
created: 2024-03-30 09:03:20.303803846+00:00
modified: 2024-06-15 09:41:41.545732525+00:00
metadata: {"tags":["systems-programming","Linux","procedural"]}
body:
  ## General purpose diagnostic programs (memory, CPU, I/O)

  ### `top`/`htop`

  We can use [ps](Processes.md) to list the
  currently running processes but it does not provide much information about the
  resource metrics or how the process changes over time. We can use `top` to get
  more information.

  `top` provides an interactive interface for the information that `ps` displays.
  It updates in real time and shows the most active processes based on the CPU
  time that they are utilising. You can also order by memory usage.

  _Here I have pressed `u` to show only the processes associated with my user:_

  ![](/img/htop.png)

  ### Main commands

  | Command | Action                          |
  | ------- | ------------------------------- |
  | -u      | Show processes by selected user |
  | M       | Sort by memory usage            |
  | P       | Sort by cumulative CPU usage    |
  | ?       | View key and explanation        |

  ### Understanding the categories

  - `Main/IO`
    - The first covers all processes. The second focuses on input/output processes
      (i.e. reading and writing to disks and other devices)
  - `PRI`
    - This stands for _priority_. This metric reflects the kernel's current
      schedule priority for the process. The higher the value, it is less likely
      that the kernel will schedule the process if there are competing processes
      that require CPU time. The lower the value, the greater priority this
      process has over others.
  - `NI`
    - This stands for _nice value_. This metric exists in order to allow
      administrators to nudge or influence the priority of a given process. You
      cannot directly tell the kernel to _do x now instead of y_ but you can make
      what are effectively suggestions by manipulating the nice value.
    - The kernel adds the nice value to the current priority value for the given
      process to determine its next time slot. When you increase the nice value of
      process _P_ you are being "nicer" to the other processes by influencing the
      priority of _P_ downwards so that the other processes receive greater
      precedence from the kernel.
    - By default, the nice value will be 0. To reduce priority of PID 1234, you
      would use:
      ```bash
      $ renice 20 1234
      ```
  - `VIRT`
    - The total amount of
      [virtual memory](Virtual_memory_and_the_MMU.md) used by
      the process including: program code, data, shared libraries, pages that have
      been swapped, pages that have been mapped but not used.
  - `RES`
    - Stands for _resident size_
    - The non swapped _physical_ memory the process has used
  - `SHR`
    - The size of the process's
      [shared pages](Virtual_memory_and_the_MMU.md#shared-pages)
  - `S`
    - Status:
      - S for sleeping (idle)
      - R for running
      - D for disk sleep

  ### `vmstat`

  `vmstat` provides similar metrics to `htop` but tells you more about the memory
  state and the activities of the kernel in a single row.

  The default output is a single line with the averages since boot. You can add a
  delay parameter (in secs) which will then output at that interval, allowing you
  to see memory usage in realtime, e.g:

  ```
  $ vmstat 5
  procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
   r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
   2  0      0 4326768 334228 5050952    0    0     8    19   80   10  4  1 94  0  0
   0  0      0 4365520 334260 5054468    0    0     0   125 2140 3434  4  1 94  0  0
   1  0      0 4382400 334276 5068940    0    0     0    77 2102 3988  3  1 95  0  0
   1  0      0 4434000 334288 5052908    0    0     0    25 2859 4278  6  1 92  0  0
   0  0      0 4391576 334304 5086484    0    0     0   110 2899 6480  8  3 90  0  0

  ```

  - `procs`
    - The number of runnable processes (`r`) and the number of blocked (`b`)
      processes
  - `memory`
    - The core memory output distinguishing:
      - Total kbs swapped to disk
      - Total kbs free
      - Total kbs currently in
        [buffer](Role_of_memory_in_computation.md#relation-between-cache-and-buffers)
        and not written
      - Total amount of virtual memory in the
        [cache](Role_of_memory_in_computation.md#relation-between-cache-and-buffers)
  - `swap`
    - Distinguishes amount of memory
      [swapped](Swap_space.md) in (`si`) to memory and
      swapped out (`so`) to disk
  - `io`
    - Disk actions
    - Amount of data read from harddisk (`bi`)
    - Amount of data written to harddisk (`bo`)
  - `system`
    - The number of times the kernel switches to kernel code
  - `cpu`
    - Percentage of the different CPU behaviours:
      - Responding to user tasks (`us`)
      - Time that it is idle (`id`)

  ## Files being used by active processes: `lsof`

  `lsof` stands for _list open files_. It lists opened files and the processes
  using them. Without modifiers it outputs a huge amount of data. The best way to
  use it is to execute it against a specific PID. For example the below output
  gives me some useful info about which files VS Code is using:

  ![](/img/lsof.png)

  ## System calls: `strace`

  A system call is when a process requests a service from the
  [kernel](The_Kernel.md), for instance an I/O operation to
  memory. We can trace these system calls with `strace`.

  ## CPU performance

  We can use the `uptime` program to assess overall CPU performance in the form of
  a load average.

  > Load average is the number of active processes currently ready to run. It is
  > an estimate of the number of processes that are capable of using the CPU at
  > any given time.

  `Uptime` gives you three load averages:

  ```bash
  $ uptime
  11:19:16 up 14 days,  3:53,  1 user,  load average: 0.84, 0.57, 0.50
  ```

  - The three numbers are load averages for the past 1 minute, 5 minutes and 15
    minutes respectively.

  - A load average close to 0 is usually a good sign because it means that your
    processor isn't being challenged and you are conserving power. Anything equal
    to or above 1 means that a single process is using the CPU nearly all the
    time. You can identify that process with `htop` and it will obviously be near
    to the top. (This is often caused by Chrome and Electron-based software.)

  ## Memory status

  We know that processes primarily interact with virtual memory in the form of
  pages which are then translated to physical blocks by the kernel via the
  [MMU](Virtual_memory_and_the_MMU.md). There are several tools
  which provide windows onto this process.

  ### System page size

  We can view the overall system page size which is a representation of the amount
  of virtual memory available:

  ```bash
  $ getconf PAGE_SIZE
  4096
  ```

  This will typically be the same for all Linux systems.

  ### `free` : available physical memory

  `free` displays the total amount of free and¬used physical and swap memory in
  the system, as well as the
  [buffers and caches](Role_of_memory_in_computation.md#relation-between-cache-and-buffers)
  used by the kernel.

  ```bash
  $ free
                total        used        free      shared  buff/cache   available
  Mem:        16099420     5931512     5039344     2046460     5128564     7781904
  Swap:        3145724           0     3145724
  ```

raw_content:
  ---
  tags:
    - systems-programming
    - Linux
    - procedural
  ---

  # Monitoring processes and resources

  ## General purpose diagnostic programs (memory, CPU, I/O)

  ### `top`/`htop`

  We can use [ps](Processes.md) to list the
  currently running processes but it does not provide much information about the
  resource metrics or how the process changes over time. We can use `top` to get
  more information.

  `top` provides an interactive interface for the information that `ps` displays.
  It updates in real time and shows the most active processes based on the CPU
  time that they are utilising. You can also order by memory usage.

  _Here I have pressed `u` to show only the processes associated with my user:_

  ![](/img/htop.png)

  ### Main commands

  | Command | Action                          |
  | ------- | ------------------------------- |
  | -u      | Show processes by selected user |
  | M       | Sort by memory usage            |
  | P       | Sort by cumulative CPU usage    |
  | ?       | View key and explanation        |

  ### Understanding the categories

  - `Main/IO`
    - The first covers all processes. The second focuses on input/output processes
      (i.e. reading and writing to disks and other devices)
  - `PRI`
    - This stands for _priority_. This metric reflects the kernel's current
      schedule priority for the process. The higher the value, it is less likely
      that the kernel will schedule the process if there are competing processes
      that require CPU time. The lower the value, the greater priority this
      process has over others.
  - `NI`
    - This stands for _nice value_. This metric exists in order to allow
      administrators to nudge or influence the priority of a given process. You
      cannot directly tell the kernel to _do x now instead of y_ but you can make
      what are effectively suggestions by manipulating the nice value.
    - The kernel adds the nice value to the current priority value for the given
      process to determine its next time slot. When you increase the nice value of
      process _P_ you are being "nicer" to the other processes by influencing the
      priority of _P_ downwards so that the other processes receive greater
      precedence from the kernel.
    - By default, the nice value will be 0. To reduce priority of PID 1234, you
      would use:
      ```bash
      $ renice 20 1234
      ```
  - `VIRT`
    - The total amount of
      [virtual memory](Virtual_memory_and_the_MMU.md) used by
      the process including: program code, data, shared libraries, pages that have
      been swapped, pages that have been mapped but not used.
  - `RES`
    - Stands for _resident size_
    - The non swapped _physical_ memory the process has used
  - `SHR`
    - The size of the process's
      [shared pages](Virtual_memory_and_the_MMU.md#shared-pages)
  - `S`
    - Status:
      - S for sleeping (idle)
      - R for running
      - D for disk sleep

  ### `vmstat`

  `vmstat` provides similar metrics to `htop` but tells you more about the memory
  state and the activities of the kernel in a single row.

  The default output is a single line with the averages since boot. You can add a
  delay parameter (in secs) which will then output at that interval, allowing you
  to see memory usage in realtime, e.g:

  ```
  $ vmstat 5
  procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
   r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
   2  0      0 4326768 334228 5050952    0    0     8    19   80   10  4  1 94  0  0
   0  0      0 4365520 334260 5054468    0    0     0   125 2140 3434  4  1 94  0  0
   1  0      0 4382400 334276 5068940    0    0     0    77 2102 3988  3  1 95  0  0
   1  0      0 4434000 334288 5052908    0    0     0    25 2859 4278  6  1 92  0  0
   0  0      0 4391576 334304 5086484    0    0     0   110 2899 6480  8  3 90  0  0

  ```

  - `procs`
    - The number of runnable processes (`r`) and the number of blocked (`b`)
      processes
  - `memory`
    - The core memory output distinguishing:
      - Total kbs swapped to disk
      - Total kbs free
      - Total kbs currently in
        [buffer](Role_of_memory_in_computation.md#relation-between-cache-and-buffers)
        and not written
      - Total amount of virtual memory in the
        [cache](Role_of_memory_in_computation.md#relation-between-cache-and-buffers)
  - `swap`
    - Distinguishes amount of memory
      [swapped](Swap_space.md) in (`si`) to memory and
      swapped out (`so`) to disk
  - `io`
    - Disk actions
    - Amount of data read from harddisk (`bi`)
    - Amount of data written to harddisk (`bo`)
  - `system`
    - The number of times the kernel switches to kernel code
  - `cpu`
    - Percentage of the different CPU behaviours:
      - Responding to user tasks (`us`)
      - Time that it is idle (`id`)

  ## Files being used by active processes: `lsof`

  `lsof` stands for _list open files_. It lists opened files and the processes
  using them. Without modifiers it outputs a huge amount of data. The best way to
  use it is to execute it against a specific PID. For example the below output
  gives me some useful info about which files VS Code is using:

  ![](/img/lsof.png)

  ## System calls: `strace`

  A system call is when a process requests a service from the
  [kernel](The_Kernel.md), for instance an I/O operation to
  memory. We can trace these system calls with `strace`.

  ## CPU performance

  We can use the `uptime` program to assess overall CPU performance in the form of
  a load average.

  > Load average is the number of active processes currently ready to run. It is
  > an estimate of the number of processes that are capable of using the CPU at
  > any given time.

  `Uptime` gives you three load averages:

  ```bash
  $ uptime
  11:19:16 up 14 days,  3:53,  1 user,  load average: 0.84, 0.57, 0.50
  ```

  - The three numbers are load averages for the past 1 minute, 5 minutes and 15
    minutes respectively.

  - A load average close to 0 is usually a good sign because it means that your
    processor isn't being challenged and you are conserving power. Anything equal
    to or above 1 means that a single process is using the CPU nearly all the
    time. You can identify that process with `htop` and it will obviously be near
    to the top. (This is often caused by Chrome and Electron-based software.)

  ## Memory status

  We know that processes primarily interact with virtual memory in the form of
  pages which are then translated to physical blocks by the kernel via the
  [MMU](Virtual_memory_and_the_MMU.md). There are several tools
  which provide windows onto this process.

  ### System page size

  We can view the overall system page size which is a representation of the amount
  of virtual memory available:

  ```bash
  $ getconf PAGE_SIZE
  4096
  ```

  This will typically be the same for all Linux systems.

  ### `free` : available physical memory

  `free` displays the total amount of free and¬used physical and swap memory in
  the system, as well as the
  [buffers and caches](Role_of_memory_in_computation.md#relation-between-cache-and-buffers)
  used by the kernel.

  ```bash
  $ free
                total        used        free      shared  buff/cache   available
  Mem:        16099420     5931512     5039344     2046460     5128564     7781904
  Swap:        3145724           0     3145724
  ```


path: zk/Morgan.md
sortable_path: zkMorgan.md
title: Morgan
lead: Morgan is middleware that is used to log HTTP requests to the Express instance.
checksum: 26ede6ed4dc04da810194afc819dca34945f17243a0803689e91c1a03c4c2568
created: 2024-03-30 09:03:20.359592551+00:00
modified: 2024-06-15 09:50:31.790011694+00:00
metadata: {"tags":["node-js","middleware"]}
body:
  Morgan is middleware that is used to log HTTP requests to the Express instance.

  ```js
  app.use(morgan("dev"));
  ```

  With Morgan in place, every time we run a request it will be logged on the
  console that is running our Node application, e.g:

  ```plain
  GET /api/courses 200 95 - 1.774 ms
  ```

  This uses the `tiny` default which logs the bare minimum giving us: request
  type; endpoint; response code; and time to execute. But there are more verbose
  settings.

  It defaults to logging on the console but can also be configured to write to a
  log file.

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

  # Morgan

  Morgan is middleware that is used to log HTTP requests to the Express instance.

  ```js
  app.use(morgan("dev"));
  ```

  With Morgan in place, every time we run a request it will be logged on the
  console that is running our Node application, e.g:

  ```plain
  GET /api/courses 200 95 - 1.774 ms
  ```

  This uses the `tiny` default which logs the bare minimum giving us: request
  type; endpoint; response code; and time to execute. But there are more verbose
  settings.

  It defaults to logging on the console but can also be configured to write to a
  log file.


path: zk/Motherboard.md
sortable_path: zkMotherboard.md
title: Motherboard
lead: <img src="../img/motherboard-pi.jpg" width="400px"/>
checksum: 0b164f3be04afb2fabe087cf47088189f48663b1e63986bb463fa53d9e6680cf
created: 2024-03-30 09:03:20.364422156+00:00
modified: 2024-06-15 10:24:33.773630215+00:00
metadata: {"tags":["hardware","computer-architecture"]}
body:
  <img src="../img/motherboard-pi.jpg" width="400px"/>

  The motherboard is the foundation of a computer. It allocates power and allows
  communication to and between the
  [CPU](CPU_architecture.md),
  [RAM](Memory.md),
  [harddisk](What_are_disks.md) and all other hardware
  components.

  It is a printed circuit board and is always the largest board within the
  computer chassis.

raw_content:
  ---
  tags:
    - hardware
    - computer-architecture
  ---

  # Motherboard

  <img src="../img/motherboard-pi.jpg" width="400px"/>

  The motherboard is the foundation of a computer. It allocates power and allows
  communication to and between the
  [CPU](CPU_architecture.md),
  [RAM](Memory.md),
  [harddisk](What_are_disks.md) and all other hardware
  components.

  It is a printed circuit board and is always the largest board within the
  computer chassis.


path: zk/Multiple_pointers.md
sortable_path: zkMultiple_pointers.md
title: Multiple pointers
checksum: ea69be937de502f66ab336fe3481f7bcfbbb3aac8c5dff1a87a765fdf3b97192
created: 2024-03-30 09:03:20.37853018+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":[]}
lead:
  The multiple pointers pattern can be useful for moving through an array and
  comparing elements against each other in a time-efficient manner and without
  costly multiple loops.

body:
  The multiple pointers pattern can be useful for moving through an array and
  comparing elements against each other in a time-efficient manner and without
  costly multiple loops.

  ## Example: moving pairs

  In this example we shuffle through an array creating a pair from each element
  encountered. Thus for an array such as `[4, 3, 2, 1, 3, 6, 7]` we would return
  `[ [ 4, 3 ], [ 3, 2 ], [ 2, 1 ], [ 1, 3 ], [ 3, 6 ], [ 6, 7 ] ]`.

  ```js
  function movingPairs(arr) {
    const store = [];
    let left = 0;
    let right = 1;
    for (left; left < arr.length - right; left++) {
      store.push([arr[left], arr[right + left]]);
    }
    return store;
  }
  ```

  ## Example: identify unique values

  An example application of the above pattern would be identifying the number of
  unique values in an array that contains duplicates. One way to do this in
  JavaScript would be to use a Set however this is a more generic approach that
  only requires a single loop:

  ```js
  function uniqueValues(arr) {
    if (!arr.length) {
      return 0;
    } else {
      let count = 1;
      let left = 0;
      let right = 1;
      for (right; right < arr.length; right++) {
        if (arr[left] !== arr[right]) {
          count++;
        }
        left++;
      }

      return count;
    }
  }
  ```

  This works by having two pointers at either end of the array; one moving from
  the right and one moving from the left. Whilst the right pointer is less than
  the array length, the left pointer is incremented as the right moves closer to
  it. At each incrementation, the values of pointers (used as indexes) are
  compared for uniqueness, and a count is kept.

  ## Example: identify duplicates

  This is very similar to the above but this time we are looking for duplicates
  rather than unique values.

  ```js
  function areThereDuplicatesPointer(...input) {
    let start = 0;
    let next = 1;
    while (next < input.length) {
      let same = input[start] === input[next];
      if (same) {
        return true;
      }
      start++;
      next++;
    }

    return false;
  }
  console.log(areThereDuplicatesPointer(1, 2, 3)); // false
  console.log(areThereDuplicatesPointer(1, 2, 2)); // true
  ```

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

  # Multiple pointers

  The multiple pointers pattern can be useful for moving through an array and
  comparing elements against each other in a time-efficient manner and without
  costly multiple loops.

  ## Example: moving pairs

  In this example we shuffle through an array creating a pair from each element
  encountered. Thus for an array such as `[4, 3, 2, 1, 3, 6, 7]` we would return
  `[ [ 4, 3 ], [ 3, 2 ], [ 2, 1 ], [ 1, 3 ], [ 3, 6 ], [ 6, 7 ] ]`.

  ```js
  function movingPairs(arr) {
    const store = [];
    let left = 0;
    let right = 1;
    for (left; left < arr.length - right; left++) {
      store.push([arr[left], arr[right + left]]);
    }
    return store;
  }
  ```

  ## Example: identify unique values

  An example application of the above pattern would be identifying the number of
  unique values in an array that contains duplicates. One way to do this in
  JavaScript would be to use a Set however this is a more generic approach that
  only requires a single loop:

  ```js
  function uniqueValues(arr) {
    if (!arr.length) {
      return 0;
    } else {
      let count = 1;
      let left = 0;
      let right = 1;
      for (right; right < arr.length; right++) {
        if (arr[left] !== arr[right]) {
          count++;
        }
        left++;
      }

      return count;
    }
  }
  ```

  This works by having two pointers at either end of the array; one moving from
  the right and one moving from the left. Whilst the right pointer is less than
  the array length, the left pointer is incremented as the right moves closer to
  it. At each incrementation, the values of pointers (used as indexes) are
  compared for uniqueness, and a count is kept.

  ## Example: identify duplicates

  This is very similar to the above but this time we are looking for duplicates
  rather than unique values.

  ```js
  function areThereDuplicatesPointer(...input) {
    let start = 0;
    let next = 1;
    while (next < input.length) {
      let same = input[start] === input[next];
      if (same) {
        return true;
      }
      start++;
      next++;
    }

    return false;
  }
  console.log(areThereDuplicatesPointer(1, 2, 3)); // false
  console.log(areThereDuplicatesPointer(1, 2, 2)); // true
  ```


path: zk/Multiplexers_and_demultiplexers.md
sortable_path: zkMultiplexers_and_demultiplexers.md
title: Multiplexers (MUX) and demultiplexers (DMUX)
lead: ## Multiplexer
checksum: 0323d69672331653828e0e1eb7e963a330e2e4b67995bc169955b8bc65148eb7
created: 2024-03-30 09:03:20.382660339+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["logic-gates","binary","nand-to-tetris"]}
body:
  ## Multiplexer

  > Multiplexing is the generic term used to describe the operation of sending one
  > or more analogue or digital signals over a common transmission line at
  > different times or speeds.

  A multiplexer selects one of several input signals and forwards ths selected
  input to a single output line.

  We have two inputs (A,B) plus a third input SEL (for "select"). Applying a value
  to SEL toggles the output between A and B.

  ![](/img/MUX.png)

  Multiplexers can be used to build larger circuits by connecting the output of
  one multiplexer to the input of another. They are often used to implement data
  selection and switching in digital systems

  ### Programable gates

  One of the main use cases of multiplexers is to implement programmable gates.
  These are gates where the logic can be switched. For example an ANDMUXOR gate
  uses the SEL value to toggle the operation of a gate between AND and OR for its
  two inputs A and B

  ## Demultiplexer

  As the name suggests, a demultiplexer reverses the functionality of a
  multiplexer. It receives a single input and based on the selection of the SEL
  input it channels it to either an A or a B output.

  ![](/img/DMUX.png)

  We can think of it as a distributor of a value into one of several possible
  channels.

raw_content:
  ---
  tags: [logic-gates, binary, nand-to-tetris]
  ---

  # Multiplexers (MUX) and demultiplexers (DMUX)

  ## Multiplexer

  > Multiplexing is the generic term used to describe the operation of sending one
  > or more analogue or digital signals over a common transmission line at
  > different times or speeds.

  A multiplexer selects one of several input signals and forwards ths selected
  input to a single output line.

  We have two inputs (A,B) plus a third input SEL (for "select"). Applying a value
  to SEL toggles the output between A and B.

  ![](/img/MUX.png)

  Multiplexers can be used to build larger circuits by connecting the output of
  one multiplexer to the input of another. They are often used to implement data
  selection and switching in digital systems

  ### Programable gates

  One of the main use cases of multiplexers is to implement programmable gates.
  These are gates where the logic can be switched. For example an ANDMUXOR gate
  uses the SEL value to toggle the operation of a gate between AND and OR for its
  two inputs A and B

  ## Demultiplexer

  As the name suggests, a demultiplexer reverses the functionality of a
  multiplexer. It receives a single input and based on the selection of the SEL
  input it channels it to either an A or a B output.

  ![](/img/DMUX.png)

  We can think of it as a distributor of a value into one of several possible
  channels.


path: zk/Multiplicative_identity.md
sortable_path: zkMultiplicative_identity.md
title: The Property of Multiplicative Identity
lead: **Let $a$ represent any member of $\mathbb{W}$ or $\mathbb{Z}$ then:**
body:
  **Let $a$ represent any member of $\mathbb{W}$ or $\mathbb{Z}$ then:**

  $$ a \cdot 1 = a $$

checksum: 52f24c40c400082277ab21ab5b035d11337ff52976576ef802044d363d17c6d0
created: 2024-03-30 09:03:20.386291903+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","theorems"]}
raw_content:
  ---
  tags:
    - prealgebra
    - theorems
  ---

  # The Property of Multiplicative Identity

  **Let $a$ represent any member of $\mathbb{W}$ or $\mathbb{Z}$ then:**

  $$ a \cdot 1 = a $$


path: zk/Multiplicative_property_of_negative_one.md
sortable_path: zkMultiplicative_property_of_negative_one.md
title: The Multiplicative Property of Negative One
lead: > **Let $a$ represent any member of $\mathbb{Z}$, then:**
body:
  > **Let $a$ represent any member of $\mathbb{Z}$, then:**

  $$ (-1) \cdot a = -a $$

checksum: 3ea5a89ea5f9b12ba31084f176279eb700d16beb03d37584492c50100ba5a991
created: 2024-03-30 09:03:20.390062084+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","theorems"]}
raw_content:
  ---
  tags:
    - prealgebra
    - theorems
  ---

  # The Multiplicative Property of Negative One

  > **Let $a$ represent any member of $\mathbb{Z}$, then:**

  $$ (-1) \cdot a = -a $$


path: zk/Multiplying_fractions.md
sortable_path: zkMultiplying_fractions.md
title: Multiplying fractions
checksum: e693fb7d5cd07d76626f4e31e791a7cbb8bbd2effeb8cfe3169c7f2999b58136
created: 2024-03-30 09:03:20.39289207+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","fractions","arithmetic"]}
lead:
  To find the product of two fractions $\frac{a}{b}$ and $\frac{c}{d}$ multiply
  their numerators and denominators and then reduce:

body:
  To find the product of two fractions $\frac{a}{b}$ and $\frac{c}{d}$ multiply
  their numerators and denominators and then reduce:

  $$\frac{a}{b} \cdot \frac{c}{d} = \frac{a \cdot c}{b \cdot d}$$

  ### Example

  $$
  \frac{1}{3} \cdot \frac{2}{5} = \frac{1 \cdot 2}{3 \cdot 5} = \frac{2}{15}
  $$

  ## Prime factorisation in place

  The example above did not require a reduction, so here is a more complex
  example:

  $$
  \frac{14}{15} \cdot \frac{30}{140} = \frac{420}{2100}
  $$

  It would be laborious to reduce such a large product using factor trees or the
  repeated application of divisors, as defined in
  [reducing fractions](Reducing_fractions.md). We can use a more efficient
  method. This method can be applied at the point at which we conduct the
  multiplication rather than afterwards once we have the product. We express the
  the initial multiplicands as prime factors:

  $$
  \frac{14}{15} \cdot \frac{30}{140} = \frac{(2 \cdot 7) \cdot (2 \cdot 3 \cdot 5) }{(3 \cdot 5) \cdot (2 \cdot  2 \cdot 7 \cdot 5)}
  $$

  We now have the product in factorised form before we have applied the
  multiplication so we can go ahead and cancel:

  $$
  \frac{\cancel{2}, \cancel{7}, \cancel{2}, \cancel{3}, \cancel{5}}{\cancel{3}, \cancel{5}, \cancel{2}, \cancel{2}, \cancel{7}, 5} = \frac{1}{5}
  $$

  **Note that in the above case, there was only a single 5 left as a denominator
  and no value left as a numerator. This is equivalent to there just being "one
  five" so we write $\frac{1}{5}$**

  ## Example of multiplying fractions with negative fractions containing variables

  Calculate: $$- \frac{6x}{55y} \cdot - \frac{110y^2}{105x^2}$$

  First multiply in place:

  $$
  \frac{(3 \cdot 2 \cdot x) \cdot (5  \cdot 2 \cdot 11 \cdot y \cdot y)}{(5 \cdot 11 \cdot y) \cdot (7 \cdot 5 \cdot 3 \cdot x \cdot x)}
  $$

  Then cancel:

  $$
  \frac{(\cancel{3} \cdot 2 \cdot \cancel{x}) \cdot (\cancel{5}  \cdot 2 \cdot \cancel{11} \cdot \cancel{y} \cdot y)}{(\cancel{5} \cdot \cancel{11} \cdot \cancel{y}) \cdot (7 \cdot 5 \cdot \cancel{3} \cdot \cancel{x} \cdot x)} =
  \frac{2  \cdot 2 \cdot y}{7 \cdot 5 \cdot x}
  $$

  Then reduce:

  $$
  \frac{2  \cdot 2 \cdot y}{7 \cdot 5 \cdot x} = \frac{4y}{35x}
  $$

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

  # Multiplying fractions

  To find the product of two fractions $\frac{a}{b}$ and $\frac{c}{d}$ multiply
  their numerators and denominators and then reduce:

  $$\frac{a}{b} \cdot \frac{c}{d} = \frac{a \cdot c}{b \cdot d}$$

  ### Example

  $$
  \frac{1}{3} \cdot \frac{2}{5} = \frac{1 \cdot 2}{3 \cdot 5} = \frac{2}{15}
  $$

  ## Prime factorisation in place

  The example above did not require a reduction, so here is a more complex
  example:

  $$
  \frac{14}{15} \cdot \frac{30}{140} = \frac{420}{2100}
  $$

  It would be laborious to reduce such a large product using factor trees or the
  repeated application of divisors, as defined in
  [reducing fractions](Reducing_fractions.md). We can use a more efficient
  method. This method can be applied at the point at which we conduct the
  multiplication rather than afterwards once we have the product. We express the
  the initial multiplicands as prime factors:

  $$
  \frac{14}{15} \cdot \frac{30}{140} = \frac{(2 \cdot 7) \cdot (2 \cdot 3 \cdot 5) }{(3 \cdot 5) \cdot (2 \cdot  2 \cdot 7 \cdot 5)}
  $$

  We now have the product in factorised form before we have applied the
  multiplication so we can go ahead and cancel:

  $$
  \frac{\cancel{2}, \cancel{7}, \cancel{2}, \cancel{3}, \cancel{5}}{\cancel{3}, \cancel{5}, \cancel{2}, \cancel{2}, \cancel{7}, 5} = \frac{1}{5}
  $$

  **Note that in the above case, there was only a single 5 left as a denominator
  and no value left as a numerator. This is equivalent to there just being "one
  five" so we write $\frac{1}{5}$**

  ## Example of multiplying fractions with negative fractions containing variables

  Calculate: $$- \frac{6x}{55y} \cdot - \frac{110y^2}{105x^2}$$

  First multiply in place:

  $$
  \frac{(3 \cdot 2 \cdot x) \cdot (5  \cdot 2 \cdot 11 \cdot y \cdot y)}{(5 \cdot 11 \cdot y) \cdot (7 \cdot 5 \cdot 3 \cdot x \cdot x)}
  $$

  Then cancel:

  $$
  \frac{(\cancel{3} \cdot 2 \cdot \cancel{x}) \cdot (\cancel{5}  \cdot 2 \cdot \cancel{11} \cdot \cancel{y} \cdot y)}{(\cancel{5} \cdot \cancel{11} \cdot \cancel{y}) \cdot (7 \cdot 5 \cdot \cancel{3} \cdot \cancel{x} \cdot x)} =
  \frac{2  \cdot 2 \cdot y}{7 \cdot 5 \cdot x}
  $$

  Then reduce:

  $$
  \frac{2  \cdot 2 \cdot y}{7 \cdot 5 \cdot x} = \frac{4y}{35x}
  $$


path: zk/Mutations_with_Apollo_Client.md
sortable_path: zkMutations_with_Apollo_Client.md
title: Mutations with Apollo Client
lead: Queries are read-only operations. Mutations are write-only operations.
checksum: 880323da395777e103a129f870f5a4fabfd04b3e50d72c3b26f72dac1248fab6
created: 2024-03-30 09:03:20.40307229+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["graphql"]}
body:
  Queries are read-only operations. Mutations are write-only operations.

  Just like the `Query` type, the `Mutation` type serves as an entrypoint to the
  schema.

  ## Naming convention

  - Use verb: `add`, `create`, `delete`
  - Refer to the datatype

  For example `addSpaceCat(){}`

  ## Demonstration mutation

  We are going to create a mutation that increments the `numberOfViews` field on
  the `Track` type:

  ## Updating the schema

  ```js
  type Mutation {
      incrementTrackViews(id: ID!): IncrementTrackViewsResponse!
  }


  // Define a specific response type that specifically matches our needs
  type IncrementTrackViewsResponse {
      code: Int! // status code
      success: Boolean! // whether mutation was successful
      message: String! // what to say if mutation successful
      track: Track   // not nullable because might error
  }
  ```

  Based on this schema, the mutation will recieve a `Track` id and increment the
  specified `Track`. It will return an object comprising the newly updated `Track`
  and a bundle of properties that provide feedback on the status of the
  operations: a status code, whether it succeeded, and a message.

  ## Updating the data source

  Remember that our sole data source in the demonstration project is a REST API.
  We handle it within GraphQL using Apollos `RESTDataSource` class. We need to add
  a method to this class that will increment the track views. We wil use the
  `PATCH` REST method:

  ```js
  class TrackAPI extends RESTDataSource {
    constructor() {...}
    getTracksForHome() {...}
    getAuthor(authorId) {...}
    getTrack(trackId){...};

    incrementTrackViews(trackId) {
      return this.patch(`track/${trackId}/numberOfViews`);
    }
  }
  ```

  The `patch()` method is procided by the `RESTDataSouce` class that `TrackAPI`
  inherits from

  ## Adding resolver

  Next we need a resolver that corresponds to the mutation we have defined in the
  schema. We will need to handle successful responses as well as errors.

  ### Success case

  As always we match the shape of the schema:

  ```js
  const resolvers = {
    Query: {
      // ...query resolvers
    },
    Mutation: {
      // increments a track's numberOfViews property
      incrementTrackViews: async (_, { id }, { dataSources }) => {
        const track = await dataSources.trackAPI.incrementTrackViews(id);
        return {
          code: 200,
          success: true,
          message: `Successfully incremented number of views for track ${id}`,
          track,
        };
      },
    },
  };
  ```

  There's more going on with this resolver than the previous one. As is standard,
  we call the API using the `TrackAPI` class. However we don't just immediately
  return this when it executes. This is because the schema specifies that the
  retrun type `IncrementTrackViewsResponse` requires more than just the updated
  `Track`. So we wait this and return it with the cluster of metadata about the
  mutation response (`code`, `success`, and `message`).

  ### Error case

  We can extend the Mutation resolver to allow for errors. We'll do this by
  refactoring the resolver into a `try...catch` block and adding the error
  handling in the `catch`.

  We'll harness the details that are provided by Apollos' own `err` object which
  is returned by the `RESTDataSource` class that our resolver ultimately traces
  back to:

  ```js
  const resolvers = {

      Query: {
          // ...query resolvers
      }

      Mutation: {
          incrementTrackViews: async (_, {id}, {dataSources}) => {
              try {
                  const track = await dataSources.trackAPI.incrementTrackViews(id);
                  return {
                      code: 200,
                      success: true,
                      message: `Successfully incremented number of views for track ${id}`,
                      track
                  };
              } catch (err) {
                  return {
                      code: err.extensions.response.status,
                      success: false,
                      message: err.extensions.response.body,
                      track: null
                  };
              }
          },
      }
  }
  ```

  ## The `useMutation` hook

  We invoke the `useMutation` hook to issue mutations from the client-side.

  As with queries and
  [query constants](Apollo_Client.md#query-constants) we
  wrap our mutation in a `gql` template string:

  ```js
  const INCREMENT_TRACK_VIEWS = gql`
    mutation IncrementTrackViews($incrementTrackViewsId: ID!) {
      incrementTrackViews(id: $incrementTrackViewsId) {
        code
        success
        message
        track {
          id
          numberOfViews
        }
      }
    }
  `;
  ```

  We then pass it to the `useMutation` hook including an options object with our
  variables. (This time the specific variable is named):

  ```js
  import { gql, useMutation } from "@apollo/client";

  useMutation(INCREMENT_TRACK_VIEWS, {
    variables: { incrementTrackViewsId: id },
  });
  ```

  `useMutation` returns an array of two elements:

  1. The mutation function that actually executes
  2. An object comprising (`loading`, `error`, `data`) - this is the same as is
     the return value of `useQuery`.

  So we can destructure like so (we don't always need the second element);

  ```js
  const [incrementTrackViews, dataObject] = useMutation(INCREMENT_TRACK_VIEWS...)
  ```

  Given that we can isolate the mutation function as the first destructured
  element of the array, we could then attach `incrementTrackViews` to a button or
  other frontend interaction.

raw_content:
  ---
  tags: [graphql]
  ---

  # Mutations with Apollo Client

  Queries are read-only operations. Mutations are write-only operations.

  Just like the `Query` type, the `Mutation` type serves as an entrypoint to the
  schema.

  ## Naming convention

  - Use verb: `add`, `create`, `delete`
  - Refer to the datatype

  For example `addSpaceCat(){}`

  ## Demonstration mutation

  We are going to create a mutation that increments the `numberOfViews` field on
  the `Track` type:

  ## Updating the schema

  ```js
  type Mutation {
      incrementTrackViews(id: ID!): IncrementTrackViewsResponse!
  }


  // Define a specific response type that specifically matches our needs
  type IncrementTrackViewsResponse {
      code: Int! // status code
      success: Boolean! // whether mutation was successful
      message: String! // what to say if mutation successful
      track: Track   // not nullable because might error
  }
  ```

  Based on this schema, the mutation will recieve a `Track` id and increment the
  specified `Track`. It will return an object comprising the newly updated `Track`
  and a bundle of properties that provide feedback on the status of the
  operations: a status code, whether it succeeded, and a message.

  ## Updating the data source

  Remember that our sole data source in the demonstration project is a REST API.
  We handle it within GraphQL using Apollos `RESTDataSource` class. We need to add
  a method to this class that will increment the track views. We wil use the
  `PATCH` REST method:

  ```js
  class TrackAPI extends RESTDataSource {
    constructor() {...}
    getTracksForHome() {...}
    getAuthor(authorId) {...}
    getTrack(trackId){...};

    incrementTrackViews(trackId) {
      return this.patch(`track/${trackId}/numberOfViews`);
    }
  }
  ```

  The `patch()` method is procided by the `RESTDataSouce` class that `TrackAPI`
  inherits from

  ## Adding resolver

  Next we need a resolver that corresponds to the mutation we have defined in the
  schema. We will need to handle successful responses as well as errors.

  ### Success case

  As always we match the shape of the schema:

  ```js
  const resolvers = {
    Query: {
      // ...query resolvers
    },
    Mutation: {
      // increments a track's numberOfViews property
      incrementTrackViews: async (_, { id }, { dataSources }) => {
        const track = await dataSources.trackAPI.incrementTrackViews(id);
        return {
          code: 200,
          success: true,
          message: `Successfully incremented number of views for track ${id}`,
          track,
        };
      },
    },
  };
  ```

  There's more going on with this resolver than the previous one. As is standard,
  we call the API using the `TrackAPI` class. However we don't just immediately
  return this when it executes. This is because the schema specifies that the
  retrun type `IncrementTrackViewsResponse` requires more than just the updated
  `Track`. So we wait this and return it with the cluster of metadata about the
  mutation response (`code`, `success`, and `message`).

  ### Error case

  We can extend the Mutation resolver to allow for errors. We'll do this by
  refactoring the resolver into a `try...catch` block and adding the error
  handling in the `catch`.

  We'll harness the details that are provided by Apollos' own `err` object which
  is returned by the `RESTDataSource` class that our resolver ultimately traces
  back to:

  ```js
  const resolvers = {

      Query: {
          // ...query resolvers
      }

      Mutation: {
          incrementTrackViews: async (_, {id}, {dataSources}) => {
              try {
                  const track = await dataSources.trackAPI.incrementTrackViews(id);
                  return {
                      code: 200,
                      success: true,
                      message: `Successfully incremented number of views for track ${id}`,
                      track
                  };
              } catch (err) {
                  return {
                      code: err.extensions.response.status,
                      success: false,
                      message: err.extensions.response.body,
                      track: null
                  };
              }
          },
      }
  }
  ```

  ## The `useMutation` hook

  We invoke the `useMutation` hook to issue mutations from the client-side.

  As with queries and
  [query constants](Apollo_Client.md#query-constants) we
  wrap our mutation in a `gql` template string:

  ```js
  const INCREMENT_TRACK_VIEWS = gql`
    mutation IncrementTrackViews($incrementTrackViewsId: ID!) {
      incrementTrackViews(id: $incrementTrackViewsId) {
        code
        success
        message
        track {
          id
          numberOfViews
        }
      }
    }
  `;
  ```

  We then pass it to the `useMutation` hook including an options object with our
  variables. (This time the specific variable is named):

  ```js
  import { gql, useMutation } from "@apollo/client";

  useMutation(INCREMENT_TRACK_VIEWS, {
    variables: { incrementTrackViewsId: id },
  });
  ```

  `useMutation` returns an array of two elements:

  1. The mutation function that actually executes
  2. An object comprising (`loading`, `error`, `data`) - this is the same as is
     the return value of `useQuery`.

  So we can destructure like so (we don't always need the second element);

  ```js
  const [incrementTrackViews, dataObject] = useMutation(INCREMENT_TRACK_VIEWS...)
  ```

  Given that we can isolate the mutation function as the first destructured
  element of the array, we could then attach `incrementTrackViews` to a button or
  other frontend interaction.


path: zk/Naming_conventions.md
sortable_path: zkNaming_conventions.md
title: Python naming conventions
lead: ## Underscores
checksum: f491ff1235bf5bb1a424568b00d219bbb0760f8c2b82962ba863401322fbac87
created: 2024-03-30 09:03:20.408941584+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  ## Underscores

  To name a variable or method with multiple words we use underscores to separate
  each word.

  ```python
  an_int = 32
  print(an_integer.is_integer) # true
  ```

  ## Constants

  There are no constants in Python. However there a pseudo-constants established
  by convention. To denote that a variable should not change you use `UPPER_CASE`.

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

  # Python naming conventions

  ## Underscores

  To name a variable or method with multiple words we use underscores to separate
  each word.

  ```python
  an_int = 32
  print(an_integer.is_integer) # true
  ```

  ## Constants

  There are no constants in Python. However there a pseudo-constants established
  by convention. To denote that a variable should not change you use `UPPER_CASE`.


path: zk/Natural_numbers.md
sortable_path: zkNatural_numbers.md
title: The set of natural numbers
lead: $$ \mathbb{N} = {1, 2, 3, ...} $$
checksum: e42ef90c0ecd54ecd903240e8eacb31b42de3577739ae5f5d31f5b0737ef78bc
created: 2024-03-30 09:03:20.412983069+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra"]}
body:
  $$ \mathbb{N} = {1, 2, 3, ...} $$

  Natural numbers are most simply expressed as **the set of numbers we use for
  counting**. The set of natural numbers start at one and continuity to infinity.

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

  ## The set of natural numbers

  $$ \mathbb{N} = {1, 2, 3, ...} $$

  Natural numbers are most simply expressed as **the set of numbers we use for
  counting**. The set of natural numbers start at one and continuity to infinity.


path: zk/Negation_Elimination.md
sortable_path: zkNegation_Elimination.md
title: Negation Elimination
checksum: dd3f9b5499b1e4738866be7beb8e9eec4f27019523378d455d22ea21a0491ef6
created: 2024-03-30 09:03:20.417294279+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":[]}
lead:
  Like the [introduction](Negation_Introduction.md) rule for
  negation, the elimination rule also works by deriving a contradiction. It is
  basically _Negation Introduction_ in reverse. Instead of starting the sub-proof
  with a true proposition from which you derive a contradiction, you start with
  the negation of a proposition, derive a contradiction and then assert the
  positive of the negated proposition you started out with.

body:
  Like the [introduction](Negation_Introduction.md) rule for
  negation, the elimination rule also works by deriving a contradiction. It is
  basically _Negation Introduction_ in reverse. Instead of starting the sub-proof
  with a true proposition from which you derive a contradiction, you start with
  the negation of a proposition, derive a contradiction and then assert the
  positive of the negated proposition you started out with.

  ![](/img/negate-elim.png)

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

  # Negation Elimination

  Like the [introduction](Negation_Introduction.md) rule for
  negation, the elimination rule also works by deriving a contradiction. It is
  basically _Negation Introduction_ in reverse. Instead of starting the sub-proof
  with a true proposition from which you derive a contradiction, you start with
  the negation of a proposition, derive a contradiction and then assert the
  positive of the negated proposition you started out with.

  ![](/img/negate-elim.png)


path: zk/Negation_Introduction.md
sortable_path: zkNegation_Introduction.md
title: Negation Introduction
checksum: 7d50df30532449f25a68b7efbb9a1aac00ae85dea0a5c01e2214cff9bd609127
created: 2024-03-30 09:03:20.426980275+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":[]}
lead:
  This is also known as _proof by contradiction_. You start with an assumption
  declared in a sub-proof. If you can derive a contradiction from this assumption
  (typically from the introduction of another proposition and its negation), then
  you are permitted to derive the negation of the auxiliary assumption in the main
  proof.

body:
  This is also known as _proof by contradiction_. You start with an assumption
  declared in a sub-proof. If you can derive a contradiction from this assumption
  (typically from the introduction of another proposition and its negation), then
  you are permitted to derive the negation of the auxiliary assumption in the main
  proof.

  ![](/img/negate-intro.png)

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

  # Negation Introduction

  This is also known as _proof by contradiction_. You start with an assumption
  declared in a sub-proof. If you can derive a contradiction from this assumption
  (typically from the introduction of another proposition and its negation), then
  you are permitted to derive the negation of the auxiliary assumption in the main
  proof.

  ![](/img/negate-intro.png)


path: zk/Negative_exponents.md
sortable_path: zkNegative_exponents.md
lead:
  ---
  tags: [algebra, exponents]
  ---

checksum: 83982af46d75ecee22726807728c9f8881c79e45e98165afb961368864727976
created: 2024-03-30 09:03:20.430766589+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["algebra","exponents"]}
body:
  ---
  tags: [algebra, exponents]
  ---

  When calculating the exponents of a negative number the answer will always will
  be positive:

  $$
  -5^2 = 25
  $$

  This confused me but it was because I was thinking of it in terms of
  $-5 \cdot 5$ when in fact it is $-5 \cdot -5$ and when two negative numbers are
  multiplied the product is always negative.

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

  When calculating the exponents of a negative number the answer will always will
  be positive:

  $$
  -5^2 = 25
  $$

  This confused me but it was because I was thinking of it in terms of
  $-5 \cdot 5$ when in fact it is $-5 \cdot -5$ and when two negative numbers are
  multiplied the product is always negative.


path: zk/Network_requests_in_Python.md
sortable_path: zkNetwork_requests_in_Python.md
title: Making network requests in Python
checksum: 4ffab8245cfa9de9d1a3639e18abc360c7d52e235b96b33beced9b53c478b206
created: 2024-03-30 09:03:20.434913949+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python","APIs"]}
lead:
  We can use the `requests` package to make API requests to
  [RESTful](RESTful_APIs.md) resources and handle the data as
  JSON.

body:
  We can use the `requests` package to make API requests to
  [RESTful](RESTful_APIs.md) resources and handle the data as
  JSON.

  ```sh
  pip install requests
  ```

  Here is a basic architecture for making a `GET` request in Python.

  ```py
  import requests

  def get_data(url):
      response = requests.get(url)
      if response.status_code == 200:
          return response.json()
      else:
          raise Exception(
              f"Failed to fetch data from API. Status code: {response.status_code}")

  def main():
      url = "https://dummyjson.com/products/1"

      try:
          data = get_data(url)
          print(data)

      except Exception as e:
          prin(e)

  if __name__ == "__main__":
      main()
  ```

  ## Parsing returned data

  Running `main` returns:

  ```py
  {
     "id":1,
     "title":"iPhone 9",
     "description":"An apple mobile which is nothing like apple",
     "price":549,
     "discountPercentage":12.96,
     "rating":4.69,
     "stock":94,
     "brand":"Apple",
     "category":"smartphones",
     "thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg",
     "images":[
        "https://i.dummyjson.com/data/products/1/1.jpg",
        "https://i.dummyjson.com/data/products/1/2.jpg",
        "https://i.dummyjson.com/data/products/1/3.jpg",
        "https://i.dummyjson.com/data/products/1/4.jpg",
        "https://i.dummyjson.com/data/products/1/thumbnail.jpg"
     ]
  }
  ```

  This is JSON but in Python is a
  [dictionary](Dictionaries_in_Python.md)

  We can use standard dictionary methods to handle the data. For example, we'll
  add to the existing `try` block:

  ```py
  example_key = "brand"  # Replace with the key you want to access from the JSON data
      if example_key in data:
          print(f"Value of '{example_key}':", data[example_key])
      else:
          print(f"'{example_key}' not found in the JSON data")
  ```

  Which, if successful, outputs:

  ```
  Value of 'brand': Apple
  ```

raw_content:
  ---
  tags: [python, APIs]
  ---

  # Making network requests in Python

  We can use the `requests` package to make API requests to
  [RESTful](RESTful_APIs.md) resources and handle the data as
  JSON.

  ```sh
  pip install requests
  ```

  Here is a basic architecture for making a `GET` request in Python.

  ```py
  import requests

  def get_data(url):
      response = requests.get(url)
      if response.status_code == 200:
          return response.json()
      else:
          raise Exception(
              f"Failed to fetch data from API. Status code: {response.status_code}")

  def main():
      url = "https://dummyjson.com/products/1"

      try:
          data = get_data(url)
          print(data)

      except Exception as e:
          prin(e)

  if __name__ == "__main__":
      main()
  ```

  ## Parsing returned data

  Running `main` returns:

  ```py
  {
     "id":1,
     "title":"iPhone 9",
     "description":"An apple mobile which is nothing like apple",
     "price":549,
     "discountPercentage":12.96,
     "rating":4.69,
     "stock":94,
     "brand":"Apple",
     "category":"smartphones",
     "thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg",
     "images":[
        "https://i.dummyjson.com/data/products/1/1.jpg",
        "https://i.dummyjson.com/data/products/1/2.jpg",
        "https://i.dummyjson.com/data/products/1/3.jpg",
        "https://i.dummyjson.com/data/products/1/4.jpg",
        "https://i.dummyjson.com/data/products/1/thumbnail.jpg"
     ]
  }
  ```

  This is JSON but in Python is a
  [dictionary](Dictionaries_in_Python.md)

  We can use standard dictionary methods to handle the data. For example, we'll
  add to the existing `try` block:

  ```py
  example_key = "brand"  # Replace with the key you want to access from the JSON data
      if example_key in data:
          print(f"Value of '{example_key}':", data[example_key])
      else:
          print(f"'{example_key}' not found in the JSON data")
  ```

  Which, if successful, outputs:

  ```
  Value of 'brand': Apple
  ```


path: zk/Node_and_MySQL_db.md
sortable_path: zkNode_and_MySQL_db.md
title: Docker example: NodeJS backend with MySQL database
lead:
  We will utilise [Docker Compose](Docker_Compose.md) to combine
  two containers:

checksum: 911afa32b2e73d281da8ff57b0d3c2e59ac159f6627e75b0426b42aef7dde8d8
created: 2024-03-30 09:03:20.446869632+00:00
modified: 2024-06-15 09:48:00.387040188+00:00
metadata: {"tags":["docker","SQL","node-js","databases"]}
body:
  We will utilise [Docker Compose](Docker_Compose.md) to combine
  two containers:

  - A container for the NodeJS backend
  - A container for the MySQL database

  We will only create a Dockerfile for the NodeJS part since the existing `mysql`
  image is sufficient for most needs and does not require a specific
  configuration.

  Each of the files listed below would be saved to the same source directory which
  would then form the basis of the
  [build context](Creating_a_Docker_image.md#creating-a-docker-image).

  ## Docker Compose file

  ```yml
  # docker-compose.yml
  version: "3.8"
  services:
    db:
      image: mysql:8.0
      container_name: mysql_container
      environment:
        MYSQL_ROOT_PASSWORD: your_root_password
        MYSQL_DATABASE: your_database_name
        MYSQL_USER: your_database_user
        MYSQL_PASSWORD: your_database_password
      volumes:
        - mysql-data:/var/lib/mysql
      ports:
        - "3306:3306"
    app:
      build: .
      container_name: node_app
      volumes:
        - .:/usr/src/app
      environment:
        MYSQL_HOST: db
        MYSQL_USER: your_database_user
        MYSQL_PASSWORD: your_database_password
        MYSQL_DB: your_database_name
      depends_on:
        - db
      ports:
        - "3000:3000"

  volumes:
    mysql-data:
  ```

  ## Dockerfile for the NodeJS backend

  ```Dockerfile
  # Dockerfile
  FROM node:14

  WORKDIR /usr/src/app

  COPY package*.json ./

  RUN npm install

  COPY . .

  EXPOSE 3000

  CMD [ "node", "app.js" ]
  ```

  ## NodeJS project setup

  ```json
  // package.json
  {
    "name": "node-mysql-docker",
    "version": "1.0.0",
    "description": "Node.js and MySQL with Docker",
    "main": "app.js",
    "scripts": {
      "start": "node app.js"
    },
    "dependencies": {
      "express": "^4.17.1",
      "mysql2": "^2.3.2"
    }
  }
  ```

  ```js
  // app.js
  const express = require("express");
  const mysql = require("mysql2/promise");

  const app = express();

  const { MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB } = process.env;

  const createConnection = async () => {
    return await mysql.createConnection({
      host: MYSQL_HOST,
      user: MYSQL_USER,
      password: MYSQL_PASSWORD,
      database: MYSQL_DB,
    });
  };

  app.get("/", async (req, res) => {
    const connection = await createConnection();
    const [rows] = await connection.query("SELECT 1 + 1 AS solution");
    res.send(`Hello World! The solution is ${rows[0].solution}`);
  });

  const PORT = 3000;
  app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
  });
  ```

  To start up the environment you would then run:

  ```
  docker-compose -up
  ```

  ## Environments

  In the example, the database connection information in the Node source is coming
  from the
  [`process.env`](Managing_environments.md)
  object, which itself is sourcing the values `MYSQL_HOST`, `MYSQL_PASSWORD` etc
  from the Docker compose file. Therefore these values are hardcoded there.

  This is not good practice as it exposes sensitive information and make managing
  different deployment environments (development, stage, test etc.) difficult.

  To get around this we would create an `.env` file in the project directory that
  is Git ignored:

  ```sh
  # .env
  MYSQL_ROOT_PASSWORD=your_root_password
  MYSQL_DATABASE=your_database_name
  MYSQL_USER=your_database_user
  MYSQL_PASSWORD=your_database_password
  ```

  Then the `docker-compose.yml` file can be updated to use these variables:

  ```yml
  version: "3.8"
  services:
    db:
      image: mysql:8.0
      container_name: mysql_container
      environment:
        MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
        MYSQL_DATABASE: ${MYSQL_DATABASE}
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      volumes:
        - mysql-data:/var/lib/mysql
      ports:
        - "3306:3306"
    app:
      build: .
      container_name: node_app
      volumes:
        - .:/usr/src/app
      environment:
        MYSQL_HOST: db
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_DB: ${MYSQL_DATABASE}
      depends_on:
        - db
      ports:
        - "3000:3000"

  volumes:
    mysql-data:
  ```

  `${VARIABLE_NAME}` syntax is used to reference environment variables from the
  .env file in the `docker-compose.yml` file. Docker Compose will automatically
  load the variables from the .env file when starting the services.

  ### Development, staging, production environments

  To specify different connection details for different environments you would
  create different `.env` files for each:

  - `.env.development`
  - `.env.staging`
  - `.env.production`

  Each file will contain **environment-specific variables**, such as database
  credentials, API keys, and other configuration details.

  For example, development and production:

  ```yml
  # docker-compose.development.yml
  version: '3.8'
  services:
    db:
      ...
    app:
      ...
      environment:
        MYSQL_HOST: db
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_DB: ${MYSQL_DATABASE}
        NODE_ENV: development

  ```

  ```yml
  # docker-compose.production.yml
  version: '3.8'
  services:
    db:
      ...
    app:
      ...
      environment:
        MYSQL_HOST: db
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_DB: ${MYSQL_DATABASE}
        NODE_ENV: production
  `
  ```

  Then you would select the specific environment with your run command:

  ```
  docker-compose -f docker-compose.development.yml up -d
  ```

  Docker won't know by default which `.env` file to use from that command however.
  Assuming all the files are in the same directory you can use Bash substitution
  to specify the source of the environment specific variables:

  ```sh
  export $(cat .env.development | xargs) && docker-compose -f docker-compose.development.yml up -d
  ```

raw_content:
  ---
  tags:
    - docker
    - SQL
    - node-js
    - databases
  ---

  # Docker example: NodeJS backend with MySQL database

  We will utilise [Docker Compose](Docker_Compose.md) to combine
  two containers:

  - A container for the NodeJS backend
  - A container for the MySQL database

  We will only create a Dockerfile for the NodeJS part since the existing `mysql`
  image is sufficient for most needs and does not require a specific
  configuration.

  Each of the files listed below would be saved to the same source directory which
  would then form the basis of the
  [build context](Creating_a_Docker_image.md#creating-a-docker-image).

  ## Docker Compose file

  ```yml
  # docker-compose.yml
  version: "3.8"
  services:
    db:
      image: mysql:8.0
      container_name: mysql_container
      environment:
        MYSQL_ROOT_PASSWORD: your_root_password
        MYSQL_DATABASE: your_database_name
        MYSQL_USER: your_database_user
        MYSQL_PASSWORD: your_database_password
      volumes:
        - mysql-data:/var/lib/mysql
      ports:
        - "3306:3306"
    app:
      build: .
      container_name: node_app
      volumes:
        - .:/usr/src/app
      environment:
        MYSQL_HOST: db
        MYSQL_USER: your_database_user
        MYSQL_PASSWORD: your_database_password
        MYSQL_DB: your_database_name
      depends_on:
        - db
      ports:
        - "3000:3000"

  volumes:
    mysql-data:
  ```

  ## Dockerfile for the NodeJS backend

  ```Dockerfile
  # Dockerfile
  FROM node:14

  WORKDIR /usr/src/app

  COPY package*.json ./

  RUN npm install

  COPY . .

  EXPOSE 3000

  CMD [ "node", "app.js" ]
  ```

  ## NodeJS project setup

  ```json
  // package.json
  {
    "name": "node-mysql-docker",
    "version": "1.0.0",
    "description": "Node.js and MySQL with Docker",
    "main": "app.js",
    "scripts": {
      "start": "node app.js"
    },
    "dependencies": {
      "express": "^4.17.1",
      "mysql2": "^2.3.2"
    }
  }
  ```

  ```js
  // app.js
  const express = require("express");
  const mysql = require("mysql2/promise");

  const app = express();

  const { MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB } = process.env;

  const createConnection = async () => {
    return await mysql.createConnection({
      host: MYSQL_HOST,
      user: MYSQL_USER,
      password: MYSQL_PASSWORD,
      database: MYSQL_DB,
    });
  };

  app.get("/", async (req, res) => {
    const connection = await createConnection();
    const [rows] = await connection.query("SELECT 1 + 1 AS solution");
    res.send(`Hello World! The solution is ${rows[0].solution}`);
  });

  const PORT = 3000;
  app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
  });
  ```

  To start up the environment you would then run:

  ```
  docker-compose -up
  ```

  ## Environments

  In the example, the database connection information in the Node source is coming
  from the
  [`process.env`](Managing_environments.md)
  object, which itself is sourcing the values `MYSQL_HOST`, `MYSQL_PASSWORD` etc
  from the Docker compose file. Therefore these values are hardcoded there.

  This is not good practice as it exposes sensitive information and make managing
  different deployment environments (development, stage, test etc.) difficult.

  To get around this we would create an `.env` file in the project directory that
  is Git ignored:

  ```sh
  # .env
  MYSQL_ROOT_PASSWORD=your_root_password
  MYSQL_DATABASE=your_database_name
  MYSQL_USER=your_database_user
  MYSQL_PASSWORD=your_database_password
  ```

  Then the `docker-compose.yml` file can be updated to use these variables:

  ```yml
  version: "3.8"
  services:
    db:
      image: mysql:8.0
      container_name: mysql_container
      environment:
        MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
        MYSQL_DATABASE: ${MYSQL_DATABASE}
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      volumes:
        - mysql-data:/var/lib/mysql
      ports:
        - "3306:3306"
    app:
      build: .
      container_name: node_app
      volumes:
        - .:/usr/src/app
      environment:
        MYSQL_HOST: db
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_DB: ${MYSQL_DATABASE}
      depends_on:
        - db
      ports:
        - "3000:3000"

  volumes:
    mysql-data:
  ```

  `${VARIABLE_NAME}` syntax is used to reference environment variables from the
  .env file in the `docker-compose.yml` file. Docker Compose will automatically
  load the variables from the .env file when starting the services.

  ### Development, staging, production environments

  To specify different connection details for different environments you would
  create different `.env` files for each:

  - `.env.development`
  - `.env.staging`
  - `.env.production`

  Each file will contain **environment-specific variables**, such as database
  credentials, API keys, and other configuration details.

  For example, development and production:

  ```yml
  # docker-compose.development.yml
  version: '3.8'
  services:
    db:
      ...
    app:
      ...
      environment:
        MYSQL_HOST: db
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_DB: ${MYSQL_DATABASE}
        NODE_ENV: development

  ```

  ```yml
  # docker-compose.production.yml
  version: '3.8'
  services:
    db:
      ...
    app:
      ...
      environment:
        MYSQL_HOST: db
        MYSQL_USER: ${MYSQL_USER}
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_DB: ${MYSQL_DATABASE}
        NODE_ENV: production
  `
  ```

  Then you would select the specific environment with your run command:

  ```
  docker-compose -f docker-compose.development.yml up -d
  ```

  Docker won't know by default which `.env` file to use from that command however.
  Assuming all the files are in the same directory you can use Bash substitution
  to specify the source of the environment specific variables:

  ```sh
  export $(cat .env.development | xargs) && docker-compose -f docker-compose.development.yml up -d
  ```


path: zk/Nodemon.md
sortable_path: zkNodemon.md
title: Nodemon
checksum: 223c81e0636c8f7c17244cad0b4e2dc871021f2954688aa7f70ee67f8a2d6bfb
created: 2024-03-30 09:03:20.460814283+00:00
modified: 2024-06-15 09:50:33.83338455+00:00
metadata: {"tags":["node-js"]}
lead:
  We don't want to have to restart the local server every time we make a change to
  our files. We can use `nodemon` instead of `node` when running our `index.js`
  file so that file-changes are immediately registered without the need for a
  restart.

body:
  We don't want to have to restart the local server every time we make a change to
  our files. We can use `nodemon` instead of `node` when running our `index.js`
  file so that file-changes are immediately registered without the need for a
  restart.

  This is a wrapper around the `node` command so it doesn't require any
  configuration. Once installed, update your start script from `node index.js` to
  `nodemon index.js`.

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

  # Nodemon

  We don't want to have to restart the local server every time we make a change to
  our files. We can use `nodemon` instead of `node` when running our `index.js`
  file so that file-changes are immediately registered without the need for a
  restart.

  This is a wrapper around the `node` command so it doesn't require any
  configuration. Once installed, update your start script from `node index.js` to
  `nodemon index.js`.


path: zk/Non_null_assertion.md
sortable_path: zkNon_null_assertion.md
title: Non-null assertion
lead: Oftentimes you will encounter errors of the form:
checksum: 3afb6d654d3d9d9c24f9155a366976af6b70dc7f501848f73c2cb17e7c8e6455
created: 2024-03-30 09:03:20.465731403+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
body:
  Oftentimes you will encounter errors of the form:

  `Property [property_name] does not exist on type [type_name].ts(2339)`

  This means the interpreter is not able to determine whether the property which
  you are seeking to change exists. It can't be sure that you are not trying to
  update a property that is either `null` or `undefined`. This is good type
  checking but it means that in cases when _you know_ the property either exists
  or _will_ exist at runtime, you are going to face a code-breaking error.

  To get around this you can use non-null assertion. Like the name suggests, here
  you are saying to TypeScript: thanks but I am sure this property exists. When
  you do this you get the value you are trying to change with `null` and
  `undefined` excluded from it.

  This typically occurs when you are assigning a property to a value and TS
  doesn't know whether the value will exist or be accessible at this point in the
  runtime. A good scenario would be dynamic data passed into a function:

  ```ts
  function doSomethingWith(typeAlias: ITypeAlias) {
    let x = typeAlias!.somePropertyEvidentAtRuntime;
  }
  ```

  However it should be used carefully and sparingly because you are obviously
  turning off core type-checking and overuse nullifies the purpose of TypeScript.

  One way to get around it is to use better
  [type-guarding](Type_guarding_and_narrowing.md) and conditionality and to
  cover cases where the value may be undefined.

raw_content:
  ---
  tags:
    - typescript
  ---

  # Non-null assertion

  Oftentimes you will encounter errors of the form:

  `Property [property_name] does not exist on type [type_name].ts(2339)`

  This means the interpreter is not able to determine whether the property which
  you are seeking to change exists. It can't be sure that you are not trying to
  update a property that is either `null` or `undefined`. This is good type
  checking but it means that in cases when _you know_ the property either exists
  or _will_ exist at runtime, you are going to face a code-breaking error.

  To get around this you can use non-null assertion. Like the name suggests, here
  you are saying to TypeScript: thanks but I am sure this property exists. When
  you do this you get the value you are trying to change with `null` and
  `undefined` excluded from it.

  This typically occurs when you are assigning a property to a value and TS
  doesn't know whether the value will exist or be accessible at this point in the
  runtime. A good scenario would be dynamic data passed into a function:

  ```ts
  function doSomethingWith(typeAlias: ITypeAlias) {
    let x = typeAlias!.somePropertyEvidentAtRuntime;
  }
  ```

  However it should be used carefully and sparingly because you are obviously
  turning off core type-checking and overuse nullifies the purpose of TypeScript.

  One way to get around it is to use better
  [type-guarding](Type_guarding_and_narrowing.md) and conditionality and to
  cover cases where the value may be undefined.


path: zk/None_in_Python.md
sortable_path: zkNone_in_Python.md
title: None in Python
checksum: 185fdbfe796eb95a3ec84eab881a101129a444faade8c2bc76b831840d9c8a1b
created: 2024-03-30 09:03:20.477447631+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python","data-types"]}
lead:
  `None` is not `null`, it is closer to `undefined` in JS. If you define a
  variable as `None`, the variable exists, it is just not yet defined.

body:
  `None` is not `null`, it is closer to `undefined` in JS. If you define a
  variable as `None`, the variable exists, it is just not yet defined.

  Using `None` is a pattern similar to using `let` in JS to name a variable and
  definine it later on.

  ```python
  temperature = None
  ```

  If we logged `temperature` it would give us `None` rather than a null pointer
  error.

  With None we can use `is None` and `is not None`, special predicates for working
  with `None` only. This is a akin to using `if (x !== undefined)` in TypeScript

  ```python
  winner = None
  print('winner:', winner)
  # winner: None
  print('winner is None:', winner is None)
  # winner is None: True
  print('winner is not None:', winner is not None)
  # winner is not None: False
  print(type(winner))
  # <class 'NoneType'>
  # Now set winner to be True
  print('Set winner to True')
  # Set winner to True
  winner = True
  print('winner:', winner)
  # winner: True
  print('winner is None:', winner is None)
  # winner is None: False
  print('winner is not None:', winner is not None)
  # winner is not None: True
  print(type(winner))
  # <class 'bool'>
  ```

raw_content:
  ---
  tags: [python, data-types]
  ---

  # None in Python

  `None` is not `null`, it is closer to `undefined` in JS. If you define a
  variable as `None`, the variable exists, it is just not yet defined.

  Using `None` is a pattern similar to using `let` in JS to name a variable and
  definine it later on.

  ```python
  temperature = None
  ```

  If we logged `temperature` it would give us `None` rather than a null pointer
  error.

  With None we can use `is None` and `is not None`, special predicates for working
  with `None` only. This is a akin to using `if (x !== undefined)` in TypeScript

  ```python
  winner = None
  print('winner:', winner)
  # winner: None
  print('winner is None:', winner is None)
  # winner is None: True
  print('winner is not None:', winner is not None)
  # winner is not None: False
  print(type(winner))
  # <class 'NoneType'>
  # Now set winner to be True
  print('Set winner to True')
  # Set winner to True
  winner = True
  print('winner:', winner)
  # winner: True
  print('winner is None:', winner is None)
  # winner is None: False
  print('winner is not None:', winner is not None)
  # winner is not None: True
  print(type(winner))
  # <class 'bool'>
  ```


path: zk/Numbers_in_Python.md
sortable_path: zkNumbers_in_Python.md
title: Numbers in Python
lead: ## Distinguishing `int` and `float`
checksum: 7686d82f635d53fb6d4a45f128d97422a6636463c04c96174ff31bbcbd339c69
created: 2024-03-30 09:03:20.481907953+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python","data-types"]}
body:
  ## Distinguishing `int` and `float`

  - In Python we have floats and integers and we can coerce one into the other
  - A `//` as an operator means float division. This obviously provides greater
    precision than int division `/`.
  - There is no increment (`++`) or decrement (`--`) operator in Python

  ```python
  # Integers and floats
  count = 1
  print(count)
  # 1
  print(type(count))
  # <class 'int'>

  exchange_rate = 1.83
  print(exchange_rate)
  # 1.83
  print(type(exchange_rate))
  # <class 'float'>

  print(float(count))
  # 1.0

  print(int(exchange_rate))
  # 1
  ```

raw_content:
  ---
  tags: [python, data-types]
  ---

  # Numbers in Python

  ## Distinguishing `int` and `float`

  - In Python we have floats and integers and we can coerce one into the other
  - A `//` as an operator means float division. This obviously provides greater
    precision than int division `/`.
  - There is no increment (`++`) or decrement (`--`) operator in Python

  ```python
  # Integers and floats
  count = 1
  print(count)
  # 1
  print(type(count))
  # <class 'int'>

  exchange_rate = 1.83
  print(exchange_rate)
  # 1.83
  print(type(exchange_rate))
  # <class 'float'>

  print(float(count))
  # 1.0

  print(int(exchange_rate))
  # 1
  ```


path: zk/Object_language_and_meta_language.md
sortable_path: zkObject_language_and_meta_language.md
title: Object and metalanguages
checksum: 1be63bc78982eb0666e64209842912123a849dc70adfa066275c571ce3350c22
created: 2024-03-30 09:03:20.486105099+00:00
modified: 2024-06-16 16:52:46.35439344+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  When we talk about a language we call that language the **object language**. A
  **metalanguage** is a language used to describe some object language.

body:
  When we talk about a language we call that language the **object language**. A
  **metalanguage** is a language used to describe some object language.

  When we are developing a formal logical language (which we may call SL or PL for
  'sentential' and 'propositional' logic respectively), the formal language is the
  object language and natural language (e.g. English) is the metalanguage.

  **Demonstration**

  If we talk about German in English, German in the object language and English is
  the metalanguage.

  ## Use and mention

  There is an associated distinction: that of use and mention.

  When we create an expression in a language we are said to _use_ that language.
  When we remark upon said expression we are said to be _mentioning_ the language.
  This distinction may correspond to the object and metalanguage difference above
  but doesn't have to; use and mention can happen in the same language. For
  example:

  ```
  'London' is the word that denotes the capital of the UK.
  ```

  ## Metavariables

  A metalinguistic variable (metavariable for short) is an expression in the
  metalanguage that is used to talk generally about expressions of the object
  language. The convention in these notes will be to embolden single letters when
  these letters are used as metavariables.

  For example, instead of saying _'P & Q' is an expression comprising two atomic
  sentences and a conjunction_ we might say **\*P** is an expression comprising
  two atomic sentences and a conjunction. In this instance **P** is a metavariable
  in the metalanguage mentioning the expression P & Q in the object language

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

  # Object and metalanguages

  When we talk about a language we call that language the **object language**. A
  **metalanguage** is a language used to describe some object language.

  When we are developing a formal logical language (which we may call SL or PL for
  'sentential' and 'propositional' logic respectively), the formal language is the
  object language and natural language (e.g. English) is the metalanguage.

  **Demonstration**

  If we talk about German in English, German in the object language and English is
  the metalanguage.

  ## Use and mention

  There is an associated distinction: that of use and mention.

  When we create an expression in a language we are said to _use_ that language.
  When we remark upon said expression we are said to be _mentioning_ the language.
  This distinction may correspond to the object and metalanguage difference above
  but doesn't have to; use and mention can happen in the same language. For
  example:

  ```
  'London' is the word that denotes the capital of the UK.
  ```

  ## Metavariables

  A metalinguistic variable (metavariable for short) is an expression in the
  metalanguage that is used to talk generally about expressions of the object
  language. The convention in these notes will be to embolden single letters when
  these letters are used as metavariables.

  For example, instead of saying _'P & Q' is an expression comprising two atomic
  sentences and a conjunction_ we might say **\*P** is an expression comprising
  two atomic sentences and a conjunction. In this instance **P** is a metavariable
  in the metalanguage mentioning the expression P & Q in the object language


path: zk/Observer_pattern.md
sortable_path: zkObserver_pattern.md
title: Observer pattern
lead: The Observer design pattern is also known as the 'publish/subscribe' pattern.
checksum: 99dd7804cf299b0c66c3128ba2673501804614fac6c91b8a33f1c7ac721d8dc1
created: 2024-03-30 09:03:20.489374856+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["OOP"]}
body:
  The Observer design pattern is also known as the 'publish/subscribe' pattern.

  > The observer pattern is a software design pattern in which an object, called
  > the subject, maintains a list of its dependents, called observers, and
  > notifies them automatically of any state changes, usually by calling one of
  > their methods.

  The observer pattern instantiates a **one-to-many** relationship between the
  subject and its observers: when one object updates, it notifies many other
  objects that it has been updated.

  This is another design pattern that is useful when implementing a global state.
  When the subject updates its subjects must follow suit and adapt to its changes.
  Here the subject is the instrument for maintaining the global state.

  ## Subject class

  This class maintains a list of observers that will be notified when it is
  updated. It should have the following attributes:

  - array of existing observers
  - an `addObserver` method which pushes an observer onto the observers array
  - a `removeObserver` method that does the opposite
  - a `notify` method that broadcasts to the observers that a change has happened

  ## Observer class

  Observers must implement an `update` method that will be called by the subject's
  `notify` method.

  ## Example

  ```js
  class Subject {
    observers = [];

    getCurrentTime() {
      var d = new Date();
      return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;
    }

    addObserver(...observer) {
      for (const o of observer) {
        this.observers.push(o);
      }
    }
    removeObserver(observer) {
      const match = this.observers.findIndex((element) => element === observer);
      this.observers.splice(match, 1);
    }
    notify() {
      for (const o of this.observers) {
        o.update(`I was updated at ${this.getCurrentTime()}`);
      }
    }
  }

  class ObserverA {
    state = "I am observer A";
    update(newState) {
      this.state = newState;
    }
  }

  class ObserverB {
    state = "I am observer B";

    update(newState) {
      this.state = newState;
    }
  }

  let subjectInstance = new Subject();
  let obsAInstance = new ObserverA();
  let obsBInstance = new ObserverB();

  subjectInstance.addObserver(obsAInstance, obsBInstance);

  subjectInstance.notify();
  console.log(obsBInstance.state);
  ```

raw_content:
  ---
  tags: [OOP]
  ---

  # Observer pattern

  The Observer design pattern is also known as the 'publish/subscribe' pattern.

  > The observer pattern is a software design pattern in which an object, called
  > the subject, maintains a list of its dependents, called observers, and
  > notifies them automatically of any state changes, usually by calling one of
  > their methods.

  The observer pattern instantiates a **one-to-many** relationship between the
  subject and its observers: when one object updates, it notifies many other
  objects that it has been updated.

  This is another design pattern that is useful when implementing a global state.
  When the subject updates its subjects must follow suit and adapt to its changes.
  Here the subject is the instrument for maintaining the global state.

  ## Subject class

  This class maintains a list of observers that will be notified when it is
  updated. It should have the following attributes:

  - array of existing observers
  - an `addObserver` method which pushes an observer onto the observers array
  - a `removeObserver` method that does the opposite
  - a `notify` method that broadcasts to the observers that a change has happened

  ## Observer class

  Observers must implement an `update` method that will be called by the subject's
  `notify` method.

  ## Example

  ```js
  class Subject {
    observers = [];

    getCurrentTime() {
      var d = new Date();
      return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;
    }

    addObserver(...observer) {
      for (const o of observer) {
        this.observers.push(o);
      }
    }
    removeObserver(observer) {
      const match = this.observers.findIndex((element) => element === observer);
      this.observers.splice(match, 1);
    }
    notify() {
      for (const o of this.observers) {
        o.update(`I was updated at ${this.getCurrentTime()}`);
      }
    }
  }

  class ObserverA {
    state = "I am observer A";
    update(newState) {
      this.state = newState;
    }
  }

  class ObserverB {
    state = "I am observer B";

    update(newState) {
      this.state = newState;
    }
  }

  let subjectInstance = new Subject();
  let obsAInstance = new ObserverA();
  let obsBInstance = new ObserverB();

  subjectInstance.addObserver(obsAInstance, obsBInstance);

  subjectInstance.notify();
  console.log(obsBInstance.state);
  ```


