# notes

path: zk/Text_manipulation.md
sortable_path: zkText_manipulation.md
title: Text manipulation
lead: ## Sorting strings: `sort`
checksum: 16bd858534a5e8bb07228d40c6a8984bad8a4b74cf7a8c1b32bae9ae2ba8dfe1
created: 2024-03-30 09:03:21.432323039+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["shell"]}
body:
  ## Sorting strings: `sort`

  If you have a `.txt` file containing text strings, each on a new line you can
  use the sort function to quickly put them in alphabetical order:

  ```bash
  sort file.txt
  ```

  Note that this will not save the sort, it only presents it as a standard output.
  To save the sort you need to direct the sort to a file in the standard way:

  ```bash
  sort file.txt > output.txt
  ```

  ### Options

  - `-r`
    - reverse sort
  - `c`
    - check if file is already sorted. If not, it will highlight the strings which
      are not sorted

  ## Find and replace: `sed`

  The `sed` programme can be used to implement find and replace procedures. In
  `sed`, find and replace are covered by the substitution option: `/s` :

  ```bash
  sed ‘s/word/replacement word/’ file.txt
  ```

  This however will only change the first instance of word to be replaced, in
  order to apply to every instance you need to add the global option: `-g` .

  As sed is a stream editor, any changes you make using it, will only occur within
  the standard output , they will not be saved to file. In order to save to file
  you need to specify a new file output (using `> output.txt`) in addition to the
  original file. This hasthe benefit of leaving the original file untouched whilst
  ensuring the desired outcome is stored permanently.

  Alternatively, you can use the `-i` option which will make the changes take
  place in the source file as well as in standard input.

  Note that this will overwrite the original version of the file and it cannot be
  regained. If this is an issue then it is recommended to include a backup command
  in the overall argument like so:

  ```bash
  sed -i.bak ‘s/word/replacement word/’ file.txt
  ```

  This will create the file `file.txt.bak` in the directory you are working within
  which is the original file before the replacement was carried out.

  ### Remove duplicates

  We can use the `sort -u` command can be used to remove duplicates:

  ```bash
  sort -u file.txt
  ```

  It is important to sort before attempting to remove duplicates since the `-u`
  flag works on the basis of the strings being adjacent.

  ## Split a large file into multiple smaller files: `split`

  Suppose you have a file containing 1000 lines. You want to break the file up
  into five separate files, each containing two hundred lines. You can use `split`
  to accomplish this, like so:

  ```bash
  split -l 200 big-file.txt new-files
  ```

  `split` will categorise the resulting five files as follows:

  - new-file-aa,
  - new-file-ab
  - new-file-ac,
  - newfile-ad,
  - new-file-ae.

  If you would rather have numeric suffixes, use the option `-d` . You can also
  split a file by its number of bytes, using the option `-b` and specifying a
  constituent file size.

  ## Merge multiple files into one with `cat`

  We can use `cat` read multiple files at once and then append a redirect to save
  them to a file:

  ```bash
  cat file_a.txt file_b.txt file_c.txt > merged-file.txt
  ```

  ## Count lines, words, etc: `wc`

  To count words:

  ```bash
  wc file.txt
  ```

  When we use the command three numbers are outputted, in order: lines, words,
  bytes.

  You can use modifiers to get just one of the numbers: `-l`, `-w` , `-b` .

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

  # Text manipulation

  ## Sorting strings: `sort`

  If you have a `.txt` file containing text strings, each on a new line you can
  use the sort function to quickly put them in alphabetical order:

  ```bash
  sort file.txt
  ```

  Note that this will not save the sort, it only presents it as a standard output.
  To save the sort you need to direct the sort to a file in the standard way:

  ```bash
  sort file.txt > output.txt
  ```

  ### Options

  - `-r`
    - reverse sort
  - `c`
    - check if file is already sorted. If not, it will highlight the strings which
      are not sorted

  ## Find and replace: `sed`

  The `sed` programme can be used to implement find and replace procedures. In
  `sed`, find and replace are covered by the substitution option: `/s` :

  ```bash
  sed ‘s/word/replacement word/’ file.txt
  ```

  This however will only change the first instance of word to be replaced, in
  order to apply to every instance you need to add the global option: `-g` .

  As sed is a stream editor, any changes you make using it, will only occur within
  the standard output , they will not be saved to file. In order to save to file
  you need to specify a new file output (using `> output.txt`) in addition to the
  original file. This hasthe benefit of leaving the original file untouched whilst
  ensuring the desired outcome is stored permanently.

  Alternatively, you can use the `-i` option which will make the changes take
  place in the source file as well as in standard input.

  Note that this will overwrite the original version of the file and it cannot be
  regained. If this is an issue then it is recommended to include a backup command
  in the overall argument like so:

  ```bash
  sed -i.bak ‘s/word/replacement word/’ file.txt
  ```

  This will create the file `file.txt.bak` in the directory you are working within
  which is the original file before the replacement was carried out.

  ### Remove duplicates

  We can use the `sort -u` command can be used to remove duplicates:

  ```bash
  sort -u file.txt
  ```

  It is important to sort before attempting to remove duplicates since the `-u`
  flag works on the basis of the strings being adjacent.

  ## Split a large file into multiple smaller files: `split`

  Suppose you have a file containing 1000 lines. You want to break the file up
  into five separate files, each containing two hundred lines. You can use `split`
  to accomplish this, like so:

  ```bash
  split -l 200 big-file.txt new-files
  ```

  `split` will categorise the resulting five files as follows:

  - new-file-aa,
  - new-file-ab
  - new-file-ac,
  - newfile-ad,
  - new-file-ae.

  If you would rather have numeric suffixes, use the option `-d` . You can also
  split a file by its number of bytes, using the option `-b` and specifying a
  constituent file size.

  ## Merge multiple files into one with `cat`

  We can use `cat` read multiple files at once and then append a redirect to save
  them to a file:

  ```bash
  cat file_a.txt file_b.txt file_c.txt > merged-file.txt
  ```

  ## Count lines, words, etc: `wc`

  To count words:

  ```bash
  wc file.txt
  ```

  When we use the command three numbers are outputted, in order: lines, words,
  bytes.

  You can use modifiers to get just one of the numbers: `-l`, `-w` , `-b` .


path: zk/The_Kernel.md
sortable_path: zkThe_Kernel.md
title: The Kernel
checksum: 87cb0d704f5463d2913a2dd25e1b809e49a24ecb011dd8b1a4670284666b94c1
created: 2024-03-30 09:03:21.438010365+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["systems-programming","memory"]}
lead:
  The kernel acts as the primary mediator between the hardware (CPU, memory) and
  user [processes](Processes.md). Let's
  look at each of its responsibilities in greater depth:

body:
  The kernel acts as the primary mediator between the hardware (CPU, memory) and
  user [processes](Processes.md). Let's
  look at each of its responsibilities in greater depth:

  - process management
  - memory management
  - device drivers
  - system calls

  ## Process management

  > A process is just another name for a running program. Process management is
  > the starting, pausing, resuming, scheduling and terminating of processes.

  On modern computers it appears that multiple processes can run simultaneously at
  once. This is only because the processor is so fast that we do not detect
  changes. In fact access to the CPU is always sequential. The sequence in which
  multiple programs are allowed to access the CPU is managed by the kernel.

  > Consider a system with a one-core CPU. Many processes may be _able_ to use the
  > CPU, but only one process can actually use the CPU at any given time...Each
  > process uses the CPU for a fraction of a second, then pauses, then another
  > process uses it for a fraction of a second and so on... (_How Linux Works:
  > Third Edition_, Brian Ward 2021)

  This process of the CPU shuffling between multiple processes is called _context
  switching_.

  The role of the kernel in facilitating this, is as follows:

  1. CPU runs process for a time slice based on its internal time. Then hands
     control back to the kernel (kernel mode)
  2. Kernel records current state of CPU and memory. This is necessary in order to
     resume the progress that was just interupted.
  3. The kernel executes any tasks that arose in the last timeslice executed by
     the CPU (e.g. collecting data from I/0)
  4. Kernel then analyses the list of processes that are ready to run next and
     chooses one.
  5. Kernel prepares memory for this new process and prepares the CPU.
  6. Kernel tells CPU how long the time slice for the new process will last.
  7. Kernel switches the CPU into user mode and hands control of CPU to the
     process.

  ## Memory management

  During the context switch from CPU to user space, the kernel allocates memory.
  It has the following jobs to manage:

  - Keeping its own private area in memory for itself that user processes cannot
    access
  - Assigning each user process its own section of memory
  - Managing shared memory between processes and ensuring the private memory of
    processes is not accessed by others
  - Managing read-only memory
  - Allowing for the use of disk space as auxiliary memory

  > Modern CPUs include a
  > [memory management unit](Virtual_memory_and_the_MMU.md#the-memory-management-unit-mmu)
  > which provides the kernel with **virtual** memory. In this scenario, memory
  > isn't directly accessed by the process instead it works on the assumption that
  > is has access to the entire memory of the machine and this is then translated
  > into a map that is applied to the real memory and managed by the kernel.

  ## Device drivers

  Devices are managed by the kernal and are not accessible directly via user
  space, since improper usage could crash the whole machine. There is little
  uniformity between devices and as a result drivers are needed. Thes are kernl
  code that enable different OS kernels to access and control the devices.

  ## System calls

  Syscalls are what enable programs to start and are required for the acts of
  opening, reading and writing files. System calls in Linux are typically managed
  via C.

  In Linux there are two particularly important system calls:

  - `fork()`
    - When a process calls fork, the kernel creates a nearly identical copy of
      this running process
  - `exec()`
    - When a process calls exec it passes a program name as a parameter. Then the
      kernel loads and starts this program, replacing the current process.

  Example with a terminal program like `ls`:

  > When you enter `ls` into the terminal window, the shell that's running inside
  > the terminal window calls `fork()` to create a copy of the shell, and then the
  > new copy of the shell calls `exec(ls)` to run `ls`. (_Ibid._)

  ## Controlling processes

  In Linux we can view, kill, pause and resume processes using
  [ps](Processes.md).

raw_content:
  ---
  tags: [systems-programming, memory]
  ---

  # The Kernel

  The kernel acts as the primary mediator between the hardware (CPU, memory) and
  user [processes](Processes.md). Let's
  look at each of its responsibilities in greater depth:

  - process management
  - memory management
  - device drivers
  - system calls

  ## Process management

  > A process is just another name for a running program. Process management is
  > the starting, pausing, resuming, scheduling and terminating of processes.

  On modern computers it appears that multiple processes can run simultaneously at
  once. This is only because the processor is so fast that we do not detect
  changes. In fact access to the CPU is always sequential. The sequence in which
  multiple programs are allowed to access the CPU is managed by the kernel.

  > Consider a system with a one-core CPU. Many processes may be _able_ to use the
  > CPU, but only one process can actually use the CPU at any given time...Each
  > process uses the CPU for a fraction of a second, then pauses, then another
  > process uses it for a fraction of a second and so on... (_How Linux Works:
  > Third Edition_, Brian Ward 2021)

  This process of the CPU shuffling between multiple processes is called _context
  switching_.

  The role of the kernel in facilitating this, is as follows:

  1. CPU runs process for a time slice based on its internal time. Then hands
     control back to the kernel (kernel mode)
  2. Kernel records current state of CPU and memory. This is necessary in order to
     resume the progress that was just interupted.
  3. The kernel executes any tasks that arose in the last timeslice executed by
     the CPU (e.g. collecting data from I/0)
  4. Kernel then analyses the list of processes that are ready to run next and
     chooses one.
  5. Kernel prepares memory for this new process and prepares the CPU.
  6. Kernel tells CPU how long the time slice for the new process will last.
  7. Kernel switches the CPU into user mode and hands control of CPU to the
     process.

  ## Memory management

  During the context switch from CPU to user space, the kernel allocates memory.
  It has the following jobs to manage:

  - Keeping its own private area in memory for itself that user processes cannot
    access
  - Assigning each user process its own section of memory
  - Managing shared memory between processes and ensuring the private memory of
    processes is not accessed by others
  - Managing read-only memory
  - Allowing for the use of disk space as auxiliary memory

  > Modern CPUs include a
  > [memory management unit](Virtual_memory_and_the_MMU.md#the-memory-management-unit-mmu)
  > which provides the kernel with **virtual** memory. In this scenario, memory
  > isn't directly accessed by the process instead it works on the assumption that
  > is has access to the entire memory of the machine and this is then translated
  > into a map that is applied to the real memory and managed by the kernel.

  ## Device drivers

  Devices are managed by the kernal and are not accessible directly via user
  space, since improper usage could crash the whole machine. There is little
  uniformity between devices and as a result drivers are needed. Thes are kernl
  code that enable different OS kernels to access and control the devices.

  ## System calls

  Syscalls are what enable programs to start and are required for the acts of
  opening, reading and writing files. System calls in Linux are typically managed
  via C.

  In Linux there are two particularly important system calls:

  - `fork()`
    - When a process calls fork, the kernel creates a nearly identical copy of
      this running process
  - `exec()`
    - When a process calls exec it passes a program name as a parameter. Then the
      kernel loads and starts this program, replacing the current process.

  Example with a terminal program like `ls`:

  > When you enter `ls` into the terminal window, the shell that's running inside
  > the terminal window calls `fork()` to create a copy of the shell, and then the
  > new copy of the shell calls `exec(ls)` to run `ls`. (_Ibid._)

  ## Controlling processes

  In Linux we can view, kill, pause and resume processes using
  [ps](Processes.md).


path: zk/The_Little_Man_computer.md
sortable_path: zkThe_Little_Man_computer.md
title: The Little Man Computer
checksum: 7c712dc9df8e9010cb8c57dd521f4f5529a19f0ab90df78a8e1d95ae9342b49f
created: 2024-03-30 09:03:21.460679122+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["CPU"]}
lead:
  The [Little Man Computer]() is a simplified
  computer that works on Von Neuman principles. It has all the CPU components we
  have detailed above. It is programmed in machine code but for simplicity it uses
  the denary rather than the binary number system.

body:
  The [Little Man Computer]() is a simplified
  computer that works on Von Neuman principles. It has all the CPU components we
  have detailed above. It is programmed in machine code but for simplicity it uses
  the denary rather than the binary number system.

  ![LMC_5.gif](LMC_5.gif)

  On the left is the instruction set. Each number constitutes and execution
  routine and the `xx` stand for the address in RAM that the execution will work
  on.

  Each row of the RAM has a denary address, 1 through to 99. Each address can hold
  three digits.

  - So the instruction `560` would mean _load the number at address 60._
  - The instruction `340` would mean _store a datum at address 40_

  ### Working through a basic computation

  We are going to add two numbers together as a basic example.

  1. First we need to place the two numbers in RAM we are going to use `5` and `3`
     - At address `60` we will put the number `5` and at address `61` we will put
       the number `3`
     - We are going to start at address `0` in the top left of the RAM grid
  1. The first instruction will be _load address 60_ which in the assembly will be
     `560` . We put this in address `0`, our starting point.
  1. This first instruction is now stored in the accumulator.
  1. Now we want to _add this number (in the accumulator) to the number in address
     61_
  1. This second instruction is `161` . We write this in address `1`
  1. Finally we want to store the output of the calculation in the RAM, let's say
     at address `62`
  1. So we store the command `362` at address `2`

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

  ## The Little Man Computer

  The [Little Man Computer]() is a simplified
  computer that works on Von Neuman principles. It has all the CPU components we
  have detailed above. It is programmed in machine code but for simplicity it uses
  the denary rather than the binary number system.

  ![LMC_5.gif](LMC_5.gif)

  On the left is the instruction set. Each number constitutes and execution
  routine and the `xx` stand for the address in RAM that the execution will work
  on.

  Each row of the RAM has a denary address, 1 through to 99. Each address can hold
  three digits.

  - So the instruction `560` would mean _load the number at address 60._
  - The instruction `340` would mean _store a datum at address 40_

  ### Working through a basic computation

  We are going to add two numbers together as a basic example.

  1. First we need to place the two numbers in RAM we are going to use `5` and `3`
     - At address `60` we will put the number `5` and at address `61` we will put
       the number `3`
     - We are going to start at address `0` in the top left of the RAM grid
  1. The first instruction will be _load address 60_ which in the assembly will be
     `560` . We put this in address `0`, our starting point.
  1. This first instruction is now stored in the accumulator.
  1. Now we want to _add this number (in the accumulator) to the number in address
     61_
  1. This second instruction is `161` . We write this in address `1`
  1. Finally we want to store the output of the calculation in the RAM, let's say
     at address `62`
  1. So we store the command `362` at address `2`


path: zk/The_PATH.md
sortable_path: zkThe_PATH.md
title: The $PATH
checksum: 4f73dff47c7baf9d241cb89b6685f5c22038ece2bf4e6585bfc08810220df656
created: 2024-03-30 09:03:21.465683781+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["shell"]}
lead:
  We know that `$PATH` is an
  [environment variable](Environmental_and_shell_variables.md).
  This variable keeps track of directories **where executables are found**.

body:
  We know that `$PATH` is an
  [environment variable](Environmental_and_shell_variables.md).
  This variable keeps track of directories **where executables are found**.

  Whenever any command is run, the shell looks up the directories contained in the
  `PATH` for the target executable file and runs it. We can see this is the case
  by using the `which` command which traces the executable of bash commands. Take
  the `echo` program:

  ```bash
  which echo
  # echo: shell built-in command
  ```

  Or `npm` :

  ```bash
  which npm
  /home/thomas/.nvm/versions/node/v19.4.0/bin/npm
  ```

  By default the path will always contain the following locations:

  - `/usr/bin`
  - `/usr/sbin`
  - `/usr/local/bin`
  - `/usr/local/sbin`
  - `/bin`
  - `/sbin`

  All the inbuilt terminal programs reside at these locations and most of them are
  at `/usr/bin`. This is why they run automatically without error. If you attempt
  to run a program that doesn’t reside at these locations then you will get an
  error along the lines of `program x is not found in PATH`.

  ## Structure of the PATH

  ```bash
  /home/thomas/.nvm/versions/node/v19.4.0/bin:
  /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:
  /usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
  ```

  ## Adding to the PATH

  Only the default directories load to the PATH on every session. How then can we
  add custom directories to the path without them being lost every time we start a
  new session? Remember that the user config `.bashrc` loads on init for every
  bash session. Therefore, if we set the custom path in this file, it will be
  created every time we start a session. This is why when you add a new program it
  often ask you to append a script to the bottom of your `.bashrc` .

  For example, at the bottom of my `.zshrc` on my work computer I have:

  ```bash
  export CHROME_BIN=/mnt/c/Program\\ Files\\ \\(x86\\)/Google/Chrome/Application/chrome.exe
  ```

  This enables me to access the Chromium binaries from my terminal session (needed
  for running Angular tests) but it doesn’t add it to the path, it creates an
  environment variable on every session.

  For demonstration, let’s add a user’s desktop directory to the PATH.

  First we go to the `.bashrc` and add the `export` command. Remember that this is
  the command for creating a new environment variable:

  ```bash
  export PATH="$PATH=:~/Desktop"
  ```

  We force a reload of the `.bashrc` with the command:

  ```bash
  source ~/.bashrc
  ```

  Then we can check this directory has been added to the path with an echo

  ```bash
  echo $PATH
  ...:~/Desktop
  ```

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

  # The `$PATH`

  We know that `$PATH` is an
  [environment variable](Environmental_and_shell_variables.md).
  This variable keeps track of directories **where executables are found**.

  Whenever any command is run, the shell looks up the directories contained in the
  `PATH` for the target executable file and runs it. We can see this is the case
  by using the `which` command which traces the executable of bash commands. Take
  the `echo` program:

  ```bash
  which echo
  # echo: shell built-in command
  ```

  Or `npm` :

  ```bash
  which npm
  /home/thomas/.nvm/versions/node/v19.4.0/bin/npm
  ```

  By default the path will always contain the following locations:

  - `/usr/bin`
  - `/usr/sbin`
  - `/usr/local/bin`
  - `/usr/local/sbin`
  - `/bin`
  - `/sbin`

  All the inbuilt terminal programs reside at these locations and most of them are
  at `/usr/bin`. This is why they run automatically without error. If you attempt
  to run a program that doesn’t reside at these locations then you will get an
  error along the lines of `program x is not found in PATH`.

  ## Structure of the PATH

  ```bash
  /home/thomas/.nvm/versions/node/v19.4.0/bin:
  /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:
  /usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
  ```

  ## Adding to the PATH

  Only the default directories load to the PATH on every session. How then can we
  add custom directories to the path without them being lost every time we start a
  new session? Remember that the user config `.bashrc` loads on init for every
  bash session. Therefore, if we set the custom path in this file, it will be
  created every time we start a session. This is why when you add a new program it
  often ask you to append a script to the bottom of your `.bashrc` .

  For example, at the bottom of my `.zshrc` on my work computer I have:

  ```bash
  export CHROME_BIN=/mnt/c/Program\\ Files\\ \\(x86\\)/Google/Chrome/Application/chrome.exe
  ```

  This enables me to access the Chromium binaries from my terminal session (needed
  for running Angular tests) but it doesn’t add it to the path, it creates an
  environment variable on every session.

  For demonstration, let’s add a user’s desktop directory to the PATH.

  First we go to the `.bashrc` and add the `export` command. Remember that this is
  the command for creating a new environment variable:

  ```bash
  export PATH="$PATH=:~/Desktop"
  ```

  We force a reload of the `.bashrc` with the command:

  ```bash
  source ~/.bashrc
  ```

  Then we can check this directory has been added to the path with an echo

  ```bash
  echo $PATH
  ...:~/Desktop
  ```


path: zk/The_Pragmatic_Programmer_1999.md
sortable_path: zkThe_Pragmatic_Programmer_1999.md
title: General
lead: ### Meyer's Uniform Access Principle
checksum: bc07049acbd0ec05b3474c5d627e7f8bd8e9e5aaf46fed0ff4c2ff2e2ebf1a41
created: 2024-03-30 09:03:21.477651731+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":[]}
body:
  ### Meyer's Uniform Access Principle

  > All services offered by a module should be available through a uniform
  > notation, which does not betray whether they are implemented through storage
  > or through computation

  This is a clear recommendation for using getters and setters with classes. You
  should not see method calls outside of the class, they should appear as
  properties of the object.

  ## Don't Repeat Yourself

  > Every piece of knowledge must have a single, unambiguous, authoritative
  > representation within a system

  ## The Principle of Orthogonality

  This notion comes from geometry. Two lines are orthogonal to each other if they
  form a right-angle when they meet.

  Their meeting isn't the important part. Think of a simple x, y graph:

  > If you move along one of the lines, **your position projected onto the other
  > doesn't change**

  In computing this is expressed in terms of **decoupling** and is implemented
  through modular, component-based architectures. As much as possible code should
  be scoped narrowly so that a change in one area does not cause changes in
  others. By keeping components discrete it is easier to make changes, refactor,
  improve and extend the codebase.

  > We want to design components that are self-contained: independent and with a
  > single, well-defined purpose. When components are isolated from one another,
  > you know that you can change one without having to worry about the rest. As
  > long as you don't change that component's external interfaces, you can be
  > comfortable that you won't cause problems that ripple through the entire
  > system.

  ### Benefits of orthogonality: productivity

  - Changes are localised so development time and testing time are reduced
  - Orthogonality promotes reuse: if components have specific, well-defined
    responsibilities, they can be combined with new components in ways that were
    not envisioned by their original implementors. The more loosely coupled your
    systems, the easier they are to reconfigure and reengineer.
  - Assume that one component does _M_ distinct things and another does _N_
    things. If they are orthogonal and you combine them, the result does _M x N_
    things. However if the two components are not orthogonal, there will be
    overlap, and the result will do less. You get more functionality per unit
    effort by combining orthogonal components.

  ### Benefits of orthogonality: reduced risk

  - Diseased sections of code are isolated. If a module is sick, it is less likely
    to spread the symptoms around the rest of the system.
  - Overall the system is less fragile: make small changes to a particular area
    and any problems you generate will be restricted to that area.
  - Orthogonal systems are better tested because it is easier to run and design
    discrete tests on modularised components.

  > Building a unit test us itself a an interesting test of orthogonality: what
  > does it take to build and link a unit test? Do you have to drag in a large
  > percentage of the rest of the system just to get a test to compile or link?
  > **If so, you've found a module that is not well decoupled from the rest of the
  > system**

  ### Relationship between DRY and orthogonality

  With DRY you're looking to minimize duplication within a system, whereas with
  orthogonality, you reduce the interdependency among the system's components. If
  you use the principle of orthogonality combined closely with the DRY principle,
  you'll find that the systems you develop are more flexible, more understandable
  and easier to debug, test, and maintain.

  ### Reversibility

  The principles of orthogonality and DRY result in code that is reversible. This
  means it is able to change in an agile way when the circumstances of its use and
  deployment change. This is important because when developing software in a
  business setting, the best decisions are not always made the first time around.
  By following the principles it should be relatively easy to change your
  program's interfaces, platform and scale. In other words, with the principle of
  orthogonality and DRY, refactoring becomes less of a chore.

  ## Prototyping and Tracer Bullets

  'Tracer bullets' are used by the military for night warfare. They are
  phosphorous bullets that are included in the magazines of guns alongside normal
  bullets. They are not intended to kill but instead light-up the surrounding area
  making it easier to see the terrain and target more efficiently.

  The authors use the notion of tracer bullets as a metaphor for developing
  software at the early stages of a project. This is **not** the same thing as
  prototyping. A tracer bullet model is useful for building things that haven't
  been built before. They exist to 'shed light' on the project's needs and to help
  the client understand what they want.

  They differ from prototypes in that they include integrated overall
  functionality but in a rough state. Whereas prototypes are more for singular,
  specific subcomponents of the project. Because tracer bullet models are
  joined-up in this way, even if they turn out to be inappropriate in some regard,
  they can be adapted and developed into a better form, without losing the core
  functionality.

  > Tracer bullets work because they operate in the same environment and under the
  > same constraints as the real bullets. They get to the target fast, so the
  > gunner gets immediate feedback. And from a practical standpoint they are a
  > relatively cheap solution. To get the same effect in code, we're looking for
  > something that gets us from a requirement to some aspect of the final system
  > quickly, visibly and repeatably.

  > Tracer code is not disposable: you write it for keeps. It contains all the
  > error-checking, structuring, documentation and self-checking that a piece of
  > production code has. It simply is not fully functional. However, once you have
  > made an end-to-end connection among the components of your system, you can
  > check how close to the target you are, adjusting as necessary.

  ### Distinguishing from prototyping

  > Prototyping generates disposable code. Tracer code is lean but complete, and
  > forms part of the skeleton of the final system. Think of prototyping as the
  > reconnaissance and intelligence gathering that takes place before a single
  > tracer bullet is fired.

  ## Design by contract

  To understand DBC we have to think of a computational process as involving two
  stages: the call and the execution of the routine that happens in response to
  the call (henceforth **caller** and **routine**).

  - the caller could be a function expression that invokes a function and passes
    arguments to it expecting a given output. The function that executes is the
    routine
  - the caller could be an object instantiation that calls a method belonging to
    its parent class
  - the caller could be a parent React component that passes props to a child
    component

  Design by contract means specifying clear and inviolable rules detailing what
  must obtain at both the call stage and the routine stage if the process is to
  execute.

  Every function and method in a software system does something. Before it starts
  that something, the routine may have some expectation of the state of the world
  and it may be able to make a statement about the state of the world when it
  concludes. These expectations are defined in terms of preconditions,
  postconditions, and invariants. They form that basis of a **contract** between
  the caller and the routine. Hence _design by contract\*\*._\*\*

  ### Preconditions

  Preconditions specify what must be true in order for the routine to be called.
  In other words, the requirements of the routine. What it needs and what should
  be the case before it even considers executing the task. A **routine should
  never get called when its preconditions would be violated**.

  ### Postconditions

  Providing the preconditions are met, this is what the routine is guaranteed to
  do. In other words: the state of affairs that must obtain after the routine has
  ran.

  ### Invariants

  Once established, the preconditions and postconditions should not change. If
  they need to change, that is a separate process and contract. In the processing
  of a routine, the data may be variant relative to the contract, but by the end
  the overall conditions establish the equilibrium of the contract.

  There is an analogue here with functional programming philosophy: the function
  should always return the same sort of output, without ancillary processes
  happening, i.e side-effects.

  One way to achieve this is to be miserly when setting up the contract, which
  overlaps with orthogonality. Only specify the minimum return on a contract
  rather than multiple postconditions. This only increases the likelihood that the
  contract will be breached at some point. If you need multiple postconditions,
  spread them out an achieve them in a compositional way, with multiple separate
  and modular processes.

  > Be strict in what you will accept before you begin, and promise as little as
  > possible in return. If your contract indicates that you'll accept anything and
  > promise the world in return, then you've got a lot of code to write!

  ### Division of responsibilities

  > If all the routine's preconditions are met by the caller, the routine shall
  > guarantee that all postconditions and invariants will be true when it
  > completes.

  Note that the emphasis of responsibilities is on the caller.

  Imagine that we have a function that returns the count of an array of integers.
  It is not the job of the count routine to verify that it has been passed
  integers and then to execute the count. Or, in the event that it is not passed
  integers, to mutate the data to integers and then execute.

  This should be resolved by the caller: it is the responsibility of the caller to
  pass integers. If it doesn't, the routine simply crashes or raises an exception.
  It doesn't try to accommodate the input because that does not come down on its
  side of the contract. The caller has failed to meet the preconditions. If, due
  to some bug, the routine receives integers and fails to output the sum, then it
  has failed on its side

  ### Example: type checking

  An obvious example of this philosophy is when you perform checks or validation
  within your code (although validation is more of an issue when you are dealing
  with user data, not your own internal code). For instance using type checking
  with dynamically-typed languages.

  When we use the `prop-types` library with React we are specifying preconditions:
  so long at the prop (effectively the caller) passed to the component
  (effectively the routine) is of type X, the component will render invariantly as
  R. If the prop is of type Y, an exception will be raised highlighting a breach
  in the contract.

  Another example would be more advanced type checking with Javascript written
  using Typescript.

  ## The Law of Demeter

  Demeter's Law has applicability chiefly when programming with classes.

  It's a fancy name for a simple principle summarised by 'don't talk to
  strangers'. Demeter's law is violated when code has more than one step between
  classes. You should avoid invoking methods of an object returned by another
  method. You should only use your own methods when dealing with it.

  ### Formal

  A method _m_ of object _O_ may only invoke the methods of the following kinds of
  objects:

  - _O_ itself
  - _m_'s parameters
  - any objects created or instantiated within _m_
  - _O_'s direct component objects (in other words nested objects)
  - a global variable (over and above _O_) accessible by _O_, within the scope of
    _m_

  ## Model, View, Controller design pattern

  The key concept behind the MVC idiom is separating the model from both the GUI
  that represents it and the controls that manage the view.

  - **Model**
    - The abstract data model representing the target object
    - The model has no direct knowledge of any views or controllers
  - **View**
    - A way to interpret the model. It subscribes to changes in the model and
      logical events from the controller
  - **Controller**
    - A way to control the view and provide the model with new data. It publishes
      events to both the model and the view

  For comparison, distinguish React from MVC. In React data is unidirectional: the
  JSX component as controller cannot change the state. The state is passed down to
  the controller. Also MVC lends itself to separation of technologies: code used
  to create the View is different from Code that manages Controller and data
  Model. In React it's all one integrated system.

  ## Refactoring

  > Rewriting, reworking, and re-architecting code is collectively known as
  > refactoring

  ### When to refactor

  - **Duplication**: you've discovered a violation of the DRY principle
  - **Non-orthogonal design**: you've discovered some code or design that could be
    made more orthogonal
  - **Outdated knowledge**: your knowledge about the problem and you skills at
    implementing a solution have changed since the code was first written. Update
    and improve the code to reflect these changes
  - **Performance: y**ou need to move functionality from one area of the system to
    another to improve performance

  ### Tips when refactoring

  - Don't try to refactor and add new functionality at the same time!
  - Make sure you have good tests before you begin refactoring. Run the tests as
    you refactor. That way you will know quickly if your changes have broken
    anything
  - Take short, deliberative steps. Refactoring often involves making many
    localised changes that result in a larger-scale change.

  ## Testing

  > Most developers hate testing. They tend to test-gently, subconsciously knowing
  > where the code will break and avoiding the weak spots. Pragmatic Programmers
  > are different. We are _driven_ to find our bugs _now_, so we don't have to
  > endure the shame of others finding our bugs later.

  ### Unit testing

  A unit test is code that exercises a module. It consists in testing each module
  in isolation to verify its behaviour. Unit testing is the foundation of all
  other forms of testing. If the parts don't work by themselves, they probably
  won't work well together. All the modules you are using must pass their own unit
  tests before you can proceed.

  We can think of unit testing as **testing against contract** (detailed above).
  We want to test that the module delivers the functionality it promises over a
  wide range of test cases and boundary conditions.

  Scope for unit testing should cover:

  - Obviously, returning the expected value/outcome
  - Ensuring that faulty arguments/ types are rejected and initiate error handling
    (deliberately breaking your code to ensure it is handled appropriately)
  - Pass in the boundary and maximum value
  - Pass in values between the zero and the maximum expressible argument to cover
    a range of cases

  Benefits of unit testing include:

  - It creates an example to other developers how to use all of the functionality
    of a given module
  - It is a means to build **regression tests** which can be used to validate any
    future changes to the code. In other words, the future changes should pass the
    older tests to prove they are consistent with the code base

  ### Integration testing

  Integration testing shows that the major subsystems that make up the project
  work and play well with each other.

  Integration testing is really just an extension of the unit testing described,
  only know you're testing how entire subsystems honour their contracts.

  ## Commenting your code

  In general, comments should detail **why** something is done, its purpose and
  its goal. The code already shows _how_ it's done, so commenting on this is
  redundant, and violates the DRY principle.

  > We like to see a simple module-level comment, comments for significant data
  > and type declarations, and a brief class and per-method header describing how
  > the function is used and anything it does that is not obvious

  ```js
  /*
  Find the highest value within a specified data range of samples

  Parameter: aRange = range of dates to search for data
  Parameter: aThreshold = minimum value to consider

  Return: the value, or null if no value found that is greater than or equal to the threshold
  */
  ```

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

  ## General

  ### Meyer's Uniform Access Principle

  > All services offered by a module should be available through a uniform
  > notation, which does not betray whether they are implemented through storage
  > or through computation

  This is a clear recommendation for using getters and setters with classes. You
  should not see method calls outside of the class, they should appear as
  properties of the object.

  ## Don't Repeat Yourself

  > Every piece of knowledge must have a single, unambiguous, authoritative
  > representation within a system

  ## The Principle of Orthogonality

  This notion comes from geometry. Two lines are orthogonal to each other if they
  form a right-angle when they meet.

  Their meeting isn't the important part. Think of a simple x, y graph:

  > If you move along one of the lines, **your position projected onto the other
  > doesn't change**

  In computing this is expressed in terms of **decoupling** and is implemented
  through modular, component-based architectures. As much as possible code should
  be scoped narrowly so that a change in one area does not cause changes in
  others. By keeping components discrete it is easier to make changes, refactor,
  improve and extend the codebase.

  > We want to design components that are self-contained: independent and with a
  > single, well-defined purpose. When components are isolated from one another,
  > you know that you can change one without having to worry about the rest. As
  > long as you don't change that component's external interfaces, you can be
  > comfortable that you won't cause problems that ripple through the entire
  > system.

  ### Benefits of orthogonality: productivity

  - Changes are localised so development time and testing time are reduced
  - Orthogonality promotes reuse: if components have specific, well-defined
    responsibilities, they can be combined with new components in ways that were
    not envisioned by their original implementors. The more loosely coupled your
    systems, the easier they are to reconfigure and reengineer.
  - Assume that one component does _M_ distinct things and another does _N_
    things. If they are orthogonal and you combine them, the result does _M x N_
    things. However if the two components are not orthogonal, there will be
    overlap, and the result will do less. You get more functionality per unit
    effort by combining orthogonal components.

  ### Benefits of orthogonality: reduced risk

  - Diseased sections of code are isolated. If a module is sick, it is less likely
    to spread the symptoms around the rest of the system.
  - Overall the system is less fragile: make small changes to a particular area
    and any problems you generate will be restricted to that area.
  - Orthogonal systems are better tested because it is easier to run and design
    discrete tests on modularised components.

  > Building a unit test us itself a an interesting test of orthogonality: what
  > does it take to build and link a unit test? Do you have to drag in a large
  > percentage of the rest of the system just to get a test to compile or link?
  > **If so, you've found a module that is not well decoupled from the rest of the
  > system**

  ### Relationship between DRY and orthogonality

  With DRY you're looking to minimize duplication within a system, whereas with
  orthogonality, you reduce the interdependency among the system's components. If
  you use the principle of orthogonality combined closely with the DRY principle,
  you'll find that the systems you develop are more flexible, more understandable
  and easier to debug, test, and maintain.

  ### Reversibility

  The principles of orthogonality and DRY result in code that is reversible. This
  means it is able to change in an agile way when the circumstances of its use and
  deployment change. This is important because when developing software in a
  business setting, the best decisions are not always made the first time around.
  By following the principles it should be relatively easy to change your
  program's interfaces, platform and scale. In other words, with the principle of
  orthogonality and DRY, refactoring becomes less of a chore.

  ## Prototyping and Tracer Bullets

  'Tracer bullets' are used by the military for night warfare. They are
  phosphorous bullets that are included in the magazines of guns alongside normal
  bullets. They are not intended to kill but instead light-up the surrounding area
  making it easier to see the terrain and target more efficiently.

  The authors use the notion of tracer bullets as a metaphor for developing
  software at the early stages of a project. This is **not** the same thing as
  prototyping. A tracer bullet model is useful for building things that haven't
  been built before. They exist to 'shed light' on the project's needs and to help
  the client understand what they want.

  They differ from prototypes in that they include integrated overall
  functionality but in a rough state. Whereas prototypes are more for singular,
  specific subcomponents of the project. Because tracer bullet models are
  joined-up in this way, even if they turn out to be inappropriate in some regard,
  they can be adapted and developed into a better form, without losing the core
  functionality.

  > Tracer bullets work because they operate in the same environment and under the
  > same constraints as the real bullets. They get to the target fast, so the
  > gunner gets immediate feedback. And from a practical standpoint they are a
  > relatively cheap solution. To get the same effect in code, we're looking for
  > something that gets us from a requirement to some aspect of the final system
  > quickly, visibly and repeatably.

  > Tracer code is not disposable: you write it for keeps. It contains all the
  > error-checking, structuring, documentation and self-checking that a piece of
  > production code has. It simply is not fully functional. However, once you have
  > made an end-to-end connection among the components of your system, you can
  > check how close to the target you are, adjusting as necessary.

  ### Distinguishing from prototyping

  > Prototyping generates disposable code. Tracer code is lean but complete, and
  > forms part of the skeleton of the final system. Think of prototyping as the
  > reconnaissance and intelligence gathering that takes place before a single
  > tracer bullet is fired.

  ## Design by contract

  To understand DBC we have to think of a computational process as involving two
  stages: the call and the execution of the routine that happens in response to
  the call (henceforth **caller** and **routine**).

  - the caller could be a function expression that invokes a function and passes
    arguments to it expecting a given output. The function that executes is the
    routine
  - the caller could be an object instantiation that calls a method belonging to
    its parent class
  - the caller could be a parent React component that passes props to a child
    component

  Design by contract means specifying clear and inviolable rules detailing what
  must obtain at both the call stage and the routine stage if the process is to
  execute.

  Every function and method in a software system does something. Before it starts
  that something, the routine may have some expectation of the state of the world
  and it may be able to make a statement about the state of the world when it
  concludes. These expectations are defined in terms of preconditions,
  postconditions, and invariants. They form that basis of a **contract** between
  the caller and the routine. Hence _design by contract\*\*._\*\*

  ### Preconditions

  Preconditions specify what must be true in order for the routine to be called.
  In other words, the requirements of the routine. What it needs and what should
  be the case before it even considers executing the task. A **routine should
  never get called when its preconditions would be violated**.

  ### Postconditions

  Providing the preconditions are met, this is what the routine is guaranteed to
  do. In other words: the state of affairs that must obtain after the routine has
  ran.

  ### Invariants

  Once established, the preconditions and postconditions should not change. If
  they need to change, that is a separate process and contract. In the processing
  of a routine, the data may be variant relative to the contract, but by the end
  the overall conditions establish the equilibrium of the contract.

  There is an analogue here with functional programming philosophy: the function
  should always return the same sort of output, without ancillary processes
  happening, i.e side-effects.

  One way to achieve this is to be miserly when setting up the contract, which
  overlaps with orthogonality. Only specify the minimum return on a contract
  rather than multiple postconditions. This only increases the likelihood that the
  contract will be breached at some point. If you need multiple postconditions,
  spread them out an achieve them in a compositional way, with multiple separate
  and modular processes.

  > Be strict in what you will accept before you begin, and promise as little as
  > possible in return. If your contract indicates that you'll accept anything and
  > promise the world in return, then you've got a lot of code to write!

  ### Division of responsibilities

  > If all the routine's preconditions are met by the caller, the routine shall
  > guarantee that all postconditions and invariants will be true when it
  > completes.

  Note that the emphasis of responsibilities is on the caller.

  Imagine that we have a function that returns the count of an array of integers.
  It is not the job of the count routine to verify that it has been passed
  integers and then to execute the count. Or, in the event that it is not passed
  integers, to mutate the data to integers and then execute.

  This should be resolved by the caller: it is the responsibility of the caller to
  pass integers. If it doesn't, the routine simply crashes or raises an exception.
  It doesn't try to accommodate the input because that does not come down on its
  side of the contract. The caller has failed to meet the preconditions. If, due
  to some bug, the routine receives integers and fails to output the sum, then it
  has failed on its side

  ### Example: type checking

  An obvious example of this philosophy is when you perform checks or validation
  within your code (although validation is more of an issue when you are dealing
  with user data, not your own internal code). For instance using type checking
  with dynamically-typed languages.

  When we use the `prop-types` library with React we are specifying preconditions:
  so long at the prop (effectively the caller) passed to the component
  (effectively the routine) is of type X, the component will render invariantly as
  R. If the prop is of type Y, an exception will be raised highlighting a breach
  in the contract.

  Another example would be more advanced type checking with Javascript written
  using Typescript.

  ## The Law of Demeter

  Demeter's Law has applicability chiefly when programming with classes.

  It's a fancy name for a simple principle summarised by 'don't talk to
  strangers'. Demeter's law is violated when code has more than one step between
  classes. You should avoid invoking methods of an object returned by another
  method. You should only use your own methods when dealing with it.

  ### Formal

  A method _m_ of object _O_ may only invoke the methods of the following kinds of
  objects:

  - _O_ itself
  - _m_'s parameters
  - any objects created or instantiated within _m_
  - _O_'s direct component objects (in other words nested objects)
  - a global variable (over and above _O_) accessible by _O_, within the scope of
    _m_

  ## Model, View, Controller design pattern

  The key concept behind the MVC idiom is separating the model from both the GUI
  that represents it and the controls that manage the view.

  - **Model**
    - The abstract data model representing the target object
    - The model has no direct knowledge of any views or controllers
  - **View**
    - A way to interpret the model. It subscribes to changes in the model and
      logical events from the controller
  - **Controller**
    - A way to control the view and provide the model with new data. It publishes
      events to both the model and the view

  For comparison, distinguish React from MVC. In React data is unidirectional: the
  JSX component as controller cannot change the state. The state is passed down to
  the controller. Also MVC lends itself to separation of technologies: code used
  to create the View is different from Code that manages Controller and data
  Model. In React it's all one integrated system.

  ## Refactoring

  > Rewriting, reworking, and re-architecting code is collectively known as
  > refactoring

  ### When to refactor

  - **Duplication**: you've discovered a violation of the DRY principle
  - **Non-orthogonal design**: you've discovered some code or design that could be
    made more orthogonal
  - **Outdated knowledge**: your knowledge about the problem and you skills at
    implementing a solution have changed since the code was first written. Update
    and improve the code to reflect these changes
  - **Performance: y**ou need to move functionality from one area of the system to
    another to improve performance

  ### Tips when refactoring

  - Don't try to refactor and add new functionality at the same time!
  - Make sure you have good tests before you begin refactoring. Run the tests as
    you refactor. That way you will know quickly if your changes have broken
    anything
  - Take short, deliberative steps. Refactoring often involves making many
    localised changes that result in a larger-scale change.

  ## Testing

  > Most developers hate testing. They tend to test-gently, subconsciously knowing
  > where the code will break and avoiding the weak spots. Pragmatic Programmers
  > are different. We are _driven_ to find our bugs _now_, so we don't have to
  > endure the shame of others finding our bugs later.

  ### Unit testing

  A unit test is code that exercises a module. It consists in testing each module
  in isolation to verify its behaviour. Unit testing is the foundation of all
  other forms of testing. If the parts don't work by themselves, they probably
  won't work well together. All the modules you are using must pass their own unit
  tests before you can proceed.

  We can think of unit testing as **testing against contract** (detailed above).
  We want to test that the module delivers the functionality it promises over a
  wide range of test cases and boundary conditions.

  Scope for unit testing should cover:

  - Obviously, returning the expected value/outcome
  - Ensuring that faulty arguments/ types are rejected and initiate error handling
    (deliberately breaking your code to ensure it is handled appropriately)
  - Pass in the boundary and maximum value
  - Pass in values between the zero and the maximum expressible argument to cover
    a range of cases

  Benefits of unit testing include:

  - It creates an example to other developers how to use all of the functionality
    of a given module
  - It is a means to build **regression tests** which can be used to validate any
    future changes to the code. In other words, the future changes should pass the
    older tests to prove they are consistent with the code base

  ### Integration testing

  Integration testing shows that the major subsystems that make up the project
  work and play well with each other.

  Integration testing is really just an extension of the unit testing described,
  only know you're testing how entire subsystems honour their contracts.

  ## Commenting your code

  In general, comments should detail **why** something is done, its purpose and
  its goal. The code already shows _how_ it's done, so commenting on this is
  redundant, and violates the DRY principle.

  > We like to see a simple module-level comment, comments for significant data
  > and type declarations, and a brief class and per-method header describing how
  > the function is used and anything it does that is not obvious

  ```js
  /*
  Find the highest value within a specified data range of samples

  Parameter: aRange = range of dates to search for data
  Parameter: aThreshold = minimum value to consider

  Return: the value, or null if no value found that is greater than or equal to the threshold
  */
  ```


path: zk/Theorems_and_empty_sets.md
sortable_path: zkTheorems_and_empty_sets.md
lead:
  ---
  tags:
  - propositional-logic
  - logic
  ---

checksum: 8cbbe399ffc21557ca7427d6cf2f73ba6ff3b4108525e763d431beff7a05df80
created: 2024-03-30 09:03:21.481695218+00:00
modified: 2024-06-16 16:53:10.369960753+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  We know that when we construct a
  [derivation](Formal_proofs_in_propositional_logic.md#derivation-rules)
  we start from a set of assumptions and then attempt to reach a proposition that
  is a consequence of the starting assumptions. However it does not always have to
  be the case that the starting set contains members. The set can in fact be
  empty.

  _Demonstration_

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

  We see in this example that there is no starting set and thus no primary
  assumptions. Instead we start with nothing other than the proposition we wish to
  derive. The proposition is effectively derived from itself. In these scenarios
  we say that we are constructing a derivation from an **empty set**.

  Propositions which possess this property are called theorems:

  > A proposition $P$ or a system of propositions in propositional logic is a
  > theorem in a system of derivation for that logic if $P$ is derivable from the
  > empty set.

  We represent a theorem as:

  $$
  \vdash P
  $$

  (There is no preceding $\Gamma$ as the set is empty. )

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

  We know that when we construct a
  [derivation](Formal_proofs_in_propositional_logic.md#derivation-rules)
  we start from a set of assumptions and then attempt to reach a proposition that
  is a consequence of the starting assumptions. However it does not always have to
  be the case that the starting set contains members. The set can in fact be
  empty.

  _Demonstration_

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

  We see in this example that there is no starting set and thus no primary
  assumptions. Instead we start with nothing other than the proposition we wish to
  derive. The proposition is effectively derived from itself. In these scenarios
  we say that we are constructing a derivation from an **empty set**.

  Propositions which possess this property are called theorems:

  > A proposition $P$ or a system of propositions in propositional logic is a
  > theorem in a system of derivation for that logic if $P$ is derivable from the
  > empty set.

  We represent a theorem as:

  $$
  \vdash P
  $$

  (There is no preceding $\Gamma$ as the set is empty. )


path: zk/Three_bit_counter.md
sortable_path: zkThree_bit_counter.md
title: 3-bit Counter
checksum: d3e61670e92e716e99fdbe0432e9429cb474fedc99fda4f8b1252e04786fb0aa
created: 2024-03-30 09:03:21.492163634+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["logic-gates","binary","memory","clock"]}
lead:
  To demonstrate the use of
  [Flip-Flops](Flip_flops.md) we will
  create the circuit for a 3-bit counter. This simply counts up from 0 to 7
  because 7 is the maximum decimal number we can create with three bits ($2^3$):

body:
  To demonstrate the use of
  [Flip-Flops](Flip_flops.md) we will
  create the circuit for a 3-bit counter. This simply counts up from 0 to 7
  because 7 is the maximum decimal number we can create with three bits ($2^3$):

  | Binary | Decimal |
  | ------ | ------- |
  | 000    | 0       |
  | 001    | 1       |
  | 010    | 2       |
  | 011    | 3       |
  | 100    | 4       |
  | 101    | 5       |
  | 110    | 6       |
  | 111    | 7       |

  The circuit will have three memory components, each representing one bit of the
  3-bit number. When the
  [clock pulses](Clock_signals.md), the
  3-bit number increments by one. We need to synchronise the operation with a
  clock because each bit by itself is meaningless, it only gains meaning by the
  relation it sustains to the other two bits hence it must be kept in sync with
  them.

  We can derive the circuit arrangement by analysing the outputs of a flipflop at
  each pulse:

  | Combined bits | Q2  | Q1  | Q0  | Decimal |
  | ------------- | --- | --- | --- | ------- |
  | 000           | 0   | 0   | 0   | 0       |
  | 001           | 0   | 0   | 1   | 1       |
  | 010           | 0   | 1   | 0   | 2       |
  | 011           | 0   | 1   | 1   | 3       |
  | 100           | 1   | 0   | 0   | 4       |
  | 101           | 1   | 0   | 1   | 5       |
  | 110           | 1   | 1   | 0   | 6       |
  | 111           | 1   | 1   | 1   | 7       |

  If we look at the pattern of each flip-flops' output we notice the following:

  - _Q0_ toggles on every pulse
  - _Q1_ toggles every time _Q0_ was previously 1
  - _Q2_ toggles when both _Q1_ and _Q0_ were previously 1

  This means that to construct a circuit that displays this behaviour we just have
  to use a
  [T flip-flop](Flip_flops.md#t-flip-flops)
  since the only state change we need is a single bit toggle three times that
  retains its value.

  Using these pulse patterns we can construct a circuit as follows:
  ![](/img/3-bit-adder-diagram.png)

  <iframe src="https://circuitverse.org/simulator/embed/3-bit-counter-d33846e3-7538-427d-b4cc-dc64fdaf0af3?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="500" width="600" allowFullScreen></iframe>

  <br />

  > Remember from the table: Q2 is the most significant bit and Q0 is the least
  > significant bit so we should read the output in the interactive circuit
  > backwards, reversing the order. Then we will see that the cicuit is cyling
  > through 0-7

  This is how the circuit works:

  - All three flip-flops are connected to the same clock signal so that they are
    synchnonised
  - Because _T0_ is connected to the voltage source _Q0_ toggles every time the
    clock pulses
  - _T1_ is connected to _Q0_ so a clock pulse causes _Q1_ to toggle only when
    _Q0_ is high in line with the table
  - _T2_ is connected to _Q0 AND Q1_ so _Q2_ only toggles on a clock pulse when
    _Q0_ and _Q1_ are both high

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

  # 3-bit Counter

  To demonstrate the use of
  [Flip-Flops](Flip_flops.md) we will
  create the circuit for a 3-bit counter. This simply counts up from 0 to 7
  because 7 is the maximum decimal number we can create with three bits ($2^3$):

  | Binary | Decimal |
  | ------ | ------- |
  | 000    | 0       |
  | 001    | 1       |
  | 010    | 2       |
  | 011    | 3       |
  | 100    | 4       |
  | 101    | 5       |
  | 110    | 6       |
  | 111    | 7       |

  The circuit will have three memory components, each representing one bit of the
  3-bit number. When the
  [clock pulses](Clock_signals.md), the
  3-bit number increments by one. We need to synchronise the operation with a
  clock because each bit by itself is meaningless, it only gains meaning by the
  relation it sustains to the other two bits hence it must be kept in sync with
  them.

  We can derive the circuit arrangement by analysing the outputs of a flipflop at
  each pulse:

  | Combined bits | Q2  | Q1  | Q0  | Decimal |
  | ------------- | --- | --- | --- | ------- |
  | 000           | 0   | 0   | 0   | 0       |
  | 001           | 0   | 0   | 1   | 1       |
  | 010           | 0   | 1   | 0   | 2       |
  | 011           | 0   | 1   | 1   | 3       |
  | 100           | 1   | 0   | 0   | 4       |
  | 101           | 1   | 0   | 1   | 5       |
  | 110           | 1   | 1   | 0   | 6       |
  | 111           | 1   | 1   | 1   | 7       |

  If we look at the pattern of each flip-flops' output we notice the following:

  - _Q0_ toggles on every pulse
  - _Q1_ toggles every time _Q0_ was previously 1
  - _Q2_ toggles when both _Q1_ and _Q0_ were previously 1

  This means that to construct a circuit that displays this behaviour we just have
  to use a
  [T flip-flop](Flip_flops.md#t-flip-flops)
  since the only state change we need is a single bit toggle three times that
  retains its value.

  Using these pulse patterns we can construct a circuit as follows:
  ![](/img/3-bit-adder-diagram.png)

  <iframe src="https://circuitverse.org/simulator/embed/3-bit-counter-d33846e3-7538-427d-b4cc-dc64fdaf0af3?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="500" width="600" allowFullScreen></iframe>

  <br />

  > Remember from the table: Q2 is the most significant bit and Q0 is the least
  > significant bit so we should read the output in the interactive circuit
  > backwards, reversing the order. Then we will see that the cicuit is cyling
  > through 0-7

  This is how the circuit works:

  - All three flip-flops are connected to the same clock signal so that they are
    synchnonised
  - Because _T0_ is connected to the voltage source _Q0_ toggles every time the
    clock pulses
  - _T1_ is connected to _Q0_ so a clock pulse causes _Q1_ to toggle only when
    _Q0_ is high in line with the table
  - _T2_ is connected to _Q0 AND Q1_ so _Q2_ only toggles on a clock pulse when
    _Q0_ and _Q1_ are both high


path: zk/Transistors.md
sortable_path: zkTransistors.md
title: Transistors
checksum: 7eed21ad50bb1212853a610fa8c4f924384ab5638d6f02c7290dd1aadc225073
created: 2024-03-30 09:03:21.508773552+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["logic-gates","binary"]}
lead:
  In the discussion of
  [digital circuits](Digital_circuits.md)
  we noted that a digital circuit requires that electrical phenomena be treated as
  discrete rather than continuous values. Although a given voltage at a point in
  the circuit can vary widely, in order to represent the binary states of 'on' and
  'off' we need it to remain fixed within certain narrow parameters.
  Typi>understanding the concept and then with transistors which are what are
  actually used in computers.

body:
  In the discussion of
  [digital circuits](Digital_circuits.md)
  we noted that a digital circuit requires that electrical phenomena be treated as
  discrete rather than continuous values. Although a given voltage at a point in
  the circuit can vary widely, in order to represent the binary states of 'on' and
  'off' we need it to remain fixed within certain narrow parameters.
  Typi>understanding the concept and then with transistors which are what are
  actually used in computers.

  ## Implementing binary logic with mechanical switches

  An electrical switch is inherently binary. When the switch is on, it acts like a
  simple copper wire through which current flows freely. When the switch is off,
  it acts like an open circuit and no current can flow.

  We can combine switches in a circuit to create analogs to logic gates.

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

  In the example above a simple AND gate is implemented with switches. Each switch
  is a conjunct and the current only flows if both switches are on, closing the
  circuit.

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

  In the example above is a circuit implementing an OR gate. The current flows
  just if one of the switches are on or if both of the switches are on but not if
  both switches are off.

  ## Transistors

  In real digital circuits, mechanical switches would be totally impractical. The
  number of switches required is too numerous and we need to be able to connect
  and interconnect the output of many circuits together. The output of one circuit
  needs to be fed into another and there is no way to do this with switches.

  Thus instead of switches, modern digital circuits use transistors, a special
  electrical component that controls the flow of current in the manner of a switch
  where the 'off' and 'on' states are represented by
  [voltage](Voltage.md) values within
  set parameters.

  There are different types of transistors but the simplest for the purposes of
  explanation are **bipolar junction transistors**.

  ![](/img/transistor-diag.svg)

  A transistor works as follows: applying a small amount of current at the base
  allows a larger current to flow from the collector to the emitter. Relating this
  back to switches, applying current to the base is like turning the switch on.
  Removing this current is like turning the switch off.

  The diagrams below show a transistor being used in a circuit to create 'on' and
  'off' switch states alongside a switch based circuit.

  ![](/img/transistor-off.png)

  ![](/img/transistor-on.png)

  - $V^{in}$ is the voltage that electrically controls the switch-as-transistor
  - $V^{out}$ is the voltage we want to control: it will be high when the
    transistor is in the 'on' state and low otherwise
  - $V^{cc}$ stands for "common collector" and is the positive supply voltage
    appliced to the collector terminal is "on".

  When the voltage at the base is low (in the diagram it is grounded to ensure
  this) no current flows from the c We recall that voltage is the potential
  difference between two points or terminals in a circuit. High voltage increases
  the flow of current, low voltage restricts or reduces it.

  When the voltage at the base is high a current flows from the collector to the
  emitter and the transistor is "on".

  When the voltage at the base is low (in the diagram it is grounded to ensure
  this) no current flows from the collector to the emitter and the transistor is
  "off".

  ## Transistors and logic gates

  With the basic element of the transistor established, we can combine transistors
  to create logic gates. A logic gate is a combination/sequence of transistors
  where the logical function is represented by the characteristic input and output
  voltages.

  For example to create an
  [AND](Logic_gates.md#and-gate) gate
  we would have two voltage inputs going into two transistors that are connected
  in sequence. The two transistors create a continuous line going from the
  collector of one to the emitter of the other. If either voltage input is low
  then the voltage of the combined line is low (equivalent to the circuit being
  broken) and there is no current flowing.

  ![](/img/and-transistor.png)

  Below, an
  [OR](Logic_gates.md#or-gate) has been
  constructed with transistors. If a voltage is applied to the base of either
  transistor, the current reaches the V-out terminal.

  ![](/img/or-transistor.svg)

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

  # Transistors

  In the discussion of
  [digital circuits](Digital_circuits.md)
  we noted that a digital circuit requires that electrical phenomena be treated as
  discrete rather than continuous values. Although a given voltage at a point in
  the circuit can vary widely, in order to represent the binary states of 'on' and
  'off' we need it to remain fixed within certain narrow parameters.
  Typi>understanding the concept and then with transistors which are what are
  actually used in computers.

  ## Implementing binary logic with mechanical switches

  An electrical switch is inherently binary. When the switch is on, it acts like a
  simple copper wire through which current flows freely. When the switch is off,
  it acts like an open circuit and no current can flow.

  We can combine switches in a circuit to create analogs to logic gates.

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

  In the example above a simple AND gate is implemented with switches. Each switch
  is a conjunct and the current only flows if both switches are on, closing the
  circuit.

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

  In the example above is a circuit implementing an OR gate. The current flows
  just if one of the switches are on or if both of the switches are on but not if
  both switches are off.

  ## Transistors

  In real digital circuits, mechanical switches would be totally impractical. The
  number of switches required is too numerous and we need to be able to connect
  and interconnect the output of many circuits together. The output of one circuit
  needs to be fed into another and there is no way to do this with switches.

  Thus instead of switches, modern digital circuits use transistors, a special
  electrical component that controls the flow of current in the manner of a switch
  where the 'off' and 'on' states are represented by
  [voltage](Voltage.md) values within
  set parameters.

  There are different types of transistors but the simplest for the purposes of
  explanation are **bipolar junction transistors**.

  ![](/img/transistor-diag.svg)

  A transistor works as follows: applying a small amount of current at the base
  allows a larger current to flow from the collector to the emitter. Relating this
  back to switches, applying current to the base is like turning the switch on.
  Removing this current is like turning the switch off.

  The diagrams below show a transistor being used in a circuit to create 'on' and
  'off' switch states alongside a switch based circuit.

  ![](/img/transistor-off.png)

  ![](/img/transistor-on.png)

  - $V^{in}$ is the voltage that electrically controls the switch-as-transistor
  - $V^{out}$ is the voltage we want to control: it will be high when the
    transistor is in the 'on' state and low otherwise
  - $V^{cc}$ stands for "common collector" and is the positive supply voltage
    appliced to the collector terminal is "on".

  When the voltage at the base is low (in the diagram it is grounded to ensure
  this) no current flows from the c We recall that voltage is the potential
  difference between two points or terminals in a circuit. High voltage increases
  the flow of current, low voltage restricts or reduces it.

  When the voltage at the base is high a current flows from the collector to the
  emitter and the transistor is "on".

  When the voltage at the base is low (in the diagram it is grounded to ensure
  this) no current flows from the collector to the emitter and the transistor is
  "off".

  ## Transistors and logic gates

  With the basic element of the transistor established, we can combine transistors
  to create logic gates. A logic gate is a combination/sequence of transistors
  where the logical function is represented by the characteristic input and output
  voltages.

  For example to create an
  [AND](Logic_gates.md#and-gate) gate
  we would have two voltage inputs going into two transistors that are connected
  in sequence. The two transistors create a continuous line going from the
  collector of one to the emitter of the other. If either voltage input is low
  then the voltage of the combined line is low (equivalent to the circuit being
  broken) and there is no current flowing.

  ![](/img/and-transistor.png)

  Below, an
  [OR](Logic_gates.md#or-gate) has been
  constructed with transistors. If a voltage is applied to the base of either
  transistor, the current reaches the V-out terminal.

  ![](/img/or-transistor.svg)


path: zk/Troubleshooting.md
sortable_path: zkTroubleshooting.md
title: Troubleshooting MongoDB connection issues
lead: ## Arch Linux troubleshooting
checksum: 764b19f1d36a6c0d05f28bf1f30d52c829fe3030cef31f8e3c8efabf28f1f4c3
created: 2024-03-30 09:03:21.533367553+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["mongo-db"]}
body:
  ## Arch Linux troubleshooting

  Most times any problems will be a result of a Mongo process that is already
  running. Resolve with:

  ```bash
  # Kill running Mongo process...
  # Identify PID
  sudo lsof -iTCP -sTCP:LISTEN -n -P
  sudo kill [pid]
  ```

  Otherwise try the below.

  ```bash
  # Check that the systemd service is runnign for Mongo
  sudo systemctl status mongodb

  # If not, enable and start service (only need to do this once for machine)
  sudo systemctl enable --now mongodb
  sudo systemctl start --now mongodb

  # If issues with Mongo daemon...
  # Remove the current socket
  rm /tmp/mongodb-27017.sock
  ```

  For additional command line utilities you will also need to install
  `mongodb-tools` from the AUR.

raw_content:
  ---
  tags: [mongo-db]
  ---

  # Troubleshooting MongoDB connection issues

  ## Arch Linux troubleshooting

  Most times any problems will be a result of a Mongo process that is already
  running. Resolve with:

  ```bash
  # Kill running Mongo process...
  # Identify PID
  sudo lsof -iTCP -sTCP:LISTEN -n -P
  sudo kill [pid]
  ```

  Otherwise try the below.

  ```bash
  # Check that the systemd service is runnign for Mongo
  sudo systemctl status mongodb

  # If not, enable and start service (only need to do this once for machine)
  sudo systemctl enable --now mongodb
  sudo systemctl start --now mongodb

  # If issues with Mongo daemon...
  # Remove the current socket
  rm /tmp/mongodb-27017.sock
  ```

  For additional command line utilities you will also need to install
  `mongodb-tools` from the AUR.


path: zk/Truth_functional_connectives.md
sortable_path: zkTruth_functional_connectives.md
title: Truth-functional connectives
checksum: 3c886282a2241af43af36a2ea37b6afc0518d29737ccc87107855f072226efeb
created: 2024-03-30 09:03:21.539863826+00:00
modified: 2024-06-16 16:53:14.529791538+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  Propositions generated from other (simple) propositions by means of
  propositional connectives are
  [compound propositions](Atomic_and_molecular_propositions.md).

body:
  Propositions generated from other (simple) propositions by means of
  propositional connectives are
  [compound propositions](Atomic_and_molecular_propositions.md).

  We know that
  [logically determinant](Logical_indeterminacy.md)
  propositions express a truth value. When simple propositions are joined with a
  connective to make a compound proposition they also have a truth value. This is
  determined by the nature of the connective and the truth value of the
  constituent propositions. We therefore call connectives of this nature truth
  _functional_ connectives since the **truth value of the compound is a function
  of the truth values of its components**.

  > A propositional connective is used truth-functionally if and only if it is
  > used to generate a compound proposition from one or more propositions in such
  > a way that the truth value of the generated compound is wholly determined by
  > the truth-values of those one or more propositions from which the compound is
  > generated, no matter what the truth values may be.

  Each truth-functional connective has a characteristic **truth table**. This
  discloses the conditions under which the constituent propositions have a given
  truth value when combined with one or more connectives.

  We shall now review each of the truth-functional connectives in detail.

  ### Conjunction

  Conjunction is equivalent to the word AND in natural language. We use $\land$ as
  the symbol for this connective.

  A molecular proposition joining two conjuncts P and Q is true iff both conjuncts
  are true and false otherwise:

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ### Disjunction

  Conjunction is equivalent to the word OR in natural language. We use `v` as the
  symbol of this connective.

  A molecular proposition joining two disjuncts P and Q is true if either disjunct
  is true or if both disjuncts are true and false otherwise. This corresponds to
  the inclusive sense of OR in natural language.

  ```
  P	Q				P	∨	Q
  T	T					T
  T	F					T
  F	T					T
  F	F					F
  ```

  ### Negation

  In contrast to the two previous connectives, negation is a unary connective not
  a binary connective. We use `~` to symbolise negation. It does not join two or
  more propositions, it applies to one proposition as a whole. This can be a
  simple proposition or a complex proposition. It simply negates the truth-value
  of whichever proposition it is applied to. Hence applied to P, it is true if P
  is false. And if P is false, it is true when P is true. !

  ```
  P			~	P
  T			F
  F			T
  ```

  ### Material conditional (a.k.a implication)

  The material conditional approximates the meaning expressed in natural language
  when we say _if_ such-and-such is the case _then_ such-and-such will the case.
  Another way of expressing the sense of the material conditional is to say that
  **P** implies **Q.**

  ```
  If it rains today the pavement will be wet.
  ```

  We call the proposition that expresses the 'if' proposition the **antecedent**
  and the proposition that expresses the 'then' statement the **consequent**. The
  symbol we use to represent the material conditional is `⊃` although you may see
  `→` used as well.

  The truth table is as follows:

  ```
  P	Q				P	⊃	Q
  T	T					T
  T	F					F
  F	T					T
  F	F					T
  ```

  The material conditional is perhaps the least intuitive of the logical
  connectives. The first case (TT) closely matches what we expect the connective
  to mean: it has rained so the pavement is wet. The antecedent is true and
  therefore the consequent is true. This chimes with what we tend to mean by 'if'
  in natural language. In the second case (TF) it also makes sense: the complex
  proposition is false because it rained and the pavement wasn't wet: this negates
  the truth of the expression. The final case (FF) is also straight forward. It
  didn't rain therefore the pavement wasn't wet, thus the overall assertion that
  rain implies wet pavements is retained.

  FT is less intuitive:

  ```
  It did not rain today. The pavement was wet.
  ```

  To some degree one just has to take these statements as axioms, whether or not
  they have intuitive sense is a secondary, more philosophical question. The
  semantic issues arise because we tacitly assume the material conditional to be a
  causal connective: there is something about the nature of **P** that _engenders_
  or _brings about_ **Q** but causality is not a logical concern.

  If we instead just focus on the simple propositions that comprise the truth
  value it is more plausible. In the case of FT we can say it didn't rain yet the
  pavement was wet does not stop the pavement being wet when it rains. The fact
  that I can pour a beer on the pavement thereby making it wet doesn't stop or
  render false the idea that the rain can also make the pavement wet. The same
  explanation covers the FF case: it hasn't rained and so the pavement is not wet
  does not contradict the assertion that when it rains the pavement will be wet.

  Things are elucidated when we look at an equivalent expression of P ⊃ Q, ~P v Q:

  ```
  P	Q				~	P	∨	Q
  T	T						T
  T	F						F
  F	T						T
  F	F						T
  ```

  A disjunction is true whenever either disjunct is true so when both are false
  the overall expression is false, the same as with FT and FF with the material
  conditional.

  ### Material biconditional (a.k.a equivalence)

  The material biconditional equates to the English expression 'if and only if',
  as a conditional connective it therefore avoids some of the perplexity aroused
  by its material cousin. In this scenario both antecedent and consequent have to
  be true for the overall expression to be true. If either is false the complex
  proposition is false. Other ways of expressing the semantics of this connective
  is to say that one proposition implies the other or that **P** and **Q** are
  equivalent.

  ```
  If and only if James studies every day he will pass the exam.
  ```

  There is no possibility in which James passes the exam and has not studied every
  day. If he studies for three out of the seven days leading up to the exam he
  will not pass. Alternatively, there is no possibility that James studied every
  day yet failed the exam. The antecedent and consequent are locked, as indicated
  by the truth-table:

  ```
  P	Q				P	≡	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					T
  ```

  The last condition (FF) maybe requires some explanation: if he has not studied
  every day then he cannot have passed the exam. Therefore, to say that he will
  pass iff he studies every day is rendered true.

  ## Combinations of truth-functional connectives

  ---

  So far we have applied connectives to simple propositions. In so doing we
  generate complex propositions. However propositions and connectives are
  inherently generative: we can build more complex expressions from less complex
  parts, using more than one type of connective or several different connectives
  to make larger complex propositions and express more detailed logical conditions
  ans statements about the world.

  For example the proposition:

  ```
  Socrates was either a philosopher or a drinker but he wasn't a politician.
  ```

  Can be expressed with greater logical clarity as:

  ```
  Socrates was a philosopher or Socrates was a drinker and Socrates was not a politician.
  ```

  Using P for 'Socrates was a philosopher', Q for 'Socrates was a drinker' and R
  for 'Socrates was a politician' we can express this symbolically as:

  ```
  (P v Q) & ~R
  ```

  Which has the truth table:

  ```
  P	Q	R				(	P	∨	Q	)	&	~	R
  T	T	T									F
  T	T	F									T
  T	F	T									F
  T	F	F									T
  F	T	T									F
  F	T	F									T
  F	F	T									F
  F	F	F									F
  ```

  Let's walk through each case where S stands for the overall proposition.

  1. S is false if Socrates was a philosopher, a drinker and a politician.
  1. **S is true if Socrates was a philosopher, a drinker but not a politician.**
  1. S is false if Socrates was a philosopher, a politician but not a drinker.
  1. **S is true if Socrates was a philosopher but not a drinker or politician.**
  1. S is false if Socrates was not a philosopher but was a drinker and politician
  1. **S is true if Socrates was not a philosopher or politician but was a
     drinker.**
  1. S is false if Socrates was neither a philosopher or drinker but was a
     politician.
  1. S is false if Socrates was neither a philosopher, drinker, or politician.

  If we look just at the true cases for simplicity, it becomes obvious that the
  truth value of the whole is a function of the truth-values of the parts.

  At the highest level of generality the proposition is a conjunction with two
  disjuncts: `P v Q` and `~R` . Therefore, for the proposition to be true both
  conjuncts must be true. The first conjunct is true just if one of the
  subordinate disjuncts is true (Socrates is either a philosopher, a drinker, or
  both). The second conjunct is true just if Socrates is not a politician. Thus
  there is only one variation for the second conjunct (not being a politician) and
  two variations for the first conjunct (being a drinker/being a philosopher)
  hence there are three cases where the overall proposition is true.

  ### Logical equivalence

  Once we start working with complex propositions with more than one
  truth-functional connective it becomes clear that the same proposition expressed
  in natural language can be expressed formally more than one way and thus that in
  logical terms, both formal expressions are equivalent. We can prove this
  equivalence by comparing truth tables.

  For example the proposition:

  ```
  I am going to the shops and the gym.
  ```

  Can obviously be expressed formally as:

  ```
  P & Q
  ```

  But also as:

  ```
  ~ (~P v ~Q)
  ```

  And we know this because the truth-tables are identical:consistency

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ```
  P	Q			~	(	~	P	∨	~	Q	)
  T	T			T
  T	F			F
  F	T			F
  F	F			F
  ```

  Another example of equivalent expressions:

  ```
  Neither Watson or Sherlock Holmes is fond of criminals
  ```

  The first formalisation:

  ```
  ~P & ~Q
  ```

  Equivalent to:

  ```
  ~(P v Q)
  ```

  Again the truth-tables for verification:

  ```
  P	Q				~	P	&	~	Q
  T	T						F
  T	F						F
  F	T						F
  F	F						T
  ```

  `~P & ~Q`

  ```
  P	Q			~	(	P	∨	Q	)
  T	T			F
  T	F			F
  F	T			F
  F	F			T
  ```

  ### Important equivalences

  The example above is a key equivalence that you will encounter a lot especially
  when deriving formal proofs. It goes together with another one. We have noted
  them both below for future reference:

  ```
  ~P & ~Q = ~P v ~Q
  ```

  ```
  ~P v ~Q = ~(P & Q)
  ```

  ## Enforcing binary connectives through bracketing

  If we had a proposition of the form

  ```
  Socrates is man, is mortal and a philosopher.
  ```

  We could not write this as:

  ```
  P & Q & R
  ```

  This would not be a well-formed proposition because at most truth functional
  connectives can only connect two simple propositions. It would not be possible
  to generate truth conditions for this proposition in its current form. Instead
  we introduce brackets to enforce a binary grouping of simple propositions. In
  this instance, the placement of the brackets does not affect the accurate
  interpretation of the truth conditions of the compound, so the following two
  formalisations are equivalent:

  ```
  (P & Q) & R

  P & (Q & R)
  ```

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

  # Truth-functional connectives

  Propositions generated from other (simple) propositions by means of
  propositional connectives are
  [compound propositions](Atomic_and_molecular_propositions.md).

  We know that
  [logically determinant](Logical_indeterminacy.md)
  propositions express a truth value. When simple propositions are joined with a
  connective to make a compound proposition they also have a truth value. This is
  determined by the nature of the connective and the truth value of the
  constituent propositions. We therefore call connectives of this nature truth
  _functional_ connectives since the **truth value of the compound is a function
  of the truth values of its components**.

  > A propositional connective is used truth-functionally if and only if it is
  > used to generate a compound proposition from one or more propositions in such
  > a way that the truth value of the generated compound is wholly determined by
  > the truth-values of those one or more propositions from which the compound is
  > generated, no matter what the truth values may be.

  Each truth-functional connective has a characteristic **truth table**. This
  discloses the conditions under which the constituent propositions have a given
  truth value when combined with one or more connectives.

  We shall now review each of the truth-functional connectives in detail.

  ### Conjunction

  Conjunction is equivalent to the word AND in natural language. We use $\land$ as
  the symbol for this connective.

  A molecular proposition joining two conjuncts P and Q is true iff both conjuncts
  are true and false otherwise:

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ### Disjunction

  Conjunction is equivalent to the word OR in natural language. We use `v` as the
  symbol of this connective.

  A molecular proposition joining two disjuncts P and Q is true if either disjunct
  is true or if both disjuncts are true and false otherwise. This corresponds to
  the inclusive sense of OR in natural language.

  ```
  P	Q				P	∨	Q
  T	T					T
  T	F					T
  F	T					T
  F	F					F
  ```

  ### Negation

  In contrast to the two previous connectives, negation is a unary connective not
  a binary connective. We use `~` to symbolise negation. It does not join two or
  more propositions, it applies to one proposition as a whole. This can be a
  simple proposition or a complex proposition. It simply negates the truth-value
  of whichever proposition it is applied to. Hence applied to P, it is true if P
  is false. And if P is false, it is true when P is true. !

  ```
  P			~	P
  T			F
  F			T
  ```

  ### Material conditional (a.k.a implication)

  The material conditional approximates the meaning expressed in natural language
  when we say _if_ such-and-such is the case _then_ such-and-such will the case.
  Another way of expressing the sense of the material conditional is to say that
  **P** implies **Q.**

  ```
  If it rains today the pavement will be wet.
  ```

  We call the proposition that expresses the 'if' proposition the **antecedent**
  and the proposition that expresses the 'then' statement the **consequent**. The
  symbol we use to represent the material conditional is `⊃` although you may see
  `→` used as well.

  The truth table is as follows:

  ```
  P	Q				P	⊃	Q
  T	T					T
  T	F					F
  F	T					T
  F	F					T
  ```

  The material conditional is perhaps the least intuitive of the logical
  connectives. The first case (TT) closely matches what we expect the connective
  to mean: it has rained so the pavement is wet. The antecedent is true and
  therefore the consequent is true. This chimes with what we tend to mean by 'if'
  in natural language. In the second case (TF) it also makes sense: the complex
  proposition is false because it rained and the pavement wasn't wet: this negates
  the truth of the expression. The final case (FF) is also straight forward. It
  didn't rain therefore the pavement wasn't wet, thus the overall assertion that
  rain implies wet pavements is retained.

  FT is less intuitive:

  ```
  It did not rain today. The pavement was wet.
  ```

  To some degree one just has to take these statements as axioms, whether or not
  they have intuitive sense is a secondary, more philosophical question. The
  semantic issues arise because we tacitly assume the material conditional to be a
  causal connective: there is something about the nature of **P** that _engenders_
  or _brings about_ **Q** but causality is not a logical concern.

  If we instead just focus on the simple propositions that comprise the truth
  value it is more plausible. In the case of FT we can say it didn't rain yet the
  pavement was wet does not stop the pavement being wet when it rains. The fact
  that I can pour a beer on the pavement thereby making it wet doesn't stop or
  render false the idea that the rain can also make the pavement wet. The same
  explanation covers the FF case: it hasn't rained and so the pavement is not wet
  does not contradict the assertion that when it rains the pavement will be wet.

  Things are elucidated when we look at an equivalent expression of P ⊃ Q, ~P v Q:

  ```
  P	Q				~	P	∨	Q
  T	T						T
  T	F						F
  F	T						T
  F	F						T
  ```

  A disjunction is true whenever either disjunct is true so when both are false
  the overall expression is false, the same as with FT and FF with the material
  conditional.

  ### Material biconditional (a.k.a equivalence)

  The material biconditional equates to the English expression 'if and only if',
  as a conditional connective it therefore avoids some of the perplexity aroused
  by its material cousin. In this scenario both antecedent and consequent have to
  be true for the overall expression to be true. If either is false the complex
  proposition is false. Other ways of expressing the semantics of this connective
  is to say that one proposition implies the other or that **P** and **Q** are
  equivalent.

  ```
  If and only if James studies every day he will pass the exam.
  ```

  There is no possibility in which James passes the exam and has not studied every
  day. If he studies for three out of the seven days leading up to the exam he
  will not pass. Alternatively, there is no possibility that James studied every
  day yet failed the exam. The antecedent and consequent are locked, as indicated
  by the truth-table:

  ```
  P	Q				P	≡	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					T
  ```

  The last condition (FF) maybe requires some explanation: if he has not studied
  every day then he cannot have passed the exam. Therefore, to say that he will
  pass iff he studies every day is rendered true.

  ## Combinations of truth-functional connectives

  ---

  So far we have applied connectives to simple propositions. In so doing we
  generate complex propositions. However propositions and connectives are
  inherently generative: we can build more complex expressions from less complex
  parts, using more than one type of connective or several different connectives
  to make larger complex propositions and express more detailed logical conditions
  ans statements about the world.

  For example the proposition:

  ```
  Socrates was either a philosopher or a drinker but he wasn't a politician.
  ```

  Can be expressed with greater logical clarity as:

  ```
  Socrates was a philosopher or Socrates was a drinker and Socrates was not a politician.
  ```

  Using P for 'Socrates was a philosopher', Q for 'Socrates was a drinker' and R
  for 'Socrates was a politician' we can express this symbolically as:

  ```
  (P v Q) & ~R
  ```

  Which has the truth table:

  ```
  P	Q	R				(	P	∨	Q	)	&	~	R
  T	T	T									F
  T	T	F									T
  T	F	T									F
  T	F	F									T
  F	T	T									F
  F	T	F									T
  F	F	T									F
  F	F	F									F
  ```

  Let's walk through each case where S stands for the overall proposition.

  1. S is false if Socrates was a philosopher, a drinker and a politician.
  1. **S is true if Socrates was a philosopher, a drinker but not a politician.**
  1. S is false if Socrates was a philosopher, a politician but not a drinker.
  1. **S is true if Socrates was a philosopher but not a drinker or politician.**
  1. S is false if Socrates was not a philosopher but was a drinker and politician
  1. **S is true if Socrates was not a philosopher or politician but was a
     drinker.**
  1. S is false if Socrates was neither a philosopher or drinker but was a
     politician.
  1. S is false if Socrates was neither a philosopher, drinker, or politician.

  If we look just at the true cases for simplicity, it becomes obvious that the
  truth value of the whole is a function of the truth-values of the parts.

  At the highest level of generality the proposition is a conjunction with two
  disjuncts: `P v Q` and `~R` . Therefore, for the proposition to be true both
  conjuncts must be true. The first conjunct is true just if one of the
  subordinate disjuncts is true (Socrates is either a philosopher, a drinker, or
  both). The second conjunct is true just if Socrates is not a politician. Thus
  there is only one variation for the second conjunct (not being a politician) and
  two variations for the first conjunct (being a drinker/being a philosopher)
  hence there are three cases where the overall proposition is true.

  ### Logical equivalence

  Once we start working with complex propositions with more than one
  truth-functional connective it becomes clear that the same proposition expressed
  in natural language can be expressed formally more than one way and thus that in
  logical terms, both formal expressions are equivalent. We can prove this
  equivalence by comparing truth tables.

  For example the proposition:

  ```
  I am going to the shops and the gym.
  ```

  Can obviously be expressed formally as:

  ```
  P & Q
  ```

  But also as:

  ```
  ~ (~P v ~Q)
  ```

  And we know this because the truth-tables are identical:consistency

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ```
  P	Q			~	(	~	P	∨	~	Q	)
  T	T			T
  T	F			F
  F	T			F
  F	F			F
  ```

  Another example of equivalent expressions:

  ```
  Neither Watson or Sherlock Holmes is fond of criminals
  ```

  The first formalisation:

  ```
  ~P & ~Q
  ```

  Equivalent to:

  ```
  ~(P v Q)
  ```

  Again the truth-tables for verification:

  ```
  P	Q				~	P	&	~	Q
  T	T						F
  T	F						F
  F	T						F
  F	F						T
  ```

  `~P & ~Q`

  ```
  P	Q			~	(	P	∨	Q	)
  T	T			F
  T	F			F
  F	T			F
  F	F			T
  ```

  ### Important equivalences

  The example above is a key equivalence that you will encounter a lot especially
  when deriving formal proofs. It goes together with another one. We have noted
  them both below for future reference:

  ```
  ~P & ~Q = ~P v ~Q
  ```

  ```
  ~P v ~Q = ~(P & Q)
  ```

  ## Enforcing binary connectives through bracketing

  If we had a proposition of the form

  ```
  Socrates is man, is mortal and a philosopher.
  ```

  We could not write this as:

  ```
  P & Q & R
  ```

  This would not be a well-formed proposition because at most truth functional
  connectives can only connect two simple propositions. It would not be possible
  to generate truth conditions for this proposition in its current form. Instead
  we introduce brackets to enforce a binary grouping of simple propositions. In
  this instance, the placement of the brackets does not affect the accurate
  interpretation of the truth conditions of the compound, so the following two
  formalisations are equivalent:

  ```
  (P & Q) & R

  P & (Q & R)
  ```


path: zk/Truth_tables.md
sortable_path: zkTruth_tables.md
title: Truth-tables
checksum: fee0561b5fde071ff1f5e3551835bd8d38c7041bef880e2b876ad27562c25129
created: 2024-03-30 09:03:21.555210921+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":[]}
lead:
  We are already familiar with truth-tables from the previous entry on the
  _truth-functional connectives_ and the relationship between sentences,
  connectives and the overall truth-value of a sentence. Here we will look in
  further depth at how to build truth-tables and on their mathematical relation to
  binary truth-values. We will also look at examples of complex truth-tables for
  large compound expressions and the systematic steps we follow to derive the
  truth conditions of compound sentences from their simple constituents.

body:
  We are already familiar with truth-tables from the previous entry on the
  _truth-functional connectives_ and the relationship between sentences,
  connectives and the overall truth-value of a sentence. Here we will look in
  further depth at how to build truth-tables and on their mathematical relation to
  binary truth-values. We will also look at examples of complex truth-tables for
  large compound expressions and the systematic steps we follow to derive the
  truth conditions of compound sentences from their simple constituents.

  ## Formulae for constructing truth-tables

  For any truth-table, the number of rows it will contain is equal to $2n$ where:

  - $n$ stands for the number of sentences
  - $2$ is the total number of possible truth values that the sentence may have:
    true or false.

  When we count the number of sentences, we mean atomic sentences. And we only
  count each sentence once. Hence for a compound sentence of the form
  $(\sim B \supset C) & (A \equiv B)$, $B$ occurs twice but there are only three
  sentences: $A$, $B$, and $C$.

  Thus for the sentence $P & Q$ ,we have two sentences so $n$ is 2 which equals 4
  rows (2 x 2):

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  For the sentence $(P \lor Q) & R$ we have three sentences so $n$ is 3 which
  equals 8 rows (2 x 2 x 2):

  ```
  P	Q	R				(	P	∨	Q	)	&	R
  T	T	T									T
  T	T	F									F
  T	F	T									T
  T	F	F									F
  F	T	T									T
  F	T	F									F
  F	F	T									F
  F	F	F									F
  ```

  For the single sentence $P$ we have one sentence so $n$ is 1 which equals 2 rows
  (2 x 1):

  ```
  P			P
  T			T
  F			F
  ```

  This tells us how many rows the truth-table should have but it doesn't tell us
  what each row should consist in. In other words: how many Ts and Fs it should
  contain. This is fine with simple truth-tables since we can just alternate each
  value but for truth-tables with three sentences and more it is easy to make
  mistakes.

  To simplify this and ensure that we are including the right number of possible
  truth-values we can extend the formula to $2n^-i$. This formula tells us how
  many groups of T and F we should have in each column.

  We can already see that there is a pattern at work by looking at the columns of
  the truth tables above. If we take the sentence $(P \lor Q) & R$ we can see that
  for each sentence:

  - $P$ consists in two sets of ${\textsf{T,T,T,T}}$ and ${\textsf{F,F,F,F}}$ with
    **four** elements per set
  - $Q$ consists in four sets of ${\textsf{T,T}}$ , ${\textsf{F,F}}$,
    ${\textsf{T,T}}$ , ${\textsf{F,F}}$ with **two** elements per set
  - $R$ consists in eight sets of ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$,
    ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$
    with **one** element per set.

  If we work through the formula we see that it returns 4, 2, 1:

  $$\begin{equation} \begin{split} 2n^-1 = 3 -1 \\ = 2 \\ = 2 \cdot 2 \\ = 4 \end{split} \end{equation}$$

  $$
  \\begin{equation} \begin{split} 2n^-2 = 3 - 2 \\ = 1 \\ = 2 \cdot 1 \\ = 2 \end{split} \end{equation}
  $$

  $$
  \\begin{equation} \begin{split} 2n^-3 = 3 - 3 \\ = 0 \\ = 2 \cdot 0 \\ = 1 \end{split} \end{equation}
  $$

  ## Truth-table concepts

  ### Recursion

  When we move to complex truth-tables with more than one connective we realise
  that truth-tables are recursive. The truth-tables for the truth-functional
  connectives provide all that we need to determine the truth-values of complex
  sentences:

  > The core truth-tables tell us how to determine the truth-value of a molecular
  > sentence given the truth-values of its
  > [immediate sentential components](Syntax%20of%20sentential%20logic.md). And if
  > the immediate sentential components of a molecular sentence are also
  > molecular, we can use the information in the characteristic truth-tables to
  > determine how the truth-value of each immediate component depends n the
  > truth-values of _its_ components and so on.

  ### Truth-value assignment

  > A truth-value assignment is an assignment of truth-values (either T or F) to
  > the atomic sentences of SL.

  When working on complex truth tables, we use the truth-assignment of atomic
  sentences to count as the values that we feed into the larger expressions at a
  higher level of the sentential abstraction.

  ### Partial assignment

  We talk about partial assignments of truth-values when we look at one specific
  row of the truth-table, independently of the others. The total set of partial
  assignments comprise all possible truth assignments for the given sentence.

  ## Working through complex truth-tables

  The truth-table below shows all truth-value assignments for the sentence
  $(\sim B \supset C) & (A \equiv B)$ :

  ```
  A	B	C				(	~	B	⊃	C	)	&	(	A	≡	B	)
  T	T	T					F	T	T	T		T		T	T	T
  T	T	F					F	T	T	F		T		T	T	T
  T	F	T					T	F	T	T		F		T	F	F
  T	F	F					T	F	F	F		F		T	F	F
  F	T	T					F	T	T	T		F		F	F	T
  F	T	F					F	T	T	F		F		F	F	T
  F	F	T					T	F	T	T		T		F	T	F
  F	F	F					T	F	F	F		F		F	T	F
  ```

  As with algebra we work outwards from each set of brackets. The sequence for
  manually arriving at the above table would be roughly as follows:

  1. For each sentence letter, copy the truth value for it in each row.
  1. Identify the connectives in the atomic sentences and the main overall
     sentence.
  1. Work out the truth-values for the smallest connectives and sub-compound
     sentences. The first should always be negation and then the other atomic
     connectives.
  1. Feed-in the truth-values of the atomic sentences as values into the main
     connective, through a process of elimination you then reach the core
     truth-assignments:

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

  # Truth-tables

  We are already familiar with truth-tables from the previous entry on the
  _truth-functional connectives_ and the relationship between sentences,
  connectives and the overall truth-value of a sentence. Here we will look in
  further depth at how to build truth-tables and on their mathematical relation to
  binary truth-values. We will also look at examples of complex truth-tables for
  large compound expressions and the systematic steps we follow to derive the
  truth conditions of compound sentences from their simple constituents.

  ## Formulae for constructing truth-tables

  For any truth-table, the number of rows it will contain is equal to $2n$ where:

  - $n$ stands for the number of sentences
  - $2$ is the total number of possible truth values that the sentence may have:
    true or false.

  When we count the number of sentences, we mean atomic sentences. And we only
  count each sentence once. Hence for a compound sentence of the form
  $(\sim B \supset C) & (A \equiv B)$, $B$ occurs twice but there are only three
  sentences: $A$, $B$, and $C$.

  Thus for the sentence $P & Q$ ,we have two sentences so $n$ is 2 which equals 4
  rows (2 x 2):

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  For the sentence $(P \lor Q) & R$ we have three sentences so $n$ is 3 which
  equals 8 rows (2 x 2 x 2):

  ```
  P	Q	R				(	P	∨	Q	)	&	R
  T	T	T									T
  T	T	F									F
  T	F	T									T
  T	F	F									F
  F	T	T									T
  F	T	F									F
  F	F	T									F
  F	F	F									F
  ```

  For the single sentence $P$ we have one sentence so $n$ is 1 which equals 2 rows
  (2 x 1):

  ```
  P			P
  T			T
  F			F
  ```

  This tells us how many rows the truth-table should have but it doesn't tell us
  what each row should consist in. In other words: how many Ts and Fs it should
  contain. This is fine with simple truth-tables since we can just alternate each
  value but for truth-tables with three sentences and more it is easy to make
  mistakes.

  To simplify this and ensure that we are including the right number of possible
  truth-values we can extend the formula to $2n^-i$. This formula tells us how
  many groups of T and F we should have in each column.

  We can already see that there is a pattern at work by looking at the columns of
  the truth tables above. If we take the sentence $(P \lor Q) & R$ we can see that
  for each sentence:

  - $P$ consists in two sets of ${\textsf{T,T,T,T}}$ and ${\textsf{F,F,F,F}}$ with
    **four** elements per set
  - $Q$ consists in four sets of ${\textsf{T,T}}$ , ${\textsf{F,F}}$,
    ${\textsf{T,T}}$ , ${\textsf{F,F}}$ with **two** elements per set
  - $R$ consists in eight sets of ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$,
    ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$
    with **one** element per set.

  If we work through the formula we see that it returns 4, 2, 1:

  $$\begin{equation} \begin{split} 2n^-1 = 3 -1 \\ = 2 \\ = 2 \cdot 2 \\ = 4 \end{split} \end{equation}$$

  $$
  \\begin{equation} \begin{split} 2n^-2 = 3 - 2 \\ = 1 \\ = 2 \cdot 1 \\ = 2 \end{split} \end{equation}
  $$

  $$
  \\begin{equation} \begin{split} 2n^-3 = 3 - 3 \\ = 0 \\ = 2 \cdot 0 \\ = 1 \end{split} \end{equation}
  $$

  ## Truth-table concepts

  ### Recursion

  When we move to complex truth-tables with more than one connective we realise
  that truth-tables are recursive. The truth-tables for the truth-functional
  connectives provide all that we need to determine the truth-values of complex
  sentences:

  > The core truth-tables tell us how to determine the truth-value of a molecular
  > sentence given the truth-values of its
  > [immediate sentential components](Syntax%20of%20sentential%20logic.md). And if
  > the immediate sentential components of a molecular sentence are also
  > molecular, we can use the information in the characteristic truth-tables to
  > determine how the truth-value of each immediate component depends n the
  > truth-values of _its_ components and so on.

  ### Truth-value assignment

  > A truth-value assignment is an assignment of truth-values (either T or F) to
  > the atomic sentences of SL.

  When working on complex truth tables, we use the truth-assignment of atomic
  sentences to count as the values that we feed into the larger expressions at a
  higher level of the sentential abstraction.

  ### Partial assignment

  We talk about partial assignments of truth-values when we look at one specific
  row of the truth-table, independently of the others. The total set of partial
  assignments comprise all possible truth assignments for the given sentence.

  ## Working through complex truth-tables

  The truth-table below shows all truth-value assignments for the sentence
  $(\sim B \supset C) & (A \equiv B)$ :

  ```
  A	B	C				(	~	B	⊃	C	)	&	(	A	≡	B	)
  T	T	T					F	T	T	T		T		T	T	T
  T	T	F					F	T	T	F		T		T	T	T
  T	F	T					T	F	T	T		F		T	F	F
  T	F	F					T	F	F	F		F		T	F	F
  F	T	T					F	T	T	T		F		F	F	T
  F	T	F					F	T	T	F		F		F	F	T
  F	F	T					T	F	T	T		T		F	T	F
  F	F	F					T	F	F	F		F		F	T	F
  ```

  As with algebra we work outwards from each set of brackets. The sequence for
  manually arriving at the above table would be roughly as follows:

  1. For each sentence letter, copy the truth value for it in each row.
  1. Identify the connectives in the atomic sentences and the main overall
     sentence.
  1. Work out the truth-values for the smallest connectives and sub-compound
     sentences. The first should always be negation and then the other atomic
     connectives.
  1. Feed-in the truth-values of the atomic sentences as values into the main
     connective, through a process of elimination you then reach the core
     truth-assignments:


path: zk/Truth_trees.md
sortable_path: zkTruth_trees.md
title: Rationale
checksum: 70fb1ff257cc0f5b5fd2de289170d9249c49be7c912043b3f802465c7e1555ba
created: 2024-03-30 09:03:21.573138301+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":[]}
lead:
  Like [truth-tables](Truth-tables.md), truth-trees are a means of graphically
  representing the logical relationships that may obtain between propositions.
  Truth-trees and truth-tables complement each other and which method you choose
  depends on which logical property you are seeking to derive.

body:
  Like [truth-tables](Truth-tables.md), truth-trees are a means of graphically
  representing the logical relationships that may obtain between propositions.
  Truth-trees and truth-tables complement each other and which method you choose
  depends on which logical property you are seeking to derive.

  Whilst truth-tables have the benefit of being exhaustive - every possible truth
  assignment is factored into the representation - their complexity grows
  exponentially with each additional proposition they contain. This can make
  manually constructing truth tables long-winded and prone to mistakes.

  Truth-trees are less onerous but they lack the exhaustive scope of a
  truth-table. They are more targeted and are best used for demonstrating _that
  something is the case_ rather than _all the possible states that could be the
  case_. For example, a truth tree will tell us that a set _S is logically
  consistent_ whereas a truth-table will tell us that _S is consistent on the
  following three assignments._

  ## Logical consistency

  Recall that a set of propositions is logically or truth-functionally
  [consistent](Consistency.md) just if there is at least one assignment of truth
  conditions which results in all members of the set being true. To identify
  consistency for a set of three propositions via the truth table approach we
  would need to construct a truth table with $2^3$ (8) rows. Assume that this set
  is consistent on one partial assignment only. This means that 87.5% of our rows
  are redundant, they are not required to prove the consistency of the set.
  However we can only know this and we can only be sure of consistency once we
  have gone through the process of generating an assignment for each row.

  Truth trees allow us to reduce the amount of work required and go straight to
  the assignment that proves consistency, disregarding the rest which are
  irrelevant.

  ## Truth tree structure and key terms

  **When using a truth tree to derive logical consistency, the goal is to
  determine whether there is a truth-value assignment on which all of the
  sentences of a set are true. If the set is consistent we should be able to
  derive a partial assignment from the tree that demonstrates consistency.**

  Each truth tree begins with a series of sentences one on top of the other in a
  column. We call the sentences that comprise the initial column **set members**.
  In constructing the tree, we work downwards from the initial column decomposing
  set members into their atomic constituents. We a call an atomic sentence that
  has been decomposed a **literal.** A literal will either be an atomic sentence
  or the negation of an atomic sentence. If one of the set members is already a
  literal, there is no need to decompose it; it can remain as it is.

  Once every set member has been decomposed the truth tree is complete. It can
  then be interpreted in order to derive logical consistency or inconsistency. If
  the set is consistent, we are able to derive the partial assignment(s) that
  demonstrate consistency.

  The rules for decomposing compound sentences match the truth conditions of the
  logical connectives. There are rules for every possible connective and the
  negation of every possible connective however in terms of their tree shape they
  all correspond to either a conjunction or a disjunction. Disjunctive
  decomposition results in new branches being formed off the main column (or
  trunk). Conjunctive decomposition is non-branching which means the decomposed
  constituents are placed within the trunk of whichever tree or branch they are
  decomposed within.

  As we construct the tree we list each line in the left-hand margin and the
  decomposition rule in the right-hand margin. When we apply a decomposition rule
  we must cite the lines to which it applies.

  ### Closed and open branches

  Any branch on which an atomic sentence ($P$) and the negation of that sentence
  ($\sim P$) both occur is a **closed branch**. A branch that is not closed is an
  **open branch**. No partial assignment is recoverable from a closed branch. An
  open branch allows truth to ‘flow up’ to the original set members whereas a
  closed branch blocks this passage.

  ### Completed open branch

  A completed open branch occurs when we have an open branch that has been fully
  decomposed: the branch is open and all molecular sentences have been ticked off
  such that it contains only literals.

  ### Completed tree

  A tree where all its branches are either completed open branches or closed
  branches.

  ### Closed tree

  A tree where all the branches are closed

  ### Open tree

  A tree with at least one completed open branch

  ## Deriving consistency

  Using the definitions above, we can now define truth-functional consistency and
  inconsistency in terms of truth trees:

  > A finite set ($\Gamma$ ) of sentences is truth-functionally inconsistent if
  > $\Gamma$ is a closed tree

  > A finite set ($\Gamma$ ) of sentences is truth-functionally consistent if
  > $\Gamma$ is an open tree

  ## Examples

  ### First example

  The following is a truth tree for the set ${P \lor Q, \sim P }$:

  ![basic-open-tree 1.svg](basic-open-tree%201.svg)

  ### Interpretation

  - We decompose the disjunction at line 1 on line 3. We tick off the compound
    sentence to indicate that it is now decomposed and no longer under
    consideration.
  - Both P and its negation exist on a single branch (at line 2 and line 3). This
    makes it a closed branch. We indicate this by the X beneath the branch that is
    closed, citing the source of the closure by line number.
  - The rightward branch is a completed open branch given the decomposition at 3
    and the lack of negation of Q. Overall this makes the tree an open tree.

  As the set gives us an open tree, it must be truth-functionally consistent. If
  this is the case we should be able to determine the partial assignment in which
  each set member is true. Given that Q is not negated the assignment of
  consistency will contain Q but we have both P and ~P. This means there are two
  possible assignments where the set is consistent: $P, Q$ and $\sim P, Q$. This
  is confirmed by the truth-table:

  ```
  P	Q				P	∨	~	P				Q
  T	T					T						T     *
  T	F					T						F
  F	T					T						T     *
  F	F					T						F
  ```

  **Any time there is an open tree with a closed branch it will be the case that
  the negated sentences of the closed branch will appear both as** $S$ and
  $\sim S$ i**n the resultant assignment.**

  Invoking the truth-table highlights the differences between the two techniques.
  The values that are derived when we interpret a truth tree are not the
  truth-functions of the set members but the truth-values for when they are
  simultaneously true. With truth-tables in contrast, we are deriving the truth
  functions for every possible truth-value assignment. In other words the values
  derived from a truth tree correspond to the left hand side of the truth table
  not the right hand side.

  ### Second example

  The following is a truth tree for the set
  ${A & \sim B, C, \sim A \lor \sim B }$.

  ![basic-closed-tree 1.svg](basic-closed-tree%201.svg)

  ### Interpretation

  - The two molecular set members are decomposed. The disjunction (line 3) results
    in a branching tree. The conjunction (line 1) results in the continuation of
    the trunk.
  - Both branches are completed making it a completed tree. As each branch is
    closed this is a closed tree.

  As this is a closed tree, the set is not truth-functionally consistent. This is
  confirmed by the truth table where there is no partial assignment where all set
  members are true.

  ```
  A	B	C				A	&	~	B				C				~	A	∨	~	C
  T	T	T					F						T						F
  T	T	F					F						F						T
  T	F	T					T						T						F
  T	F	F					T						F						T
  F	T	T					F						T						T
  F	T	F					F						F						T
  F	F	T					F						T						T
  F	F	F					F						F						T
  ```

  ## Truth tree decomposition rules

  ---

  So far we have encountered the decomposition rules for conjunction (`&D`) and
  disjunction (`vD`). We will now list all the rules. We will see that for each
  rule, the decomposition either branches or does not branch which is to say that
  each rule either has the shape of a conjunction or a disjunction (however the
  permitted values of the specific disjuncts/conjuncts obviously differ in each
  case). Moreover there is a parallel rule for the decomposition of the negation
  of each of the main connectives and these rules rely on logical equivalences

  ### Negated negation decomposition: `~~D`

  ![negated-negation-decomposition-rule 2.svg](negated-negation-decomposition-rule%202.svg)

  Truth passes only if $P$ is true

  ### Conjunction decomposition: `&D`

  ![conjunction-decomposition-rule.svg](conjunction-decomposition-rule.svg)

  Truth passes only $P$ and $Q$ are both true.

  ### Negated Conjunction decomposition: `~&D`

  ![negated-conjunction-decomposition-rule.svg](negated-conjunction-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $\sim Q$ is true. This rule is a consequence
  of the equivalence between $\sim (P & Q)$ and $\sim P \lor \sim Q$ , the first
  of DeMorgan’s Laws.

  ### Disjunction decomposition: `vD`

  ![disjunction-decomposition-rule.svg](disjunction-decomposition-rule.svg)

  Truth passes if either $P$or $Q$ are true.

  ### Negated Disjunction decomposition: `~vD`

  ![negated-disjunction-decomposition-rule.svg](negated-disjunction-decomposition-rule.svg)

  Truth passes if both $P$ and $Q$ are false. This rule is a consequence of the
  equivalence between $\sim (P \lor Q)$ and $\sim P & \sim Q$, the second of
  DeMorgan’s Laws.

  ### Conditional decomposition: `⊃D`

  ![conditional-decomposition-rule.svg](conditional-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $Q$ are true. This rule is a consequence of
  the equivalence between $P \supset Q$ and $\sim P \lor Q$ therefore this branch
  has the shape of a disjunction with $\sim P$ , $Q$ as its disjuncts.

  ### Negated Conditional decomposition: `~⊃D`

  Truth passes if both $P$ and $\sim Q$ are true. This is a consequence of the
  equivalence between $\sim (P \supset Q)$ and $P & \sim Q$.

  ![negated-conditional-decomposition-rule.svg](negated-conditional-decomposition-rule.svg)

  ### Biconditional decomposition: `≡D`

  ![biconditional-decomposition-rule.drawio(1).svg](biconditional-decomposition-rule.drawio%281%29.svg)

  Truth passes if either $P$ and $Q$ are true or $\sim P & \sim Q$ are true. This
  is an interesting rule because it combines the disjunction and conjunction tree
  shapes.

  ### Negated biconditional decomposition: `~≡D`

  ![negated-biconditional-decomposition-rule.drawio.svg](negated-biconditional-decomposition-rule.drawio.svg)

  Truth passes if either $P$ and $\sim Q$ is true or if $\sim P$ and $Q$ is true.

  ## Further examples and heuristics for complex truth trees

  With truth-trees regardless of which order you decompose the set members, the
  conclusion should always be the same. This said, there more are more efficient
  ways than others to construct the trees. You want to find the route that will
  demonstrate consistency or non-consistency with the shortest amount of work. The
  following heuristic techniques followed in order, facilitate this:

  1. Decompose those molecular sentences the decomposition of which does not
     produce new branches. In other words that are decompositions of double
     negations or pure conjunctions.
  1. Perform those decompositions that will rapidly generate closed branches.
  1. If neither (1) or (2) is applicable, decompose **the most complex** sentence
     first.

  Here are some examples of these rules applied:

  ![complex-tree.svg](complex-tree.svg)

  Observe that here we don’t bother to decompose the sentence on line 1. This is
  because, having decomposed the sentences on lines 2 and 3 we have arrived at a
  closed tree. It is therefore unnecessary to go any further for if two sentences
  in the set are inconsistent with each other, adding another sentence is not
  going to change the overall assignment of inconsistency.

  ## Deriving properties other than logical consistency from truth trees

  So far truth trees have been discussed purely in terms of logical consistency
  however they can be used to derive all the other key truth-functional properties
  of propositional logic. Given the foundational role of consistency to logic,
  these properties are expressible in terms of consistency which is what makes
  them amenable to formulation in terms of truth trees.

  ### Logical falsity

  For a given finite set $\Gamma$, $\Gamma$ is logically consistent just if all of
  its members can be true at once. Expressed in terms of truth trees, this is
  equivalent to an open tree. Contrariwise, $\Gamma$ is inconsistent if it is not
  possible for every member of the set to be true at once. This is the same as a
  tree where all of the branches are closed (i.e. a closed tree).

  When we wish to assess
  [logical falsity](Logical%20truth%20and%20falsity.md#logical-falsity) we are not
  focused on sets however, we are interested in a property of a sentence. However
  we can easily construe single sentences as unit sets: sets with a single member.
  With this in mind and the above accounts of consistency and logical falsity we
  are equipped to express logical falsity in terms of truth-trees with the
  following rule:

  > A sentence $P$ is logically false if and only if the unit set ${ P }$ has a
  > closed tree

  A logically false sentence cannot be true on any assignment. This is the same
  thing as an inconsistent set. Thus it will be represented in a truth tree as
  inconsistency which is disclosed via a closed tree.

  ![logical-falsity-tree.svg](logical-falsity-tree.svg)

  ### Logical truth

  For a sentence $P$ to be
  [logically true](Logical%20truth%20and%20falsity.md#logical-truth), there must
  be no possible assignment in which $P$ is false. We express this informally by
  saying _it is not possible to consistently deny $P$._ We know that in terms of
  truth trees an inconsistent set is a closed tree therefore a unit set of ${ P }$
  is logically true if ${ \sim P }$ is a closed tree. This is to say: if the
  negation of $P$ is inconsistent.

  > A sentence $P$ is logically true if and only if the set ${ \sim P }$ has a
  > closed tree

  ### Logical indeterminacy

  [Indeterminacy](Indeterminacy.md) follows from the two definitions above; we do
  not require any additional apparatus. We recall that a sentence $P$ is logically
  indeterminate just if it is neither logically true or logically false. Thus the
  truth tree for an indeterminate sentence is straightforward:

  > A sentence $P$ is logically indeterminate if and only if neither the set
  >  ${ P }$ nor the set ${ \sim P }$ has a closed tree

  This follows because a closed tree for  ${ P }$ means it is not logically false
  and an open tree for ${ \sim P }$ means it is not logically true. So if it is
  neither of these things, $P$ must be indeterminate.

  ### Logical equivalence

  Recall that $P$ and $Q$ are [logically equivalent](Logical%20equivalence.md)
  just if there is no truth assignment on which one is true and the other is
  false. We know from the
  [material biconditional shorthand](Corresponding%20material%20and%20biconditional.md#corresponding-material-biconditional)
  that this state of affairs can be expressed as $P \equiv Q$ and that if this
  compound sentence is true on every assignment then both simple sentences are
  equivalent. But ‘true on every assignment’ is another way of saying _logically
  true_ since there is no possibility of a false assignment. We already know what
  logical truth looks like as a truth tree: it is a closed tree for the negation
  of the sentence being tested. Therefore, to test the logical equivalence of two
  sentences it is necessary to construct a truth tree for the negation of the
  sentences conjoined by the biconditional (i.e. $\sim (P \equiv Q)$ )and see if
  this results in a closed tree. If it does, the two sentences are logically
  equivalent.

  > Sentences $P$ and $Q$ are truth-functionally equivalent if and only if the set
  > $\sim (P \equiv Q)$ has a closed tree

  ![logical-equivalence-tree.svg](logical-equivalence-tree.svg)

  ### Logical entailment and validity

  Let’s remind ourselves of the meaning of truth-functional
  [entailment](Validity%20and%20entailment.md#entailment) and
  [validity](Validity%20and%20entailment.md#validity) and the relation between the
  two. $\Gamma$ $\vdash$ $P$ is true if and only if there is no truth-assignment
  in which every member of $\Gamma$ is true and $P$ is false. Entailment is
  closely related to validity; it is really just a matter of emphasis: we say that
  $\Gamma$ are the premises and $P$ is the conclusion and that this is a valid
  argument if there is no assignment in which every member of $\Gamma$ is true and
  $P$ is false.

  As with the previous properties, to express validity and entailment in terms of
  truth trees we need to express these concepts in the language of logical
  consistency. $\Gamma$ entails $P$ just if one cannot consistently assert
  $\Gamma$ whilst denying $P$. This is to say that the set $\Gamma \cup {\sim P}$
  is inconsistent. So we just need a closed truth tree for $\Gamma \cup {\sim P}$
  to demonstrate the validity of this set.

  > A finite set of sentences $\Gamma$ truth-functionally entails a sentence $P$
  > if and only if the set $\Gamma \cup {\sim P}$ has a closed truth tree.

  > An argument is truth functionally valid if and only if the set consisting of
  > the premises and the negation of the conclusion has a closed truth tree.

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

  ## Rationale

  Like [truth-tables](Truth-tables.md), truth-trees are a means of graphically
  representing the logical relationships that may obtain between propositions.
  Truth-trees and truth-tables complement each other and which method you choose
  depends on which logical property you are seeking to derive.

  Whilst truth-tables have the benefit of being exhaustive - every possible truth
  assignment is factored into the representation - their complexity grows
  exponentially with each additional proposition they contain. This can make
  manually constructing truth tables long-winded and prone to mistakes.

  Truth-trees are less onerous but they lack the exhaustive scope of a
  truth-table. They are more targeted and are best used for demonstrating _that
  something is the case_ rather than _all the possible states that could be the
  case_. For example, a truth tree will tell us that a set _S is logically
  consistent_ whereas a truth-table will tell us that _S is consistent on the
  following three assignments._

  ## Logical consistency

  Recall that a set of propositions is logically or truth-functionally
  [consistent](Consistency.md) just if there is at least one assignment of truth
  conditions which results in all members of the set being true. To identify
  consistency for a set of three propositions via the truth table approach we
  would need to construct a truth table with $2^3$ (8) rows. Assume that this set
  is consistent on one partial assignment only. This means that 87.5% of our rows
  are redundant, they are not required to prove the consistency of the set.
  However we can only know this and we can only be sure of consistency once we
  have gone through the process of generating an assignment for each row.

  Truth trees allow us to reduce the amount of work required and go straight to
  the assignment that proves consistency, disregarding the rest which are
  irrelevant.

  ## Truth tree structure and key terms

  **When using a truth tree to derive logical consistency, the goal is to
  determine whether there is a truth-value assignment on which all of the
  sentences of a set are true. If the set is consistent we should be able to
  derive a partial assignment from the tree that demonstrates consistency.**

  Each truth tree begins with a series of sentences one on top of the other in a
  column. We call the sentences that comprise the initial column **set members**.
  In constructing the tree, we work downwards from the initial column decomposing
  set members into their atomic constituents. We a call an atomic sentence that
  has been decomposed a **literal.** A literal will either be an atomic sentence
  or the negation of an atomic sentence. If one of the set members is already a
  literal, there is no need to decompose it; it can remain as it is.

  Once every set member has been decomposed the truth tree is complete. It can
  then be interpreted in order to derive logical consistency or inconsistency. If
  the set is consistent, we are able to derive the partial assignment(s) that
  demonstrate consistency.

  The rules for decomposing compound sentences match the truth conditions of the
  logical connectives. There are rules for every possible connective and the
  negation of every possible connective however in terms of their tree shape they
  all correspond to either a conjunction or a disjunction. Disjunctive
  decomposition results in new branches being formed off the main column (or
  trunk). Conjunctive decomposition is non-branching which means the decomposed
  constituents are placed within the trunk of whichever tree or branch they are
  decomposed within.

  As we construct the tree we list each line in the left-hand margin and the
  decomposition rule in the right-hand margin. When we apply a decomposition rule
  we must cite the lines to which it applies.

  ### Closed and open branches

  Any branch on which an atomic sentence ($P$) and the negation of that sentence
  ($\sim P$) both occur is a **closed branch**. A branch that is not closed is an
  **open branch**. No partial assignment is recoverable from a closed branch. An
  open branch allows truth to ‘flow up’ to the original set members whereas a
  closed branch blocks this passage.

  ### Completed open branch

  A completed open branch occurs when we have an open branch that has been fully
  decomposed: the branch is open and all molecular sentences have been ticked off
  such that it contains only literals.

  ### Completed tree

  A tree where all its branches are either completed open branches or closed
  branches.

  ### Closed tree

  A tree where all the branches are closed

  ### Open tree

  A tree with at least one completed open branch

  ## Deriving consistency

  Using the definitions above, we can now define truth-functional consistency and
  inconsistency in terms of truth trees:

  > A finite set ($\Gamma$ ) of sentences is truth-functionally inconsistent if
  > $\Gamma$ is a closed tree

  > A finite set ($\Gamma$ ) of sentences is truth-functionally consistent if
  > $\Gamma$ is an open tree

  ## Examples

  ### First example

  The following is a truth tree for the set ${P \lor Q, \sim P }$:

  ![basic-open-tree 1.svg](basic-open-tree%201.svg)

  ### Interpretation

  - We decompose the disjunction at line 1 on line 3. We tick off the compound
    sentence to indicate that it is now decomposed and no longer under
    consideration.
  - Both P and its negation exist on a single branch (at line 2 and line 3). This
    makes it a closed branch. We indicate this by the X beneath the branch that is
    closed, citing the source of the closure by line number.
  - The rightward branch is a completed open branch given the decomposition at 3
    and the lack of negation of Q. Overall this makes the tree an open tree.

  As the set gives us an open tree, it must be truth-functionally consistent. If
  this is the case we should be able to determine the partial assignment in which
  each set member is true. Given that Q is not negated the assignment of
  consistency will contain Q but we have both P and ~P. This means there are two
  possible assignments where the set is consistent: $P, Q$ and $\sim P, Q$. This
  is confirmed by the truth-table:

  ```
  P	Q				P	∨	~	P				Q
  T	T					T						T     *
  T	F					T						F
  F	T					T						T     *
  F	F					T						F
  ```

  **Any time there is an open tree with a closed branch it will be the case that
  the negated sentences of the closed branch will appear both as** $S$ and
  $\sim S$ i**n the resultant assignment.**

  Invoking the truth-table highlights the differences between the two techniques.
  The values that are derived when we interpret a truth tree are not the
  truth-functions of the set members but the truth-values for when they are
  simultaneously true. With truth-tables in contrast, we are deriving the truth
  functions for every possible truth-value assignment. In other words the values
  derived from a truth tree correspond to the left hand side of the truth table
  not the right hand side.

  ### Second example

  The following is a truth tree for the set
  ${A & \sim B, C, \sim A \lor \sim B }$.

  ![basic-closed-tree 1.svg](basic-closed-tree%201.svg)

  ### Interpretation

  - The two molecular set members are decomposed. The disjunction (line 3) results
    in a branching tree. The conjunction (line 1) results in the continuation of
    the trunk.
  - Both branches are completed making it a completed tree. As each branch is
    closed this is a closed tree.

  As this is a closed tree, the set is not truth-functionally consistent. This is
  confirmed by the truth table where there is no partial assignment where all set
  members are true.

  ```
  A	B	C				A	&	~	B				C				~	A	∨	~	C
  T	T	T					F						T						F
  T	T	F					F						F						T
  T	F	T					T						T						F
  T	F	F					T						F						T
  F	T	T					F						T						T
  F	T	F					F						F						T
  F	F	T					F						T						T
  F	F	F					F						F						T
  ```

  ## Truth tree decomposition rules

  ---

  So far we have encountered the decomposition rules for conjunction (`&D`) and
  disjunction (`vD`). We will now list all the rules. We will see that for each
  rule, the decomposition either branches or does not branch which is to say that
  each rule either has the shape of a conjunction or a disjunction (however the
  permitted values of the specific disjuncts/conjuncts obviously differ in each
  case). Moreover there is a parallel rule for the decomposition of the negation
  of each of the main connectives and these rules rely on logical equivalences

  ### Negated negation decomposition: `~~D`

  ![negated-negation-decomposition-rule 2.svg](negated-negation-decomposition-rule%202.svg)

  Truth passes only if $P$ is true

  ### Conjunction decomposition: `&D`

  ![conjunction-decomposition-rule.svg](conjunction-decomposition-rule.svg)

  Truth passes only $P$ and $Q$ are both true.

  ### Negated Conjunction decomposition: `~&D`

  ![negated-conjunction-decomposition-rule.svg](negated-conjunction-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $\sim Q$ is true. This rule is a consequence
  of the equivalence between $\sim (P & Q)$ and $\sim P \lor \sim Q$ , the first
  of DeMorgan’s Laws.

  ### Disjunction decomposition: `vD`

  ![disjunction-decomposition-rule.svg](disjunction-decomposition-rule.svg)

  Truth passes if either $P$or $Q$ are true.

  ### Negated Disjunction decomposition: `~vD`

  ![negated-disjunction-decomposition-rule.svg](negated-disjunction-decomposition-rule.svg)

  Truth passes if both $P$ and $Q$ are false. This rule is a consequence of the
  equivalence between $\sim (P \lor Q)$ and $\sim P & \sim Q$, the second of
  DeMorgan’s Laws.

  ### Conditional decomposition: `⊃D`

  ![conditional-decomposition-rule.svg](conditional-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $Q$ are true. This rule is a consequence of
  the equivalence between $P \supset Q$ and $\sim P \lor Q$ therefore this branch
  has the shape of a disjunction with $\sim P$ , $Q$ as its disjuncts.

  ### Negated Conditional decomposition: `~⊃D`

  Truth passes if both $P$ and $\sim Q$ are true. This is a consequence of the
  equivalence between $\sim (P \supset Q)$ and $P & \sim Q$.

  ![negated-conditional-decomposition-rule.svg](negated-conditional-decomposition-rule.svg)

  ### Biconditional decomposition: `≡D`

  ![biconditional-decomposition-rule.drawio(1).svg](biconditional-decomposition-rule.drawio%281%29.svg)

  Truth passes if either $P$ and $Q$ are true or $\sim P & \sim Q$ are true. This
  is an interesting rule because it combines the disjunction and conjunction tree
  shapes.

  ### Negated biconditional decomposition: `~≡D`

  ![negated-biconditional-decomposition-rule.drawio.svg](negated-biconditional-decomposition-rule.drawio.svg)

  Truth passes if either $P$ and $\sim Q$ is true or if $\sim P$ and $Q$ is true.

  ## Further examples and heuristics for complex truth trees

  With truth-trees regardless of which order you decompose the set members, the
  conclusion should always be the same. This said, there more are more efficient
  ways than others to construct the trees. You want to find the route that will
  demonstrate consistency or non-consistency with the shortest amount of work. The
  following heuristic techniques followed in order, facilitate this:

  1. Decompose those molecular sentences the decomposition of which does not
     produce new branches. In other words that are decompositions of double
     negations or pure conjunctions.
  1. Perform those decompositions that will rapidly generate closed branches.
  1. If neither (1) or (2) is applicable, decompose **the most complex** sentence
     first.

  Here are some examples of these rules applied:

  ![complex-tree.svg](complex-tree.svg)

  Observe that here we don’t bother to decompose the sentence on line 1. This is
  because, having decomposed the sentences on lines 2 and 3 we have arrived at a
  closed tree. It is therefore unnecessary to go any further for if two sentences
  in the set are inconsistent with each other, adding another sentence is not
  going to change the overall assignment of inconsistency.

  ## Deriving properties other than logical consistency from truth trees

  So far truth trees have been discussed purely in terms of logical consistency
  however they can be used to derive all the other key truth-functional properties
  of propositional logic. Given the foundational role of consistency to logic,
  these properties are expressible in terms of consistency which is what makes
  them amenable to formulation in terms of truth trees.

  ### Logical falsity

  For a given finite set $\Gamma$, $\Gamma$ is logically consistent just if all of
  its members can be true at once. Expressed in terms of truth trees, this is
  equivalent to an open tree. Contrariwise, $\Gamma$ is inconsistent if it is not
  possible for every member of the set to be true at once. This is the same as a
  tree where all of the branches are closed (i.e. a closed tree).

  When we wish to assess
  [logical falsity](Logical%20truth%20and%20falsity.md#logical-falsity) we are not
  focused on sets however, we are interested in a property of a sentence. However
  we can easily construe single sentences as unit sets: sets with a single member.
  With this in mind and the above accounts of consistency and logical falsity we
  are equipped to express logical falsity in terms of truth-trees with the
  following rule:

  > A sentence $P$ is logically false if and only if the unit set ${ P }$ has a
  > closed tree

  A logically false sentence cannot be true on any assignment. This is the same
  thing as an inconsistent set. Thus it will be represented in a truth tree as
  inconsistency which is disclosed via a closed tree.

  ![logical-falsity-tree.svg](logical-falsity-tree.svg)

  ### Logical truth

  For a sentence $P$ to be
  [logically true](Logical%20truth%20and%20falsity.md#logical-truth), there must
  be no possible assignment in which $P$ is false. We express this informally by
  saying _it is not possible to consistently deny $P$._ We know that in terms of
  truth trees an inconsistent set is a closed tree therefore a unit set of ${ P }$
  is logically true if ${ \sim P }$ is a closed tree. This is to say: if the
  negation of $P$ is inconsistent.

  > A sentence $P$ is logically true if and only if the set ${ \sim P }$ has a
  > closed tree

  ### Logical indeterminacy

  [Indeterminacy](Indeterminacy.md) follows from the two definitions above; we do
  not require any additional apparatus. We recall that a sentence $P$ is logically
  indeterminate just if it is neither logically true or logically false. Thus the
  truth tree for an indeterminate sentence is straightforward:

  > A sentence $P$ is logically indeterminate if and only if neither the set
  >  ${ P }$ nor the set ${ \sim P }$ has a closed tree

  This follows because a closed tree for  ${ P }$ means it is not logically false
  and an open tree for ${ \sim P }$ means it is not logically true. So if it is
  neither of these things, $P$ must be indeterminate.

  ### Logical equivalence

  Recall that $P$ and $Q$ are [logically equivalent](Logical%20equivalence.md)
  just if there is no truth assignment on which one is true and the other is
  false. We know from the
  [material biconditional shorthand](Corresponding%20material%20and%20biconditional.md#corresponding-material-biconditional)
  that this state of affairs can be expressed as $P \equiv Q$ and that if this
  compound sentence is true on every assignment then both simple sentences are
  equivalent. But ‘true on every assignment’ is another way of saying _logically
  true_ since there is no possibility of a false assignment. We already know what
  logical truth looks like as a truth tree: it is a closed tree for the negation
  of the sentence being tested. Therefore, to test the logical equivalence of two
  sentences it is necessary to construct a truth tree for the negation of the
  sentences conjoined by the biconditional (i.e. $\sim (P \equiv Q)$ )and see if
  this results in a closed tree. If it does, the two sentences are logically
  equivalent.

  > Sentences $P$ and $Q$ are truth-functionally equivalent if and only if the set
  > $\sim (P \equiv Q)$ has a closed tree

  ![logical-equivalence-tree.svg](logical-equivalence-tree.svg)

  ### Logical entailment and validity

  Let’s remind ourselves of the meaning of truth-functional
  [entailment](Validity%20and%20entailment.md#entailment) and
  [validity](Validity%20and%20entailment.md#validity) and the relation between the
  two. $\Gamma$ $\vdash$ $P$ is true if and only if there is no truth-assignment
  in which every member of $\Gamma$ is true and $P$ is false. Entailment is
  closely related to validity; it is really just a matter of emphasis: we say that
  $\Gamma$ are the premises and $P$ is the conclusion and that this is a valid
  argument if there is no assignment in which every member of $\Gamma$ is true and
  $P$ is false.

  As with the previous properties, to express validity and entailment in terms of
  truth trees we need to express these concepts in the language of logical
  consistency. $\Gamma$ entails $P$ just if one cannot consistently assert
  $\Gamma$ whilst denying $P$. This is to say that the set $\Gamma \cup {\sim P}$
  is inconsistent. So we just need a closed truth tree for $\Gamma \cup {\sim P}$
  to demonstrate the validity of this set.

  > A finite set of sentences $\Gamma$ truth-functionally entails a sentence $P$
  > if and only if the set $\Gamma \cup {\sim P}$ has a closed truth tree.

  > An argument is truth functionally valid if and only if the set consisting of
  > the premises and the negation of the conclusion has a closed truth tree.


path: zk/Tuples_in_Python.md
sortable_path: zkTuples_in_Python.md
title: Tuples in Python
lead: TODO: Exapand tuple notes - give more use cases
checksum: 030111bc7de9da55f9dfd32fbbf6212bc9095e33ae0e9729bc59f02b2410c4b6
created: 2024-03-30 09:03:21.65884988+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["python","data-structures"]}
body:
  TODO: Exapand tuple notes - give more use cases

  Tuples are one of the main data-structures or containers in Python. Tuples are
  useful in cases where you want to group related data and ensure that it will not
  change.

  Tuples have the following properties:

  - They are **ordered**
  - They have a **fixed size**
  - They are **immutable** and cannot be modified
  - **Allow duplicate** members
  - They are **indexed**

  > In essence a tuple is a list that is immutable.

  As with all containers in Python they permit any data type.

  > Tuples are denoted with `(...)`

  ## Basic usage

  ```python
  tup1 = (1, 3, 5, 7)
  print(tup1[2])

  # 5
  """
  ```

  ## Slicing

  ```python
  tup1 = (1, 3, 5, 7)

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

  """
  (3, 5)
  (1, 3, 5)
  (3, 5, 7)
  (7, 5, 3, 1)
  """
  ```

  ## Looping

  ```python
  tup3 = ('apple', 'pear', 'orange', 'plum', 'apple')
  for x in tup3:
      print(x)

  """
  apple
  pear
  orange
  plum
  apple
  """
  ```

  ## Useful methods and predicates

  ```python
  tup3 = ('apple', 'pear', 'orange', 'plum', 'apple')

  # Count instances of a member
  print(tup3.count('apple'))
  # 2

  # Get index of a member
  print(tup3.index('pear'))
  # 1

  # Check for membership
  if 'orange' in tup3:
      print('orange is in the Tuple')

  # orange is in the Tuple
  ```

  ## Nest tuples

  ```python

  tuple2 = ('John', 'Denise', 'Phoebe', 'Adam')
  tuple3 = (42, tuple1, tuple2, 5.5)
  print(tuple3)

  # (42, (1, 3, 5, 7), ('John', 'Denise', 'Phoebe', 'Adam'), 5.5)

  ```

  // TODO: How to flatten a tuple?

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

  # Tuples in Python

  TODO: Exapand tuple notes - give more use cases

  Tuples are one of the main data-structures or containers in Python. Tuples are
  useful in cases where you want to group related data and ensure that it will not
  change.

  Tuples have the following properties:

  - They are **ordered**
  - They have a **fixed size**
  - They are **immutable** and cannot be modified
  - **Allow duplicate** members
  - They are **indexed**

  > In essence a tuple is a list that is immutable.

  As with all containers in Python they permit any data type.

  > Tuples are denoted with `(...)`

  ## Basic usage

  ```python
  tup1 = (1, 3, 5, 7)
  print(tup1[2])

  # 5
  """
  ```

  ## Slicing

  ```python
  tup1 = (1, 3, 5, 7)

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

  """
  (3, 5)
  (1, 3, 5)
  (3, 5, 7)
  (7, 5, 3, 1)
  """
  ```

  ## Looping

  ```python
  tup3 = ('apple', 'pear', 'orange', 'plum', 'apple')
  for x in tup3:
      print(x)

  """
  apple
  pear
  orange
  plum
  apple
  """
  ```

  ## Useful methods and predicates

  ```python
  tup3 = ('apple', 'pear', 'orange', 'plum', 'apple')

  # Count instances of a member
  print(tup3.count('apple'))
  # 2

  # Get index of a member
  print(tup3.index('pear'))
  # 1

  # Check for membership
  if 'orange' in tup3:
      print('orange is in the Tuple')

  # orange is in the Tuple
  ```

  ## Nest tuples

  ```python

  tuple2 = ('John', 'Denise', 'Phoebe', 'Adam')
  tuple3 = (42, tuple1, tuple2, 5.5)
  print(tuple3)

  # (42, (1, 3, 5, 7), ('John', 'Denise', 'Phoebe', 'Adam'), 5.5)

  ```

  // TODO: How to flatten a tuple?


path: zk/Turing_machines.md
sortable_path: zkTuring_machines.md
title: What is a Turing Machine?
checksum: 77e11526878feb70c1c19503d27b1578de495f47bd8b505942c1f238cf73903f
created: 2024-03-30 09:03:21.664596419+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["theory-of-computation"]}
lead:
  A Turing Machine consists of an infinitely long tape, that has been divided up
  into cells. Each cell can contain either a 1, a 0 or an empty space. Above one
  cell of the tape is a head, which can either move left or right, and can read
  the symbols written in the cells. The head is also capable of erasing symbols
  and writing new symbols into the cells.

body:
  A Turing Machine consists of an infinitely long tape, that has been divided up
  into cells. Each cell can contain either a 1, a 0 or an empty space. Above one
  cell of the tape is a head, which can either move left or right, and can read
  the symbols written in the cells. The head is also capable of erasing symbols
  and writing new symbols into the cells.

  ![Turing_machines_01.gif](Turing_machines_01.gif) The direction that the
  head moves, which values it erases, and which values it writes in, are dependent
  on a set of instructions provided to the machine.0

  Different sets of instructions can be divided into **states.** States are like
  sub-routines and can themselves feature as part of instructions.

  For example:

  ### State 2

  - If 0 then erase
  - Write 1 then move right
  - Go to state 5

  ### State 5

  - If 1, then erase
  - Write 0 then move left
  - Go to state _n_

  Alan Turing proved that **any problem that is computable** can be computed by a
  Turing Machine using this simple system.

raw_content:
  ---
  tags:
    - theory-of-computation
  ---

  ## What is a Turing Machine?

  A Turing Machine consists of an infinitely long tape, that has been divided up
  into cells. Each cell can contain either a 1, a 0 or an empty space. Above one
  cell of the tape is a head, which can either move left or right, and can read
  the symbols written in the cells. The head is also capable of erasing symbols
  and writing new symbols into the cells.

  ![Turing_machines_01.gif](Turing_machines_01.gif) The direction that the
  head moves, which values it erases, and which values it writes in, are dependent
  on a set of instructions provided to the machine.0

  Different sets of instructions can be divided into **states.** States are like
  sub-routines and can themselves feature as part of instructions.

  For example:

  ### State 2

  - If 0 then erase
  - Write 1 then move right
  - Go to state 5

  ### State 5

  - If 1, then erase
  - Write 0 then move left
  - Go to state _n_

  Alan Turing proved that **any problem that is computable** can be computed by a
  Turing Machine using this simple system.


path: zk/Twos_complement.md
sortable_path: zkTwos_complement.md
title: Two's complement
lead: # Two's complement
checksum: bab6a4413f25f5efd54c0cbf5f71c9123ab4051101c7f462e952b0892f8bbafe
created: 2024-03-30 09:03:21.670206681+00:00
modified: 2024-06-15 09:55:30.772337479+00:00
metadata: {"created":"Tuesday, March 19, 2024","id":"gktb","tags":["binary"],"title":"Two's complement"}
body:
  # Two's complement

  ## Summary

  - _Two's complement_ is a method for representing signed numbers (negative
    integers) in binary.

  - It is derived by inverting the values of an unsigned binary integer to create
    its signed equivalent.

  - A benefit is that hardware implementation of the binary arithmetic of signed
    and unsigned numbers can be handled in the same manner as unsigned numbers,
    requiring no additional handling. A drawback is that it halves the
    informational capacity of the given word length for the binary system.

  ## Detail

  ### Procedural steps

  Two's complement divides the available word length (see
  [[Binary_encoding|binary encoding]]) into two subsets: one for negative integrs
  and one for positive integers.

  Take the binary encoding of decimal five (`0101`). Its complement is `1011`.

  The procedure for deriving the complement is as follows.

  To derive the complement of an unsigned number:

  1. Take the unsigned number and invert its digits: `0` becomes `1`, `1` becomes
     `0`
  2. Add one

  ![](/img/unsigned-to-signed.png)

  To derive the unsigned equivalent of a signed number you invert the process but
  still make the smallest digit `1`:

  ![](/img/signed-to-unsigned.png)

  ### Formal expression

  $$
      2^n - x
  $$

  - where $x$ is the negative integer in binary that we wish to derive
  - where $n$ is the word length of the binary system in bits.

  Applied to the earlier example we have $2^4 -5$ which is:

  $$
      16 - 5 = 11
  $$

  When we convert the decimal `11` to binary we get `1011` which is identical to
  the signed version of the unsigned integer.

  We can confirm the correctness of the derviation by summing the signed and
  unsigned binary values. If this results in zeros (ignoring the overflow bit),
  the derivation is correct as the two values effectively cancel each other out:

  $$

    1011 + 0101 = 0000
  $$

  ### Advantages

  - The circuit implementation of arithmetic involving positive and negative
    integers is the same as the implementation of positive integers. There is no
    need for additional harware or special handling of the values.
  - This can be contrasted with the alternative approaches to signing numbers such
    as **signed magnitude representation** which uses certain bits as designators
    of negative/positive status.

  ### Limitations

  - Two's complement reduces the overall informational capacity of the given
    binary word length, effectively halving the total number of unique values.

  - In a 4-bit system instead of 16 total unique encodings of integers you have 8
    encodings for positive integers and 8 encodings for the their signed
    equivalent. For integers larger than denary 8 you would need to increase the
    bit length of the system

  - Consequently two's complement can necessitate larger overall word lengths.

  ## Related notes

  [[Signed_and_unsigned_numbers|signed_and_unsigned_numbers]],
  [[Binary_addition|binary addition]], [[Signed_magnitude_representation]]

raw_content:
  ---
  id: gktb
  title: Two's complement
  tags:
    - binary
  created: Tuesday, March 19, 2024
  ---

  # Two's complement

  ## Summary

  - _Two's complement_ is a method for representing signed numbers (negative
    integers) in binary.

  - It is derived by inverting the values of an unsigned binary integer to create
    its signed equivalent.

  - A benefit is that hardware implementation of the binary arithmetic of signed
    and unsigned numbers can be handled in the same manner as unsigned numbers,
    requiring no additional handling. A drawback is that it halves the
    informational capacity of the given word length for the binary system.

  ## Detail

  ### Procedural steps

  Two's complement divides the available word length (see
  [[Binary_encoding|binary encoding]]) into two subsets: one for negative integrs
  and one for positive integers.

  Take the binary encoding of decimal five (`0101`). Its complement is `1011`.

  The procedure for deriving the complement is as follows.

  To derive the complement of an unsigned number:

  1. Take the unsigned number and invert its digits: `0` becomes `1`, `1` becomes
     `0`
  2. Add one

  ![](/img/unsigned-to-signed.png)

  To derive the unsigned equivalent of a signed number you invert the process but
  still make the smallest digit `1`:

  ![](/img/signed-to-unsigned.png)

  ### Formal expression

  $$
      2^n - x
  $$

  - where $x$ is the negative integer in binary that we wish to derive
  - where $n$ is the word length of the binary system in bits.

  Applied to the earlier example we have $2^4 -5$ which is:

  $$
      16 - 5 = 11
  $$

  When we convert the decimal `11` to binary we get `1011` which is identical to
  the signed version of the unsigned integer.

  We can confirm the correctness of the derviation by summing the signed and
  unsigned binary values. If this results in zeros (ignoring the overflow bit),
  the derivation is correct as the two values effectively cancel each other out:

  $$

    1011 + 0101 = 0000
  $$

  ### Advantages

  - The circuit implementation of arithmetic involving positive and negative
    integers is the same as the implementation of positive integers. There is no
    need for additional harware or special handling of the values.
  - This can be contrasted with the alternative approaches to signing numbers such
    as **signed magnitude representation** which uses certain bits as designators
    of negative/positive status.

  ### Limitations

  - Two's complement reduces the overall informational capacity of the given
    binary word length, effectively halving the total number of unique values.

  - In a 4-bit system instead of 16 total unique encodings of integers you have 8
    encodings for positive integers and 8 encodings for the their signed
    equivalent. For integers larger than denary 8 you would need to increase the
    bit length of the system

  - Consequently two's complement can necessitate larger overall word lengths.

  ## Related notes

  [[Signed_and_unsigned_numbers|signed_and_unsigned_numbers]],
  [[Binary_addition|binary addition]], [[Signed_magnitude_representation]]


path: zk/Type_guarding_narrowing.md
sortable_path: zkType_guarding_narrowing.md
title: Type narrowing and guarding
checksum: 7564494e12f78a3668cfbe3687e534dbde08369009382cf9f3ca136535b4b597
created: 2024-03-30 09:03:21.705579348+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["typescript"]}
lead:
  Type narrowing is the process of working out from a supertype like
  [any](Any.md) or [unknown](Unknown.md) whic type the value should be in the
  x course of your code. This is necessary since a process will not necessarily
  always return or involve homogenous types.

body:
  Type narrowing is the process of working out from a supertype like
  [any](Any.md) or [unknown](Unknown.md) whic type the value should be in the
  x course of your code. This is necessary since a process will not necessarily
  always return or involve homogenous types.

  Related to this is type guarding: ensuring that a value is of the suitable type
  as a factor of control flow. For instance using typeof to ensure that an input
  is numerical before proceeding with a function's logic.

raw_content:
  ---
  tags:
    - typescript
  ---

  # Type narrowing and guarding

  Type narrowing is the process of working out from a supertype like
  [any](Any.md) or [unknown](Unknown.md) whic type the value should be in the
  x course of your code. This is necessary since a process will not necessarily
  always return or involve homogenous types.

  Related to this is type guarding: ensuring that a value is of the suitable type
  as a factor of control flow. For instance using typeof to ensure that an input
  is numerical before proceeding with a function's logic.


path: zk/Type_hinting.md
sortable_path: zkType_hinting.md
title: Type hinting in Python
checksum: 2edd704a1bbc701030556aa35823683193b46331daf39f1bed8c62a50c08a78b
created: 2024-03-30 09:03:21.720533103+00:00
modified: 2024-06-15 10:06:23.357526824+00:00
metadata: {"tags":["python","data-types"]}
lead:
  With type hinting we can add type information to variables, functions, and
  classes. This is not enforced by the Python interpreter but can be used by
  external tools like `mypy` to check the code.

body:
  With type hinting we can add type information to variables, functions, and
  classes. This is not enforced by the Python interpreter but can be used by
  external tools like `mypy` to check the code.

  Prior to 3.10, to use types you would have to import the types you wanted `from`
  the `typing` module. With later versions you can use them directly without
  importing from `typing`.

  ## Basic syntax

  ### Variables

  ```py
  name: str = "John"
  age: int = 30
  ```

  ### Functions

  ```py
  def greet(name: str) -> str:
      return "Hello, " + name
  ```

  ### Classes

  // TODO: Add examples

  ## Complex data types

  We can type complex data types such as dictionaries, lists, tuples etc.

  The following function takes a tuple comprising two integers as a parameter and
  returns a list of ints:

  ```py
  def populate_pair_values(pair: tuple[int, int]) -> list[int]:
      return [i for i in range(pair[0], pair[1] + 1)]
  ```

  ## Custom dictionary types

  We can create custom dictionary types in the manner of TypeScript types and
  interfaces as follows:

  ```py
  class ArticleInfo(TypedDict):
      timestamp: str
      article_    link: str
  ```

  We could then use it thus:

  ```py
  def parse_articles() -> list[ArticleInfo]:
  ```

  ## Optional types

  `Optional` can be used to indicate that a variable can be `None` or the
  specified type.

  ```py
  from typing import Optional

  def find_index(numbers: list[int], target: int) -> Optional[int]:
      try:
          return numbers.index(target)
      except ValueError:
          return None
  ```

  The function above returns an `int` or `None`.

  Post 3.10, we don't need to use `Optional`, we can use a union to cover the
  `None` case. Refactoring the previous example:

  ```py
  def find_index(numbers: list[int], target: int) -> int | None:
      try:
          return numbers.index(target)
      except ValueError:
          return None
  ```

  ## Union types

  `Union` can be used to indicate that a variable can be one of several types.

  ```py
  from typing import Union

  def add(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
      return a + b
  ```

  The function above returns an `int` or `float`.

  > Note that in the case of `Optional` and `Union` the type is specified using
  > square brackets. This is a syntactic covnention and does not denote that the
  > return type is a list. Below is an example of how we would use type hinting to
  > indicate that the return type actually is a list:

  ```py
  from typing import List, Union
  def my_function() -> List[int]:
      return [1, 2, 3]

  # And to show a return type of a list of integers or floats:

  def my_function() -> List[Union[int, float]]:
      return [1, 2, 3]
  ```

  Post 3.10 we can use `|` syntax instead of importing `Union`. E.g:

  ```py

  def add(a: int | float, b: int | float) -> int | float:
      return a + b
  ```

raw_content:
  ---
  tags:
    - python
    - data-types
  ---

  # Type hinting in Python

  With type hinting we can add type information to variables, functions, and
  classes. This is not enforced by the Python interpreter but can be used by
  external tools like `mypy` to check the code.

  Prior to 3.10, to use types you would have to import the types you wanted `from`
  the `typing` module. With later versions you can use them directly without
  importing from `typing`.

  ## Basic syntax

  ### Variables

  ```py
  name: str = "John"
  age: int = 30
  ```

  ### Functions

  ```py
  def greet(name: str) -> str:
      return "Hello, " + name
  ```

  ### Classes

  // TODO: Add examples

  ## Complex data types

  We can type complex data types such as dictionaries, lists, tuples etc.

  The following function takes a tuple comprising two integers as a parameter and
  returns a list of ints:

  ```py
  def populate_pair_values(pair: tuple[int, int]) -> list[int]:
      return [i for i in range(pair[0], pair[1] + 1)]
  ```

  ## Custom dictionary types

  We can create custom dictionary types in the manner of TypeScript types and
  interfaces as follows:

  ```py
  class ArticleInfo(TypedDict):
      timestamp: str
      article_    link: str
  ```

  We could then use it thus:

  ```py
  def parse_articles() -> list[ArticleInfo]:
  ```

  ## Optional types

  `Optional` can be used to indicate that a variable can be `None` or the
  specified type.

  ```py
  from typing import Optional

  def find_index(numbers: list[int], target: int) -> Optional[int]:
      try:
          return numbers.index(target)
      except ValueError:
          return None
  ```

  The function above returns an `int` or `None`.

  Post 3.10, we don't need to use `Optional`, we can use a union to cover the
  `None` case. Refactoring the previous example:

  ```py
  def find_index(numbers: list[int], target: int) -> int | None:
      try:
          return numbers.index(target)
      except ValueError:
          return None
  ```

  ## Union types

  `Union` can be used to indicate that a variable can be one of several types.

  ```py
  from typing import Union

  def add(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
      return a + b
  ```

  The function above returns an `int` or `float`.

  > Note that in the case of `Optional` and `Union` the type is specified using
  > square brackets. This is a syntactic covnention and does not denote that the
  > return type is a list. Below is an example of how we would use type hinting to
  > indicate that the return type actually is a list:

  ```py
  from typing import List, Union
  def my_function() -> List[int]:
      return [1, 2, 3]

  # And to show a return type of a list of integers or floats:

  def my_function() -> List[Union[int, float]]:
      return [1, 2, 3]
  ```

  Post 3.10 we can use `|` syntax instead of importing `Union`. E.g:

  ```py

  def add(a: int | float, b: int | float) -> int | float:
      return a + b
  ```


path: zk/Union_types.md
sortable_path: zkUnion_types.md
title: Union types
checksum: c821ae262bc36f541ebebc61d22c3d5bd95bb51fa3dd7b44522c80fe2956ee3f
created: 2024-03-30 09:03:21.728878828+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["typescript"]}
lead:
  Like intersection types, union types allow us to combine the properties of
  existing primitive and custom types to make composites.

body:
  Like intersection types, union types allow us to combine the properties of
  existing primitive and custom types to make composites.

  Intersection types work well when you have complete knowledge of what form your
  data will take. This approach only works if you know in advance that your data
  will be and that it will share properties from more than one type. But what if
  you don't have this knowledge? You only know that it will be one of the three
  possible types? This is where union types become helpful. Instead of saying that
  event X will be of type A we say that event X may be one of types A or B or C.

  > A union type is a type formed from two or more other types, representing
  > values that may be any one of those types. We refer to each of these types as
  > the union’s members.

  ## Demonstration

  ```ts
  type Petrol = {
    maxSpeed: number;
    tankVolume: number; // unique property
  };

  type Electric = {
    maxSpeed: number;
    maxCharge: number; // unique property
  };

  type Car = Petrol | Electric;

  const tesla: Car = {
    maxSpeed: 120,
    maxCharge: 22,
  };
  ```

  - We have defined two custom types: `Petrol` and `Electric`
  - We have unified them to make a new custom type of `Car`
  - `tesla` is an instance of the union type

  ## Usage with functions

  Union type syntax is not limited to the declaration phase. You can also use them
  as parameters to functions or function return types:

  ```ts
  function generateCar(car: Petrol | Electric): void {
    console.log(`The car has a max speed of ${car.maxSpeed}`);
  }
  ```

  ## Unifying primitive and custom types

  We can also use unions with primitive types as well as custom types:

  ```ts
  function printId(id: number | string): void {
    console.log(`Your id is ${id}`);
  }
  ```

  We can also combine primitive and custom types in the same union:

  ```ts
  function printStringOrNumber(): string | Petrol {}
  ```

  ## Set theory: understanding errors

  In JavaScript we have the primitive types of string , number , boolean etc. In
  TS, these types are distinct and exclusive: if you declare that x : string you
  cannot redefine it as boolean or expect Boolean properties and methods to belong
  to a string type. For instance a string type is incapable of having as its value
  false.

  This is not true in the case of unions.

  With a union of `string | boolean` a value `x` could be `'banana'` or `true`. We
  are allowing for all values that are either from the set string or the set
  boolean. Logically this is equivalent to the OR operator: a value of type
  `string | boolean` could be one of infinite possible strings or true/false. With
  union types then, we radically widen the scope of possible values for the type.
  a

  These constraints don't just apply to the values themselves but the methods and
  properties you can use with the values. For instance a boolean doesn't have the
  property of `length`. Therefore if you have a union type of `string|boolean` you
  are going to get an error if you apply `length` to it since **the property must
  exist on both types that comprise the union**. The way round this is to use type
  narrowing and execute control flow based on the parameter that is passed to a
  function that is declared to be of this mixed type.

  Here is an example of this:

  ```ts
  function printId(id: number | string) {
    if (typeof id === "string") {
      // In this branch, id is of type 'string'
      console.log(id.toUpperCase());
    } else {
      // Here, id is of type 'number'
      console.log(id);
    }
  }
  ```

raw_content:
  ---
  tags:
    - typescript
  ---

  # Union types

  Like intersection types, union types allow us to combine the properties of
  existing primitive and custom types to make composites.

  Intersection types work well when you have complete knowledge of what form your
  data will take. This approach only works if you know in advance that your data
  will be and that it will share properties from more than one type. But what if
  you don't have this knowledge? You only know that it will be one of the three
  possible types? This is where union types become helpful. Instead of saying that
  event X will be of type A we say that event X may be one of types A or B or C.

  > A union type is a type formed from two or more other types, representing
  > values that may be any one of those types. We refer to each of these types as
  > the union’s members.

  ## Demonstration

  ```ts
  type Petrol = {
    maxSpeed: number;
    tankVolume: number; // unique property
  };

  type Electric = {
    maxSpeed: number;
    maxCharge: number; // unique property
  };

  type Car = Petrol | Electric;

  const tesla: Car = {
    maxSpeed: 120,
    maxCharge: 22,
  };
  ```

  - We have defined two custom types: `Petrol` and `Electric`
  - We have unified them to make a new custom type of `Car`
  - `tesla` is an instance of the union type

  ## Usage with functions

  Union type syntax is not limited to the declaration phase. You can also use them
  as parameters to functions or function return types:

  ```ts
  function generateCar(car: Petrol | Electric): void {
    console.log(`The car has a max speed of ${car.maxSpeed}`);
  }
  ```

  ## Unifying primitive and custom types

  We can also use unions with primitive types as well as custom types:

  ```ts
  function printId(id: number | string): void {
    console.log(`Your id is ${id}`);
  }
  ```

  We can also combine primitive and custom types in the same union:

  ```ts
  function printStringOrNumber(): string | Petrol {}
  ```

  ## Set theory: understanding errors

  In JavaScript we have the primitive types of string , number , boolean etc. In
  TS, these types are distinct and exclusive: if you declare that x : string you
  cannot redefine it as boolean or expect Boolean properties and methods to belong
  to a string type. For instance a string type is incapable of having as its value
  false.

  This is not true in the case of unions.

  With a union of `string | boolean` a value `x` could be `'banana'` or `true`. We
  are allowing for all values that are either from the set string or the set
  boolean. Logically this is equivalent to the OR operator: a value of type
  `string | boolean` could be one of infinite possible strings or true/false. With
  union types then, we radically widen the scope of possible values for the type.
  a

  These constraints don't just apply to the values themselves but the methods and
  properties you can use with the values. For instance a boolean doesn't have the
  property of `length`. Therefore if you have a union type of `string|boolean` you
  are going to get an error if you apply `length` to it since **the property must
  exist on both types that comprise the union**. The way round this is to use type
  narrowing and execute control flow based on the parameter that is passed to a
  function that is declared to be of this mixed type.

  Here is an example of this:

  ```ts
  function printId(id: number | string) {
    if (typeof id === "string") {
      // In this branch, id is of type 'string'
      console.log(id.toUpperCase());
    } else {
      // Here, id is of type 'number'
      console.log(id);
    }
  }
  ```


path: zk/Unknown.md
sortable_path: zkUnknown.md
title: Unknown
checksum: 8c0f68fad261ac22cf13cf968e917e2308e19247798725d27b71e2be1a1a3da2
created: 2024-03-30 09:03:21.737215877+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["typescript"]}
lead:
  You might think that a good use case for `any` is a scenario where you don't
  know in advance what the data type is going to be.

body:
  You might think that a good use case for `any` is a scenario where you don't
  know in advance what the data type is going to be.

  In order to mark that this is the case, you type the value as `any`. In fact,
  TypeScript provides a type for this specific scenario: `unknown`.

  Like `any`, `unknown` is equivalent to every type in TS (it is also a supertype)
  but it is deliberately inhibiting, in contrast to `any`. When you use `unknown`
  you have to use type narrowing to avoid it being returned. So if your code
  starts with `unknown`, you should filter out scenarios where it evaluates to
  each of the possible types otherwise if `unknown` is returned it will cause an
  error.

raw_content:
  ---
  tags:
    - typescript
  ---

  # Unknown

  You might think that a good use case for `any` is a scenario where you don't
  know in advance what the data type is going to be.

  In order to mark that this is the case, you type the value as `any`. In fact,
  TypeScript provides a type for this specific scenario: `unknown`.

  Like `any`, `unknown` is equivalent to every type in TS (it is also a supertype)
  but it is deliberately inhibiting, in contrast to `any`. When you use `unknown`
  you have to use type narrowing to avoid it being returned. So if your code
  starts with `unknown`, you should filter out scenarios where it evaluates to
  each of the possible types otherwise if `unknown` is returned it will cause an
  error.


path: zk/Update_an_SQL_table.md
sortable_path: zkUpdate_an_SQL_table.md
title: Updating an SQL table
checksum: 9645a802e36a0c8433bba7eea42e93ce70bebe748378d2896fd9a1f511bf77c5
created: 2024-03-30 09:03:21.7468863+00:00
modified: 2024-06-15 09:48:17.284043094+00:00
metadata: {"tags":["SQL","databases"]}
lead:
  We use the `ALTER` query to add, remove and otherwise change the structural
  properties of a table in the database.

body:
  We use the `ALTER` query to add, remove and otherwise change the structural
  properties of a table in the database.

  ## Add an additional field to existing table

  This adds a `price` field to the `sales` table. The `price` field accepts data
  of the type `real`.

  ```sql
  ALTER TABLE sales ADD price real;
  ```

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

  # Updating an SQL table

  We use the `ALTER` query to add, remove and otherwise change the structural
  properties of a table in the database.

  ## Add an additional field to existing table

  This adds a `price` field to the `sales` table. The `price` field accepts data
  of the type `real`.

  ```sql
  ALTER TABLE sales ADD price real;
  ```


path: zk/Update_existing_data_in_SQL_table.md
sortable_path: zkUpdate_existing_data_in_SQL_table.md
title: Update existing data with the SQL UPDATE command
lead: We use `UPDATE` to modify existing records in our table.
checksum: 8d899c4df6b07aa260bf8286610be856b821f2fdc69aaebbd7557a866cf15cf5
created: 2024-03-30 09:03:21.766801302+00:00
modified: 2024-06-15 09:48:23.917508085+00:00
metadata: {"tags":["SQL","databases"]}
body:
  We use `UPDATE` to modify existing records in our table.

  ## Schematic syntax

  ```sql
  UPDATE [table_name]
  SET [field]
  WHERE [conditional expression/filter]
  ```

  ## Example

  ```sql
  UPDATE manufacturer
  SET url = '<http://www.hp.co.uk>'
  WHERE manufacturer_id = 4; --typically this will be the primary key as you are updating and existing record and need to identify it uniquely
  ```

  ## Update multiple fields

  ```sql
  UPDATE manufacturer
  SET url = '<http://www.apple.co.uk>',
      year_founded = 1977
  WHERE manufacturer_id = 2;
  ```

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

  # Update existing data with the SQL `UPDATE` command

  We use `UPDATE` to modify existing records in our table.

  ## Schematic syntax

  ```sql
  UPDATE [table_name]
  SET [field]
  WHERE [conditional expression/filter]
  ```

  ## Example

  ```sql
  UPDATE manufacturer
  SET url = '<http://www.hp.co.uk>'
  WHERE manufacturer_id = 4; --typically this will be the primary key as you are updating and existing record and need to identify it uniquely
  ```

  ## Update multiple fields

  ```sql
  UPDATE manufacturer
  SET url = '<http://www.apple.co.uk>',
      year_founded = 1977
  WHERE manufacturer_id = 2;
  ```


path: zk/Useful_operators_in_SQL.md
sortable_path: zkUseful_operators_in_SQL.md
title: Useful operators in SQL
lead: ## True and false
checksum: 378ed8a4886205275eb46560a2a2acab4e6a008ca30aa641e5a9529d3500620c
created: 2024-03-30 09:03:21.771065785+00:00
modified: 2024-06-15 09:48:32.657681264+00:00
metadata: {"tags":["SQL","databases"]}
body:
  ## True and false

  > In SQL false results are represented by `0` and true results are represented
  > by `1`. Any zero value is false and any non-zero value is true.

  > When comparing operators we can use `SELECT` as a general "execute" function
  > for SQL to interpret

  ```sql
  SELECT 0 = 0 -- 1
  SELECT 0.0 = 0 -- 1
  SELECT 9 >= 12 -- 0
  ```

  ## Boolean operators

  SQL supports all Boolean operators:

  ```sql
  SELECT (9 > 7) AND (3 < 5) -- 1
  SELECT (3 > 1) OR (2 > 5) -- 1
  SELECT (3 > 1) XOR (5 > 1) -- 0
  ```

  We also have `IS` and `IS NOT` for testing Boolean values:

  ```sql
  SELECT (9 > 7) IS TRUE -- 1
  SELECT (9 > 7) IS NOT TRUE -- 0
  ```

  ## `NULL`

  - `NULL` is neither true (1) or false (0). It has no value.
  - An empty string (`""`) is **not** `NULL`

  ```sql
  SELECT 1 IS NULL -- 0
  SELECT 1 IS NOT NULL -- 1
  SELECT NULL IS NULL -- 1
  SELECT "" IS NULL -- 0
  ```

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

  # Useful operators in SQL

  ## True and false

  > In SQL false results are represented by `0` and true results are represented
  > by `1`. Any zero value is false and any non-zero value is true.

  > When comparing operators we can use `SELECT` as a general "execute" function
  > for SQL to interpret

  ```sql
  SELECT 0 = 0 -- 1
  SELECT 0.0 = 0 -- 1
  SELECT 9 >= 12 -- 0
  ```

  ## Boolean operators

  SQL supports all Boolean operators:

  ```sql
  SELECT (9 > 7) AND (3 < 5) -- 1
  SELECT (3 > 1) OR (2 > 5) -- 1
  SELECT (3 > 1) XOR (5 > 1) -- 0
  ```

  We also have `IS` and `IS NOT` for testing Boolean values:

  ```sql
  SELECT (9 > 7) IS TRUE -- 1
  SELECT (9 > 7) IS NOT TRUE -- 0
  ```

  ## `NULL`

  - `NULL` is neither true (1) or false (0). It has no value.
  - An empty string (`""`) is **not** `NULL`

  ```sql
  SELECT 1 IS NULL -- 0
  SELECT 1 IS NOT NULL -- 1
  SELECT NULL IS NULL -- 1
  SELECT "" IS NULL -- 0
  ```


path: zk/User_Space.md
sortable_path: zkUser_Space.md
title: User space
lead:
  User space is the portion of the main memory that the kernel allocates for user
  processes.

checksum: 0f715b149fd53bcd27b76a8ea54ca32c44d754de7d0b4a42f11cf03b22269cb8
created: 2024-03-30 09:03:21.777096563+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":null}
body:
  User space is the portion of the main memory that the kernel allocates for user
  processes.

  There is a hierarchy within the user space with several different groups of user
  processes:

  <dl>

  <dt>Basic services</dt>
  <dd>Run at the bottom level and therefore are closest to ther kernel. Comprises small components that perform single, uncomplicated tasks. Examples include network configuration, the communication bus, and diagnostic logging</dd> 
  <dt>Utiility services</dt>
  <dd>The middle level: larger components such as mail, print, and database services</dd>
  <dt>Services controllable by the user</dt>
  <dd>The top level: complicated tasks that the user controls directly such as using a web-browser, editing a text file</dd>
  </dl>

  ## What is a user?

  > A user is an entity that can run processes and own files.

  At the level of users, a user is associated with a user name however the kernel
  does not manage usernames, it identifies users via numeric identifiers called
  **user IDs**.

  Users exist in order to support permissions and access boundaries. A user may
  terminate or modify the behaviour of its own processes (within limits) but
  cannot interfere with another user's proceses. In addition, users may own files
  and choose whether to share them with other users.

  ### Root user (superuser)

  A Linux system normally has a number of users in addition to the ones that
  correspond to the real human users. The most important is **root**. The root
  user _can_ terminate and alter another user's processes and access any file on
  the local system.

  A person who can operate as root is an administrator on a typical user system.

raw_content:
  ---
  tags:
  ---

  # User space

  User space is the portion of the main memory that the kernel allocates for user
  processes.

  There is a hierarchy within the user space with several different groups of user
  processes:

  <dl>

  <dt>Basic services</dt>
  <dd>Run at the bottom level and therefore are closest to ther kernel. Comprises small components that perform single, uncomplicated tasks. Examples include network configuration, the communication bus, and diagnostic logging</dd> 
  <dt>Utiility services</dt>
  <dd>The middle level: larger components such as mail, print, and database services</dd>
  <dt>Services controllable by the user</dt>
  <dd>The top level: complicated tasks that the user controls directly such as using a web-browser, editing a text file</dd>
  </dl>

  ## What is a user?

  > A user is an entity that can run processes and own files.

  At the level of users, a user is associated with a user name however the kernel
  does not manage usernames, it identifies users via numeric identifiers called
  **user IDs**.

  Users exist in order to support permissions and access boundaries. A user may
  terminate or modify the behaviour of its own processes (within limits) but
  cannot interfere with another user's proceses. In addition, users may own files
  and choose whether to share them with other users.

  ### Root user (superuser)

  A Linux system normally has a number of users in addition to the ones that
  correspond to the real human users. The most important is **root**. The root
  user _can_ terminate and alter another user's processes and access any file on
  the local system.

  A person who can operate as root is an administrator on a typical user system.


path: zk/User_management.md
sortable_path: zkUser_management.md
title: User management
lead: ## Switch user
checksum: 7471761c9969b0082d177ba36ec4df2757b179da41d029f2c7c1b34483623f84
created: 2024-03-30 09:03:21.78483203+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":[]}
body:
  ## Switch user

  If already logged in as a user you can switch users with the command
  `su - [username]`.

  ## Login as root

  If you are logged in as a standard user, if you use `su -` without specifying a
  username, then it will assume you wish to log in as root.

  If you wish to login as root in the tty at startup, then use `root` as the
  username.

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

  # User management

  ## Switch user

  If already logged in as a user you can switch users with the command
  `su - [username]`.

  ## Login as root

  If you are logged in as a standard user, if you use `su -` without specifying a
  username, then it will assume you wish to log in as root.

  If you wish to login as root in the tty at startup, then use `root` as the
  username.


path: zk/User_management_on_AWS.md
sortable_path: zkUser_management_on_AWS.md
title: User management and roles
lead: ## IAM
checksum: 173c1b3adbc04dfc09924b34e135e231e739c359cede8053c343a03bc5c2f4a8
created: 2024-03-30 09:03:21.791840628+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["AWS"]}
body:
  ## IAM

  > Identity and Access Management

  An IAM user is an identity with long-term credentials that is used to interact
  with AWS in an account. An IAM login is different from a root login. The root
  user is the one who creates the AWS account and who has the power to delete it.
  The root user can create IAM logins so that other users can log in through
  dedicated IAM portals. The root user should also create an IAM login for himself
  and use that, set to administrator policies, for most activities, reserving root
  for the most consequential stuff like adding users etc.

  ## Users and user groups

  - Use to create users and access rights accross the AWS services
  - Users get permissions through policies (use template or create your own)
  - You can also create user groups and add users to them to simplify role
    attribution

  ## Roles

  Roles are adopted by _services_ rather than users. They run either periodically
  (from a `cronjob` say) or when they are hit from an API.

  This allows you to assign policies to a role that a service can adopt when
  running. A good example is a lambda. Lambdas often need access to other services
  such as an S3 bucket or a DynamoDB table. Say you have a service that combines a
  lambda with a DynamoDB database. You could assign a role to the lambda and it
  would have access to the database.

  ## Distinction between _principal_ and _identity_

  Both “principal” and “identity” refer to entities that can perform actions and
  interact with AWS resources. However, there is a subtle difference in their
  usage:

  > a principal is a specific type of entity that can take actions in AWS, while
  > an identity is the unique identifier associated with that principal.

  1. Principal: In the context of IAM policies, a principal represents the entity
     that is allowed or denied access to AWS rThe principal is specified in the
     policy statement as the entity to which the permissions are granted or
     deniedesources. It can be an IAM user, an IAM role, an AWS service. The
     principal is specified in the policy statement as the entity to which the
     permissions are granted or denied.
  2. Identity: An identity, on the other hand, is a broader term that encompasses
     both the principal and the authentication credentials associated with that
     principal. It refers to the entity’s unique identifier, such as an IAM user’s
     username or an IAM role’s ARN (Amazon Resource Name). An identity is used for
     authentication purposes to verify the entity’s identity and determine its
     permissions.

  ## Cognito

  > Amazon Cognito provides authentication, authorization, and user management for
  > your web and mobile apps.

  There are **user pools** and **identity pools**.

  User pools provide sign-up and sign-in options for your app users. Identity
  pools provide AWS credentials to grant your users access to other AWS services.

  You would use identity pools to manage access to API endpoints or S3 buckets.
  They can be authenticated or unauthenticated. For example if you want to use an
  image from S3 in a website you would authenticate for that, but you would
  explicitly have to specify that it is general access by applying the policy
  `AmazonS3ReadOnlyAccess`.

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

  # User management and roles

  ## IAM

  > Identity and Access Management

  An IAM user is an identity with long-term credentials that is used to interact
  with AWS in an account. An IAM login is different from a root login. The root
  user is the one who creates the AWS account and who has the power to delete it.
  The root user can create IAM logins so that other users can log in through
  dedicated IAM portals. The root user should also create an IAM login for himself
  and use that, set to administrator policies, for most activities, reserving root
  for the most consequential stuff like adding users etc.

  ## Users and user groups

  - Use to create users and access rights accross the AWS services
  - Users get permissions through policies (use template or create your own)
  - You can also create user groups and add users to them to simplify role
    attribution

  ## Roles

  Roles are adopted by _services_ rather than users. They run either periodically
  (from a `cronjob` say) or when they are hit from an API.

  This allows you to assign policies to a role that a service can adopt when
  running. A good example is a lambda. Lambdas often need access to other services
  such as an S3 bucket or a DynamoDB table. Say you have a service that combines a
  lambda with a DynamoDB database. You could assign a role to the lambda and it
  would have access to the database.

  ## Distinction between _principal_ and _identity_

  Both “principal” and “identity” refer to entities that can perform actions and
  interact with AWS resources. However, there is a subtle difference in their
  usage:

  > a principal is a specific type of entity that can take actions in AWS, while
  > an identity is the unique identifier associated with that principal.

  1. Principal: In the context of IAM policies, a principal represents the entity
     that is allowed or denied access to AWS rThe principal is specified in the
     policy statement as the entity to which the permissions are granted or
     deniedesources. It can be an IAM user, an IAM role, an AWS service. The
     principal is specified in the policy statement as the entity to which the
     permissions are granted or denied.
  2. Identity: An identity, on the other hand, is a broader term that encompasses
     both the principal and the authentication credentials associated with that
     principal. It refers to the entity’s unique identifier, such as an IAM user’s
     username or an IAM role’s ARN (Amazon Resource Name). An identity is used for
     authentication purposes to verify the entity’s identity and determine its
     permissions.

  ## Cognito

  > Amazon Cognito provides authentication, authorization, and user management for
  > your web and mobile apps.

  There are **user pools** and **identity pools**.

  User pools provide sign-up and sign-in options for your app users. Identity
  pools provide AWS credentials to grant your users access to other AWS services.

  You would use identity pools to manage access to API endpoints or S3 buckets.
  They can be authenticated or unauthenticated. For example if you want to use an
  image from S3 in a website you would authenticate for that, but you would
  explicitly have to specify that it is general access by applying the policy
  `AmazonS3ReadOnlyAccess`.


path: zk/Using_GraphQL_with_Node.md
sortable_path: zkUsing_GraphQL_with_Node.md
title: Using GraphQL with Node.js
lead: ## Create a basic Express server
checksum: e3c1f88a98fa95431d72ca0c9afb595dbff93cd2697e9b17e3c285598ac98a95
created: 2024-03-30 09:03:21.797366505+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["graphql","node-js"]}
body:
  ## Create a basic Express server

  First we create a basic server in Node using Express:

  ```js
  import express from "express";

  app.get("/", (req, res) => {
    res.send("Graph QL test project");
  });

  app.listen(8080, () =>
    console.log("Running server on port localhost:8080/graphql")
  );
  ```

  ## Add GraphQL as middlewear

  Next we introduce GraphQL as a piece of Node.js
  [middlewear](Middleware.md), with the
  `app.use()` method.

  ```js
  import { graphqlHTTP } from "express-graphql";

  app.use(
    "/graphql",
    graphqlHTTP({
      schema: schema,
      rootValue: resolvers,
      graphiql: true,
    })
  );
  ```

  - `schema` is a reference to our GraphQL schema - the structure of the fields
    that define our server.
  - `rootValue` is a reference to our resolvers.
  - `graphiql` is the GUI tool that will be served from the GraphQL endpoint at
    `localhost:8080/graphql`. This tool enables us to interrogate our data using
    the defined schema and see what data we would get back from frontend queries.

raw_content:
  ---
  tags: [graphql, node-js]
  ---

  # Using GraphQL with Node.js

  ## Create a basic Express server

  First we create a basic server in Node using Express:

  ```js
  import express from "express";

  app.get("/", (req, res) => {
    res.send("Graph QL test project");
  });

  app.listen(8080, () =>
    console.log("Running server on port localhost:8080/graphql")
  );
  ```

  ## Add GraphQL as middlewear

  Next we introduce GraphQL as a piece of Node.js
  [middlewear](Middleware.md), with the
  `app.use()` method.

  ```js
  import { graphqlHTTP } from "express-graphql";

  app.use(
    "/graphql",
    graphqlHTTP({
      schema: schema,
      rootValue: resolvers,
      graphiql: true,
    })
  );
  ```

  - `schema` is a reference to our GraphQL schema - the structure of the fields
    that define our server.
  - `rootValue` is a reference to our resolvers.
  - `graphiql` is the GUI tool that will be served from the GraphQL endpoint at
    `localhost:8080/graphql`. This tool enables us to interrogate our data using
    the defined schema and see what data we would get back from frontend queries.


path: zk/Using_arguments_with_Apollo_Client.md
sortable_path: zkUsing_arguments_with_Apollo_Client.md
title: Using arguments with Apollo Client
lead: ## What are arguments?
checksum: 3d64509966807a581a0b74dcb400372cbc2441e505fe91173b05d7fed2473ed1
created: 2024-03-30 09:03:21.808215412+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["graphql"]}
body:
  ## What are arguments?

  - An argument is a value you provide for a particular field in a query.
  - The schema must define the arguments that a field accepts (if any)
  - The resolver invokes a field's provided arguments to determine how to return
    specific data
  - Some example use cases of arguments:
    - retrieve specific objects
    - filter through a set of objects
    - transform a field's returned value

  ## Updated schema

  In order to demonstrate arguments we need to expand the
  [original schema](Apollo_Server.md#example-schema).

  Remember a Track is a group of modules that teaches about a specific topic. We
  are going to add:

  - `description` and `numberOfViews` fields to the original `Track` type
  - A new `Module` type
  - A field `modules` to the `Track` type that will be an array of type `Module`

  ```js
  type Query {
    tracksForHome: [Track!]!
  }

  type Track {
      id: ID!
          authorId: String! // They added this on the sly
      thumbnail: String
      length: Int
      modulesCount: Int
      description: String
      numberOfViews: Int
      modules: [Module!]!
  }

  type Module {
      id: ID!
          length: Int
  }

  type Author {
      id: ID!
      name: String!
      photo: String
  }
  ```

  ## Adding additional query

  > Currently we only have one query in the schema (`tracksForHome`) and this
  > returns an array of all the tracks. To demonstrate arguments we want to return
  > a specific track by its `id`. We are going to add a query that enables this.

  ```js
  type Query {
      track(id: ID!):  Track
  }
  ```

  This query will take an `id` as an argument and the resolver will match the `id`
  to a given track and return that `Track`.

  To define an argument for a field in the schema we add parentheses after the
  field name. In the parentheses we write the name of the argument and its type.
  If we have more than one argument we can separate them with commas.

  ## Create resolver for new query

  Now we have to create a resolver for our new `track` query. We will quickly run
  through the [server-side process](Apollo_Server.md).

  ### Update our `RESTDataSource`

  ```js
  class TrackAPI extends RESTDataSource {
    constructor() {...}
    getTracksForHome() {...}
    getAuthor(authorId) {...}

    getTrack(trackId){
      return this.get(`track/${trackId}`);
    }
  }

  ```

  ### Add resolver to `resolvers.js`

  ```js
  const resolvers = {
    Query: {
      tracksForHome: (_, __, { dataSources }) => {
        return dataSources.trackApi.getTracksForHome();
      },
      track: (_, { id }, { dataSources }) => {
        return dataSources.trackAPI.getTrack(id);
      },
    },
    Track: {
      author: ({ authorId }, _, { dataSources }) => {
        return dataSources.trackApi.getAuthor(authorId);
      },
    },
  };
  ```

  ### Making a query

  With the server changes complete, we can now issue a query with an argument from
  the client:

  ```js
  query track(id: 'xyz'){
    title
  }
  ```

  This will return the `title` field from the track with the specific id. This
  query complies with the schema since the `Track` type has the field `title`

  ### Resolver chains

  What about the following query:

  ```js
  query track(id: 'xyz'){
    title
    author {
      name
    }
  }
  ```

  It's not obvious how the resolver should respond to the nested query here since
  author name is not a field on `Track`, it is a field on `Author`:

  ```js
  type Track {
    id: ID!
      authorId: String!
    thumbnail: String
    length: Int
    modulesCount: Int
    description: String
    numberOfViews: Int
    modules: [Module!]!
  }

  type Author {
    id: ID!
    name: String!
    photo: String
  }
  ```

  This is where we use a **resolver chain**. Because `authorId` exists on `Track`
  we can use this to get the `name` part of the query. We already have a resolver
  for `author` under the `Track` resolver:

  ```js
  Track: {
      author: ({ authorId }, _, { dataSources }) => {
        return dataSources.trackApi.getAuthor(authorId);
      },
    },
  ```

  Notice that `authorId` is used in the place of the `parent` parameter. It
  already exists on the `Track` type that wraps the resolver. So this can be
  invoked to fulfill `author` and thereby access the author name from the graph.

  This process is also required for our extended schema. The `Track` type now has
  a `modules` field that comprises an array of the `Module` type.

  Here's a reminder of the `Module` type:

  ```js
  type Module {
      id: ID!
          length: Int
  }
  ```

  The modules for a `Track` can be sourced from the track `id`. So we can again
  add a resolver chain function to the resolvers file:

  ```js
  const resolvers = {
    Query: {
      tracksForHome: (_, __, { dataSources }) => {
        return dataSources.trackApi.getTracksForHome();
      },
      track: (_, { id }, { dataSources }) => {
        return dataSources.trackAPI.getTrack(id);
      },
    },
    Track: {
      author: ({ authorId }, _, { dataSources }) => {
        return dataSources.trackApi.getAuthor(authorId);
      },
      modules: ({ id }, _, { dataSources }) => {
        return dataSources.trackAPI.getTrackModules(id);
      },
    },
  };
  ```

  Once again we are using a property on the parent `Track` type to use as a
  parameter in the resolver function.

  This setup would enables like the following to be run:

  ```js
  query track(id: 'xyz'){
    title
    modules
  }
  ```

  The `id` parameter would be used by the `modules` resolver to return the array
  of the `Modules` type.

  ## Variables in arguments

  Instead of writing the following within our query constants on the client-side:

  ```js
  query GetTrack {
    track(id: 'xyz'){
      title
    }
  }
  ```

  We can make the code more reusable by using variables instead of the hardcoded
  `id` argument:

  ```js
  query GetTrack($trackId: ID!) {
    track(id: $trackId){
      title
    }
  }
  ```

  This way we do not need to write a new query constant every time we want to
  return a specific track.

  ## Send query with arguments using Apollo `useQuery`

  We can now write a proper query using the
  [useQuery hook](Apollo_Client.md#usequery-hook) from
  Apollo Client, with variables.

  First define our query constant:

  ```js
  export const GET_TRACK = gql`
    query GetTrack($trackId: ID!) {
      track(id: $trackId) {
        id
        title
        author {
          name
        }
        description
        modules {
          id
          title
          length
        }
      }
    }
  `;
  ```

  Then to employ in React:

  ```jsx
  const trackId = "xyz";

  const { loading, error, data } = useQuery(GET_TRACK, {
    variables: {
      id: trackId,
    },
  });
  ```

  Note that in contrast to the
  [simple example](Apollo_Client.md#query-constants)
  because we are using variables, we have to pass-in an additional options object
  with the query constant that specifies our variables.

raw_content:
  ---
  tags: [graphql]
  ---

  # Using arguments with Apollo Client

  ## What are arguments?

  - An argument is a value you provide for a particular field in a query.
  - The schema must define the arguments that a field accepts (if any)
  - The resolver invokes a field's provided arguments to determine how to return
    specific data
  - Some example use cases of arguments:
    - retrieve specific objects
    - filter through a set of objects
    - transform a field's returned value

  ## Updated schema

  In order to demonstrate arguments we need to expand the
  [original schema](Apollo_Server.md#example-schema).

  Remember a Track is a group of modules that teaches about a specific topic. We
  are going to add:

  - `description` and `numberOfViews` fields to the original `Track` type
  - A new `Module` type
  - A field `modules` to the `Track` type that will be an array of type `Module`

  ```js
  type Query {
    tracksForHome: [Track!]!
  }

  type Track {
      id: ID!
          authorId: String! // They added this on the sly
      thumbnail: String
      length: Int
      modulesCount: Int
      description: String
      numberOfViews: Int
      modules: [Module!]!
  }

  type Module {
      id: ID!
          length: Int
  }

  type Author {
      id: ID!
      name: String!
      photo: String
  }
  ```

  ## Adding additional query

  > Currently we only have one query in the schema (`tracksForHome`) and this
  > returns an array of all the tracks. To demonstrate arguments we want to return
  > a specific track by its `id`. We are going to add a query that enables this.

  ```js
  type Query {
      track(id: ID!):  Track
  }
  ```

  This query will take an `id` as an argument and the resolver will match the `id`
  to a given track and return that `Track`.

  To define an argument for a field in the schema we add parentheses after the
  field name. In the parentheses we write the name of the argument and its type.
  If we have more than one argument we can separate them with commas.

  ## Create resolver for new query

  Now we have to create a resolver for our new `track` query. We will quickly run
  through the [server-side process](Apollo_Server.md).

  ### Update our `RESTDataSource`

  ```js
  class TrackAPI extends RESTDataSource {
    constructor() {...}
    getTracksForHome() {...}
    getAuthor(authorId) {...}

    getTrack(trackId){
      return this.get(`track/${trackId}`);
    }
  }

  ```

  ### Add resolver to `resolvers.js`

  ```js
  const resolvers = {
    Query: {
      tracksForHome: (_, __, { dataSources }) => {
        return dataSources.trackApi.getTracksForHome();
      },
      track: (_, { id }, { dataSources }) => {
        return dataSources.trackAPI.getTrack(id);
      },
    },
    Track: {
      author: ({ authorId }, _, { dataSources }) => {
        return dataSources.trackApi.getAuthor(authorId);
      },
    },
  };
  ```

  ### Making a query

  With the server changes complete, we can now issue a query with an argument from
  the client:

  ```js
  query track(id: 'xyz'){
    title
  }
  ```

  This will return the `title` field from the track with the specific id. This
  query complies with the schema since the `Track` type has the field `title`

  ### Resolver chains

  What about the following query:

  ```js
  query track(id: 'xyz'){
    title
    author {
      name
    }
  }
  ```

  It's not obvious how the resolver should respond to the nested query here since
  author name is not a field on `Track`, it is a field on `Author`:

  ```js
  type Track {
    id: ID!
      authorId: String!
    thumbnail: String
    length: Int
    modulesCount: Int
    description: String
    numberOfViews: Int
    modules: [Module!]!
  }

  type Author {
    id: ID!
    name: String!
    photo: String
  }
  ```

  This is where we use a **resolver chain**. Because `authorId` exists on `Track`
  we can use this to get the `name` part of the query. We already have a resolver
  for `author` under the `Track` resolver:

  ```js
  Track: {
      author: ({ authorId }, _, { dataSources }) => {
        return dataSources.trackApi.getAuthor(authorId);
      },
    },
  ```

  Notice that `authorId` is used in the place of the `parent` parameter. It
  already exists on the `Track` type that wraps the resolver. So this can be
  invoked to fulfill `author` and thereby access the author name from the graph.

  This process is also required for our extended schema. The `Track` type now has
  a `modules` field that comprises an array of the `Module` type.

  Here's a reminder of the `Module` type:

  ```js
  type Module {
      id: ID!
          length: Int
  }
  ```

  The modules for a `Track` can be sourced from the track `id`. So we can again
  add a resolver chain function to the resolvers file:

  ```js
  const resolvers = {
    Query: {
      tracksForHome: (_, __, { dataSources }) => {
        return dataSources.trackApi.getTracksForHome();
      },
      track: (_, { id }, { dataSources }) => {
        return dataSources.trackAPI.getTrack(id);
      },
    },
    Track: {
      author: ({ authorId }, _, { dataSources }) => {
        return dataSources.trackApi.getAuthor(authorId);
      },
      modules: ({ id }, _, { dataSources }) => {
        return dataSources.trackAPI.getTrackModules(id);
      },
    },
  };
  ```

  Once again we are using a property on the parent `Track` type to use as a
  parameter in the resolver function.

  This setup would enables like the following to be run:

  ```js
  query track(id: 'xyz'){
    title
    modules
  }
  ```

  The `id` parameter would be used by the `modules` resolver to return the array
  of the `Modules` type.

  ## Variables in arguments

  Instead of writing the following within our query constants on the client-side:

  ```js
  query GetTrack {
    track(id: 'xyz'){
      title
    }
  }
  ```

  We can make the code more reusable by using variables instead of the hardcoded
  `id` argument:

  ```js
  query GetTrack($trackId: ID!) {
    track(id: $trackId){
      title
    }
  }
  ```

  This way we do not need to write a new query constant every time we want to
  return a specific track.

  ## Send query with arguments using Apollo `useQuery`

  We can now write a proper query using the
  [useQuery hook](Apollo_Client.md#usequery-hook) from
  Apollo Client, with variables.

  First define our query constant:

  ```js
  export const GET_TRACK = gql`
    query GetTrack($trackId: ID!) {
      track(id: $trackId) {
        id
        title
        author {
          name
        }
        description
        modules {
          id
          title
          length
        }
      }
    }
  `;
  ```

  Then to employ in React:

  ```jsx
  const trackId = "xyz";

  const { loading, error, data } = useQuery(GET_TRACK, {
    variables: {
      id: trackId,
    },
  });
  ```

  Note that in contrast to the
  [simple example](Apollo_Client.md#query-constants)
  because we are using variables, we have to pass-in an additional options object
  with the query constant that specifies our variables.


path: zk/Utilities,_operators,_flags.md
sortable_path: zkUtilities,_operators,_flags.md
title: Flags
lead: ### Prevent newline
checksum: a337ba34dc84259e41d04f49430beff149ec176bc74d5f2bf49b7b17623cd857
created: 2024-03-30 09:03:21.835849695+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["shell"]}
body:
  ### Prevent newline

  Prevent bash from adding a new line after an echo:

  ```bash
  echo 'Your name is Thomas'
  echo 'and you are 33 years old'

  # Your name is Thomas
  # and you are 33 years old
  ```

  ```bash
  echo -n 'Your name is Thomas '
  echo 'and you are 33 years old'

  # Your name is Thomas and you are 33 years old
  ```

  ## Operators

  ### Mathematical

  ```bash
  -lt , -gt,
  ```

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

  The following are useful built-in utility methods that you can use for checking
  and validation in the course of your bash scripts.

  ## Flags

  ### Prevent newline

  Prevent bash from adding a new line after an echo:

  ```bash
  echo 'Your name is Thomas'
  echo 'and you are 33 years old'

  # Your name is Thomas
  # and you are 33 years old
  ```

  ```bash
  echo -n 'Your name is Thomas '
  echo 'and you are 33 years old'

  # Your name is Thomas and you are 33 years old
  ```

  ## Operators

  ### Mathematical

  ```bash
  -lt , -gt,
  ```


path: zk/Validating_Mongoose_schemas.md
sortable_path: zkValidating_Mongoose_schemas.md
title: Validating Mongoose schemas
lead: Let's say we have the following schema:
checksum: 6496732cadf905b4237080346be416f1e3c35867c00365de046d36f3297ba104
created: 2024-03-30 09:03:21.840865001+00:00
modified: 2024-06-15 10:04:52.229227212+00:00
metadata: {"tags":["mongo-db","mongoose","node-js","databases"]}
body:
  Let's say we have the following schema:

  ```js
  const courseSchema = new mongoose.Schema({
    name: { type: String, required: true, minlength: 5, maxlength: 255 },
    author: String,
    tags: [String],
    data: { type: Date, default: Date.now }, // if unspecified, entry will default to current date
    isPublished: Boolean,
  });
  ```

  We set our validation criteria as the second property for each schema value. If
  it does not obtain, an exception will be thrown when we attempt to modify the
  database. This will most likely take the form of a Promise rejection.

  Note that this validation only occurs at the level of Mongoose. There is no
  mechanism for running the validation within the database itself. This is one of
  the ways that Mongo differs from an SQL database where you would be able to set
  the property of `required` for a cell in the table. If we didn't set any
  validation via Mongoose, Mongo would accept whatever we sent to it.

  What is the relationship between this Mongoose validation and the
  [Joi](Validation.md) validation that we
  use when validating API requests in Node/Express? They complement each other. We
  use Joi to validate the client request to the API. If this is valid, the process
  would then move onto the next stage which would be transforming the data from a
  REST request to a database request implemented through Mongoose, where a second
  layer of validation would appy. We would also have validation on the
  client-side.

  ## Validator functions

  Instead of simply applying a built-in validator like `required`, we can instead
  pass a function that validates agains the validation status of another property
  in the schema. For instance: make the `price` property required only if the
  entry has `true` for `isPublished`:

  ```js
  const courseSchema = new mongoose.Schema({
    ...
    isPublished: Boolean,
    price: {
      type: Number,
      required: function() {return this.isPublished}
    }
  });
  ```

  ## Enum validators

  An enum validator enables us to ensure that whatever value is passed when
  instantiating a document that is of a given schema type, it must be one of a
  finite set of values:

  ```js
  const anotherSchema = new mongoose.Schema({
    sex: {
      type: String,
      enum: ["male", "female"],
    },
  });
  ```

raw_content:
  ---
  tags:
    - mongo-db
    - mongoose
    - node-js
    - databases
  ---

  # Validating Mongoose schemas

  Let's say we have the following schema:

  ```js
  const courseSchema = new mongoose.Schema({
    name: { type: String, required: true, minlength: 5, maxlength: 255 },
    author: String,
    tags: [String],
    data: { type: Date, default: Date.now }, // if unspecified, entry will default to current date
    isPublished: Boolean,
  });
  ```

  We set our validation criteria as the second property for each schema value. If
  it does not obtain, an exception will be thrown when we attempt to modify the
  database. This will most likely take the form of a Promise rejection.

  Note that this validation only occurs at the level of Mongoose. There is no
  mechanism for running the validation within the database itself. This is one of
  the ways that Mongo differs from an SQL database where you would be able to set
  the property of `required` for a cell in the table. If we didn't set any
  validation via Mongoose, Mongo would accept whatever we sent to it.

  What is the relationship between this Mongoose validation and the
  [Joi](Validation.md) validation that we
  use when validating API requests in Node/Express? They complement each other. We
  use Joi to validate the client request to the API. If this is valid, the process
  would then move onto the next stage which would be transforming the data from a
  REST request to a database request implemented through Mongoose, where a second
  layer of validation would appy. We would also have validation on the
  client-side.

  ## Validator functions

  Instead of simply applying a built-in validator like `required`, we can instead
  pass a function that validates agains the validation status of another property
  in the schema. For instance: make the `price` property required only if the
  entry has `true` for `isPublished`:

  ```js
  const courseSchema = new mongoose.Schema({
    ...
    isPublished: Boolean,
    price: {
      type: Number,
      required: function() {return this.isPublished}
    }
  });
  ```

  ## Enum validators

  An enum validator enables us to ensure that whatever value is passed when
  instantiating a document that is of a given schema type, it must be one of a
  finite set of values:

  ```js
  const anotherSchema = new mongoose.Schema({
    sex: {
      type: String,
      enum: ["male", "female"],
    },
  });
  ```


path: zk/Validation.md
sortable_path: zkValidation.md
title: Creating a RESTful API: Validation
checksum: 6f0c4e7f0af95a7522c442d4991bd06ab0a817dac6eabb19ed81afb036089759
created: 2024-03-30 09:03:21.853146113+00:00
modified: 2024-06-15 09:50:54.243778564+00:00
metadata: {"tags":["node-js","REST","APIs"]}
lead:
  We can provide server-side validation for our projects by using a **schema
  validator**. This is a program that declaratively parses the JSON values
  received as requests from the client. This makes it easy to systematically
  validate the data that we receive from any HTTP requests where the client sends
  a body to the endpoint.

body:
  We can provide server-side validation for our projects by using a **schema
  validator**. This is a program that declaratively parses the JSON values
  received as requests from the client. This makes it easy to systematically
  validate the data that we receive from any HTTP requests where the client sends
  a body to the endpoint.

  One of the most popular schema validators for NodeJS is
  [joi](joi).

  ## Demonstration

  Let's say we have a POST request that expects a single field as the body that
  must be a string and greater than two characters long. First we define our
  schema:

  ```js
  const schema = Joi.object({
    name: Joi.string().min(3).required(),
  });

  const { error } = schema.validate(req.body);
  ```

  The `schema` variable is an object whose keys should match those of the intended
  request body. Instead of actual values we provide Joi's in-built validators,
  concatenated as necessary. We then store the results of the validation in a
  variable.

  Next we add handling in the case of errors:

  ```js
  if (error) {
    error.details.map((joiErr) => res.status(400).send(joiErr.message));
    return;
  }
  ```

  We loop through the error array and return 400s as the response if they are
  found. If there are no errors, the Joi object will return `undefined`.

raw_content:
  ---
  tags:
    - node-js
    - REST
    - APIs
  ---

  # Creating a RESTful API: Validation

  We can provide server-side validation for our projects by using a **schema
  validator**. This is a program that declaratively parses the JSON values
  received as requests from the client. This makes it easy to systematically
  validate the data that we receive from any HTTP requests where the client sends
  a body to the endpoint.

  One of the most popular schema validators for NodeJS is
  [joi](joi).

  ## Demonstration

  Let's say we have a POST request that expects a single field as the body that
  must be a string and greater than two characters long. First we define our
  schema:

  ```js
  const schema = Joi.object({
    name: Joi.string().min(3).required(),
  });

  const { error } = schema.validate(req.body);
  ```

  The `schema` variable is an object whose keys should match those of the intended
  request body. Instead of actual values we provide Joi's in-built validators,
  concatenated as necessary. We then store the results of the validation in a
  variable.

  Next we add handling in the case of errors:

  ```js
  if (error) {
    error.details.map((joiErr) => res.status(400).send(joiErr.message));
    return;
  }
  ```

  We loop through the error array and return 400s as the response if they are
  found. If there are no errors, the Joi object will return `undefined`.


path: zk/Validity_and_entailment.md
sortable_path: zkValidity_and_entailment.md
title: Validity
lead: ### Informal definition
checksum: 6584299fc0369b2b59b2914c24ce7a50c67c1c51701beacf50c231b8f29309fe
created: 2024-03-30 09:03:21.866073262+00:00
modified: 2024-06-16 16:53:19.349602968+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  ### Informal definition

  In order to say whether an argument is 'good' or 'bad' we must have criteria of
  evaluation. in logic there are different criteria of evaluation:

  - **Deductive validity**

    An **argument is deductively valid if and only if it is not possible for the
    premises to be true and the conclusion false**. Linking to consistency: it is
    not possible to consistently assert all of the premises but deny the
    conclusion.

  - **Inductive strength**

    We do not say that inductive arguments have 'validity' because despite
    inductive premises being true, the conclusion may be falsifiable. Therefore we
    say inductive 'strength' rather than 'validity'. An argument is inductively
    strong if and only if the conclusion is probably true given the premises.

  #### Demonstration

  The Socrates demonstration above is an example of deductive validity.

  The following is an example of an argument that is inductively strong:

  ```
  99% of deaf persons have no musical talent.
  Beethoven was deaf.
  ___________________________________________
  Beethoven had no musical talent.
  ```

  The test for a strong inductive argument is not whether the conclusion is true,
  rather it concerns the evidence the premises provide in support of the
  conclusion.

  > In propositional logic we are concerned solely with deductive validity or
  > invalidity.

  ### Formal definition

  > An argument is truth-functionally valid if and only if there is no
  > truth-assignment on which all the premises are true and the conclusion is
  > false.

  Linking this to derivation, we say:

  > In a system of derivation in propositional logic, an argument is valid if the
  > conclusion of the argument is derivable within the system of derivation from
  > the set consisting of the premises, and invalid otherwise.

  ### Demonstration

  The inference from the set ${P, P \rightarrow Q}$ to $Q$ is valid

  ### Truth-table

  | $P$ | $Q$ | $P \rightarrow Q$ | $P$ | $Q$ | Assessment |
  | --- | --- | ----------------- | --- | --- | ---------- |
  | T   | T   | T                 | T   | T   | Valid      |
  | T   | F   | F                 | T   | F   |            |
  | F   | T   | T                 | F   | T   |            |

  ## Entailment

  ### Informal definition

  Entailment as a concept is almost identical to validity. We say that a
  proposition is entailed by a set of propositions if it is not possible for every
  member of this set to be true and the proposition to be false.

  The difference with validity resides in the fact that the propositions are
  distinguished in terms of whether they are premises or a conclusion. So,
  technically, validity is a subclass of entailment. A case of entailment where we
  distinguish propositions in terms of whether they are premises or conclusions. A
  proposition may be entailed by a given set without that proposition being the
  _conclusion_ of the set and where the set is a syllogism.

  ### Formal definition

  > A finite set of sentences $\Gamma$ $\vdash$ $P$ if and only if there is no
  > truth-assignment in which every member of $\Gamma$ is true and $P$ is false.

  #### Informal demonstration

  ```
  It is raining.
  If it is raining then the pavement will be wet.
  The pavement is wet.
  ```

  #### Formal demonstration

  $$
    \{ P, P\rightarrow Q   \} \vdash Q
  $$

  #### Truth-table

  | $P$ | $Q$ | $P \rightarrow Q$ | $P$ | $Q$ | Assessment |
  | --- | --- | ----------------- | --- | --- | ---------- |
  | T   | T   | T                 | T   | T   | Valid      |
  | T   | F   | F                 | T   | F   |            |
  | F   | T   | T                 | F   | T   |            |

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

  ## Validity

  ### Informal definition

  In order to say whether an argument is 'good' or 'bad' we must have criteria of
  evaluation. in logic there are different criteria of evaluation:

  - **Deductive validity**

    An **argument is deductively valid if and only if it is not possible for the
    premises to be true and the conclusion false**. Linking to consistency: it is
    not possible to consistently assert all of the premises but deny the
    conclusion.

  - **Inductive strength**

    We do not say that inductive arguments have 'validity' because despite
    inductive premises being true, the conclusion may be falsifiable. Therefore we
    say inductive 'strength' rather than 'validity'. An argument is inductively
    strong if and only if the conclusion is probably true given the premises.

  #### Demonstration

  The Socrates demonstration above is an example of deductive validity.

  The following is an example of an argument that is inductively strong:

  ```
  99% of deaf persons have no musical talent.
  Beethoven was deaf.
  ___________________________________________
  Beethoven had no musical talent.
  ```

  The test for a strong inductive argument is not whether the conclusion is true,
  rather it concerns the evidence the premises provide in support of the
  conclusion.

  > In propositional logic we are concerned solely with deductive validity or
  > invalidity.

  ### Formal definition

  > An argument is truth-functionally valid if and only if there is no
  > truth-assignment on which all the premises are true and the conclusion is
  > false.

  Linking this to derivation, we say:

  > In a system of derivation in propositional logic, an argument is valid if the
  > conclusion of the argument is derivable within the system of derivation from
  > the set consisting of the premises, and invalid otherwise.

  ### Demonstration

  The inference from the set ${P, P \rightarrow Q}$ to $Q$ is valid

  ### Truth-table

  | $P$ | $Q$ | $P \rightarrow Q$ | $P$ | $Q$ | Assessment |
  | --- | --- | ----------------- | --- | --- | ---------- |
  | T   | T   | T                 | T   | T   | Valid      |
  | T   | F   | F                 | T   | F   |            |
  | F   | T   | T                 | F   | T   |            |

  ## Entailment

  ### Informal definition

  Entailment as a concept is almost identical to validity. We say that a
  proposition is entailed by a set of propositions if it is not possible for every
  member of this set to be true and the proposition to be false.

  The difference with validity resides in the fact that the propositions are
  distinguished in terms of whether they are premises or a conclusion. So,
  technically, validity is a subclass of entailment. A case of entailment where we
  distinguish propositions in terms of whether they are premises or conclusions. A
  proposition may be entailed by a given set without that proposition being the
  _conclusion_ of the set and where the set is a syllogism.

  ### Formal definition

  > A finite set of sentences $\Gamma$ $\vdash$ $P$ if and only if there is no
  > truth-assignment in which every member of $\Gamma$ is true and $P$ is false.

  #### Informal demonstration

  ```
  It is raining.
  If it is raining then the pavement will be wet.
  The pavement is wet.
  ```

  #### Formal demonstration

  $$
    \{ P, P\rightarrow Q   \} \vdash Q
  $$

  #### Truth-table

  | $P$ | $Q$ | $P \rightarrow Q$ | $P$ | $Q$ | Assessment |
  | --- | --- | ----------------- | --- | --- | ---------- |
  | T   | T   | T                 | T   | T   | Valid      |
  | T   | F   | F                 | T   | F   |            |
  | F   | T   | T                 | F   | T   |            |


path: zk/Variable_indirection.md
sortable_path: zkVariable_indirection.md
title: Variable indirection
lead: > Write proper notes on this
checksum: 6682743efcc1a97b2ba0760899b1690e9af193e72e3df5a957904ab8e01726a5
created: 2024-03-30 09:03:21.87146792+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["shell"]}
body:
  > Write proper notes on this

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

  my_array=(1 2 3)

  function push() {
      # $1 = stack name
      local stack_name="$1"
      if [ "${stack_name}" ]; then # stack exists
          if array_empty "${!stack_name}"; then
              echo "stack is empty"
          else
              echo "stack is not empty"
          fi
      else
          echo "Error: ${stack_name} does not exist."
      fi
  }

  push "my_array"

  ```

  In Bash scripting, ${!stack_name} is an example of indirect variable referencing
  or variable indirection. It's a way to use a variable whose name is stored in
  another variable.

  Let's break it down:

  `${stack_name}`: This syntax is used to reference the value of a variable. So if
  stack_name="my_array", then ${stack_name} would return "my_array".

  `${!stack_name}`: Adding the ! introduces indirection. Now, instead of getting
  the value of stack_name, you get the value of the variable whose name is stored
  in stack_name. So if stack_name="my_array" and my_array=(1 2 3), then
  ${!stack_name} would return (1 2 3).

  So, in the context of your script, `${!stack_name} allows you to pass the name
  of an array (as a string) to your push function, and then use that string to
  indirectly reference the actual array within the function.

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

  # Variable indirection

  > Write proper notes on this

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

  my_array=(1 2 3)

  function push() {
      # $1 = stack name
      local stack_name="$1"
      if [ "${stack_name}" ]; then # stack exists
          if array_empty "${!stack_name}"; then
              echo "stack is empty"
          else
              echo "stack is not empty"
          fi
      else
          echo "Error: ${stack_name} does not exist."
      fi
  }

  push "my_array"

  ```

  In Bash scripting, ${!stack_name} is an example of indirect variable referencing
  or variable indirection. It's a way to use a variable whose name is stored in
  another variable.

  Let's break it down:

  `${stack_name}`: This syntax is used to reference the value of a variable. So if
  stack_name="my_array", then ${stack_name} would return "my_array".

  `${!stack_name}`: Adding the ! introduces indirection. Now, instead of getting
  the value of stack_name, you get the value of the variable whose name is stored
  in stack_name. So if stack_name="my_array" and my_array=(1 2 3), then
  ${!stack_name} would return (1 2 3).

  So, in the context of your script, `${!stack_name} allows you to pass the name
  of an array (as a string) to your push function, and then use that string to
  indirectly reference the actual array within the function.


path: zk/Variables_and_data_types.md
sortable_path: zkVariables_and_data_types.md
title: Types
lead: ## Variables
checksum: 5d0734a826d4cd925e0eac4fc3da7eb6b0ad3cbedebb3da59ffefc3189d0347e
created: 2024-03-30 09:03:21.876696019+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["shell"]}
body:
  ## Variables

  We use the equality symbol to create a variable:

  ```bash
  stringVar="My first variable"
  floatVar="50.3"
  boolVar="true"
  ```

  As there is no typing in bash, the names of these variables are purely notional.

  To invoke a variable we use special brackets:

  ```bash
  echo ${stringVar} # My first variable
  echo ${floatVar} # 50.3
  echo ${boolVar} # true
  ```

  - Quotation marks at declaration are also not strictly necessary however they
    can help avoid bugs. Also serves as a reminder that every type is basically a
    string at the end of the day

  ## Common operations

  ### Check if a variable exists

  ```sh
  if [ -v variable_name ]; then
      echo "The variable exists."
  else
      echo "The variable does not exist."
  fi
  ```

  ### Check if a variable is set (defined)

  ```sh
  if [ -z "${variable_name+x}" ]; then
      echo "The variable does not exist."
  else
      echo "The variable exists."
  fi
  ```

  ## Parameter expansion

  Still not very clear on this.

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

  ## Types

  ## Variables

  We use the equality symbol to create a variable:

  ```bash
  stringVar="My first variable"
  floatVar="50.3"
  boolVar="true"
  ```

  As there is no typing in bash, the names of these variables are purely notional.

  To invoke a variable we use special brackets:

  ```bash
  echo ${stringVar} # My first variable
  echo ${floatVar} # 50.3
  echo ${boolVar} # true
  ```

  - Quotation marks at declaration are also not strictly necessary however they
    can help avoid bugs. Also serves as a reminder that every type is basically a
    string at the end of the day

  ## Common operations

  ### Check if a variable exists

  ```sh
  if [ -v variable_name ]; then
      echo "The variable exists."
  else
      echo "The variable does not exist."
  fi
  ```

  ### Check if a variable is set (defined)

  ```sh
  if [ -z "${variable_name+x}" ]; then
      echo "The variable does not exist."
  else
      echo "The variable exists."
  fi
  ```

  ## Parameter expansion

  Still not very clear on this.


path: zk/Viewing_remote_changes_without_merging.md
sortable_path: zkViewing_remote_changes_without_merging.md
title: Viewing remote changes without merging
lead:
  ```
  git fetch origin/master
  git log origin/main
  git show origin/master
  ```

body:
  ```
  git fetch origin/master
  git log origin/main
  git show origin/master
  ```

checksum: 1a5f587f96e84a733ebdf522999e5efdca8e55052d1f846baa71e7e768081e7c
created: 2024-03-30 09:03:21.882007116+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["git"]}
raw_content:
  ---
  tags: [git]
  ---

  # Viewing remote changes without merging

  ```
  git fetch origin/master
  git log origin/main
  git show origin/master
  ```


path: zk/Views_in_relational_databases.md
sortable_path: zkViews_in_relational_databases.md
title: Views in relational databases
checksum: aa86bf1638f1e9cf8b84fed560845377464ceeec2eddb10b22827ebf7752e5ec
created: 2024-03-30 09:03:21.885545399+00:00
modified: 2024-06-15 09:46:52.662345801+00:00
metadata: {"tags":[]}
lead:
  A view is a virtual table that is based on the result-set of a query. Although
  it doesn't actually exist in the same manner as an actual table it has exactly
  the same properties and functionality. Rather than adding the raw data manually,
  you derive it from logic applied to existing actual tables.

body:
  A view is a virtual table that is based on the result-set of a query. Although
  it doesn't actually exist in the same manner as an actual table it has exactly
  the same properties and functionality. Rather than adding the raw data manually,
  you derive it from logic applied to existing actual tables.

  This is a good way of dynamically generating data and is often employed as a way
  of creating mutations without changing the underlying data structure. In SQL,
  views can be created by combining `PRIMARY` and `FOREIGN` keys.

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

  # Views in relational databases

  A view is a virtual table that is based on the result-set of a query. Although
  it doesn't actually exist in the same manner as an actual table it has exactly
  the same properties and functionality. Rather than adding the raw data manually,
  you derive it from logic applied to existing actual tables.

  This is a good way of dynamically generating data and is often employed as a way
  of creating mutations without changing the underlying data structure. In SQL,
  views can be created by combining `PRIMARY` and `FOREIGN` keys.


path: zk/Virtual_memory_and_the_MMU.md
sortable_path: zkVirtual_memory_and_the_MMU.md
title: Virtual memory and the Memory Management Unit
lead: ## What is virtual memory?
checksum: 27e6de194c9421820975b5d98581aa3f82be4ba9645e72a767795944d4467be7
created: 2024-03-30 09:03:21.890885008+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["memory"]}
body:
  ## What is virtual memory?

  Virtual memory is implemented at the level of the operating system and is an
  abstraction on top of 'real', physical memory (i.e the charges stored within the
  actual DRAM component.).

  When virtual memory is used, the CPU handles physical memory allocation and
  presents this to the kernel as an idealised representation. This means that the
  kernel and, by extension, programs and processes do not need to think about
  accessing the real memory blocks. This reduces complexity because often memory
  will be allocated in places that are non-contiguous with similar running
  processes or be located in the cache or swap memory on the disk.

  ![](/img/virtual-memory-diagram.jpg)

  It would require considerable processing work for the kernel to be tracing these
  disparate memory sources at every instance. By working on an idealised
  (contiguous, unlimited) memory set the kernel can focus on task management and
  CPU sequencing as its primary task.

  The memory is idealised in that all locations are represented virtually as being
  contiguous (even when this is not physically the case). Secondly, quantities of
  available memory can be presented as much larger than is actually the case and
  which often exceed the physical memory limits of the device. This is achieved
  through paging, handled by the MMU.

  ## The Memory Management Unit (MMU)

  Without an MMU, when the CPU accesses RAM, the actual RAM locations never
  change. The memory address is always the same physical location within the RAM.
  The MMU is a chip that sits between the CPU and RAM recalculating the actual
  memory address from the virtual memory location requested by the kernel.

  ## Pages

  We use the term **pages** to denote blocks of virtual memory and to distinguish
  them from **addresses** as physical blocks. The MMU possesses a **page table**
  which is registry logging which pages correspond to which physical blocks.

  ## Shared pages

  Virtual memory allows the sharing of files and memory by multiple processes.
  Crucially the shared data doesn't have to be within the address of each process,
  instead there is a reference in the page table that each process has access to
  the shared data.

  ## Page faults

  There are two kinds of error that can occur with relation to paged memory:

  - minor page faults
    - The desired page is in main memory but the MMU doesn't currently know where
      it is
  - major page faults
    - The desired page is not in main memory at all. Therefore the kernel must
      fetch it from disk

  Minor page faults are very common and are to be expected; they resolve quickly.
  On the other hand too many major page faults can slow the system down both
  because of the time-costly process of fetching data from disk and because it
  demands more kernel resources to locate the missing page, which puts other
  processes on hold.

  ## Resources

  [Virtual memory](9_VirtualMemory.html#:~:text=Virtual%20memory%20also%20allows%20the,to%20more%20than%20one%20process)

raw_content:
  ---
  tags: [memory]
  ---

  # Virtual memory and the Memory Management Unit

  ## What is virtual memory?

  Virtual memory is implemented at the level of the operating system and is an
  abstraction on top of 'real', physical memory (i.e the charges stored within the
  actual DRAM component.).

  When virtual memory is used, the CPU handles physical memory allocation and
  presents this to the kernel as an idealised representation. This means that the
  kernel and, by extension, programs and processes do not need to think about
  accessing the real memory blocks. This reduces complexity because often memory
  will be allocated in places that are non-contiguous with similar running
  processes or be located in the cache or swap memory on the disk.

  ![](/img/virtual-memory-diagram.jpg)

  It would require considerable processing work for the kernel to be tracing these
  disparate memory sources at every instance. By working on an idealised
  (contiguous, unlimited) memory set the kernel can focus on task management and
  CPU sequencing as its primary task.

  The memory is idealised in that all locations are represented virtually as being
  contiguous (even when this is not physically the case). Secondly, quantities of
  available memory can be presented as much larger than is actually the case and
  which often exceed the physical memory limits of the device. This is achieved
  through paging, handled by the MMU.

  ## The Memory Management Unit (MMU)

  Without an MMU, when the CPU accesses RAM, the actual RAM locations never
  change. The memory address is always the same physical location within the RAM.
  The MMU is a chip that sits between the CPU and RAM recalculating the actual
  memory address from the virtual memory location requested by the kernel.

  ## Pages

  We use the term **pages** to denote blocks of virtual memory and to distinguish
  them from **addresses** as physical blocks. The MMU possesses a **page table**
  which is registry logging which pages correspond to which physical blocks.

  ## Shared pages

  Virtual memory allows the sharing of files and memory by multiple processes.
  Crucially the shared data doesn't have to be within the address of each process,
  instead there is a reference in the page table that each process has access to
  the shared data.

  ## Page faults

  There are two kinds of error that can occur with relation to paged memory:

  - minor page faults
    - The desired page is in main memory but the MMU doesn't currently know where
      it is
  - major page faults
    - The desired page is not in main memory at all. Therefore the kernel must
      fetch it from disk

  Minor page faults are very common and are to be expected; they resolve quickly.
  On the other hand too many major page faults can slow the system down both
  because of the time-costly process of fetching data from disk and because it
  demands more kernel resources to locate the missing page, which puts other
  processes on hold.

  ## Resources

  [Virtual memory](9_VirtualMemory.html#:~:text=Virtual%20memory%20also%20allows%20the,to%20more%20than%20one%20process)


path: zk/Voltage.md
sortable_path: zkVoltage.md
title: Voltage
lead: ## Difference of potential and the transfer of energy
checksum: aad60b4e37e2791658c5676cb4e0c66e309094763d8e8260822ed70057858666
created: 2024-03-30 09:03:21.904807011+00:00
modified: 2024-05-12 10:51:33.662225809+00:00
metadata: {"tags":["physics","electricity"]}
body:
  ## Difference of potential and the transfer of energy

  We noted in the discussion of [current](Current.md) that current flows when
  there is a difference of potential between two points with negatively charged
  atoms at one point and positively charged atoms at the other.

  _Difference of potential_ is the same thing as voltage. Voltage is essential to
  current because it is the force that enables the current to flow.

  Without voltage there can be no current because in their natural state, the
  electrons in an atom are **in random motion with no direction. To produce a
  current, energy must be imparted to the electrons so that they all flow in the
  same direction.**

  Voltage is the application of this energy. Any
  [form of energy](Voltage_sources.md) that dislodges electrons from atoms can be
  used to produce current. Thus:

  > Voltage is the work required per coulomb to move a charge from one point to
  > another.

  ### Voltage exists even without current

  Given that voltage is the force that generates current, it would be natural to
  think that voltage only exists when a voltage source (such as a
  [battery](Cells_and_batteries.md)) is connected to a circuit. This however is
  not the case. Even if a 9V battery isn't connected to anything it still has a
  difference of potential of 9-volts accross its terminals. Remember voltage is
  _potential energy_ not just the actualisation of that energy.

  ## Voltage rise and voltage drops

  In circuits there are actually two types of voltage:

  1. Voltage rise
  2. Voltage drop

  ### Voltage rise

  When we introduce potential energy into a ciruit in the form of voltage, this is
  a voltage rise. The current flows from the negative terminal of the voltage
  source and returns to the positive terminal of the voltage source.

  A 12V battery connected to a circuit gives it a voltage rise of 12 volts.

  ### Voltage drop

  Voltage drop is the corrolary to voltage rise. It is the loss of energy that the
  electrons of the circuit current experience as a result of encountering
  resistance.

  As they move through the circuit the electrons encounter a **load** which is
  what we call resistance to the flow of current. As they run into this, they give
  up their energy. The relinquishing of energy happens in the form of a conversion
  of electrical energy to heat (hence why computers heat up). The amount lost is
  equal to the amount of energy imparted by the voltage rise.

  > The voltage drop in a circuit equals the the voltage rise of the circuit
  > because energy cannot be created or destroyed, only changed to another form.
  > When a voltage rise is converted to a voltage drop we say that **the energy
  > has been _consumed_ by the circuit**.

  #### Examples

  - If a 12V source is connected to a 12V lamp, the source supplies a 12V voltage
    rise and the lamp produces a 12V voltage drop.
  - If two identical 6V lamps are connected in series to the same 12V source, each
    lamp produces a 6V drop for a total of 12 volts
  - If two different lamps are connected in series to to a 12V source: a 3V and a
    9V lamp, the 9V lamp produces a drop of 9V and the 3V lamp produces a drop of
    3V. The sum of the voltage drops equals the voltage rise of 12 volts.

  These examples demonstrate that the voltage rise: voltage drop ratio always
  evens out.

  ### Kirchoff's Voltage Law

  The relationship between voltage rise and voltage drop is expressed in
  Kirchoff's Voltage Law:

  > The sum of the voltages around any closed loop in a circuit must be zero.

  The application of the Law is illustrated in the following diagram:

  ![](/img/voltage-drop.png)

  The explanation for the voltage drop at the positions $V^{A}$ and $V^{D}$ are
  obvious enough: they are at the beginning and end of the loop so are equal to
  the maximal voltage rise and minimal voltage drop, respectively.

  We can work out the voltage of the remaining voltage points by inverting
  [Ohm's Law](Ohms_Law.md): $V = I \times R$:

  For the voltage at $V^{B}$:

  $$
    0.5\textsf{mA} \times \textsf{4}k\Omega = 2 \textsf{V}
  $$

  For the voltage at $V^{C}$:

  $$
    0.5\textsf{mA} \times \textsf{6}k\Omega = 3 \textsf{V}
  $$

  The total drops (2V, 3V, 5V) are equal to the initial volatage rise (10V)

  ## Distinguishing _voltage_ from _electric field_

  It can be confusing that two different symbols often seemed to be used
  interchangeably when talking about voltage: $V$ and $E$. However, while they
  broadly point to the same phenomenon there is a difference in emphasis.

  - $V$ stands for volts or voltage conceived purely in terms of the difference in
    potential between two points: the positive and negative terminals
  - $E$ stands for electric field. This is the field that surround each electric
    charge and exerts force on all other charges in the field, attracting or
    repelling them. So it is more the physical process that the volatage
    represents rather than the numerical representation of the potential between
    the terminals.

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

  # Voltage

  ## Difference of potential and the transfer of energy

  We noted in the discussion of [current](Current.md) that current flows when
  there is a difference of potential between two points with negatively charged
  atoms at one point and positively charged atoms at the other.

  _Difference of potential_ is the same thing as voltage. Voltage is essential to
  current because it is the force that enables the current to flow.

  Without voltage there can be no current because in their natural state, the
  electrons in an atom are **in random motion with no direction. To produce a
  current, energy must be imparted to the electrons so that they all flow in the
  same direction.**

  Voltage is the application of this energy. Any
  [form of energy](Voltage_sources.md) that dislodges electrons from atoms can be
  used to produce current. Thus:

  > Voltage is the work required per coulomb to move a charge from one point to
  > another.

  ### Voltage exists even without current

  Given that voltage is the force that generates current, it would be natural to
  think that voltage only exists when a voltage source (such as a
  [battery](Cells_and_batteries.md)) is connected to a circuit. This however is
  not the case. Even if a 9V battery isn't connected to anything it still has a
  difference of potential of 9-volts accross its terminals. Remember voltage is
  _potential energy_ not just the actualisation of that energy.

  ## Voltage rise and voltage drops

  In circuits there are actually two types of voltage:

  1. Voltage rise
  2. Voltage drop

  ### Voltage rise

  When we introduce potential energy into a ciruit in the form of voltage, this is
  a voltage rise. The current flows from the negative terminal of the voltage
  source and returns to the positive terminal of the voltage source.

  A 12V battery connected to a circuit gives it a voltage rise of 12 volts.

  ### Voltage drop

  Voltage drop is the corrolary to voltage rise. It is the loss of energy that the
  electrons of the circuit current experience as a result of encountering
  resistance.

  As they move through the circuit the electrons encounter a **load** which is
  what we call resistance to the flow of current. As they run into this, they give
  up their energy. The relinquishing of energy happens in the form of a conversion
  of electrical energy to heat (hence why computers heat up). The amount lost is
  equal to the amount of energy imparted by the voltage rise.

  > The voltage drop in a circuit equals the the voltage rise of the circuit
  > because energy cannot be created or destroyed, only changed to another form.
  > When a voltage rise is converted to a voltage drop we say that **the energy
  > has been _consumed_ by the circuit**.

  #### Examples

  - If a 12V source is connected to a 12V lamp, the source supplies a 12V voltage
    rise and the lamp produces a 12V voltage drop.
  - If two identical 6V lamps are connected in series to the same 12V source, each
    lamp produces a 6V drop for a total of 12 volts
  - If two different lamps are connected in series to to a 12V source: a 3V and a
    9V lamp, the 9V lamp produces a drop of 9V and the 3V lamp produces a drop of
    3V. The sum of the voltage drops equals the voltage rise of 12 volts.

  These examples demonstrate that the voltage rise: voltage drop ratio always
  evens out.

  ### Kirchoff's Voltage Law

  The relationship between voltage rise and voltage drop is expressed in
  Kirchoff's Voltage Law:

  > The sum of the voltages around any closed loop in a circuit must be zero.

  The application of the Law is illustrated in the following diagram:

  ![](/img/voltage-drop.png)

  The explanation for the voltage drop at the positions $V^{A}$ and $V^{D}$ are
  obvious enough: they are at the beginning and end of the loop so are equal to
  the maximal voltage rise and minimal voltage drop, respectively.

  We can work out the voltage of the remaining voltage points by inverting
  [Ohm's Law](Ohms_Law.md): $V = I \times R$:

  For the voltage at $V^{B}$:

  $$
    0.5\textsf{mA} \times \textsf{4}k\Omega = 2 \textsf{V}
  $$

  For the voltage at $V^{C}$:

  $$
    0.5\textsf{mA} \times \textsf{6}k\Omega = 3 \textsf{V}
  $$

  The total drops (2V, 3V, 5V) are equal to the initial volatage rise (10V)

  ## Distinguishing _voltage_ from _electric field_

  It can be confusing that two different symbols often seemed to be used
  interchangeably when talking about voltage: $V$ and $E$. However, while they
  broadly point to the same phenomenon there is a difference in emphasis.

  - $V$ stands for volts or voltage conceived purely in terms of the difference in
    potential between two points: the positive and negative terminals
  - $E$ stands for electric field. This is the field that surround each electric
    charge and exerts force on all other charges in the field, attracting or
    repelling them. So it is more the physical process that the volatage
    represents rather than the numerical representation of the potential between
    the terminals.


path: zk/Voltage_sources.md
sortable_path: zkVoltage_sources.md
title: Voltage sources
lead: The following phenomena are the main sources that we use to generate voltage:
checksum: 88d7509073e3453a58af7a34f89f36e3de1f0212048f3255ad57489a3e2bf0a2
created: 2024-03-30 09:03:21.931596527+00:00
modified: 2024-05-16 06:09:38.490863436+00:00
metadata: {"tags":["physics","electricity"]}
body:
  The following phenomena are the main sources that we use to generate voltage:

  - friction
  - magnetism
  - chemicals
  - light
  - heat
  - pressure

  > In each case, energy in one state is transferred into energy as current. No
  > energy is lost or gained, it simply changes its form.

  ### Magentism (electrical generators)

  Magnetism is used as the voltage source in electrical generators and is by far
  the most common method of producing powerful and large currents at scale.

  If a conductive wire is passed through a magnetic field voltage will be produced
  so long as there is motion between the magnetic field and the conductor. A
  **generator** is a device that generates current in this manner. Generators
  themselves need to be powered. They can be powered by steam from a nuclear power
  plant, water, wind, coal or other fossil fuels.

  #### AC/DC

  Depending on how it is wired, a generator can produce **directed current** (DC)
  or **alternating current** (AC):

  - **Directed current**

    - The electrons flow in only one direction

  - **Alternating current**
    - The electrons flow in one direction and then the other

  ### Chemicals (cells and batteries)

  The chemical creation of current is the physics behind
  [batteries](Cells_and_batteries.md). Chemical current production produces
  currents on a smaller and less industrial scale than generators.

  A chemical cell consists in two dissimilar metals such as copper and zinc. We
  call these the **electrodes**. They are immersed in a salt, acid or alkaline
  solution. We call these the **electrolytes**. The electrolyte pulls the free
  electrons from the copper electrode which leaves it imbalanced with a positive
  charge. The zinc electrode attracts the free electrons from the electrolyte
  giving it a negative charge, thus a difference of potential is achieved.

  ### Light (photovoltaic cells)

  Solar energy can be converted to electrical energy through solar panels which
  are large collections of **photovoltaic cells**.

  When the surfaces of these cells are exposed to light, it dislodges electrons
  from their orbits around the surface atoms of the cell material. For each cell
  this only produces a very small amount of energy, therefore large quantities
  must be used.

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

  # Voltage sources

  The following phenomena are the main sources that we use to generate voltage:

  - friction
  - magnetism
  - chemicals
  - light
  - heat
  - pressure

  > In each case, energy in one state is transferred into energy as current. No
  > energy is lost or gained, it simply changes its form.

  ### Magentism (electrical generators)

  Magnetism is used as the voltage source in electrical generators and is by far
  the most common method of producing powerful and large currents at scale.

  If a conductive wire is passed through a magnetic field voltage will be produced
  so long as there is motion between the magnetic field and the conductor. A
  **generator** is a device that generates current in this manner. Generators
  themselves need to be powered. They can be powered by steam from a nuclear power
  plant, water, wind, coal or other fossil fuels.

  #### AC/DC

  Depending on how it is wired, a generator can produce **directed current** (DC)
  or **alternating current** (AC):

  - **Directed current**

    - The electrons flow in only one direction

  - **Alternating current**
    - The electrons flow in one direction and then the other

  ### Chemicals (cells and batteries)

  The chemical creation of current is the physics behind
  [batteries](Cells_and_batteries.md). Chemical current production produces
  currents on a smaller and less industrial scale than generators.

  A chemical cell consists in two dissimilar metals such as copper and zinc. We
  call these the **electrodes**. They are immersed in a salt, acid or alkaline
  solution. We call these the **electrolytes**. The electrolyte pulls the free
  electrons from the copper electrode which leaves it imbalanced with a positive
  charge. The zinc electrode attracts the free electrons from the electrolyte
  giving it a negative charge, thus a difference of potential is achieved.

  ### Light (photovoltaic cells)

  Solar energy can be converted to electrical energy through solar panels which
  are large collections of **photovoltaic cells**.

  When the surfaces of these cells are exposed to light, it dislodges electrons
  from their orbits around the surface atoms of the cell material. For each cell
  this only produces a very small amount of energy, therefore large quantities
  must be used.


path: zk/What_are_disks.md
sortable_path: zkWhat_are_disks.md
title: What are disks?
lead:
  A disk is a mass storage [block_device](Devices.md) which we
  can write to and read from.

checksum: c22f8c91037866cc18d6f7236e40d00f66c3cf1ecf398f8f67b6bdbdae24f0ac
created: 2024-03-30 09:03:21.942804056+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["disks"]}
body:
  A disk is a mass storage [block_device](Devices.md) which we
  can write to and read from.

  ## SCSI

  - Small Computer System Interface, responsible for handling disk access on most
    Linux systems.
  - It is a protocol that allows communicaton between printers, scanners and other
    peripherals in addition to harddisks.

  ## Disk schematic

  The following diagram represents the basic anatomy of a disk device.

  ![](/img/harddisk.png)

  - A disk is divided up into [partitions](Partitions.md)
    which are subsections of the overall disk. The kernel presents each partition
    as a [block device](Devices.md) as it would with an entire
    disk.
  - The disk dedicates a small part of its contents to a **partition table**: this
    defines the different partitions that comprise the total disk space.
  - The **filesystem** is a database of files and directories: this comprises the
    bulk of the partition and is what you interact with in
    [user space](User_Space.md) when reading and writing data.

raw_content:
  ---
  tags:
    - disks
  ---

  # What are disks?

  A disk is a mass storage [block_device](Devices.md) which we
  can write to and read from.

  ## SCSI

  - Small Computer System Interface, responsible for handling disk access on most
    Linux systems.
  - It is a protocol that allows communicaton between printers, scanners and other
    peripherals in addition to harddisks.

  ## Disk schematic

  The following diagram represents the basic anatomy of a disk device.

  ![](/img/harddisk.png)

  - A disk is divided up into [partitions](Partitions.md)
    which are subsections of the overall disk. The kernel presents each partition
    as a [block device](Devices.md) as it would with an entire
    disk.
  - The disk dedicates a small part of its contents to a **partition table**: this
    defines the different partitions that comprise the total disk space.
  - The **filesystem** is a database of files and directories: this comprises the
    bulk of the partition and is what you interact with in
    [user space](User_Space.md) when reading and writing data.


path: zk/Whole_numbers.md
sortable_path: zkWhole_numbers.md
title: The set of whole numbers
lead: We recall the set of whole numbers:
checksum: 55a213e7c8d3fd57417fb5291e2452abb12fdc19266c9a6c00130544e5b85789
created: 2024-03-30 09:03:21.964186692+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["prealgebra"]}
body:
  We recall the set of whole numbers:

  $$ \mathbb{W} = {0, 1, 2, 3, ...} $$

  # The properties of $\mathbb{W}$

  > In mathematics, a **property** is any characteristic that applies to a given
  > set.

  ## The commutative property

  ### Addition

  When **adding** whole numbers, the placement of the addends does not affect the
  sum.

  Let **a**, **b** represent whole numbers, then:

  $$ a + b = b + a $$

  ### Multiplication

  When **multiplying** whole numbers the placement of the
  [multiplicands](Symbols-and-formal-conventions-80aeaf1872f94a0d97a2e8d07e3855bd)
  does not affect the
  [product](Symbols-and-formal-conventions-80aeaf1872f94a0d97a2e8d07e3855bd).

  Let **a, b** represent whole numbers, then:

  $$ a \cdot b = b \cdot a $$

  ### Subtraction

  **Subtraction** is not commutative, viz:

  $$ a - b \neq b - a $$

  ### Division

  Division is not commutative, viz:

  $$ a \div b \neq b \div a $$

  ## The associative property

  ### Addition

  When grouping symbols (parentheses, brackets, braces) are used with addition,
  the particular placement of the grouping symbols relative to each of the addends
  does not change the sum.

  Let **a**, **b, c** represent whole numbers, then:

  $$ (a + b) + c = a + (b + c) $$

  ### Multiplication

  Let **a, b, c** represent whole numbers, then:

  $$ a \cdot (b \cdot c) = (a \cdot b) \cdot c $$

  ### Subtraction

  Subtraction is not associative, viz:

  $$ (a - b) - c \neq a - (b - c) $$

  ### Division

  Division is not associative

  $$ (a \div b) \div c \neq a \div (b \div c) $$

  ## The property of additive identity

  If **a** is any whole number, then:

  $$ a + 0 = a $$

  We therefore call zero the additive identity: whenever we add zero to a whole
  number, the sum is equal to the whole number itself.

  ## The property of multiplicative identity

  If **a** is any whole number, then:

  $$ (a \cdot 1 = a) = (1 \cdot a = a) $$

  ## Multiplication by zero

  If **a** is any whole number, then:

  $$ (a \cdot 0 = 0) = (0 \cdot a = 0) $$

  ## Division by zero

  Division by zero is **undefined** but zero divided is zero.

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

  # The set of whole numbers

  We recall the set of whole numbers:

  $$ \mathbb{W} = {0, 1, 2, 3, ...} $$

  # The properties of $\mathbb{W}$

  > In mathematics, a **property** is any characteristic that applies to a given
  > set.

  ## The commutative property

  ### Addition

  When **adding** whole numbers, the placement of the addends does not affect the
  sum.

  Let **a**, **b** represent whole numbers, then:

  $$ a + b = b + a $$

  ### Multiplication

  When **multiplying** whole numbers the placement of the
  [multiplicands](Symbols-and-formal-conventions-80aeaf1872f94a0d97a2e8d07e3855bd)
  does not affect the
  [product](Symbols-and-formal-conventions-80aeaf1872f94a0d97a2e8d07e3855bd).

  Let **a, b** represent whole numbers, then:

  $$ a \cdot b = b \cdot a $$

  ### Subtraction

  **Subtraction** is not commutative, viz:

  $$ a - b \neq b - a $$

  ### Division

  Division is not commutative, viz:

  $$ a \div b \neq b \div a $$

  ## The associative property

  ### Addition

  When grouping symbols (parentheses, brackets, braces) are used with addition,
  the particular placement of the grouping symbols relative to each of the addends
  does not change the sum.

  Let **a**, **b, c** represent whole numbers, then:

  $$ (a + b) + c = a + (b + c) $$

  ### Multiplication

  Let **a, b, c** represent whole numbers, then:

  $$ a \cdot (b \cdot c) = (a \cdot b) \cdot c $$

  ### Subtraction

  Subtraction is not associative, viz:

  $$ (a - b) - c \neq a - (b - c) $$

  ### Division

  Division is not associative

  $$ (a \div b) \div c \neq a \div (b \div c) $$

  ## The property of additive identity

  If **a** is any whole number, then:

  $$ a + 0 = a $$

  We therefore call zero the additive identity: whenever we add zero to a whole
  number, the sum is equal to the whole number itself.

  ## The property of multiplicative identity

  If **a** is any whole number, then:

  $$ (a \cdot 1 = a) = (1 \cdot a = a) $$

  ## Multiplication by zero

  If **a** is any whole number, then:

  $$ (a \cdot 0 = 0) = (0 \cdot a = 0) $$

  ## Division by zero

  Division by zero is **undefined** but zero divided is zero.


path: zk/Why_computers_use_binary.md
sortable_path: zkWhy_computers_use_binary.md
title: Why computers use binary
lead: ## Now we know how binary works, how does it relate to computing?
checksum: f4ecd720bd3d0573e2236d1728dc1afa38b91079c9a8747f0318511b4cac5036
created: 2024-03-30 09:03:22.004239333+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["binary"]}
body:
  ## Now we know how binary works, how does it relate to computing?

  The reason is straight forward: it is the simplest way on the level of pure
  engineering of representing numerical and logical values; both of which are the
  basic foundations of programming. An electronic circuit or transistor only needs
  to represent two states: on (1) and off (0) which corresponds to the switch on
  an electrical circuit. A single circuit representing the binary values of 1 and
  0:

  ![multi_on_off 1.gif](multi_on_off.gif)

  It would be much more complicated to have to represent ten different states
  under the decimal number system, although denary computers do exist.

  If we want more digits, we just need to add in more circuits, and we can
  represent as large a binary number as we need. We just need one switch for every
  digit we want to represent. The switches used in modern computers are so cheap
  and so small that billions can be fitted on a single circuit board.

  ![multiple_circuits.gif](multiple_circuits.gif)

  When we use the term 'switch' we actually mean the transistor components of a
  circuit. We don't need to know the physical details at this level but we can say
  that a transistor turns a current on and off. They can also be used to amplify
  the current.

  On the level of electrical engineering and in the subsequent examples using a
  light bulb and a simple circuit, the switch being OFF corresponds to the light
  being ON, this is because the switch breaks the current. So when it is unbroken,
  the current passes to the bulb. This also means that 1 corresponds to the switch
  being OFF and 0 corresponds to the switch being ON which can be confusing at
  first.

  ## From circuits to programs

  The following breaks down how we get from the binary number system to electrical
  circuits to computer programs:

  1. ”Data”= a piece or pieces of **information**

  1. In computing all data is represented in **binary form:**

     2.1. ”Binary” means that the value of a piece of data is exclusively one
     thing or another

     2.2. The values in binary code are exclusively 1 and 0 (either a piece of
     data is a 1 or it is a 0, it can never be both). Binary can also be expressed
     in logical terms where 1 is equal to `true` and 0 is equal to `false`

  1. The smallest piece of data is a **bit**

  1. We apply the binary number system to bits

  1. Thus by 1-4, **a bit is either 1 or 0**

  1. Binary form bears an isomorphic relation to switches on electrical circuits
     (the hardware of computers). Thus binary code can be _mapped onto circuits_.

     - Circuits are controlled by switches
     - A switch is a binary function: it is either on or off ”On/off”
       corresponding to 1/0.
     - In this way, switches control the flow of electric current

  1. Thus by the above, hardware (which is a physical structure made up of
     electric circuits) is **readily programmable in binary terms**

  1. Restatement of 6: in order for hardware to be programmed the program must
     provide the hardware with something that it can actually perform. The program
     must be formulated in binary code.

raw_content:
  ---
  tags: [binary]
  ---

  # Why computers use binary

  ## Now we know how binary works, how does it relate to computing?

  The reason is straight forward: it is the simplest way on the level of pure
  engineering of representing numerical and logical values; both of which are the
  basic foundations of programming. An electronic circuit or transistor only needs
  to represent two states: on (1) and off (0) which corresponds to the switch on
  an electrical circuit. A single circuit representing the binary values of 1 and
  0:

  ![multi_on_off 1.gif](multi_on_off.gif)

  It would be much more complicated to have to represent ten different states
  under the decimal number system, although denary computers do exist.

  If we want more digits, we just need to add in more circuits, and we can
  represent as large a binary number as we need. We just need one switch for every
  digit we want to represent. The switches used in modern computers are so cheap
  and so small that billions can be fitted on a single circuit board.

  ![multiple_circuits.gif](multiple_circuits.gif)

  When we use the term 'switch' we actually mean the transistor components of a
  circuit. We don't need to know the physical details at this level but we can say
  that a transistor turns a current on and off. They can also be used to amplify
  the current.

  On the level of electrical engineering and in the subsequent examples using a
  light bulb and a simple circuit, the switch being OFF corresponds to the light
  being ON, this is because the switch breaks the current. So when it is unbroken,
  the current passes to the bulb. This also means that 1 corresponds to the switch
  being OFF and 0 corresponds to the switch being ON which can be confusing at
  first.

  ## From circuits to programs

  The following breaks down how we get from the binary number system to electrical
  circuits to computer programs:

  1. ”Data”= a piece or pieces of **information**

  1. In computing all data is represented in **binary form:**

     2.1. ”Binary” means that the value of a piece of data is exclusively one
     thing or another

     2.2. The values in binary code are exclusively 1 and 0 (either a piece of
     data is a 1 or it is a 0, it can never be both). Binary can also be expressed
     in logical terms where 1 is equal to `true` and 0 is equal to `false`

  1. The smallest piece of data is a **bit**

  1. We apply the binary number system to bits

  1. Thus by 1-4, **a bit is either 1 or 0**

  1. Binary form bears an isomorphic relation to switches on electrical circuits
     (the hardware of computers). Thus binary code can be _mapped onto circuits_.

     - Circuits are controlled by switches
     - A switch is a binary function: it is either on or off ”On/off”
       corresponding to 1/0.
     - In this way, switches control the flow of electric current

  1. Thus by the above, hardware (which is a physical structure made up of
     electric circuits) is **readily programmable in binary terms**

  1. Restatement of 6: in order for hardware to be programmed the program must
     provide the hardware with something that it can actually perform. The program
     must be formulated in binary code.


path: zk/Wildcards_in_SQL.md
sortable_path: zkWildcards_in_SQL.md
title: Wildcards in SQL
lead:
  SQL does not use Regex. Instead it has a simpler glob-like syntax for carrying
  out string matching.

checksum: b761196c7c6f74fc89c73b5473e5d14148d6db91415904bc5456d190063aa12f
created: 2024-03-30 09:03:22.009604092+00:00
modified: 2024-06-15 09:48:37.544444617+00:00
metadata: {"tags":["SQL","databases"]}
body:
  SQL does not use Regex. Instead it has a simpler glob-like syntax for carrying
  out string matching.

  In order to signal that you wish to compare by a wildcard and not a value, you
  have to use the `LIKE` keyword. The actual wildcard operator is `%` .

  In an SQL statement, the `%` wild card will match any number of occurrences of
  any character. Any characters can appear before or after ‘MacBook’ and the
  record will still be returned:

  ```sql
  SELECT name, cores, release_date
  FROM model
  WHERE name LIKE '%MacBook%';
  ```

  This wildcard only filters characters that come after `Raspberry` :

  ```sql
  SELECT name, cores, release_date
  FROM model
  WHERE name LIKE 'Raspberry%';
  ```

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

  # Wildcards in SQL

  SQL does not use Regex. Instead it has a simpler glob-like syntax for carrying
  out string matching.

  In order to signal that you wish to compare by a wildcard and not a value, you
  have to use the `LIKE` keyword. The actual wildcard operator is `%` .

  In an SQL statement, the `%` wild card will match any number of occurrences of
  any character. Any characters can appear before or after ‘MacBook’ and the
  record will still be returned:

  ```sql
  SELECT name, cores, release_date
  FROM model
  WHERE name LIKE '%MacBook%';
  ```

  This wildcard only filters characters that come after `Raspberry` :

  ```sql
  SELECT name, cores, release_date
  FROM model
  WHERE name LIKE 'Raspberry%';
  ```


path: zk/Working_with_numbers_in_Bash.md
sortable_path: zkWorking_with_numbers_in_Bash.md
title: Working with numbers in Bash
lead: We distinguish:
checksum: d793b757573d5c77ece45d9b81efbc9a285dfe699f8221faf4f0ab9f095c96c5
created: 2024-03-30 09:03:22.015441917+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["shell"]}
body:
  We distinguish:

  - **arithmetic expansion** `$(( ))`
    - returns the result of literal numbers that can then be stored in a variable
  - **artihmetic evaluation** `(( ))`
    - perform calculations on _existing_ variables

  An example of expansion:

  ```bash
  a=3
  ((a += 3))
  echo $a
  # 6
  ((a++))
  # 7
  ```

  > Note: we do not use a dollar-sign when referring to variables within
  > arithmetic evaluation, there is no need. If we do, we get an error. This is
  > because we are using an
  > [expansion](Expansions_and_substitutions.md),
  > therefore the variables are already being interpreted as variables.

  ## Declaring variables as integers

  It is good practice to safeguard against Bash treating numbers as strings to
  declare them as integers in addition to using arithmetic evaluation, e.g:

  ```bash
  declare -i b=3
  ```

  Whilst this isn't a strict type, it means we can do this:

  ```bash
  b=$b+4
  echo b
  # 7
  ```

  Without getting `3+4` in return

  ## No decimals in bash

  Bash does not support decimal calculations natively. This is what you'd get for
  example:

  ```bash
  echo $(( 1/3 ))
  # 0 (not 0.33)
  ```

  So work with decimals you should use `awk` or `bc` ("basic calculations").

  Example of using `bc`:

  ```bash
  declare -i c=1
  declare -i d=3
  e=$(echo "scale=3; $c/$d" | bc)
  # 0.333
  ```

  ## Random numbers

  Generate a pseudo-random number between 1 and 20:

  ```bash
  echo $(( 1 + RANDOM % 10))
  # 18
  ```

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

  # Working with numbers in Bash

  We distinguish:

  - **arithmetic expansion** `$(( ))`
    - returns the result of literal numbers that can then be stored in a variable
  - **artihmetic evaluation** `(( ))`
    - perform calculations on _existing_ variables

  An example of expansion:

  ```bash
  a=3
  ((a += 3))
  echo $a
  # 6
  ((a++))
  # 7
  ```

  > Note: we do not use a dollar-sign when referring to variables within
  > arithmetic evaluation, there is no need. If we do, we get an error. This is
  > because we are using an
  > [expansion](Expansions_and_substitutions.md),
  > therefore the variables are already being interpreted as variables.

  ## Declaring variables as integers

  It is good practice to safeguard against Bash treating numbers as strings to
  declare them as integers in addition to using arithmetic evaluation, e.g:

  ```bash
  declare -i b=3
  ```

  Whilst this isn't a strict type, it means we can do this:

  ```bash
  b=$b+4
  echo b
  # 7
  ```

  Without getting `3+4` in return

  ## No decimals in bash

  Bash does not support decimal calculations natively. This is what you'd get for
  example:

  ```bash
  echo $(( 1/3 ))
  # 0 (not 0.33)
  ```

  So work with decimals you should use `awk` or `bc` ("basic calculations").

  Example of using `bc`:

  ```bash
  declare -i c=1
  declare -i d=3
  e=$(echo "scale=3; $c/$d" | bc)
  # 0.333
  ```

  ## Random numbers

  Generate a pseudo-random number between 1 and 20:

  ```bash
  echo $(( 1 + RANDOM % 10))
  # 18
  ```


path: zk/Zero_property_of_multiplication.md
sortable_path: zkZero_property_of_multiplication.md
lead:
  ---
  tags:
  - prealgebra
  - theorems
  ---

checksum: ab65cf0fbf5ec242a321c119815347b74bba3c9a520fa4fb59b5be4e147f563c
created: 2024-03-30 09:03:22.025204158+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["prealgebra","theorems"]}
body:
  ---
  tags:
    - prealgebra
    - theorems
  ---

  **Let $a$ represent any member of $\mathbb{W}$ or $\mathbb{Z}$ then:**

  $$ a \cdot 0 = 0 $$

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

  **Let $a$ represent any member of $\mathbb{W}$ or $\mathbb{Z}$ then:**

  $$ a \cdot 0 = 0 $$


path: zk/Zip_function_in_Python.md
sortable_path: zkZip_function_in_Python.md
title: zip
checksum: 0e7191e6c8fbb369de0479b6d8f0ebcd60161e6cf2f4f978eb878aa8436e54a1
created: 2024-03-30 09:03:22.028631364+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
metadata: {"tags":["python","data-structures"]}
lead:
  The `zip` function returns a zip object when you pass in two iterator data types
  (typically two lists). This is an iterator of tuples where the first item in
  each passed iterator is paired together, and then the second item in each passed
  iterator are paired together, and so on.

body:
  The `zip` function returns a zip object when you pass in two iterator data types
  (typically two lists). This is an iterator of tuples where the first item in
  each passed iterator is paired together, and then the second item in each passed
  iterator are paired together, and so on.

  ## Example

  ```py
  a = ("John", "Charles", "Mike")
  b = ("Jenny", "Christy", "Monica", "Vicky")

  x = zip(a,b)

  print(x)
  # <zip object at 0x1514af34c340>

  # To display the resulting tuple in a readable format:

  print(tuple(x))

  (('John', 'Jenny'), ('Charles', 'Christy'), ('Mike', 'Monica'))
  ```

  As indicated above, if the input iterators are of equal lengths, Python will
  ignore the value in iterator B that doesn't have an equivalent index in iterator
  A.

  ## Real-life example

  ```py
  prompts = [
      "Enter title",
      "Enter author",
      "Enter publication date",
      "Enter year you read the book"
  ]

  keys = ["Title", "Author", "Publication_date", "Date_read"]

  book = {}

  for key, prompt in zip(keys,prompts):
      book[key] = input(prompt + ": ")

  ```

  Here I take the `prompts` and `keys` lists, collate them via `zip` and then loop
  through them to populate the `book` dictionary, which contains properties
  sourced from the `keys` list and values from the user's answers to the
  `prompts`.

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

  # `zip`

  The `zip` function returns a zip object when you pass in two iterator data types
  (typically two lists). This is an iterator of tuples where the first item in
  each passed iterator is paired together, and then the second item in each passed
  iterator are paired together, and so on.

  ## Example

  ```py
  a = ("John", "Charles", "Mike")
  b = ("Jenny", "Christy", "Monica", "Vicky")

  x = zip(a,b)

  print(x)
  # <zip object at 0x1514af34c340>

  # To display the resulting tuple in a readable format:

  print(tuple(x))

  (('John', 'Jenny'), ('Charles', 'Christy'), ('Mike', 'Monica'))
  ```

  As indicated above, if the input iterators are of equal lengths, Python will
  ignore the value in iterator B that doesn't have an equivalent index in iterator
  A.

  ## Real-life example

  ```py
  prompts = [
      "Enter title",
      "Enter author",
      "Enter publication date",
      "Enter year you read the book"
  ]

  keys = ["Title", "Author", "Publication_date", "Date_read"]

  book = {}

  for key, prompt in zip(keys,prompts):
      book[key] = input(prompt + ": ")

  ```

  Here I take the `prompts` and `keys` lists, collate them via `zip` and then loop
  through them to populate the `book` dictionary, which contains properties
  sourced from the `keys` list and values from the user's answers to the
  `prompts`.


path: zk/format_specifiers_in_c.md
sortable_path: zkformat_specifiers_in_c.md
title: Format specifiers in C
lead: # Format specifiers in C
checksum: 0811c81b188bb421c8b324c826bf8e8954a704211ee3da9ef179cbbc967177ba
created: 2024-03-30 09:03:22.03361984+00:00
modified: 2024-03-27 10:45:52.506858751+00:00
body:
  # Format specifiers in C

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

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

raw_content:
  ---
  id: 5faz2y2e
  title: Format specifiers in C
  tags: []
  created: Thursday, February 29, 2024 | 17:41
  ---

  # Format specifiers in C

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

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

metadata:
  {"created":"Thursday, February 29, 2024 | 17:41","id":"5faz2y2e","tags":[],"title":"Format specifiers in C"}


path: zk/fs.md
sortable_path: zkfs.md
title: fs module
checksum: a3c17d65c0c5d9bab3633e9193d8efba72e77fbea56266c54e89eac529bb80e9
created: 2024-03-30 09:03:22.049680323+00:00
modified: 2024-06-15 09:50:03.09612186+00:00
metadata: {"tags":["node-js"]}
lead:
  File System is an essential built-in module of Node that contains utility
  methods for working with files and directories.

body:
  File System is an essential built-in module of Node that contains utility
  methods for working with files and directories.

  Every method associated with `fs` has a _blocking_ and _asynchronous_
  implementation. The former obviously blocks the
  [event queue](Event_loop.md), the
  latter does not.

  The synchronous methods are useful to have in some contexts but in general and
  with real-world applications, you should be using the async implementation so as
  to accord with the single-threaded event-driven architecture of Node.

  ## Common I/0 methods

  ### Read directory

  Return a string array of all files in the current directory.

  ```js
  fs.readdir("./", function (err, files) {
    if (err) {
      console.error(err);
    } else {
      console.log(files);
    }
  });
  ```

  ### Read from file

  ```js
  fs.readFile("./lorem.md", "UTF-8", function (err, fileContents) {
    console.log(fileContents);
  });
  ```

  ### Write to file

  ```js
  let md = `A new file`;

  fs.writeFile("testFile.md", md.trim(), function () {
    console.log("File was created");
  });
  ```

  ### Appending to file

  ```js
  fs.appendFile("testFile.md", "new content");
  ```

  ### Create directory

  ```js
  if (!fs.existsSync("directory_name")) {
    fs.mkdir("directory_name", function (err) {
      if (err) {
        console.err(err);
      }
    });
  } else {
    console.warn("Directory already exists");
  }
  ```

  ### Rename and remove files

  ```js
  fs.rename("./filename.js", "./newname.js", function () {
    console.log("file renamed");
  });
  ```

  ```js
  // Remove file
  fs.unlink("./file-to-delete", function (err) {
    if (err) {
      console.error(err);
    }
  });
  ```

  ### Rename and remove directories

  ```js
  // Best to use synchronous method here
  fs.rmSync("/dir", { recursive: true, force: true });
  ```

  ## Streams

  See [Handling streams with fs](Streams.md)

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

  # fs module

  File System is an essential built-in module of Node that contains utility
  methods for working with files and directories.

  Every method associated with `fs` has a _blocking_ and _asynchronous_
  implementation. The former obviously blocks the
  [event queue](Event_loop.md), the
  latter does not.

  The synchronous methods are useful to have in some contexts but in general and
  with real-world applications, you should be using the async implementation so as
  to accord with the single-threaded event-driven architecture of Node.

  ## Common I/0 methods

  ### Read directory

  Return a string array of all files in the current directory.

  ```js
  fs.readdir("./", function (err, files) {
    if (err) {
      console.error(err);
    } else {
      console.log(files);
    }
  });
  ```

  ### Read from file

  ```js
  fs.readFile("./lorem.md", "UTF-8", function (err, fileContents) {
    console.log(fileContents);
  });
  ```

  ### Write to file

  ```js
  let md = `A new file`;

  fs.writeFile("testFile.md", md.trim(), function () {
    console.log("File was created");
  });
  ```

  ### Appending to file

  ```js
  fs.appendFile("testFile.md", "new content");
  ```

  ### Create directory

  ```js
  if (!fs.existsSync("directory_name")) {
    fs.mkdir("directory_name", function (err) {
      if (err) {
        console.err(err);
      }
    });
  } else {
    console.warn("Directory already exists");
  }
  ```

  ### Rename and remove files

  ```js
  fs.rename("./filename.js", "./newname.js", function () {
    console.log("file renamed");
  });
  ```

  ```js
  // Remove file
  fs.unlink("./file-to-delete", function (err) {
    if (err) {
      console.error(err);
    }
  });
  ```

  ### Rename and remove directories

  ```js
  // Best to use synchronous method here
  fs.rmSync("/dir", { recursive: true, force: true });
  ```

  ## Streams

  See [Handling streams with fs](Streams.md)


path: zk/http.md
sortable_path: zkhttp.md
title: http module
checksum: d74610fc301d67759fe76291d7e99eda1b918720fe38a694e5d28775c0b7ee25
created: 2024-03-30 09:03:22.065522364+00:00
modified: 2024-06-15 09:50:58.8172+00:00
metadata: {"tags":["node-js"]}
lead:
  The HTTP Module allows us to create a web server that listens for HTTP requests
  on a given port. It is therefore perfect for creating backends for client-side
  JavaScript.

body:
  The HTTP Module allows us to create a web server that listens for HTTP requests
  on a given port. It is therefore perfect for creating backends for client-side
  JavaScript.

  ## Creating a server

  An HTTP server is another instance of an
  [event emitter](events.md)). It
  therefore has all the same methods as the `EventEmitter` class: `on`, `emit`,
  `addListener` etc. This demonstrates again how much of Node's core functionality
  is based on event emitters.

  _Creating a server_

  ```js
  const http = require("http");

  const server = http.createServer(); // Create server as emitter

  // Register functions to run when listener is triggered
  server.on("connection", (socket) => {
    console.log("new connection...");
  });

  server.listen(3000);
  console.log("Listening on port 3000");
  ```

  This server is functionally equivalent to a generic event emitter:

  ```js
  // Raise an event
  const emitter = new EventEmitter("messageLogged");

  // Register a listener
  emitter.on("messagedLogged", function () {
    console.log("The listener was called.");
  });
  ```

  Whenever a request is made to this server, it raises an event. We can therefore
  target it with the `on` method and make it execute a function when requests are
  made.

  If we were to start the server by running the file and we then used a browser to
  navigate to the port, we would see `new connection` logged every time we refresh
  the page.

  ### Sockets and `req, res`

  A socket is a generic protocol for client-server communication. Crucially it
  **allows simultaneous communication both ways**. The client can contact the
  server but the server can also contact the client. Our listener function above
  uses a socket as the callback function but in most cases this is quite
  low-level, not distinguishing responses from requests. It is more likely that
  you would initiate a `request, resource` architecture in place of a socket:

  ```js
  const server = http.createServer((req, res) => {
    if (req.url === "/") {
      res.write("hello");
      res.end();
    }
  });
  ```

  #### Return JSON

  Below is an example of using this architecture to return JSON to the client:

  ```js
  const server = http.createServer((req, res) => {
    if (req.url === "/products") {
      res.write(JSON.stringify(["shoes", "lipstick", "cups"]));
      res.end();
    }
  });
  ```

  ### Express

  In reality you would rarely use the `http` module directly to create a server.
  This is because it is quite low level and each response must be written in a
  linear fashion as with the two URLs in the previous example. Instead we use
  Express which is a framework for creating servers and routing that is an
  abstraction on top of the core HTTP module.

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

  # `http` module

  The HTTP Module allows us to create a web server that listens for HTTP requests
  on a given port. It is therefore perfect for creating backends for client-side
  JavaScript.

  ## Creating a server

  An HTTP server is another instance of an
  [event emitter](events.md)). It
  therefore has all the same methods as the `EventEmitter` class: `on`, `emit`,
  `addListener` etc. This demonstrates again how much of Node's core functionality
  is based on event emitters.

  _Creating a server_

  ```js
  const http = require("http");

  const server = http.createServer(); // Create server as emitter

  // Register functions to run when listener is triggered
  server.on("connection", (socket) => {
    console.log("new connection...");
  });

  server.listen(3000);
  console.log("Listening on port 3000");
  ```

  This server is functionally equivalent to a generic event emitter:

  ```js
  // Raise an event
  const emitter = new EventEmitter("messageLogged");

  // Register a listener
  emitter.on("messagedLogged", function () {
    console.log("The listener was called.");
  });
  ```

  Whenever a request is made to this server, it raises an event. We can therefore
  target it with the `on` method and make it execute a function when requests are
  made.

  If we were to start the server by running the file and we then used a browser to
  navigate to the port, we would see `new connection` logged every time we refresh
  the page.

  ### Sockets and `req, res`

  A socket is a generic protocol for client-server communication. Crucially it
  **allows simultaneous communication both ways**. The client can contact the
  server but the server can also contact the client. Our listener function above
  uses a socket as the callback function but in most cases this is quite
  low-level, not distinguishing responses from requests. It is more likely that
  you would initiate a `request, resource` architecture in place of a socket:

  ```js
  const server = http.createServer((req, res) => {
    if (req.url === "/") {
      res.write("hello");
      res.end();
    }
  });
  ```

  #### Return JSON

  Below is an example of using this architecture to return JSON to the client:

  ```js
  const server = http.createServer((req, res) => {
    if (req.url === "/products") {
      res.write(JSON.stringify(["shoes", "lipstick", "cups"]));
      res.end();
    }
  });
  ```

  ### Express

  In reality you would rarely use the `http` module directly to create a server.
  This is because it is quite low level and each response must be written in a
  linear fashion as with the two URLs in the previous example. Instead we use
  Express which is a framework for creating servers and routing that is an
  abstraction on top of the core HTTP module.


path: zk/journald.md
sortable_path: zkjournald.md
title: journald
checksum: 4b92143448c0d4f6bfc813177950c5e2837edcc526ea906d5480495762a6a11d
created: 2024-03-30 09:03:22.082651017+00:00
modified: 2024-06-15 09:41:46.875852168+00:00
metadata: {"tags":["systems-programming","Linux","procedural"]}
lead:
  `journald` is a program that comes as default with [systemd](systemd.md). It is
  a service for collecting and storing system-level log data. I keeps a track of
  all [kernel](The_Kernel.md) processes. It is invaluable when tracing the source
  of problems and errors that may arise on the system level. It keeps a track of
  all kernal processes.

body:
  `journald` is a program that comes as default with [systemd](systemd.md). It is
  a service for collecting and storing system-level log data. I keeps a track of
  all [kernel](The_Kernel.md) processes. It is invaluable when tracing the source
  of problems and errors that may arise on the system level. It keeps a track of
  all kernal processes.

  ![](/img/journald.png)

  ## `journalctl`

  We use `journalctl` to access the logs. The command by itself outputs the entire
  log which will be huge and hard to scroll through. We can refine the results
  with modifiers.

  ### View logs for a specific process with pid

  ```bash
  journalctl _PID=1234
  ```

  ### View logs for a specific time period

  This can be really helpful since you can bracket the most recent events which
  will be more memorable.

  ```bash
  journalctl -S -1h
  ```

  ### View logs for a specfic systemd unit

  ```bash
  journalctl -u [unit_name] -e
  ```

  ### View boot logs

  ```bash
  journalctl -b
  ```

  #### Identify specific boot

  ```bash
  journalctl --list-boots

  ```

  ### List only kernel entries to the journal

  ```bash
  journalctl -k

  ```

raw_content:
  ---
  tags:
    - systems-programming
    - Linux
    - procedural
  ---

  # `journald`

  `journald` is a program that comes as default with [systemd](systemd.md). It is
  a service for collecting and storing system-level log data. I keeps a track of
  all [kernel](The_Kernel.md) processes. It is invaluable when tracing the source
  of problems and errors that may arise on the system level. It keeps a track of
  all kernal processes.

  ![](/img/journald.png)

  ## `journalctl`

  We use `journalctl` to access the logs. The command by itself outputs the entire
  log which will be huge and hard to scroll through. We can refine the results
  with modifiers.

  ### View logs for a specific process with pid

  ```bash
  journalctl _PID=1234
  ```

  ### View logs for a specific time period

  This can be really helpful since you can bracket the most recent events which
  will be more memorable.

  ```bash
  journalctl -S -1h
  ```

  ### View logs for a specfic systemd unit

  ```bash
  journalctl -u [unit_name] -e
  ```

  ### View boot logs

  ```bash
  journalctl -b
  ```

  #### Identify specific boot

  ```bash
  journalctl --list-boots

  ```

  ### List only kernel entries to the journal

  ```bash
  journalctl -k

  ```


path: zk/jq.md
sortable_path: zkjq.md
title: jq
lead: ## Remove property
checksum: 5feb67010bf5858df2ef8885605c6d9d67125fa962979f1041181bb9d3481894
created: 2024-03-30 09:03:22.101647543+00:00
modified: 2024-06-15 09:33:27.677497792+00:00
metadata: {"tags":["json"]}
body:
  ## Remove property

  Remove property from a JSON array of objects:

  ```json
  {
    "member_inputs": [
      {
        "name": "Thomas",
        "input_type": "select"
      },
      {
        "name": "Martha",
        "input_type": "text"
      }
    ]
  }
  ```

  ```sh
  jq '.member_details |= map(del(.input_type))' memberDetails.json
  ```

  > Add '-i' to modify the source file directly (in-place)

  > Multiple properties can be deleted simply by chaining,
  > e.g.`(del(.input_type, .another_property))`

raw_content:
  ---
  tags:
    - json
  ---

  # jq

  ## Remove property

  Remove property from a JSON array of objects:

  ```json
  {
    "member_inputs": [
      {
        "name": "Thomas",
        "input_type": "select"
      },
      {
        "name": "Martha",
        "input_type": "text"
      }
    ]
  }
  ```

  ```sh
  jq '.member_details |= map(del(.input_type))' memberDetails.json
  ```

  > Add '-i' to modify the source file directly (in-place)

  > Multiple properties can be deleted simply by chaining,
  > e.g.`(del(.input_type, .another_property))`


path: zk/printing_values_in_c.md
sortable_path: zkprinting_values_in_c.md
title: Printing values in C
lead: # Printing values in C
checksum: c2e34b4564bc870e2bb6e51e8f6d0258b4aff638c0ae6ffd42862cd7b6f6b79c
created: 2024-03-30 09:03:22.111056926+00:00
modified: 2024-03-27 10:45:52.510192133+00:00
body:
  # Printing values in C

  ## To print a literal

  ```c
  printf("hello world")
  ```

  ## To print a variable

  ```c
  int age = 25
  print("%d", age)

  ```

  The `%d` is an example of a [format specifier](./zk/format-specifiers-in-c.md).
  It is the decimal integer format specifier.

raw_content:
  ---
  id: 3hcv0amc
  title: Printing values in C
  tags: []
  created: Thursday, February 29, 2024 | 17:33
  ---

  # Printing values in C

  ## To print a literal

  ```c
  printf("hello world")
  ```

  ## To print a variable

  ```c
  int age = 25
  print("%d", age)

  ```

  The `%d` is an example of a [format specifier](./zk/format-specifiers-in-c.md).
  It is the decimal integer format specifier.

metadata:
  {"created":"Thursday, February 29, 2024 | 17:33","id":"3hcv0amc","tags":[],"title":"Printing values in C"}


path: zk/process.md
sortable_path: zkprocess.md
title: The process module in Node.js
checksum: e4914775c038b571eee647ad93112820406cd759d64d7cf130ebb13727e32a68
created: 2024-03-30 09:03:22.127479884+00:00
modified: 2024-06-15 09:50:40.420178522+00:00
metadata: {"tags":["node-js"]}
lead:
  `process` is a core module and a global object accessible from anywhere in a
  Node.js application. It contains functionality that allows us to interact with
  information about the current process instance of Node.

body:
  `process` is a core module and a global object accessible from anywhere in a
  Node.js application. It contains functionality that allows us to interact with
  information about the current process instance of Node.

  For example, we can use it to get environment information, read environment
  variables, parse arguments that are passed to the run command, communicate with
  the terminal and exit the current process.

  ## Managing runtime environments

  See
  [Managing Environments](Managing_environments.md).

  ## Accessing arguments: `process.argv`

  We can use `process.argv` to return an array containing the command-line
  arguments passed when a Node process is launched. This could be a whole-app
  entrypoint (i.e. `index.js`) or a single file we are running.

  For instance if we run the following file:

  ```js
  // process-demo.js
  console.log(3 + 3);
  console.log(process.argv);
  ```

  We get:

  ```bash
  6
  [
    '/Users/thomasbishop/.nvm/versions/node/v16.10.0/bin/node',
    '/Users/thomasbishop/prepos/testNode.js'
  ]
  ```

  The first item is the last executed process. The second is the array of
  arguments.

  In the array, the first value is a reference to the Node runtime binary. The
  second is the file that was passed to Node.

  If we passed in a parameter we would get that too:

  ```
  node process-demo.js --fakeFlag
  ```

  Gives us:

  ```bash
  [
    "/Users/thomasbishop/.nvm/versions/node/v16.10.0/bin/node",
    "/Users/thomasbishop/prepos/testNode.js",
    "--fake-flag",
  ]
  ```

  When writing Node CLI applications we can write functions that parse standard
  input. For example:

  ```js
  function parseStandardInput(flag) {
    let indexAfterFlag = process.argv.indexOf(flag);
    console.log(process.argv[indexAfterFlag]);
  }
  ```

  Say we ran a program that took in a username:

  ```bash
  node userName.js --user Thomas
  ```

  Then `parseStandardInput("--user")` would give us `"thomas"`

  ## Standard input and output

  Below is the most basic use case: writing to the output.

  ```js
  // alpha.js
  process.stdout.write("Hello from file \n");
  ```

  ```
  node alpha.js
  Hello from file
  ```

  Typically we want to write and read data that the user provides. To do this we
  need to wait on that event. So we use Node's asynchonous
  [event listeners](events.md). We wait
  on the `data` event:

  ```js
  process.stdin.on("data", (data) => {
    process.stdout.write(data.toString().trim());
  });

  process.stdout.write("\n What is your name? \n");
  process.stdout.write(` \n > `);
  ```

  This waits for whatever input is given and then echoes it.

  Currently there is no way to end this process; it will just keep echoing the
  input.

  So we add an exit clause:

  ```js
  process.stdin.on("data", (data) => {
    process.stdout.write(data.toString().trim());
    process.exit();
  });
  ```

  We can also execute functionality on the exit event:

  ```js
  process.on("exit", () => {
    // Do something
  });
  ```

  > Timings can also obviously be managed via the `setTimeout` and `setInterval`
  > methods.

  ### Additional useful methods

  ```js
  process.stdout.clearLine();
  process.stdout.cursorTo([int]);
  ```

  ### `readline`

  The core `readline` module simplifies the handling of standard input and output
  by providing a wrapper. It reads the input stream line by line. Basic usage:

  ```js
  var readline = require("readline");

  var rl = readline.createInterface(process.stdin, process.stdout);

  rl.question("What is your age? ", (age) => {
    console.log("Your age is: " + age);
  });
  ```

  Usage scenarios at: https://www.geeksforgeeks.org/node-js-readline-module/

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

  # The `process` module in Node.js

  `process` is a core module and a global object accessible from anywhere in a
  Node.js application. It contains functionality that allows us to interact with
  information about the current process instance of Node.

  For example, we can use it to get environment information, read environment
  variables, parse arguments that are passed to the run command, communicate with
  the terminal and exit the current process.

  ## Managing runtime environments

  See
  [Managing Environments](Managing_environments.md).

  ## Accessing arguments: `process.argv`

  We can use `process.argv` to return an array containing the command-line
  arguments passed when a Node process is launched. This could be a whole-app
  entrypoint (i.e. `index.js`) or a single file we are running.

  For instance if we run the following file:

  ```js
  // process-demo.js
  console.log(3 + 3);
  console.log(process.argv);
  ```

  We get:

  ```bash
  6
  [
    '/Users/thomasbishop/.nvm/versions/node/v16.10.0/bin/node',
    '/Users/thomasbishop/prepos/testNode.js'
  ]
  ```

  The first item is the last executed process. The second is the array of
  arguments.

  In the array, the first value is a reference to the Node runtime binary. The
  second is the file that was passed to Node.

  If we passed in a parameter we would get that too:

  ```
  node process-demo.js --fakeFlag
  ```

  Gives us:

  ```bash
  [
    "/Users/thomasbishop/.nvm/versions/node/v16.10.0/bin/node",
    "/Users/thomasbishop/prepos/testNode.js",
    "--fake-flag",
  ]
  ```

  When writing Node CLI applications we can write functions that parse standard
  input. For example:

  ```js
  function parseStandardInput(flag) {
    let indexAfterFlag = process.argv.indexOf(flag);
    console.log(process.argv[indexAfterFlag]);
  }
  ```

  Say we ran a program that took in a username:

  ```bash
  node userName.js --user Thomas
  ```

  Then `parseStandardInput("--user")` would give us `"thomas"`

  ## Standard input and output

  Below is the most basic use case: writing to the output.

  ```js
  // alpha.js
  process.stdout.write("Hello from file \n");
  ```

  ```
  node alpha.js
  Hello from file
  ```

  Typically we want to write and read data that the user provides. To do this we
  need to wait on that event. So we use Node's asynchonous
  [event listeners](events.md). We wait
  on the `data` event:

  ```js
  process.stdin.on("data", (data) => {
    process.stdout.write(data.toString().trim());
  });

  process.stdout.write("\n What is your name? \n");
  process.stdout.write(` \n > `);
  ```

  This waits for whatever input is given and then echoes it.

  Currently there is no way to end this process; it will just keep echoing the
  input.

  So we add an exit clause:

  ```js
  process.stdin.on("data", (data) => {
    process.stdout.write(data.toString().trim());
    process.exit();
  });
  ```

  We can also execute functionality on the exit event:

  ```js
  process.on("exit", () => {
    // Do something
  });
  ```

  > Timings can also obviously be managed via the `setTimeout` and `setInterval`
  > methods.

  ### Additional useful methods

  ```js
  process.stdout.clearLine();
  process.stdout.cursorTo([int]);
  ```

  ### `readline`

  The core `readline` module simplifies the handling of standard input and output
  by providing a wrapper. It reads the input stream line by line. Basic usage:

  ```js
  var readline = require("readline");

  var rl = readline.createInterface(process.stdin, process.stdout);

  rl.question("What is your age? ", (age) => {
    console.log("Your age is: " + age);
  });
  ```

  Usage scenarios at: https://www.geeksforgeeks.org/node-js-readline-module/


path: zk/python_advent_learnings.md
sortable_path: zkpython_advent_learnings.md
title: Python advent learnings
lead: ### Construct paths without hard-coding absolute path
checksum: 41e623fc64d7bdeedbc4cb74740ba94f0d812a86c637bec43edfdfb62f90f6b5
created: 2024-03-30 09:03:22.147095316+00:00
modified: 2024-03-27 10:45:52.510192133+00:00
metadata: {}
body:
  ### Construct paths without hard-coding absolute path

  ```py
  puzzle_input_path = os.path.join(os.path.dirname(__file__), "data/test_input.txt")

  ```

  ### Read file by line and store in variable

  ```py
  puzzle_input_path = os.path.join(os.path.dirname(__file__), "data/test_input.txt")

  def getPuzzleInput(file_path):
      with open(file_path, "r") as file:
          return [line.strip() for line in file]


  puzzle_input = getPuzzleInput(puzzle_input_path)

  ```

  ### Conditional tests for members of arrays

  Check that all elements are greater than zero:

  ```py
  if all(element >= 0 for element in array):
      print('All elements greater than 0')
  ```

  Check if any element is less than 0:

  ```py
  if any(element < 0 for element in array):
      print('There is an element that is less than 0')
  ```

  ### Check for substring:

  ```py
  text = "latest test"
  if "test" in text:
      return True
  ```

  ### Control flow in loop

  ```py
  for counter_values in games:
      colour = counter_values[1]
      count = int(counter_values[0])
      if colour in initial_values.keys() and count > initial_values[colour]:
          break
  else:
      valid_games_count += game_number
  ```

  The use of the `else` clause here is a special feature of Python. The `else`
  clause after a `for` loop will normally only execute when the loop has finished
  iterating over the list, but not when the loop is terminated by a `break`
  statement.

  The `else` clause is executed if the `for` loop completes normally. If the
  `break` statement is executed (i.e., if any count in `counter_values` is greater
  than the corresponding count in `initial_values`), the `for` loop is terminated
  and the `else` clause is skipped.

  Here the combination of a `for` loop and the `break` statement creates
  `if, else` logic. If the break condition is not reached, then the `else` block
  runs for every iteration of the loop.

  ### Multiple loop in list comprehension

  ```py
  games = [item.strip().split() for game in games for item in game.split(",")]
  ```

raw_content:
  # Python advent learnings

  ### Construct paths without hard-coding absolute path

  ```py
  puzzle_input_path = os.path.join(os.path.dirname(__file__), "data/test_input.txt")

  ```

  ### Read file by line and store in variable

  ```py
  puzzle_input_path = os.path.join(os.path.dirname(__file__), "data/test_input.txt")

  def getPuzzleInput(file_path):
      with open(file_path, "r") as file:
          return [line.strip() for line in file]


  puzzle_input = getPuzzleInput(puzzle_input_path)

  ```

  ### Conditional tests for members of arrays

  Check that all elements are greater than zero:

  ```py
  if all(element >= 0 for element in array):
      print('All elements greater than 0')
  ```

  Check if any element is less than 0:

  ```py
  if any(element < 0 for element in array):
      print('There is an element that is less than 0')
  ```

  ### Check for substring:

  ```py
  text = "latest test"
  if "test" in text:
      return True
  ```

  ### Control flow in loop

  ```py
  for counter_values in games:
      colour = counter_values[1]
      count = int(counter_values[0])
      if colour in initial_values.keys() and count > initial_values[colour]:
          break
  else:
      valid_games_count += game_number
  ```

  The use of the `else` clause here is a special feature of Python. The `else`
  clause after a `for` loop will normally only execute when the loop has finished
  iterating over the list, but not when the loop is terminated by a `break`
  statement.

  The `else` clause is executed if the `for` loop completes normally. If the
  `break` statement is executed (i.e., if any count in `counter_values` is greater
  than the corresponding count in `initial_values`), the `for` loop is terminated
  and the `else` clause is skipped.

  Here the combination of a `for` loop and the `break` statement creates
  `if, else` logic. If the break condition is not reached, then the `else` block
  runs for every iteration of the loop.

  ### Multiple loop in list comprehension

  ```py
  games = [item.strip().split() for game in games for item in game.split(",")]
  ```


path: zk/systemd.md
sortable_path: zksystemd.md
title: systemd
checksum: d49ea781434a68ed07dee06e34d8c820c7a529fd8d33e46835c65c6a2a542718
created: 2024-03-30 09:03:22.152461124+00:00
modified: 2024-06-15 09:43:23.547978531+00:00
metadata: {"tags":["systems-programming","Linux","procedural","systemd"]}
lead:
  Once the [boot process](Boot_process.md) has completed and the bootloader has
  located the kernel and injected it into memory the first user space program
  runs: `init` (for _initialisation_). `init` is a [daemon](Daemons.md) process
  that continues running until shutdown and is responsible for starting all the
  processes that are prerequisites for user space. For example: network
  connections, disk access, user logins etc. `init` is the parent of all
  processes: PID1. Whilst it does a lot of its work in quick succession at boot
  time it is not limited to the this stage of the lifescycle but runs continuously
  in reponse to new user events.

body:
  Once the [boot process](Boot_process.md) has completed and the bootloader has
  located the kernel and injected it into memory the first user space program
  runs: `init` (for _initialisation_). `init` is a [daemon](Daemons.md) process
  that continues running until shutdown and is responsible for starting all the
  processes that are prerequisites for user space. For example: network
  connections, disk access, user logins etc. `init` is the parent of all
  processes: PID1. Whilst it does a lot of its work in quick succession at boot
  time it is not limited to the this stage of the lifescycle but runs continuously
  in reponse to new user events.

  On Linux systems `systemd` is used to implement `init`.

  `systemd` is directly accessible from user space and provides a straightforward
  way to enable/disable, start/stop system level processes

  > `systemd` can track individual service daemons after they start, and group
  > together multiple processes associated with a service, giving you more power
  > and insight into exactly what is running on the system _How Linux Works: Third
  > Edition_, Brian Ward 2021

  ## How systemd works

  ### Goal-directed units

  `systemd` works on the basis of _goals_. Each goal is system task defined as a
  **unit**. A unit contains instructions and a specification of dependencies to
  other units.

  When activating a unit, `systemd` first activates the dependencies and then
  moves onto the details of the unit itself. `init` as implemented by `systemd`
  does not follow a rigid sequence every time, initialising units in the same
  sequence and waiting for one to complete before starting another. Instead it
  activates units whenever they are ready. This, its parallelized nature, is one
  of the main advantages over previous programs that managed the `init` sequence
  on Linux (such as for example System V);

  ### Unit types

  Units are organised into **unit types**. The main types that run at boot time
  are as follows:

  - service units (`.service`)
    - control service daemons
  - socket units (`.socket`)
    - handle incoming network connection request locations
  - device units (`.device`)
    - disks and other devices
  - mount units (`.mount`)
    - handle the attachment of filesystems to the system
  - target units
    - control other units by organising them into groups

  For example, at boot, a target unit called `default.target` groups together a
  number of service and mount units as dependencies. These then run in a
  graph-like dependency structure where a unit that comes late in the boot process
  can depend on several previous units making earlier branches of a dependency
  tree join back together.

  ## systemd configuration files

  Units are managed via `systemd` configuration files.

  ### Configuration file locations

  System level `systemd` config files are located in the _system unit directory_
  at `/usr/lib/systemd/system`. You shouldn't change or manipulate these files or
  attempt to add new config files here since they will be overwritten by the
  system.

  ![](/img/systemd-global-files.png) _`systemd` global unit files_

  Local definitions that relate to the specific user and where the user herself
  can define units are located in the _system configuration_ directory:
  `/etc/systemd/system`.

  ![](/img/systemd-local-files.png)

  _`systemd` local unit files, specific to the currently logged-in user_

  ### Example files

  Below is the systemd service file for the creation of UUIDs:

  ```plain
  [Unit]
  Description=Daemon for generating UUIDs
  Documentation=man:uuidd(8)
  Requires=uuidd.socket

  [Service]
  ExecStart=/usr/sbin/uuidd --socket-activation
  Restart=no
  User=uuidd
  Group=uuidd
  ProtectSystem=strict
  ProtectHome=yes
  PrivateDevices=yes
  PrivateUsers=yes
  ProtectKernelTunables=yes
  ProtectKernelModules=yes
  ProtectControlGroups=yes
  MemoryDenyWriteExecute=yes
  ReadWritePaths=/var/lib/libuuid/
  SystemCallFilter=@default @file-system @basic-io @system-service @signal @io-event @network-io

  [Install]
  Also=uuidd.socket
  ```

  - The `Unit` section provides metadata about the unit including which `systemd`
    dependencies it has
  - `Service` constitutes the main specification for the unit
  - `Install` is the call to set the dependencies running before the `Service`
    functions are accessible.

  ## systemd operations: systemctl

  The `systemctl` command is the chief way of interacting with `systemd`. You use
  it to activate and deactivate services, list their status, reload the
  configuration and so.

  ### View the dependency graph

  `systemctl status` by itself will print a long list of units grouped by their
  dependency relations. It will also provide some metadata about the current
  systemd boot context.

  ### Viewing active units

  Below I have listed the running units pertaining to bluetooth:

  ```
  $ systemctl list-units | grep bluetooth
   sys-devices-pci0000:00-0000:00:14.0-usb3-3\x2d10-3\x2d10:1.0-bluetooth-hci0-hci0:3585.device                                   loaded active plugged   /sys/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0/bluetooth/hci0/hci0:3585
    sys-devices-pci0000:00-0000:00:14.0-usb3-3\x2d10-3\x2d10:1.0-bluetooth-hci0.device                                             loaded active plugged   /sys/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0/bluetooth/hci0
    sys-subsystem-bluetooth-devices-hci0.device                                                                                    loaded active plugged   /sys/subsystem/bluetooth/devices/hci0
    sys-subsystem-bluetooth-devices-hci0:3585.device                                                                               loaded active plugged   /sys/subsystem/bluetooth/devices/hci0:3585
    bluetooth.service                                                                                                              loaded active running   Bluetooth service
    bluetooth.target                                                                                                               loaded active active    Bluetooth Support
  ```

  ### List jobs

  Requests to activate, reactivate and restart units are called **jobs** in
  `systemd`. They can be thought of as unit state changes. Current jobs can be
  listed with `systemctl list-jobs`.

  This will most likely return `No jobs running` if the computer has been running
  for a while. Most jobs execute at boot.

  ### Enable/disable, start/stop units

  If a unit has dependencies it is necessary to _enable_ it before starting it.
  This installs the dependencies.

  ```bash
  systemctl enable mongodb.service
  Created symlink /etc/systemd/system/multi-user.target.wants/mongodb.service → /usr/lib/systemd/system/mongodb.service.
  ```

  Then we can start:

  ```
  systemctl start mongodb.service
  ```

  To stop the service:

  ```
  systemctl stop mongodb.service
  ```

  After this we should disable it, in order to remove any symbolic links it has
  created on the system as part of the install process:

  ```bash
  systemctl disable mongodb.service
  Removed "/etc/systemd/system/multi-user.target.wants/mongodb.service".
  ```

  ## Why use systemd over cron ?

  https://mark.stosberg.com/2016-08-26-rsnapshot-and-systemd/

raw_content:
  ---
  tags:
    - systems-programming
    - Linux
    - procedural
    - systemd
  ---

  # systemd

  Once the [boot process](Boot_process.md) has completed and the bootloader has
  located the kernel and injected it into memory the first user space program
  runs: `init` (for _initialisation_). `init` is a [daemon](Daemons.md) process
  that continues running until shutdown and is responsible for starting all the
  processes that are prerequisites for user space. For example: network
  connections, disk access, user logins etc. `init` is the parent of all
  processes: PID1. Whilst it does a lot of its work in quick succession at boot
  time it is not limited to the this stage of the lifescycle but runs continuously
  in reponse to new user events.

  On Linux systems `systemd` is used to implement `init`.

  `systemd` is directly accessible from user space and provides a straightforward
  way to enable/disable, start/stop system level processes

  > `systemd` can track individual service daemons after they start, and group
  > together multiple processes associated with a service, giving you more power
  > and insight into exactly what is running on the system _How Linux Works: Third
  > Edition_, Brian Ward 2021

  ## How systemd works

  ### Goal-directed units

  `systemd` works on the basis of _goals_. Each goal is system task defined as a
  **unit**. A unit contains instructions and a specification of dependencies to
  other units.

  When activating a unit, `systemd` first activates the dependencies and then
  moves onto the details of the unit itself. `init` as implemented by `systemd`
  does not follow a rigid sequence every time, initialising units in the same
  sequence and waiting for one to complete before starting another. Instead it
  activates units whenever they are ready. This, its parallelized nature, is one
  of the main advantages over previous programs that managed the `init` sequence
  on Linux (such as for example System V);

  ### Unit types

  Units are organised into **unit types**. The main types that run at boot time
  are as follows:

  - service units (`.service`)
    - control service daemons
  - socket units (`.socket`)
    - handle incoming network connection request locations
  - device units (`.device`)
    - disks and other devices
  - mount units (`.mount`)
    - handle the attachment of filesystems to the system
  - target units
    - control other units by organising them into groups

  For example, at boot, a target unit called `default.target` groups together a
  number of service and mount units as dependencies. These then run in a
  graph-like dependency structure where a unit that comes late in the boot process
  can depend on several previous units making earlier branches of a dependency
  tree join back together.

  ## systemd configuration files

  Units are managed via `systemd` configuration files.

  ### Configuration file locations

  System level `systemd` config files are located in the _system unit directory_
  at `/usr/lib/systemd/system`. You shouldn't change or manipulate these files or
  attempt to add new config files here since they will be overwritten by the
  system.

  ![](/img/systemd-global-files.png) _`systemd` global unit files_

  Local definitions that relate to the specific user and where the user herself
  can define units are located in the _system configuration_ directory:
  `/etc/systemd/system`.

  ![](/img/systemd-local-files.png)

  _`systemd` local unit files, specific to the currently logged-in user_

  ### Example files

  Below is the systemd service file for the creation of UUIDs:

  ```plain
  [Unit]
  Description=Daemon for generating UUIDs
  Documentation=man:uuidd(8)
  Requires=uuidd.socket

  [Service]
  ExecStart=/usr/sbin/uuidd --socket-activation
  Restart=no
  User=uuidd
  Group=uuidd
  ProtectSystem=strict
  ProtectHome=yes
  PrivateDevices=yes
  PrivateUsers=yes
  ProtectKernelTunables=yes
  ProtectKernelModules=yes
  ProtectControlGroups=yes
  MemoryDenyWriteExecute=yes
  ReadWritePaths=/var/lib/libuuid/
  SystemCallFilter=@default @file-system @basic-io @system-service @signal @io-event @network-io

  [Install]
  Also=uuidd.socket
  ```

  - The `Unit` section provides metadata about the unit including which `systemd`
    dependencies it has
  - `Service` constitutes the main specification for the unit
  - `Install` is the call to set the dependencies running before the `Service`
    functions are accessible.

  ## systemd operations: systemctl

  The `systemctl` command is the chief way of interacting with `systemd`. You use
  it to activate and deactivate services, list their status, reload the
  configuration and so.

  ### View the dependency graph

  `systemctl status` by itself will print a long list of units grouped by their
  dependency relations. It will also provide some metadata about the current
  systemd boot context.

  ### Viewing active units

  Below I have listed the running units pertaining to bluetooth:

  ```
  $ systemctl list-units | grep bluetooth
   sys-devices-pci0000:00-0000:00:14.0-usb3-3\x2d10-3\x2d10:1.0-bluetooth-hci0-hci0:3585.device                                   loaded active plugged   /sys/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0/bluetooth/hci0/hci0:3585
    sys-devices-pci0000:00-0000:00:14.0-usb3-3\x2d10-3\x2d10:1.0-bluetooth-hci0.device                                             loaded active plugged   /sys/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0/bluetooth/hci0
    sys-subsystem-bluetooth-devices-hci0.device                                                                                    loaded active plugged   /sys/subsystem/bluetooth/devices/hci0
    sys-subsystem-bluetooth-devices-hci0:3585.device                                                                               loaded active plugged   /sys/subsystem/bluetooth/devices/hci0:3585
    bluetooth.service                                                                                                              loaded active running   Bluetooth service
    bluetooth.target                                                                                                               loaded active active    Bluetooth Support
  ```

  ### List jobs

  Requests to activate, reactivate and restart units are called **jobs** in
  `systemd`. They can be thought of as unit state changes. Current jobs can be
  listed with `systemctl list-jobs`.

  This will most likely return `No jobs running` if the computer has been running
  for a while. Most jobs execute at boot.

  ### Enable/disable, start/stop units

  If a unit has dependencies it is necessary to _enable_ it before starting it.
  This installs the dependencies.

  ```bash
  systemctl enable mongodb.service
  Created symlink /etc/systemd/system/multi-user.target.wants/mongodb.service → /usr/lib/systemd/system/mongodb.service.
  ```

  Then we can start:

  ```
  systemctl start mongodb.service
  ```

  To stop the service:

  ```
  systemctl stop mongodb.service
  ```

  After this we should disable it, in order to remove any symbolic links it has
  created on the system as part of the install process:

  ```bash
  systemctl disable mongodb.service
  Removed "/etc/systemd/system/multi-user.target.wants/mongodb.service".
  ```

  ## Why use systemd over cron ?

  https://mark.stosberg.com/2016-08-26-rsnapshot-and-systemd/


path: zk/systemd_status.md
sortable_path: zksystemd_status.md
title: systemd status
lead: # Get status of a specific unit
checksum: f2ec1eb5d619ecd6270fe8ede5fc2088b176c6af31e805c2ea6bfe25f5bb717d
created: 2024-03-30 09:03:22.167794749+00:00
modified: 2024-06-15 09:42:04.726250922+00:00
body:
  # Get status of a specific unit

  Here I have requested the status of the currently running `mongodb` unit:

  ```
  $ systemctl status mongod
  mongodb.service - MongoDB Database Server
       Loaded: loaded (/usr/lib/systemd/system/mongodb.service; enabled; preset: disabled)
       Active: active (running) since Wed 2022-08-17 07:25:27 BST; 24h ago
         Docs: https://docs.mongodb.org/manual
     Main PID: 931 (mongod)
       Memory: 304.1M
          CPU: 2min 18.630s
       CGroup: /system.slice/mongodb.service
               └─931 /usr/bin/mongod --config /etc/mongodb.conf
  Aug 17 07:25:27 archbish systemd[1]: Started MongoDB Database Server.****
  ```

  In addition to the core info it tells us the unit type. In this case it is a
  service.

  We can also view the journal entry for the given unit. This provides you with
  its diagnostic log messages:

  ```
  journalctl --unit=mongodb.service
  - Boot b9565dfe8aca4d069143209b3aa84d8e --
  Aug 05 18:31:30 archbish systemd[1]: Started MongoDB Database Server.
  Aug 06 14:27:33 archbish systemd[1]: mongodb.service: Deactivated successfully.
  Aug 06 14:27:33 archbish systemd[1]: mongodb.service: Consumed 3min 17.598s CPU time.
  -- Boot 01922f84c3bd4b3a8f11824cf05f7320 --
  Aug 07 11:58:09 archbish systemd[1]: Started MongoDB Database Server.
  Aug 08 14:43:01 archbish systemd[1]: mongodb.service: Deactivated successfully.
  Aug 08 14:43:01 archbish systemd[1]: mongodb.service: Consumed 5min 28.760s CPU time.
  -- Boot e52b735e115c43bdad8c00462aaff395 --
  Aug 10 13:13:22 archbish systemd[1]: Started MongoDB Database Server.
  Aug 11 07:46:40 archbish systemd[1]: mongodb.service: Deactivated successfully.
  Aug 11 07:46:40 archbish systemd[1]: mongodb.service: Consumed 2min 16.629s CPU time.
  ```

  Each entry is organised around specific boots.

raw_content:
  ---
  id: v5f6xkdv
  title: systemd status
  tags:
    - systemd
    - systems-programming
    - Linux
    - procedural
  created: Thursday, February 29, 2024 | 18:19
  ---

  # Get status of a specific unit

  Here I have requested the status of the currently running `mongodb` unit:

  ```
  $ systemctl status mongod
  mongodb.service - MongoDB Database Server
       Loaded: loaded (/usr/lib/systemd/system/mongodb.service; enabled; preset: disabled)
       Active: active (running) since Wed 2022-08-17 07:25:27 BST; 24h ago
         Docs: https://docs.mongodb.org/manual
     Main PID: 931 (mongod)
       Memory: 304.1M
          CPU: 2min 18.630s
       CGroup: /system.slice/mongodb.service
               └─931 /usr/bin/mongod --config /etc/mongodb.conf
  Aug 17 07:25:27 archbish systemd[1]: Started MongoDB Database Server.****
  ```

  In addition to the core info it tells us the unit type. In this case it is a
  service.

  We can also view the journal entry for the given unit. This provides you with
  its diagnostic log messages:

  ```
  journalctl --unit=mongodb.service
  - Boot b9565dfe8aca4d069143209b3aa84d8e --
  Aug 05 18:31:30 archbish systemd[1]: Started MongoDB Database Server.
  Aug 06 14:27:33 archbish systemd[1]: mongodb.service: Deactivated successfully.
  Aug 06 14:27:33 archbish systemd[1]: mongodb.service: Consumed 3min 17.598s CPU time.
  -- Boot 01922f84c3bd4b3a8f11824cf05f7320 --
  Aug 07 11:58:09 archbish systemd[1]: Started MongoDB Database Server.
  Aug 08 14:43:01 archbish systemd[1]: mongodb.service: Deactivated successfully.
  Aug 08 14:43:01 archbish systemd[1]: mongodb.service: Consumed 5min 28.760s CPU time.
  -- Boot e52b735e115c43bdad8c00462aaff395 --
  Aug 10 13:13:22 archbish systemd[1]: Started MongoDB Database Server.
  Aug 11 07:46:40 archbish systemd[1]: mongodb.service: Deactivated successfully.
  Aug 11 07:46:40 archbish systemd[1]: mongodb.service: Consumed 2min 16.629s CPU time.
  ```

  Each entry is organised around specific boots.

metadata:
  {"created":"Thursday, February 29, 2024 | 18:19","id":"v5f6xkdv","tags":["systemd","systems-programming","Linux","procedural"],"title":"systemd status"}


path: zk/v8.md
sortable_path: zkv8.md
title: v8
checksum: b10a7303881a96d28e9623865c847d3ad3c39bf9056b9d8c1820f7236db2b385
created: 2024-03-30 09:03:22.234796202+00:00
modified: 2024-06-15 09:50:49.133680044+00:00
metadata: {"tags":["node-js"]}
lead:
  The v8 module is useful for providing information about the resource-usage of
  your Node.js apps, such as memory usage and capacity.

body:
  The v8 module is useful for providing information about the resource-usage of
  your Node.js apps, such as memory usage and capacity.

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

  # v8

  The v8 module is useful for providing information about the resource-usage of
  your Node.js apps, such as memory usage and capacity.


path: zk/variables_in_c.md
sortable_path: zkvariables_in_c.md
title: Variables in C
lead: # Variables in C
body:
  # Variables in C

  ```c
  int age = 25
  ```

checksum: b702bec2aebc256d92e83e60ef9beefdd46605d1a435691d4936b840e6ba160c
created: 2024-03-30 09:03:22.242584575+00:00
modified: 2024-03-27 10:45:52.510192133+00:00
raw_content:
  ---
  id: r4i16p1x
  title: Variables in C
  tags: [C]
  created: Thursday, February 29, 2024 | 17:20
  ---

  # Variables in C

  ```c
  int age = 25
  ```

metadata:
  {"created":"Thursday, February 29, 2024 | 17:20","id":"r4i16p1x","tags":["C"],"title":"Variables in C"}


path: zk/virtual_private_cloud.md
sortable_path: zkvirtual_private_cloud.md
title: Virtual Private Cloud
lead: # Virtual Private Cloud
checksum: 15665690b24aff7e50083932162a8fb8e22aa4065f512ff3495b497587d4bca4
created: 2024-03-30 09:03:22.249101381+00:00
modified: 2024-03-27 10:45:52.510192133+00:00
body:
  # Virtual Private Cloud

  AWS VPC is used to create a virtual network. This is typically used in concert
  with [[zk/Elastic_Compute_Cloud]] to manage connections to a virtual server both
  privately and accross the internet.

  You define a network address range and then create subnets for managing
  different connections and functionality. You use a public subnet for resources
  that must be connected to the internet and a private subnet for resources that
  are to remain isolated from the internet.

  The diagram below details a basic VPC configuration:

  ![](../img/aws-vpc-example.png)

  - Within a given AWS region we have created a VPC network.
  - This comprises public and private subnets
  - Both subnets host an EC2 instance
  - The public subnet has a bridge to the internet through the Internet Gateway
  - Both subnets have a routing table to manage requests and access

raw_content:
  ---
  id: mdw5fe5a
  title: Virtual Private Cloud
  tags: [AWS, networks]
  created: Tuesday, February 20, 2024 | 08:31
  ---

  # Virtual Private Cloud

  AWS VPC is used to create a virtual network. This is typically used in concert
  with [[zk/Elastic_Compute_Cloud]] to manage connections to a virtual server both
  privately and accross the internet.

  You define a network address range and then create subnets for managing
  different connections and functionality. You use a public subnet for resources
  that must be connected to the internet and a private subnet for resources that
  are to remain isolated from the internet.

  The diagram below details a basic VPC configuration:

  ![](../img/aws-vpc-example.png)

  - Within a given AWS region we have created a VPC network.
  - This comprises public and private subnets
  - Both subnets host an EC2 instance
  - The public subnet has a bridge to the internet through the Internet Gateway
  - Both subnets have a routing table to manage requests and access

metadata:
  {"created":"Tuesday, February 20, 2024 | 08:31","id":"mdw5fe5a","tags":["AWS","networks"],"title":"Virtual Private Cloud"}


path: zk/Docker_CLI.md
sortable_path: zkDocker_CLI.md
title: Docker_CLI
lead: # Docker_CLI
checksum: d53b218fa9b08d4a661007985dc7ef4b67cd04f31860b13f88b1a9d3f71a9d34
created: 2024-03-30 14:42:27.76745933+00:00
modified: 2024-06-16 17:05:29.100841947+00:00
metadata: {"created":"Saturday, March 30, 2024","id":"6g54","tags":["docker","shell"],"title":"Docker_CLI"}
body:
  # Docker_CLI

  ```sh
  # List images
  docker image ls

  # List container
  docker container ls

  # Remove container
  docker rm container-name

  # Remove image

  docker rmi image_name:version

  ```

raw_content:
  ---
  id: 6g54
  title: Docker_CLI
  tags:
    - docker
    - shell
  created: Saturday, March 30, 2024
  ---

  # Docker_CLI

  ```sh
  # List images
  docker image ls

  # List container
  docker container ls

  # Remove container
  docker rm container-name

  # Remove image

  docker rmi image_name:version

  ```


path: zk/Stack_memory.md
sortable_path: zkStack_memory.md
title: Stack_memory
lead: # Stack memory
checksum: 9ea1d4406f69d2cc83a433d862b3314b6ea9830bb94aa92c4cf800e465cac391
created: 2024-04-16 06:46:27.952023721+00:00
modified: 2024-04-20 12:40:16.05327467+00:00
metadata: {"created":"Tuesday, April 16, 2024","id":"1fig","tags":["memory"],"title":"Stack_memory"}
body:
  # Stack memory

  ## Summary

  - Along with [[Heap_memory|heap memory]] heap memory, programs make use of
    _stack memory_ during runtime.
  - [ ]
  - Best suited for small-sized singular values that are temporary/ephemeral
    rather than large or complex data types that need to persist for a longer
    time.

  - Specifically used to keep track of memory used during function executions
    within a given scope: control flow, local variables, returned value.

  - Uses a LIFO data-structure

  Similarly to the [[Stacks|stack data structure]], it works on the basis of LIFO:
  the last item put on the stack is the first item to be withdrawn.

  Note, this doesn't mean that the data at the memory addresses that comprise the
  stack can _only_ be accessed in a LIFO manner. Any item currently on the stack
  can be accessed (be read or modified) at any time. LIFO applies when it is is
  time to clear memory from the stack: the most recent addition is cleared first
  from the top down.

  ### Stack pointer

  A **stack pointer** is used to manage the items in the stack. This value is
  stored in a [[CPU_architecture#registers|register]] and its value is the memory
  address of the item currently on the top of the stack. Note: _not_ the value
  itself, the _address_ of the value.

  When a new value is added to the top of the stack, the pointer's value is
  adjusted to increase the size of the stack and make room for the new value. So
  this will be a new memory address for the latest item. When an item is removed,
  the reverse happens. The previously top address is cleared and the pointer
  points to the new top item.

  ## Threads

  For every thread that a program spawns, it gets its own stack or "stack frame".
  Stack memory within the thread can only be accessed by processes within that
  thread which is what makes it well suited for managing scoped function memory.

  ### Role in program execution

  1. Function call management: a stack records the key information pertaining to
     the execution of a function: parameters, local variables, the location of the
     address for the returned value.

  2. Local variable storage: storing the variables local to a function's scope and
     removing them once the function returns, avoiding
     [[Memory_leaks|memory leaks]]

  3. Control flow: record from where a function is called and where its return
     value should be stored, meaning the right value is always returned no matter
     how nested it is.

  4. Recursion: individuating each call of a function that calls itself and its
     given step to avoid infinite loops

  ## Related notes

raw_content:
  ---
  id: 1fig
  title: Stack_memory
  tags: [memory]
  created: Tuesday, April 16, 2024
  ---

  # Stack memory

  ## Summary

  - Along with [[Heap_memory|heap memory]] heap memory, programs make use of
    _stack memory_ during runtime.
  - [ ]
  - Best suited for small-sized singular values that are temporary/ephemeral
    rather than large or complex data types that need to persist for a longer
    time.

  - Specifically used to keep track of memory used during function executions
    within a given scope: control flow, local variables, returned value.

  - Uses a LIFO data-structure

  Similarly to the [[Stacks|stack data structure]], it works on the basis of LIFO:
  the last item put on the stack is the first item to be withdrawn.

  Note, this doesn't mean that the data at the memory addresses that comprise the
  stack can _only_ be accessed in a LIFO manner. Any item currently on the stack
  can be accessed (be read or modified) at any time. LIFO applies when it is is
  time to clear memory from the stack: the most recent addition is cleared first
  from the top down.

  ### Stack pointer

  A **stack pointer** is used to manage the items in the stack. This value is
  stored in a [[CPU_architecture#registers|register]] and its value is the memory
  address of the item currently on the top of the stack. Note: _not_ the value
  itself, the _address_ of the value.

  When a new value is added to the top of the stack, the pointer's value is
  adjusted to increase the size of the stack and make room for the new value. So
  this will be a new memory address for the latest item. When an item is removed,
  the reverse happens. The previously top address is cleared and the pointer
  points to the new top item.

  ## Threads

  For every thread that a program spawns, it gets its own stack or "stack frame".
  Stack memory within the thread can only be accessed by processes within that
  thread which is what makes it well suited for managing scoped function memory.

  ### Role in program execution

  1. Function call management: a stack records the key information pertaining to
     the execution of a function: parameters, local variables, the location of the
     address for the returned value.

  2. Local variable storage: storing the variables local to a function's scope and
     removing them once the function returns, avoiding
     [[Memory_leaks|memory leaks]]

  3. Control flow: record from where a function is called and where its return
     value should be stored, meaning the right value is always returned no matter
     how nested it is.

  4. Recursion: individuating each call of a function that calls itself and its
     given step to avoid infinite loops

  ## Related notes


path: zk/Heap_memory.md
sortable_path: zkHeap_memory.md
title: Heap_memory
lead: # Heap memory
checksum: e3c8291a7b7a967ad06d00498dc3ac2452b15cfac3719ddb91f8b867d740c0bb
created: 2024-04-20 12:14:59.272099511+00:00
modified: 2024-04-20 13:42:01.847818114+00:00
metadata: {"created":"Saturday, April 20, 2024","id":"18bl","tags":["memory","C"],"title":"Heap_memory"}
body:
  # Heap memory

  Along with [[Stack_memory|Stack memory]], programs make use of _heap memory_
  during runtime.

  Heap memory does not use a standardised data structure and can be accessed from
  any point within the program.

  Whereas stack memory with it's LIFO structure has memory management built-in
  when programs allocate memory from the heap they must manually deallocate it
  when it is no longer required. This process of "freeing memory" is known as
  _garbage collection_. In a language like C, this is the explicit concern of the
  programmer and is not abstracted away. Failure to properly manage garbage
  collection is what causes [[Memory_leaks]].

  Heap memory is used in combination with the stack since a given heap memory
  allocation address is stored as a stack variable during runtime. It points to
  the heap memory address whilst not being that memory itself.

  Here is an example of managing heap memory allocation in C:

  ```c
  void * data;
  data = malloc(512)
  ```

  The first line assigns a special _pointer_ variable (indicated by `void *`
  rather than `int` or `str`) . This is a variable only holds a memory address.
  The `malloc` method requests 512 bytes that it wants to assign to the `data`
  variable. It will return the address of the first byte in the newly allocated
  memory. `data` will then refer to the address on the stack that holds the
  address allocation on the heap.

  ## Related notes

raw_content:
  ---
  id: 18bl
  title: Heap_memory
  tags: [memory, C]
  created: Saturday, April 20, 2024
  ---

  # Heap memory

  Along with [[Stack_memory|Stack memory]], programs make use of _heap memory_
  during runtime.

  Heap memory does not use a standardised data structure and can be accessed from
  any point within the program.

  Whereas stack memory with it's LIFO structure has memory management built-in
  when programs allocate memory from the heap they must manually deallocate it
  when it is no longer required. This process of "freeing memory" is known as
  _garbage collection_. In a language like C, this is the explicit concern of the
  programmer and is not abstracted away. Failure to properly manage garbage
  collection is what causes [[Memory_leaks]].

  Heap memory is used in combination with the stack since a given heap memory
  allocation address is stored as a stack variable during runtime. It points to
  the heap memory address whilst not being that memory itself.

  Here is an example of managing heap memory allocation in C:

  ```c
  void * data;
  data = malloc(512)
  ```

  The first line assigns a special _pointer_ variable (indicated by `void *`
  rather than `int` or `str`) . This is a variable only holds a memory address.
  The `malloc` method requests 512 bytes that it wants to assign to the `data`
  variable. It will return the address of the first byte in the newly allocated
  memory. `data` will then refer to the address on the stack that holds the
  address allocation on the heap.

  ## Related notes


path: zk/Bitwise_operators.md
sortable_path: zkBitwise_operators.md
title: Bitwise_operators
lead: # Bitwise operators
checksum: de0c5734330e9f4351e0dd66dcc6527fe46b016e2ab7622f79349aa8723bcf57
created: 2024-04-20 13:49:57.582757199+00:00
modified: 2024-04-20 14:25:08.308261193+00:00
metadata: {"created":"Saturday, April 20, 2024","id":"t127","tags":["binary"],"title":"Bitwise_operators"}
body:
  # Bitwise operators

  In addition to mathematical, logical and comparison operators, there are
  _bitwise operators_. These operators execute conditions based on the actual bits
  of a value rather than the values that the bits are encoded to represent.

  Bitwise operators are typically represented with single characters of existing
  operators, e.g. `&` instead of `&&`:

  | Bitwise operation | Operator      |
  | ----------------- | ------------- |
  | AND               | `&`           |
  | OR                | (single pipe) |
  | NOT               | `~`           |

  An example of using the `&` operator:

  ```py
  x = 5
  y = 3
  a = x & y
  b = x | y
  ```

  The value of `a` will be 1. The reason is we are looking at the bit values of
  `x` and `y` and then applying Boolean AND to each bit:

  ```
  x = 5 = 0101
  y = 3 = 0011
  a = 1 = 0001
  ```

  Working from right to left for each column:

  - true and true = true
  - false and true = false
  - true and false = false
  - false and false = false

  This leaves us with 0001 which is equal to 1 in binary and denary.

  For the case of bitwise OR we get 7 as the result of `x | y`:

  ```
  x = 5 = 0101
  y = 3 = 0011
  b = 7 = 0111
  ```

  - true or true = true
  - false or true = true
  - true or false = true
  - false or false = true

  This leaves us with 0111 which is equal to 7 in denary.

  ## Why use them?

  Bitwise operations are more of a hallmark of lower-level programming. As bit
  operations are the fastest and lowest level of computation, being able to
  directly access and operate on bits within a programming language can be
  beneficial when efficiency and speed of execution is a factor or when memory is
  constrained.

  ## Related notes

raw_content:
  ---
  id: t127
  title: Bitwise_operators
  tags: [binary]
  created: Saturday, April 20, 2024
  ---

  # Bitwise operators

  In addition to mathematical, logical and comparison operators, there are
  _bitwise operators_. These operators execute conditions based on the actual bits
  of a value rather than the values that the bits are encoded to represent.

  Bitwise operators are typically represented with single characters of existing
  operators, e.g. `&` instead of `&&`:

  | Bitwise operation | Operator      |
  | ----------------- | ------------- |
  | AND               | `&`           |
  | OR                | (single pipe) |
  | NOT               | `~`           |

  An example of using the `&` operator:

  ```py
  x = 5
  y = 3
  a = x & y
  b = x | y
  ```

  The value of `a` will be 1. The reason is we are looking at the bit values of
  `x` and `y` and then applying Boolean AND to each bit:

  ```
  x = 5 = 0101
  y = 3 = 0011
  a = 1 = 0001
  ```

  Working from right to left for each column:

  - true and true = true
  - false and true = false
  - true and false = false
  - false and false = false

  This leaves us with 0001 which is equal to 1 in binary and denary.

  For the case of bitwise OR we get 7 as the result of `x | y`:

  ```
  x = 5 = 0101
  y = 3 = 0011
  b = 7 = 0111
  ```

  - true or true = true
  - false or true = true
  - true or false = true
  - false or false = true

  This leaves us with 0111 which is equal to 7 in denary.

  ## Why use them?

  Bitwise operations are more of a hallmark of lower-level programming. As bit
  operations are the fastest and lowest level of computation, being able to
  directly access and operate on bits within a programming language can be
  beneficial when efficiency and speed of execution is a factor or when memory is
  constrained.

  ## Related notes


path: zk/NodeJS_scripts.md
sortable_path: zkNodeJS_scripts.md
title: NodeJS_scripts
lead: # NodeJS_scripts
checksum: 7f543a01a82b789f7dbb6cdf76cb210ae0e4522439bd842a5694761bb0943872
created: 2024-04-26 09:50:22.490916912+00:00
modified: 2024-04-28 10:34:57.917110341+00:00
metadata: {"created":"Friday, April 26, 2024","id":5043,"tags":["node-js"],"title":"NodeJS_scripts"}
body:
  # NodeJS_scripts

  If we want to use NodeJS as a scripting language without going to the trouble of
  setting up an NPM-based development environment, we can use the following
  architecture.

  ```js
  #!/usr/bin/env node

  const process = require("process");

  const actualScript = (firstParam, secondParam) => {
    // Do something
  };

  if (require.main === module) {
    const [, param1, param2] = process.argv;
    actualScript(param1, param2).catch(console.error);
  }
  ```

  Basically we have a function that contains the main actions of the script. This
  is then invoked within `main` in the manner of a Bash or
  ![Python script](./Single_file_Python_scripts.md)

  This obviously requires the Node binary to be in your path and the script must
  be run with executable privileges.

  If you use any module not in the Node core, you will need to have this installed
  globally for it to work. (There are
  [ways around this](https://thom4.net/2014/self-contained-node-scripts/) for Node
  scripts you publish but for personal scripts this doesn't matter.)

  ## `require.main`

  This construction checks that the script is being directy invoked via the 'node'
  command in the terminal rather than called by another module.

  This is equivalent to the [following construct]() in Python:

  ```py
  if __name__ == "__main__":
  ```

raw_content:
  ---
  id: 5043
  title: NodeJS_scripts
  tags: [node-js]
  created: Friday, April 26, 2024
  ---

  # NodeJS_scripts

  If we want to use NodeJS as a scripting language without going to the trouble of
  setting up an NPM-based development environment, we can use the following
  architecture.

  ```js
  #!/usr/bin/env node

  const process = require("process");

  const actualScript = (firstParam, secondParam) => {
    // Do something
  };

  if (require.main === module) {
    const [, param1, param2] = process.argv;
    actualScript(param1, param2).catch(console.error);
  }
  ```

  Basically we have a function that contains the main actions of the script. This
  is then invoked within `main` in the manner of a Bash or
  ![Python script](./Single_file_Python_scripts.md)

  This obviously requires the Node binary to be in your path and the script must
  be run with executable privileges.

  If you use any module not in the Node core, you will need to have this installed
  globally for it to work. (There are
  [ways around this](https://thom4.net/2014/self-contained-node-scripts/) for Node
  scripts you publish but for personal scripts this doesn't matter.)

  ## `require.main`

  This construction checks that the script is being directy invoked via the 'node'
  command in the terminal rather than called by another module.

  This is equivalent to the [following construct]() in Python:

  ```py
  if __name__ == "__main__":
  ```


path: zk/Single_file_Python_scripts.md
sortable_path: zkSingle_file_Python_scripts.md
title: Single_file_Python_scripts
lead: # Single_file_Python_scripts
checksum: 6145bde1593264784876cfb088585d22a6aaaf70fadb477df93bec755ee419a3
created: 2024-04-26 09:58:57.238585351+00:00
modified: 2024-04-28 10:51:45.411832459+00:00
metadata: {"created":"Friday, April 26, 2024","id":"atzw","tags":[],"title":"Single_file_Python_scripts"}
body:
  # Single_file_Python_scripts

  ## Basic architecture

  ```py
  #! /usr/local/bin/python3

  import sys

  if __name__ == "__main__":
      all_args = sys.argv
      specific_arg = sys.argv[0]
      # Main functionality...
  ```

  When you run a script (module) Python assigns the string `__main__` to the
  `__name__` attribute to the script that is being executed.

  If you run the script as an import into another script, the `__name__` attribute
  of the imported module is set to the module name, not `__main__`.

  Everything can go under the `__main__` conditional, or, for better readability,
  you can define a `main` function that is then invoked, e.g:

  ```py

  def main():
      # Do some stuff


  if __name__ == "__main__":
      main()

  ```

  ## Related notes

  ![Python modules and imports](./Python_modules_and_imports.md)

raw_content:
  ---
  id: atzw
  title: Single_file_Python_scripts
  tags: []
  created: Friday, April 26, 2024
  ---

  # Single_file_Python_scripts

  ## Basic architecture

  ```py
  #! /usr/local/bin/python3

  import sys

  if __name__ == "__main__":
      all_args = sys.argv
      specific_arg = sys.argv[0]
      # Main functionality...
  ```

  When you run a script (module) Python assigns the string `__main__` to the
  `__name__` attribute to the script that is being executed.

  If you run the script as an import into another script, the `__name__` attribute
  of the imported module is set to the module name, not `__main__`.

  Everything can go under the `__main__` conditional, or, for better readability,
  you can define a `main` function that is then invoked, e.g:

  ```py

  def main():
      # Do some stuff


  if __name__ == "__main__":
      main()

  ```

  ## Related notes

  ![Python modules and imports](./Python_modules_and_imports.md)


path: zk/Working_with_CSVs_in_Python.md
sortable_path: zkWorking_with_CSVs_in_Python.md
title: Working_with_CSVs_in_Python
lead: # Working_with_CSVs_in_Python
checksum: 9dd9c338f63f3d56c19fa7e30cc88d5665e984f99de28190efbe6f02e5dac060
created: 2024-04-28 11:17:03.295837625+00:00
modified: 2024-04-28 16:09:21.572769207+00:00
body:
  # Working_with_CSVs_in_Python

  ## Core package

  ```py
  import csv
  ```

  ## Read and write to CSV

  ### Read

  Use standard Pythonic "read" syntax:

  ```py
  with open('./path.csv', mode="r") as csv_file:
      reader = csv.reader(csv_file)
  ```

  ### Parse values

  Having created the `reader` object, you can then loop through this as an
  iterable:

  ```py
  for row in reader:
      print(row)
  ```

  Will return something like:

  ```csv
  column something, column something else, ...
  ```

  Which we can individuate with:

  ```py
  print(row[0])
  # column something
  ```

  We can also parse the rows as a dictionary for easier individuation. We do this
  by creating a `DictReader` rather than the default `reader`:

  ```py
  ...
  dict_reader = csv.DictReader(csv_file)
  ```

  Now we can use the header row values to individuate particular columns.

  Say we have a CSV with the following headers:

  ```csv
  name, profession
  ```

  We can individuate thus:

  ```py
  for row in dict_reader
      name = row["name"]
  ```

  ### Write

  Use standard Pythonic "read" syntax:

  ```py
  with open('./new_csv_file.csv', mode="w") as csv_file:
      writer = csv.writer(csv_file)
  ```

  The above will create the file as well, it doesn't need to be pre-existing.

  This creates the writer object. To actually write a row:

  ```py
  some_list = ['thomas', 'engineer']
  for element in some_list:
      writer.writerow(element)
  ```

raw_content:
  ---
  id: sgtn
  title: Working_with_CSVs_in_Python
  tags: [python, csv]
  created: Sunday, April 28, 2024
  ---

  # Working_with_CSVs_in_Python

  ## Core package

  ```py
  import csv
  ```

  ## Read and write to CSV

  ### Read

  Use standard Pythonic "read" syntax:

  ```py
  with open('./path.csv', mode="r") as csv_file:
      reader = csv.reader(csv_file)
  ```

  ### Parse values

  Having created the `reader` object, you can then loop through this as an
  iterable:

  ```py
  for row in reader:
      print(row)
  ```

  Will return something like:

  ```csv
  column something, column something else, ...
  ```

  Which we can individuate with:

  ```py
  print(row[0])
  # column something
  ```

  We can also parse the rows as a dictionary for easier individuation. We do this
  by creating a `DictReader` rather than the default `reader`:

  ```py
  ...
  dict_reader = csv.DictReader(csv_file)
  ```

  Now we can use the header row values to individuate particular columns.

  Say we have a CSV with the following headers:

  ```csv
  name, profession
  ```

  We can individuate thus:

  ```py
  for row in dict_reader
      name = row["name"]
  ```

  ### Write

  Use standard Pythonic "read" syntax:

  ```py
  with open('./new_csv_file.csv', mode="w") as csv_file:
      writer = csv.writer(csv_file)
  ```

  The above will create the file as well, it doesn't need to be pre-existing.

  This creates the writer object. To actually write a row:

  ```py
  some_list = ['thomas', 'engineer']
  for element in some_list:
      writer.writerow(element)
  ```

metadata:
  {"created":"Sunday, April 28, 2024","id":"sgtn","tags":["python","csv"],"title":"Working_with_CSVs_in_Python"}


path: zk/Time_and_computers.md
sortable_path: zkTime_and_computers.md
title: Time_and_computers
lead: # Time and computers
checksum: 87767416ea6b2c08381c74cdda962a1553cc48b405f37fbaa2ef1fc722745479
created: 2024-04-28 16:09:38.744041761+00:00
modified: 2024-06-15 10:06:32.764361797+00:00
body:
  # Time and computers

  ## Epochs and Unix Time

  An epoch is a period of time identified by a starting point.

  The standard epoch for measuring against is known colloqially as "Unix Time":
  midnight on 1st January 1970. Unix time is the number of seconds that have
  elapsed since this point. This is the standard for Windows and most unix
  systems.

  For example midnight on 2nd January 1970 would be 86400 seconds since the epoch.
  The deduction is as follows:

  ```
  60 * 60 * 24 = 86400
  ```

  We are multiplpying the number of seconds in a minute by the number of minutes
  in an hour by the number of hours in a day.

  To represent times and dates before 1970, we use negative integers.

  ## UTC and time zones

  "Coordinated Universal Time" (UTC) is the time standard against which the
  world's timekeeping is synchronized. It is not itself a timezone, it is a
  transcendent standard that defines what time zones are.

  Time zones are defined relative to UTC, by their offset to this value.

  For example North America is behind UTC by five or six hours hence uses the
  notation UTC-5:00 or UTC-6:00.

  UTC is the successor to GMT of which there were variances necessitating a
  universal value. Despite this GMT is equal to UTC+00:00 so apart from when
  daylight saving is in effect in the UK, GMT = UTC.

  Depending on your locale, relative to UTC, you will get different outputs when
  you attempt to log Unix Time.

  Also daylight savings can impact on the outputs. For instance for half of the
  year, when DST applies, the UK is UTC+01:00.

  ## Examples in Python

  The following gives us the unix seconds in my current time zone (GMT DST):

  ```py
  import time
  print(time.time())
  # 1714322393.2929392
  ```

  If I want UTC, I can do:

  ```py
  import datetime
  print(datetime.datetime.utcnow().strftime('%s'))
  # '1714318952'
  ```

  We see clearly that they are not identical.

raw_content:
  ---
  id: l29u
  title: Time_and_computers
  tags:
    - unix
    - time
  created: Sunday, April 28, 2024
  ---

  # Time and computers

  ## Epochs and Unix Time

  An epoch is a period of time identified by a starting point.

  The standard epoch for measuring against is known colloqially as "Unix Time":
  midnight on 1st January 1970. Unix time is the number of seconds that have
  elapsed since this point. This is the standard for Windows and most unix
  systems.

  For example midnight on 2nd January 1970 would be 86400 seconds since the epoch.
  The deduction is as follows:

  ```
  60 * 60 * 24 = 86400
  ```

  We are multiplpying the number of seconds in a minute by the number of minutes
  in an hour by the number of hours in a day.

  To represent times and dates before 1970, we use negative integers.

  ## UTC and time zones

  "Coordinated Universal Time" (UTC) is the time standard against which the
  world's timekeeping is synchronized. It is not itself a timezone, it is a
  transcendent standard that defines what time zones are.

  Time zones are defined relative to UTC, by their offset to this value.

  For example North America is behind UTC by five or six hours hence uses the
  notation UTC-5:00 or UTC-6:00.

  UTC is the successor to GMT of which there were variances necessitating a
  universal value. Despite this GMT is equal to UTC+00:00 so apart from when
  daylight saving is in effect in the UK, GMT = UTC.

  Depending on your locale, relative to UTC, you will get different outputs when
  you attempt to log Unix Time.

  Also daylight savings can impact on the outputs. For instance for half of the
  year, when DST applies, the UK is UTC+01:00.

  ## Examples in Python

  The following gives us the unix seconds in my current time zone (GMT DST):

  ```py
  import time
  print(time.time())
  # 1714322393.2929392
  ```

  If I want UTC, I can do:

  ```py
  import datetime
  print(datetime.datetime.utcnow().strftime('%s'))
  # '1714318952'
  ```

  We see clearly that they are not identical.

metadata:
  {"created":"Sunday, April 28, 2024","id":"l29u","tags":["unix","time"],"title":"Time_and_computers"}


path: zk/Invoking_the_shell_in_Python.md
sortable_path: zkInvoking_the_shell_in_Python.md
title: Invoking_the_shell_in_Python
lead: # Invoking the shell_in_Python
checksum: d4c82bc097aa7ad47db3bcbb91b7b5b84643617f9be062aa61c1a5064031cd43
created: 2024-04-29 16:47:43.359796333+00:00
modified: 2024-04-29 16:50:47.361758113+00:00
body:
  # Invoking the shell_in_Python

  ```py
  import subprocess

  try:
      process = subprocess.run(
          ["ls", "-la"],
          shell=True,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
      )
      return json.loads(process.stdout)

  except subprocess.CalledProcessError as e:
          return e.stderr.strip()
  ```

raw_content:
  ---
  id: cfr4
  title: Invoking_the_shell_in_Python
  tags: [python, shell]
  created: Monday, April 29, 2024
  ---

  # Invoking the shell_in_Python

  ```py
  import subprocess

  try:
      process = subprocess.run(
          ["ls", "-la"],
          shell=True,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
      )
      return json.loads(process.stdout)

  except subprocess.CalledProcessError as e:
          return e.stderr.strip()
  ```

metadata:
  {"created":"Monday, April 29, 2024","id":"cfr4","tags":["python","shell"],"title":"Invoking_the_shell_in_Python"}


path: zk/Linux_disk_partitions.md
sortable_path: zkLinux_disk_partitions.md
title: Linux disk partitions
checksum: d7baa86ca87df009a142bb4abedcd8c958483edf2cbe88de0544eb50cbe41a11
created: 2024-05-06 12:16:39.739170407+00:00
modified: 2024-05-06 13:16:38.848343997+00:00
metadata: {"tags":["disks"]}
lead:
  A disk is divided up into partitions which are subsections of the overall disk.
  The kernel presents each partition as a [block device](Devices.md#Devices) as it
  would with an entire disk.

body:
  A disk is divided up into partitions which are subsections of the overall disk.
  The kernel presents each partition as a [block device](Devices.md#Devices) as it
  would with an entire disk.

  The disk dedicates a small part of its contents to a **partition table**: this
  defines the different partitions that comprise the total disk space.

  ## Viewing current partitions

  Whenever you install a Linux distribution on a real or virtual machine, you must
  partition the drive. There are three main tools to choose from: `parted`,
  `g(raphical)parted`, `fdisk`.

  For a top-level overview of your disks and their main partitions you can run
  `lsblk` (_list block devices_):

  ```bash
  $ lsblk

  NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
  sda           8:0    0 465.7G  0 disk
  ├─sda1        8:1    0   200M  0 part
  └─sda2        8:2    0 465.4G  0 part /run/media/thomas/ganesh
  nvme0n1     259:0    0 476.9G  0 disk
  ├─nvme0n1p1 259:1    0   488M  0 part
  ├─nvme0n1p2 259:2    0  27.9G  0 part /
  └─nvme0n1p3 259:3    0 448.5G  0 part /home
  ```

  We can use `parted -l` to view the partition table for the current machine:

  ```bash
  Model: SKHynix_HFS512GDE9X081N (nvme)
  Disk /dev/nvme0n1: 512GB
  Sector size (logical/physical): 512B/512B
  Partition Table: gpt
  Disk Flags:

  Number  Start   End     Size    File system  Name  Flags
   1      1049kB  513MB   512MB   fat32              boot, esp
   2      513MB   30.5GB  30.0GB  ext4
   3      30.5GB  512GB   482GB   ext4
  ```

  We can use `fdisk -l` to get slightly more info:

  ```bash
  disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
  Disk model: SKHynix_HFS512GDE9X081N
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 08175E77-CB9F-C34A-9032-DF29A3F8F0FE

  Device            Start        End   Sectors   Size Type
  /dev/nvme0n1p1     2048    1001471    999424   488M EFI System
  /dev/nvme0n1p2  1001472   59594751  58593280  27.9G Linux filesystem
  /dev/nvme0n1p3 59594752 1000214527 940619776 448.5G Linux filesystem
  ```

  ## Standard Linux partitions

  The two tools disclose that the main harddrive is `/dev/nvme0n1` (equivalent to
  `sda` on older machines running Linux) and it has the standard three partitions:

  - Boot partition (`/dev/nvme0n1p1`)
  - Root dir (`/dev/nvme0n1p2`)
  - Home dir (`/dev/nvme0n1p3`)

  ### Boot partition

  - Takes up the smallest amount of space
  - Contains the Linux kernel and the bootloader (GRUB, usually) and any other
    files required for booting.
  - Once the BIOS has initialized the hardware components it hands control to the
    bootloader stored in this partition.
  - The bootloader loads the kernel contained in its partition, into memory

  ### Root partition (`/`)

  - This houses everything to do with the operation of the operating system apart
    from the initial booting process. Once the kernel is in memory it accesses the
    resources in the root partition to get the OS up and running. These resources
    include system libraries, utilities, system-wide configs and application
    software.

  - The root partition is vital for the system's functionality. Without it, the
    system cannot boot or operate. It needs to be  
     large enough to accommodate the operating system, installed programs, and system
    libraries, but it doesn't need to store user data or  
     additional applications, which can be located on other partitions.

  - This is the domain of the [superuser](User_Space.md#root-user-superuser). The
    part of the filesystem that you need sudo priveleges to access and where you
    manage users

  ### Home partition

  - This is presented as `/home/`, i.e. that it is a descendent of `/`. But this
    is because the Linux file system is hierarchical. Typically `/home/` is
    usually its own partition.
  - Separating user data and system data in this way protects user data during
    upgrades or reinstallation. Reinstalling or upgrading the OS can be done
    without affecting these files.
  - Each user has their own directory off of `/home`, e.g `/home/thomas`.
  - While the root partition (`/`) is essential for the operation of the OS,
    placing `/home` on a separate partition allows for better management of user
    data, enhancing the system's flexibility and the user's control over their own
    files, without compromising the integrity or performance of the OS. This
    approach exemplifies the Linux philosophy of giving the user control while
    maintaining a clear distinction between system  
    resources and user data.

  ## Types of partition table

  In the Linux world there are two main types: MBR and GPT. The type of table used
  determines how the OS boots. So although partition tables are also responsible
  for the partitioning of non-bootable sectors of a disk, **they are distinguished
  by the boot system they implement**. If we look at the output from `parted` and
  `fdisk` above we see that the harddrive uses the GPT partition type.

  #### Primary, extended and logical partitions

  Most standard partition tables allow for primary, extended and logical
  partitions. The primary partition is the part of the harddisk that contains the
  operating system and is thus described as 'bootable' and may be called the 'boot
  partition'. During the bootstrapping process this is injected into memory as the
  [kernel](The_Kernel.md).

  The extended partition is basically everything other than the primary partition.
  This is typically subdivided into other partitions that are called _logical_
  partitions. This is because they physically reside in the same sector of the
  disk (the extended partition) but are treated as virtual and independent disks.

  In our example above:

  - `/dev/nvme0n1p1` is the primary/boot partition
  - `/dev/nvme0n1p2` and `/dev/nvme0n1p3` comprise the extended partition and by
    themselves are each logical partitions.

  <dl>
    <dt>MBR</dt>
    <dd> 
      <ul>
        <li>Stands for Master Boot Record</li> 
        <li>Uses BIOS in the boot process</li> 
        <li>Can only works with disks up to 2TB in size</li> 
        <li>Only supports 4 primary partitions. This means the number of operating systems you install is limitied to this number.</li> 
        <li>This is the first 512 bytes of a storage device, preceding the first partition.</li>
      </ul> 
    </dd>
    <dt>GPT</dt>
    <dd>
    <ul>
        <li>Stands for GUID Partition Table </li>
        <li>Gradually replacing MBR</li>
        <li>Uses UEFI instead of BIOS</li>
        <li>As name indicates, every partition on disk has its own globally-unique identifier</li>
        <li>Vastly more partitions available than with MBR (dependent on operating system)</li>
        <li>Offers greater recovery options and anti-corruption safeguards</li> 
      </ul>
  </dd>
  </dl>

  ## Related notes

  ![Creating a Linux partition table](./Creating_a_Linux_partition_table.md)

raw_content:
  ---
  tags:
    - disks
  ---

  # Linux disk partitions

  A disk is divided up into partitions which are subsections of the overall disk.
  The kernel presents each partition as a [block device](Devices.md#Devices) as it
  would with an entire disk.

  The disk dedicates a small part of its contents to a **partition table**: this
  defines the different partitions that comprise the total disk space.

  ## Viewing current partitions

  Whenever you install a Linux distribution on a real or virtual machine, you must
  partition the drive. There are three main tools to choose from: `parted`,
  `g(raphical)parted`, `fdisk`.

  For a top-level overview of your disks and their main partitions you can run
  `lsblk` (_list block devices_):

  ```bash
  $ lsblk

  NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
  sda           8:0    0 465.7G  0 disk
  ├─sda1        8:1    0   200M  0 part
  └─sda2        8:2    0 465.4G  0 part /run/media/thomas/ganesh
  nvme0n1     259:0    0 476.9G  0 disk
  ├─nvme0n1p1 259:1    0   488M  0 part
  ├─nvme0n1p2 259:2    0  27.9G  0 part /
  └─nvme0n1p3 259:3    0 448.5G  0 part /home
  ```

  We can use `parted -l` to view the partition table for the current machine:

  ```bash
  Model: SKHynix_HFS512GDE9X081N (nvme)
  Disk /dev/nvme0n1: 512GB
  Sector size (logical/physical): 512B/512B
  Partition Table: gpt
  Disk Flags:

  Number  Start   End     Size    File system  Name  Flags
   1      1049kB  513MB   512MB   fat32              boot, esp
   2      513MB   30.5GB  30.0GB  ext4
   3      30.5GB  512GB   482GB   ext4
  ```

  We can use `fdisk -l` to get slightly more info:

  ```bash
  disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
  Disk model: SKHynix_HFS512GDE9X081N
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 08175E77-CB9F-C34A-9032-DF29A3F8F0FE

  Device            Start        End   Sectors   Size Type
  /dev/nvme0n1p1     2048    1001471    999424   488M EFI System
  /dev/nvme0n1p2  1001472   59594751  58593280  27.9G Linux filesystem
  /dev/nvme0n1p3 59594752 1000214527 940619776 448.5G Linux filesystem
  ```

  ## Standard Linux partitions

  The two tools disclose that the main harddrive is `/dev/nvme0n1` (equivalent to
  `sda` on older machines running Linux) and it has the standard three partitions:

  - Boot partition (`/dev/nvme0n1p1`)
  - Root dir (`/dev/nvme0n1p2`)
  - Home dir (`/dev/nvme0n1p3`)

  ### Boot partition

  - Takes up the smallest amount of space
  - Contains the Linux kernel and the bootloader (GRUB, usually) and any other
    files required for booting.
  - Once the BIOS has initialized the hardware components it hands control to the
    bootloader stored in this partition.
  - The bootloader loads the kernel contained in its partition, into memory

  ### Root partition (`/`)

  - This houses everything to do with the operation of the operating system apart
    from the initial booting process. Once the kernel is in memory it accesses the
    resources in the root partition to get the OS up and running. These resources
    include system libraries, utilities, system-wide configs and application
    software.

  - The root partition is vital for the system's functionality. Without it, the
    system cannot boot or operate. It needs to be  
     large enough to accommodate the operating system, installed programs, and system
    libraries, but it doesn't need to store user data or  
     additional applications, which can be located on other partitions.

  - This is the domain of the [superuser](User_Space.md#root-user-superuser). The
    part of the filesystem that you need sudo priveleges to access and where you
    manage users

  ### Home partition

  - This is presented as `/home/`, i.e. that it is a descendent of `/`. But this
    is because the Linux file system is hierarchical. Typically `/home/` is
    usually its own partition.
  - Separating user data and system data in this way protects user data during
    upgrades or reinstallation. Reinstalling or upgrading the OS can be done
    without affecting these files.
  - Each user has their own directory off of `/home`, e.g `/home/thomas`.
  - While the root partition (`/`) is essential for the operation of the OS,
    placing `/home` on a separate partition allows for better management of user
    data, enhancing the system's flexibility and the user's control over their own
    files, without compromising the integrity or performance of the OS. This
    approach exemplifies the Linux philosophy of giving the user control while
    maintaining a clear distinction between system  
    resources and user data.

  ## Types of partition table

  In the Linux world there are two main types: MBR and GPT. The type of table used
  determines how the OS boots. So although partition tables are also responsible
  for the partitioning of non-bootable sectors of a disk, **they are distinguished
  by the boot system they implement**. If we look at the output from `parted` and
  `fdisk` above we see that the harddrive uses the GPT partition type.

  #### Primary, extended and logical partitions

  Most standard partition tables allow for primary, extended and logical
  partitions. The primary partition is the part of the harddisk that contains the
  operating system and is thus described as 'bootable' and may be called the 'boot
  partition'. During the bootstrapping process this is injected into memory as the
  [kernel](The_Kernel.md).

  The extended partition is basically everything other than the primary partition.
  This is typically subdivided into other partitions that are called _logical_
  partitions. This is because they physically reside in the same sector of the
  disk (the extended partition) but are treated as virtual and independent disks.

  In our example above:

  - `/dev/nvme0n1p1` is the primary/boot partition
  - `/dev/nvme0n1p2` and `/dev/nvme0n1p3` comprise the extended partition and by
    themselves are each logical partitions.

  <dl>
    <dt>MBR</dt>
    <dd> 
      <ul>
        <li>Stands for Master Boot Record</li> 
        <li>Uses BIOS in the boot process</li> 
        <li>Can only works with disks up to 2TB in size</li> 
        <li>Only supports 4 primary partitions. This means the number of operating systems you install is limitied to this number.</li> 
        <li>This is the first 512 bytes of a storage device, preceding the first partition.</li>
      </ul> 
    </dd>
    <dt>GPT</dt>
    <dd>
    <ul>
        <li>Stands for GUID Partition Table </li>
        <li>Gradually replacing MBR</li>
        <li>Uses UEFI instead of BIOS</li>
        <li>As name indicates, every partition on disk has its own globally-unique identifier</li>
        <li>Vastly more partitions available than with MBR (dependent on operating system)</li>
        <li>Offers greater recovery options and anti-corruption safeguards</li> 
      </ul>
  </dd>
  </dl>

  ## Related notes

  ![Creating a Linux partition table](./Creating_a_Linux_partition_table.md)


path: zk/Creating_a_Linux_partition_table.md
sortable_path: zkCreating_a_Linux_partition_table.md
title: Creating_a_Linux_partition_table
lead: ## Creating a partition table
checksum: 81594b70c67dcb48f219bad83218bbc3c34ae53548dae081c5b9fab5ef72b461
created: 2024-05-06 12:29:42.564542506+00:00
modified: 2024-06-16 16:59:33.587399238+00:00
body:
  ## Creating a partition table

  To demonstrate the process of partitioning a harddrive I am going to repartition
  an external SATA drive as if it were being primed for a fresh Linux install.

  Let's take a look at the disk in its current form:

  ```
  $ fdisk -l

    Disk /dev/sda: 465.74 GiB, 500079525888 bytes, 976717824 sectors
    Disk model: My Passport 071Aumount /dev/sda2
    Disklabel type: gpt
    Disk identifier: 9993F1BB-626C-485F-8542-3CC73BB40953

    Device      Start       End   Sectors   Size Type
    /dev/sda1      40    409639    409600   200M EFI System
    /dev/sda2  409640 976455639 976046000 465.4G Apple HFS/HFS+
  ```

  (This disk was previously used as a backup disk for MacOS so in addition to the
  extended partition which has a proprietary file system type (Apple HFS) it has a
  primary partition which would load the recovery OS. In contrast to my main
  harddrive this uses the standard SCSI prototcol and thus the partitions are
  prepended with `sda`.)

  #### 1. Unmount existing partitions

  ```bash
  umount /dev/sda1
  umount /dev/sda2
  ```

  #### 2. Deleting the existing partitions

  ```bash
  # Load the disk into fdisk
  $ sudo fdisk /dev/sda
  Command (m for help): d
  Partition number (1,2, default 2): 1
  Partition 1 has been deleted.
  Command (m for help): d
  Selected partition 2
  Partition 2 has been deleted.

  # Verify deletion with p(rint) command
  Command (m for help): p
  Disk /dev/sda: 465.74 GiB, 500079525888 bytes, 976717824 sectors
  Disk model: My Passport 071A
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 9993F1BB-626C-485F-8542-3CC73BB40953

  # Write the changes to disk with w(rite)
  w

  # Check execution:
  $ lsblk

  NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
  sda           8:0    0 465.7G  0 disk
  ```

  So now the disk has no partitions, just the physical disk space and no
  mountpoints. We are ready to re-partition.

  #### 3. Re-partition the disk

  We are going to create a GPT partition table

  ```bash
  $ sudo fdisk /dev/sda
  Command (m for help): g
  Created a new GPT disklabel (GUID: E316B1A9-6F73-FB41-8CBB-FA4E3C448F2B).

  Command (m for help): n
  Partition number (1-128, default 1):
  First sector (2048-976717790, default 2048):

  # Just press enter here

  # The last sector is where we put the actual size we want for the partition
  Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-976717790, default 976715775): +100G
  Created a new partition 1 of type 'Linux filesystem' and of size 100 GiB.

  # For the second and final partition, we follow the same steps but don't add a partition size, since it will default to what is left.

  Command (m for help): n
  Partition number (2-128, default 2):
  First sector (209717248-976717790, default 209717248):
  Last sector, +/-sectors or +/-size{K,M,G,T,P} (209717248-976717790, default 976715775):

  Created a new partition 2 of type 'Linux filesystem' and of size 365.7 GiB.

  # Check the partitions with p(rint)
  Command (m for help): p
  Disk /dev/sda: 465.74 GiB, 500079525888 bytes, 976717824 sectors
  Disk model: My Passport 071A
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 92739978-F7D2-5042-9758-C1429B5C8E11

  Device         Start       End   Sectors   Size Type
  /dev/sda1       2048 209717247 209715200   100G Linux filesystem
  /dev/sda2  209717248 976715775 766998528 365.7G Linux filesystem

  # Then write with w
  Command (m for help): w
  The partition table has been altered.
  Calling ioctl() to re-read partition table.
  Syncing disks.
  ```

  We can now confirm our new partitions:

  ```bash
  $ lsblk
  NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
  sda           8:0    0 465.7G  0 disk
  ├─sda1        8:1    0   100G  0 part
  └─sda2        8:2    0 365.7G  0 part
  ```

  #### Naming a partition

  By default each partition will have its GUID as its name, but you can add a
  human friendly name with:

  ```bash
  sudo e2label /dev/sda1 my_human_name
  ```

  > Whilst we have created our partitions we cannot yet mount them. This is
  > because we have not yet set up a filesystem on the partitions. This is the
  > next step.

  ## Related notes

  ![Linux disk partitions](./Linux_disk_partitions.md)

raw_content:
  ---
  id: h7f8
  title: Creating_a_Linux_partition_table
  tags:
    - disks
    - Linux
  created: Monday, May 06, 2024
  ---

  ## Creating a partition table

  To demonstrate the process of partitioning a harddrive I am going to repartition
  an external SATA drive as if it were being primed for a fresh Linux install.

  Let's take a look at the disk in its current form:

  ```
  $ fdisk -l

    Disk /dev/sda: 465.74 GiB, 500079525888 bytes, 976717824 sectors
    Disk model: My Passport 071Aumount /dev/sda2
    Disklabel type: gpt
    Disk identifier: 9993F1BB-626C-485F-8542-3CC73BB40953

    Device      Start       End   Sectors   Size Type
    /dev/sda1      40    409639    409600   200M EFI System
    /dev/sda2  409640 976455639 976046000 465.4G Apple HFS/HFS+
  ```

  (This disk was previously used as a backup disk for MacOS so in addition to the
  extended partition which has a proprietary file system type (Apple HFS) it has a
  primary partition which would load the recovery OS. In contrast to my main
  harddrive this uses the standard SCSI prototcol and thus the partitions are
  prepended with `sda`.)

  #### 1. Unmount existing partitions

  ```bash
  umount /dev/sda1
  umount /dev/sda2
  ```

  #### 2. Deleting the existing partitions

  ```bash
  # Load the disk into fdisk
  $ sudo fdisk /dev/sda
  Command (m for help): d
  Partition number (1,2, default 2): 1
  Partition 1 has been deleted.
  Command (m for help): d
  Selected partition 2
  Partition 2 has been deleted.

  # Verify deletion with p(rint) command
  Command (m for help): p
  Disk /dev/sda: 465.74 GiB, 500079525888 bytes, 976717824 sectors
  Disk model: My Passport 071A
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 9993F1BB-626C-485F-8542-3CC73BB40953

  # Write the changes to disk with w(rite)
  w

  # Check execution:
  $ lsblk

  NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
  sda           8:0    0 465.7G  0 disk
  ```

  So now the disk has no partitions, just the physical disk space and no
  mountpoints. We are ready to re-partition.

  #### 3. Re-partition the disk

  We are going to create a GPT partition table

  ```bash
  $ sudo fdisk /dev/sda
  Command (m for help): g
  Created a new GPT disklabel (GUID: E316B1A9-6F73-FB41-8CBB-FA4E3C448F2B).

  Command (m for help): n
  Partition number (1-128, default 1):
  First sector (2048-976717790, default 2048):

  # Just press enter here

  # The last sector is where we put the actual size we want for the partition
  Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-976717790, default 976715775): +100G
  Created a new partition 1 of type 'Linux filesystem' and of size 100 GiB.

  # For the second and final partition, we follow the same steps but don't add a partition size, since it will default to what is left.

  Command (m for help): n
  Partition number (2-128, default 2):
  First sector (209717248-976717790, default 209717248):
  Last sector, +/-sectors or +/-size{K,M,G,T,P} (209717248-976717790, default 976715775):

  Created a new partition 2 of type 'Linux filesystem' and of size 365.7 GiB.

  # Check the partitions with p(rint)
  Command (m for help): p
  Disk /dev/sda: 465.74 GiB, 500079525888 bytes, 976717824 sectors
  Disk model: My Passport 071A
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 92739978-F7D2-5042-9758-C1429B5C8E11

  Device         Start       End   Sectors   Size Type
  /dev/sda1       2048 209717247 209715200   100G Linux filesystem
  /dev/sda2  209717248 976715775 766998528 365.7G Linux filesystem

  # Then write with w
  Command (m for help): w
  The partition table has been altered.
  Calling ioctl() to re-read partition table.
  Syncing disks.
  ```

  We can now confirm our new partitions:

  ```bash
  $ lsblk
  NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
  sda           8:0    0 465.7G  0 disk
  ├─sda1        8:1    0   100G  0 part
  └─sda2        8:2    0 365.7G  0 part
  ```

  #### Naming a partition

  By default each partition will have its GUID as its name, but you can add a
  human friendly name with:

  ```bash
  sudo e2label /dev/sda1 my_human_name
  ```

  > Whilst we have created our partitions we cannot yet mount them. This is
  > because we have not yet set up a filesystem on the partitions. This is the
  > next step.

  ## Related notes

  ![Linux disk partitions](./Linux_disk_partitions.md)

metadata:
  {"created":"Monday, May 06, 2024","id":"h7f8","tags":["disks","Linux"],"title":"Creating_a_Linux_partition_table"}


path: zk/DynamoDB.md
sortable_path: zkDynamoDB.md
title: DynamoDB
lead: # DynamoDB
checksum: dd37e5cdd877dd5fad9cec869426f36bc4cd86842da2a435ed693134d28c2496
created: 2024-06-09 16:24:31.762182012+00:00
modified: 2024-06-15 09:47:47.456782021+00:00
body:
  # DynamoDB

  ## Data structure

  ### Non-relational tables

  DynamoDB is "NoSQL" because it does not support #SQL queries and is
  non-relational meaning there cannot be JOIN operations via
  [foreign_keys](Foreign_keys_in_SQL.md)

  ![](../img/dynamodb.png)

  ### Primary key

  Although the data is stored as a table, one of the attributes is a primary key
  and the rest of the attributes are effectively the "value" associated with it.

  Because DynamoDB is schemaless, neither the attributes or their data types need
  to be defined beforehand and each item can have its own distinct attributes.

  Each item in the table is uniquely identifiable by its primary key.

  There are two types of primary key available:

  - partition key: a simple primary key composed of one attribute only. Because
    the primary key is hash-mapped items can be retrieved very rapidly using the
    primary key. This would be the `personId` alone.

  - composite key: this comprises a partition key and a _sort key_ both of which
    are attributes. In a table that has a partition key and a sort key, it's
    possible for multiple items to have the same partition key value. However,
    those items must have different sort key values. You could then query by
    either key or both. For instance using the `personId` along with `LastName`

  ### Secondary index

  As well as the index provided by the primary key, you can set one or more
  **secondary indices**. A secondary index lets you query the data in the table
  using an alternate key.

  A **global secondary index** is useful for querying data that needs to be
  accessed using non-primary key attributes. For example, if you have a Users
  table with `UserID` as the primary key but often need to fetch users by their
  `Email`, a GSI on `Email` would be appropriate.

  There are also **local secondary indices** but I don't understand the
  difference.

  ## Real example

  Below is a specification of the DynamoDB table I am using for my time-entries
  project:

  ```json
  {
    "TableName": "TimeEntries",
    "AttributeDefinitions": [
      {
        "AttributeName": "activity_start_end",
        "AttributeType": "S"
      },
      {
        "AttributeName": "start",
        "AttributeType": "S"
      },
      {
        "AttributeName": "activity_type",
        "AttributeType": "S"
      }
    ],
    "KeySchema": [
      {
        "AttributeName": "activity_start_end",
        "KeyType": "HASH"
      }
    ],
    "GlobalSecondaryIndexes": [
      {
        "IndexName": "StartIndex",
        "KeySchema": [
          {
            "AttributeName": "start",
            "KeyType": "HASH"
          },
          {
            "AttributeName": "activity_type",
            "KeyType": "RANGE"
          }
        ],
        "Projection": {
          "ProjectionType": "ALL"
        }
      }
    ]
  }
  ```

  This defines the attribute `activity_start_end` as the primary key. This string
  (`S`) value is a concatenation of three attributes, which is a way of ensuring
  each entry for the attribute will be unique.

  I also define two GSIs. -- check that these are correct for getting entries by
  range!

  ## Usage

  ## Related notes

raw_content:
  ---
  id: xowl
  title: DynamoDB
  tags:
    - AWS
    - databases
    - dynamodb
  created: Sunday, June 09, 2024
  ---

  # DynamoDB

  ## Data structure

  ### Non-relational tables

  DynamoDB is "NoSQL" because it does not support #SQL queries and is
  non-relational meaning there cannot be JOIN operations via
  [foreign_keys](Foreign_keys_in_SQL.md)

  ![](../img/dynamodb.png)

  ### Primary key

  Although the data is stored as a table, one of the attributes is a primary key
  and the rest of the attributes are effectively the "value" associated with it.

  Because DynamoDB is schemaless, neither the attributes or their data types need
  to be defined beforehand and each item can have its own distinct attributes.

  Each item in the table is uniquely identifiable by its primary key.

  There are two types of primary key available:

  - partition key: a simple primary key composed of one attribute only. Because
    the primary key is hash-mapped items can be retrieved very rapidly using the
    primary key. This would be the `personId` alone.

  - composite key: this comprises a partition key and a _sort key_ both of which
    are attributes. In a table that has a partition key and a sort key, it's
    possible for multiple items to have the same partition key value. However,
    those items must have different sort key values. You could then query by
    either key or both. For instance using the `personId` along with `LastName`

  ### Secondary index

  As well as the index provided by the primary key, you can set one or more
  **secondary indices**. A secondary index lets you query the data in the table
  using an alternate key.

  A **global secondary index** is useful for querying data that needs to be
  accessed using non-primary key attributes. For example, if you have a Users
  table with `UserID` as the primary key but often need to fetch users by their
  `Email`, a GSI on `Email` would be appropriate.

  There are also **local secondary indices** but I don't understand the
  difference.

  ## Real example

  Below is a specification of the DynamoDB table I am using for my time-entries
  project:

  ```json
  {
    "TableName": "TimeEntries",
    "AttributeDefinitions": [
      {
        "AttributeName": "activity_start_end",
        "AttributeType": "S"
      },
      {
        "AttributeName": "start",
        "AttributeType": "S"
      },
      {
        "AttributeName": "activity_type",
        "AttributeType": "S"
      }
    ],
    "KeySchema": [
      {
        "AttributeName": "activity_start_end",
        "KeyType": "HASH"
      }
    ],
    "GlobalSecondaryIndexes": [
      {
        "IndexName": "StartIndex",
        "KeySchema": [
          {
            "AttributeName": "start",
            "KeyType": "HASH"
          },
          {
            "AttributeName": "activity_type",
            "KeyType": "RANGE"
          }
        ],
        "Projection": {
          "ProjectionType": "ALL"
        }
      }
    ]
  }
  ```

  This defines the attribute `activity_start_end` as the primary key. This string
  (`S`) value is a concatenation of three attributes, which is a way of ensuring
  each entry for the attribute will be unique.

  I also define two GSIs. -- check that these are correct for getting entries by
  range!

  ## Usage

  ## Related notes

metadata:
  {"created":"Sunday, June 09, 2024","id":"xowl","tags":["AWS","databases","dynamodb"],"title":"DynamoDB"}


