# notes

path: zk/Function_overloads.md
sortable_path: zkFunction_overloads.md
title: Function overloads
checksum: 87852e5b84337d535639055d15b1785225e6a06a613f594ecc3995a161a76311
created: 2024-03-30 09:03:19.630274798+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
lead:
  Function overloading is not a feature of JavaScript but something close to it
  can be achieved with TypeScript. It proceeds by defining multiple function types
  (defined above the function) that may serve as the actual function's parameters.
  Then with the actual function, you leave the changeable parameters open as
  optional unions and/or `unknown` :

body:
  Function overloading is not a feature of JavaScript but something close to it
  can be achieved with TypeScript. It proceeds by defining multiple function types
  (defined above the function) that may serve as the actual function's parameters.
  Then with the actual function, you leave the changeable parameters open as
  optional unions and/or `unknown` :

  ```ts
  // First oveload type:
  function logSearch(term: string, options?: string): void;

  // Second overload type:
  function logSearch(term: string, options?: number): void;

  // Implementation:
  function logSearch(term: string, p2?: unknown) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearch("apples", "braeburn");
  logSearch("bananas", 3);
  ```

  ```ts
  // First overload type:
  function logSearchUnion(term: string, options?: string): void;

  // Second overload type:
  function logSearchUnion(term: string, options?: number): void;

  // Implementation:
  function logSearchUnion(term: string, p2?: string | number) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearchUnion("melon", "honey-dew");
  logSearchUnion("oranges", 4);
  ```

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

  ## Function overloads

  Function overloading is not a feature of JavaScript but something close to it
  can be achieved with TypeScript. It proceeds by defining multiple function types
  (defined above the function) that may serve as the actual function's parameters.
  Then with the actual function, you leave the changeable parameters open as
  optional unions and/or `unknown` :

  ```ts
  // First oveload type:
  function logSearch(term: string, options?: string): void;

  // Second overload type:
  function logSearch(term: string, options?: number): void;

  // Implementation:
  function logSearch(term: string, p2?: unknown) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearch("apples", "braeburn");
  logSearch("bananas", 3);
  ```

  ```ts
  // First overload type:
  function logSearchUnion(term: string, options?: string): void;

  // Second overload type:
  function logSearchUnion(term: string, options?: number): void;

  // Implementation:
  function logSearchUnion(term: string, p2?: string | number) {
    let query = `https://searchdatabase/${term}`;
    if (typeof p2 === "string") {
      query = `${query}/tag=${p2}`;
      console.log(query);
    } else {
      query = `${query}/page=${p2}`;
      console.log(query);
    }
  }

  logSearchUnion("melon", "honey-dew");
  logSearchUnion("oranges", 4);
  ```


path: zk/Functions.md
sortable_path: zkFunctions.md
title: Functions
checksum: d07fc49bfba92f05596275674fd922ea512e4efde44e593414c7127dfe821e8e
created: 2024-03-30 09:03:19.633898896+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript","react"]}
lead:
  Continuing from the other examples of React Typescript, we could do standard
  listing function, like:

body:
  Continuing from the other examples of React Typescript, we could do standard
  listing function, like:

  ```tsx
  <ul>
    {people.map((person) => {
      return <li>{person.name}</li>;
    })}
  </ul>
  ```

  But it's neater to do it with a function defined within the `List` component:

  ```tsx
  const renderList = (): JSX.Element[] => {
    return people.map((person) => {
      return (
        <li>
          <div>{person.name}</div>
          <div>{person.age}</div>
        </li>
      );
    });
  };
  ```

  And then change the eariler list to a function invocation:

  ```tsx
      <ul>{renderList()}<ul>
  ```

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

  # Functions

  Continuing from the other examples of React Typescript, we could do standard
  listing function, like:

  ```tsx
  <ul>
    {people.map((person) => {
      return <li>{person.name}</li>;
    })}
  </ul>
  ```

  But it's neater to do it with a function defined within the `List` component:

  ```tsx
  const renderList = (): JSX.Element[] => {
    return people.map((person) => {
      return (
        <li>
          <div>{person.name}</div>
          <div>{person.age}</div>
        </li>
      );
    });
  };
  ```

  And then change the eariler list to a function invocation:

  ```tsx
      <ul>{renderList()}<ul>
  ```


path: zk/Functions_called_on_click.md
sortable_path: zkFunctions_called_on_click.md
title: Testing that functions are called on click events
lead: https://stackoverflow.com/a/73167308
body: https://stackoverflow.com/a/73167308
checksum: 8e43109f339d820e323a0144f1c13496bb0dbe6198369f4de8ab3bfcca1e09a1
created: 2024-03-30 09:03:19.638050638+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript","react","testing"]}
raw_content:
  ---
  tags:
    - javascript
    - react
    - testing
  ---

  # Testing that functions are called on click events

  https://stackoverflow.com/a/73167308


path: zk/Functions_in_Bash.md
sortable_path: zkFunctions_in_Bash.md
title: Functions in Bash
checksum: 1436902153eb2fb5b49756f000141710ad80901576075f3acc535f4985194065
created: 2024-03-30 09:03:19.641370438+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
lead:
  We don't name function parameters in the function declaration. Instead we have
  an implied index of arguments: `$1, $2, $3,...`. When the function is called,
  the first value after the function name becomes `$1` by default, then the
  subsequent arguments.

body:
  We don't name function parameters in the function declaration. Instead we have
  an implied index of arguments: `$1, $2, $3,...`. When the function is called,
  the first value after the function name becomes `$1` by default, then the
  subsequent arguments.

  ```bash
  function expandRange() {
      declare -a expandedRange=()
      for (( i=$1; i<=$2; i++ )); do
          expandedRange+=($i)
      done
      echo "${expandedRange[@]}"
  }
  ```

  ```bash
  expandedRange=$(expandRange 1 4)
  echo $expandedRange
  # 1 2 3 4
  ```

  ## Get all arguments as an array

  We can access all the arguments passed to a function using the `$@` syntax we
  encountered before when
  [passing arguments to scripts](Passing_arguments_to_Bash_scripts.md).
  (Here a function is a kind of script in miniature so the process is the same.)

  ```sh
  function numberThings() {
    i=1
    for f in "$@"; do
      echo $i: "$f"
      (( i++ ))
    done
  }
  ```

  ## Local variables

  ```sh
  var1="I'm variable 1"

  function myfunction() {
    var2="I'm variable 2"
    local var3="I'm variable 3"
  }

  myfunction
  echo $var1
  echo $var2
  echo $var3

  # I'm variable 1
  # I'm variable 2
  ```

  > The convention is to put functions at the top of the script, after the shebang
  > and after the global variables

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

  # Functions in Bash

  We don't name function parameters in the function declaration. Instead we have
  an implied index of arguments: `$1, $2, $3,...`. When the function is called,
  the first value after the function name becomes `$1` by default, then the
  subsequent arguments.

  ```bash
  function expandRange() {
      declare -a expandedRange=()
      for (( i=$1; i<=$2; i++ )); do
          expandedRange+=($i)
      done
      echo "${expandedRange[@]}"
  }
  ```

  ```bash
  expandedRange=$(expandRange 1 4)
  echo $expandedRange
  # 1 2 3 4
  ```

  ## Get all arguments as an array

  We can access all the arguments passed to a function using the `$@` syntax we
  encountered before when
  [passing arguments to scripts](Passing_arguments_to_Bash_scripts.md).
  (Here a function is a kind of script in miniature so the process is the same.)

  ```sh
  function numberThings() {
    i=1
    for f in "$@"; do
      echo $i: "$f"
      (( i++ ))
    done
  }
  ```

  ## Local variables

  ```sh
  var1="I'm variable 1"

  function myfunction() {
    var2="I'm variable 2"
    local var3="I'm variable 3"
  }

  myfunction
  echo $var1
  echo $var2
  echo $var3

  # I'm variable 1
  # I'm variable 2
  ```

  > The convention is to put functions at the top of the script, after the shebang
  > and after the global variables


path: zk/Functions_in_Python.md
sortable_path: zkFunctions_in_Python.md
title: Functions in Python
checksum: 02169f9893dea8f188e6c3ea80dca601bb3edef2f5ad05b6f5cf5875b8760e09
created: 2024-03-30 09:03:19.649234225+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
lead:
  - Convention is to leave a double line-break after a function definition (but
    not with nested functions - here, a single linebreak is sufficient)
  - Scope within functions is demarcated by indents, as everything in Python
  - We use a docstring _within_ the function body, to document our function. This
    text will then show up in Intellisense etc.

body:
  - Convention is to leave a double line-break after a function definition (but
    not with nested functions - here, a single linebreak is sufficient)
  - Scope within functions is demarcated by indents, as everything in Python
  - We use a docstring _within_ the function body, to document our function. This
    text will then show up in Intellisense etc.

  ## Basic examples

  ```py

  # No params, no return
  def print_msg():
      """ A function that prints hello world """
      print('Hello World!')

  print_msg()
  # Hello World!

  print(type(print_msg))
  # <class 'function'>

  # Params, no return
  def print_my_msg(msg):
      """ A simple function to print a message """
      print(msg)

  print_my_msg('Good day')
  # Good day

  # Params and return
  def square(n):
      return n * n

  print(square(2))
  # 4

  result = square(4)
  print(result)

  # 16

  ```

  ## Default parameters

  ```py
  def greeter(name, message='Live Long and Prosper'):
      print('Welcome', name, '-', message)

  greeter('Eloise')

  # Welcome Eloise - Live Long and Prosper
  ```

  ## Optional parameters

  ````py
  def func_with_optional(non_optional, optional_param=None):
    if optional_param is not None:
      # Do something with specific value
    else:
      # Run standard process


  ## Function with arbitrary parameter list

  ```python
  def greeter(*args):
      for name in args:
          print('Welcome', name)

  greeter('John', 'Denise', 'Phoebe', 'Adam', 'Gryff', 'Natalia')

  """
  Welcome John
  Welcome Denise
  Welcome Phoebe
  Welcome Adam
  Welcome Gryff
  Welcome Natalia
  """
  ````

  ## Scoping

  Function variables are locally scoped by default.

  They can access variables that are outer to them and can redefine them within
  their own scope _and_ within the global scope using the keywords `global` and
  `nonlocal`.

  Below a global variable is accessed and changed but only internally within a
  function scope

  ```py
  max = 100
  print('initial value of max:', max)

  def print_max():
      global max
      max = max + 1
      print('inside function:', max)


  print_max()
  print('outside function:', max)

  """
  initial value of max: 100
  inside function: 101
  outside function: 101
  """
  ```

  Below a higher-scoped variable is redefined from within the lower scope:

  ```py
  def myfunc1():
    x = "John"
    def myfunc2():
      nonlocal x
      x = "hello"
    myfunc2()
    return x

  print(myfunc1())

  # hello
  ```

  We cannot however redefine a global variable from a function scope permanently.
  It will remain whatever it is in global scope, after the function has run.

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

  # Functions in Python

  - Convention is to leave a double line-break after a function definition (but
    not with nested functions - here, a single linebreak is sufficient)
  - Scope within functions is demarcated by indents, as everything in Python
  - We use a docstring _within_ the function body, to document our function. This
    text will then show up in Intellisense etc.

  ## Basic examples

  ```py

  # No params, no return
  def print_msg():
      """ A function that prints hello world """
      print('Hello World!')

  print_msg()
  # Hello World!

  print(type(print_msg))
  # <class 'function'>

  # Params, no return
  def print_my_msg(msg):
      """ A simple function to print a message """
      print(msg)

  print_my_msg('Good day')
  # Good day

  # Params and return
  def square(n):
      return n * n

  print(square(2))
  # 4

  result = square(4)
  print(result)

  # 16

  ```

  ## Default parameters

  ```py
  def greeter(name, message='Live Long and Prosper'):
      print('Welcome', name, '-', message)

  greeter('Eloise')

  # Welcome Eloise - Live Long and Prosper
  ```

  ## Optional parameters

  ````py
  def func_with_optional(non_optional, optional_param=None):
    if optional_param is not None:
      # Do something with specific value
    else:
      # Run standard process


  ## Function with arbitrary parameter list

  ```python
  def greeter(*args):
      for name in args:
          print('Welcome', name)

  greeter('John', 'Denise', 'Phoebe', 'Adam', 'Gryff', 'Natalia')

  """
  Welcome John
  Welcome Denise
  Welcome Phoebe
  Welcome Adam
  Welcome Gryff
  Welcome Natalia
  """
  ````

  ## Scoping

  Function variables are locally scoped by default.

  They can access variables that are outer to them and can redefine them within
  their own scope _and_ within the global scope using the keywords `global` and
  `nonlocal`.

  Below a global variable is accessed and changed but only internally within a
  function scope

  ```py
  max = 100
  print('initial value of max:', max)

  def print_max():
      global max
      max = max + 1
      print('inside function:', max)


  print_max()
  print('outside function:', max)

  """
  initial value of max: 100
  inside function: 101
  outside function: 101
  """
  ```

  Below a higher-scoped variable is redefined from within the lower scope:

  ```py
  def myfunc1():
    x = "John"
    def myfunc2():
      nonlocal x
      x = "hello"
    myfunc2()
    return x

  print(myfunc1())

  # hello
  ```

  We cannot however redefine a global variable from a function scope permanently.
  It will remain whatever it is in global scope, after the function has run.


path: zk/Fundamental_Theorem_of_Arithmetic.md
sortable_path: zkFundamental_Theorem_of_Arithmetic.md
lead:
  ---
  tags:
  - prealgebra
  - theorems
  ---

checksum: 71bc228e57d5941a05c676ee5eaee935bdf63391796039f106b192afc9af7807
created: 2024-03-30 09:03:19.65193431+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","theorems"]}
body:
  ---
  tags:
    - prealgebra
    - theorems
  ---

  > Every integer greater than one is either a prime number itself or is product
  > of a unique combination of primes.

  This is also known as the **Unique Factorisation Theorem**.

  'Unique' means that there is not more than one way to derive the whole number.
  Once you reduce the factorisation to primes, there can only be one set of
  numbers that results in the target number.

  For example, $24$ has the following factors: ${12, 24}$ and $6, 4$ but these are
  composite numbers. The unique factorisation combination for 24 is $2, 2, 3$.

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

  > Every integer greater than one is either a prime number itself or is product
  > of a unique combination of primes.

  This is also known as the **Unique Factorisation Theorem**.

  'Unique' means that there is not more than one way to derive the whole number.
  Once you reduce the factorisation to primes, there can only be one set of
  numbers that results in the target number.

  For example, $24$ has the following factors: ${12, 24}$ and $6, 4$ but these are
  composite numbers. The unique factorisation combination for 24 is $2, 2, 3$.


path: zk/Further_examples_of_generics.md
sortable_path: zkFurther_examples_of_generics.md
title: Further examples of generics in TypeScript
lead: ## Basic function
checksum: 02599d71a25bee261d052b3ee96b7d8605f670235fcf4d65b46eeac1344375fc
created: 2024-03-30 09:03:19.655279977+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
body:
  ## Basic function

  In the code below we have a simple JavaScript function that receives a value and
  an an array as parameters. It returns a new array comprising the original array
  plus the additional value:

  ```js
  function generateArray(existingArray, newValue) {
    return [...existingArray, newValue];
  }
  ```

  Imagine that we want to ensure that each of the parameters share the same data
  type. In other words: if the function is passed a string array, the second
  parameter must also be a string. For example, it should not be the case that you
  can append a string to an array of numbers.

  Now imagine that we don't know in advance what type the value or array will be,
  we just know that the data types of the parameters must match.

  In converting the function to TypeScript, one way of overcoming our lack of
  foreknowledge would be to deploy `any`. This way it doesn't matter which types
  are passed to the function:

  ```ts
  function generateArray(existingArray: any[], newValue: any): any[] {
    return [...existingArray, newValue];
  }
  ```

  But this is no solution at all. The problem — as always with `any` — is that it
  strips our function of any type checks whatsoever and would therefore invite
  calls of form: `generateArray([1,2,3], 'lorem')`.

  Enter generics:

  ```ts
  function generateArray<T>(existingArr: T[], newValue: T): T[] {
    return [...existingArr, newValue];
  }
  ```

  Now, whilst we haven't asserted ahead of time which types will be used,
  whichever types we do pass in, must match. The function header is saying:

  - both arguments must be of the same type (represented by `T`)
  - the function will return an array of this same `T` type.

  If I then tried to run the function with unmatched types (for example
  `generateArray([1,2,3,4], true)` ) TypeScript would raise the following error:

  ```
  Argument of type 'boolean' is not assignable to parameter of type 'number'
  ```

  Note that even though the function in question does not express any preference
  for number types, given that our first parameter is a number, TypeScript knows
  that the second parameter must also be a number.

  > In the generic function we have used `T` as our placeholder for a generic type
  > as this is the convention. However there is no compunction to do so. We could
  > have used any letter or string, providing that the string is not a reserved
  > term.

  ### More advanced function

  This example demonstrates how we can use generics to reduce repetition when
  writing functions and is also a more realistic use case.

  Let's say we have two types or interfaces:

  ```tsx
  type VideoFormatUrls = {
    format720p: URL;
    format1080p: URL;
  };
  ```

  ```tsx
  type SubtitleFormatUrls = {
    english: URL;
    german: URL;
  };
  ```

  An example of an object matching these type definitions:

  ```tsx
  const videoFormats: VideoFormatUrls = {
  	format720p: https://www.format720p.co.uk,
  	format1080p: https://www.format1080p.co.uk
  }
  ```

  Imagine we wanted to be able to check whether a given film is available in a
  certain video format. We could write a function like this:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  Now imagine that we need to do the same thing with subtitles, but given that
  `isFormatAvailable()` is typed to the `VideoFormatUrls` type we would get an
  error if we used this function for subtitles. But we also don't want to write a
  near identical function typed to `SubtitleFormatUrls` to subtitles just to
  ensure adequate type safety.

  Alternatively we could use a union type, for example:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls | SubtitleFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  But this quickly becomes unwieldy if we, for the sake of argument have a great
  many URL types that we want the function to utilise.

  This is where generics become super helpful. Here is how we would rewrite the
  function as a generic:

  ```tsx
  function isAvailable<Formats>(obj: Formats, key: string): key is keyof Formats {
    return key in obj;
  }
  ```

  We could then explicitly type our calls of this function, viz:

  ```tsx
  isFormatAvailable<SubtitleFormatUrls>(subtitles, "english");
  ```

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

  # Further examples of generics in TypeScript

  ## Basic function

  In the code below we have a simple JavaScript function that receives a value and
  an an array as parameters. It returns a new array comprising the original array
  plus the additional value:

  ```js
  function generateArray(existingArray, newValue) {
    return [...existingArray, newValue];
  }
  ```

  Imagine that we want to ensure that each of the parameters share the same data
  type. In other words: if the function is passed a string array, the second
  parameter must also be a string. For example, it should not be the case that you
  can append a string to an array of numbers.

  Now imagine that we don't know in advance what type the value or array will be,
  we just know that the data types of the parameters must match.

  In converting the function to TypeScript, one way of overcoming our lack of
  foreknowledge would be to deploy `any`. This way it doesn't matter which types
  are passed to the function:

  ```ts
  function generateArray(existingArray: any[], newValue: any): any[] {
    return [...existingArray, newValue];
  }
  ```

  But this is no solution at all. The problem — as always with `any` — is that it
  strips our function of any type checks whatsoever and would therefore invite
  calls of form: `generateArray([1,2,3], 'lorem')`.

  Enter generics:

  ```ts
  function generateArray<T>(existingArr: T[], newValue: T): T[] {
    return [...existingArr, newValue];
  }
  ```

  Now, whilst we haven't asserted ahead of time which types will be used,
  whichever types we do pass in, must match. The function header is saying:

  - both arguments must be of the same type (represented by `T`)
  - the function will return an array of this same `T` type.

  If I then tried to run the function with unmatched types (for example
  `generateArray([1,2,3,4], true)` ) TypeScript would raise the following error:

  ```
  Argument of type 'boolean' is not assignable to parameter of type 'number'
  ```

  Note that even though the function in question does not express any preference
  for number types, given that our first parameter is a number, TypeScript knows
  that the second parameter must also be a number.

  > In the generic function we have used `T` as our placeholder for a generic type
  > as this is the convention. However there is no compunction to do so. We could
  > have used any letter or string, providing that the string is not a reserved
  > term.

  ### More advanced function

  This example demonstrates how we can use generics to reduce repetition when
  writing functions and is also a more realistic use case.

  Let's say we have two types or interfaces:

  ```tsx
  type VideoFormatUrls = {
    format720p: URL;
    format1080p: URL;
  };
  ```

  ```tsx
  type SubtitleFormatUrls = {
    english: URL;
    german: URL;
  };
  ```

  An example of an object matching these type definitions:

  ```tsx
  const videoFormats: VideoFormatUrls = {
  	format720p: https://www.format720p.co.uk,
  	format1080p: https://www.format1080p.co.uk
  }
  ```

  Imagine we wanted to be able to check whether a given film is available in a
  certain video format. We could write a function like this:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  Now imagine that we need to do the same thing with subtitles, but given that
  `isFormatAvailable()` is typed to the `VideoFormatUrls` type we would get an
  error if we used this function for subtitles. But we also don't want to write a
  near identical function typed to `SubtitleFormatUrls` to subtitles just to
  ensure adequate type safety.

  Alternatively we could use a union type, for example:

  ```tsx
  function isFormatAvailable(
    obj: VideoFormatUrls | SubtitleFormatUrls,
    format: string
  ): format is keyof VideoFormatUrls {
    return format in obj;
  }
  ```

  But this quickly becomes unwieldy if we, for the sake of argument have a great
  many URL types that we want the function to utilise.

  This is where generics become super helpful. Here is how we would rewrite the
  function as a generic:

  ```tsx
  function isAvailable<Formats>(obj: Formats, key: string): key is keyof Formats {
    return key in obj;
  }
  ```

  We could then explicitly type our calls of this function, viz:

  ```tsx
  isFormatAvailable<SubtitleFormatUrls>(subtitles, "english");
  ```


path: zk/Generics.md
sortable_path: zkGenerics.md
title: Generics
checksum: 74484845b015dd880dd9a5e01c698353617bd818b86e244c6946d64ab002a41c
created: 2024-03-30 09:03:19.658230652+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
lead:
  Generics are a powerful feature in TypeScript that enables you to write reusable
  and flexible code while maintaining strong typing. With generics, you can create
  functions, classes, and interfaces that work with various types while preserving
  type information.

body:
  Generics are a powerful feature in TypeScript that enables you to write reusable
  and flexible code while maintaining strong typing. With generics, you can create
  functions, classes, and interfaces that work with various types while preserving
  type information.

  The main purpose of generics is to allow developers to write code that can
  **operate on different data types without knowing the specific type
  beforehand**. This helps to keep the code DRY and maintainable.

  ## Basic usage

  ### Functions

  ```ts
  function identity<T>(arg: T): T {
    return arg;
  }
  ```

  Here, `T` is a generic type variable. The `identity` function is a generic
  function that takes an argument of type `T` and returns a value of type `T`.

  To use this generic function you can either explicitly provide the type within
  the angle brackets, or let TypeScript infer the type based on the value passed:

  ```ts
  // Explicitly specifying the type
  let output1 = identity<string>("hello");

  // TypeScript infers the type as `number`
  let output2 = identity(42);
  ```

  #### Restricting the available types

  In the previous example any type could be used with the `identity` function. The
  only constraint that we place on usage is that the types must be consistent: if
  we pass a string as an argument, then a string must be returned.

  However we can add further restrictions on types by using the `extend` keyword,
  combined with an interface.

  ```ts
  interface Lengthwise {
    length: number;
  }

  function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
  }
  ```

  In this example the `logLength` generic function is limited to types that
  implement the `Lengthwise` interface. So: any argument that is passed to
  `logLength` and any value that is returned by it must match the shape of
  `Lengthwise`, having the `length` property.

  ## Interfaces and classes

  Generics can also be used profitably when working with stricter OOP constructs.

  ```ts
  interface KeyValuePair<K, V> {
    key: K;
    value: V;
  }
  ```

  The `KeyValuePair` interface has two generic type parameters: K for the key and
  V for the value. The interface defines an object shape that must possess the
  properties `key` and `value`, e.g:

  ```ts
  {
    key: 2,
    value: 'something'
  }
  ```

  In this form the generic specifies that whatever types are used for the key and
  the pair must be used consistently, e.g. this would be wrong:

  ```ts
  {
    key: 'age',
    value: 32
  }
  ```

  The following class uses the `KeyValuePair` interface:

  ```ts
  class Storage<K, V> {
    private items: KeyValuePair<K, V>[] = [];

    add(item: KeyValuePair<K, V>): void {
      this.items.push(item);
    }

    getByKey(key: K): V | undefined {
      const foundItem = this.items.find((item) => item.key === key);
      return foundItem ? foundItem.value : undefined;
    }

    getAll(): KeyValuePair<K, V>[] {
      return this.items;
    }
  }
  ```

  This class stores objects that match the `KeyValuesPair` interface in an array
  and provides and add and list method for accessing/returning them.

  The `add` method takes an item of type `KeyValuePair<K, V>` and adds it to the
  `items` array. The `getByKey` method takes a key of type `K` and returns the
  value of type `V` associated with that key or undefined if the key is not found.
  The `getAll` method returns all stored key-value pairs.

  Here is an example of instantiating the class:

  ```ts
  const storage = new Storage<number, string>();

  storage.add({ key: 1, value: "one" });
  storage.add({ key: 2, value: "two" });

  const value = storage.getByKey(1);
  const allItems = storage.getAll();

  console.log(value);
  // value: "one"

  console.log(allItems);

  // allItems: [
  //   { key: 1, value: "one" },
  //   { key: 2, value: "two" }
  // ]
  ```

  ## Real examples

  ### GraphQL client for query and mutation requests over `fetch`

  ```ts
  type GraphQlResult<T> = {
    data: T;
    errors?: Array<{
      message: string;
      locations: Array<{ line: number; column: number }>;
      path: Array<string | number>;
    }>;
  };

  export class GraphQlClient {
    private endpoint: string;

    constructor(endpoint: string) {
      this.endpoint = endpoint;
    }
    async request<T>(
      query: string,
      variables?: Record<string, unknown>
    ): Promise<T> {
      try {
        const response = await fetch(this.endpoint, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ query, variables }),
        });

        if (!response.ok) {
          throw new Error(
            `Network error: ${response.status} - ${response.statusText}`
          );
        }

        const result: GraphQlResult<T> = await response.json();

        if (result.errors) {
          throw new Error(`GraphQL error: ${JSON.stringify(result.errors)}`);
        }

        return result.data;
      } catch (error) {
        console.error(error);
        throw error;
      }
    }
  }
  ```

  ### VSCode extension TreeView generator

  In VSCode a TreeView is a list of values that may have nested values, like a
  directory. The following generic is a helper function that generates a TreeView
  based on a given class that is passed in as an argument, along with the class's
  constructor values (`args` in the example). It also calls a method `refresh` on
  each instance of the class.

  ```ts
  function createTreeView<
    T extends IndexHyperlinksProvider | IndexMetadataProvider,
    U extends LinkTypes | MetadataTypes
  >(
    viewType: string,
    ProviderClass: new (...args: any[]) => T,
    type: U,
    activeEditor?: string | undefined,
    ...args: ConstructorParameters<typeof ProviderClass>
  ): T {
    const view = new ProviderClass(...args, type);
    if (view instanceof IndexHyperlinksProvider) {
      view.refresh(activeEditor, type as LinkTypes);
    } else if (view instanceof IndexMetadataProvider) {
      view.refreshIndex();
    }
    vscode.window.registerTreeDataProvider(viewType, view);
    return view;
  }
  ```

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

  # Generics

  Generics are a powerful feature in TypeScript that enables you to write reusable
  and flexible code while maintaining strong typing. With generics, you can create
  functions, classes, and interfaces that work with various types while preserving
  type information.

  The main purpose of generics is to allow developers to write code that can
  **operate on different data types without knowing the specific type
  beforehand**. This helps to keep the code DRY and maintainable.

  ## Basic usage

  ### Functions

  ```ts
  function identity<T>(arg: T): T {
    return arg;
  }
  ```

  Here, `T` is a generic type variable. The `identity` function is a generic
  function that takes an argument of type `T` and returns a value of type `T`.

  To use this generic function you can either explicitly provide the type within
  the angle brackets, or let TypeScript infer the type based on the value passed:

  ```ts
  // Explicitly specifying the type
  let output1 = identity<string>("hello");

  // TypeScript infers the type as `number`
  let output2 = identity(42);
  ```

  #### Restricting the available types

  In the previous example any type could be used with the `identity` function. The
  only constraint that we place on usage is that the types must be consistent: if
  we pass a string as an argument, then a string must be returned.

  However we can add further restrictions on types by using the `extend` keyword,
  combined with an interface.

  ```ts
  interface Lengthwise {
    length: number;
  }

  function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
  }
  ```

  In this example the `logLength` generic function is limited to types that
  implement the `Lengthwise` interface. So: any argument that is passed to
  `logLength` and any value that is returned by it must match the shape of
  `Lengthwise`, having the `length` property.

  ## Interfaces and classes

  Generics can also be used profitably when working with stricter OOP constructs.

  ```ts
  interface KeyValuePair<K, V> {
    key: K;
    value: V;
  }
  ```

  The `KeyValuePair` interface has two generic type parameters: K for the key and
  V for the value. The interface defines an object shape that must possess the
  properties `key` and `value`, e.g:

  ```ts
  {
    key: 2,
    value: 'something'
  }
  ```

  In this form the generic specifies that whatever types are used for the key and
  the pair must be used consistently, e.g. this would be wrong:

  ```ts
  {
    key: 'age',
    value: 32
  }
  ```

  The following class uses the `KeyValuePair` interface:

  ```ts
  class Storage<K, V> {
    private items: KeyValuePair<K, V>[] = [];

    add(item: KeyValuePair<K, V>): void {
      this.items.push(item);
    }

    getByKey(key: K): V | undefined {
      const foundItem = this.items.find((item) => item.key === key);
      return foundItem ? foundItem.value : undefined;
    }

    getAll(): KeyValuePair<K, V>[] {
      return this.items;
    }
  }
  ```

  This class stores objects that match the `KeyValuesPair` interface in an array
  and provides and add and list method for accessing/returning them.

  The `add` method takes an item of type `KeyValuePair<K, V>` and adds it to the
  `items` array. The `getByKey` method takes a key of type `K` and returns the
  value of type `V` associated with that key or undefined if the key is not found.
  The `getAll` method returns all stored key-value pairs.

  Here is an example of instantiating the class:

  ```ts
  const storage = new Storage<number, string>();

  storage.add({ key: 1, value: "one" });
  storage.add({ key: 2, value: "two" });

  const value = storage.getByKey(1);
  const allItems = storage.getAll();

  console.log(value);
  // value: "one"

  console.log(allItems);

  // allItems: [
  //   { key: 1, value: "one" },
  //   { key: 2, value: "two" }
  // ]
  ```

  ## Real examples

  ### GraphQL client for query and mutation requests over `fetch`

  ```ts
  type GraphQlResult<T> = {
    data: T;
    errors?: Array<{
      message: string;
      locations: Array<{ line: number; column: number }>;
      path: Array<string | number>;
    }>;
  };

  export class GraphQlClient {
    private endpoint: string;

    constructor(endpoint: string) {
      this.endpoint = endpoint;
    }
    async request<T>(
      query: string,
      variables?: Record<string, unknown>
    ): Promise<T> {
      try {
        const response = await fetch(this.endpoint, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ query, variables }),
        });

        if (!response.ok) {
          throw new Error(
            `Network error: ${response.status} - ${response.statusText}`
          );
        }

        const result: GraphQlResult<T> = await response.json();

        if (result.errors) {
          throw new Error(`GraphQL error: ${JSON.stringify(result.errors)}`);
        }

        return result.data;
      } catch (error) {
        console.error(error);
        throw error;
      }
    }
  }
  ```

  ### VSCode extension TreeView generator

  In VSCode a TreeView is a list of values that may have nested values, like a
  directory. The following generic is a helper function that generates a TreeView
  based on a given class that is passed in as an argument, along with the class's
  constructor values (`args` in the example). It also calls a method `refresh` on
  each instance of the class.

  ```ts
  function createTreeView<
    T extends IndexHyperlinksProvider | IndexMetadataProvider,
    U extends LinkTypes | MetadataTypes
  >(
    viewType: string,
    ProviderClass: new (...args: any[]) => T,
    type: U,
    activeEditor?: string | undefined,
    ...args: ConstructorParameters<typeof ProviderClass>
  ): T {
    const view = new ProviderClass(...args, type);
    if (view instanceof IndexHyperlinksProvider) {
      view.refresh(activeEditor, type as LinkTypes);
    } else if (view instanceof IndexMetadataProvider) {
      view.refreshIndex();
    }
    vscode.window.registerTreeDataProvider(viewType, view);
    return view;
  }
  ```


path: zk/Git_bisect.md
sortable_path: zkGit_bisect.md
title: Bisect
lead:
  Bisect can be used to identify commits that introduce a bug or regression in our
  code.

checksum: 582180e21ae5adc1089dce7b0205d74e9345d25d43bb03b7a0d473da0f4a3774
created: 2024-03-30 09:03:19.661589073+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["git"]}
body:
  Bisect can be used to identify commits that introduce a bug or regression in our
  code.

  It is most useful for when we know there is a problem in the code and we know
  there was a point in the past where the bug did not exist. We can compare the
  two points and try to identify where things went wrong.

  We mark the last good revision and the first bad revision. Bisect will the reset
  the code to the midpoint between the good and bad versions and let you test it.
  You mark that as a good or bad version and then bisect repeats the process.

  ![](/img/git-bisect.svg)

  ## Procedure

  ```
  git bisect start

  git bisect good <SHA, tag, branch>

  git bisect bad <SHA, tag, branch>
  ```

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

  # Bisect

  Bisect can be used to identify commits that introduce a bug or regression in our
  code.

  It is most useful for when we know there is a problem in the code and we know
  there was a point in the past where the bug did not exist. We can compare the
  two points and try to identify where things went wrong.

  We mark the last good revision and the first bad revision. Bisect will the reset
  the code to the midpoint between the good and bad versions and let you test it.
  You mark that as a good or bad version and then bisect repeats the process.

  ![](/img/git-bisect.svg)

  ## Procedure

  ```
  git bisect start

  git bisect good <SHA, tag, branch>

  git bisect bad <SHA, tag, branch>
  ```


path: zk/Global_object.md
sortable_path: zkGlobal_object.md
title: Global object
checksum: 894865fd08e53e68b528a8a73f92e343b7d7b14b3c6366fac9783d8de27a3a1b
created: 2024-03-30 09:03:19.665038443+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["backend","node-js"]}
lead:
  > In Node every function and variable should be scoped to a module. We should
  > not define functions and variables within the global scope.

body:
  > In Node every function and variable should be scoped to a module. We should
  > not define functions and variables within the global scope.

  - In Node the equivalent to the browser's `Window` object is `global`. The
    properties and methods that belong to this method are available anywhere in a
    program.

  - Just as we can technically write `Window.console.log()`, we can write
    `global.console.log()` however in both cases it is more sane to use the
    shorthand.

  - However if we declare a variable in this scope in browser-based JavaScript,
    this variable becomes accessible via the `Window` object and thus is
    accessible in global scope. The same is not true for Node. If you declare a
    variable at this level it will return undefined.

  - This is because of Node's modular nature. If you were to define a function
    `foo` in a module and then also define it in the global scope, when you call
    `foo`, the Node interpreter would not know which function to call. Hence it
    chooses not to recognise the global `foo`, returning undefined.

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

  # Global object

  > In Node every function and variable should be scoped to a module. We should
  > not define functions and variables within the global scope.

  - In Node the equivalent to the browser's `Window` object is `global`. The
    properties and methods that belong to this method are available anywhere in a
    program.

  - Just as we can technically write `Window.console.log()`, we can write
    `global.console.log()` however in both cases it is more sane to use the
    shorthand.

  - However if we declare a variable in this scope in browser-based JavaScript,
    this variable becomes accessible via the `Window` object and thus is
    accessible in global scope. The same is not true for Node. If you declare a
    variable at this level it will return undefined.

  - This is because of Node's modular nature. If you were to define a function
    `foo` in a module and then also define it in the global scope, when you call
    `foo`, the Node interpreter would not know which function to call. Hence it
    chooses not to recognise the global `foo`, returning undefined.


path: zk/Grep.md
sortable_path: zkGrep.md
title: Grep
lead: ## Purpose of `grep`
checksum: be1a28cd33fe7976d80627887f90d029ed84591c7e294bc75165973cc3f70c59
created: 2024-03-30 09:03:19.668728834+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
body:
  ## Purpose of `grep`

  `grep` stands for “global regular expression print”. It allows you to search
  plain text data sets for strings which match a regular expression or pattern.

  ## Syntax

  ### Schematic

  ```bash
  grep [options] [pattern] [source file] > [output file]
  ```

  Note that above we redirect the file matches to a new file. You don't have to do
  this. If you omit the redirection, `grep` will output to standard output.

  ### Applied

  ```bash
  grep -i -n "banana" fruits.txt > banana.txt
  ```

  The above example searches, using regex, for strings matching the pattern
  “banana” in the file `fruits.txt` regardless of the character case (`-i` ensures
  this) and outputs its findings to the file `banana.txt`, with the line number
  where the match occurs appended to the output (`-n` takes care of this).

  Note that for simplicity, you can chain optional values together, i.e. the
  options in the above example could be input as `-in`.

  ## Useful options

  - ignore case: `i`
  - count matches instead of returning actual match: `-c`
  - precede each match with the line number where it occurs: `-n`
  - invert the match (show everything that doesn't match the expression): `-v`
  - search entire directories recursively: `-r`
  - list file names where matches occur (in the scenario of a recursive match):
    `-l`

  ## `ripgrep`

  `ripgrep` is generally faster however it does not come as default with Unix and
  only works recursively, i.e. it is designed to find strings within files within
  multiple directories not just single files or piped streams.

  It also respects `.gitignore` files that it finds within directories by default
  and `node_modules` which is really handy.

  Most of the standard `grep` options transfer over.

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

  # Grep

  ## Purpose of `grep`

  `grep` stands for “global regular expression print”. It allows you to search
  plain text data sets for strings which match a regular expression or pattern.

  ## Syntax

  ### Schematic

  ```bash
  grep [options] [pattern] [source file] > [output file]
  ```

  Note that above we redirect the file matches to a new file. You don't have to do
  this. If you omit the redirection, `grep` will output to standard output.

  ### Applied

  ```bash
  grep -i -n "banana" fruits.txt > banana.txt
  ```

  The above example searches, using regex, for strings matching the pattern
  “banana” in the file `fruits.txt` regardless of the character case (`-i` ensures
  this) and outputs its findings to the file `banana.txt`, with the line number
  where the match occurs appended to the output (`-n` takes care of this).

  Note that for simplicity, you can chain optional values together, i.e. the
  options in the above example could be input as `-in`.

  ## Useful options

  - ignore case: `i`
  - count matches instead of returning actual match: `-c`
  - precede each match with the line number where it occurs: `-n`
  - invert the match (show everything that doesn't match the expression): `-v`
  - search entire directories recursively: `-r`
  - list file names where matches occur (in the scenario of a recursive match):
    `-l`

  ## `ripgrep`

  `ripgrep` is generally faster however it does not come as default with Unix and
  only works recursively, i.e. it is designed to find strings within files within
  multiple directories not just single files or piped streams.

  It also respects `.gitignore` files that it finds within directories by default
  and `node_modules` which is really handy.

  Most of the standard `grep` options transfer over.


path: zk/Ground.md
sortable_path: zkGround.md
title: Ground
checksum: 06d6a82b347c9527a50fa40afccff167a415ff9ab7bfed33ff87c58dc8ae473d
created: 2024-03-30 09:03:19.673447106+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["physics","electricity"]}
lead:
  The term 'ground' is used as a general reference for 'zero potential': the point
  in an electrical circuit where there is no potential difference between the
  positive and negative terminals of the voltage source.

body:
  The term 'ground' is used as a general reference for 'zero potential': the point
  in an electrical circuit where there is no potential difference between the
  positive and negative terminals of the voltage source.

  As a consequence of the lack of potential, ground is a point at which no current
  can be generated.

  The term originates from domestic electrical systems where the circuit box is
  literally attached to the ground (represented by the green and yellow ribboned
  wires in plug sockets and light fixtures etc) where current is discharged to
  prevent appliances emitting electrical shocks.

  In circuit diagrams with simple DC current, ground is taken to be the negative
  terminal of the voltage source.

  The symbol for ground in circuit diagrams:

  <img src="/home/thomas/repos/computer_science/img/ground-symbol.png" width="300px">

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

  # Ground

  The term 'ground' is used as a general reference for 'zero potential': the point
  in an electrical circuit where there is no potential difference between the
  positive and negative terminals of the voltage source.

  As a consequence of the lack of potential, ground is a point at which no current
  can be generated.

  The term originates from domestic electrical systems where the circuit box is
  literally attached to the ground (represented by the green and yellow ribboned
  wires in plug sockets and light fixtures etc) where current is discharged to
  prevent appliances emitting electrical shocks.

  In circuit diagrams with simple DC current, ground is taken to be the negative
  terminal of the voltage source.

  The symbol for ground in circuit diagrams:

  <img src="/home/thomas/repos/computer_science/img/ground-symbol.png" width="300px">


path: zk/Guide_to_YAML.md
sortable_path: zkGuide_to_YAML.md
title: Guide to YAML
checksum: fbc35123772f4d3ade2f3690116e1cb6530123688b131475ed9a4081a428c765
created: 2024-03-30 09:03:19.678384789+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":[]}
lead:
  - Data description language
  - Alternative to XML and JSON
  - Easy to read by humans and process by code

body:
  - Data description language
  - Alternative to XML and JSON
  - Easy to read by humans and process by code

  ## Main rule

  > Never use tabs for indentation, use two spaces

  ## Available data types

  - strings
  - numbers
  - booleans
  - arrays
  - maps (key, value pairs)

  ### Example

  ````yaml
  # Basic scalar data types
  age: 51
  greeting: Hello, world
  length: 182.3
  happy: true
  ``

  ```yaml
  # array
  pets:
    - cat
    - dog

  # nested array
  pets:
    - cat
    - dog
      - staffy
      - pitty
  ````

  ```yaml
  # map
  languages:
    - java: advanced
    - python: average
    - javascript: basic
  ```

  ### Strings

  - Don't have to use quotes unless string contains reserved YAML characters
    (dashes, colons etc)
  - Can use single or double quotes

  ### Multi-line text

  Two methods:

  ```yaml
  # Literal block preserves new line characters
  literal_block: |
    Lorem ipsum dolar sit avcc lorem dolar 
    Blah blah
    Blah
  ```

  ```yaml
  folded_block: >
    Each different line will be folded back on itself so you don't have to keep
    scrolling to the right
  ```

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

  # Guide to YAML

  - Data description language
  - Alternative to XML and JSON
  - Easy to read by humans and process by code

  ## Main rule

  > Never use tabs for indentation, use two spaces

  ## Available data types

  - strings
  - numbers
  - booleans
  - arrays
  - maps (key, value pairs)

  ### Example

  ````yaml
  # Basic scalar data types
  age: 51
  greeting: Hello, world
  length: 182.3
  happy: true
  ``

  ```yaml
  # array
  pets:
    - cat
    - dog

  # nested array
  pets:
    - cat
    - dog
      - staffy
      - pitty
  ````

  ```yaml
  # map
  languages:
    - java: advanced
    - python: average
    - javascript: basic
  ```

  ### Strings

  - Don't have to use quotes unless string contains reserved YAML characters
    (dashes, colons etc)
  - Can use single or double quotes

  ### Multi-line text

  Two methods:

  ```yaml
  # Literal block preserves new line characters
  literal_block: |
    Lorem ipsum dolar sit avcc lorem dolar 
    Blah blah
    Blah
  ```

  ```yaml
  folded_block: >
    Each different line will be folded back on itself so you don't have to keep
    scrolling to the right
  ```


path: zk/HTTP_request_types.md
sortable_path: zkHTTP_request_types.md
title: GET
lead: - Get data
body:
  - Get data

  ## POST

  - Create data

  ## PUT

  - Update data

  ## DELETE

  - Remove data

checksum: c03cde7001b95e7efcafb18bf1886ccc1d5811171e64b039814f5b4015f4b642
created: 2024-03-30 09:03:19.682695112+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":[]}
raw_content:
  ---
  tags: []
  ---

  ## GET

  - Get data

  ## POST

  - Create data

  ## PUT

  - Update data

  ## DELETE

  - Remove data


path: zk/Half_adder_and_full_adder.md
sortable_path: zkHalf_adder_and_full_adder.md
title: The half adder and full adder
lead: ## Binary arithmetic
checksum: 6c57575017ae451e8a8b357b8e8a6c8cdab2de4305b514ab62eaa9bfeb654556
created: 2024-03-30 09:03:19.685941999+00:00
modified: 2024-04-11 18:03:36.776792856+00:00
metadata: {"tags":["logic-gates","binary"]}
body:
  ## Binary arithmetic

  The half adder and full adder are components of digital circuits that enable us
  to carry out binary addition. Using adders and half adders we can add two binary
  numbers together. Adders are a type of [integrated circuit]() comprising certain
  [logic gates](Logic_gates.md) where the arrangement allows for the
  representation of the addition of bits.

  ### Example addition

  Consider the following binary addition:

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  How does the calculation proceed? Moving from the right-most column:

  - $1 + 0 = 1$ hence the sum of the first column (the binary 1 coumn) is $1$ with
    no carry
  - Moving to the second column (the binary 2 column), $1 + 1 = 2$ however we do
    not have the digit "2" in binary, therefore we must bump the place value by
    one place and carry this sum as a $1$ leaving a zero to mark the place value
  - Thus the third column (the binary 4 column) has the calculation:
    $1 + 0 + 0 = 1$ with no carry.

  ### Bits

  From the previous example we can identified the following repeated actions:

  - For calculations of the form $1 + 0$ or $0 + 0$ there will be no carried digit
  - For calculations of the form $1 + 1$ there will be a carried digit

  In the context of adders the digits are bits. We distinguish three core bits by
  their role in the calculation: the carry, the **least significant bit** and the
  **most significant bit**. The least significant bit is the right-most digit in
  the calculation. The most significant bit is the leftmost.

  The two types of adders are distinguished by which bits of the calculation they
  operate on. The half adder focuses on the least significant bit whereas the full
  adder focuses on the most significant bit.

  ## Half adder

  The half adder receives two bits (A and B) which are to be added together. It
  outputs this value as the **sum bit**. If there is a bit to be carried to the
  next column in the binary calculation this will be output as the **carry-out**
  bit.

  | A                            | B                             | S           | C_out             |
  | ---------------------------- | ----------------------------- | ----------- | ----------------- |
  | The first number to be added | The second number to be added | The sum bit | The carry-out bit |

  The diagram below shows the circuit representation of a half-adder and an
  example calculation. This calculation matches the ones column of the earlier
  binary addition example: $0011 + 0010$.

  ![](/img/half-adder-new.png)

  ### Implementation with logic gates

  If we think about it, the possible inputs and outputs of a half adder are highly
  circumscribed:

  - If the sum exceeds $1$, the sum bit will be $0$ and the carry-out bit will be
    $1$
  - In all other cases the carry-out bit will be $0$. These other cases are when
    the sum bit is either $0$ or $1$, e.g: $1 + 0$ or $0 + 0$.

  We can represent this with a simple truth-table:

  | A   | B   | S   | C_out |
  | --- | --- | --- | ----- |
  | 0   | 0   | 0   | 0     |
  | 0   | 1   | 1   | 0     |
  | 1   | 0   | 1   | 0     |
  | 1   | 1   | 0   | 1     |

  We can see that the sum bit column replicates the truth-conditions of
  [XOR](Logic_gates.md#xor-gate):

  | P   | Q   | P V Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | T     |
  | F   | T   | T     |
  | F   | F   | F     |

  And the carry-out bit replicates the truth conditions of
  [AND](Logic_gates.md#and-gate):

  | P   | Q   | P & Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | F     |
  | F   | T   | F     |
  | F   | F   | T     |

  It is therefore possible to implement a half-adder with just these two logic
  gates:

  ![](/img/half-adder-gates-three.png)

  The digital circuit above has the same inputs and outputs as the half adder
  diagram above.

  <iframe src="https://circuitverse.org/simulator/embed/half-adder-67b14a9f-d1ed-4240-ab19-1d753f18a40d" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

  ## Full adder

  As the half adder only calculates the least significant bit, it is not
  sufficient by itself to complete a binary addition; it cannot account for
  movements in binary place value. To carry out full calculations it must be
  supplemented with the full adder.

  The full adder takes in three inputs and has two outputs. It is identical to the
  half-adder apart from the fact that one of its inputs is **carry-in**. This is
  equivalent to the value that is designated as **carry-out** in a half adder. It
  is an incoming value that is the product of a previous operation that resulted
  in a carry-out. This is added together with the full adders own two inputs (A
  and B) and like the half adder, generates a sum bit and a carry-out bit.

  | A                            | B                             | C_in                     | S                      | C_out                        |
  | ---------------------------- | ----------------------------- | ------------------------ | ---------------------- | ---------------------------- |
  | The first number to be added | The second number to be added | The incoming carried bit | The sum bit (A+B+C_in) | The carry-out bit (A+B+C_in) |

  ![](/img/full-adder-new.png)

  The diagram above is equivalent to the calculation taking place in the fours
  column. It has received a carry from the twos column ($1 + 1$ results in $1$ as
  a carry) and then adds this together with its own inputs ($0$ and $0$).

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  ### Implementation with logic gates

  When it comes to implementing the full adder with logic gates we can allow
  ourselves greater abstraction as we already have the half adder to work with. We
  don't need to create a half adder from scratch, we can reuse it.

  We are adding three bits: $1$, $0$ and $0$. This can be achieved with two half
  adders:

  - One half adder (HA1) for the sum of $0 + 0$
  - One half adder (HA2) for the sum of $(0 + 0) + 1$ (the previous sum plus the
    third bit)

  The sum of HA1 ($0 + 0$) is passed in to the B input on HA2 and the $1$ is
  passed in as the A input of HA2. This gives us $1$ as the sum bit of HA2.

  At this point we have completed the addition and have successfully added the
  three bits: $1$, $0$, and $0$ to get $1$ as ths sum. However we also have to
  account for the fact that the addition may result in its own carry-out bit. What
  if the inputs were $1, 1, 0$ for example?

  <iframe src="https://circuitverse.org/simulator/embed/full-adder-674f8ddf-c5aa-42fa-9c0f-4349ffabe723" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="700" allowFullScreen></iframe>

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

  # The half adder and full adder

  ## Binary arithmetic

  The half adder and full adder are components of digital circuits that enable us
  to carry out binary addition. Using adders and half adders we can add two binary
  numbers together. Adders are a type of [integrated circuit]() comprising certain
  [logic gates](Logic_gates.md) where the arrangement allows for the
  representation of the addition of bits.

  ### Example addition

  Consider the following binary addition:

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  How does the calculation proceed? Moving from the right-most column:

  - $1 + 0 = 1$ hence the sum of the first column (the binary 1 coumn) is $1$ with
    no carry
  - Moving to the second column (the binary 2 column), $1 + 1 = 2$ however we do
    not have the digit "2" in binary, therefore we must bump the place value by
    one place and carry this sum as a $1$ leaving a zero to mark the place value
  - Thus the third column (the binary 4 column) has the calculation:
    $1 + 0 + 0 = 1$ with no carry.

  ### Bits

  From the previous example we can identified the following repeated actions:

  - For calculations of the form $1 + 0$ or $0 + 0$ there will be no carried digit
  - For calculations of the form $1 + 1$ there will be a carried digit

  In the context of adders the digits are bits. We distinguish three core bits by
  their role in the calculation: the carry, the **least significant bit** and the
  **most significant bit**. The least significant bit is the right-most digit in
  the calculation. The most significant bit is the leftmost.

  The two types of adders are distinguished by which bits of the calculation they
  operate on. The half adder focuses on the least significant bit whereas the full
  adder focuses on the most significant bit.

  ## Half adder

  The half adder receives two bits (A and B) which are to be added together. It
  outputs this value as the **sum bit**. If there is a bit to be carried to the
  next column in the binary calculation this will be output as the **carry-out**
  bit.

  | A                            | B                             | S           | C_out             |
  | ---------------------------- | ----------------------------- | ----------- | ----------------- |
  | The first number to be added | The second number to be added | The sum bit | The carry-out bit |

  The diagram below shows the circuit representation of a half-adder and an
  example calculation. This calculation matches the ones column of the earlier
  binary addition example: $0011 + 0010$.

  ![](/img/half-adder-new.png)

  ### Implementation with logic gates

  If we think about it, the possible inputs and outputs of a half adder are highly
  circumscribed:

  - If the sum exceeds $1$, the sum bit will be $0$ and the carry-out bit will be
    $1$
  - In all other cases the carry-out bit will be $0$. These other cases are when
    the sum bit is either $0$ or $1$, e.g: $1 + 0$ or $0 + 0$.

  We can represent this with a simple truth-table:

  | A   | B   | S   | C_out |
  | --- | --- | --- | ----- |
  | 0   | 0   | 0   | 0     |
  | 0   | 1   | 1   | 0     |
  | 1   | 0   | 1   | 0     |
  | 1   | 1   | 0   | 1     |

  We can see that the sum bit column replicates the truth-conditions of
  [XOR](Logic_gates.md#xor-gate):

  | P   | Q   | P V Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | T     |
  | F   | T   | T     |
  | F   | F   | F     |

  And the carry-out bit replicates the truth conditions of
  [AND](Logic_gates.md#and-gate):

  | P   | Q   | P & Q |
  | --- | --- | ----- |
  | T   | T   | F     |
  | T   | F   | F     |
  | F   | T   | F     |
  | F   | F   | T     |

  It is therefore possible to implement a half-adder with just these two logic
  gates:

  ![](/img/half-adder-gates-three.png)

  The digital circuit above has the same inputs and outputs as the half adder
  diagram above.

  <iframe src="https://circuitverse.org/simulator/embed/half-adder-67b14a9f-d1ed-4240-ab19-1d753f18a40d" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

  ## Full adder

  As the half adder only calculates the least significant bit, it is not
  sufficient by itself to complete a binary addition; it cannot account for
  movements in binary place value. To carry out full calculations it must be
  supplemented with the full adder.

  The full adder takes in three inputs and has two outputs. It is identical to the
  half-adder apart from the fact that one of its inputs is **carry-in**. This is
  equivalent to the value that is designated as **carry-out** in a half adder. It
  is an incoming value that is the product of a previous operation that resulted
  in a carry-out. This is added together with the full adders own two inputs (A
  and B) and like the half adder, generates a sum bit and a carry-out bit.

  | A                            | B                             | C_in                     | S                      | C_out                        |
  | ---------------------------- | ----------------------------- | ------------------------ | ---------------------- | ---------------------------- |
  | The first number to be added | The second number to be added | The incoming carried bit | The sum bit (A+B+C_in) | The carry-out bit (A+B+C_in) |

  ![](/img/full-adder-new.png)

  The diagram above is equivalent to the calculation taking place in the fours
  column. It has received a carry from the twos column ($1 + 1$ results in $1$ as
  a carry) and then adds this together with its own inputs ($0$ and $0$).

  ```
    c_1
  0   0   1   0
  0   0   1   1
  _____________
  0   1   0   1
  ```

  ### Implementation with logic gates

  When it comes to implementing the full adder with logic gates we can allow
  ourselves greater abstraction as we already have the half adder to work with. We
  don't need to create a half adder from scratch, we can reuse it.

  We are adding three bits: $1$, $0$ and $0$. This can be achieved with two half
  adders:

  - One half adder (HA1) for the sum of $0 + 0$
  - One half adder (HA2) for the sum of $(0 + 0) + 1$ (the previous sum plus the
    third bit)

  The sum of HA1 ($0 + 0$) is passed in to the B input on HA2 and the $1$ is
  passed in as the A input of HA2. This gives us $1$ as the sum bit of HA2.

  At this point we have completed the addition and have successfully added the
  three bits: $1$, $0$, and $0$ to get $1$ as ths sum. However we also have to
  account for the fact that the addition may result in its own carry-out bit. What
  if the inputs were $1, 1, 0$ for example?

  <iframe src="https://circuitverse.org/simulator/embed/full-adder-674f8ddf-c5aa-42fa-9c0f-4349ffabe723" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="700" allowFullScreen></iframe>


path: zk/Handle_multiple_endpoints_single_lambda.md
sortable_path: zkHandle_multiple_endpoints_single_lambda.md
title: Handle multiple API Gateway endpoints in a single lambda
lead:
  With the following config we have a single lambda (`/query-waktime`) with
  multiple endpoints:

checksum: 26f95034a2cb57a44f57665902bd8e26e29f799d291fb598ad3297abf09dd90f
created: 2024-03-30 09:03:19.699910599+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["AWS","aws-lambda","backend"]}
body:
  With the following config we have a single lambda (`/query-waktime`) with
  multiple endpoints:

  ```yml
  Resources:
    QueryWakatimeFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: src/query-wakatime/
        Handler: index.handler
        Events:
          MainMetrics:
            Type: Api
            Properties:
              Path: /query-wakatime/main-metrics
              Method: get
              Auth:
                Authorizer: AWS_IAM
          Durations:
            Type: Api
            Properties:
              Path: /query-wakatime/durations
              Method: get
              Auth:
                Authorizer: AWS_IAM
          TodayOnly:
            Type: Api
            Properties:
              Path: /query-wakatime/today-only
              Method: get
              Auth:
                Authorizer: AWS_IAM
  ```

  Our endpoints are as follows:

  - `/query-wakatime/main-metrics`
  - `/query-wakatime/durations`
  - `/query-wakatime/today-only`

  Each returns a different dataset from an external API. The lambda function makes
  a different request to the API based on the endpoint it receives and the time
  period that is passed as a query string parameter, e.g.
  `/query-wakatime?range=last_7_days`.

  Below is a template for concisely handling requests to the different endpoints
  in the single lambda function wihout excessive repetition.

  ```js
  const getEndpoint = (path: string, timePeriod?: string | undefined) => {
    const basePath = "/users/current";
    switch (path) {
      case "/query-wakatime/main-metrics":
        return `${basePath}/stats/${timePeriod}`;
      case "/query-wakatime/durations":
        return `${basePath}/summaries?range=${timePeriod}`;
      case "/query-wakatime/today-only":
        return `${basePath}/status_bar/today`;
    }
  };

  export const handler = async (event) => {
    const fetchClient = new FetchClient("https://wakatime.com/api/v1/");
    const path = event.path;
    const timePeriod = event.queryStringParameters?.timePeriod;
    const endpoint = getEndpoint(path, timePeriod);

    const init: RequestInit = {
      headers: {
        Authorization: `Basic ${encodedApiKey}`,
      },
    };

    if (!endpoint) {
      throw new Error("Endpoint is undefined")
    }

   let data = await fetchClient.get(endpoint, init)
   let dataJson = (await data.json()) as string

   response = {
      statusCode: 200,
      body: JSON.stringify(dataJson),
      headers: responseHeaders,
      }
  };



  ```

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

  # Handle multiple API Gateway endpoints in a single lambda

  With the following config we have a single lambda (`/query-waktime`) with
  multiple endpoints:

  ```yml
  Resources:
    QueryWakatimeFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: src/query-wakatime/
        Handler: index.handler
        Events:
          MainMetrics:
            Type: Api
            Properties:
              Path: /query-wakatime/main-metrics
              Method: get
              Auth:
                Authorizer: AWS_IAM
          Durations:
            Type: Api
            Properties:
              Path: /query-wakatime/durations
              Method: get
              Auth:
                Authorizer: AWS_IAM
          TodayOnly:
            Type: Api
            Properties:
              Path: /query-wakatime/today-only
              Method: get
              Auth:
                Authorizer: AWS_IAM
  ```

  Our endpoints are as follows:

  - `/query-wakatime/main-metrics`
  - `/query-wakatime/durations`
  - `/query-wakatime/today-only`

  Each returns a different dataset from an external API. The lambda function makes
  a different request to the API based on the endpoint it receives and the time
  period that is passed as a query string parameter, e.g.
  `/query-wakatime?range=last_7_days`.

  Below is a template for concisely handling requests to the different endpoints
  in the single lambda function wihout excessive repetition.

  ```js
  const getEndpoint = (path: string, timePeriod?: string | undefined) => {
    const basePath = "/users/current";
    switch (path) {
      case "/query-wakatime/main-metrics":
        return `${basePath}/stats/${timePeriod}`;
      case "/query-wakatime/durations":
        return `${basePath}/summaries?range=${timePeriod}`;
      case "/query-wakatime/today-only":
        return `${basePath}/status_bar/today`;
    }
  };

  export const handler = async (event) => {
    const fetchClient = new FetchClient("https://wakatime.com/api/v1/");
    const path = event.path;
    const timePeriod = event.queryStringParameters?.timePeriod;
    const endpoint = getEndpoint(path, timePeriod);

    const init: RequestInit = {
      headers: {
        Authorization: `Basic ${encodedApiKey}`,
      },
    };

    if (!endpoint) {
      throw new Error("Endpoint is undefined")
    }

   let data = await fetchClient.get(endpoint, init)
   let dataJson = (await data.json()) as string

   response = {
      statusCode: 200,
      body: JSON.stringify(dataJson),
      headers: responseHeaders,
      }
  };



  ```


path: zk/Handling_negative_fractions.md
sortable_path: zkHandling_negative_fractions.md
title: Negative fractions
checksum: 134790bda657057e507a4087eb4d4ce42197cdcf7f3eb41586397ff6af015de4
created: 2024-03-30 09:03:19.702385792+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra","fractions"]}
lead:
  To work with negative fractions we draw on the
  [Rules for operations on like and unlike terms](Rules%20for%20operations%20on%20like%20and%20unlike%20terms.md).

body:
  To work with negative fractions we draw on the
  [Rules for operations on like and unlike terms](Rules%20for%20operations%20on%20like%20and%20unlike%20terms.md).

  ## Fractions with unlike terms

  - A fraction is just one number divided by another. $\frac{5}{10}$ is just ten
    divided by 5.

  - A positive integer divided by a negative or vice versa will always result in a
    negative. Thus $\frac{5}{-15}$ is equal to $-3$.

  - We can therefore express the whole fraction as a negative:

    $$
    -	\frac{5}{15}
    $$

  - Or we could apply the negative symbol to the numerator. It would stand for the
    same value:
    $$
    \\frac{-5}{15}
    $$

  Therefore:

  > Let $a,b$ be any integers. The following three fractions are
  > [equivalent](Equivalent%20fractions.md):
  > $$\frac{-5}{15}, \frac{5}{-15}, - \frac{5}{15}$$

  ## Fractions with like terms

  - In cases where both the numerator and denominator are both negative, the value
    that the fraction represents will be positive overall. This is because the
    quotient of a negative integer divided by a negative integer will always be
    positive.

  - Thus: $$ \frac{- 12xy^2}{ - 18xy^2} = \frac{12xy^2}{18xy^2}$$

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

  # Negative fractions

  To work with negative fractions we draw on the
  [Rules for operations on like and unlike terms](Rules%20for%20operations%20on%20like%20and%20unlike%20terms.md).

  ## Fractions with unlike terms

  - A fraction is just one number divided by another. $\frac{5}{10}$ is just ten
    divided by 5.

  - A positive integer divided by a negative or vice versa will always result in a
    negative. Thus $\frac{5}{-15}$ is equal to $-3$.

  - We can therefore express the whole fraction as a negative:

    $$
    -	\frac{5}{15}
    $$

  - Or we could apply the negative symbol to the numerator. It would stand for the
    same value:
    $$
    \\frac{-5}{15}
    $$

  Therefore:

  > Let $a,b$ be any integers. The following three fractions are
  > [equivalent](Equivalent%20fractions.md):
  > $$\frac{-5}{15}, \frac{5}{-15}, - \frac{5}{15}$$

  ## Fractions with like terms

  - In cases where both the numerator and denominator are both negative, the value
    that the fraction represents will be positive overall. This is because the
    quotient of a negative integer divided by a negative integer will always be
    positive.

  - Thus: $$ \frac{- 12xy^2}{ - 18xy^2} = \frac{12xy^2}{18xy^2}$$


path: zk/Hardware_Description_Language.md
sortable_path: zkHardware_Description_Language.md
title: Hardware Description Language
checksum: 165dfe31b4879e4bb4e7cb751f68c3732809d918fb7c78529bc82727f3eadbcb
created: 2024-03-30 09:03:19.715156042+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["nand-to-tetris"]}
lead:
  An HDL is a declarative programming language used to describe the behaviour or
  structure of
  [digital circuits](Integrated_circuits.md).
  They are used to simulate the circuit and check its response.

body:
  An HDL is a declarative programming language used to describe the behaviour or
  structure of
  [digital circuits](Integrated_circuits.md).
  They are used to simulate the circuit and check its response.

  The hardware designer specifies a chip's logic by writing an HDL program which
  is then rigorously tested. At this stage, a
  [hardware simulator](Hardware_simulation.md) takes the
  HDL program as input and creates a software representation of the chip logic.
  The designer can instruct the simulator to test the virtual chip on various sets
  of inputs. This is done to check the chip's functionality but also to benchmark
  a variety of other parameters such as speed of computation and energy
  consumption.

  There are many HDLs but the most popular are VHDL ("very high speed
  integrated-circuit description language") and Verilog.

  ## Usage in _NAND to Tetris_

  We won't use an actual HDL language, instead we will use a simplified toy
  language called HDL that is simple enough that when it is used with a simulator,
  we can learn the main facets of chip design. Its syntax is very similar to VHDL.

  ## Demonstration of HDL program

  ### Boolean function to enact

  We will create an HDL program for an XOR gate that is implemented through the
  following arrangement of NOT, AND, and OR gates:

  ![](/img/xor-hdl.png)

  ### HDL file (`Xor.hdl`):

  Here is our HDL file:

  ```
  /* Xor gate
     If a!=b out=1 else out=0
  */
  CHIP Xor {
    IN a, b;
    OUT out;
    PARTS:
    Not (in=a, out=nota);
    Not (in=b, out=notb);
    And (a=a, b=notb, out=w1);
    And (a=nota, b=b, out=w2);
    Or  (a=w1, b=w2, out=out)
  }
  ```

  #### Interface (`CHIP, IN, OUT`)

  At the top level of the HDL program, the `CHIP` name and `IN`/`OUT` declaration
  is the _interface_ of the chip. Here we specify our naming convention for the
  `IN` and `OUT` values which we will refer to in the implementation declaration
  in `PARTS`.

  #### Implementation (`PARTS`)

  Everything under the `PARTS` section is the chip _implementation_. We can draw
  on composite gates in the `PARTS` declaration (e.g. `Not`, `And`, `Or`). The
  convention is to work from left to right when transcribing from a digital
  circuit diagram

  #### Pins

  In an HDL program we distinguish internal pins along with the standard
  [input and output pins](Integrated_circuits.md).
  At the level of the interface, we are concerned only with input and output pins
  (in the example program these are `a`, `b` and `out`). It is at the level of the
  implementation that internal pins are encountered. In the example these are the
  connections between, e.g. the AND and NOT gates such as
  `And (a=a, b-notb, out=w1)`. This means the AND gate is receiving through its
  `a` pin the input `a` value and through its `b` pin the value of `b` inverted by
  a NOT. `out` is the value that is computed based on the input pins of `a` and
  `b`.

  ### Test file (`Xor.tst`)

  Along with the HDL file we also create a test file. This runs the chip against
  the inputs we supply, these will typically be equivalent to the (left-hand)
  truth-values column in a truth table which is the same as the parameters passed
  to a [Boolean function](Boolean_functions.md), for
  example:

  ```vhdl
  load Xor.hdl
  output-list a, b, out;
  set a 0, set b 0, eval, output;
  set a 0, set b 1, eval, output;
  set a 1, set b 0, eval, output;
  set a 1, set b =, eval, output;
  ```

  ### Output file (`Xor.out`)

  When the test file is run against the HDL file it will generate an output file.
  This is effectively the result of the unit test. And will take the form of a
  truth table:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```

raw_content:
  ---
  tags: [nand-to-tetris]
  ---

  # Hardware Description Language

  An HDL is a declarative programming language used to describe the behaviour or
  structure of
  [digital circuits](Integrated_circuits.md).
  They are used to simulate the circuit and check its response.

  The hardware designer specifies a chip's logic by writing an HDL program which
  is then rigorously tested. At this stage, a
  [hardware simulator](Hardware_simulation.md) takes the
  HDL program as input and creates a software representation of the chip logic.
  The designer can instruct the simulator to test the virtual chip on various sets
  of inputs. This is done to check the chip's functionality but also to benchmark
  a variety of other parameters such as speed of computation and energy
  consumption.

  There are many HDLs but the most popular are VHDL ("very high speed
  integrated-circuit description language") and Verilog.

  ## Usage in _NAND to Tetris_

  We won't use an actual HDL language, instead we will use a simplified toy
  language called HDL that is simple enough that when it is used with a simulator,
  we can learn the main facets of chip design. Its syntax is very similar to VHDL.

  ## Demonstration of HDL program

  ### Boolean function to enact

  We will create an HDL program for an XOR gate that is implemented through the
  following arrangement of NOT, AND, and OR gates:

  ![](/img/xor-hdl.png)

  ### HDL file (`Xor.hdl`):

  Here is our HDL file:

  ```
  /* Xor gate
     If a!=b out=1 else out=0
  */
  CHIP Xor {
    IN a, b;
    OUT out;
    PARTS:
    Not (in=a, out=nota);
    Not (in=b, out=notb);
    And (a=a, b=notb, out=w1);
    And (a=nota, b=b, out=w2);
    Or  (a=w1, b=w2, out=out)
  }
  ```

  #### Interface (`CHIP, IN, OUT`)

  At the top level of the HDL program, the `CHIP` name and `IN`/`OUT` declaration
  is the _interface_ of the chip. Here we specify our naming convention for the
  `IN` and `OUT` values which we will refer to in the implementation declaration
  in `PARTS`.

  #### Implementation (`PARTS`)

  Everything under the `PARTS` section is the chip _implementation_. We can draw
  on composite gates in the `PARTS` declaration (e.g. `Not`, `And`, `Or`). The
  convention is to work from left to right when transcribing from a digital
  circuit diagram

  #### Pins

  In an HDL program we distinguish internal pins along with the standard
  [input and output pins](Integrated_circuits.md).
  At the level of the interface, we are concerned only with input and output pins
  (in the example program these are `a`, `b` and `out`). It is at the level of the
  implementation that internal pins are encountered. In the example these are the
  connections between, e.g. the AND and NOT gates such as
  `And (a=a, b-notb, out=w1)`. This means the AND gate is receiving through its
  `a` pin the input `a` value and through its `b` pin the value of `b` inverted by
  a NOT. `out` is the value that is computed based on the input pins of `a` and
  `b`.

  ### Test file (`Xor.tst`)

  Along with the HDL file we also create a test file. This runs the chip against
  the inputs we supply, these will typically be equivalent to the (left-hand)
  truth-values column in a truth table which is the same as the parameters passed
  to a [Boolean function](Boolean_functions.md), for
  example:

  ```vhdl
  load Xor.hdl
  output-list a, b, out;
  set a 0, set b 0, eval, output;
  set a 0, set b 1, eval, output;
  set a 1, set b 0, eval, output;
  set a 1, set b =, eval, output;
  ```

  ### Output file (`Xor.out`)

  When the test file is run against the HDL file it will generate an output file.
  This is effectively the result of the unit test. And will take the form of a
  truth table:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```


path: zk/Hardware_abstraction_and_modularity.md
sortable_path: zkHardware_abstraction_and_modularity.md
title: Hardware abstraction and modularity
checksum: 6d101cf97794548ab23f8a70cf416e70d8642d888d2ba82ecbbe32ac1327bc4a
created: 2024-03-30 09:03:19.732731161+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["nand-to-tetris"]}
lead:
  In computer architecture we deal with complexity by breaking the system into
  **modules**. For each module we distinguish **_abstraction_** from
  **_implementation_**.

body:
  In computer architecture we deal with complexity by breaking the system into
  **modules**. For each module we distinguish **_abstraction_** from
  **_implementation_**.

  <dl>
    <dt>abstraction</dt>
    <dd>what the module does</dd>
    <dt>implementation</dt>
    <dd>how it does it</dd>
  </dl>

  When using a module as a building block you are to focus exclusively on the
  module's abstraction, ignoring completely its implementation details.

  > The abstraction-implementation paradigm helps developers manage complexity and
  > maintain sanity: by dividing an overwhelming system into well-defined modules
  > we create manageable chunks of implementation work and localize error
  > detection and correction.

  [N.Nisan, S.Schoken. 2021. **The Elements of Computing Systems** (Second
  Edition)]

  The design of the diagram below emphasises the role of abstraction and
  modularity in the movement from transistors to chips:

  ![](/img/hardware-abstraction-hierarchy.png)

raw_content:
  ---
  tags: [nand-to-tetris]
  ---

  # Hardware abstraction and modularity

  In computer architecture we deal with complexity by breaking the system into
  **modules**. For each module we distinguish **_abstraction_** from
  **_implementation_**.

  <dl>
    <dt>abstraction</dt>
    <dd>what the module does</dd>
    <dt>implementation</dt>
    <dd>how it does it</dd>
  </dl>

  When using a module as a building block you are to focus exclusively on the
  module's abstraction, ignoring completely its implementation details.

  > The abstraction-implementation paradigm helps developers manage complexity and
  > maintain sanity: by dividing an overwhelming system into well-defined modules
  > we create manageable chunks of implementation work and localize error
  > detection and correction.

  [N.Nisan, S.Schoken. 2021. **The Elements of Computing Systems** (Second
  Edition)]

  The design of the diagram below emphasises the role of abstraction and
  modularity in the movement from transistors to chips:

  ![](/img/hardware-abstraction-hierarchy.png)


path: zk/Hardware_simulation.md
sortable_path: zkHardware_simulation.md
title: Hardware simulation
checksum: 81eaf5ae524f90bc704e6b23c5218acb3c5324da1ad875beec4ae07caba430d7
created: 2024-03-30 09:03:19.735626091+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["nand-to-tetris"]}
lead:
  In order to test our
  [HDL](Hardware_Description_Language.md) files we load
  them into the hardware simulator program. We will demonstrate this with the
  following XOR implementation:

body:
  In order to test our
  [HDL](Hardware_Description_Language.md) files we load
  them into the hardware simulator program. We will demonstrate this with the
  following XOR implementation:

  ![](/img/xor-addition-four.png)

  There are several simulation options:

  - interactive
  - script-based (where we load a test script into the simulator along with the
    HDL file
  - comparative (running the HDL program against our intended output specified in
    the `.cmp` file)

  The use-cases for each mode are based on the complexity of the chip you are
  evaluating. For a small chip, interactive and script-based testing would be
  sufficient but for much larger components like an ALU a comparative approach
  would be more manageable and efficient.

  ## Interactive

  The image below shows a basic interactive usage of the simulator. We have
  uploaded the `Xor.hdl` file into the simulator and changed the input pins to
  `a=1, b=0` and clicked the calculator icon (representing "evaluation"). This
  then shows the output and internal pin values for these inputs.

  <img src="/home/thomas/repos/computer_science/img/hardware-sim-basic.png" >

  ## Script-based

  This time we have clicked the script icon to load `Xor.tst`. This loads the test
  script into the main GUI panel on the left. We can step through each line of the
  test file and we will see the pin values update in accordance with the test.

  When this is run it automatically generates an output file in the source
  directory at `Xor.out`. This can be viewed within the simulator via the 'View'
  drop down.

  ![](/img/hd-sim-test.png)

  ## Comparison-based

  With a comparison-based approach to chip testing we run a comparison against the
  `.out` file that the simulator generates when running the HDL program against a
  `.cmp` comparison file that we provide. Both are simply truth-tables. For XOR if
  the program matched the comparison specification both `Xor.out` and `Xor.cmp`
  would look like the following:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```

  You don't have to do anything to apply the comparison since the compare file
  will already be loaded as part of the test file's set up:

  ```vhdl
  load Xor.hdl
  output file Xor.out
  compare-to Xor.cmp
  output-list a, b, out;
  set ...
  ```

raw_content:
  ---
  tags: [nand-to-tetris]
  ---

  # Hardware simulation

  In order to test our
  [HDL](Hardware_Description_Language.md) files we load
  them into the hardware simulator program. We will demonstrate this with the
  following XOR implementation:

  ![](/img/xor-addition-four.png)

  There are several simulation options:

  - interactive
  - script-based (where we load a test script into the simulator along with the
    HDL file
  - comparative (running the HDL program against our intended output specified in
    the `.cmp` file)

  The use-cases for each mode are based on the complexity of the chip you are
  evaluating. For a small chip, interactive and script-based testing would be
  sufficient but for much larger components like an ALU a comparative approach
  would be more manageable and efficient.

  ## Interactive

  The image below shows a basic interactive usage of the simulator. We have
  uploaded the `Xor.hdl` file into the simulator and changed the input pins to
  `a=1, b=0` and clicked the calculator icon (representing "evaluation"). This
  then shows the output and internal pin values for these inputs.

  <img src="/home/thomas/repos/computer_science/img/hardware-sim-basic.png" >

  ## Script-based

  This time we have clicked the script icon to load `Xor.tst`. This loads the test
  script into the main GUI panel on the left. We can step through each line of the
  test file and we will see the pin values update in accordance with the test.

  When this is run it automatically generates an output file in the source
  directory at `Xor.out`. This can be viewed within the simulator via the 'View'
  drop down.

  ![](/img/hd-sim-test.png)

  ## Comparison-based

  With a comparison-based approach to chip testing we run a comparison against the
  `.out` file that the simulator generates when running the HDL program against a
  `.cmp` comparison file that we provide. Both are simply truth-tables. For XOR if
  the program matched the comparison specification both `Xor.out` and `Xor.cmp`
  would look like the following:

  ```
  a | b | out
  -----------
  0 | 0 | 0
  0 | 1 | 1
  1 | 0 | 1
  1 | 1 | 0
  ```

  You don't have to do anything to apply the comparison since the compare file
  will already be loaded as part of the test file's set up:

  ```vhdl
  load Xor.hdl
  output file Xor.out
  compare-to Xor.cmp
  output-list a, b, out;
  set ...
  ```


path: zk/Hexadecimal_number_system.md
sortable_path: zkHexadecimal_number_system.md
title: Hexadecimal number system
checksum: 142383e619077fbab7ffd1fea51bba66be637ab65f95f0a1542c3101798bb564
created: 2024-03-30 09:03:19.740848011+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["number-systems"]}
lead:
  Hexadecimal is the other main number system used in computing. It works in
  tandem with the [binary number system](Binary_number_system.md) and provides an
  easier and more accessible means of working with long sequences of binary
  numbers.

body:
  Hexadecimal is the other main number system used in computing. It works in
  tandem with the [binary number system](Binary_number_system.md) and provides an
  easier and more accessible means of working with long sequences of binary
  numbers.

  ## Hexadecimal place value

  Unlike denary which uses base ten and binary which uses base two, hexadecimal
  uses base 16 as its place value.

  > Each place in a hexadecimal number represents a power of 16 and each place can
  > be one of 16 symbols.

  ## Hexadecimal values

  The table below shows the symbols comprising hexadecimal alongside their denary
  and binary equivalents:

  | Hexadecimal | Decimal | Binary |
  | ----------- | ------- | ------ |
  | 0           | 0       | 0000   |
  | 1           | 1       | 0001   |
  | 2           | 2       | 0010   |
  | 3           | 3       | 0011   |
  | 4           | 4       | 0100   |
  | 5           | 5       | 0101   |
  | 6           | 6       | 0110   |
  | 7           | 7       | 0111   |
  | 8           | 8       | 1000   |
  | 9           | 9       | 1001   |
  | A           | 10      | 1010   |
  | B           | 11      | 1011   |
  | C           | 12      | 1100   |
  | D           | 13      | 1101   |
  | E           | 14      | 1110   |
  | F           | 15      | 1111   |

  This table shows the raw value of each hexadecimal place value:

  | $16^{3}$ | $16^{2}$ | $16^{1}$ | $16^{0}$ |
  | -------- | -------- | -------- | -------- |
  | 4096     | 256      | 16       | 1        |

  ## Hexadecimal prefix

  The custom is to prefix a hexadecimal number with `0x` to indicate that the
  number is hexadecimal.

  ## Converting hexadecimal numbers

  Using the previous table we can convert hexadecimal values to decimal.

  For example we can convert `1A5` as follows, working from right to left:

  $(5 \cdot 1 = 5) + (A \cdot 16 = 160) + (1 \cdot 256 = 256) = 421$

  The process is quite easy: we get the n from $16^{n}$ based on the position of
  the digit and then multiply this by the value of the symbol (1,2,...F):

  $$
    16^{n} \cdot 1,2,...F
  $$

  As applied to `0x1A5`:

  | $16^{3}$               | $16^{2}$              | $16^{1}$                   | $16^{0}$            |
  | ---------------------- | --------------------- | -------------------------- | ------------------- |
  | $1\cdot 16^{3} = 4096$ | $1\cdot 16^{2} = 256$ | $A (10)\cdot 16^{1} = 160$ | $5\cdot 16^{0} = 5$ |

  Another example for `0xF00F`:

  $(15 \cdot 4096 = 61440) + (0 \cdot 256 = 0) + (0 \cdot 16 = 0) + (15 \cdot 1 = 15) = 61455$

  ## Using hexadecimal to simplify binary numbers

  Whilst computers themselves do not use the hexadecimal number system (everything
  is binary), hexadecimal offers advantages for humans who must work with binary:

  1. It is much easier to read a hexadecimal number than long sequences of binary
     numbers
  2. It is easier to quickly convert binary numbers to hexadecimal than to convert
     binary numbers to decimal

  Look at the following equivalences

  | Number system   | Example 1           | Example 2           |
  | --------------- | ------------------- | ------------------- |
  | **Binary**      | 1111 0000 0000 1111 | 1000 1000 1000 0001 |
  | **Hexadecimal** | F00F                | 8881                |
  | **Decimal**     | 61,455              | 34,945              |

  It is obvious that a pattern is maintained between the hexadecimal and binary
  numbers and that this pattern is obscured by the decimal conversion. In the
  first example the binary half-byte `1111` is matched by the hexadecimal `F00F`.

  Mathematically comparing hex `F` and binary `1111`:

  $$
    \textsf{1111} = ((1 \cdot 2^{3}) + (1 \cdot 2^{2}) + (1 \cdot 2^{1}) + (1 \cdot 2^{0})) \\
    = 8 + 4 + 2 + 1 \\
    = 15
  $$

  $$
    \textsf{F} = 15 \cdot 16^{0}  \\
    = 15
  $$

  ![](/img/hexadecimal-to-bytes.svg)

  > Every four bits (or half byte) in binary corresponds to one symbol in
  > hexadecimal. Therefore **a byte can be easily represented with two hexadecimal
  > symbols, a 16-bit number can be represented with four hex symbols, a 32-bit
  > number can represented with eight hex symbols and so on.**

raw_content:
  ---
  tags: [number-systems]
  ---

  # Hexadecimal number system

  Hexadecimal is the other main number system used in computing. It works in
  tandem with the [binary number system](Binary_number_system.md) and provides an
  easier and more accessible means of working with long sequences of binary
  numbers.

  ## Hexadecimal place value

  Unlike denary which uses base ten and binary which uses base two, hexadecimal
  uses base 16 as its place value.

  > Each place in a hexadecimal number represents a power of 16 and each place can
  > be one of 16 symbols.

  ## Hexadecimal values

  The table below shows the symbols comprising hexadecimal alongside their denary
  and binary equivalents:

  | Hexadecimal | Decimal | Binary |
  | ----------- | ------- | ------ |
  | 0           | 0       | 0000   |
  | 1           | 1       | 0001   |
  | 2           | 2       | 0010   |
  | 3           | 3       | 0011   |
  | 4           | 4       | 0100   |
  | 5           | 5       | 0101   |
  | 6           | 6       | 0110   |
  | 7           | 7       | 0111   |
  | 8           | 8       | 1000   |
  | 9           | 9       | 1001   |
  | A           | 10      | 1010   |
  | B           | 11      | 1011   |
  | C           | 12      | 1100   |
  | D           | 13      | 1101   |
  | E           | 14      | 1110   |
  | F           | 15      | 1111   |

  This table shows the raw value of each hexadecimal place value:

  | $16^{3}$ | $16^{2}$ | $16^{1}$ | $16^{0}$ |
  | -------- | -------- | -------- | -------- |
  | 4096     | 256      | 16       | 1        |

  ## Hexadecimal prefix

  The custom is to prefix a hexadecimal number with `0x` to indicate that the
  number is hexadecimal.

  ## Converting hexadecimal numbers

  Using the previous table we can convert hexadecimal values to decimal.

  For example we can convert `1A5` as follows, working from right to left:

  $(5 \cdot 1 = 5) + (A \cdot 16 = 160) + (1 \cdot 256 = 256) = 421$

  The process is quite easy: we get the n from $16^{n}$ based on the position of
  the digit and then multiply this by the value of the symbol (1,2,...F):

  $$
    16^{n} \cdot 1,2,...F
  $$

  As applied to `0x1A5`:

  | $16^{3}$               | $16^{2}$              | $16^{1}$                   | $16^{0}$            |
  | ---------------------- | --------------------- | -------------------------- | ------------------- |
  | $1\cdot 16^{3} = 4096$ | $1\cdot 16^{2} = 256$ | $A (10)\cdot 16^{1} = 160$ | $5\cdot 16^{0} = 5$ |

  Another example for `0xF00F`:

  $(15 \cdot 4096 = 61440) + (0 \cdot 256 = 0) + (0 \cdot 16 = 0) + (15 \cdot 1 = 15) = 61455$

  ## Using hexadecimal to simplify binary numbers

  Whilst computers themselves do not use the hexadecimal number system (everything
  is binary), hexadecimal offers advantages for humans who must work with binary:

  1. It is much easier to read a hexadecimal number than long sequences of binary
     numbers
  2. It is easier to quickly convert binary numbers to hexadecimal than to convert
     binary numbers to decimal

  Look at the following equivalences

  | Number system   | Example 1           | Example 2           |
  | --------------- | ------------------- | ------------------- |
  | **Binary**      | 1111 0000 0000 1111 | 1000 1000 1000 0001 |
  | **Hexadecimal** | F00F                | 8881                |
  | **Decimal**     | 61,455              | 34,945              |

  It is obvious that a pattern is maintained between the hexadecimal and binary
  numbers and that this pattern is obscured by the decimal conversion. In the
  first example the binary half-byte `1111` is matched by the hexadecimal `F00F`.

  Mathematically comparing hex `F` and binary `1111`:

  $$
    \textsf{1111} = ((1 \cdot 2^{3}) + (1 \cdot 2^{2}) + (1 \cdot 2^{1}) + (1 \cdot 2^{0})) \\
    = 8 + 4 + 2 + 1 \\
    = 15
  $$

  $$
    \textsf{F} = 15 \cdot 16^{0}  \\
    = 15
  $$

  ![](/img/hexadecimal-to-bytes.svg)

  > Every four bits (or half byte) in binary corresponds to one symbol in
  > hexadecimal. Therefore **a byte can be easily represented with two hexadecimal
  > symbols, a 16-bit number can be represented with four hex symbols, a 32-bit
  > number can represented with eight hex symbols and so on.**


path: zk/IO_in_Python.md
sortable_path: zkIO_in_Python.md
title: IO in Python
lead: ## The open() object
checksum: 77d495df925a692a62bda67b41974bad69749f3a2d561295bdd329c71ac17039
created: 2024-03-30 09:03:19.746239686+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  ## The open() object

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

  The general syntax is as follows:

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

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

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

  ### All access modes

  In addition we have the following access modes

  ## Reading files

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

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

  ### Read

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

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

  The basic syntax is as follows:

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

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

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

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

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

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

  ### Readline

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

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

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

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

      # Do something with the line
      print(line)

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

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

  ### Readlines

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

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

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

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

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

  ### Error handling

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

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

  ## Close and "with as"

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

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

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

  ## Writing to files

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

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

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

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

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

  ## Renaming and deleting files

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

  To rename an existing file:

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

  To delete a file:

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

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

  # IO in Python

  ## The open() object

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

  The general syntax is as follows:

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

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

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

  ### All access modes

  In addition we have the following access modes

  ## Reading files

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

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

  ### Read

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

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

  The basic syntax is as follows:

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

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

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

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

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

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

  ### Readline

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

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

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

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

      # Do something with the line
      print(line)

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

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

  ### Readlines

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

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

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

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

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

  ### Error handling

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

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

  ## Close and "with as"

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

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

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

  ## Writing to files

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

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

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

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

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

  ## Renaming and deleting files

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

  To rename an existing file:

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

  To delete a file:

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


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

checksum: 4276aeff9c2f164c65c35fb617983aa2739633e1617b080b74ae32b35baacc1a
created: 2024-03-30 09:03:19.756286126+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["git"]}
body:
  ```sh
  # View merged
  git branch --merged

  # View merged remote
  git branch -r --merged

  # View unmerged
  git branch --no-merged

  # View unmerged remote

  git branch -r --no-merged
  ```

  The above cases run the merge check from the point of view of HEAD as this is
  the default.

  But we can also run it from any branch.

  ```
  git branch --merged non_head_branch
  ```

  ## Demonstration

  We have the following branches

  ```
  git branch

  * main
  key_feature
  another_feature
  ```

  The branches other than `main`, have not been merged:

  ```
  git branch --merged

  * main
  ```

  Just to confirm:

  ```
  git branch --no-merged

  key_feature
  another_feature
  ```

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

  # Identify merged branches

  ```sh
  # View merged
  git branch --merged

  # View merged remote
  git branch -r --merged

  # View unmerged
  git branch --no-merged

  # View unmerged remote

  git branch -r --no-merged
  ```

  The above cases run the merge check from the point of view of HEAD as this is
  the default.

  But we can also run it from any branch.

  ```
  git branch --merged non_head_branch
  ```

  ## Demonstration

  We have the following branches

  ```
  git branch

  * main
  key_feature
  another_feature
  ```

  The branches other than `main`, have not been merged:

  ```
  git branch --merged

  * main
  ```

  Just to confirm:

  ```
  git branch --no-merged

  key_feature
  another_feature
  ```


path: zk/Importing_data.md
sortable_path: zkImporting_data.md
title: Importing data to MongoDB
checksum: 0aad294f4a7d076efe83d77bad1f9ce03d3c2c12502776762c7cb57a68972daf
created: 2024-03-30 09:03:19.758819348+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["mongo-db","node-js"]}
lead:
  We can use the command line to import data into a Mongo database. We can also
  combine the importing of data with the creation of a database:

body:
  We can use the command line to import data into a Mongo database. We can also
  combine the importing of data with the creation of a database:

  ```bash
  mongoimport --db [preexisting_or_new_database name] --collection [collection_name] --file sourcedata.json --jsonArray
  ```

  We use `--jsonArray` to indicate the format of the data we are importing.

  Once this has executed, the data will be visible in Compass.

raw_content:
  ---
  tags: [mongo-db, node-js]
  ---

  # Importing data to MongoDB

  We can use the command line to import data into a Mongo database. We can also
  combine the importing of data with the creation of a database:

  ```bash
  mongoimport --db [preexisting_or_new_database name] --collection [collection_name] --file sourcedata.json --jsonArray
  ```

  We use `--jsonArray` to indicate the format of the data we are importing.

  Once this has executed, the data will be visible in Compass.


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

  $$
  \\frac{3 \cdot 4}{5 \cdot 4} = \frac{12}{20}
  $$

  _Express $\frac{2}{3}$ as an equivalent fraction having the denominator 21_

  $$
  \\frac{2 \cdot 7}{3 \cdot 7} = \frac{14}{21}
  $$

  ## Increasing fractions with variables to higher terms

  _Express $\frac{2}{9}$ as an equivalent fraction having the denominator 18a_

  In these cases, just append the variable to the factor:

  $$
  \\frac{2 \cdot 2a}{9 \cdot 2a} = \frac{4a}{18a}
  $$

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

  Given the equivalence between factors and divisors we can increase fractions to
  higher terms in a very similar way to when we reduce fractions. In the latter
  case we are dividing by divisors to reduce. In the former, we are multiplying by
  factors to increase.

  > Whenever we increase a fraction, the resultant fraction will always be
  > [equivalent](Equivalent%20fractions.md) to the fraction we started with.

  ## Demonstration

  _Express $\frac{3}{4}$ as an equivalent fraction having the denominator 20_

  $$
  \\frac{3 \cdot 4}{5 \cdot 4} = \frac{12}{20}
  $$

  _Express $\frac{2}{3}$ as an equivalent fraction having the denominator 21_

  $$
  \\frac{2 \cdot 7}{3 \cdot 7} = \frac{14}{21}
  $$

  ## Increasing fractions with variables to higher terms

  _Express $\frac{2}{9}$ as an equivalent fraction having the denominator 18a_

  In these cases, just append the variable to the factor:

  $$
  \\frac{2 \cdot 2a}{9 \cdot 2a} = \frac{4a}{18a}
  $$


path: zk/Insert_data_into_SQL_table.md
sortable_path: zkInsert_data_into_SQL_table.md
title: Insert data into table with SQL INSERT statement
lead: _A table named `sales`:_
checksum: 13aeaa65794487606bb604a14dc6fe00ae02f35901fc28e6295f163e7004d7f2
created: 2024-03-30 09:03:19.772063797+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["SQL","relational-databases"]}
body:
  _A table named `sales`:_

  | saleId | modelId | date       | salesPerson |
  | ------ | ------- | ---------- | ----------- |
  | 223    | 2       | 2020-01-09 | tgomay      |
  | 321    | 3       | 2020-03-14 | snedgamon   |
  | 491    | 4       | 2021-08-06 | tricksahoy  |

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01','mhogan');
  ```

  > If you intend to miss out a value, you shouldn't leave it blank, you should
  > instead use `NULL` :

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01', NULL);
  ```

  > The approach above works well if the order of entry matches the order of the
  > table fields. If this is not the case you can specify the order as part of the
  > `INSERT` statement:

  ```sql
  INSERT INTO sales**(employee_id, sale_id, model_id, sale_date)**
  VALUES ('mhogan', 1, 11, '2020-01-01',);
  ```

raw_content:
  ---
  tags: [SQL, relational-databases]
  ---

  # Insert data into table with SQL `INSERT` statement

  _A table named `sales`:_

  | saleId | modelId | date       | salesPerson |
  | ------ | ------- | ---------- | ----------- |
  | 223    | 2       | 2020-01-09 | tgomay      |
  | 321    | 3       | 2020-03-14 | snedgamon   |
  | 491    | 4       | 2021-08-06 | tricksahoy  |

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01','mhogan');
  ```

  > If you intend to miss out a value, you shouldn't leave it blank, you should
  > instead use `NULL` :

  ```sql
  INSERT INTO sales
  VALUES (1, 11, '2020-01-01', NULL);
  ```

  > The approach above works well if the order of entry matches the order of the
  > table fields. If this is not the case you can specify the order as part of the
  > `INSERT` statement:

  ```sql
  INSERT INTO sales**(employee_id, sale_id, model_id, sale_date)**
  VALUES ('mhogan', 1, 11, '2020-01-01',);
  ```


path: zk/Instruction_set_architectures.md
sortable_path: zkInstruction_set_architectures.md
title: Instruction Set Architectures
lead: ## Summary
checksum: 38ccb65b129d4cbcb060765e9e148bd9527299c2ec783167fa140f86880ee1db
created: 2024-03-30 09:03:19.774620306+00:00
modified: 2024-04-09 06:25:57.380871694+00:00
metadata: {"tags":["CPU"]}
body:
  ## Summary

  - Computers with different hardware execute machine code instructions (e.g
    _add_) in different ways. They have different _instruction set architectures_
    (ISAs).

  - A family of CPUs that implement the core instructions of the
    [[Arithmetic_Logic_Unit]] in the same way share an ISA. Any software built for
    that ISA will work on any CPU that implements the ISA

  - The two main CPU architectures are ARM and x86

  ## Architecture is logical not physical

  The "architecture" of an ISA is logical rather than physical, i.e processors
  that share an ISA may work very differently on the hardware level whilst still
  adhering to the same instruction set.

  ## ARM and x86

  The two main processor architectures are ARM and x86. x86 processors were first
  introduced by Intel but are also made by AMD.

  ### x86

  - First introduced by Intel but also made by AMD.

  - Name stems from Intel's internal naming conventions - every new processor they
    introduced would have a letter designation ending with "86". Later they would
    adopt names such as "Pentium", "Celeron" etc. Regardless, these processors
    still fall under the "x86" family.

  - Over time, new instructions have been added to the x86 architecture but they
    all maintain backwards compatibility with preceding generations.

  - There have been different, successive generations of x86 corresponding to
    their [word-size](Signed_and_unsigned_numbers.md): 16-bit, 32-bit, 64-bit.
    Word size here just means how many bits the processor can work with at a time.
    A 34-bit processor can operate on values that are 32 bits in length. This
    means it has 32bit [[CPU_architecture#registers|registers]], a 32-bit data
    [[Bus|bus]], a 32-bit address bus and so on.

  - AMD introduced the first 64-bit processor, not Intel but they later caught up.
    A 64-bit x86 processor is often referred to as _x64_ or _x86-64_.

  ### ARM

  - x86 processors dominate the personal computer and server space

  - On movile devices ARM ISA predominates

  - The ARM company develops the architecture and standard which it licenses it to
    other companies to implement.

  - Preferred on mobile because they have reduced power consumption and lower
    costs compared to x86.

  - The processor is on a single chip that contains the CPU as well as memory and
    other hardware.

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

  # Instruction Set Architectures

  ## Summary

  - Computers with different hardware execute machine code instructions (e.g
    _add_) in different ways. They have different _instruction set architectures_
    (ISAs).

  - A family of CPUs that implement the core instructions of the
    [[Arithmetic_Logic_Unit]] in the same way share an ISA. Any software built for
    that ISA will work on any CPU that implements the ISA

  - The two main CPU architectures are ARM and x86

  ## Architecture is logical not physical

  The "architecture" of an ISA is logical rather than physical, i.e processors
  that share an ISA may work very differently on the hardware level whilst still
  adhering to the same instruction set.

  ## ARM and x86

  The two main processor architectures are ARM and x86. x86 processors were first
  introduced by Intel but are also made by AMD.

  ### x86

  - First introduced by Intel but also made by AMD.

  - Name stems from Intel's internal naming conventions - every new processor they
    introduced would have a letter designation ending with "86". Later they would
    adopt names such as "Pentium", "Celeron" etc. Regardless, these processors
    still fall under the "x86" family.

  - Over time, new instructions have been added to the x86 architecture but they
    all maintain backwards compatibility with preceding generations.

  - There have been different, successive generations of x86 corresponding to
    their [word-size](Signed_and_unsigned_numbers.md): 16-bit, 32-bit, 64-bit.
    Word size here just means how many bits the processor can work with at a time.
    A 34-bit processor can operate on values that are 32 bits in length. This
    means it has 32bit [[CPU_architecture#registers|registers]], a 32-bit data
    [[Bus|bus]], a 32-bit address bus and so on.

  - AMD introduced the first 64-bit processor, not Intel but they later caught up.
    A 64-bit x86 processor is often referred to as _x64_ or _x86-64_.

  ### ARM

  - x86 processors dominate the personal computer and server space

  - On movile devices ARM ISA predominates

  - The ARM company develops the architecture and standard which it licenses it to
    other companies to implement.

  - Preferred on mobile because they have reduced power consumption and lower
    costs compared to x86.

  - The processor is on a single chip that contains the CPU as well as memory and
    other hardware.


path: zk/Integrated_circuits.md
sortable_path: zkIntegrated_circuits.md
title: Integrated circuits
checksum: 73ebcb419feaa64000026c9642ef49530fa55a95c552243d76711cc350533d4d
created: 2024-03-30 09:03:19.796542348+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["logic-gates"]}
lead:
  An integrated circuit (IC) is a single unit that comprises several logic gates
  designed for the easy construction of
  [digital circuits](Digital_circuits.md).
  The terms "integrated circuit" and "chip" are often used interchangeably.

body:
  An integrated circuit (IC) is a single unit that comprises several logic gates
  designed for the easy construction of
  [digital circuits](Digital_circuits.md).
  The terms "integrated circuit" and "chip" are often used interchangeably.

  An IC puts the gates on a single piece of silicon that has electrical contact
  points called pins. There are two types of pins on an IC: input pins and output
  pins. Input pins are used to receive signals from other components in the
  circuit, while output pins are used to send signals to other components. The
  function of a pin is determined by the connection it has to the rest of the
  circuit and the type of signal it is intended to transmit or receive.

  The type we will look at are called **dual in-line packages** (DIPs). They are
  rectangular wth two parallel rows of pins. The pins make it easy to connect DIPs
  to a breadboard.

  _An integrated circuit and its use on a breadboard:_

  <img align="left" width="200" src="/home/thomas/repos/computer_science/img/integrated-circuit.jpeg">
  <img  width="200" src="/home/thomas/repos/computer_science/img/breadboard-DIP.jpg">

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

  # Integrated circuits

  An integrated circuit (IC) is a single unit that comprises several logic gates
  designed for the easy construction of
  [digital circuits](Digital_circuits.md).
  The terms "integrated circuit" and "chip" are often used interchangeably.

  An IC puts the gates on a single piece of silicon that has electrical contact
  points called pins. There are two types of pins on an IC: input pins and output
  pins. Input pins are used to receive signals from other components in the
  circuit, while output pins are used to send signals to other components. The
  function of a pin is determined by the connection it has to the rest of the
  circuit and the type of signal it is intended to transmit or receive.

  The type we will look at are called **dual in-line packages** (DIPs). They are
  rectangular wth two parallel rows of pins. The pins make it easy to connect DIPs
  to a breadboard.

  _An integrated circuit and its use on a breadboard:_

  <img align="left" width="200" src="/home/thomas/repos/computer_science/img/integrated-circuit.jpeg">
  <img  width="200" src="/home/thomas/repos/computer_science/img/breadboard-DIP.jpg">


path: zk/Interactive_staging.md
sortable_path: zkInteractive_staging.md
title: Interactive staging
checksum: fdef4d4ff9933facb66f0b9ca70df3e69d95d9376ecb3748f72eb15d9a91a064
created: 2024-03-30 09:03:19.801911632+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["git"]}
lead:
  With interactive staging we can stage sub-portions of files rather than the
  whole file. This makes commits much more targetted and atomic.

body:
  With interactive staging we can stage sub-portions of files rather than the
  whole file. This makes commits much more targetted and atomic.

  ## Basic use

  We enter interactive mode with:

  ```
  git add -i
  ```

  This opens an interface:

  ![](/img/git-interactive-mode-2.png)

  We select 2 and it lets us stage by number. If I enter 1, it will stage the
  first change.

  If we then go into 3 for revert, we can undo what we have staged.

  > Note that untracked files are not automatically added to the changes. We have
  > to manually add them with 4 ("add untracked").

  > Interactive mode doesn't commit anything, it is only for staging.

  ## Working with patches and hunks

  There's not much utility in interactive mode if you are commiting whole files.
  You can just use `git add [file]` and not bother with it.

  But it is useful for staging sub-portions of a file, which are called **hunks**.

  We access these via **patch mode**:

  ![](/img/git-patch-mode.png)

  ### Splitting hunks

  Git will try to suggest the best hunks it can but these will sometimes not
  reflect the changes you want. Sometimes they will reflect multiple changes
  rather than a single change.

  So long as there is an unchanged line between the different changes that it has
  selected as a hunk you can split the hunk further. You just hit `s` in patch
  mode, and it will repeatedly split the hunk each time until you get what you
  want.

  This is still an automated process and you may not find that the splitting
  results in a hunk you want to commit. In this case you can use manual splitting
  mode. We enter this with `e`.

  This will oped up Vim for the manual work to be done:

  ![](/img/git-manual-hunk.png)

  We use the diff symbols `+`, `-` and space to do this.

  ### Accessing patch mode directly

  If we just want to go straight to patch mode, without anything else:

  ```
  git add -p
  ```

  We can also work with patches in the following commands:

  ```
  git stash -p
  git reset -p
  git restore -p
  git commit -p
  ```

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

  # Interactive staging

  With interactive staging we can stage sub-portions of files rather than the
  whole file. This makes commits much more targetted and atomic.

  ## Basic use

  We enter interactive mode with:

  ```
  git add -i
  ```

  This opens an interface:

  ![](/img/git-interactive-mode-2.png)

  We select 2 and it lets us stage by number. If I enter 1, it will stage the
  first change.

  If we then go into 3 for revert, we can undo what we have staged.

  > Note that untracked files are not automatically added to the changes. We have
  > to manually add them with 4 ("add untracked").

  > Interactive mode doesn't commit anything, it is only for staging.

  ## Working with patches and hunks

  There's not much utility in interactive mode if you are commiting whole files.
  You can just use `git add [file]` and not bother with it.

  But it is useful for staging sub-portions of a file, which are called **hunks**.

  We access these via **patch mode**:

  ![](/img/git-patch-mode.png)

  ### Splitting hunks

  Git will try to suggest the best hunks it can but these will sometimes not
  reflect the changes you want. Sometimes they will reflect multiple changes
  rather than a single change.

  So long as there is an unchanged line between the different changes that it has
  selected as a hunk you can split the hunk further. You just hit `s` in patch
  mode, and it will repeatedly split the hunk each time until you get what you
  want.

  This is still an automated process and you may not find that the splitting
  results in a hunk you want to commit. In this case you can use manual splitting
  mode. We enter this with `e`.

  This will oped up Vim for the manual work to be done:

  ![](/img/git-manual-hunk.png)

  We use the diff symbols `+`, `-` and space to do this.

  ### Accessing patch mode directly

  If we just want to go straight to patch mode, without anything else:

  ```
  git add -p
  ```

  We can also work with patches in the following commands:

  ```
  git stash -p
  git reset -p
  git restore -p
  git commit -p
  ```


path: zk/Intersection_types.md
sortable_path: zkIntersection_types.md
title: Intersection types
checksum: f5d651d47122ae6fa8ecc204ac6dad3d06c12d1531df51dbfcbaafb1aaea5b91
created: 2024-03-30 09:03:19.805183635+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
lead:
  Like union types, intersection types allow us to combine the properties of
  existing primitive and custom types to make composites.

body:
  Like union types, intersection types allow us to combine the properties of
  existing primitive and custom types to make composites.

  ### Creating an intersection

  Say you have the following three types:

  ```ts
  type IConcert = {
    date: number;
    location: string;
    band: string;
  };

  type IConference = {
    date: number;
    location: string;
    keynoteSpeaker: string;
  };

  type IMeeting = {
    date: number;
    location: string;
    agenda: string[];
  };
  ```

  Each type corresponds to a different event and has two properties in common with
  the other events: `date` and `location`.

  Rather than repeat these properties for each individual type, we could create a
  general `IEvent` type and apply this as an intersection to each of the
  individual event types:

  ```ts
  // The common event type
  type IEvent = {
    date: number;
    location: string;
  };
  ```

  ```ts
  // The intersection

  type IConcert = IEvent & {
    band: string;
  };

  type IConference = IEvent & {
    keynoteSpeaker: string;
  };
  ```

  > In the case of interfaces, instead of using `&` we would write
  > `interface IConcert extends IEvent`

  ### Alternative method

  There is another equivalent way of intersecting the types. We could first define
  `IConcert` and `IConference` as their own types (without `extends` or `&`) and
  then intersect with, e.g., type IConcert = SelfStandingConcertType & IEvent.

  ### Benefits of intersections

  - It is more concise and we do not repeat ourselves needlessly
  - Common properties can be modelled **in one place** which makes updates and
    changes much easier: we do not have to change multiple type declarations, just
    change the 'master' type that is intersected with the variants.
  - It is more readable: we can see which properties are unique to the type
    without having to parse repeated properties.

  ## Set theory: understanding errors

  With intersections we literally use the & operator which discloses that the
  logic is equivalent to logical AND: a member of the intersection must have the
  properties of both types. In the case of primitive values this is impossible or,
  more accurately, it results in an empty set because, for example, `string` and
  `boolean` do not share any common values. This means you will get an error if
  you assign an intersection type to a value that does not have all the features
  of each custom type in the intersection.

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

  # Intersection types

  Like union types, intersection types allow us to combine the properties of
  existing primitive and custom types to make composites.

  ### Creating an intersection

  Say you have the following three types:

  ```ts
  type IConcert = {
    date: number;
    location: string;
    band: string;
  };

  type IConference = {
    date: number;
    location: string;
    keynoteSpeaker: string;
  };

  type IMeeting = {
    date: number;
    location: string;
    agenda: string[];
  };
  ```

  Each type corresponds to a different event and has two properties in common with
  the other events: `date` and `location`.

  Rather than repeat these properties for each individual type, we could create a
  general `IEvent` type and apply this as an intersection to each of the
  individual event types:

  ```ts
  // The common event type
  type IEvent = {
    date: number;
    location: string;
  };
  ```

  ```ts
  // The intersection

  type IConcert = IEvent & {
    band: string;
  };

  type IConference = IEvent & {
    keynoteSpeaker: string;
  };
  ```

  > In the case of interfaces, instead of using `&` we would write
  > `interface IConcert extends IEvent`

  ### Alternative method

  There is another equivalent way of intersecting the types. We could first define
  `IConcert` and `IConference` as their own types (without `extends` or `&`) and
  then intersect with, e.g., type IConcert = SelfStandingConcertType & IEvent.

  ### Benefits of intersections

  - It is more concise and we do not repeat ourselves needlessly
  - Common properties can be modelled **in one place** which makes updates and
    changes much easier: we do not have to change multiple type declarations, just
    change the 'master' type that is intersected with the variants.
  - It is more readable: we can see which properties are unique to the type
    without having to parse repeated properties.

  ## Set theory: understanding errors

  With intersections we literally use the & operator which discloses that the
  logic is equivalent to logical AND: a member of the intersection must have the
  properties of both types. In the case of primitive values this is impossible or,
  more accurately, it results in an empty set because, for example, `string` and
  `boolean` do not share any common values. This means you will get an error if
  you assign an intersection type to a value that does not have all the features
  of each custom type in the intersection.


path: zk/Intro_to_the_Unix_shell.md
sortable_path: zkIntro_to_the_Unix_shell.md
title: Unix based systems
checksum: 5ae0adb0aa72338a198a1ace4ecc38eb42cf5515fb938b953f3a447a1497fd93
created: 2024-03-30 09:03:19.808151053+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["shell"]}
lead:
  Many operating systems are based on the UNIX software architecture. macOS/OSX
  (Darwin) and GNU/Linux are two very popular examples. Most web servers run a
  version of Linux as their native OS thus a knowledge of command line for UNIX
  systems is invaluable for web developers. Windows systems are not UNIX based so
  everything that is written here is not applicable to the Windows command line
  program Command Prompt although there are obvious conceptual overlaps.

body:
  Many operating systems are based on the UNIX software architecture. macOS/OSX
  (Darwin) and GNU/Linux are two very popular examples. Most web servers run a
  version of Linux as their native OS thus a knowledge of command line for UNIX
  systems is invaluable for web developers. Windows systems are not UNIX based so
  everything that is written here is not applicable to the Windows command line
  program Command Prompt although there are obvious conceptual overlaps.

  UNIX was initially developed by AT&T and is still owned by them. GNU (the basis
  for Linux) is a recursive acronym for ’GNU is not UNIX’. Functionally it is the
  same as Unix however, it just doesn’t contain the proprietary code.

  ## Key terms

  ### Kernal

  The kernel is the central part of an operating system. It manages the operations
  of the computer and the hardware; most notably memory and CPU time. There are
  two types of kernels: a **micro-kernel**, which only contains basic
  functionality; and a **monolithic kernel**, which contains many device drivers.

  ### Shell

  A shell is a user interface for access to an operating system’s services. In
  general, operating system shells use either a command-line interface or GUI,
  depending on a computer’s role and particular operation. It is named a shell
  because it is the outermost layer around the operating system kernel

  ### Bash

  Bash is a Unix shell and command language written by Brian Fox for the GNU
  Project as a free software replacement for the Bourne shell. First released in
  1989, it has been distributed widely as the default login shell for most Linux
  distributions and Apple’s macOS.

  ## What is the command line?

  Command line is both a general computing concept and a specific utility. In its
  general sense, engaging with a computer via command line means inputting
  abbreviated written commands into a console or terminal window on a computer.
  The computer then runs these commands on the basis of the instructions you have
  given.

  You can get the computer to ‘do things’ (make files, connect to networks etc)
  via command line and you can use it to check the status of systems within your
  computer.

  This mode of engagement contrasts with the engagement facilitated by modern
  graphical user interfaces (GUIs) where the user navigates via a combination of
  keyboard and mouse inputs and interacts with windows whose functionality is
  usually self-explanatory and therefore more user-friendly. Command line predates
  GUIs but it is considered a more efficient and expansive means of interacting
  with the whole of your computer especially if you are a developer or programmer.

  In the more specific sense, the ‘command line’ refers to the location in the
  console window where you input your commands. The command line consists in a
  user name and the current directory you are in followed by a dollar sign and a
  cursor waiting for input.

  ## Basic orientation

  When you are using command line you are always situated somewhere within a file
  system. Typically this will be your own user files. directory structure By
  default you are always in your home directory, this is represented by the tilda
  symbol: `~`. This is why you see the tilda symbol as part of the command line.
  In command line, we do not use the term ‘folders’, instead they are called
  directories. (I will follow this convention from hereon.) You do not have to
  worry too much about remembering the names of specific folders and files you can
  always ask the computer to display them to you by using `ls` argument or `pwd`.

  ## Command line syntax

  The syntax of the language used to input commands is analogous to the grammar of
  natural languages. We have a verb that is operative on an object/noun and which
  can be modified through adverbs. The syntax of a command sequence is as follows:

  1. Command (verb): what we want to do
  1. Option (adverb): modifying the command - always starts with a hyphen
  1. Argument (noun/object): what we want our command to operate on

  We will see that not all commands require arguments, but this is the general
  structure

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

  ## Unix based systems

  Many operating systems are based on the UNIX software architecture. macOS/OSX
  (Darwin) and GNU/Linux are two very popular examples. Most web servers run a
  version of Linux as their native OS thus a knowledge of command line for UNIX
  systems is invaluable for web developers. Windows systems are not UNIX based so
  everything that is written here is not applicable to the Windows command line
  program Command Prompt although there are obvious conceptual overlaps.

  UNIX was initially developed by AT&T and is still owned by them. GNU (the basis
  for Linux) is a recursive acronym for ’GNU is not UNIX’. Functionally it is the
  same as Unix however, it just doesn’t contain the proprietary code.

  ## Key terms

  ### Kernal

  The kernel is the central part of an operating system. It manages the operations
  of the computer and the hardware; most notably memory and CPU time. There are
  two types of kernels: a **micro-kernel**, which only contains basic
  functionality; and a **monolithic kernel**, which contains many device drivers.

  ### Shell

  A shell is a user interface for access to an operating system’s services. In
  general, operating system shells use either a command-line interface or GUI,
  depending on a computer’s role and particular operation. It is named a shell
  because it is the outermost layer around the operating system kernel

  ### Bash

  Bash is a Unix shell and command language written by Brian Fox for the GNU
  Project as a free software replacement for the Bourne shell. First released in
  1989, it has been distributed widely as the default login shell for most Linux
  distributions and Apple’s macOS.

  ## What is the command line?

  Command line is both a general computing concept and a specific utility. In its
  general sense, engaging with a computer via command line means inputting
  abbreviated written commands into a console or terminal window on a computer.
  The computer then runs these commands on the basis of the instructions you have
  given.

  You can get the computer to ‘do things’ (make files, connect to networks etc)
  via command line and you can use it to check the status of systems within your
  computer.

  This mode of engagement contrasts with the engagement facilitated by modern
  graphical user interfaces (GUIs) where the user navigates via a combination of
  keyboard and mouse inputs and interacts with windows whose functionality is
  usually self-explanatory and therefore more user-friendly. Command line predates
  GUIs but it is considered a more efficient and expansive means of interacting
  with the whole of your computer especially if you are a developer or programmer.

  In the more specific sense, the ‘command line’ refers to the location in the
  console window where you input your commands. The command line consists in a
  user name and the current directory you are in followed by a dollar sign and a
  cursor waiting for input.

  ## Basic orientation

  When you are using command line you are always situated somewhere within a file
  system. Typically this will be your own user files. directory structure By
  default you are always in your home directory, this is represented by the tilda
  symbol: `~`. This is why you see the tilda symbol as part of the command line.
  In command line, we do not use the term ‘folders’, instead they are called
  directories. (I will follow this convention from hereon.) You do not have to
  worry too much about remembering the names of specific folders and files you can
  always ask the computer to display them to you by using `ls` argument or `pwd`.

  ## Command line syntax

  The syntax of the language used to input commands is analogous to the grammar of
  natural languages. We have a verb that is operative on an object/noun and which
  can be modified through adverbs. The syntax of a command sequence is as follows:

  1. Command (verb): what we want to do
  1. Option (adverb): modifying the command - always starts with a hyphen
  1. Argument (noun/object): what we want our command to operate on

  We will see that not all commands require arguments, but this is the general
  structure


path: zk/Introduction.md
sortable_path: zkIntroduction.md
title: MongoDB: Introduction
checksum: 9b86c458782b52658c193987aaea5a39878e69e8b06e1a618746e0a97de09b7d
created: 2024-03-30 09:03:19.81113123+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["mongo-db"]}
lead:
  MongoDB is not a relational database system like SQL, instead it is
  document-based which means you do not neeed to design database tables and
  schema, you are simply storing data as JSON and there is no transformation of
  the data.

body:
  MongoDB is not a relational database system like SQL, instead it is
  document-based which means you do not neeed to design database tables and
  schema, you are simply storing data as JSON and there is no transformation of
  the data.

  Most of the notes on Mongo will introduce it within the context of Node.js
  backend. We will be sending Javascript objects and arrays to Mongo and returning
  them as JSON.

  ## Databases, collections, documents

  Although Mongo is not a relational database it has a structure that we can
  understand in relation to that paradigm. A **database** is obviously the overall
  structure. It comprises **collections** which are organised sets of data that
  are analagous to [tables](Relational_database_architecture.md#table)
  in RDBs. Within each collection are a series of **documents** which we can think
  of as being equivalent to [rows](Relational_database_architecture.md)
  in RDB table: units that comprise the collection.

  A document is a container comprising key-value pairs in the manner of an object.

  ![](/img/mongo-db-structure.svg)

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

  # MongoDB: Introduction

  MongoDB is not a relational database system like SQL, instead it is
  document-based which means you do not neeed to design database tables and
  schema, you are simply storing data as JSON and there is no transformation of
  the data.

  Most of the notes on Mongo will introduce it within the context of Node.js
  backend. We will be sending Javascript objects and arrays to Mongo and returning
  them as JSON.

  ## Databases, collections, documents

  Although Mongo is not a relational database it has a structure that we can
  understand in relation to that paradigm. A **database** is obviously the overall
  structure. It comprises **collections** which are organised sets of data that
  are analagous to [tables](Relational_database_architecture.md#table)
  in RDBs. Within each collection are a series of **documents** which we can think
  of as being equivalent to [rows](Relational_database_architecture.md)
  in RDB table: units that comprise the collection.

  A document is a container comprising key-value pairs in the manner of an object.

  ![](/img/mongo-db-structure.svg)


path: zk/Inversion_of_operators.md
sortable_path: zkInversion_of_operators.md
title: Addition, subtraction
lead:
  Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6
  $$

checksum: b0619f98d78539336feb4a54fe6c92f7b523edb763e77e3ef30a0130d98ae458
created: 2024-03-30 09:03:19.825906309+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["prealgebra"]}
body:
  Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6
  $$

  Subtraction is the inverse of addition: $$(x + a) - a = x$$ $$ (3 + 2) - 2 = 3$$

  Division is the inverse of multiplication $$ \frac{a \cdot x}{a} = x$$
  $$ \frac{6
  \cdot 3}{6} = 3$$

  Multiplication is the inverse of division $$ a \cdot \frac{x}{a} = x$$
  $$ 2 \cdot
  \frac{8}{2} = 8$$

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

  ## Addition, subtraction

  Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6
  $$

  Subtraction is the inverse of addition: $$(x + a) - a = x$$ $$ (3 + 2) - 2 = 3$$

  Division is the inverse of multiplication $$ \frac{a \cdot x}{a} = x$$
  $$ \frac{6
  \cdot 3}{6} = 3$$

  Multiplication is the inverse of division $$ a \cdot \frac{x}{a} = x$$
  $$ 2 \cdot
  \frac{8}{2} = 8$$


path: zk/Iterating.md
sortable_path: zkIterating.md
title: Iterating through data
checksum: 790250efa0da7a6ea6bec9a10ddecd192aa2ce87209786cdce922f72cbfef6ef
created: 2024-03-30 09:03:19.829017239+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript","react","react-hooks"]}
lead:
  As React follows the **functional programming paradigm** and state versioning,
  looping is not the recommended way of engineering repeated processes since it
  presupposes pre-existing state. Instead React relies on the functional methods
  of modern JavaScript such as `map` , `filter` , `reduce` to output the values of
  iterative data structures.

body:
  As React follows the **functional programming paradigm** and state versioning,
  looping is not the recommended way of engineering repeated processes since it
  presupposes pre-existing state. Instead React relies on the functional methods
  of modern JavaScript such as `map` , `filter` , `reduce` to output the values of
  iterative data structures.

  Instead of mutating the state of a reference type, these methods are
  'non-destructive': they create a new array that meets the requirements set in
  the particular method. These newly created arrays and objects therefore form the
  basis of the state versioning, because each new array that is engendered, is a
  new state configuration.

  ## The `key` attribute

  The `key` attribute is a special React attribute that should be added to the
  elements of any listed content (i.e numbered lists, unordered lists, `select`
  options etc; basically anything rendered that comes about through mapping arrays
  and objects).

  This enables React to monitor the state of the list: which items have changed,
  been added, or removed.

  The best way to pick a key is to use a string that uniquely identifies a list
  item among its siblings.

  For instance, using our earlier `numbers` array, assuming each number is unique,
  we could use the number itself as the key:

  ```jsx
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) => (
    <li key={number.toString()}> {number}</li>
  ));
  ```

  > Keys have to be unique to the individual element and not shared by any other
  > sibling. But they do not need to be globally unique. You could use the same
  > `key` name in another array list.

  > Keys should be string types, not other data types. You can use the
  > `.toString()` method to ensure this is the case.

  If you can't find a unique basis for naming a series of keys, you can use the
  `index` value of the `Array.map` function. This should only be used if the order
  of the items doesn't change. An example:

  ```jsx
  class List extends React.Component {
    render() {
      const demoArr = ["cat", "dog", "mouse"];
      return (
          <ul>
            {demoArr.map((x, index) => (
              <li key={index}>{x}</li>
            ))}
          </ul>
        </div>
      );
    }
  }
  ```

  ## Demonstration

  ### Source data

  ```jsx
  const blogData = [
    {
      id: 1,
      date: "2020-11-15",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
    {
      id: 2,
      date: "2020-08-11",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
  ];
  ```

  ### Child component

  ```jsx
  export default function BlogPreviewCard(props) {
    return (
      <Card>
        <CardMedia image={props.image} />
        <CardContent>
          <Typography>{props.date}</Typography>
          <Typography>{props.title}</Typography>
          <Typography>{props.shortByline}</Typography>
        </CardContent>
        <CardActions>
          <Button>Read</Button>
        </CardActions>
      </Card>
    );
  }
  ```

  ### Iteration

  ```jsx
  // This uses the Material-UI grid component

  import BlogPreviewCard from "../components/BlogPreviewCard"
  import blogData from "../data/blogData

  export default function BlogPreviewSet() {
    return (
      <Grid>
        {blogDummyData.map((data, index) => (
          <Grid key={index}>
            <BlogPreviewCard key={index} {...data} />
          </Grid>
        ))}
      </Grid>
    )
  }
  ```

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

  # Iterating through data

  As React follows the **functional programming paradigm** and state versioning,
  looping is not the recommended way of engineering repeated processes since it
  presupposes pre-existing state. Instead React relies on the functional methods
  of modern JavaScript such as `map` , `filter` , `reduce` to output the values of
  iterative data structures.

  Instead of mutating the state of a reference type, these methods are
  'non-destructive': they create a new array that meets the requirements set in
  the particular method. These newly created arrays and objects therefore form the
  basis of the state versioning, because each new array that is engendered, is a
  new state configuration.

  ## The `key` attribute

  The `key` attribute is a special React attribute that should be added to the
  elements of any listed content (i.e numbered lists, unordered lists, `select`
  options etc; basically anything rendered that comes about through mapping arrays
  and objects).

  This enables React to monitor the state of the list: which items have changed,
  been added, or removed.

  The best way to pick a key is to use a string that uniquely identifies a list
  item among its siblings.

  For instance, using our earlier `numbers` array, assuming each number is unique,
  we could use the number itself as the key:

  ```jsx
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) => (
    <li key={number.toString()}> {number}</li>
  ));
  ```

  > Keys have to be unique to the individual element and not shared by any other
  > sibling. But they do not need to be globally unique. You could use the same
  > `key` name in another array list.

  > Keys should be string types, not other data types. You can use the
  > `.toString()` method to ensure this is the case.

  If you can't find a unique basis for naming a series of keys, you can use the
  `index` value of the `Array.map` function. This should only be used if the order
  of the items doesn't change. An example:

  ```jsx
  class List extends React.Component {
    render() {
      const demoArr = ["cat", "dog", "mouse"];
      return (
          <ul>
            {demoArr.map((x, index) => (
              <li key={index}>{x}</li>
            ))}
          </ul>
        </div>
      );
    }
  }
  ```

  ## Demonstration

  ### Source data

  ```jsx
  const blogData = [
    {
      id: 1,
      date: "2020-11-15",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
    {
      id: 2,
      date: "2020-08-11",
          shortByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. ",
      longByline:
        "Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor.",
      image: blogImage,
    },
  ];
  ```

  ### Child component

  ```jsx
  export default function BlogPreviewCard(props) {
    return (
      <Card>
        <CardMedia image={props.image} />
        <CardContent>
          <Typography>{props.date}</Typography>
          <Typography>{props.title}</Typography>
          <Typography>{props.shortByline}</Typography>
        </CardContent>
        <CardActions>
          <Button>Read</Button>
        </CardActions>
      </Card>
    );
  }
  ```

  ### Iteration

  ```jsx
  // This uses the Material-UI grid component

  import BlogPreviewCard from "../components/BlogPreviewCard"
  import blogData from "../data/blogData

  export default function BlogPreviewSet() {
    return (
      <Grid>
        {blogDummyData.map((data, index) => (
          <Grid key={index}>
            <BlogPreviewCard key={index} {...data} />
          </Grid>
        ))}
      </Grid>
    )
  }
  ```


path: zk/Iterating_through_complex_data_structures_in_Python.md
sortable_path: zkIterating_through_complex_data_structures_in_Python.md
title: Iterating through complex data structures in Python
lead: // TODO: sections :
checksum: 98d8eba3f0d6f72ac9997837f53a665a43a5ce07f0d5dc1ab46a6bc28d06c031
created: 2024-03-30 09:03:19.83326494+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  // TODO: sections :

  - Looping, mapping, filtering lists
  - Looping, mapping, filtering dictionaries
  - As above with multi-dimensional lists
  - As above with list of dictionaries

  ## Complex data structures

  In JavaScript when we return data from an API we tend to use an array of objects
  as the canonical form of a repeating iterable, e.g:

  ```js
  const data = [
    {
      name: "Thomas",
      age: 35,
    },
    {
      name: "Joe",
      age: 75,
    },
  ];
  ```

  In Python there are two common ways to handle similar data structures:

  - A list of lists:
    ```py
    data = [
      ["Thomas", 35],
      ["Joe", 75]
    ]
    ```
  - A list of dictionaries:
    ```py
    data = [
       {"name": "Thomas", "age": 35},
       {"name": "Joe", "age": 75},
    ]
    ```

  ## Multi-dimensional lists (list of lists)

  ### Sorting by common property

  Assuming the sub-lists have an identical structure, you can
  [sort](Sorting_lists_in_Python.md) them by
  a common property by passing a
  [lambda function](Lambdas_in_Python.md) to
  the `key` value of `sorted()` and `.sort()`.

  For example, to sort the following list of lists by the second `age` property:

  ```python
  people = [["Alice", 30], ["Bob", 25], ["Clare", 35], ["Dave", 28]]
  ```

  Using `sorted()`:

  ```py
  sorted_people = sorted(people, key=lambda x: x[1])
  print(sorted_people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  Using `.sort`:

  ```py
  people.sort(key=lambda x: x[1])
  print(people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  If you want to sort by name instead, you could change the lambda function to
  `lambda x: x[0]`:

  ```python
  # Using sorted()
  sorted_people = sorted(people, key=lambda x: x[0])
  print(sorted_people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]

  # Using .sort()
  people.sort(key=lambda x: x[0])
  print(people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]
  ```

  ### Updating an inner value within a multidimensional list

  In the following example we have a list of the following structure:

  ```py
  data = [
      ["1688582410", "Article One"],
      ["1688647447", "Article Two"],
      ["1689023491", "Article Three"],
  ]
  ```

  Below we use
  [map](Map_and_filter_in_Python.md) and a
  [lambda function](Lambdas_in_Python.md) to
  convert the first element of each iner list from a Unix timestamp to a readable
  string:

  ```py
   readable_date = list(map(lambda i: [convert_timestamp(i[0])] + i[1:], date))
  ```

  We could also use
  [list comprehension](List_comprehension_etc.md)
  to achieve the same outcome:

  ```py
  readable_date = [[convert_timestamp(i[0])] + i[1:] for i in data]
  ```

  ### Filter elements in a multidimensional list

  Say we have the following data structure:

  ```py
  name_age = [ ["Anthony", 16], ["Christopher", 22], ["James", 6] ]
  ```

  We can return only the people who are older than 18 with the following `filter`
  function and `lambda`:

  ```py
  filtered_ages = list(filter(lambda person: person[1] > 18, name_age))
  ```

  ### Remove duplicate entries from multidimensional list

  If we are just working with a normal list, a quick way to remove duplicates is
  just:

  ```py
  our_list = [9, 9, 3, 2]
  unique = list(set(our_list))
  ```

  This won't work with a list of lists because lists are not hashable.

  There are different approaches. Let's say we have the following multidimensional
  list:

  ```py
  dummy_data = [
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
      ["1688582410", "Article One", "Lorem ipsum...", "https://example.com"],
      ["1688647447", "Article Two", "Lorem ipsum...", "https://example.com"],
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
  ]
  ```

  Here is one method:

  ```py
  unique = []
  seen = set()
  for element in dummy_data:
      element_to_check = element[0]
      if element_to_check not in seen:
          unique.append(element)
          seen.add(element_to_check)
  print(unique)
  ```

  We designate a unique property in the inner lists and then store a unique record
  of each. If the seen set doesn't have the incoming element, we add it to the
  unique array.

  It's more efficient to use a dictionary because it allows us to do this in 0(1)
  time rather than 0(n):

  ```py
  unique_dict = {x[0]: x for x in dummy_data}
  unique_data = list(unique_dict.values())
  ```

  This approach leverages the fact that a dictionary cannot have duplicate keys:
  if you try to insert an element with a key that already exists, the new value
  will simply overwrite the old value for that key.

  Accordingly, we create a dictionary which uses the unique key in each list as
  the key of each dictionary entry via
  [dictionary comprehension](List_comprehension_etc.md#dictionary-comprehension)
  that loops through each value in the inner lists of the multidimensional array.
  We then parse the values of the dictionary into a list.

  ## List of dictionaries

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

  # Iterating through complex data structures in Python

  // TODO: sections :

  - Looping, mapping, filtering lists
  - Looping, mapping, filtering dictionaries
  - As above with multi-dimensional lists
  - As above with list of dictionaries

  ## Complex data structures

  In JavaScript when we return data from an API we tend to use an array of objects
  as the canonical form of a repeating iterable, e.g:

  ```js
  const data = [
    {
      name: "Thomas",
      age: 35,
    },
    {
      name: "Joe",
      age: 75,
    },
  ];
  ```

  In Python there are two common ways to handle similar data structures:

  - A list of lists:
    ```py
    data = [
      ["Thomas", 35],
      ["Joe", 75]
    ]
    ```
  - A list of dictionaries:
    ```py
    data = [
       {"name": "Thomas", "age": 35},
       {"name": "Joe", "age": 75},
    ]
    ```

  ## Multi-dimensional lists (list of lists)

  ### Sorting by common property

  Assuming the sub-lists have an identical structure, you can
  [sort](Sorting_lists_in_Python.md) them by
  a common property by passing a
  [lambda function](Lambdas_in_Python.md) to
  the `key` value of `sorted()` and `.sort()`.

  For example, to sort the following list of lists by the second `age` property:

  ```python
  people = [["Alice", 30], ["Bob", 25], ["Clare", 35], ["Dave", 28]]
  ```

  Using `sorted()`:

  ```py
  sorted_people = sorted(people, key=lambda x: x[1])
  print(sorted_people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  Using `.sort`:

  ```py
  people.sort(key=lambda x: x[1])
  print(people)
  # Output: [['Bob', 25], ['Dave', 28], ['Alice', 30], ['Clare', 35]]
  ```

  If you want to sort by name instead, you could change the lambda function to
  `lambda x: x[0]`:

  ```python
  # Using sorted()
  sorted_people = sorted(people, key=lambda x: x[0])
  print(sorted_people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]

  # Using .sort()
  people.sort(key=lambda x: x[0])
  print(people)
  # Output: [['Alice', 30], ['Bob', 25], ['Clare', 35], ['Dave', 28]]
  ```

  ### Updating an inner value within a multidimensional list

  In the following example we have a list of the following structure:

  ```py
  data = [
      ["1688582410", "Article One"],
      ["1688647447", "Article Two"],
      ["1689023491", "Article Three"],
  ]
  ```

  Below we use
  [map](Map_and_filter_in_Python.md) and a
  [lambda function](Lambdas_in_Python.md) to
  convert the first element of each iner list from a Unix timestamp to a readable
  string:

  ```py
   readable_date = list(map(lambda i: [convert_timestamp(i[0])] + i[1:], date))
  ```

  We could also use
  [list comprehension](List_comprehension_etc.md)
  to achieve the same outcome:

  ```py
  readable_date = [[convert_timestamp(i[0])] + i[1:] for i in data]
  ```

  ### Filter elements in a multidimensional list

  Say we have the following data structure:

  ```py
  name_age = [ ["Anthony", 16], ["Christopher", 22], ["James", 6] ]
  ```

  We can return only the people who are older than 18 with the following `filter`
  function and `lambda`:

  ```py
  filtered_ages = list(filter(lambda person: person[1] > 18, name_age))
  ```

  ### Remove duplicate entries from multidimensional list

  If we are just working with a normal list, a quick way to remove duplicates is
  just:

  ```py
  our_list = [9, 9, 3, 2]
  unique = list(set(our_list))
  ```

  This won't work with a list of lists because lists are not hashable.

  There are different approaches. Let's say we have the following multidimensional
  list:

  ```py
  dummy_data = [
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
      ["1688582410", "Article One", "Lorem ipsum...", "https://example.com"],
      ["1688647447", "Article Two", "Lorem ipsum...", "https://example.com"],
      ["1689023491", "Article Three", "Lorem ipsum...", "https://example.com"],
  ]
  ```

  Here is one method:

  ```py
  unique = []
  seen = set()
  for element in dummy_data:
      element_to_check = element[0]
      if element_to_check not in seen:
          unique.append(element)
          seen.add(element_to_check)
  print(unique)
  ```

  We designate a unique property in the inner lists and then store a unique record
  of each. If the seen set doesn't have the incoming element, we add it to the
  unique array.

  It's more efficient to use a dictionary because it allows us to do this in 0(1)
  time rather than 0(n):

  ```py
  unique_dict = {x[0]: x for x in dummy_data}
  unique_data = list(unique_dict.values())
  ```

  This approach leverages the fact that a dictionary cannot have duplicate keys:
  if you try to insert an element with a key that already exists, the new value
  will simply overwrite the old value for that key.

  Accordingly, we create a dictionary which uses the unique key in each list as
  the key of each dictionary entry via
  [dictionary comprehension](List_comprehension_etc.md#dictionary-comprehension)
  that loops through each value in the inner lists of the multidimensional array.
  We then parse the values of the dictionary into a list.

  ## List of dictionaries


path: zk/Joins_in_SQL.md
sortable_path: zkJoins_in_SQL.md
title: Joins in SQL
checksum: 6ee42065b32ff486f22010844d1a48a85d4ecfbd9b6d07234ada2398a4467f33
created: 2024-03-30 09:03:19.864689426+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["SQL"]}
lead:
  <!-- Once a relationship has been created using primary and foreign keys (as detailed in the previous section), you are able to combine and integrate data from the different tables. This is known as performing **joins.** -->

body:
  <!-- Once a relationship has been created using primary and foreign keys (as detailed in the previous section), you are able to combine and integrate data from the different tables. This is known as performing **joins.** -->

  We perform joins in SQL when we combine and integrate data from multiple tables.
  In order for a join to be possible, **the tables you want to join must have rows
  that share the same fields**.

  The output will be represented as a table but it is virtual, not an actual
  table. (If you wish to actually create a table or a view off the back of a join
  operation you should combine the join with the
  [`CREATE TABLE`](Create_an_SQL_table.md) command etc.)

  ## Inner joins

  > An inner join combines rows that have matching values in two or more tables.
  > This join is based on the existence of a common field between the tables and
  > is used to retrieve data that appears in both tables.

  The following is a good scenario for creating an inner join:

  > We want to create a list of the name of all computers that have been sold and
  > when they were sold.

  We will be working from the following tables.

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |

  The data points we need are not provided in a single table. `sales` gives us the
  `saleDate` but not the `modelName`. In order to meet our requirements we must
  perform a join that combines `modelName` from the `model` table and `saleDate`
  from the `sale` table to give us a table like the following:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |

  We can do this by utilising the field they have in common: `modelId`.

  ### Implementation

  The general syntax for creating an inner join from two tables that share a
  common field is as follows:

  ```sql
  SELECT column_name(s)
  FROM table1
  INNER JOIN table2
  ON table1.columnName = table2.columnName
  ```

  So basically, look at `table1` and `table2` return the rows where the fields for
  `table1` and `table2` match, where these fields are the ones specified in the
  `SELECT`.

  Applied to our example:

  ```sql
  SELECT model.modelName, sales.saleDate -- Name the fields
  FROM model -- Specify the first table
  INNER JOIN sales -- Specify the second table
  ON model.model_id = sales.model_id; -- Specify the match criteria
  ```

  > It is important to note that the match must _be_ a match. Both tables must
  > have a row with the same value for `modelId` for the join to be applied. If
  > there is a `modelId` of X in `sales` but no corresponding X for `modelId` in
  > `model`, nothing will be returned for that row.

  We can represent the logical relationship that obtains between the `sales` and
  `model` tables as follows:

  ![](/img/sql-inner-join.png)

  ## Outer joins

  Outer joins are joins that return matched values **and unmatched** values from
  either or both tables.

  To explain outer joins we need to expand our data set:

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |
  | 4      |         | 2020-03-01 | msmelk     | 699.99  |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |
  | 66      | Google Chromebook    |

  > We will assume that `sales` is our left table and `model` our right.

  ### Left outer join

  A left outer join returns all records from the left or first table and the
  matching records from the right or second table. If there are not matches on the
  right then only the left data is returned.

  In the context of our dataset, in the _sales_ database we have a row where there
  is a sale with no corresponding `modelId`. If we performed a left outer join
  this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |

  The logical relationship sustained between `sales` and `model` by a left inner
  join is represented in the following diagram:

  ![](/img/sql-left-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  LEFT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  LEFT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Right outer join

  This is the inverse of the right outer join. A right outer join returns all
  records from the right or second table and the matching records from the left or
  first table. If there are not matches on the left then only the right data is
  returned.

  In the context of our dataset, in the _model_ database we have a row where there
  is a `modelId` without a corresponding entry in the `sales` table. If we
  performed a right outer join this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  | Google Chromebook    |            |

  The logical relationship sustained between `sales` and `model` by a right inner
  join is represented in the following diagram:

  ![](/img/sql-right-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  RIGHT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  RIGHT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Full outer join

  The full outer join returns all rows when there is a match in either the left or
  the right table. So, for table A and table B the full join returns all rows in A
  and all rows in B including those where there is a match between A and B. This
  is the broadest type of join and can often result in large data sets.

  In the context of our dataset it would result in the following table being
  generated:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |
  | Google Chromebook    |            |

  Represented by the following diagram:

  ![](/img/sql-full-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  FULL OUTER JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  FULL OUTER JOIN model
  ON sales.modelId = model.modelId
  ```

  This type of join is used when you want to discern when there is _not_ a match
  between two fields across tables. For example: imagine that you wanted a list of
  computers that had never been sold. In this case, you would be interested in
  rows where there is a `model_id` without a corresponding `sales_id` .

  In SQL this would be achieved with:

  ```sql
  SELECT model.name, sales.sale_date
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id;
  ```

  Note that this would return all the model names but where there isn't a sale
  data, `NULL` would be returned. This is an **important distinction :** the outer
  join method doesn't just return the rows with a `NULL` value for `sale_date` as
  we might expect. It returns all models along with those that have not been sold.
  This is because it is oriented to the "left" table, equivalent to the table in
  the SQL that we cited first with the `on` keyword.

  > A left outer join returns all the records from the left (model) table and
  > those that match in the right (sales) table. Where there are no matching
  > records in the right (sales) table, a `NULL` value is returned.

  A **right outer join**, often referred to as a right join, is the opposite of a
  left outer; it returns all the records from the right table and those that match
  in the left table. In our scenario this would be all the models that had a
  `sale_date` including models that didn't have a `sale_date` , i.e which returned
  `NULL`

  Finally, a **full outer join** returns all the records from both tables, and
  where a record cannot be matched, a NULL value is returned. So this would mean
  there could be `NULL`s in both fields of the returned rows.

  We can combine multiple types of join in the same SQL query:

  ```sql
  SELECT model.name, sales.sale_date, manufacturer.url
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id
  INNER JOIN manufacturer on model.manufacturer_id = manufacturer.manufacturer_id;
  ```

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

  # Joins in SQL

  <!-- Once a relationship has been created using primary and foreign keys (as detailed in the previous section), you are able to combine and integrate data from the different tables. This is known as performing **joins.** -->

  We perform joins in SQL when we combine and integrate data from multiple tables.
  In order for a join to be possible, **the tables you want to join must have rows
  that share the same fields**.

  The output will be represented as a table but it is virtual, not an actual
  table. (If you wish to actually create a table or a view off the back of a join
  operation you should combine the join with the
  [`CREATE TABLE`](Create_an_SQL_table.md) command etc.)

  ## Inner joins

  > An inner join combines rows that have matching values in two or more tables.
  > This join is based on the existence of a common field between the tables and
  > is used to retrieve data that appears in both tables.

  The following is a good scenario for creating an inner join:

  > We want to create a list of the name of all computers that have been sold and
  > when they were sold.

  We will be working from the following tables.

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |

  The data points we need are not provided in a single table. `sales` gives us the
  `saleDate` but not the `modelName`. In order to meet our requirements we must
  perform a join that combines `modelName` from the `model` table and `saleDate`
  from the `sale` table to give us a table like the following:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |

  We can do this by utilising the field they have in common: `modelId`.

  ### Implementation

  The general syntax for creating an inner join from two tables that share a
  common field is as follows:

  ```sql
  SELECT column_name(s)
  FROM table1
  INNER JOIN table2
  ON table1.columnName = table2.columnName
  ```

  So basically, look at `table1` and `table2` return the rows where the fields for
  `table1` and `table2` match, where these fields are the ones specified in the
  `SELECT`.

  Applied to our example:

  ```sql
  SELECT model.modelName, sales.saleDate -- Name the fields
  FROM model -- Specify the first table
  INNER JOIN sales -- Specify the second table
  ON model.model_id = sales.model_id; -- Specify the match criteria
  ```

  > It is important to note that the match must _be_ a match. Both tables must
  > have a row with the same value for `modelId` for the join to be applied. If
  > there is a `modelId` of X in `sales` but no corresponding X for `modelId` in
  > `model`, nothing will be returned for that row.

  We can represent the logical relationship that obtains between the `sales` and
  `model` tables as follows:

  ![](/img/sql-inner-join.png)

  ## Outer joins

  Outer joins are joins that return matched values **and unmatched** values from
  either or both tables.

  To explain outer joins we need to expand our data set:

  _The `sales` table_:

  | saleId | modelId | saleDate   | employeeId | price   |
  | ------ | ------- | ---------- | ---------- | ------- |
  | 1      | 44      | 2020-07-27 | tbishop    | 40.99   |
  | 2      | 22      | 2021-02-05 | tbishop    | 986.99  |
  | 3      | 14      | 2022-11-16 | tgnomay    | 1248.99 |
  | 4      |         | 2020-03-01 | msmelk     | 699.99  |

  _The `model` table_:

  | modelId | modelName            |
  | ------- | -------------------- |
  | 44      | Raspberry Pi Model A |
  | 22      | Apple MacBook Air    |
  | 14      | Lenovo ThinkPad P15  |
  | 66      | Google Chromebook    |

  > We will assume that `sales` is our left table and `model` our right.

  ### Left outer join

  A left outer join returns all records from the left or first table and the
  matching records from the right or second table. If there are not matches on the
  right then only the left data is returned.

  In the context of our dataset, in the _sales_ database we have a row where there
  is a sale with no corresponding `modelId`. If we performed a left outer join
  this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |

  The logical relationship sustained between `sales` and `model` by a left inner
  join is represented in the following diagram:

  ![](/img/sql-left-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  LEFT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  LEFT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Right outer join

  This is the inverse of the right outer join. A right outer join returns all
  records from the right or second table and the matching records from the left or
  first table. If there are not matches on the left then only the right data is
  returned.

  In the context of our dataset, in the _model_ database we have a row where there
  is a `modelId` without a corresponding entry in the `sales` table. If we
  performed a right outer join this would give us the following table in return:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  | Google Chromebook    |            |

  The logical relationship sustained between `sales` and `model` by a right inner
  join is represented in the following diagram:

  ![](/img/sql-right-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  RIGHT JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  RIGHT JOIN model
  ON sales.modelId = model.modelId
  ```

  ### Full outer join

  The full outer join returns all rows when there is a match in either the left or
  the right table. So, for table A and table B the full join returns all rows in A
  and all rows in B including those where there is a match between A and B. This
  is the broadest type of join and can often result in large data sets.

  In the context of our dataset it would result in the following table being
  generated:

  | modelName            | sale_date  |
  | -------------------- | ---------- |
  | Raspberry Pi Model A | 2015-02-01 |
  | Apple Macbook Air    | 2018-11-01 |
  | Lenovo ThinkPad P15  | 2018-11-01 |
  |                      | 2020-03-01 |
  | Google Chromebook    |            |

  Represented by the following diagram:

  ![](/img/sql-full-outer-join.png)

  #### Implementation

  The schematic form:

  ```sql
  SELECT column_name(s)
  FROM table1
  FULL OUTER JOIN table2
  ON table1.column_name = table2.column_name;
  ```

  Applied to our data set:

  ```sql
  SELECT model.modelName, sales.saleDate
  FROM sales.saleDate
  FULL OUTER JOIN model
  ON sales.modelId = model.modelId
  ```

  This type of join is used when you want to discern when there is _not_ a match
  between two fields across tables. For example: imagine that you wanted a list of
  computers that had never been sold. In this case, you would be interested in
  rows where there is a `model_id` without a corresponding `sales_id` .

  In SQL this would be achieved with:

  ```sql
  SELECT model.name, sales.sale_date
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id;
  ```

  Note that this would return all the model names but where there isn't a sale
  data, `NULL` would be returned. This is an **important distinction :** the outer
  join method doesn't just return the rows with a `NULL` value for `sale_date` as
  we might expect. It returns all models along with those that have not been sold.
  This is because it is oriented to the "left" table, equivalent to the table in
  the SQL that we cited first with the `on` keyword.

  > A left outer join returns all the records from the left (model) table and
  > those that match in the right (sales) table. Where there are no matching
  > records in the right (sales) table, a `NULL` value is returned.

  A **right outer join**, often referred to as a right join, is the opposite of a
  left outer; it returns all the records from the right table and those that match
  in the left table. In our scenario this would be all the models that had a
  `sale_date` including models that didn't have a `sale_date` , i.e which returned
  `NULL`

  Finally, a **full outer join** returns all the records from both tables, and
  where a record cannot be matched, a NULL value is returned. So this would mean
  there could be `NULL`s in both fields of the returned rows.

  We can combine multiple types of join in the same SQL query:

  ```sql
  SELECT model.name, sales.sale_date, manufacturer.url
  FROM model
  LEFT JOIN sales on model.model_id = sales.model_id
  INNER JOIN manufacturer on model.manufacturer_id = manufacturer.manufacturer_id;
  ```


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

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

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

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

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

  ## Architecture

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

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

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

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

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

  ## Implementation overview

  ### Queries, mutations, subscriptions

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

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

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

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

  ### Frontend

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

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

  ### Backend: structure and behaviour

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

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

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

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

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

  ### GraphQL endpoint

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

  ## Benefits

  ### Single requests

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

  The REST scenario:

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

  The GraphQL scenario:

  ![](/img/graphQL_request_load.png)

  ### Abstraction of multiple services

  ![](/img/graphql_multiple_resources.png)

  ### Stops overfetching

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

  ### Single endpoint

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

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

  # Key characteristics of GraphQL

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

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

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

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

  ## Architecture

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

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

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

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

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

  ## Implementation overview

  ### Queries, mutations, subscriptions

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

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

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

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

  ### Frontend

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

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

  ### Backend: structure and behaviour

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

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

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

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

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

  ### GraphQL endpoint

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

  ## Benefits

  ### Single requests

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

  The REST scenario:

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

  The GraphQL scenario:

  ![](/img/graphQL_request_load.png)

  ### Abstraction of multiple services

  ![](/img/graphql_multiple_resources.png)

  ### Stops overfetching

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

  ### Single endpoint

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


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

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

  $ 7890
  # Gives you process id

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

  ## Alterntive

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

  Returns:

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

  Kill with:

  ```
  kill 10493
  ```

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

  ## Kill a process running on a port

  For example a local server.

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

  $ 7890
  # Gives you process id

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

  ## Alterntive

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

  Returns:

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

  Kill with:

  ```
  kill 10493
  ```


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

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

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

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

  ![](/img/diode.png)

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

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

  # LEDs

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

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

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

  ![](/img/diode.png)

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


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

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

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

  # Lambda directory structure

  I am using the following structure for my lambdas:

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


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

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

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

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

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

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

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

  ### Full example:

  In JavaScript:

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

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

  In Python:

  ```py
  import json

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

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

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

  # AWS Lambda handler function

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

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

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

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

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

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

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

  ### Full example:

  In JavaScript:

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

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

  In Python:

  ```py
  import json

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

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


path: zk/Lambda_programming_model.md
sortable_path: zkLambda_programming_model.md
title: AWS Lambda programming model
lead: The overall architecure consists in the following three processes:
checksum: 9f60949e0f8ec84c322ac2a9546bbb83adbfe66e2b5357bbd1184859d0e2304e
created: 2024-03-30 09:03:19.899938286+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["AWS","aws-lambda","backend"]}
body:
  The overall architecure consists in the following three processes:

  1. Triggers
  2. Handler function
  3. Code

  ## Triggers

  See [AWS Lambda triggers](Lambda_triggers.md)

  ## Handler function

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

  ## Code

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

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

  # AWS Lambda programming model

  The overall architecure consists in the following three processes:

  1. Triggers
  2. Handler function
  3. Code

  ## Triggers

  See [AWS Lambda triggers](Lambda_triggers.md)

  ## Handler function

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

  ## Code

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


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

  ### API Gateway trigger

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

  ### DynamoDB table change trigger

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

  ### S3 trigger

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

  ### SQS trigger

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

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

  # AWS Lambda triggers

  Here are some common trigger patterns

  ### API Gateway trigger

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

  ### DynamoDB table change trigger

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

  ### S3 trigger

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

  ### SQS trigger

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


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

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

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

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

  Here is the two syntaxes side by side:

  ```js
  // JavaScript

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

  ```py
  # Python

  double = lambda x: x * x
  ```

  Here is a lambda with multiple parameters:

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

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

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

  # Lambdas in Python

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

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

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

  Here is the two syntaxes side by side:

  ```js
  // JavaScript

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

  ```py
  # Python

  double = lambda x: x * x
  ```

  Here is a lambda with multiple parameters:

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

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


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

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

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

  ## What is a latch?

  A latch is a circuit component that wor

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

  The SR Latch goes through the following state changes:

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

  This is represented more clearly in the table below:

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

  The most succinct account of a latch:

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

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

  ![](/img/sr_latch_diagram.png)

  ## Creating a latch circuit

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

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

  The circuit is created as follows:

  ![](/img/sr_latch_logic_circuit.png)

  Interactive version:

  <iframe src="https://circuitverse.org/simulator/embed/nor-latch-0869192c-7d7b-4161-b13f-3f72c1bce8e9" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

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

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

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

  # Latches

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

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

  ## What is a latch?

  A latch is a circuit component that wor

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

  The SR Latch goes through the following state changes:

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

  This is represented more clearly in the table below:

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

  The most succinct account of a latch:

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

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

  ![](/img/sr_latch_diagram.png)

  ## Creating a latch circuit

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

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

  The circuit is created as follows:

  ![](/img/sr_latch_logic_circuit.png)

  Interactive version:

  <iframe src="https://circuitverse.org/simulator/embed/nor-latch-0869192c-7d7b-4161-b13f-3f72c1bce8e9" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

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

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


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

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

checksum: 5eb2ecbc3c43d887cecc329c0194344dc093bb5fa50438f52f931b4d091942ac
created: 2024-03-30 09:03:19.941153028+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["propositional-logic"]}
raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Law of Non-Contradiction

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

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


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

checksum: 7e49c5d4e4a68feb6b36d2c2f9de2a102191a1438cac5f57b72efbecf22c5f80
created: 2024-03-30 09:03:19.943841479+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["propositional-logic"]}
body:
  > Every proposition has to be either true or false. There can be no middle
  > ground.

  $$
  P \lor \sim P
  $$

raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Law of the Excluded Middle

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

  $$
  P \lor \sim P
  $$


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

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

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

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

  ## Specific lifecycle phases

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

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

  The two methods above only occur once in a lifecycle.

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

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

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

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

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

  6. `componentWillUnmount()`

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

  ## Side-effects: why lifecycle phases matter

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

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

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

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

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

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

  # Lifecycle methods

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

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

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

  ## Specific lifecycle phases

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

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

  The two methods above only occur once in a lifecycle.

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

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

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

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

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

  6. `componentWillUnmount()`

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

  ## Side-effects: why lifecycle phases matter

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

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

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

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

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


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

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

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

  ## Syntax

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

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

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

  The basic syntax is as follows:

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

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

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

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

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

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

  ### Another example

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

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

  ```

  ### Adding a condition

  We can apply a conditional to a comprehension:

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

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

  ### Filters

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

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

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

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

  ## Set comprehension

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

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

  ## Dictionary comprehension

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

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

  # List comprehension

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

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

  ## Syntax

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

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

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

  The basic syntax is as follows:

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

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

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

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

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

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

  ### Another example

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

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

  ```

  ### Adding a condition

  We can apply a conditional to a comprehension:

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

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

  ### Filters

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

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

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

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

  ## Set comprehension

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

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

  ## Dictionary comprehension

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


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

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

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

  ## Navigation shorthands

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

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

  ## Listing options

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

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

  ## Navigation shorthands

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


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

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

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

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

  ## Index notation

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

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

  # element3
  ```

  ## Iterating through arrays

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

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

  ## Sorting arrays

  ### Sorting an integer array highests to lowest

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

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

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

  ## Pushing, appending to an array

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

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

  ```

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

  ## Check if array empty

  ```sh
  an_array=()

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

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

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

  ## Weirdness with functions

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

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

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

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

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

  my_array=()
  array_empty "my_array"
  ```

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

  ## Associational arrays / maps

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

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

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

  ## Lists as implicit string arrays

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

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

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

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

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

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

  # Arrays in Bash

  We define an array as follows:

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

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

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

  ## Index notation

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

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

  # element3
  ```

  ## Iterating through arrays

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

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

  ## Sorting arrays

  ### Sorting an integer array highests to lowest

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

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

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

  ## Pushing, appending to an array

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

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

  ```

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

  ## Check if array empty

  ```sh
  an_array=()

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

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

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

  ## Weirdness with functions

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

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

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

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

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

  my_array=()
  array_empty "my_array"
  ```

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

  ## Associational arrays / maps

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

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

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

  ## Lists as implicit string arrays

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

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

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

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

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


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

  Lists have the following properties:

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

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

  ## Duplicating lists

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

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

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

  ## Basic usage

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

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

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

  ## Slicing

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

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

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

  ## Adding additional values to existing list

  ### Single value

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

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

  ### Multiple values

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

  ### Specific index

  ```python

  ## Insert at specific index

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

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

  ## Removing elements

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

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

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

  ### `del`

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

  We can remove multiple items at once via a slice:

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

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

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

  ### `remove()`

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

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

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

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

  ### pop()

  Remove and return the element removed

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

  ```

  ## Return index of a list element

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

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

  ## Nesting lists

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

  ## Merging (flattening) lists

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

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

  ## See also

  [Sorting lists in Python](Sorting_lists_in_Python.md)

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

  # Lists in Python

  Lists are the equivalent of a simple array in JavaScript.

  Lists have the following properties:

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

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

  ## Duplicating lists

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

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

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

  ## Basic usage

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

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

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

  ## Slicing

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

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

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

  ## Adding additional values to existing list

  ### Single value

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

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

  ### Multiple values

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

  ### Specific index

  ```python

  ## Insert at specific index

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

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

  ## Removing elements

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

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

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

  ### `del`

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

  We can remove multiple items at once via a slice:

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

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

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

  ### `remove()`

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

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

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

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

  ### pop()

  Remove and return the element removed

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

  ```

  ## Return index of a list element

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

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

  ## Nesting lists

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

  ## Merging (flattening) lists

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

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

  ## See also

  [Sorting lists in Python](Sorting_lists_in_Python.md)


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

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

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

  ## Basic set up

  Enter your project directory.

  First build your SAM application:

  ```sh
  sam build
  ```

  We then run:

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

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

  This will be indicated by:

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

  If we want to invoke the function directly we use:

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

  ## Using environment variables

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

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

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

  ## Create secret

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

  ## Create local ENV file

  > These must be in JSON to work with SAM:

  ### Local env

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

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

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

  ### Update `template.yaml`

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

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

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

  ### Pass in the environment variable at invocation:

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

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

  ### Create handler within the Lambda itself

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

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

  const secretsManager = new AWS.SecretsManager();

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

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

  # Local AWS development with SAM

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

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

  ## Basic set up

  Enter your project directory.

  First build your SAM application:

  ```sh
  sam build
  ```

  We then run:

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

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

  This will be indicated by:

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

  If we want to invoke the function directly we use:

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

  ## Using environment variables

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

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

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

  ## Create secret

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

  ## Create local ENV file

  > These must be in JSON to work with SAM:

  ### Local env

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

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

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

  ### Update `template.yaml`

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

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

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

  ### Pass in the environment variable at invocation:

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

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

  ### Create handler within the Lambda itself

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

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

  const secretsManager = new AWS.SecretsManager();

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


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

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

  A logarithm can be expressed identically using exponents for example:

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


  $$

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

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

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

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


     $$

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

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


     $$

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

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


  $$

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

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


  $$

  ## Common base values

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

  $$
  \log1000


  $$

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

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


  $$

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

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

  An example

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


  $$

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

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

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

  More formally:

  > x raised to what power gives me y

  Below is an example of a logarithm:

  $$
  \log \_{3} 9


  $$

  We read it:

  > log base 3 of 9

  And it means:

  > 3 raised to what power gives me 9?

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

  ## Using exponents to calculate logarithms

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

  A logarithm can be expressed identically using exponents for example:

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


  $$

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

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

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

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


     $$

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

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


     $$

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

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


  $$

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

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


  $$

  ## Common base values

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

  $$
  \log1000


  $$

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

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


  $$

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

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

  An example

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


  $$


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

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

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

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

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

  ## Elementary and composite gates

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

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

  ## Gate interface / gate implementation

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

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

  // Add: Interface diagram

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

  // Add: Implementation diagram

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

  ## NOT gate

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

  ### Truth conditions

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

  ### Interactive circuit

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

  ### Truth conditions

  tion) truth functional connective

  ### Symbol

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

  ### Truth conditions

  ### Truth conditions

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

  ### Interactive circuit

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

  ## NAND gate

  > The NAND gate inverts the truth conditions of AND.

  ### Symbol

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

  ### Truth conditions

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

  ### Interactive circuit

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

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

  ## OR gate

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

  ### Symbol

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

  ### Truth condition

  ### Truth conditions

  s

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

  ### Interactive circui

  ### Truth conditions

  t

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

  ## XOR gate

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

  ### Symbol

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

  ### Truth conditions

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

  ### Interactive circuit

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

  ## NOR gate

  > The NOR gate inverts the function of an OR gate

  ### Symbol

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

  ### Truth conditions

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

  ### Interactive circuit

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

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

  # Logic gates

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

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

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

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

  ## Elementary and composite gates

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

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

  ## Gate interface / gate implementation

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

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

  // Add: Interface diagram

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

  // Add: Implementation diagram

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

  ## NOT gate

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

  ### Truth conditions

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

  ### Interactive circuit

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

  ### Truth conditions

  tion) truth functional connective

  ### Symbol

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

  ### Truth conditions

  ### Truth conditions

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

  ### Interactive circuit

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

  ## NAND gate

  > The NAND gate inverts the truth conditions of AND.

  ### Symbol

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

  ### Truth conditions

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

  ### Interactive circuit

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

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

  ## OR gate

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

  ### Symbol

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

  ### Truth condition

  ### Truth conditions

  s

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

  ### Interactive circui

  ### Truth conditions

  t

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

  ## XOR gate

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

  ### Symbol

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

  ### Truth conditions

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

  ### Interactive circuit

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

  ## NOR gate

  > The NOR gate inverts the function of an OR gate

  ### Symbol

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

  ### Truth conditions

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

  ### Interactive circuit

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


path: zk/Logical_consistency.md
sortable_path: zkLogical_consistency.md
title: Logical consistency
lead: ## Informal definition
checksum: 9b2917a368c1f0c06aa948bea169ae07c4bbb2c52d033df8efffa985579d64f0
created: 2024-03-30 09:03:20.041343934+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["propositional-logic"]}
body:
  ## Informal definition

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

  ### Demonstration

  The following set of propositions form an inconsistent set:

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

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

  ## Formal definition

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

  ### Informal expression

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

  ### Formal expression

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

  ### Truth table

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

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

  ## Derivation

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

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

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

  ![](/img/derivation_from_contradiction.png)

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

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

raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Logical consistency

  ## Informal definition

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

  ### Demonstration

  The following set of propositions form an inconsistent set:

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

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

  ## Formal definition

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

  ### Informal expression

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

  ### Formal expression

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

  ### Truth table

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

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

  ## Derivation

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

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

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

  ![](/img/derivation_from_contradiction.png)

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

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


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

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

  ### Informal expression

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

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

  ### Formal expression

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

  ### Truth-tables

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

  ### Derivation

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

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

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

raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Logical equivalence

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

  ### Informal expression

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

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

  ### Formal expression

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

  ### Truth-tables

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

  ### Derivation

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

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

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


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

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

  ## Informal definition

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

  For example the proposition:

  ```
  It is raining.
  ```

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

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

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

  Contrariwise the proposition:

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

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

  ## Formal definition

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

  ```
  P
  ```

  ### Truth-table

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

raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Logical indeterminacy

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

  ## Informal definition

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

  For example the proposition:

  ```
  It is raining.
  ```

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

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

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

  Contrariwise the proposition:

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

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

  ## Formal definition

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

  ```
  P
  ```

  ### Truth-table

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


path: zk/Logical_possibility_and_necessity.md
sortable_path: zkLogical_possibility_and_necessity.md
title: Logical possibility and necessity
lead: ## Logical possibility
checksum: ba031e7482ff9c32d0c871d0cc78658237901235ec237a7d1807f2c5261678bd
created: 2024-03-30 09:03:20.088733205+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["propositional-logic"]}
body:
  ## Logical possibility

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

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

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

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

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

  Some examples of contradictions:

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

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

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

  ## Logical necessity

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

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

raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Logical possibility and necessity

  ## Logical possibility

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

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

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

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

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

  Some examples of contradictions:

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

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

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

  ## Logical necessity

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

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


path: zk/Logical_truth_and_falsity.md
sortable_path: zkLogical_truth_and_falsity.md
title: Logical truth and falsity
lead: We say of certain propositions that they are logically true or logically false.
checksum: 31c1fe49c392131c726b984726791c7fc9578e778d969fa0382b8891fdd7f658
created: 2024-03-30 09:03:20.106473653+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["propositional-logic"]}
body:
  We say of certain propositions that they are logically true or logically false.

  ## Logical falsity

  ### Informal definition

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

  **Demonstration**

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

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

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

  ### Formal definition

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

  ### Formal expression

  $$ P \land \lnot P $$

  ### Truth-table

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

  ## Logical truth

  ### Informal definition

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

  **Demonstration**

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

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

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

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

  ### Formal definition

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

  $$ P \lor \lnot P$$

  ### Truth-table

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

  ### Consequences

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

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

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

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

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

raw_content:
  ---
  tags: [propositional-logic]
  ---

  # Logical truth and falsity

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

  ## Logical falsity

  ### Informal definition

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

  **Demonstration**

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

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

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

  ### Formal definition

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

  ### Formal expression

  $$ P \land \lnot P $$

  ### Truth-table

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

  ## Logical truth

  ### Informal definition

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

  **Demonstration**

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

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

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

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

  ### Formal definition

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

  $$ P \lor \lnot P$$

  ### Truth-table

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

  ### Consequences

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

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

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

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

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


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

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

  1. Traditional `for` loop with range:

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

     This is equivalent to the following in JavaScript:

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

  2. `for` loop with iterable:

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

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

  ### Loop in increments greater than 1

  The following loop increments by 2 on each iteration

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

  # 0  2  4  6  8
  ```

  ### Break and continue

  #### Break

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

  #### Continue

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

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

  ## While

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

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

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

  > There are no `do while` loops in Python

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

  # Loops in Python

  ## For

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

  1. Traditional `for` loop with range:

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

     This is equivalent to the following in JavaScript:

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

  2. `for` loop with iterable:

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

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

  ### Loop in increments greater than 1

  The following loop increments by 2 on each iteration

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

  # 0  2  4  6  8
  ```

  ### Break and continue

  #### Break

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

  #### Continue

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

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

  ## While

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

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

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

  > There are no `do while` loops in Python


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

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

  # 1 2 3 4 5
  ```

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

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

  ## While loop

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

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

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

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

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

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

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

  ## Until loop

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

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

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

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

  # Loops in Bash

  ## Traditional for loop

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

  # 1 2 3 4 5
  ```

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

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

  ## While loop

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

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

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

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

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

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

  ```sh
  while read line;
  do
      # something
  done < "$file_to_read"
  ```

  ## Until loop

  > `until` loops execute until a condition is false (1)

  ```sh
  declare -i m=0
  until (( m==10 )): do
    echo "m:$m"
    (( m++ ))
  done
  ```

  This gives us the same output as `n` with the while loop but here it runs so
  long as `m==10` is false. As soon as `m` is equal to 100, the condition becomes
  true and hence the loop stops.


path: zk/Machine_code.md
sortable_path: zkMachine_code.md
title: Machine_code
lead: # Machine code
checksum: 44047dbf1cd0ccdae2cb7733a887580bbe1bb8ebc35d4884ba175290cd6987ba
created: 2024-03-30 09:03:20.126143504+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"created":"Monday, March 11, 2024","id":"iqh8","tags":["binary","CPU"],"title":"Machine_code"}
body:
  # Machine code

  No matter how a program was originally written, it eventually needs to execute
  on a [[CPU_architecture|CPU]] as a series of machine language instructions.

  ## Example machine instruction

  The following machine instruction is an instruction that would be understood by
  an [[Instruction_set_architectures| ARM ISA]]. It moves the number `4` into the
  `r7` register:

  ```
  11100011101000000111000000000100
  ```

  This 32-bit line of binary has a series of instructions embedded within it. We
  partition each part of the sequence below, from left to right, mapping it to the
  instruction:

  | Binary sequence | Instruction          | Action                                   |
  | --------------- | -------------------- | ---------------------------------------- |
  | 1110            | condition            | always execute (unconditional)           |
  | 00              | --                   | --                                       |
  | 1               | immediate            | value is in the last 8 bits              |
  | 1101            | opcode               | `mov` : move the value                   |
  | 0               | --                   | --                                       |
  | 0000            | --                   | --                                       |
  | 0111            | destination register | destination is 0111 which means `r7`     |
  | 0000            | --                   | --                                       |
  | 00000100        | immediate value      | the binary representation of decimal '4' |

  - The blank zeros are also instructions, they are just not used in this
    instruction

  - The condition sequence tells us the instruction should run in all conditions
    not only under certain circumstances

  - The immediate bit (`1`) tells us whether the the value we are accessing is
    contained within the instruction or whether it is stored in a register. In
    this scenario `1` means the value is in the instruction. If it were `0`, the
    register where the value is located would be specified elsewhere in the
    instruction (in one of the currently blank sequences).

  - The opcode correspoons to `mov` the value.

  - THe destination register details where the value should be moved to (`r7`)

  - Finally, the immediate value is equivalent to decimal `4`

  ## Difficulty

  Binary sequences are hard to understand, even if converted to the
  [[Hexadecimal_number_system]]. We have a better way of managing operations on
  the machine level: [assembly](Assembly.md)

raw_content:
  ---
  id: iqh8
  title: Machine_code
  tags: [binary, CPU]
  created: Monday, March 11, 2024
  ---

  # Machine code

  No matter how a program was originally written, it eventually needs to execute
  on a [[CPU_architecture|CPU]] as a series of machine language instructions.

  ## Example machine instruction

  The following machine instruction is an instruction that would be understood by
  an [[Instruction_set_architectures| ARM ISA]]. It moves the number `4` into the
  `r7` register:

  ```
  11100011101000000111000000000100
  ```

  This 32-bit line of binary has a series of instructions embedded within it. We
  partition each part of the sequence below, from left to right, mapping it to the
  instruction:

  | Binary sequence | Instruction          | Action                                   |
  | --------------- | -------------------- | ---------------------------------------- |
  | 1110            | condition            | always execute (unconditional)           |
  | 00              | --                   | --                                       |
  | 1               | immediate            | value is in the last 8 bits              |
  | 1101            | opcode               | `mov` : move the value                   |
  | 0               | --                   | --                                       |
  | 0000            | --                   | --                                       |
  | 0111            | destination register | destination is 0111 which means `r7`     |
  | 0000            | --                   | --                                       |
  | 00000100        | immediate value      | the binary representation of decimal '4' |

  - The blank zeros are also instructions, they are just not used in this
    instruction

  - The condition sequence tells us the instruction should run in all conditions
    not only under certain circumstances

  - The immediate bit (`1`) tells us whether the the value we are accessing is
    contained within the instruction or whether it is stored in a register. In
    this scenario `1` means the value is in the instruction. If it were `0`, the
    register where the value is located would be specified elsewhere in the
    instruction (in one of the currently blank sequences).

  - The opcode correspoons to `mov` the value.

  - THe destination register details where the value should be moved to (`r7`)

  - Finally, the immediate value is equivalent to decimal `4`

  ## Difficulty

  Binary sequences are hard to understand, even if converted to the
  [[Hexadecimal_number_system]]. We have a better way of managing operations on
  the machine level: [assembly](Assembly.md)


path: zk/Managing_environments.md
sortable_path: zkManaging_environments.md
title: Managing environments
lead: With a full-scale Node application you will typically run three environments:
checksum: 62898e519645ad59a04dc7e3572fd3c89073f29ce163fa333d7b45d526036283
created: 2024-03-30 09:03:20.146421913+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["backend","node-js"]}
body:
  With a full-scale Node application you will typically run three environments:

  - Development
  - Testing
  - Production

  ## Accessing the current environment

  To determine the current environment we can use the variable
  **`process.env.NODE_ENV`** from the
  [global process object](Process_object.md).
  This works universally regardless of the kind of Node app we are building.

  If you have not manually set up your environments, **`process.env.NODE_ENV`**
  will return `undefined`.

  ### Setting the Node environment

  #### For the session

  `NODE_ENV` is a bash
  [environment variable](Environmental_and_shell_variables.md)
  like any other. So we can set it in the normal way:

  ```bash
  export NODE_ENV=production
  ```

  ### In Express

  In Express, there is a built in method for retrieving the current envrionment:
  `app.get('env')`. Express will default to the development environment.

  <p style="color:red">! How to keep Express and Node environment in sync?</p>

  ## Configuring environments

  We use the third-party [Config](node-config)
  package to manage different configurations based on the environment.

  Once installed we set up a dedicated config directory with a structure as
  follows:

  ```
  config/
      default.json
      development.json
      production.json
  ```

  For example:

  ```json
  // default.json
  {
    "name": "My Express app"
  }
  ```

  Then to utilise config variables:

  ```js
  const config = require("config");
  console.log("Application name:" + config.get("name"));
  ```

  If we toggled the different environments, we would see different outputs from
  the above code (assuming we had different config files in `/config` with
  different names).

  ### Sensitive config data

  We will need to store passwords, API keys and other kinds of authentication data
  for our application. We obviously shouldn't store this data openly in our config
  files since it would be made public.

  We can do so securely by utilising
  [environmental variables](Environmental_and_shell_variables.md)
  alongside the config pacakage.

  We create a file called `custom-environment-variables` (must be called this) and
  map a property to an environmental environment we have already set.

  Let's create an environmental variable for a password:

  ```bash
  export APP_PASSWORD='mypassword123'
  ```

  Then in our custom variable file:

  ```json
  {
    "password": "APP_PASSWORD"
  }
  ```

  We can then safely reference this value in the course of our normal code:

  ```js
  console.log(config.get("password"));
  ```

  <p style="color:red">! But how would this be achieved in a production server?</p>

  <p style="color:red">! And how could we do this programmatically at the start of a local development session without manually setting each environment variable in the terminal?</p>

raw_content:
  ---
  tags:
    - backend
    - node-js
  ---

  # Managing environments

  With a full-scale Node application you will typically run three environments:

  - Development
  - Testing
  - Production

  ## Accessing the current environment

  To determine the current environment we can use the variable
  **`process.env.NODE_ENV`** from the
  [global process object](Process_object.md).
  This works universally regardless of the kind of Node app we are building.

  If you have not manually set up your environments, **`process.env.NODE_ENV`**
  will return `undefined`.

  ### Setting the Node environment

  #### For the session

  `NODE_ENV` is a bash
  [environment variable](Environmental_and_shell_variables.md)
  like any other. So we can set it in the normal way:

  ```bash
  export NODE_ENV=production
  ```

  ### In Express

  In Express, there is a built in method for retrieving the current envrionment:
  `app.get('env')`. Express will default to the development environment.

  <p style="color:red">! How to keep Express and Node environment in sync?</p>

  ## Configuring environments

  We use the third-party [Config](node-config)
  package to manage different configurations based on the environment.

  Once installed we set up a dedicated config directory with a structure as
  follows:

  ```
  config/
      default.json
      development.json
      production.json
  ```

  For example:

  ```json
  // default.json
  {
    "name": "My Express app"
  }
  ```

  Then to utilise config variables:

  ```js
  const config = require("config");
  console.log("Application name:" + config.get("name"));
  ```

  If we toggled the different environments, we would see different outputs from
  the above code (assuming we had different config files in `/config` with
  different names).

  ### Sensitive config data

  We will need to store passwords, API keys and other kinds of authentication data
  for our application. We obviously shouldn't store this data openly in our config
  files since it would be made public.

  We can do so securely by utilising
  [environmental variables](Environmental_and_shell_variables.md)
  alongside the config pacakage.

  We create a file called `custom-environment-variables` (must be called this) and
  map a property to an environmental environment we have already set.

  Let's create an environmental variable for a password:

  ```bash
  export APP_PASSWORD='mypassword123'
  ```

  Then in our custom variable file:

  ```json
  {
    "password": "APP_PASSWORD"
  }
  ```

  We can then safely reference this value in the course of our normal code:

  ```js
  console.log(config.get("password"));
  ```

  <p style="color:red">! But how would this be achieved in a production server?</p>

  <p style="color:red">! And how could we do this programmatically at the start of a local development session without manually setting each environment variable in the terminal?</p>


path: zk/Map_and_filter_in_Python.md
sortable_path: zkMap_and_filter_in_Python.md
title: Map and filter in Python
lead: ## Map
checksum: 657b2624c0782eaa5f2b92a064f41d79185fc42eef1be5b4c8ab202f87745c76
created: 2024-03-30 09:03:20.165674202+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  ## Map

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  new_data = map(lambda i: i + 10, data)
  print(new_data)
  ```

  We can also pass-in a function rather than use a lambda:

  ```py
  def add_one(i):
      return i + 1

  x = list(map(addOne, data))
  # [2, 4, 6, 3, 8, 5, 11]
  # necessary to add `list` to get some output
  ```

  ### Map returns an object

  When you run `map` against a list it returns a map object not a list. This can
  present problems if you are not aware of it since it is not an entity that you
  can loop through.

  The following map removes whitespaces from elements in a list:

  ```py
    lines = map(lambda x: x.strip(), lines)
  ```

  In order to be able to run list-like operations against the resulting `lines`
  object we would need to convert it to a list:

  ```py
    lines = list(map(lambda x: x.strip(), lines))
  ```

  ## Filter

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  d1 = list(filter(lambda i: i % 2 == 0, data))
  print(d1)
  #  [2, 4, 10]

  def is_even(i):
      return i % 2 == 0


  # Filter for even numbers using a named function
  d2 = list(filter(is_even, data))

  # [2, 4, 10]
  ```

  ## Chaining

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]
  new_data = list(map(lambda i: i + 10, filter(is_even, data)))
  print(new_data)
  # new_data: [12, 14, 20]
  ```

raw_content:
  ---
  tags: [python]
  ---

  # Map and filter in Python

  ## Map

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  new_data = map(lambda i: i + 10, data)
  print(new_data)
  ```

  We can also pass-in a function rather than use a lambda:

  ```py
  def add_one(i):
      return i + 1

  x = list(map(addOne, data))
  # [2, 4, 6, 3, 8, 5, 11]
  # necessary to add `list` to get some output
  ```

  ### Map returns an object

  When you run `map` against a list it returns a map object not a list. This can
  present problems if you are not aware of it since it is not an entity that you
  can loop through.

  The following map removes whitespaces from elements in a list:

  ```py
    lines = map(lambda x: x.strip(), lines)
  ```

  In order to be able to run list-like operations against the resulting `lines`
  object we would need to convert it to a list:

  ```py
    lines = list(map(lambda x: x.strip(), lines))
  ```

  ## Filter

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]

  d1 = list(filter(lambda i: i % 2 == 0, data))
  print(d1)
  #  [2, 4, 10]

  def is_even(i):
      return i % 2 == 0


  # Filter for even numbers using a named function
  d2 = list(filter(is_even, data))

  # [2, 4, 10]
  ```

  ## Chaining

  ```py
  data = [1, 3, 5, 2, 7, 4, 10]
  new_data = list(map(lambda i: i + 10, filter(is_even, data)))
  print(new_data)
  # new_data: [12, 14, 20]
  ```


path: zk/Map_and_filter_to_reduce.md
sortable_path: zkMap_and_filter_to_reduce.md
title: Expressing combined map and filter operations to a single reduce
lead: ## `map` + `filter`
checksum: c55ae1785e85ae7113094c8c7f48a7190f3e0eb2cec9022f07c79701ce3fea0a
created: 2024-03-30 09:03:20.169127095+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript"]}
body:
  ## `map` + `filter`

  The following combines a `map` and a `filter` to square a list of numbers and
  then filter the ones that are greater than or equal to 10:

  ```js
  const arr = [1, 2, 3, 4];

  const filteredSquaredNumbers = arr.map((x) => x * x).filter((y) => y >= 10);
  ```

  Using `reduce` we can combine these operations more concisely:

  ```js
  const filteredSquaredNumbers = arr.reduce((acc, x) => {
    const squared = x * x;
    if (squared >= 10) {
      acc.push(squared);
    }
    return acc;
  }, []);
  ```

  ## `filter` + `map`

  Here we use a filter and then a map to filter the even numbers and then double
  them:

  ```js
  const arr = [1, 2, 3, 4];

  const evenDoubled = arr.filter((x) => x % 2 === 0).map((y) => y * 2);
  ```

  Using `reduce` :

  ```js
  const evenDoubled = arr.reduce((acc, x) => {
    if (x % 2 === 0) {
      acc.push(x * 2);
    }
    return acc;
  }, []);
  ```

  > The empty array [] is the initial value of the accumulator. As the callback
  > function processes each element of the array, it can add or modify the
  > accumulator based on the desired logic. By providing an empty array as the
  > initial value for the accumulator, we ensure that the results will be
  > collected in a new array without modifying the original array.

raw_content:
  ---
  tags: [javascript]
  ---

  # Expressing combined map and filter operations to a single reduce

  ## `map` + `filter`

  The following combines a `map` and a `filter` to square a list of numbers and
  then filter the ones that are greater than or equal to 10:

  ```js
  const arr = [1, 2, 3, 4];

  const filteredSquaredNumbers = arr.map((x) => x * x).filter((y) => y >= 10);
  ```

  Using `reduce` we can combine these operations more concisely:

  ```js
  const filteredSquaredNumbers = arr.reduce((acc, x) => {
    const squared = x * x;
    if (squared >= 10) {
      acc.push(squared);
    }
    return acc;
  }, []);
  ```

  ## `filter` + `map`

  Here we use a filter and then a map to filter the even numbers and then double
  them:

  ```js
  const arr = [1, 2, 3, 4];

  const evenDoubled = arr.filter((x) => x % 2 === 0).map((y) => y * 2);
  ```

  Using `reduce` :

  ```js
  const evenDoubled = arr.reduce((acc, x) => {
    if (x % 2 === 0) {
      acc.push(x * 2);
    }
    return acc;
  }, []);
  ```

  > The empty array [] is the initial value of the accumulator. As the callback
  > function processes each element of the array, it can add or modify the
  > accumulator based on the desired logic. By providing an empty array as the
  > initial value for the accumulator, we ensure that the results will be
  > collected in a new array without modifying the original array.


path: zk/Mapped_types.md
sortable_path: zkMapped_types.md
title: Mapped types in TypeScript
lead:
  A mapped type is a way to create new types based on existing ones by
  transforming their properties

checksum: ccabedb3387b7583fb0265e8e625b02b3cfe9ddc04875240fe8f29b61a4fab8e
created: 2024-03-30 09:03:20.172244522+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["typescript"]}
body:
  A mapped type is a way to create new types based on existing ones by
  transforming their properties

  As it works on a single type and typically narrows or limits the properties it
  works in a way that is opposite to a [union]() or [intersection]() type.

  > Mapped types are an example of when `type` is preferable to `interface`, since
  > you cannot generate mapped types as interfaces, although you may generate a
  > mapped type from an interface.

  For the demonstrations we will use the following type as the example that we
  will map from:

  ```ts
  type Person = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```

  ## Main varieties of mapped types

  ### Read only

  Creates a type with all properties of the given type set to `readonly`:

  ```ts
  type Readonly<T> = {
    readonly [P in keyof T]: T[P];
  };

  type ReadonlyPerson = Readonly<Person>;
  ```

  This is equivalent to:

  ```ts
  type ReadonlyPerson = {
    readonly name: string;
    readonly age: number;
    readonly city: string;
    readonly country: string;
  };
  ```

  ### Partial

  Creates a type with all properties of the given type set to optional.

  ```ts
  type Partial<T> = {
    [P in keyof T]?: T[P];
  };

  type PartialPerson = Partial<Person>;
  ```

  This is equivalent to:

  ```ts
  type PartialPerson = {
    name?: string;
    age?: number;
    city?: string;
    country?: string;
  };
  ```

  ### Pick

  This is useful when you want to create a new type based on a subset of
  properties from an existing type.

  ```ts
  type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
  };

  type PersonNameAndAge = Pick<Person, "name" | "age">;
  ```

  This is equivalent to:

  ```ts
  type PersonNameAndAge = {
    name: string;
    age: number;
  };
  ```

  ### Record

  Creates a type with keys of the given type and values of the specified type. It
  is a way of shoehorning keys from an existing type with new values.

  Basic syntax:

  ```ts
  Record<Keys, ValueType>;
  ```

  Example:

  ```ts
  type UserRole = "admin" | "user" | "guest";

  // Create a Record type to store user role descriptions
  type UserRoleDescriptions = Record<UserRole, string>;

  // Create an object that implements the UserRoleDescriptions type
  const roleDescriptions: UserRoleDescriptions = {
    admin: "Has full access to the system",
    user: "Can access limited resources",
    guest: "Can view public resources only",
  };
  ```

  Here is a simpler example

  ```ts
  type Person = {
    name: string;
    nationality: string;
  };

  type PersonInstance = Record<string, Person>;
  const thomasInstance: PersonInstance = {
    thomas: {
      name: "Thomas",
      nationality: "British",
    },
  };
  ```

  ### Exclude

  Creates a type by excluding specific properties from the given type.

  ```ts
  type Exclude<T, U> = T extends U ? never : T;

  type KeysWithoutAge = Exclude<keyof Person, "age">;
  ```

  This is equivalent to:

  ```ts
  type KeysWithoutAge = "name" | "city" | "country";
  ```

  ### Extract

  Creates a type by extracting specific properties from the given type. Basically
  the opposite operation to Exclude.

  ```ts
  type Extract<T, U> = T extends U ? T : never;

  type NameKey = Extract<keyof Person, "name">;
  ```

  This is equivalent to:

  ```ts
  type NameKey = "name";
  ```

  ### Non-nullable

  Creates a type by removing null and undefined from the given type.

  ```ts
  type NonNullable<T> = T extends null | undefined ? never : T;

  type NullablePerson = {
    name: string | null;
    age: number | null;
    city: string | null;
    country: string | null;
  };

  type NonNullablePerson = NonNullable<NullablePerson>;
  ```

  Equivalent to:

  ```ts
  type NonNullablePerson = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```

raw_content:
  ---
  tags:
    - typescript
  ---

  # Mapped types in TypeScript

  A mapped type is a way to create new types based on existing ones by
  transforming their properties

  As it works on a single type and typically narrows or limits the properties it
  works in a way that is opposite to a [union]() or [intersection]() type.

  > Mapped types are an example of when `type` is preferable to `interface`, since
  > you cannot generate mapped types as interfaces, although you may generate a
  > mapped type from an interface.

  For the demonstrations we will use the following type as the example that we
  will map from:

  ```ts
  type Person = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```

  ## Main varieties of mapped types

  ### Read only

  Creates a type with all properties of the given type set to `readonly`:

  ```ts
  type Readonly<T> = {
    readonly [P in keyof T]: T[P];
  };

  type ReadonlyPerson = Readonly<Person>;
  ```

  This is equivalent to:

  ```ts
  type ReadonlyPerson = {
    readonly name: string;
    readonly age: number;
    readonly city: string;
    readonly country: string;
  };
  ```

  ### Partial

  Creates a type with all properties of the given type set to optional.

  ```ts
  type Partial<T> = {
    [P in keyof T]?: T[P];
  };

  type PartialPerson = Partial<Person>;
  ```

  This is equivalent to:

  ```ts
  type PartialPerson = {
    name?: string;
    age?: number;
    city?: string;
    country?: string;
  };
  ```

  ### Pick

  This is useful when you want to create a new type based on a subset of
  properties from an existing type.

  ```ts
  type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
  };

  type PersonNameAndAge = Pick<Person, "name" | "age">;
  ```

  This is equivalent to:

  ```ts
  type PersonNameAndAge = {
    name: string;
    age: number;
  };
  ```

  ### Record

  Creates a type with keys of the given type and values of the specified type. It
  is a way of shoehorning keys from an existing type with new values.

  Basic syntax:

  ```ts
  Record<Keys, ValueType>;
  ```

  Example:

  ```ts
  type UserRole = "admin" | "user" | "guest";

  // Create a Record type to store user role descriptions
  type UserRoleDescriptions = Record<UserRole, string>;

  // Create an object that implements the UserRoleDescriptions type
  const roleDescriptions: UserRoleDescriptions = {
    admin: "Has full access to the system",
    user: "Can access limited resources",
    guest: "Can view public resources only",
  };
  ```

  Here is a simpler example

  ```ts
  type Person = {
    name: string;
    nationality: string;
  };

  type PersonInstance = Record<string, Person>;
  const thomasInstance: PersonInstance = {
    thomas: {
      name: "Thomas",
      nationality: "British",
    },
  };
  ```

  ### Exclude

  Creates a type by excluding specific properties from the given type.

  ```ts
  type Exclude<T, U> = T extends U ? never : T;

  type KeysWithoutAge = Exclude<keyof Person, "age">;
  ```

  This is equivalent to:

  ```ts
  type KeysWithoutAge = "name" | "city" | "country";
  ```

  ### Extract

  Creates a type by extracting specific properties from the given type. Basically
  the opposite operation to Exclude.

  ```ts
  type Extract<T, U> = T extends U ? T : never;

  type NameKey = Extract<keyof Person, "name">;
  ```

  This is equivalent to:

  ```ts
  type NameKey = "name";
  ```

  ### Non-nullable

  Creates a type by removing null and undefined from the given type.

  ```ts
  type NonNullable<T> = T extends null | undefined ? never : T;

  type NullablePerson = {
    name: string | null;
    age: number | null;
    city: string | null;
    country: string | null;
  };

  type NonNullablePerson = NonNullable<NullablePerson>;
  ```

  Equivalent to:

  ```ts
  type NonNullablePerson = {
    name: string;
    age: number;
    city: string;
    country: string;
  };
  ```


path: zk/Maps_and_sets.md
sortable_path: zkMaps_and_sets.md
title: Maps and Sets
checksum: 391de8d7c7b97236ff25a0ce78fc54bd629b9d1010dd5c1ed91b6de808339bac
created: 2024-03-30 09:03:20.176351677+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript"]}
lead:
  > Both `Map` and `Set` are new data structures introduced with ES6. They combine
  > the properties of the pre-existing data structures: arrays and objects and
  > offer advantages in certain non-typical use cases.

body:
  > Both `Map` and `Set` are new data structures introduced with ES6. They combine
  > the properties of the pre-existing data structures: arrays and objects and
  > offer advantages in certain non-typical use cases.

  ## Maps

  ### Concepts

  A map (not to be confused with the array map method) introduces something like
  the associative array model to JavaScript. Prior to ES6 the closest thing JS had
  to this was an object.

  Maps are like objects in that their structure consists in key/map pairs but like
  arrays in that the order in which the entries are placed is retained and can be
  referred to throughout the lifecycle of the Map. Also, the length of the Map is
  a known and easily returnable quantity, with objects we have to do some
  wrangling with `Object.keys` and `Object.entries` to retrieve meta data about
  the object itself. Moreover, with an object the order in which the keys are
  stored is not recorded.

  At the same time Maps are different from arrays because the index of an array
  element is not unique, it will always be a number of the form `[0], [1], [2]...`
  .

  Finally, the key can be any data type not just a string. This is an important
  characteristic of maps because although you can use numbers or booleans as the
  keys in an object, they are not actually numbers, they are converted to strings
  at execution. With Maps, whatever data type you assign as a key, retains its
  type.

  Maps

  - Have key/value pair structure
  - Have length property
  - Order of entries and insertions is maintained
  - Can use any data type as a key
  - Disallow duplicate keys

  ### Creating a Map and adding entries

  ```jsx
  const map = new Map();

  // We then pass in key/value pairs using `set`:
  map.set("firstName", "Luke");
  map.set("lastName", "Skywalker");
  map.set("occupation", "Jedi Knight");
  ```

  There is an alternative method for defining a map:

  ```jsx
  const map = new Map([
    ["firstName", "Luke"],
    ["lastName", "Skywalker"],
    ["occupation", "Jedi Knight"],
  ]);
  ```

  Internally, this data will be represented as follows:

  ```
  Map(3)
  0: {"firstName" => "Luke"}
  1: {"lastName" => "Skywalker"}
  2: {"occupation" => "Jedi Knight"}
  ```

  ### Working with map elements

  ```jsx
  // check for the existence of an item in a map by searching for existence of key
  map.has("firstName"); // return boolean

  // return all keys in a Map as iterator
  map.keys(); // MapIterator {'firstName', 'lastName', 'occupation'}

  // return all values as iterator
  map.values();

  // return array of key/value pairs
  map.entries(); // {"firstName" => "Luke", "lastName" => "Skywalker", "occupation" => "Jedi Knight"}
  // retrieve an item from a Map
  map.get("lastName"); // Skywalker

  // delete an item from a map by key, returns boolean as confirmation
  map.delete("occupation"); // true

  //return the length of a map (just like array.length)
  map.size; // 3
  // clear all values
  map.clear();
  ```

  ### Looping

  ```jsx
  // Map has its own .forEach method, like with arrays. Hewre they are side by side

  // Map
  Map.prototype.forEach((value, key, map) = () => {})

  // Array
  Array.prototype.forEach((item, index, array) = () => {})

  // You can also use a for of loop
  // Destructure the key and value out of the Map item
  for (const [key, value] of map) {
    // Log the keys and values of the Map with for...of
    console.log(`${key}: ${value}`)
  }
  ```

  ### Interfacing between Maps and other reference types

  Given that we can create a map with both object and array-style syntax, its easy
  to convert maps to and from the more established data structures.

  It's easy to convert an object to a map:

  ```jsx
  const luke = {
    firstName: "Luke",
    lastName: "Skywalker",
    occupation: "Jedi Knight",
  };

  const map = new Map(Object.entries(luke));
  ```

  And a map to an object:

  ```jsx
  const obj = Object.fromEntries(map);
  ```

  A map to an array:

  ```jsx
  const arr = Array.from(map);
  ```

  We saw earlier that a Map is a multidimensional array, so any multidimensional
  array can be used in the Map constructor invocation to turn it into a Map.

  ## Sets

  A set is special kind of array, an atypical supplement to arrays. It is
  basically an array of unique values. If you try to add a duplicate entry to a
  set it will be ignored.

  ```jsx
  const set = new Set();
  set.add("Beethoven");
  set.add("Bach");

  // Or:
  const set = new Set(["Beethoven", "Bach"]);

  // Convert array to set (note, this will remove duplicates on initialisation)
  const arr = [...set];
  ```

  We can use `keys()`, `values()`, and `entries()` with sets but obviously, `keys`
  and `values` will be identical.

raw_content:
  ---
  tags: [javascript]
  ---

  # Maps and Sets

  > Both `Map` and `Set` are new data structures introduced with ES6. They combine
  > the properties of the pre-existing data structures: arrays and objects and
  > offer advantages in certain non-typical use cases.

  ## Maps

  ### Concepts

  A map (not to be confused with the array map method) introduces something like
  the associative array model to JavaScript. Prior to ES6 the closest thing JS had
  to this was an object.

  Maps are like objects in that their structure consists in key/map pairs but like
  arrays in that the order in which the entries are placed is retained and can be
  referred to throughout the lifecycle of the Map. Also, the length of the Map is
  a known and easily returnable quantity, with objects we have to do some
  wrangling with `Object.keys` and `Object.entries` to retrieve meta data about
  the object itself. Moreover, with an object the order in which the keys are
  stored is not recorded.

  At the same time Maps are different from arrays because the index of an array
  element is not unique, it will always be a number of the form `[0], [1], [2]...`
  .

  Finally, the key can be any data type not just a string. This is an important
  characteristic of maps because although you can use numbers or booleans as the
  keys in an object, they are not actually numbers, they are converted to strings
  at execution. With Maps, whatever data type you assign as a key, retains its
  type.

  Maps

  - Have key/value pair structure
  - Have length property
  - Order of entries and insertions is maintained
  - Can use any data type as a key
  - Disallow duplicate keys

  ### Creating a Map and adding entries

  ```jsx
  const map = new Map();

  // We then pass in key/value pairs using `set`:
  map.set("firstName", "Luke");
  map.set("lastName", "Skywalker");
  map.set("occupation", "Jedi Knight");
  ```

  There is an alternative method for defining a map:

  ```jsx
  const map = new Map([
    ["firstName", "Luke"],
    ["lastName", "Skywalker"],
    ["occupation", "Jedi Knight"],
  ]);
  ```

  Internally, this data will be represented as follows:

  ```
  Map(3)
  0: {"firstName" => "Luke"}
  1: {"lastName" => "Skywalker"}
  2: {"occupation" => "Jedi Knight"}
  ```

  ### Working with map elements

  ```jsx
  // check for the existence of an item in a map by searching for existence of key
  map.has("firstName"); // return boolean

  // return all keys in a Map as iterator
  map.keys(); // MapIterator {'firstName', 'lastName', 'occupation'}

  // return all values as iterator
  map.values();

  // return array of key/value pairs
  map.entries(); // {"firstName" => "Luke", "lastName" => "Skywalker", "occupation" => "Jedi Knight"}
  // retrieve an item from a Map
  map.get("lastName"); // Skywalker

  // delete an item from a map by key, returns boolean as confirmation
  map.delete("occupation"); // true

  //return the length of a map (just like array.length)
  map.size; // 3
  // clear all values
  map.clear();
  ```

  ### Looping

  ```jsx
  // Map has its own .forEach method, like with arrays. Hewre they are side by side

  // Map
  Map.prototype.forEach((value, key, map) = () => {})

  // Array
  Array.prototype.forEach((item, index, array) = () => {})

  // You can also use a for of loop
  // Destructure the key and value out of the Map item
  for (const [key, value] of map) {
    // Log the keys and values of the Map with for...of
    console.log(`${key}: ${value}`)
  }
  ```

  ### Interfacing between Maps and other reference types

  Given that we can create a map with both object and array-style syntax, its easy
  to convert maps to and from the more established data structures.

  It's easy to convert an object to a map:

  ```jsx
  const luke = {
    firstName: "Luke",
    lastName: "Skywalker",
    occupation: "Jedi Knight",
  };

  const map = new Map(Object.entries(luke));
  ```

  And a map to an object:

  ```jsx
  const obj = Object.fromEntries(map);
  ```

  A map to an array:

  ```jsx
  const arr = Array.from(map);
  ```

  We saw earlier that a Map is a multidimensional array, so any multidimensional
  array can be used in the Map constructor invocation to turn it into a Map.

  ## Sets

  A set is special kind of array, an atypical supplement to arrays. It is
  basically an array of unique values. If you try to add a duplicate entry to a
  set it will be ignored.

  ```jsx
  const set = new Set();
  set.add("Beethoven");
  set.add("Bach");

  // Or:
  const set = new Set(["Beethoven", "Bach"]);

  // Convert array to set (note, this will remove duplicates on initialisation)
  const arr = [...set];
  ```

  We can use `keys()`, `values()`, and `entries()` with sets but obviously, `keys`
  and `values` will be identical.


path: zk/Match_statements_in_Python.md
sortable_path: zkMatch_statements_in_Python.md
title: Match statements in Python
lead: > A `match` statement is the equivalent of a switch or case statement in Python
checksum: 4eca8abdc2e26149bf6e163394aa1e01aaef771d47d653884dac8ec213313c40
created: 2024-03-30 09:03:20.181893761+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["python"]}
body:
  > A `match` statement is the equivalent of a switch or case statement in Python

  ```python
  command = input("What are you doing next? ")

  match command:
      case "quit":
          print("Goodbye!")
      case "look":
          print("Looking out")
      case "up" | "down":
          print("up or down")
      case _:
          print("The default")

  """
  What are you doing next? up
  up or down
  """

  match command.split():
      case ["go", "left"]:
          print("go left")
      case ["go", ("fast" | "slow")]:
          print("go fast or slow")

  point = (3, 3)
  match point:
      case (x, y) if x == y:
          print(f"The point is located on the diagonal Y=X at {x}.")
      case (x, y):
          print(f"Point is not on the diagonal.")
  """
  The point is located on the diagonal Y=X at 3.
  """
  ```

raw_content:
  ---
  tags: [python]
  ---

  # Match statements in Python

  > A `match` statement is the equivalent of a switch or case statement in Python

  ```python
  command = input("What are you doing next? ")

  match command:
      case "quit":
          print("Goodbye!")
      case "look":
          print("Looking out")
      case "up" | "down":
          print("up or down")
      case _:
          print("The default")

  """
  What are you doing next? up
  up or down
  """

  match command.split():
      case ["go", "left"]:
          print("go left")
      case ["go", ("fast" | "slow")]:
          print("go fast or slow")

  point = (3, 3)
  match point:
      case (x, y) if x == y:
          print(f"The point is located on the diagonal Y=X at {x}.")
      case (x, y):
          print(f"Point is not on the diagonal.")
  """
  The point is located on the diagonal Y=X at 3.
  """
  ```


path: zk/Matter_and_atoms.md
sortable_path: zkMatter_and_atoms.md
title: Matter and atoms
lead: ## Matter, elements, compounds and mixtures
checksum: 3f5628d93b77e4acadfd3a283c09d933482db0d3e12f6dda0d3487836d3b4174
created: 2024-03-30 09:03:20.185694027+00:00
modified: 2024-05-09 06:45:47.437065198+00:00
metadata: {"tags":["physics"]}
body:
  ## Matter, elements, compounds and mixtures

  - Matter is anything that occupies space and has weight. It may be found in any
    one of three states: liquid, solid, or gas.
  - Matter can be classed in terms of **elements**, **compounds** and
    **mixtures**.
    - An element is a substance that cannot be reduced to a simpler substance by
      chemical means.
    - The chemical combination of two or more elements is a compound. Compounds
      _can_ be separated or reduced by chemical means but they cannot be
      physically separated.
    - A _physical_ (note not _chemical_) combination of elements and compounds is
      a mixture.
    - For example "air" is a mixture which is made up of oxygen, nitrogen, carbon
      dioxide, and other gases.

  ## Atoms and molecules

  - Elements are made up of **atoms**.
  - A **molecule** is a type of compound where the atoms of two types of elements
    have joined together. For example sodium and chlorine are two elements that
    can join to form sodium chloride. We call this **chemical bonding**.

  ## Atomic particles
  ![](/img/atom-diagram.svg)



  - There are three particles comprising atoms: **protons**, **neutrons** and
    **electrons**.

  - Protons and neutrons comprise the nucleus of the atom. A proton is positively
    charged. A neutron has no charge.

  - Electrons orbit around the nucleus in concentric rings. Electrons are
    negatively charged. Because of this protons and electrons attract each other.

  ### Atomic number

  - The number of protons in the nucleus is called the element's **atomic**
    number.
  - We use the atomic number of elements to distinguish one element from another.

  ### Atomic weight

  - The atomic weight of an atom is its mass.
  - The mass is determined by the number of protons and neutrons in the nucleus.
  - An electron $\frac{1}{1845}$ the mass of a proton. Its mass is so neglible
    that it makes no significant difference to the atomic weight.

raw_content:
  ---
  tags: [physics]
  ---

  # Matter and atoms

  ## Matter, elements, compounds and mixtures

  - Matter is anything that occupies space and has weight. It may be found in any
    one of three states: liquid, solid, or gas.
  - Matter can be classed in terms of **elements**, **compounds** and
    **mixtures**.
    - An element is a substance that cannot be reduced to a simpler substance by
      chemical means.
    - The chemical combination of two or more elements is a compound. Compounds
      _can_ be separated or reduced by chemical means but they cannot be
      physically separated.
    - A _physical_ (note not _chemical_) combination of elements and compounds is
      a mixture.
    - For example "air" is a mixture which is made up of oxygen, nitrogen, carbon
      dioxide, and other gases.

  ## Atoms and molecules

  - Elements are made up of **atoms**.
  - A **molecule** is a type of compound where the atoms of two types of elements
    have joined together. For example sodium and chlorine are two elements that
    can join to form sodium chloride. We call this **chemical bonding**.

  ## Atomic particles
  ![](/img/atom-diagram.svg)



  - There are three particles comprising atoms: **protons**, **neutrons** and
    **electrons**.

  - Protons and neutrons comprise the nucleus of the atom. A proton is positively
    charged. A neutron has no charge.

  - Electrons orbit around the nucleus in concentric rings. Electrons are
    negatively charged. Because of this protons and electrons attract each other.

  ### Atomic number

  - The number of protons in the nucleus is called the element's **atomic**
    number.
  - We use the atomic number of elements to distinguish one element from another.

  ### Atomic weight

  - The atomic weight of an atom is its mass.
  - The mass is determined by the number of protons and neutrons in the nucleus.
  - An electron $\frac{1}{1845}$ the mass of a proton. Its mass is so neglible
    that it makes no significant difference to the atomic weight.


path: zk/Memoization.md
sortable_path: zkMemoization.md
title: Memoization with useCallback and useMemo
lead: ## Rationale
checksum: 25b257d0e108be10d16dce8b4a517529ff6421ed8267173cf576ab1adf5ceca2
created: 2024-03-30 09:03:20.190462176+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["javascript","react","react-hooks"]}
body:
  ## Rationale

  In the lifecycle of a component, React re-renders the component when an update
  is made. When React checks for any changes in a component, it may detect an
  unintended or unexpected change due to how JavaScript handles equality and
  shallow comparisons. This change in the React application will cause it to
  re-render unnecessarily.

  Additionally, if that re-rendering is an expensive operation, like a long for
  loop, it can hurt performance. Expensive operations can be costly in either
  time, memory, or processing. In addition to potential technical issues, this may
  lead to poor user experience.

  If one part re-renders, it re-renders the entire component tree. `useCallback`
  and `useMemo` can be used reduce this performance impact.

  ## useCallback

  The `useCallback` hook is used to wrap functions. It tells React to not
  re-create a wrapped function when a component re-renders, unless any of the
  useCallback's dependencies change.

  `useCallback` returns a memoized version of the callback function it is passed.
  This means that the function object returned from useCallback will be the same
  between re-renders.

  Remember that in JavaScript, functions are objects and components are functions.
  As a result, every time a component containing a function re-renders, it create
  a new instance of the function in memory.

  > Given the same dependency value, the `useCallback` hook returns the same
  > function instance between renderings (aka memoization).

  This said, for small functions that are not intensive, it doesn't really matter
  if they are not memoized.

  ### Syntax

  A standard case of this would be a function that runs on a button click, for
  instance when sending data from a form to a server. In the example below there
  is quite a lot going on, and most of it is independent of the actual UI-cycle of
  the component.

  ```jsx
  const handleSubmit = useCallback(
    async (formValues) => {
      setPendSaveConfig(true);
      const payload = new GenerateConfig({
        workflowId: project_id,
        blockId: blockId,
        config: formValues,
      });
      axios
        .post(`${process.env.REACT_APP_ENDPOINT}/save-block-config`, payload)
        .then((res) => console.log(res))
        .finally(() => setPendSaveConfig(false))
        .catch((err) => console.error(err));
    },
    [blockId, project_id]
  );
  ```

  Note that the syntax is similar to [useEffect](useEffect.md): there is a
  dependency array. The effect is the same: the function contained within
  `useCallback` will only re-rendered if one of these dependencies changes.
  However (see next section) the function will run in its memoized form on every
  click.

  ### Reference versus result

  `useCallback` only memoizes the function _object_ (the reference) not the value
  that is _returned_ by the function (the result).

  In the example below, the `calculatePi()` function reference will not change
  between renders but each time the click event fires, the value returned by
  `calculatePi()` will change. In other words, it will be assigned fresh memory.

  ```jsx
  function ParentComponent() {
    const onHandleClick = useCallback(() => {
      const special = calculatePi();
    });

    return <SubComponent handleClick={onHandleClick} />;
  }
  ```

  ### Use cases

  You should not apply `useCallback` in a blanket fashion, this can reduce
  performance rather than improve it. The best scenarios are:

  1. A functional component wrapped inside React.memo() accepts a function object
     prop

  2. When the function object is a dependency to other hooks, e.g.
     `useEffect(..., [callback])`

  3. When the function has some internal state, e.g. when the function is
     debounced or throttled.

  ## useMemo

  We can think of `useMemo` as a complement to `useCallback`. The main difference
  is that whilst `useCallback` memoizes the function reference, `useMemo` memoizes
  the function result; the value that is returned.

  > In memoization, the result is “remembered” when the same parameters are
  > passed-in subsequently. If we have a function compute 1 + 1, it will return 2.
  > But if it uses memoization, the next time we run 1’s through the function, it
  > won’t add them up; it will just remember the answer is 2 without executing the
  > adding function.

  Like `useCallback`, `useMemo` takes a function and an array of dependencies for
  the same reason: if one of the dependencies changes then the function will
  recalculate (and re-memoize) the result but they don't the same memoized value
  will be returned.

  As with `useCallback`, `useMemo` is best applied to complex functions that are
  computationally expensive, it shouldn't be used indiscriminately.

  It should not be used with asynchronous functions, in these instances,
  `useEffect` would be a better choice.

  ### Syntax

  ```jsx
  const List = React.useMemo(
    () =>
      listOfItems.map((item) => ({
        ...item,
        itemProp1: expensiveFunction(props.first),
        itemProp2: anotherPriceyFunction(props.second),
      })),
    [listOfItems]
  );
  ```

raw_content:
  ---
  tags:
    - javascript
    - react
    - react-hooks
  ---

  # Memoization with useCallback and useMemo

  ## Rationale

  In the lifecycle of a component, React re-renders the component when an update
  is made. When React checks for any changes in a component, it may detect an
  unintended or unexpected change due to how JavaScript handles equality and
  shallow comparisons. This change in the React application will cause it to
  re-render unnecessarily.

  Additionally, if that re-rendering is an expensive operation, like a long for
  loop, it can hurt performance. Expensive operations can be costly in either
  time, memory, or processing. In addition to potential technical issues, this may
  lead to poor user experience.

  If one part re-renders, it re-renders the entire component tree. `useCallback`
  and `useMemo` can be used reduce this performance impact.

  ## useCallback

  The `useCallback` hook is used to wrap functions. It tells React to not
  re-create a wrapped function when a component re-renders, unless any of the
  useCallback's dependencies change.

  `useCallback` returns a memoized version of the callback function it is passed.
  This means that the function object returned from useCallback will be the same
  between re-renders.

  Remember that in JavaScript, functions are objects and components are functions.
  As a result, every time a component containing a function re-renders, it create
  a new instance of the function in memory.

  > Given the same dependency value, the `useCallback` hook returns the same
  > function instance between renderings (aka memoization).

  This said, for small functions that are not intensive, it doesn't really matter
  if they are not memoized.

  ### Syntax

  A standard case of this would be a function that runs on a button click, for
  instance when sending data from a form to a server. In the example below there
  is quite a lot going on, and most of it is independent of the actual UI-cycle of
  the component.

  ```jsx
  const handleSubmit = useCallback(
    async (formValues) => {
      setPendSaveConfig(true);
      const payload = new GenerateConfig({
        workflowId: project_id,
        blockId: blockId,
        config: formValues,
      });
      axios
        .post(`${process.env.REACT_APP_ENDPOINT}/save-block-config`, payload)
        .then((res) => console.log(res))
        .finally(() => setPendSaveConfig(false))
        .catch((err) => console.error(err));
    },
    [blockId, project_id]
  );
  ```

  Note that the syntax is similar to [useEffect](useEffect.md): there is a
  dependency array. The effect is the same: the function contained within
  `useCallback` will only re-rendered if one of these dependencies changes.
  However (see next section) the function will run in its memoized form on every
  click.

  ### Reference versus result

  `useCallback` only memoizes the function _object_ (the reference) not the value
  that is _returned_ by the function (the result).

  In the example below, the `calculatePi()` function reference will not change
  between renders but each time the click event fires, the value returned by
  `calculatePi()` will change. In other words, it will be assigned fresh memory.

  ```jsx
  function ParentComponent() {
    const onHandleClick = useCallback(() => {
      const special = calculatePi();
    });

    return <SubComponent handleClick={onHandleClick} />;
  }
  ```

  ### Use cases

  You should not apply `useCallback` in a blanket fashion, this can reduce
  performance rather than improve it. The best scenarios are:

  1. A functional component wrapped inside React.memo() accepts a function object
     prop

  2. When the function object is a dependency to other hooks, e.g.
     `useEffect(..., [callback])`

  3. When the function has some internal state, e.g. when the function is
     debounced or throttled.

  ## useMemo

  We can think of `useMemo` as a complement to `useCallback`. The main difference
  is that whilst `useCallback` memoizes the function reference, `useMemo` memoizes
  the function result; the value that is returned.

  > In memoization, the result is “remembered” when the same parameters are
  > passed-in subsequently. If we have a function compute 1 + 1, it will return 2.
  > But if it uses memoization, the next time we run 1’s through the function, it
  > won’t add them up; it will just remember the answer is 2 without executing the
  > adding function.

  Like `useCallback`, `useMemo` takes a function and an array of dependencies for
  the same reason: if one of the dependencies changes then the function will
  recalculate (and re-memoize) the result but they don't the same memoized value
  will be returned.

  As with `useCallback`, `useMemo` is best applied to complex functions that are
  computationally expensive, it shouldn't be used indiscriminately.

  It should not be used with asynchronous functions, in these instances,
  `useEffect` would be a better choice.

  ### Syntax

  ```jsx
  const List = React.useMemo(
    () =>
      listOfItems.map((item) => ({
        ...item,
        itemProp1: expensiveFunction(props.first),
        itemProp2: anotherPriceyFunction(props.second),
      })),
    [listOfItems]
  );
  ```


path: zk/Memory.md
sortable_path: zkMemory.md
title: Memory
lead: ## Why do we need memory?
checksum: 422431e6b4bdb4b5df9c66952e4b35e386e28e5b3371139495a854b624077d88
created: 2024-03-30 09:03:20.199851706+00:00
modified: 2024-03-27 10:45:52.503525368+00:00
metadata: {"tags":["memory","motherboard"]}
body:
  ## Why do we need memory?

  > A CPU is just an operator on memory. It reads its instructions and data from
  > the memory and writes back out to the memory. (Ward 2021)

  When a [CPU](CPU_architecture.md) executes a program,
  it needs a place to store the program's **instructions** and **related data**.
  This is the role of memory.

  ## What is memory?

  The data that comprises a program is a series of bits. The basic unit of memory
  storage is a **memory cell**: a circuit that can store a single bit.

  ## Memory types

  There are two types of memory: SRAM and DRAM. Both types of RAM memory are
  _volatile_ : the memory is only retained whilst the computer has a power supply
  and is wiped when the computer is rebooted. This contrasts with the memory of
  the harddisk which is non-volatile and is retained after a reboot.

  Programs that are executing are loaded into memory because the chips that
  comprise memory can read and store data much faster than the harddisk. It would
  be possible to run a program from the harddisk but it would be 500 - 1000 times
  slower than memory.

  #### DRAM

  DRAM uses capacitors to create the memory cell:

  > a **capacitor** is an electronic component that stores electrical energy in an
  > electrical field. A device which can accumulate and release electrical charge.

  In a DRAM cell, each bit of data is stored as a charge in a capacitor. The
  presence of charge represents a '1' bit and the absence of charge represents a
  '0' bit. Each of these cells is paired with a
  [transistor](Transistors.md) that
  controls the reading and writing of data.

  However capacitors lose
  [charge](Current.md) over time due
  to leaks. As a result DRAM is memory that needs to be refreshed (recharged)
  frequently. For this reason and because it only uses one transistor and
  capacitor per bit, DRAM is the less expensive form of volatile memory.

  #### SRAM

  SRAM (Static Random Access Memory) is also volatile memory but its electronical
  implementation is different. Unlike DRAM it doesn't use capacitors. Consequently
  the transistors do not leak or need to be refreshed, hence why SRAM is _static_
  and DRAM is _dynamic_.

  SRAM uses [flip flops](Flip_flops.md)
  to store the bits. It also uses multiple transistors per bit. This makes it
  faster than DRAM but more expensive. DRAM is at least ten times slower than
  SRAM.

  ## The role of memory in computation

  The following steps outline the way in which memory interacts with the processor
  during computational cycles, once the
  [bootstrapping](Boot_process.md) process has completed and
  the OS kernel is itself loaded into memory.

  1. A file is loaded from the harddisk into memory.
  2. The instruction at the first address is sent to the CPU, travelling accross
     the data bus part of the [system bus](Bus.md).
  3. The CPU processes this instruction and then sends a request accross the
     address bus part of the system bus for the next instruction to the memory
     controller within the
     [chipset](Chipset_and_controllers.md).
  4. The chipset finds where this instruction is stored within the
     [DRAM](Memory.md#dram) and issues a request to
     have it read out and send to the CPU over the data bus.

  > This is a simplified account; it is not the case that only single requests are
  > passed back and forth. This would be inefficient and time-wasting. The kernel
  > sends to the CPU not just the first instruction in the requested file but also
  > a number of instructions that immediately follow it.

  ![](/img/memory-flow.svg)

  Every part of the above process - the journey accross the bus, the lookup in the
  controller, the operations on the DRAM, the journey back accross the bus - takes
  multiple CPU clock cycles.

  ## CPU register and cache memory

  As partly indicated in the diagram above, the CPU has its own memory in the form
  of registers and cache memory.

  Registers are a form of memory that are positioned on the same chip as the CPU.
  They are very fast but can only store a small amount of data. They are used to
  store the results of calculations and the addresses of the next instructions to
  be processed.

  The cache is SRAM memory that is separate from the DRAM memory which comprises
  the main memory. It exists in order to boost perfomance when executing the
  read/request cycles of the steps detailed above.

  There are two types of cache memory:

  - L1 cache
    - Situated on the CPU chip itself
    - Fastest to access but stores less
  - L2 cache
    - Situated outside of the CPU on its own chip
    - Slower to access than L1 but can store more data

  The L1 cache is the fastest since the data has less distance to travel when
  moving to and from the CPU. This said, the L2 cache is still very fast when
  compared to the main memory, both because it is SRAM rather than DRAM and
  because it is closer to the processor than the main memory.

  Cache controllers use complex algorithms to determine what should go into the
  cache to facilitate the best performance, but generally they work on the
  principle that what has been previously used by the CPU will be requested again
  soon. If the CPU has just asked for an instruction at memory location 555 it's
  very likely that it will next ask for the one at 556, and after that the one at
  557 and so on. The cache's controller circuits therefore go ahead and fetch
  these from slow DRAM to fast SRAM.

  ## The memory hierarchy

  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

  ![](/img/Memory-Hierarchy.jpg)

  ## Memory addresses

  > Computers assign numeric addresses to bytes of memory and the CPU can read or
  > write to those addresses

  We can think of the internals of RAM as grids of memory cells.

  Each single-bit cell in the grid can be identified using two dimensional
  coordinates, like in a graph. The coordinates are the location of that cell in
  the grid.

  Handling one bit at a time isn't very efficient so RAM accesses **multiple
  grids** of 1-bit memory cells in parallel. This allows for reads or writes of
  multiple bits at once, such as a whole byte.

  The location of a set of bits in memory is known as a **memory address**.

  ### Demonstration

  Let's imagine we have a computer system that can address up to 64KB of memory
  and our system is byte addressable. This means there are
  $64 \cdot 1024 = 65,536$ bytes of memory because 1KB = 1024 bytes.

  We therefore have 65,536 addresses and each address can store one byte. So our
  addresses go from 0 to 65, 535.

  We now need to consider how many bits we need to uniquely represent an address
  on this system.

  What does this mean? Although there are approximately 64 thousand bytes of
  memory, to refer to each byte we can't just use 1, 2, 3... because computers use
  binary numbers. We need a binary number to refer to a given byte in the the 64KB
  of memory. The question we are asking is: how long does this binary number need
  to be to be able to represent each of the 64 thousand bytes?

  1 bit can represent two addresses: 0 and 1. 2 bits can represent four addresses:
  00, 01, 10, 11. The formula is as follows: number of addresses = $2^n$ where $n$
  is the number of bits.

  We need to reverse this formula to find out how many bits we need to represent a
  given number of addresses. We can do this with a
  [logarithm](Logarithms.md).

  We can reverse the formula as follows: number of bits = $\log_2$(number of
  addresses).

  In our case we have 65,536 addresses so we need $\log_2(65,536)$ bits to
  represent each address. This is approximately 16 bits. Thus a 16 bit memory
  address is needed to address 65, 546 bytes.

  Using memory addresses we end up with tables like the following:

  | Memory address   | Data             |
  | ---------------- | ---------------- |
  | 0000000000000000 | 1010101010101010 |
  | 0000000000000001 | 0010001001001011 |
  | 0000000000000010 | 0010001001001010 |

  This is hard to parse so we can instead use
  [hexadecimal numbers](Hexadecimal_number_system.md)
  to represent the addresses:

  | Memory address (as hex) | Data (as binary) |
  | ----------------------- | ---------------- |
  | 0x0000                  | 1010101010101010 |
  | 0x0001                  | 0010001001001011 |
  | 0x0002                  | 0010001001001010 |

  By itself, the the data is meaningless but we know from
  [binary encoding](Binary_encoding.md) that the
  binary data will correspond to some meaningful data, such as a character or a
  colour, depending on the encoding scheme used. The above table could correspond
  to the characters for 'A', 'B' and 'C' in the ASCII encoding scheme:

  | Memory address (as hex) | Data (as binary) | Data (as ASCII) |
  | ----------------------- | ---------------- | --------------- |
  | 0x0000                  | 1010101010101010 | A               |
  | 0x0001                  | 0010001001001011 | B               |
  | 0x0002                  | 0010001001001010 | C               |

raw_content:
  ---
  tags: [memory, motherboard]
  ---

  # Memory

  ## Why do we need memory?

  > A CPU is just an operator on memory. It reads its instructions and data from
  > the memory and writes back out to the memory. (Ward 2021)

  When a [CPU](CPU_architecture.md) executes a program,
  it needs a place to store the program's **instructions** and **related data**.
  This is the role of memory.

  ## What is memory?

  The data that comprises a program is a series of bits. The basic unit of memory
  storage is a **memory cell**: a circuit that can store a single bit.

  ## Memory types

  There are two types of memory: SRAM and DRAM. Both types of RAM memory are
  _volatile_ : the memory is only retained whilst the computer has a power supply
  and is wiped when the computer is rebooted. This contrasts with the memory of
  the harddisk which is non-volatile and is retained after a reboot.

  Programs that are executing are loaded into memory because the chips that
  comprise memory can read and store data much faster than the harddisk. It would
  be possible to run a program from the harddisk but it would be 500 - 1000 times
  slower than memory.

  #### DRAM

  DRAM uses capacitors to create the memory cell:

  > a **capacitor** is an electronic component that stores electrical energy in an
  > electrical field. A device which can accumulate and release electrical charge.

  In a DRAM cell, each bit of data is stored as a charge in a capacitor. The
  presence of charge represents a '1' bit and the absence of charge represents a
  '0' bit. Each of these cells is paired with a
  [transistor](Transistors.md) that
  controls the reading and writing of data.

  However capacitors lose
  [charge](Current.md) over time due
  to leaks. As a result DRAM is memory that needs to be refreshed (recharged)
  frequently. For this reason and because it only uses one transistor and
  capacitor per bit, DRAM is the less expensive form of volatile memory.

  #### SRAM

  SRAM (Static Random Access Memory) is also volatile memory but its electronical
  implementation is different. Unlike DRAM it doesn't use capacitors. Consequently
  the transistors do not leak or need to be refreshed, hence why SRAM is _static_
  and DRAM is _dynamic_.

  SRAM uses [flip flops](Flip_flops.md)
  to store the bits. It also uses multiple transistors per bit. This makes it
  faster than DRAM but more expensive. DRAM is at least ten times slower than
  SRAM.

  ## The role of memory in computation

  The following steps outline the way in which memory interacts with the processor
  during computational cycles, once the
  [bootstrapping](Boot_process.md) process has completed and
  the OS kernel is itself loaded into memory.

  1. A file is loaded from the harddisk into memory.
  2. The instruction at the first address is sent to the CPU, travelling accross
     the data bus part of the [system bus](Bus.md).
  3. The CPU processes this instruction and then sends a request accross the
     address bus part of the system bus for the next instruction to the memory
     controller within the
     [chipset](Chipset_and_controllers.md).
  4. The chipset finds where this instruction is stored within the
     [DRAM](Memory.md#dram) and issues a request to
     have it read out and send to the CPU over the data bus.

  > This is a simplified account; it is not the case that only single requests are
  > passed back and forth. This would be inefficient and time-wasting. The kernel
  > sends to the CPU not just the first instruction in the requested file but also
  > a number of instructions that immediately follow it.

  ![](/img/memory-flow.svg)

  Every part of the above process - the journey accross the bus, the lookup in the
  controller, the operations on the DRAM, the journey back accross the bus - takes
  multiple CPU clock cycles.

  ## CPU register and cache memory

  As partly indicated in the diagram above, the CPU has its own memory in the form
  of registers and cache memory.

  Registers are a form of memory that are positioned on the same chip as the CPU.
  They are very fast but can only store a small amount of data. They are used to
  store the results of calculations and the addresses of the next instructions to
  be processed.

  The cache is SRAM memory that is separate from the DRAM memory which comprises
  the main memory. It exists in order to boost perfomance when executing the
  read/request cycles of the steps detailed above.

  There are two types of cache memory:

  - L1 cache
    - Situated on the CPU chip itself
    - Fastest to access but stores less
  - L2 cache
    - Situated outside of the CPU on its own chip
    - Slower to access than L1 but can store more data

  The L1 cache is the fastest since the data has less distance to travel when
  moving to and from the CPU. This said, the L2 cache is still very fast when
  compared to the main memory, both because it is SRAM rather than DRAM and
  because it is closer to the processor than the main memory.

  Cache controllers use complex algorithms to determine what should go into the
  cache to facilitate the best performance, but generally they work on the
  principle that what has been previously used by the CPU will be requested again
  soon. If the CPU has just asked for an instruction at memory location 555 it's
  very likely that it will next ask for the one at 556, and after that the one at
  557 and so on. The cache's controller circuits therefore go ahead and fetch
  these from slow DRAM to fast SRAM.

  ## The memory hierarchy

  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

  ![](/img/Memory-Hierarchy.jpg)

  ## Memory addresses

  > Computers assign numeric addresses to bytes of memory and the CPU can read or
  > write to those addresses

  We can think of the internals of RAM as grids of memory cells.

  Each single-bit cell in the grid can be identified using two dimensional
  coordinates, like in a graph. The coordinates are the location of that cell in
  the grid.

  Handling one bit at a time isn't very efficient so RAM accesses **multiple
  grids** of 1-bit memory cells in parallel. This allows for reads or writes of
  multiple bits at once, such as a whole byte.

  The location of a set of bits in memory is known as a **memory address**.

  ### Demonstration

  Let's imagine we have a computer system that can address up to 64KB of memory
  and our system is byte addressable. This means there are
  $64 \cdot 1024 = 65,536$ bytes of memory because 1KB = 1024 bytes.

  We therefore have 65,536 addresses and each address can store one byte. So our
  addresses go from 0 to 65, 535.

  We now need to consider how many bits we need to uniquely represent an address
  on this system.

  What does this mean? Although there are approximately 64 thousand bytes of
  memory, to refer to each byte we can't just use 1, 2, 3... because computers use
  binary numbers. We need a binary number to refer to a given byte in the the 64KB
  of memory. The question we are asking is: how long does this binary number need
  to be to be able to represent each of the 64 thousand bytes?

  1 bit can represent two addresses: 0 and 1. 2 bits can represent four addresses:
  00, 01, 10, 11. The formula is as follows: number of addresses = $2^n$ where $n$
  is the number of bits.

  We need to reverse this formula to find out how many bits we need to represent a
  given number of addresses. We can do this with a
  [logarithm](Logarithms.md).

  We can reverse the formula as follows: number of bits = $\log_2$(number of
  addresses).

  In our case we have 65,536 addresses so we need $\log_2(65,536)$ bits to
  represent each address. This is approximately 16 bits. Thus a 16 bit memory
  address is needed to address 65, 546 bytes.

  Using memory addresses we end up with tables like the following:

  | Memory address   | Data             |
  | ---------------- | ---------------- |
  | 0000000000000000 | 1010101010101010 |
  | 0000000000000001 | 0010001001001011 |
  | 0000000000000010 | 0010001001001010 |

  This is hard to parse so we can instead use
  [hexadecimal numbers](Hexadecimal_number_system.md)
  to represent the addresses:

  | Memory address (as hex) | Data (as binary) |
  | ----------------------- | ---------------- |
  | 0x0000                  | 1010101010101010 |
  | 0x0001                  | 0010001001001011 |
  | 0x0002                  | 0010001001001010 |

  By itself, the the data is meaningless but we know from
  [binary encoding](Binary_encoding.md) that the
  binary data will correspond to some meaningful data, such as a character or a
  colour, depending on the encoding scheme used. The above table could correspond
  to the characters for 'A', 'B' and 'C' in the ASCII encoding scheme:

  | Memory address (as hex) | Data (as binary) | Data (as ASCII) |
  | ----------------------- | ---------------- | --------------- |
  | 0x0000                  | 1010101010101010 | A               |
  | 0x0001                  | 0010001001001011 | B               |
  | 0x0002                  | 0010001001001010 | C               |


