# entries

title: Compile_from_source
last_modified: 2024-10-18 18:55:56
body:
# Compile package from source

1. Clone the repo from GitHub
2. `cd` into the repo
3. Run `makepkg -c`

## Remove compiled package

1. If `build` dir still exists for package try running `make uninstall`.
2. Reinstall the package and run `make uninstall`
3. Locate the installed package in `/usr/local/bin` and run `sudo rm -rf`


title: Theorems_and_empty_sets
last_modified: 2024-10-19 10:00:27
body:
# Theorems and empty sets

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

_Demonstration_

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

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

Propositions which possess this property are called theorems:

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

We represent a theorem as:

$$
\vdash P
$$

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


title: Reciprocals
last_modified: 2024-10-18 18:55:56
body:
# Recipricols

The [Property of Multiplicative Identity](Multiplicative%20identity.md) applies
to fractions as well as to whole numbers:

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

With fractions there is a related property: the **Multiplicative Inverse**.

> If $\frac{a}{b}$ is any fraction, the fraction $\frac{b}{a}$ is called the
> _multiplicative inverse_ or _reciprocol_ of $\frac{a}{b}$. The product of a
> fraction multiplied by its reciprocol will always be 1. $$ \frac{a}{b} \cdot
> \frac{b}{a} = 1$$

For example:

$$
\frac{3}{4} \cdot \frac{4}{3} = \frac{12}{12} = 1
$$

In this case $\frac{4}{3}$ is the reciprocol or multiplicative inverse of
$\frac{3}{4}$.

This accords with what we know a fraction to be: a representation of an amount
that is less than one whole. When we multiply a fraction by its reciprocol, we
demonstrate that it makes up one whole.

This also means that whenever we have a whole number $n$, we can represent it
fractionally by expressing it as $\frac{n}{1}$


title: Soundness
last_modified: 2024-10-18 18:55:56
body:
# Soundness

Recall that in the definition of
[deductive validity](Validity_and_entailment.md#validity)
we do not say: an argument is valid iff if the premises _are true_ and the
conclusion _is true_. We say _if it is possible for the premises to be true_.
This is important: we are not interested in the actual truth of the premises or
the conclusion.

#### Demonstration

Therefore this argument is valid:

```
(P1) Oranges are the same colour as bananas.
(P2) Bananas are yellow.
____________________________________________
(C) Oranges are yellow.
```

Of course oranges are not yellow but _were_ (P1) true, then given (P2), the
conclusion must also be true.

This argument is also valid:

```
(P1) Oranges are the same colour as carrots.
(P2) Carrots are orange.
____________________________________________
(C) Oranges are orange in colour.
```

The difference here is that the premises happen to be true and, given that the
argument is valid, the conclusion must also be true. What we have defined here
is **soundness**: the argument is said to be sound as well as valid. This is an
additional and stronger criterion of evaluation.

> An argument is sound if and only if it is deductively valid and all its
> premises are true.

We must not forget that truth alone is not the sole condition for soundness. We
can have arguments whose conclusion and premises are all true without the
argument being sound:

```
(P1) London is the capital of the United Kingdom
(P2) The capital of the United Kingdom is in the southern part of the United Kingdom.
(P3) Cambridge is not the capital of the United Kingdom
____________________________________________
(C) London is south of Cambridge
```

All propositions are true here but the argument is not deductively valid: the
premises are all true but the conclusion is false.

We can also have arguments which are valid but which are not sound:

```
(P1) Vitamin C prevents colds.
(P2) Vitamin C does not prevent colds.
____________________________________________
(C) Vitamin C is harmless
```

This argument is valid because we cannot consistently assert the premises and
deny the conclusion. In either case, the conclusion can be said to follow from
the premises. The problem is that we cannot consistently assert both premises:
it is not possible for both propositions to be true at the same time.


title: Testing_Python_code
last_modified: 2024-10-18 18:55:56
body:
# Testing Python code

## `pytest`

Pytest is the most popular testing library for Python. It is not included with
the Python standard library so it must be installed with
[pip](Python_package_management.md).
While it does not include a declaration library, it is robust enough to handle
most scenarios having a rich and expressive set of constructs and decorators
that let you declare what your tests should do, under what conditions they
should run, and how they should interact with the rest of your system.

### Using `pytest`

- Pytest will automatically detect test files so long as they are named
  appropriately. E.g. for a module called `lorem`, it will detzect the unit test
  files `lorem_test.py` and `test_lorem.py`.
- In order to detect tests it should be run from a directory level above them

### Examples

Here is a basic example of using pytest for a local module callled `palindrome`:

```py
import palindrome

def test_is_palindrome():
    assert palindrome.is_palindrome('soros')
    assert palindrome.is_palindrome('torot')
    assert not palindrome.is_palindrome('chair')
```

## Mocking

`patch()` and `Mock` enable us to mock objects whilst testing (classes,
functions, methods and properties belonging). They are used in combination.

The `@patch` decorator temporarily replaces a specified object in your code with
a mock object and restores the original object after the test is complete

A Mock object simulates the object it replaces so that the object behaves as
expected during testing. For example, if your code calls
`some_function.some_method()`, and `some_method` is mocked, calling
`some_method` will not execute real logic but will interact with the Mock object
instead. Mock objects record details about how they have been used, like what
methods have been called, with what arguments, etc., allowing you to make
assertions about how they have been used.

> `@patch` and `Mock` work together because a patch is used to replace an object
> or attribute with a Mock object. `Mock` handles the simulated functionality,
> and `@patch` designates the real value we are replacing with the mock.

### Example case

I will use the following example from one my projects:

```py
# get_articles.py
def get_articles(article_type: str) -> Optional[Dict[str, Any]]:
    """Retrieve articles from Pocket API"""

    if POCKET_LAMBDA_ENDPOINT is None:
        logging.error("Error: POCKET_LAMBDA_ENDPOINT envinronment variable is not set")
        return None
    else:
        # Interpolate the article_type into the Pocket request URL
        endpoint = POCKET_LAMBDA_ENDPOINT.format(article_type=article_type)

    try:
        response = requests.get(endpoint)
        response.raise_for_status()
        return response.json()

    except RequestException as e:
        print(f"An error occurred: {e}")
        return None
```

This function: sources a URL from an environment variable, interpolates a query
string into the URL (which comes in as a parameter), makes a request to the URL,
and returns the response as JSON.

It has some safeguards in place:

- It checks that the environment variable is set
- It checks that the request was successful

In the example we could use a Mock object to simulate the response from the
Pocket API. This would allow us to test the function without having to make a
real request to the API:

```py
def test_successful_request():
    # Replace the requests.get function with a Mock object (mock_get)
    with patch("requests.get") as mock_get:
        # Specify the return value of the mock_get object)
        mock_get.return_value = Mock(ok=true)
        mock_get.return_value.json.return_value = {"value": "test"}

        # Call the function under test
        result = get_articles("gaby")

    # Assert expected outcomes
    mock_get.assert_called_once_with(endpoint)
    assert result == mock_json_response
```

The example above follows the **Arrange, Act, Assert** pattern:

| Stage   | Action                                                                          |
| ------- | ------------------------------------------------------------------------------- |
| Arrange | Replace the `requests.get` function with `patch` and set properties with `Mock` |
| Act     | Call the function under test                                                    |
| Assert  | Assert that the function under test behaved as expected                         |

### Alternative mock syntax

The `with patch(...) as mock_name` syntax is fine for small-scale mocking but
can become cumbersome when you are mocking several dependencies.

There is another syntax (which I actually find clearer). Say we have a function
with three dependencies: `update_worksheet`, `process_articles`, `get_articles`.
We could mock like so:

```py
@patch("app.update_worksheet")
@patch("app.process_articles")
@patch("app.get_articles")
def test_success(
    mock_get_articles, mock_process_articles, mock_update_worksheet
):
    mock_get_articles.return_value = [1, 2, 3]
```

Here the patching is done by the decorator and the mocks are defined as
parameters to the test function (always in reverse order)

### Mock assertion lexicon

#### `return_value`

State what the mock should return

```py
my_mocked_function.return_value = ['one', 'two', 'three']
```

#### `call_count`

Test how many times a dependent function is called

```py
assert my_mocked_function.call_count = 3
```

#### `assert_any_call()`

Test that a given mock is called at least once during the execution of the
function under test

```py
my_mocked_function.assert_any_call(some_mocked_return_value)
```

When the output of one function is used as a parameter to another, and we don't
particularly care about the details of what is concerned we can just pass the
executed function, e.g:

```py
my_mocked_function.assert_any_call(preceding_function())
```

#### `call_args_list`

Get a list of all the arguments that a mock object was called with during the
test.

`call_args_list` is useful when you want to check the arguments that a mock
object was called with during the test, especially if the mock object was called
multiple times with different arguments. You can use it to inspect the arguments
of each call and make assertions based on them.

```py
second_my_mocked_function_call = my_mocked_function.call_args_list[1]

# check the first argument of the second call:

assert second_my_mocked_function_call[0][0] == "expected arg"
```

#### `side_effect`

Use to trigger a side effect when returning a value from a mock. Most useful for
mocking exceptions.

```py
my_mocked_function.side_effect = Exception("Some exception raised")
```

## Testing exceptions with `raises`

Testing exceptions is quite straightforward. You can use the `raises` helper
provided by pytest, and combine this with `excinfo` ("exception info") to
inspect the exception message.

```py
if POCKET_LAMBDA_ENDPOINT is None:
    raise ValueError(
        "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set"
    )
```

Then to test this, we would use pytest's `excinfo` fixture along with `raises`:

```py
 with pytest.raises(ValueError) as excinfo:  # Watch for the ValueError
    get_articles("some_type")

    assert "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set" in str(
        excinfo.value
    )
```

We could actually simplify the above test by using the `match` parameter with
`raise`. This way we do not need the separate assertion:

```py
with pytest.raises(ValueError, match="Error: POCKET_LAMBDA_ENDPOINT environment variable is not set"):
    get_articles("some_type")
```

Note that `excinfo` is best used for testing the exception text that you the
developer explicitly `raise`. For exceptions tha may occur naturaly in the code
you are testing, you should use `caplog` or `capsys` (see below).

## Before-each and after-each

When testing functions, we achieve this in Python using `setup_function` and
`teardown_function` methods. These methods are called before and after each test
method respectively.

To apply a "before each" to _every test_ just put your setup function and/or
teardown function at the top level of your test module.

For example, below we set and remove an env var before and after each test:

```py
@pytest.fixture(scope="function") # specify that this fixture should be run before each function test
def setup_function():
    print("Setting up test environment...")
    os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}"


def teardown_function():
    print("Tearing down test environment...")
    del os.environ["POCKET_LAMBDA_ENDPOINT"]
```

If the setup/teardown should only be applied to a subset of tests, just pass the
name of the fixture as a parameter to the test function:

```py
def some_function(setup_function):
    # setup_function will be run before this test
```

You don't need to use the names `setup_function` and `teardown_function` so long
as you are passing the fixture as a parameter.

You can also use `yield` to combine the setup and teardown into a single
function:

```py
@pytest.fixture(scope="function")
def setup_function():
    os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}"
    yield
    del os.environ["POCKET_LAMBDA_ENDPOINT"]
```

### Another example:

The following test suite uses the same three mocked functions in every test. The
following set-up assigns the mocks before each test and resets after each
individual test has run:

```py
@pytest.fixture(scope="function")
def setup_function():
    with patch("app.get_articles") as mock_get_articles, patch(
        "app.process_articles"
    ) as mock_process_articles, patch("app.update_worksheet") as mock_update_worksheet:
        yield mock_get_articles, mock_process_articles, mock_update_worksheet
```

Then to use:

```py
def individual_test(setup_function):
    mock_get_articles, mock_process_articles, mock_update_worksheet = setup_function
    # Now each mock can be referenced using the vars above

```

## Parameterized tests

For a sequence of tests that are repetitive, to avoid repeating the same code
over and over again, we can use parameterized tests. This is where we pass in a
list of parameters to the test function and the test function is run once for
each parameter.

For example, in the function below I am handling numerous possible Exceptions
that could be raised by the `requests.get` method:

```py
    try:
        response = requests.get(endpoint)
        response.raise_for_status()
        return response.json()

    except HTTPError as http_err:
        logging.error(f"HTTP Error occurred: {http_err}")

    except ConnectionError as conn_err:
        logging.error(f"Connection Error occurred: {conn_err}")

    except Timeout as timeout_err:
        logging.error(f"Timeout Error occurred: {timeout_err}")

    except RequestException as e:
        logging.error(f"Request Exception occurred: {e}")

    return None
```

Instead of writing something like the following for each of the four exceptions:

```py

 def test_exception_generic(caplog):
    with patch("requests.get", side_effect=RequestException("Some error")):
        result = get_articles("some_type")

    assert "Request Exception occurred" in caplog.text
    assert result is None
```

I could parameterize like so:

```py
@pytest.mark.parametrize(
    "exception_type, log_message",
    [
        (RequestException, "Request Exception occurred: "),
        (HTTPError, "HTTP Error occurred: "),
        (Timeout, "Timeout Error occurred: "),
        (ConnectionError, "Connection Error occurred: "),
    ],
)
def test_exceptions(caplog, exception_type, log_message):
    with patch("requests.get", side_effect=exception_type("Some error")):
        result = get_articles("some_type")

    assert log_message in caplog.text
    assert result is None
```

## Caplog, syslog, excinfo

`caplog` and `capsys` are built-in pytest fixtures. `caplog` lets you test log
messages. `capsys` lets you test stdout and stderr. As such they are very useful
when testing that error messages are logged correctly.

### `caplog`

In our example, if the endpoing environment is not set, we log an error message.
We can test that this message is logged correctly using `caplog`:

```py
def test_no_endpoint_env_var(caplog):
    os.environ.pop("POCKET_LAMBDA_ENDPOINT", None)  # Remove env variable if it exists

    with caplog.at_level(logging.ERROR):
        result = get_articles("some_type")

    assert (
        "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set" in caplog.text
    )
    assert result is None
```

> Note tha we pass in `caplog` as a parameter to the test function. This is how
> pytest knows to use it as a fixture.

### `capsys`

In our example, if the request is unsuccessful, we log an error message with
`print` rather than `logging`. We can test that this message is printed
correctly using `capsys` to check the stdout:

```py
def test_http_error(capsys):
    with patch("requests.get") as mock_get:
        mock_get.return_value = Mock(ok=False, status_code=404)

        # Raise an HTTP error when raise_for_status is called
        mock_get.return_value.raise_for_status.side_effect = RequestException()

        result = get_articles("some_type")

    captured = capsys.readouterr()
    assert "An error occurred" in captured.out
```

### `excinfo`


title: Law_of_the_Excluded_Middle
last_modified: 2024-10-18 18:55:56
body:
# Law of the Excluded Middle

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

$$
P \lor \sim P
$$


title: Environmental_and_shell_variables
last_modified: 2024-10-18 19:13:04
body:
# Environmental and shell variables

## Important!

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

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

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

## Questions, research

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

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

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

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

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

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

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

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

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

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

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

## What are shell variables useful for?

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

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

## Viewing shell and environmental variables

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### Using config files to create variables

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

## Important environmental and shell variables

### `PATH`

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

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

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

### `SHELL`

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

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

### `USER`

The current logged in user.

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

### `PWD`

The current working directory.

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


title: Non_null_assertion_in_TypeScript
last_modified: 2024-10-18 18:55:56
body:
# Non-null assertion

Oftentimes you will encounter errors of the form:

`Property [property_name] does not exist on type [type_name].ts(2339)`

This means the interpreter is not able to determine whether the property which
you are seeking to change exists. It can't be sure that you are not trying to
update a property that is either `null` or `undefined`. This is good type
checking but it means that in cases when _you know_ the property either exists
or _will_ exist at runtime, you are going to face a code-breaking error.

To get around this you can use non-null assertion. Like the name suggests, here
you are saying to TypeScript: thanks but I am sure this property exists. When
you do this you get the value you are trying to change with `null` and
`undefined` excluded from it.

This typically occurs when you are assigning a property to a value and TS
doesn't know whether the value will exist or be accessible at this point in the
runtime. A good scenario would be dynamic data passed into a function:

```ts
function doSomethingWith(typeAlias: ITypeAlias) {
  let x = typeAlias!.somePropertyEvidentAtRuntime;
}
```

However it should be used carefully and sparingly because you are obviously
turning off core type-checking and overuse nullifies the purpose of TypeScript.

One way to get around it is to use better
[type-guarding](Type_guarding_and_narrowing.md) and conditionality and to
cover cases where the value may be undefined.


title: Full_Node_API_example
last_modified: 2024-10-18 18:55:56
body:
# Creating a RESTful API: Full example

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

// Routes
app.use("/api/courses", courses);

// Middlewear
app.use(express.json());
app.use(helmet());

const courses = [
  {
    id: 1,
    name: "First course",
  },
  {
    id: 2,
    name: "Second course",
  },
  {
    id: 3,
    name: "Third course",
  },
];

if (app.get("env") === "development") {
  app.use(morgan("common"));
}

app.listen(port, () => console.log(`Listening on ${port}`));

function validateCourse(course) {
  const schema = Joi.object({
    name: Joi.string().min(3).required(),
  });

  const { error } = schema.validate(course);
  return error;
}

/**
 *  Note that the following request handlers would not be stored in `index.js` they would be in their dedicated routing file.
 **/

// Return all data from API
courses.get("/", (req, res) => {
  res.send(courses);
});

// Return a specific value
courses.get("/:id", (req, res) => {
  const course = courses.find((c) => c.id === parseInt(req.params.id));
  if (!course) res.status(404).send("A course with the given ID was not found");
  res.send(course);
});

// Add a new course
courses.post("/", (req, res) => {
  const schema = Joi.object({
    name: Joi.string().min(3).required(),
  });

  const { error } = schema.validate(req.body);
  if (error)
    return error.details.map((joiErr) => res.status(400).send(joiErr.message));

  const course = {
    id: courses.length + 1,
    name: req?.body.name,
  };
  courses.push(course);
  res.send(course);
});

// Update a course
courses.put("/:id", (req, res) => {
  const course = courses.find((c) => c.id === parseInt(req.params.id));

  if (!course)
    return res.status(404).send("A course with the given ID was not found");
  const { error } = validateCourse(req.body);

  if (error)
    return error.details.map((joiErr) => res.status(400).send(joiErr.message));

  course.name = req.body.name;
  res.send(course);
});

// Delete a course
courses.delete("/:id", (req, res) => {
  const course = courses.find((c) => c.id === parseInt(req.params.id));
  if (!course)
    return res.status(404).send("A course with the given ID was not found");

  courses.indexOf(course);
  courses.splice(index, 1);

  res.send(course);
});
```


title: IP_addresses
last_modified: 2024-10-18 18:55:56
body:
# IP addresses

## IPv4 vs IPv6

IP addresses come in two formats and bit-lengths depending on which version of
the [Internet Protocol](Internet_Layer_of_Internet_Protocol.md) is being used.

- IPv4 addresses are 32-bits in length
- IPv6 addresses are 128-bits in length

The larger the bit-length, the more unique addresses that can be generated. Thus
IPv6 can create more addresses that IPv4.

This entry focuses on IPv4 addresses.

## Anatomy of an IPv4 address

Each of the 32 bits are divided into four groups which are each 8 bits in
length. Each group therefore comprises an octet and is separated by dots.

The decimal number system, rather than binary is used to represent the values.
This means each binary octet is three decimal digits in length.

The 32 bits comprise two major groupings

- the **network prefix**
- the **host**

![Different sections of IP address](../img/ip-address-anatomy.png)

Devices connected to the same network share the same network prefix. They are
said to be on the same **subnet**. However each device will have a unique value
for the host octet.

(This account is somewhat idealised. The length of the prefix does not always
align with the octet boundary. For instance, the prefix could be 25 bits in
length, rather than 24. In this case it would "steal" one bit from the host
section making it 23 bits in length.)

Any changes to the length of the network prefix change the number of hosts
available on the subnet. If the network prefix is longer, the number of unique
hosts is reduced. If the network prefix is shorted, the number of unique hosts
is increased.

## Identifying the network and host groupings

Given that the network prefix and host identifier do not always stick to set
boundaries, we require a way of distinguishing the two values. There are two
dominant methods.

### CIDR notation

This stands for _Classless Inter-Domain Routing_. An example:

> 192.168.1.23/24

The value after the forward slash is the number of bits that specify the network
address. In the example, it is the standard 24-bits. Thus 192.168.1.23 stands
for the network.

### Subnet masking

The subnet mask is another number _in addition to_ the network address. It is a
kind of superimposed map on top of the address.

Here is an example of a subnet mask:

> 11111111.11111111.11111111.00000000

The denary form would be as follows (as 255 is the maximal decimal number that
can be represented with a single 8-bit number and 0 is the smallest):

> 255.255.255.0

In the binary form, the 1 values represent the bits which designate the network
address and the 0 values represent the bits that designate the host.

In the example above this corresponds to the idealised 32-bit:8-bit ratio of
CIDR /24.

There is a clever consequence of the subnet mask: if you apply a bitwise AND
operator against the IP address and mask (both in their binary form) you can
determine whether two addresses are on the same network.

To compare the IP address 192.168.1.23 against 192.168.1.100 to demonstrate:

```
192.168.1.23:
IP:     11000000.10101000.00000001.00010111
Mask:   11111111.11111111.11111111.00000000
Result: 11000000.10101000.00000001.00000000
___________________________________

192.168.1.100:
IP:     11000000.10101000.00000001.00010111
Mask:   11111111.11111111.11111111.00000000
Result: 11000000.10101000.00000001.00000000
```

After applying the bitwise AND logic we see that the result is identical for
both IPs indicating they are on the same network (share the same network
prefix), whilst the host value is "masked".

Here is a scenario where the subnet mask indicates that two IPs are not on the
shared network:

```
192.168.1.23:
IP:     11000000.10101000.00000001.00010111
Mask:   11111111.11111111.11111111.00000000
Result: 11000000.10101000.00000001.00000000
___________________________________

192.168.2.1
IP:     11000000.10101000.00000010.00000001
Mask:   11111111.11111111.11111111.00000000
Result: 11000000.10101000.00000010.00000000

```

255.255.255.0 is not the only possible subnet mask. There are masks
corresponding to ratios other than CIDR /24. For example:

- 255.0.0.0 (/8)
- 255.255.0.0 (/8)
- 255.255.255.192 (/26)
- 255.255.255.240 (/28)

As noted previously, the different ratios will obviously affect the number of
unique hosts available on the subnet.

## Determining the range of hosts: practical example

Let's say we have the following IP address expressed in CIDR: 192.168.0.133/27.
In binary this would be:

```
110000000.10101000.00000000.100--00101
```

The `--` indicates the demarcation point between the network prefix and the bits
designated for the host. 32 - 27 leaves us 5 bits for our range of hosts. This
gives us 2^5 = 32 unique host values.

In fact it will actually be 30 values since the first value (`00000`) will be
used to identify the network itself and the last value (`11111`) will be the
broadcast address - the address used when a message needs to be sent to all
hosts on the network.

An actual host address in this range is included in the example: `00101`.


title: Defining_a_computer
last_modified: 2024-10-18 18:55:56
body:
# Defining a computer

> A general-purpose computer is one that, given the appropriate instructions and
> required time, should be able to perform most common computing tasks.

This sets a general purpose computer aside from a special-purpose computer, like
the one you might find in your dishwasher which may have its instructions
hardwired or coded into the machine. Special purpose computers only perform a
single set of tasks according to prewritten instructions. We’ll take the term
_computer_ to mean general purpose computer.

Simplified model of what a computer is:
![1.4-Input-Process-Output.png](1.4-Input-Process-Output.png)

Although the input, output and storage parts of a computer are very important,
they will not be the focus of this course. Instead we are going to learn all
about the process part, which will focus on how the computer is able to follow
instructions to make calculations.

## **Supplementary Resources**

### Early computing (_Crash Course Computer Science)_

[Early Computing: Crash Course Computer Science #1](watch?v=O5nskjZ_GoI)

- The abacus was created because the scale of society had become greater than
  what a single person could create and manipulate in their mind.
  - Eg thousands of people in a village and tens of thousands of cattle
- In a basic abacus each row of beads (say its coloured) represents a different
  power of ten
- As well as aiding calculation, the abacus acts as a primitive storage device
- Similar early computing devices: astrolabe, slide rule, sunrise clocks, tide
  clocks

> As each increase in knowledge, as well as on the contrivance of every new
> tool, human labour becomes abridged. **Charles Babbage**

- One of the first computers of the modern era was the Step Reckoner built by
  Leibniz in 1694.
  - In addition to adding, this machine was able to multiply and divide
    basically through hacks because from a mechanical point of view,
    multiplications and divisions are just many additions and subtractions
  - For example, to divide 17/5, we just subtract 5, then 5, then 5 again until
    we can't do anymore hence two left over
- But as these machines were expensive and slow, people used pre-computed tables
  in book form generated by human computers. Useful particularly for things like
  square roots.
- Similarly range tables were created that aided the military in calculating
  distances for gunboat artillery which factored in contextual factors like
  wind, drift, slope and elevation. These were used well into WW2 but they were
  limited to the particular type of cannon or shell

![Screenshot_2020-08-09_at_21.32.54 1.png](Screenshot_2020-08-09_at_21.32.54%201.png)

![Screenshot_2020-08-09_at_21.34.48.png](Screenshot_2020-08-09_at_21.34.48.png)

> Before the invention of actual computers, 'computer' was a job-title denoting
> people who were employed to conduct complex calculations, sometimes with the
> aid of machinery, but most often not. This persisted until the late 18th
> century when the word changed to include devices like adding machines.

- Babbage sought to overcome this by designing the **Difference Engine** which
  was able to compute polynomials. Complex mathematical expressions that have
  constants, variables and exponent. He failed to complete it in his lifetime
  because of the complexity and number of intricate parts required. His model
  was eventually successfully created in the 90s using his designs and it
  worked.
  - But while he was coming up with this he also conceived of a better and
    general purpose computing device that wasn't limited to polynomial
    calculations → the Analytical Engine.
  - It could run operations in sequence and had memory and a primitive printer.
    It was way ahead of its time and was never completed.
  - Ada #Lovelace wrote hypothetical programs for the Analytical Engine, hence
    she is considered the world's first computer programmer.
- At this point then, computing was limited to scientific and engineering
  disciplines but in 1890, the US govt needed a computer in order to comply with
  the constitutional stipulation to have a census every ten years. This was
  getting increasingly difficult with the growing population - it would take
  more than 13 years to complete. This led to punch cards designed by Herman
  Hollereth. From this IBM was born


title: Truth_tables
last_modified: 2024-10-18 18:55:56
body:
# Truth-tables

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

## Formulae for constructing truth-tables

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Truth-table concepts

### Recursion

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

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

### Truth-value assignment

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

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

### Partial assignment

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

## Working through complex truth-tables

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

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

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

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


title: Operating_system_API
last_modified: 2024-10-18 18:55:56
body:
# Operating system API

A user can interact with the operating system either via the GUI or the
[shell](Intro_to_the_Unix_shell.md). (Both ultimately access the OS via the
shell since the UI is an abstraction layer of the shell.)

When _programs_ interact with the operating system they use the **operating
system's API** directly. The GUI and shell also invoke the OS API but the access
is mediated.

Every OS has an API. This specification details how a program should interact
with the OS, exposing functions, properties and data types.

Whether using the GUI or a terminal emulator, the shell translates the user's
commands into API calls. The API the invokes internal operating system code to
perform the action.

![OS API diagram](../img/OS-api.svg)

## Example: opening a file

A user can use a file explorer and the mouse to open a file. Or they can open
the file by invoking it from the terminal.

A running application doesn't have to do this. If it's remit includes opening
files, it can do so by calling the OS API.

On a Unix-like system the applicaton would use the `open` API function to open
the file:

```
open("foo.txt", 0_WRONLY|0_CREAT)
```

This command makes the action write-only and creates the file because it does
not yet exist. When the user opens a file in their File Explorer, the
application translates their graphical actions into this API call.

## Different OSs have different APIs

Unix-like and Linux operating systems use variations on the **Portable Operating
System Interface** (POSIX) standard. This defines the OS API and also the rules
for the shell's behaviour and its included utilities.

Variations on this include _Cocoa_, macOS's API and Android's _Android Platform_
API.

Windows has its own API called _The Windows API_. The original version was
16-bit called _Win16_, then when it moved to 32-bits it became _Win32_. When it
moved to an x86 [instruction_set_architecture](Instruction_set_architectures.md)
it became _Win64_. Starting with Windows 10, it introduced the _Universal
Windows Platform_ which aims to make app development consistent accross
different devices that run Windows.


title: Sorting_lists_in_Python
last_modified: 2024-10-18 18:55:56
body:
# Sorting lists in Python

### Using `sorted()` Function:

The built-in `sorted()` function returns a new list containing all the elements
of the original list sorted in the specified order (either ascending, which is
default, or descending).

```python
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5]
sorted_list = sorted(my_list)
print(sorted_list)  # Output: [1, 1, 2, 3, 4, 5, 5, 6, 9]
```

For sorting in descending order, you can use the `reverse=True` argument:

```python
sorted_list_desc = sorted(my_list, reverse=True)
print(sorted_list_desc)  # Output: [9, 6, 5, 5, 4, 3, 2, 1, 1]
```

### Using `.sort()` Method:

The `.sort()` method sorts the elements of a list in-place and returns `None`.
This modifies the original list.

```python
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5]
my_list.sort()
print(my_list)  # Output: [1, 1, 2, 3, 4, 5, 5, 6, 9]
```

For sorting in descending order:

```python
my_list.sort(reverse=True)
print(my_list)  # Output: [9, 6, 5, 5, 4, 3, 2, 1, 1]
```

### Using a Custom Key Function:

Both `sorted()` and `.sort()` accept a `key` parameter that allows you to
specify a function to determine the sorting order. The function should take a
single argument and return a value that will be used for sorting purposes.

For example, if you have a list of strings and you want to sort them by their
length:

```python
my_str_list = ["apple", "banana", "cherry", "date"]
sorted_by_length = sorted(my_str_list, key=len)
print(sorted_by_length)  # Output: ['date', 'apple', 'cherry', 'banana']
```


title: Type_guarding_and_narrowing_in_TS
last_modified: 2024-10-18 18:55:56
body:
# Type narrowing and guarding

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

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


title: Register_and_cache_memory
last_modified: 2024-10-18 18:55:56
body:
# Register and cache memory

The CPU has its own memory devices in the form of registers and cache memory.

[Registers](./CPU_architecture.md#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](./DRAM_and_SRAM_memory.md).


title: Symlinks
last_modified: 2024-10-18 18:55:56
body:
> A symbolic link, also termed a soft link, is a special kind of file that
> points to another file. Unlike a hard link, a symbolic link does not contain
> the data in the target file. It simply points to another entry somewhere in
> the file system.

# Syntax

```
ln -s -f ~/[existing_file] ~/.[file_you_want_to_symbolise]
```

Real example:

```
ln -s -f ~/dotfiles/.vimrc ~/.vimrc
```


title: journald
last_modified: 2024-10-18 18:55:56
body:
# `journald`

`journald` is a program that comes as default with [systemd](systemd.md). It is
a service for collecting and storing system-level log data. I keeps a track of
all [kernel](The_kernel.md) processes. It is invaluable when tracing the source
of problems and errors that may arise on the system level. It keeps a track of
all kernal processes.

![](/img/journald.png)

## `journalctl`

We use `journalctl` to access the logs. The command by itself outputs the entire
log which will be huge and hard to scroll through. We can refine the results
with modifiers.

### View logs for a specific process with pid

```bash
journalctl _PID=1234
```

### View logs for a specific time period

This can be really helpful since you can bracket the most recent events which
will be more memorable.

```bash
journalctl -S -1h
```

### View logs for a specfic systemd unit

```bash
journalctl -u [unit_name] -e
```

### View boot logs

```bash
journalctl -b
```

#### Identify specific boot

```bash
journalctl --list-boots

```

### List only kernel entries to the journal

```bash
journalctl -k

```


title: Internet_Layer_of_Internet_Protocol
last_modified: 2024-10-18 18:55:56
body:
# Internet Layer of the Internet Protocol Suite

The Internet Layer is concerned with establishing the basis for the connection
and communication between hosts accross the internet.

In order for the data to get from a machine on one local network to a machine on
another local network, each machine must have a way of identifying and
interfacing with the other.

The Internet Layer utilises the **Internet Protocol** to determine how devices
will be publicly identified to devices on other networks and how different
devices on the same local network will be distinguished from outside of this
network. This is achieved through
[Internet Protocol (IP) addresses](./IP_addresses.md).

## Packets

Data is sent accross the internet between hosts as _packets_. These packets,
when sent and received within the
[Link Layer](Link_Layer_of_Internet_Protocol.md) on the local network, are
enclosed within a Link Layer frame (in its payload section):

![Internet Layer packet](../img/internet-layer-packet-revised.png)

Like a frame, an IP packet has a header and a payload. The payload comprises the
data between sent between hosts. The header contains a source IP address and a
destination IP address and also specifies which version of the Internet Protocol
is being used.

// Need to find out more about packets work.


title: Foreign_keys_in_SQL
last_modified: 2024-10-18 18:55:56
body:
# Creating views with foreign keys

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

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

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

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

In SQL this would be set up as follows:

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

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

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

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

Here's our `returns` table:

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

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

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

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

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

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

We establish the foreign key reference with ther following SQL:

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

A table can have more than one foreign key.

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


title: 82f9c228_dissolution_of_ARPANET
last_modified: 2024-10-28 15:16:38
body:
# 82f9c228_dissolution_of_ARPANET

By 1984, the US military became concerned about the expansion of the ARPANET and
its growing non-military applications (see the film _War Games_ released in
1983!). They split the network into the _ARPANET_ for academic research and the
_MILNET_ for non-civilian users where transmission was encrypted and protected.

Throughout the 1980s, the US National Science Foundation began promoting private
networks that used the ARPANET as their _backbone_. (Private in the sense of not
publicly-funded.)

Later, these became commercial and
[subscription-based](cfbef1c4_web_precursors.md). A plethora of ..._NETS_ grew
up (NSFNET, USENET, CSNET, TELENET etc.), all based on #packet-switching, the
ARPANET nodes as the backbone and [TCP/IP](Link_Layer_of_Internet_Protocol.md)
host communication.

These networks, although accessible to the public through subscriptions,
remained unwieldy and inconsistent to access until the Web.


title: Devices
last_modified: 2024-10-18 18:55:56
body:
# Devices

Devices are hardware that require access to the CPU in order to function.
Devices can either be external and plugged-in or internal to the motherboard.
The most common type of device that you will work with are [disks](Disks.md).

Devices are files but they have some different capabilities than ordinaryq
files. There are two types: **block** and **stream**. Device files reside in the
`/dev/` directory.

Some of the most important device files are:

- `hda` : a harddisk on a port
- `sda` : a harddisk on another port

> Device files are an interface to a driver which accesses the hardware. A
> driver is therefore part of the Linux kernel.

So when you see `sda` listed for example, this isn't the harddisk itself, it is
a file that communicates with a driver that controls how the kernel can interact
with it. This is why when you add a new piece of hardware (such as a mouse for
example) you have to install drivers (typically provided by the manufacturer) so
that your kernel is able to interact with it and provide it with access to the
CPU.

## Listing devices

The following `ls` within the `/dev/` directory shows my main harddrive
partitions:

```
brw-rw----   1 root   disk   259,     1 Jun  4 11:00 nvme0n1p1
brw-rw----   1 root   disk   259,     2 Jun  4 11:00 nvme0n1p2
brw-rw----   1 root   disk   259,     3 Jun  4 11:00 nvme0n1p3
```

> Since device files are files we can interact with them using standard file
> programs like `ls` and `cat`.

The
[mode](File_permissions_and_execution_in_Bash.md#what-the-output-means)
is different from ordinary files. Each device file is prepended with
`b, p, c, s` before the standard permissions. These stand for the major types of
devices: _block, character, pipe_ and _socket_.

<dl>
  <dt>block</dt>
  <dd>Block devices transfer data as large fixed-size blocks. These are the most common device type and include harddrives and filesystems. As the data can be split up into discrete blocks of data, this facilitates quick random access from the kernel. </dd>
  <dt>character</dt>
  <dd>Character devices transfer data one character at a time.The data is not in discrete chunks, it is a continuous stream of characters. An example of a stream device file is a printer however many character devices (such as `/dev/null`) are not physically connected to the machine.</dd>
  <dt>pipe<dt>
  <dd>Pipe devices allow two or more processes to communicate with each other. They work in the same way as character devices (transferring data as a stream) but instead of the output being sent to a device, it is sent to another process.</dd>
  <dt>socket<dt>
  <dd>The same as pipe devices, facilitating communication between processes however they can communicate with many processes at once, not just a single process.</dd>
</dl>

## /dev/null

`/dev/null` is a virtual device: it doesn't actually exist as a piece of
hardware on the system. It can be useful when
[bash scripting](Redirect_to_dev_null.md) as a
place to direct output that you don't care about, for example errors or verbose
program read-outs.

I like to think of `/dev/null` as being like earth in a circuit. It's an outlet
to safely dispose of things you are not interested in.

> ! Make notes on this. Base on : https://linuxhint.com/what_is_dev_null/


title: None_in_Python
last_modified: 2024-10-18 18:55:56
body:
# None in Python

`None` is not `null`, it is closer to `undefined` in JS. If you define a
variable as `None`, the variable exists, it is just not yet defined.

Using `None` is a pattern similar to using `let` in JS to name a variable and
definine it later on.

```python
temperature = None
```

If we logged `temperature` it would give us `None` rather than a null pointer
error.

With None we can use `is None` and `is not None`, special predicates for working
with `None` only. This is a akin to using `if (x !== undefined)` in TypeScript

```python
winner = None
print('winner:', winner)
# winner: None
print('winner is None:', winner is None)
# winner is None: True
print('winner is not None:', winner is not None)
# winner is not None: False
print(type(winner))
# <class 'NoneType'>
# Now set winner to be True
print('Set winner to True')
# Set winner to True
winner = True
print('winner:', winner)
# winner: True
print('winner is None:', winner is None)
# winner is None: False
print('winner is not None:', winner is not None)
# winner is not None: True
print(type(winner))
# <class 'bool'>
```


title: React_useState
last_modified: 2024-10-18 18:55:56
body:
# `useState`

`useState` is the primary way in which React updates the application state based
on user interaction.

- `useState` **receives**: the initial state as an argument
- `useState` **returns**: the current state and a function that updates it

The two items that are returned are returned as an array. We could refer to them
with array syntax (i.e with `[0]` and `[1]` ) but it is more concise to use the
destructuring syntax. But all that's happening is that a method belonging to the
`React` library (i.e. `useState` ) is taking an argument (the initial state) and
returning two values as an array of two elements. The first of these values is a
variable, the second is a function.

```jsx
(1) const [count, setCount] = useState(0);
(2) const handleClick = () => { setCount(count + 1) };

(3) <span>{count}</span>
(4) <button onClick={handleClick)>Add one</button>
```

- At line 1 we initialise the `useState` hook.
  - We use destructuring syntax to declare two variables
  - The first variable stores our initial state. The value of the initial state
    is an argument passed to `useState` , i.e. `0`. Do not confuse the variable
    with the value it refers to.
  - The second variable is a method that mutates the state referred to by the
    first variable into some new value, i.e. from `0` to a different integer
- At line 3, which in reality, will be within your JSX and part of the
  `render()` block of the function, we render the initial state in the React
  DOM. This will change and be re-rendered in accordance with the state logic
- At line 4 we have a user interface that executes the function on line 2. This
  function applies the state change represented by the second variable in the
  destructuring assignment at line 1 (`setCount` ): it mutates the initial state
  (`0`) by the value of `1'.

> You can utilise as many `useState` declarations as your component requires.

## Passing variables as initial state

We don't always have to declare the data types within the destructuring
assignment. We can store them in variables and pass the variables to the
destructuring assignment on `useState` . Below we do this for a function that
mutates a string value:

```jsx
const hi = "hello";
const bye = "goodbye";

const [initText, changeText] = useState(hi);

<p>{initText}</p>
<button onClick={()=>changeText(bye)}>ChangeText</button>
```

## Lazily setting initial state

Although you will typically use a literal value or variable as your initial
state, React provides a way for you to set this value through a function. This
will only run once at the first render so as not to interfere with the second
update parameter of `useState`. This is called **lazy initialisation**

```jsx
const getInitialState = () => Number(window.localStorage.getItem("count"));
const [count, setCount] = React.useState(getInitialState);
```

## Accessing previous state

It is also possible to access the previous state via the `useState` hook. We can
access this as a callback on the updater function.

Let's say we have a simple counter set up like so:

```js
const [count, setCount] = useState(0);
```

We can access the prior value of `count` with:

```js
const getPrevValue = () => setCount((prev) => console.log(prev));
```

### Naming best practise

When destructuring the two variables from `useState`, React follows uses the
following semantics: `[something, setSomething]` . This isn't required but it
helps to keep everything clear, particularly the fact that the first thing
destructured is a variable and the second is a function.

> https://stackoverflow.com/questions/54807454/what-is-prevstate-in-reactjs


title: Nodemon
last_modified: 2024-10-18 18:55:56
body:
# Nodemon

We don't want to have to restart the local server every time we make a change to
our files. We can use `nodemon` instead of `node` when running our `index.js`
file so that file-changes are immediately registered without the need for a
restart.

This is a wrapper around the `node` command so it doesn't require any
configuration. Once installed, update your start script from `node index.js` to
`nodemon index.js`.


title: Classes_in_Python
last_modified: 2024-11-03 14:59:11
body:
# Classes in Python

## General points

- We don't need to use `new` when instantiating an instance of a class.
- All properties and methods must be defined in the constructor. This is in
  contrast to JavaScript where the properties in the constructor are those we
  want to initialise when the object is created and where there may be other
  properties and methods that are used outside of the instantiation process.
- In contrast to TypeScript and other OOP languages it is not necessary to
  declare the properties you wish to define in the constructor, outside of the
  constructor. As long as they are defined in the constructor they are
  accessible.

## Class properties

In contrast to other languages, you do not need to declare any properties you
wish to use in the class at the top before you can assign to them. This is true
whether you define them in the constructor or not.

Instead, you can create properties just by invoking `self.some_property_name` in
the course of your code and Python will create the variable implicitly.

For continuity with other languages you may however define them at the top of
the class if you wish.

## Basic syntax

```py
class Person:
	""" An example class to hold a persons name and age"""

	def __init__(self, name, age):
		self.name = name
		self.age = age

	def birthday(self):
        print('Happy birthday you were', self.age)
        self.age += 1
        print('You are now', self.age)

p1 = Person('John', 36)
p2 = Person('Thomas', 34)
print(p1)
print(p2)

print(p1.name)

# John
```

Key points to note:

- The `__init__` method is the constructor function and must exist on every
  class to define the properties of the class
- `self` is a reference to the class itself and the object it will create, akin
  to `this` in other languages
- You must pass `self` as a parameter to every method (this is a difference from
  JS)
- As with functions, we can use docstrings to document the class. What you write
  here will show up in Intellisense etc

## More complex class

```py
class Person:
	""" An example class to hold a persons name and age"""

	def __init__(self, name, age):
		self.name = name
		self.age = age

	def __str__(self):
		return self.name + ' is ' + str(self.age)

	def birthday(self):
		print ('Happy birthday you were', self.age)
		self.age += 1
		print('You are now', self.age)

	def calculate_pay(self, hours_worked):
		rate_of_pay = 7.50
		if self.age >= 21:
			rate_of_pay += 2.50
		return hours_worked * rate_of_pay

	def is_teenager(self):
		return self.age < 20
```

## Object references

When you log a class you get a reference to its hexadecimal [memory](Memory.md)
reference.

```py
p1 = Person('John', 36)
p2 = Person('Thomas', 34)

print(p1)
print(p2)

# <__main__.Person object at 0x102e75510>
# <__main__.Person object at 0x102e75511>
```

This shows each object is unique. You can also generate a specific ID with the
`id()` method:

```py
print(id(p1))
print(id(p2))

# 4379088272
# 4379088656
```

## Copying objects

The same principle that applies to [copying functions](Functions_in_Python.md)
applies to copying objects created through classes: redeclaration results in a
duplicate entity. Thus changes to the duplicate will affect the original.

This becomes obvious when you use `id()` but otherwise might not be apparent. To
copy you should therefore declare a new instance of the class.

### The `str` method

The memory reference isn't very helpful for viewing the data contents. To get
around this we can use the builtin `__string__` method which automatically logs
whatever properties you put in there:

```py
class Person:
    """ An example class to hold a persons name and age"""

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return self.name + ' is ' + str(self.age)

p3 = Person('Thomas', 34)
print(p3)
# Thomas is 34
```

## Deleting objects

You might want to delete an object reference because:

- the variable referencing the object goes out of scope
- the variable is set to `None`

After the `del` statement is applied to a variable that holds an object, the
object will no longer be available and any attempt to reference it will result
in an error.

```py
p1 = Person('J-Man', 76)
del p1
```


title: Variables_and_data_types_in_Bash
last_modified: 2024-10-19 10:01:58
body:
# Variables and datatypes in Bash

## Types

## Variables

We use the equality symbol to create a variable:

```bash
stringVar="My first variable"
floatVar="50.3"
boolVar="true"
```

As there is no typing in bash, the names of these variables are purely notional.

To invoke a variable we use special brackets:

```bash
echo ${stringVar} # My first variable
echo ${floatVar} # 50.3
echo ${boolVar} # true
```

- Quotation marks at declaration are also not strictly necessary however they
  can help avoid bugs. Also serves as a reminder that every type is basically a
  string at the end of the day

## Common operations

### Check if a variable exists

```sh
if [ -v variable_name ]; then
    echo "The variable exists."
else
    echo "The variable does not exist."
fi
```

### Check if a variable is set (defined)

```sh
if [ -z "${variable_name+x}" ]; then
    echo "The variable does not exist."
else
    echo "The variable exists."
fi
```

## Parameter expansion

Still not very clear on this.


title: Database_options_on_AWS
last_modified: 2024-10-18 18:55:56
body:
# Database options

Amazon offers numerous database options however they are often named differently
or are Amazon's own implementation of a certain database type:

- **Relational databases**

  - Amazon offers two core RDSs: **Amazon Aurora** and **Amazon RDS**. Both
    allow you to use MySQL and PostgreSQL as the management implementation. The
    differences come down to which management implmentations are supported,
    number of backups and throughput. For instance Aurora has higher
    performance, more logging and fail-safes.

- **NoSQL**
  - Document-based databases
    - The obvious candidate for this kind of database is MongoDB. Amazon doesn't
      offer MongoDB it offers its own implementation which is almost identical
      and compatible with Mongo: **Amazon DocumentDB**. (There is some bitching
      about this from Mongo but they are competitors.)
  - Key-value database
    - A key-value database is basically a hash-table or a map
    - The Amazon implementation of this is **DynamoDB**
    - Confusingly, for NoSQL, a DynamoDB instance comprises "tables" which are
      groupings of keys and values. I guess "table" here is meant in terms of
      "hash table"
    - A cool feature is that you can subscribe to a DynamoDB table and run a
      lambda when a value changes.


title: MongoDB_Introduction
last_modified: 2024-10-18 18:55:56
body:
# 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)


title: Conjunction_Introduction
last_modified: 2024-10-18 19:11:52
body:
# Conjunction Introduction

If two conjuncts have each been independently derived then they can be
conjoined. Also known more simply as _Conjunction_

![](/img/conjunc-intro.png)


title: Maps_and_sets_in_JS
last_modified: 2024-10-18 18:55:56
body:
# 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.


title: Validating_Mongoose_schemas
last_modified: 2024-10-18 18:55:56
body:
# Validating Mongoose schemas

Let's say we have the following schema:

```js
const courseSchema = new mongoose.Schema({
  name: { type: String, required: true, minlength: 5, maxlength: 255 },
  author: String,
  tags: [String],
  data: { type: Date, default: Date.now }, // if unspecified, entry will default to current date
  isPublished: Boolean,
});
```

We set our validation criteria as the second property for each schema value. If
it does not obtain, an exception will be thrown when we attempt to modify the
database. This will most likely take the form of a Promise rejection.

Note that this validation only occurs at the level of Mongoose. There is no
mechanism for running the validation within the database itself. This is one of
the ways that Mongo differs from an SQL database where you would be able to set
the property of `required` for a cell in the table. If we didn't set any
validation via Mongoose, Mongo would accept whatever we sent to it.

What is the relationship between this Mongoose validation and the
[Joi](Validation_in_NodeJS.md) validation that we
use when validating API requests in Node/Express? They complement each other. We
use Joi to validate the client request to the API. If this is valid, the process
would then move onto the next stage which would be transforming the data from a
REST request to a database request implemented through Mongoose, where a second
layer of validation would appy. We would also have validation on the
client-side.

## Validator functions

Instead of simply applying a built-in validator like `required`, we can instead
pass a function that validates agains the validation status of another property
in the schema. For instance: make the `price` property required only if the
entry has `true` for `isPublished`:

```js
const courseSchema = new mongoose.Schema({
  ...
  isPublished: Boolean,
  price: {
    type: Number,
    required: function() {return this.isPublished}
  }
});
```

## Enum validators

An enum validator enables us to ensure that whatever value is passed when
instantiating a document that is of a given schema type, it must be one of a
finite set of values:

```js
const anotherSchema = new mongoose.Schema({
  sex: {
    type: String,
    enum: ["male", "female"],
  },
});
```


title: Memory_leaks
last_modified: 2024-10-18 18:55:56
body:
# Memory leaks

A memory leak occurs when you program memory and forget to delete it once you
are finished. The consequences of memory leak is that it reduces the performance
of the computer by reducing the amount of available memory. Eventually, in the
worst case, too much of the available memory may become allocated and all or
part of the system or device stops working correctly, the application fails, or
the system slows down vastly.


title: Proper_shebang_syntax
last_modified: 2024-10-18 18:55:56
body:
# Proper shebang syntax

This is a more portable way of writing the shebang because the location of Bash
on different systems can vary.

```bash
#!/usr/bin/env Bash
```

Rather than:

```bash
#!/bin/bash
```


title: Restructure_URLs_lambda
last_modified: 2024-10-18 18:55:56
body:
# AWS Lambda examples

## Reconstruct CloudFront URLs

```js
exports.handler = (event, context, callback) => {
  // Extract the request from the CloudFront event that is sent to Lambda@Edge
  var request = event.Records[0].cf.request;

  // Extract the URI from the request
  var olduri = request.uri;

  // Match any '/' that occurs at the end of a URI. Replace it with a default index
  var newuri = olduri.replace(/\/$/, "/index.html");

  // Replace the received URI with the URI that includes the index page
  request.uri = newuri;

  // Return to CloudFront
  return callback(null, request);
};
```

Let's walk through the above example making reference to the general structure
of the
[AWS Lambda handler function](Lambda_handler_function.md):

1. The `event` parameter is invoked to gain access to the event that triggered
   the function. This is a CloudFront request event which is targetted with
   `event.Records[0].cf.request`
2. We classify this request as the `olduri` since it doesn't have the form we
   want.
3. We mutate the original request by replacing `/` with `/index.html`
4. We then re-assign the `request.uri` value to the value of the new URI.
5. Finally we invoke the `callback` parameter to signal the completion of the
   transformation and return the new request as the return value.


title: c301a0b3-1d8_Mosaic_Netscape_and_Browser_Wars
last_modified: 2024-11-04 16:40:18
body:
# c301a0b3_Mosaic_Netscape_and_Browser_Wars

The Mosaic browser was conceived and produced at the University of Illinois
during the very early growth of the Web, when TBL was still at CERN.

_Mosaic_:

![Mosaic browser](../img/mosaic-browser.jpg)

It became the best of the early browsers (a group that included ViolaWWW and
Erwise) with the notable advantage of being able to embed graphics alongside
text (instead of inside a separate window). It also supported multiple
[application layer](Application_Layer_of_Internet_Protocol.md) protocols - FTP,
the Network News Transfer Protocol, Gopher - not just HTTP.

At this point there were nascent browsers for different operating systems and
which browser you used was mostly determined by your OS. Being the product of a
US university, Mosaic was originally written for Unix's X-Window system but was
ported to multiple OSs later, a key factor in its success and uptake.

Mosaic's creators formed their own company outside of the university: Netscape.
The browser they created, Netscape Navigator became the heir to Mosaic, released
in 1994.

_Netscape Navigator_:

![Netscape Navigator](../img/Navigator_Netscape.png)

It was made free of charge for non-commercial use which assured rapid uptake.
This was done to outrun Microsoft's Internet Explorer (released two months after
Netscape went public) which they intended to bundle with all new versions of
Windows 95. Netscape became the de facto standard browser during the early Web,
including on Windows. It took a few years for IE to overtake it.

_Internet Explorer 1 with licensing info_:

![Internet Explorer](../img/internet_explorer.png)

Netscape also birthed JavaScript, which was written rapidly specifically for the
browser. Prior to this, dynamic client-side functionality was provided by Java
'applets' which never really took off.

IE in fact shares a genealogy with Mosaic since Microsoft licensed a version of
the Mosaic code and used it to construct IE.

By the end of the 1990s, Netscape's usage had declined precipitously as IE
captured the market share.


title: Integrated_circuits
last_modified: 2024-10-18 18:55:56
body:
# Integrated circuits

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

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

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

_An integrated circuit and its use on a breadboard:_

![](../img/integrated-circuit.jpeg)

![](../img/breadboard-DIP.jpg)


title: Error_handling_in_Python
last_modified: 2024-10-25 09:32:22
body:
# Error handling and exceptions in Python

Errors can typically occur in the following scenarios:

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

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

## Difference between errors and exceptions.

Python distinguises between errors and exceptions.

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

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

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

## The Exception hierarchy

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

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

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

## Exception syntax

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

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

Exaple of `raise`:

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

Example of `except`:

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

```

### Scaffolding exception handling

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

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

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

## Custom exceptions

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

```py
class CustomError(Exception):
    pass

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


title: Map_and_filter_in_Python
last_modified: 2024-10-18 18:55:56
body:
# 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]
```


title: Streams_in_NodeJS
last_modified: 2024-10-18 18:55:56
body:
# Handling streams with fs

When reading from a file, the
[`fs.readFile()`](fs.md) method waits
until the entire file has been read before executing the callback. It's obvious
why this might not be ideal in certain cases. If the file is very large you are
utilising a lot of [memory](Memory.md) for a
single process. Additionally, the data you need might appear early in the file,
in which case, once you find the data you want, there is no need to read to the
end of the file.

> An example of this in practice is watching a Netflix film: we don't have to
> wait for the whole film to download, we can start watching it immediately
> because it is passed to us in chunks.

## Reading streams

This is why the ability to read files and data as streams exists in Node.js.
Streams promote memory and time efficiency. As you are not necessarily reading
the whole file, you can extract the data you need quickly and you are not
putting the whole file in memory.

When read as a stream, the file is broken up into smaller chunks. Readable
streams raise data events and pass the chunks to our callbacks. Hence we don't
have to wait for the whole file before registering data chunks.

## Implementation

Instead of, e.g :

```js
fs.readFile("./lorem.md", "UTF-8", function (err, fileContents) {
  console.log(fileContents);
});
```

We would:

```js
let stream = fs.createReadStream("./lorem.md", "UTF-8");

let data;

stream.once("data", (chunk) => {
  console.log("read stream started");
});

stream.on("data", (chunk) => {
  data += chunk;
  console.log(data);
});

stream.on("end", () => console.log("stream ends"));
```

We use `once` to do something at the moment the stream starts to be read.

We use `on` to target the `"data"` and `"end"` events. We use this to log the
chunks via the `data` variable.

## Writing streams

When we write as a stream, we break data up and pass this to a reader as a
stream. It is what creates a stream in the first place.

For example we could create a stream that handles `stdin` and writes this to a
file in chunks:

```js
var stream = fs.createWriteStream("lorem.txt")

process.stdin.once("data", (data) => {
    stream.write(data, function() => {
        // data is now written.
    })
})

process.on("exit", function () {
    stream.close()
})
```

Note that we always have to close the stream on exit


title: Transport_Layer_of_Internet_Protocol
last_modified: 2024-10-18 18:55:56
body:
# Transport Layer of the Internet Protocol

Transport Layer protocols provide a communications channel for data to be sent
between hosts. The Transport Layer is dependent upon the
[Internet Layer](Internet_Layer_of_Internet_Protocol.md) for addressing and
routing the data and the
[Link_Layer_of_Internet_Protocol](Link_Layer_of_Internet_Protocol.md) for
transferring the data to a specific host once it has reached the local network.

The protocol governing the Transport Layer is the **Transmission Control
Protocol** (TCP).

A TCP **fragment** fits within an IP packet's data section, giving us the
following iteration of the network stack:

![Transport layer fragment](../img/transport-layer-segment.png)

The segment header contains a destination network port number. The port number
identifies the specific service or process on the host device which will receive
the data.

A useful analogy:

_The IP address is like the street address of an office building. The port
number is like the office number of a specific worker within the building. Once
a letter arrives at the building, the mail processing office (like the OS) is
not the intended recipient. Instead the mail worker passes on the letter to the
intended recipient. Likewise, the OS examines the port number and delivers the
inbound data to the process listening on the specified port._

## Ports

A host with a single IP address can have multiple active ports, each used for
performing a different type of activity on the network.

Both servers and clients possess ports. A server will have a dedicated port
which it listens for requests on (typically port 80 or port 443). A client will
have an "ephemeral" port where it listens for responses from a server. They are
ephemeral because they are open only temporarily to receive a response to a
certain request and are assigned dynamically by the networking components of the
OS.

An IP address plus a port (e.g: 192.168.2.1:3000) constitutes an **endpoint**.
An instance of an endpoint is a **socket**.

If multiple clients connect to the same endpoint each has its own socket (for
instance on a host that is a server). A socket can listen for new connections or
it can represent an established, ongoing connection.


title: Turing_machines
last_modified: 2024-10-19 10:00:57
body:
# Turing Machines

## What is a Turing Machine?

Turing Machine is a machine that contains mutable state, executes sequences of
simple instructions that read and write that state, and can pick different
execution paths depending on the state (via conditional branch instructions.)

This is a general expression, it is typically expanded as follows.

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

![Turing_machines_01.gif](Turing_machines_01.gif)

The direction that the head moves, which values it erases, and which values it
writes in, are dependent on a set of instructions provided to the machine.

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

For example:

### State 2

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

### State 5

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

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


title: Prime_factorization
last_modified: 2024-10-18 19:19:08
body:
# Prime factorisation

Prime factorisation is the activity of expressing a composite number as the
unique product of [prime numbers](Primes%20and%20composites.md). There are two
main approaches to this:

- - factor\* trees
- repeated division by two

> **Factor trees:** we take a number $n$ and break it down into two factors of
> $n$. We then repeat this process with the resulting factors working
> recursively until the numbers we are left with are primes.

![Untitled Diagram-Page-1.drawio.png](Untitled%20Diagram-Page-1.drawio.png) _The
prime factors of 27 are 2, 3, 3_

it doesn't matter which products we choose as the interim factors, we should
always reach the same outcome:
![Untitled Diagram-Page-3.drawio 1.png](Untitled%20Diagram-Page-3.drawio%201.png)

![Untitled Diagram-Page-2.drawio.png](Untitled%20Diagram-Page-2.drawio.png)


title: DynamoDB_data_structure
last_modified: 2024-10-18 18:55:56
body:
# DynamoDB

## Data structure

### Non-relational tables

DynamoDB is "NoSQL" because it does not support #SQL queries and is
non-relational meaning there cannot be JOIN operations via
[foreign_keys](Foreign_keys_in_SQL.md)

![](../img/dynamodb.png)

### Primary key

Although the data is stored as a table, one of the attributes is a primary key
and the rest of the attributes are effectively the "value" associated with it.

Because DynamoDB is schemaless, other than the primary key, neither the
attributes or their data types need to be defined beforehand and each item can
have its own distinct attributes.

Each item in the table is uniquely identifiable by its primary key.

There are two types of primary key available:

- partition key: a simple primary key composed of one attribute only. Because
  the primary key is hash-mapped items can be retrieved very rapidly using the
  primary key. This would be the `personId` alone.

- composite key: this comprises a partition key and a _sort key_ both of which
  are attributes. In a table that has a partition key and a sort key, it's
  possible for multiple items to have the same partition key value. However,
  those items must have different sort key values. You could then query by
  either key or both. For instance using the `PersonId` along with `LastName`

### Secondary index

As well as the index provided by the primary key, you can set one or more
**secondary indices**. A secondary index lets you query the data in the table
using an alternate key.

A **global secondary index** is useful for querying data that needs to be
accessed using non-primary key attributes. For example, if you have a Users
table with `UserID` as the primary key but often need to fetch users by their
`Email`, a GSI on `Email` would be appropriate.

There are also **local secondary indices** but I don't understand the
difference.

## Real example

Below is a specification of the DynamoDB table I am using for my time-entries
project:

```json
{
  "TableName": "TimeEntries",
  "KeyAttributes": {
    "PartitionKey": {
      "AttributeName": "activity_start_end",
      "AttributeType": "S"
    }
  },
  "NonKeyAttributes": [
    {
      "AttributeName": "activity_type",
      "AttributeType": "S"
    },
    {
      "AttributeName": "start",
      "AttributeType": "S"
    },
    {
      "AttributeName": "end",
      "AttributeType": "S"
    },
    {
      "AttributeName": "duration",
      "AttributeType": "N"
    },
    {
      "AttributeName": "description",
      "AttributeType": "S"
    },
    {
      "AttributeName": "year",
      "AttributeType": "S"
    }
  ],
  "GlobalSecondaryIndexes": [
    {
      "IndexName": "YearIndex",
      "KeyAttributes": {
        "PartitionKey": {
          "AttributeName": "year",
          "AttributeType": "S"
        },
        "SortKey": {
          "AttributeName": "start",
          "AttributeType": "S"
        }
      },
      "Projection": {
        "ProjectionType": "ALL"
      }
    }
  ]
}
```

This defines the attribute `activity_start_end` as the primary key. This string
(`S`) value is a concatenation of three attributes, which is a way of ensuring
each entry for the attribute will be unique.

The `NonKeyAttributes` are all the other attributes in addition to the primary
key. As mentioned these do not actually need to be defined when setting up the
table but they are listed here for clarity.

I have also defined a GSI. This is derived from the `Year` attribute. This will
group all the items by their `Year`, allowing me to query directly by year but
also helping to chunk the entries which will make look-ups quicker and less
expensive.

## Related notes


title: CPU_architecture
last_modified: 2024-10-18 18:55:56
body:
# CPU architecture

At the core of a computer sits the Central Processing Unit. This is the assembly
of chips that execute all computation. Instructions are passed to the CPU along
the data bus part of the [system bus](Bus.md) from the
memory. The [kernel](The_kernel.md), also residing in memory,
sequences and schedules the sending of data to the CPU and manages requests from
the CPU for data in memory.

The CPU comprises three core components:

- Registers (a type of memory positioned on the same chip as the CPU )
- the Arithmetic Logic Unit (ALU)
- the Control Unit (CU)

![](/img/von_neumann_architecture.jpeg)

> This method of putting together a computer is known as the **Von Neumann
> Architecture**. It was devised by John von Neumann in about 1945, well before
> any of the components that would be needed to produce it had actually been
> invented.

## Registers

This is the part of the CPU that stores data. The memory cells that comprise it
do not have [capacitors](Memory.md) (unlike RAM)
so they cannot store very much data but they work faster, which is what is
important. Because their memory capacity is so small, we measure the size of
registers in bits rather than bytes.

In terms of speed, registers sit at the top part of the overall
[memory hierarchy](Memory.md#the-memory-hierarchy).

There are five main types of register in the CPU:

| Register type           | What it stores                                              |
| ----------------------- | ----------------------------------------------------------- |
| Accumulator             | The results of calculations                                 |
| Instruction Register    | The DRAM address of the **instruction** to be processed     |
| Memory Address Register | The DRAM address of the **data** to be processed            |
| Memory Data Register    | The store of the data that is currently being processed     |
| Program Counter         | The RAM address of the **next instruction** to be processed |

## Arithmetic Logic Unit

See [Arithmetic Logic Unit](Arithmetic_Logic_Unit.md)

## Control Unit

The CPU's [controller](Chipset_and_controllers.md). It
takes the instructions in binary form from RAM memory (separate from the CPU,
but connected) and then signals to the to ALU and memory registers what it is
supposed to do to execute the instructions. Think of it as the overseer that
gets the ALU and registers to work together to run program instructions.

## The system clock

Whilst modern CPUs and multithreading make it appear as though the CPU is
capable of running multiple processes at once, access to the CPU is in fact
sequential. The illusion of simultaneous computation is due to the fact the
processor is so fast that we do not detect the sequential changes. For this to
happen, the CPU needs to have a means of scheduling and sequencing processes.
This is made possible through the system clock, hence when talking about the
speed of the CPU we do so with reference to _clock speeds_ and the _clock
cycle_.

The clock's circuitry is based on a quartz crystal system like that used in
watches. At precisely timed intervals, the clock sends out pulses of electricity
that cause bits to move from place to place within
[logic gates](Logic_gates.md) or
between logic gates and
[registers](CPU_architecture.md#registers). This is
covered in greater detail in the discussion of
[clock signals in digital circuits](Clock_signals.md).

Simple instructions such as add can often be executed in just one clock cycle,
whilst complex operations such as divide will require a number of smaller steps,
each using one cycle.

We measure the speed of a chip process within the CPU in **Hertz (Hz)**. One
Hertz is equivalent to _1 cycle per second_ where a "cycle" is equivalent to a
single clock **tick**. Thus a tick covers a period of 1 second.

A speed of 2GHz for example means two billion cycles per second. This would mean
that the clock was completing two billion cycles at each tick. It is clock speed
that is being referred to when computers are marketed in terms of the number of
Hz a processor possesses.

## Processing cycles

Each "cycle" is the execution of a process that commences once the
[kernel](The_kernel.md) hands control to the CPU. Each cycle
follows a sequence of events known as
[fetch, decode, and execute](Fetch_decode_execute.md).

## Electromagnetism: broader scientific context

Hertz was the scientist who detected
[electromagentic waves](Electromagnetism.md).
We use Hertz as a measure of the frequency of electromatic wave cycles in a
signal.

![](/img/hertz_wave_freq.gif)

As the diagram above shows, a cycle is equal to one ascending and one descending
crest. The more cycles per time unit, the greater the Hertz. We see the Hz
increase as the number of cycles increases over time.

## Processor cores

// TODO: add to dedicated file

// Add diagram The vast majority of general purpose computers are multi-core.
This means that the CPU contains more than one processing unit. They are best
thought of as mini-CPUs within the main CPU since they each have the same
overall Von Neumann architecture.

With Intel processors the two main consumer processors are the i5 and i7. The
latter has more cores than the former. Consequently it has faster clock speeds
and greater concurrency due to increased threads.

### Cache access

// See pp.129

### History

The development of multi-core processors emerged because practical limits began
to be reached when trying to increase the clock speeds of processors. For a
period, increased transistor density meant that there were steady increases in
clock speeds. This meant that more instructions could be executed per second.

When the 3GHz level was reached it became clear that pushing a CPU beyond this
led to excessive heat production and the logic gates not being able to keep up
at those speeds. Instead of maxing out the clock rate, attention turned to
running multiple instructions in parallel in order to increase performance.


title: 4a3dc316_key_figures_ARPANET
last_modified: 2024-10-28 15:39:04
body:
# 4a3dc316_key_figures_ARPANET

- [Paul Baran](385af4b4_Baran_distributed_networks.md) for his work at RAND on
  distributed networks as an alternative to the extant
  centralised/de-centralised phone networks.

- Donald Davies at the UK Physical Laboratory for developing ideas on
  #packet-switching, similar to but derived independently of Baran's work.

- Bob Kahn as the principle designer of the ARPANET, who with Vint Cerf, wrote
  the TCP/IP protocols. Became director of the Information Processing Techniques
  Office within ARPA/DARPA.

- Vint Cerf was head of the International Networking Group and worked on the
  Network Control Protocol (the first protocol for packet switching using the
  IMPs). Later joined up with Kahn on TCP/IP and the network of packet-switching
  networks (early internet).


title: Why_computers_use_binary
last_modified: 2024-10-18 18:55:56
body:
# Why computers use binary

## Now we know how binary works, how does it relate to computing?

The reason is straight forward: it is the simplest way on the level of pure
engineering of representing numerical and logical values; both of which are the
basic foundations of programming. An electronic circuit or transistor only needs
to represent two states: on (1) and off (0) which corresponds to the switch on
an electrical circuit. A single circuit representing the binary values of 1 and
0:

![multi_on_off 1.gif](multi_on_off.gif)

It would be much more complicated to have to represent ten different states
under the decimal number system, although denary computers do exist.

If we want more digits, we just need to add in more circuits, and we can
represent as large a binary number as we need. We just need one switch for every
digit we want to represent. The switches used in modern computers are so cheap
and so small that billions can be fitted on a single circuit board.

![multiple_circuits.gif](multiple_circuits.gif)

When we use the term 'switch' we actually mean the transistor components of a
circuit. We don't need to know the physical details at this level but we can say
that a transistor turns a current on and off. They can also be used to amplify
the current.

On the level of electrical engineering and in the subsequent examples using a
light bulb and a simple circuit, the switch being OFF corresponds to the light
being ON, this is because the switch breaks the current. So when it is unbroken,
the current passes to the bulb. This also means that 1 corresponds to the switch
being OFF and 0 corresponds to the switch being ON which can be confusing at
first.

## From circuits to programs

The following breaks down how we get from the binary number system to electrical
circuits to computer programs:

1. ”Data”= a piece or pieces of **information**

1. In computing all data is represented in **binary form:**

   2.1. ”Binary” means that the value of a piece of data is exclusively one
   thing or another

   2.2. The values in binary code are exclusively 1 and 0 (either a piece of
   data is a 1 or it is a 0, it can never be both). Binary can also be expressed
   in logical terms where 1 is equal to `true` and 0 is equal to `false`

1. The smallest piece of data is a **bit**

1. We apply the binary number system to bits

1. Thus by 1-4, **a bit is either 1 or 0**

1. Binary form bears an isomorphic relation to switches on electrical circuits
   (the hardware of computers). Thus binary code can be _mapped onto circuits_.

   - Circuits are controlled by switches
   - A switch is a binary function: it is either on or off ”On/off”
     corresponding to 1/0.
   - In this way, switches control the flow of electric current

1. Thus by the above, hardware (which is a physical structure made up of
   electric circuits) is **readily programmable in binary terms**

1. Restatement of 6: in order for hardware to be programmed the program must
   provide the hardware with something that it can actually perform. The program
   must be formulated in binary code.


title: Printing_values_in_C
last_modified: 2024-10-18 18:55:56
body:
# Printing values in C

## To print a literal

```c
printf("hello world")
```

## To print a variable

```c
int age = 25
print("%d", age)

```

The `%d` is an example of a [format specifier](./zk/format-specifiers-in-c.md).
It is the decimal integer format specifier.


title: Filesystems
last_modified: 2024-10-18 18:55:56
body:
# Filesystems

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

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

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

## Creating a filesystem

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

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

## Mounting a filesystem

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

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

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

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

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

## fstab

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

This is my current `fstab`:

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

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

/swapfile none swap sw 0 0

```

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

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


title: Observer_pattern
last_modified: 2024-10-18 18:55:56
body:
# Observer pattern

The Observer design pattern is also known as the 'publish/subscribe' pattern.

> The observer pattern is a software design pattern in which an object, called
> the subject, maintains a list of its dependents, called observers, and
> notifies them automatically of any state changes, usually by calling one of
> their methods.

The observer pattern instantiates a **one-to-many** relationship between the
subject and its observers: when one object updates, it notifies many other
objects that it has been updated.

This is another design pattern that is useful when implementing a global state.
When the subject updates its subjects must follow suit and adapt to its changes.
Here the subject is the instrument for maintaining the global state.

## Subject class

This class maintains a list of observers that will be notified when it is
updated. It should have the following attributes:

- array of existing observers
- an `addObserver` method which pushes an observer onto the observers array
- a `removeObserver` method that does the opposite
- a `notify` method that broadcasts to the observers that a change has happened

## Observer class

Observers must implement an `update` method that will be called by the subject's
`notify` method.

## Example

```js
class Subject {
  observers = [];

  getCurrentTime() {
    var d = new Date();
    return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;
  }

  addObserver(...observer) {
    for (const o of observer) {
      this.observers.push(o);
    }
  }
  removeObserver(observer) {
    const match = this.observers.findIndex((element) => element === observer);
    this.observers.splice(match, 1);
  }
  notify() {
    for (const o of this.observers) {
      o.update(`I was updated at ${this.getCurrentTime()}`);
    }
  }
}

class ObserverA {
  state = "I am observer A";
  update(newState) {
    this.state = newState;
  }
}

class ObserverB {
  state = "I am observer B";

  update(newState) {
    this.state = newState;
  }
}

let subjectInstance = new Subject();
let obsAInstance = new ObserverA();
let obsBInstance = new ObserverB();

subjectInstance.addObserver(obsAInstance, obsBInstance);

subjectInstance.notify();
console.log(obsBInstance.state);
```


title: Basics_of_web_components
last_modified: 2024-10-18 18:55:56
body:
# Basics of Web Components

Web Components are custom HTML elements like those we create with frameworks
like React and Angular but which are native to HTML and the DOM and therefore do
not require any special compilation or libraries.

- Designed to be interoperable with normal HTML
- Designed to be interoperable with component frameworks like React
- Designed to be highly modular and reusable
- Utilise the shadow DOM which is like the virtual DOM but native to the browser
  and invocable through plain JavaScript. A bit like an iFrame within the
  "light" DOM and helps greatly with style-scoping.

## Example of composing a web component:

```js
// MyComponent.js
class MyComponent extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<h1>Hello world</h1>`;
  }
}

customElements.define("my-component", MyComponent);
``;
```

```html
<!-- index.html -->
<my-component></my-component>
```

## Resources

[A complete introdution to Web Components]()


title: Relays
last_modified: 2024-10-18 18:55:56
body:
# Relays

A relay is an electrically operated switch.

It uses an electromagnet to mechancially operate a switch. A switch in one
circuit can be used the trigger the operation of another circuit.

## Core components

- electromagnet
  - a coil of wire around an iron core
- armature
  - a movable magnetic strip
- contacts
  - electrical connections that can be open or closed
- spring
  - returns the armature to its resting position when not energised

## Operation

![Operation of a relay](../img/relay.gif)

Current flows through the electromagnet which creates a magnetic field. This
field attracts the armature which then moves to open or close the contacts. When
the current stops, this returns the armature to its original position.

## Usage

During the
[electro-mechanical era of computing](The_History_of_Computing_Swade.md) they
were used as precursors to vacuum tubes (and later, transistors) to implement
logic gates. While they were superseded in this domain, they are still used in
industrial control systems and automobiles.


title: Processes
last_modified: 2024-10-18 18:55:56
body:
# Processes

Programs are sequences of machine instructions stored in a file. However they do
not work by themselves. Something needs to load the file's intructions into
memory, direct the CPU to run the program and manage it during runtime. The OS
does this via processes.

A process **is a running instance of a given program**. It can be thought of as
a container in which a program runs. This container includes:

- a copy of the program code loaded into memory
- a memory address
- other information about the state of the process

Other than the `init` process started by the kernel (PID1) (see
![systemd](systemd.md)), every process has a parent process that started it.
This parent-child relationship creates a tree of processes.

It is possible that a parent process will terminate before one of its child
processes. In this instance the child becomes an orphan. When this occurs in
#Linux, the orphan process is adopted by `init`.

Below, I have used the `pstree` utility to list all the running processes on my
machine hierarchically.

![diagram of `pstree` output](../img/ps-tree.png)

Children are represented vertically and horizontally.

```
├─terminator─┬─zsh───tmux: client
│            ├─zsh───pstree
│            └─6*[{terminator}]

```

For instance here, `terminator` is a child of `init`, as are `zsh` and `tmux`
but they are also children of `terminator`.`pstree` is a child of `zsh` and
therefore also a child (grandchild) of `terminator`.

Each process has a unique identifier called a _process identifier_, a
_processID_ or just _PID_.

## Related notes

![systemd](./systemd.md)

![ps](./ps.md)

![Monitoring processes and resources](Monitoring_processes_and_resources.md)


title: Equivalent_fractions
last_modified: 2024-10-18 19:13:26
body:
# Equivalent fractions

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

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

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

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

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

Therefore the following rule obtains:

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

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

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

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

Thus:

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

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


title: Importing_MongoDB_data
last_modified: 2024-10-18 18:55:56
body:
# 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.


title: Transistors
last_modified: 2024-10-18 18:55:56
body:
# Transistors

A digital circuit requires that electrical phenomena be treated as discrete
rather than continuous values.

Although a given voltage at a point in the circuit can vary widely, in order to
represent the binary states of 'on' and 'off' we need it to remain fixed within
certain narrow parameters. This is achieved with transistors.

Transistors are an electrical component that is capable of controlling the flow
of current in the manner of a switch where the 'off' and 'on' states are
represented by [voltage](Voltage.md) values within set parameters.

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

![BJT transistor terminals](../img/bjt-terminals.jpg)

The pins:

- C: collector
- B: base
- E: emitter

Applying a small amount of current at the base allows a larger current to flow
from the collector to the emitter. Applying current to the base is like turning
the switch on. Removing this current is like turning the switch off.

This happens because of how current and voltage interact in a transistor. The
small base current controls the larger collector-emitter current through a
process called current amplification.

When a small voltage is applied to the base, it allows a small current to flow
from the base to the emitter. This base current triggers a much larger current
to flow from the collector to the emitter. Think of it like a water valve: a
small turn of the handle (base current) can control a large flow of water
(collector-emitter current). The transistor amplifies this effect, allowing a
tiny input signal to control a much larger output.

## Transistors and logic gates

We can combine transistors to create logic gates. A logic gate is a combination
of transistors arranged such that the logical function is embodied by the
characteristic input and output voltages to the transistor.

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

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

// Add example of OR gate created with transistors


title: Hexadecimal_number_system
last_modified: 2024-10-18 18:55:56
body:
# 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.**


title: Swap_space
last_modified: 2024-10-18 18:55:56
body:
# Swap space

A swap partition is a partition on a disk that is not intended to be used as a
filesystem. Instead, it is a part of the disk that is used to augment the main
memory.

If you run out of memory and have set up a swap partition, the OS will be able
to move pieces of memory to and from disk storage. This is called _swapping_
because pieces of idle programs are swapped to the disk in exchange for active
pieces residing on the disk.

## View current swap usage

If you have a swap space established, the command `free` will show current
usage:

```bash
free
               total        used        free      shared  buff/cache   available
Mem:        16099420     3031572    10157652     1153144     2910196    11605820
Swap:        3145724           0     3145724
```

## Create a swap partition

To use an existing disk partition as a swap you can run the command
`mkswap [device]` and then `swapon [device]` to register the space with the
[kernel](The_kernel.md).

### Add to `fstab`

You will want the swap to be activated every time the OS boots so add the
following line to the [fstab](Filesystems.md#fstab),
where `/sda3` is used as the example partition:

```bash
/dev/sda3e none swap sw 0 0
```

## Create a swap file

// Add info here


title: Formal_proofs_in_propositional_logic
last_modified: 2024-10-18 18:55:56
body:
# Formal proofs in propositional logic

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

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

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

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

For instance to demonstrate derivability for:

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

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

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

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

## Constructing proofs

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

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

_Schematically_:

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

_Applied example_:

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

## Sub-proofs

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

## Derivation rules

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

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

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

The main derivation rules:

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


title: Practical_walkthrough_Lambda_creation_within_AWS
last_modified: 2024-10-18 18:55:56
body:
# Practical walkthrough of creating a Lambda function via the AWS Management Console

## Basic set-up

First we name the function and accept the defaults:

![](/img/lambda_func_one.png)

This presents us with the function dashboard - a graphical representation of the
Lambda showing [triggers]() as an input and destination as an output:

![](/img/lambda-func-two.png)

Beneath this we have a code editor with the handler function with a basic
boilerplate:

![](/img/lambda-func-three.png)

## Adding a trigger

Next we need to add a [trigger](Lambda_triggers.md) that
execute the handler.

We will do this using [AWS API Gateway](AWS_API_Gateway.md). We
select "Add trigger" from the dashboard view and input basic settings:

![](/img/api-gateway-trigger.png)

Now we see this step displayed in the dashboard:

![](/img/lambda-overview.png)

With the endpoint and other settings displayed:

![](/img/trigger-info.png)

If we go to the endpoint URL
(`https://4kjqwbvd7g.execute-api.us-east-1.amazonaws.com/default/myFirstFunction`),
we will see the output: `Hello from Lambda`.

## Handling parameters

We can make the example more realistic by expanding the handler to accept query
parameters. We do this by accessing the value `queryStringParameters` on the
`event` object:

```js
exports.handler = async (event) => {
  const name = event.queryStringParameters && event.queryStringParameters.name;

  let message = "Hello Lambda";

  if (name !== null) {
    message = `Hello ${name}`;
  }

  const response {
    statusCode: 200,
    body: JSON.stringify(message)

  }
};
```

If we now access
`https://4kjqwbvd7g.execute-api.us-east-1.amazonaws.com/default/myFirstFunction?name=Thomas`

We get `Hello Thomas` as output.

For a more advanced API with multiple endpoints and parameters, it's easiest to
use Postman:

![](/img/postman-lambda.png)


title: Time_and_computers
last_modified: 2024-10-18 18:55:56
body:
# Time and computers

## Epochs and Unix Time

An epoch is a period of time identified by a starting point.

The standard epoch for measuring against is known colloqially as "Unix Time":
midnight on 1st January 1970. Unix time is the number of seconds that have
elapsed since this point. This is the standard for Windows and most unix
systems.

For example midnight on 2nd January 1970 would be 86400 seconds since the epoch.
The deduction is as follows:

```
60 * 60 * 24 = 86400
```

We are multiplpying the number of seconds in a minute by the number of minutes
in an hour by the number of hours in a day.

To represent times and dates before 1970, we use negative integers.

## UTC and time zones

"Coordinated Universal Time" (UTC) is the time standard against which the
world's timekeeping is synchronized. It is not itself a timezone, it is a
transcendent standard that defines what time zones are.

Time zones are defined relative to UTC, by their offset to this value.

For example North America is behind UTC by five or six hours hence uses the
notation UTC-5:00 or UTC-6:00.

UTC is the successor to GMT of which there were variances necessitating a
universal value. Despite this GMT is equal to UTC+00:00 so apart from when
daylight saving is in effect in the UK, GMT = UTC.

Depending on your locale, relative to UTC, you will get different outputs when
you attempt to log Unix Time.

Also daylight savings can impact on the outputs. For instance for half of the
year, when DST applies, the UK is UTC+01:00.

## Examples in Python

The following gives us the unix seconds in my current time zone (GMT DST):

```py
import time
print(time.time())
# 1714322393.2929392
```

If I want UTC, I can do:

```py
import datetime
print(datetime.datetime.utcnow().strftime('%s'))
# '1714318952'
```

We see clearly that they are not identical.


title: Basic_model_of_the_operating_system
last_modified: 2024-10-18 18:55:56
body:
# Basic model of a nix operating system

We can abstract the Linux OS into three operational levels or tiers, from the
bottom up:

<dl>
<dt>User processes: user space</dt>
<dd>The running programs that the kernel manages. Also known as the user space which is the memory that the kernal assigns for user processes. Comprising:
<ul>
    <li>Graphical user interface</li>
    <li>Servers</li> 
    <li>Shell</li>    
  </ul>
<dt>Kernel: kernel space</dt>
</dd>
<dd>The core of the operating system. Software residing in memory that tells the CPU where to look for its next task. Acts as a mediator and primary interface between the hardware and the user processes. Known as kernel space: the memory that the kernel allocates for itself. Comprising:
  <ul>
    <li>System calls</li>
    <li>Process management</li> 
    <li>Memory management</li>    
    <li>Device drivers</li>    
  </ul>
</dd>
<dt>Hardware</dt>
<dd>The base: one or more CPUs and RAM memory performing computations and writing to memory. Comprising:
  <ul>
    <li>Processor (CPU)</li>
    <li>Main memory (RAM)</li> 
    <li>Disks</li>    
    <li>Network ports</li>    
  </ul>
</dl>

!! Add info on kernel mode and user mode
https://www.geeksforgeeks.org/user-mode-and-kernel-mode-switching/


title: Relation_between_kernel_and_CPU
last_modified: 2024-10-18 18:55:56
body:
# Relation between the kernel and CPU

It can be confusing to understand how the kernel and CPU interact with one
another. Whilst it is true to say the kernel mediates between the hardware and
user processes, this led me to think that the kernel was somehow 'over and
above' the actions of the CPU. This is wrong and if you think about it, how
could this be since any process, kernel included, requires the processor? The
kernel is executed by and loaded into the CPU just like any other instruction in
the
[fetch, decode, execute cycle](Fetch_decode_execute.md)
of the CPU.

However as a process, the kernel is the 'first amongst equals' given that it is
the core embodiment of the operating system. At boot time, the kernel is
injected into memory and at this point the CPU _looks to this address_ in memory
in order to source its first instruction. That's how the kernel gets going. It
is the first instruction that the CPU fetches and this is what allows the kernel
to play its mediatory role. However most of the fetch, decode, execute cycles of
the CPU take place independently of the kernel.

![](/img/kernel-cpu-interaction.svg)

> Fetch decode and execute refer to processor pipeline stages. They occur
> automatically as part of normal processor operation, the kernel doesn’t
> generally have any direct control over how that happens...When the boot loader
> loads the kernel, it points the CPU’s program counter to an entry point in the
> kernel’s code. From there, the CPU will continue executing (fetching decoding
> and executing) kernel code until you point the program counter to a userspace
> program, where it will continue executing userspace code until an interrupt
> points the program counter back into kernel code. That’s basically what the
> CPU will be doing from boot up to shut down; switching between executing
> kernel code and executing userspace code
> ([Reddit]())

This is because of [context switching](), when the CPU is running its cycle, the
kernel is idle waiting for it to complete. Once control switches back to the
kernel it can then run its next duty which will be either process, memory or
device management or system calls. Then it will hand control over to either the
processor or user space.


title: Headless_Raspi_network_setup
last_modified: 2024-10-18 18:55:56
body:
# Headless network setup

If you are using a headless system and you aren't using an ethernet connection,
you face a chicken-egg issue of how to connect the device to WiFi via `ssh` when
the device isn't yet on the network.

You can get around this by saving a file called `wp_supplicant.conf` to the
`/boot` directory of the device. This specifies the SSID name and password and
will be used to auto-connect when the device boots.

For example:

```
country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
     ssid="SKYVL7XP"
     scan_ssid=1
     psk="WIFI_PASSWORD"
     key_mgmt=WPA-PSK
}
```

## Related notes


title: Truth_functional_connectives
last_modified: 2024-10-18 18:55:56
body:
# Truth-functional connectives

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

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

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

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

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

### Conjunction

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

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

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

### Disjunction

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

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

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

### Negation

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

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

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

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

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

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

The truth table is as follows:

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

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

FT is less intuitive:

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

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

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

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

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

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

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

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

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

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

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

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

## Combinations of truth-functional connectives

---

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

For example the proposition:

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

Can be expressed with greater logical clarity as:

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

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

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

Which has the truth table:

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

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

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

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

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

### Logical equivalence

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

For example the proposition:

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

Can obviously be expressed formally as:

```
P & Q
```

But also as:

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

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

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

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

Another example of equivalent expressions:

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

The first formalisation:

```
~P & ~Q
```

Equivalent to:

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

Again the truth-tables for verification:

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

`~P & ~Q`

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

### Important equivalences

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

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

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

## Enforcing binary connectives through bracketing

If we had a proposition of the form

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

We could not write this as:

```
P & Q & R
```

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

```
(P & Q) & R

P & (Q & R)
```


title: SAM
last_modified: 2024-10-18 18:55:56
body:
# AWS SAM

SAM stands for **serverless application model**. It is a framework developed by
AWS to simplify the process of building, deploying and managing serverless
applications. It provides a concise syntax for defining the components of a
serverless application, such as
[Lambda functions](zk/Lambda_programming_model.md),
[API gateway](/zk/AWS_API_Gateway.md) and database tables.

The SAM infrastructure is defined in a YAML file which is then deployed to AWS.
SAM syntax gets transformed into CloudFormation during the deployment process.
(CloudFormation is a broader and more robust AWS tool for large, highly
scaleable infrastructures).

## Key features of SAM

- Single deployment configuration
- Integration with development tools
- Local testing and debugging
- Built on AWS CloudFormation

## Main technologies required

### Docker

Whilst SAM can be used to create a deployable file for AWS it can also be run as
a container for local development with Docker.

### AWS CLI

This is installed using Python and allows you to interact directly with AWS via
the command-line.

### AWS SAM CLI

See
[https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)

## Setting up credentials for the AWS CLI

You require an access key for the given
[IAM user](zk/AWS_User_management_and_roles.md#iam). You should create an IAM
account specific to the project with bounded permissions.

```
aws configure
AWS Access Key ID [None]: AK*******
AWS Secret Access Key [None]: ukp******
Default region name [None]:
Default output format [None]:
```

This information can be found in the Security Credentials section of the given
[IAM](zk/AWS_User_management_and_roles.md#iam) user:

![](/img/access-key-aws.png)

### Switching between credentials

You should set up a different IAM user for each project.

You can do this with:

```sh
aws configure --profile <profile-name>
```

This will then ask you to add the credentials for the user.

You can switch between different credentials for the user as follows:

```sh
AWS_PROFILE=<profile-name> sam build

```

## Starting a SAM project

First create a directory for your project which will serve as the repository:

```sh
mkdir aws-sam-learning
cd aws-sam-learning
```

Then we can use the `sam` cli to bootstrap the project:

```sh
sam init --runtime nodejs16.x
```

We can just click through and accept the basic HelloWorld Lambda.

This will create the Lambda as well as an API Gateway trigger URL.

### `template.yaml`

This is autogenerated and details the main constituents of the project. There
are lots of fields but the most important are the following:

```yaml
HelloWorldFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: hello-world/
    Handler: app.lambdaHandler
    Runtime: nodejs16.x
    Architectures:
      - x86_64
    Events:
      HelloWorld:
        Type: Api
        Properties:
          Path: /hello
          Method: get
```

This details the location of the [handler function](/Lambda_handler_function.md)
which is contained at the path `hello-world/app.js`:

```js
exports.lambdaHandler = async (event, context) => {
  try {
    // const ret = await axios(url);
    response = {
      statusCode: 200,
      body: JSON.stringify({
        message: "hello world",
        // location: ret.data.trim()
      }),
    };
  } catch (err) {
    console.log(err);
    return err;
  }

  return response;
};
```

It also lists the `get` event that we can use to call API Gateway and trigger
the Lambda.

The full template is below:

![](/img/sam-template-yaml.png)

## Adding our own code

We will create our own function and API Gateway trigger.

We will place our function after the existing `HelloWorldFunction`

```yaml
ClockFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: clock/
    Handler: handler.clock
    Runtime: nodejs16.x
  Events:
    ClockApi:
      Type: Api
      Properties:
        Path: /clock
        Method: get
```

We can test the syntax with:

```sh
sam validate
```

Just like with `HelloWorld`, we will create a directory for this function:
`clock` and we will initialise it as an `npm` project.

```sh
mkdir clock
cd clock
npm init
```

We will use `handler.js` as our root, handler function.

We have said in the template file that our `Handler: handler.clock`, therefore
the main function in the `handler` module should be `clock`:

```js
const moment = require("moment");

exports.clock = async (event) => {
  console.log("Clock function run");
  const message = moment().format();
  const response = {
    statusCode: 200,
    body: JSON.stringify(message),
  };
  return response;
};
```

The directory structure is as follows:

![](/_img/sam-directory.png)

When we call the API Gateway path `/clock` with `GET`, our function will be
triggered.

## Deploying the project

We will now deploy our project to AWS from the local environment.

The process is as follows:

1. Build
2. Package
3. Deploy

### Build

We need to install the runtime dependencies for the function. We do this by
running `sam build`. This ignores test files and development dependencies and
installs the project dependencies and source files to a temporary subdirectory.

![](/img/sam-build.png)

The build directory is `.aws-sam/build/`. There will be a subdirectory for each
of our files.

### Package

As noted, CloudFront handles the deployment of the application. It can only
receive one file as an input. The packaging process consists in creating that
single file.

The packaging proces will first archive all of the project artefacts into a zip
file and then upload that to [S3](zk/AWS_S3.md). A reference to this S3 entity
is then provided to CloudFormation.

![](/img/s3-package-again.svg)

The command is as follows:

```sh
sam package
  --template-file template.yaml
  --output-template-file pkg.yml
  --region eu-west-1
```

This will automatically create a hashed bucket name for you in S3 (I have tried
to add my own naming but it doesn't comply.)

### Local development with Docker

In order to work with your application locally without actually sending requests
to AWS and using credit, you can run a local instance.

See [Local AWS Development with SAM](zk/Local_AWS_development_with_SAM.md).

### Deploy

Once you have packaged the app you can deploy with `sam deploy --guided`. This
will talk you through the defaults and will deploy the package to
CloudFormation. In CloudFormation each individual project is called a **stack**.

If we then go to Cloud Formation we will see the deployed application.

![](/img/cloud-formation-stack.png)

## Call the endpoint

If we now go to the Lambda console, we will see our function listed, and the API
Gateway endpoint under `triggers`:

![](/img/gateway-trigger.png)

We can then call this from Postman to check everything is working as it should:

![](/img/postman-aws-output.png)


title: Relational_database_architecture
last_modified: 2024-10-18 18:55:56
body:
# Relational database architecture

Tables, fields and records are the basic building blocks of databases

![](/img/FL-Databases-1.5_terminology.gif)

## Table

A group of similar data with rows for **records** and columns for each
**field**.

## Record

A horizontal row: a collection of items which may be of different data types all
relating to the individual or object that the record describes. A single _entry_
in the table. Sometimes called **row** interchangeably.

## Field

A vertical column: stores a single particular unit of data for each record. Each
field must use the same data type. Sometimes called **field** interchangeably.

Each individual field has **properties:** such as the data type, length or the
total memory allocation.


title: Further_examples_of_TS_generics
last_modified: 2024-10-18 18:55:56
body:
# 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");
```


title: Hardware_simulation
last_modified: 2024-10-18 18:55:56
body:
# 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 ...
```


title: Lambda_handler_function
last_modified: 2024-10-18 18:55:56
body:
# 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
```


title: Ports_in_NodeJS
last_modified: 2024-10-18 18:55:56
body:
# Ports

When working in development we are able to specify the specific port from which
we want top serve our application. In production, we do not always have this
control: the port will most likely be set by the provider of the server
environment.

While we may not know the specific port, whatever it is, it will be accessible
via the `PORT` environment variable. So we can use this when writing our
[event listeners](Events%20module.md#event-emitters):

```js
const port = process.env.PORT || 3000;
```

This way, if a port is set by the provider it will use it. If not, it will fall
back to 3000.


title: 385af4b4_Baran_distributed_networks
last_modified: 2024-10-24 17:17:48
body:
# 385af4b4_Baran_distributed_networks

## Paul Baran's concept of a distributed network and influence on architecture of the ARPANET

Baran worked for RAND not ARPA and his work predated the commencement of the
ARPANET. However his model of a distributed network was highly influential on
the design of the ARPANET and served as a consultant during the initial
construction.

Unlike ARPA, Baran was concerned with command and control and how a
communication network could be resiliant enough to withstand nuclear conflict.
He analysed the weaknesses in AT&T's telephone network and proposed a more
robust and resiliant network model.

If we think of the nodes of the phone network as being loci for the transferral
of data, the phone network was highly centralised. The message would go from its
transmitter to a central switch which would route it to the intended receiver:

![Centralised network](../img/centralised.png)

The problem is obvious: if the central switch went down, all data transferance
stop.

In fact this is an over-simplification. The phone network at the time was
actually decentralised:

![Decentralised network](../img/decentralised.png)

This system is an improvement because each region has a switch and not all nodes
are dependent on the central switch at all times. If the central switch goes
down, the peripheral switches can still manage connections within their regions.
It just means that trans-regional messages are not longer possible. Conversely,
if one regional switch goes down, the other regions are not affected and can
communicate between themselves and each other. So there is greater resiliance
but still points of failure - at the central and regional switches.

Baran conceived a _distributed_ network as his model for the most resiliant
communications network. Instead of centralised or decentralised switches, nodes
would connect to eachother in aggregate:

![Distributed network](../img/distributed.png)

Under this system, every node can eventually access every other node through
intermediary nodes, without going through a main switch that establishes the
connection.

This is far more resiliant than the previous two network types because if a
single node or multiple nodes go down, the message can be re-routed through
others.

Furthermore, by distributing the connections between nodes, the overall transfer
of data can be more efficient as there isn't a bottleneck of centralised
switches that can block up at times of high network activity. If one pathway is
blocked, another route can be taken.

The distributed model also opens up the opportunity to optimise transmission.
For a given node in the network, it becomes possible to find the shortest route
to another node. This would be very important in designing the IMP nodes of the
ARPANET.


title: Machine_code
last_modified: 2024-10-18 18:55:56
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)


title: SQLite
last_modified: 2024-11-13 18:20:27
body:
# SQLite

## INSERT OR IGNORE INTO

If table has a `UNIQUE` constraint on a field, insert if it is unique otherwise
skip without throwing a constraint error.

```sql
INSERT OR IGNORE INTO table_name (value) VALUES (:value)
```


title: Comparing_React_classes_to_hooks
last_modified: 2024-10-18 18:55:56
body:
# Comparing class components to hook-based components

We will look at the same component written with hooks and classes. Both use this
child component:

```jsx
const CounterBox = (props) => {
  return <div className="output">{props.counterValue}</div>;
};
```

## Class component

```jsx
import CounterBox from "./CounterBox";

class CounterClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counterValue: 0,
    };
  }

  addOne() {
    this.setState({
      counterValue: this.state.counterValue + 1,
    });
  }

  render() {
    return (
      <React.Fragment>
        <button onClick={() => this.addOne()}>Add One</button>
        <CounterBox counterValue={this.state.counterValue} />
      </React.Fragment>
    );
  }
}
```

## Hook version

```jsx
import React, { useState } from "react";
import CounterBox from "./CounterBox";

function CounterHook() {
  const [counter, setCounter] = useState(0);
  const handleClick = () => setCounter(counter + 1);
  return (
    <React.Fragment>
      <button onClick={handleClick}>Add One</button>
      <CounterBox counterValue={counter}></CounterBox>
    </React.Fragment>
  );
}
```

## Main differences

- Obviously with hooks we use a function, not a class. We can therefore think of
  them as **simple components** that are able to handle state as well as props.
- We have written the hook as a declared function but we could just as easily
  write it as a function expression or arrow function.
- With the class, the state versioning is managed via the following properties
  on the `React.Component` class: `this.state` (start state) and `this.setState`
  (the change).
- In both cases the function that actually creates the change is handled via an
  arrow function. With the hook we use the customary name `handleClick` .

- The same process is managed in the hook via the `useState()` method. We
  effectively set 'before' and 'after' destructured variables on this method:

  ```jsx
  const [counter, setCounter] = useState(0);

  // Schematically:
  const [val, setVal] = useState(initialVal);
  ```

## Binding with class component state

In the class component example we use an inline arrow function to execute
`addOne()` however the official way to do it is to bind the event via the
constructor method to avoid the problems created by `this` scope in classes.
Using arrows is mostly fine and is less verbose, but it is important to
understand the 'official' approach.

The way to write the state change in this way is:

```jsx
class CounterClass extends React.Component{
  constructor(props){
    super(props);
		this.state = { counterValue: 0};
    this.handleClick = this.handleClick.bind(this); // bind the click to the class scope
  }

  handleClick(event){
    this.setState({
            counterValue: this.state.counterValue + 1;
    });
  }

  render(){
    return (
      <button type="button"
      onClick={this.handleClick}> // reference the class scope with `this`
      Click Me
      </button>
			<CounterBox counterValue={this.state.counterValue}></CounterBox>
    );
  }
}
```

## How hooks improve on class components

- State, when managed via hooks can be easily decoupled from a specific
  component and reused elsewhere. This also means it can be tested separately
  from any specific component where it is applied. This is much harder to do
  with classes which are closely entangled with the non-state aspects of a
  component through `this` and binding:

  > With Hooks, you can extract stateful logic from a component so it can be
  > tested independently and reused. Hooks allow you to reuse stateful logic
  > without changing your component hierarchy. This makes it easy to share Hooks
  > among many components or with the community.

- Hooks simplify lifestyle methods and the use of `componentDidMount` etc
  through the `useEffect` hook
- Classes and `this` are confusing generally. Functions are easier to grasp and
  read.

## Relation to function components

You can think of hooks as function components (previously known as stateless
components) plus state. Previously function components were 'dumb' components.
That is, they didn't allow for state management and generally used for layout.
They could receive data as props but not state. This changes with hooks: you can
add state to functions and no longer need to use classes exclusively for state.
In fact you no longer need to use classes at all, unless you want to.

> The stateful portion of a function component must be placed before the
> `return()` statement in the function.


title: Forms_in_React
last_modified: 2024-10-18 18:55:56
body:
# Forms using hooks

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

>

## Basic approach

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

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

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

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

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

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

### Applied example

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

```jsx

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

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

## More complex forms

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

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

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

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

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

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

There are three parts:

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

### Applied example

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

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

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

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

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


title: Module_pattern
last_modified: 2024-10-18 18:55:56
body:
# Module pattern

> Come back and compare this with learning from Node.js

With the module design pattern we create encapsulation: the variables and
functions (as methods) are kept private inside the module and cannot be
overwritten. This design pattern is familiar from Node.js development: every
package you import into your project and download from NPM is a module.

Generally you will create the module as a class, import it and then instantiate
a new instance. However for private development, you could just as well use an
object and duplicate it with `Object.create`.

## Example

Here is an example of a simple module that returns the age of a person.

```js
export default class Age {
  constructor(name, birthYear) {
    this.name = name;
    this.birthYear = birthYear;
  }
  currentYear() {
    return new Date().getFullYear();
  }
  get age() {
    return this.currentYear() - this.birthYear;
  }
}
```

```js
const martha = new Age("Martha", 1997);
console.log(martha.age); // 24
```

## Controlling access

In the example above, `aValue` could be edited in instantiations of the class.
Given that modules should not be overwrittable, you could make it a private
property on the class. The benefit of getters and setters is that they dictate
what can be modified and retrieved from the module. So if you use `get` and
`set` you can prevent overwrites.

## Object modules

If you want to use an object instead of a class, you have to take greater care
to ensure that the objects are not overwritable. **Also you cannot use the `#`
modifier to make properties private.**

- Use getters and setters for updating and retrieving values
- Use `Object.seal` to prevent changes to the parent object
- Instead of using getters and setters, for individual properties you can set
  `writable` to be `false` for properties that you don't want changed

There are examples of each in the following:

```jsx
export const age = {
  name: "",
  birthYear: new Number(),
  currentYear() {
    return new Date().getFullYear();
  },
  get age() {
    return this.currentYear() - this.birthYear;
  },
};

Object.seal(age);
Object.defineProperty(age, "aValue", {
  value: 6,
  writable: false,
});
```


title: Mainframe_computers
last_modified: 2024-10-18 19:16:47
body:
# Mainframe computers

A mainframe computer, also known as a "big iron" is a high performance computer
used for resource-intensive tasks that require greater security and availability
than smaller scale machines. They are typically used for centralised rather than
distributed computing.

The heyday of mainframes was before the mini- and micro-computer revolution but
they are still used.

IBM remains the main manufacturer and service provider for mainframes. Example
use cases include banks who need to process large volumes of transatction and
high frequency tranding; government departments, the military and internal
revenue who require huge databases.


title: Redirection_in_Bash
last_modified: 2024-10-18 18:55:56
body:
# Redirection

## Redirecting outputs

The symbol `>` is called the **redirection operator** because it redirects
`stdout` to another program or file. You most frequently use this when you want
to save contents to a file rather than standard output.

```bash
ls | grep d* > result.txt
```

### Combining redirection with file escriptors

It is common practice to combine redirection with the
[file descriptors](File_descriptors.md) to redirect
the output of `stdout` and `stderr`. A common case is to
[redirect error output to `/dev/null`](Redirect_to_dev_null.md).

Redirection defaults to interpreting `>` as the redirection of `stdout` (`1`);

## Redirecting inputs

We can also switch the direction of the redirection symbol and pass in a file to
a command rather than command ouput to a file:

```bash
sql-processing-program < data.sql
```

We can redirect a string with three carets:

```bash
program <<< "this is a string input"
```

## Appending

We use `>>` to append contents on the next available line of a pre-existing
file. Continuing on from the example above:

```bash
echo 'These are the files I just grepped' >> result.txt
```


title: Routing_on_link_click_in_React
last_modified: 2024-10-18 18:55:56
body:
# Test: Routing on link click

## Component

```jsx
const CertificateHtmlLink = () => {
  return (
    <div className="certificate-html-link html-version-message">
      <Link className="btn btn-primary ml-2" to="/some-location">
        View HTML version
      </Link>
    </div>
  );
};
```

## Test

```js
import { render, fireEvent } from "@testing-library/react";
import { Router } from "react-router-dom";

describe("<CertificateHtmlLink", () => {
  it("should link to dynamic HTML certificate page URL on click event", () => {
    // Arrange:
    const history = createMemoryHistory({ initialEntries: ["/"] });

    // Act:
    const { getByText } = render(
      <Router history={history}>
        <CertificateHtmlLink to="/certificate/123" />
      </Router>
    );

    // Assert:
    expect(history.location.pathname).toBe("/");
    fireEvent.click(getByText("View HTML version"));
    expect(history.location.pathname).toBe("/certificate/123");
  });
});
```


title: Iterating_in_React
last_modified: 2024-10-18 18:55:56
body:
# 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>
  )
}
```


title: Conditional_statements_in_Python
last_modified: 2024-10-18 18:55:56
body:
# Conditional statements in Python

## Basic example

```python
input_string = input('Please input a number: ')

if input_string.isnumeric():
    print('The number is accepted')
else:
    print('The input is invalid')

# 5
# The number is accepted

# Using an and in the condition
print('-' * 25)
age = 15
status = None
if age > 12 and age < 20:
    status = 'teenager'
else:
    status = 'not teenager'
print(status)
```

## Else if

```python
savings = float(input("Enter how much you have in savings: "))

if savings == 0:
    print("Sorry no savings")
elif savings < 500:
    print('Well done')
elif savings < 1000:
    print('That is a tidy sum')
elif savings < 10000:
    print('Welcome Sir!')
else:
    print('Thank you')
```

## Nested conditions

```python
snowing = True
temp = -1
if temp < 0:
    print('It is freezing')
    if snowing:
        print('Put on boots')
    print('Time for Hot Chocolate')
print('Bye')
```

## Ternaries/ shorthand conditionals

```python
status = 'teenager' if age > 12 and age < 20 else 'not teenager'
print(status)

num = int(input('Enter a simple number: '))
result = -1 if num < 0 else 1
print('Result is ', result)
```


title: AWS_CloudWatch
last_modified: 2024-10-18 18:55:56
body:
# AWS CloudWatch

AWS CloudWatch is a monitoring and observability service offered by Amazon Web
Services (AWS). It is designed to help users collect, track, and analyze various
metrics, logs, and events related to their AWS resources and applications
running on the AWS cloud. CloudWatch enables users to gain operational insights
and take timely actions to maintain the performance, availability, and health of
their infrastructure and applications.

## Key features

- Metrics
  - CloudWatch collects and stores metrics from various AWS services, such as
    EC2 instances, Lambda functions, and RDS databases. Users can monitor these
    metrics in real-time, set alarms based on thresholds, and visualize them
    through customizable dashboards.
- Logs
  - CloudWatch Logs enables users to collect, store, and analyze log data from
    AWS resources and applications.
- Events
  - Events allows users to respond to changes in their AWS resources
    automatically by triggering actions such as sending notifications, invoking
    Lambda functions, or starting/stopping EC2 instances.
- Alarms
  - Users can set alarms based on specified metric thresholds. When an alarm is
    triggered, CloudWatch can send notifications or automatically take
    corrective actions.
- Dashboards
  - Dashboards provide customizable, at-a-glance visualizations of various
    metrics and logs, making it easier to monitor the health and performance of
    AWS resources and applications.

## Accessing CloudWatch

CloudWatch can usually be accessed directly from within the dashboard for the
given AWS service under the "Monitor" tab. For example with AWS Lambda:

![](/img/lambda-cloudwatch.png)

In addition to graphical metrics, we can also view specific logs for each event
and execution of the given service. In the case of a Lambda function:

![](/img/cloudwatch-logs.png)


title: Writing_to_files_in_Python
last_modified: 2024-10-25 09:34:09
body:
# Writing to files in Python

We create a file object with `open()` and 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()
```

Alternatively we use `with open` which automatically closes the file:

```py

with open("example.txt", "w") as file
    file.write('some lines')

```

> 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.


title: Creating_a_Docker_image
last_modified: 2024-10-18 18:55:56
body:
# Creating a Docker image

We create a new image with `docker build...`

The key components of a build:

- A Dockerfile
- An asset directory (the **build context**) containing the Dockerfile and all
  resources needed for the build.

## Syntax

Here is an example of a Dockerfile that builds a container for running a Python
app:

```Dockerfile
# Use an official Python runtime as a parent image
FROM python:3.8-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

```

### Main syntax explained

| Keyword   | Role                                                                                                                                                                                                                                                                   |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| #         | Lines starting with '#' are comments, providing explanations or context for the Dockerfile.                                                                                                                                                                            |
| `FROM`    | Specifies the base image used to build the new image. In this case, it's using the official Python 3.8 slim image.                                                                                                                                                     |
| `WORKDIR` | Sets the working directory for any subsequent instructions in the Dockerfile. Here, it's set to '/app'. Everything after a `WORKDIR` line, all instructions will be executed in this directory. If it doesn't exist, Docker will create it. Can have multiple.         |
| `COPY`    | Copies files or directories from the source (in this case, the current directory) to the destination (in this case, '/app') within the Docker image. The source is the build context. We are copying from the build context to the container when we use this keyword. |
| `ADD`     | Same as `COPY` but can be passed a tarball or a URL                                                                                                                                                                                                                    |
| `RUN`     | Executes a command and commits the result as a new layer in the image. In this example, it installs packages specified in the 'requirements.txt' file using pip.                                                                                                       |
| `EXPOSE`  | Informs Docker that the container listens on the specified network ports at runtime. In this case, port 80.                                                                                                                                                            |
| `ENV`     | Sets an environment variable in the image. In this example, the variable 'NAME' is set to 'World'.                                                                                                                                                                     |
| `CMD`     | Specifies the command to run when the container starts. Here, it runs 'app.py' using Python. Note that there can only be one CMD instruction in a Dockerfile.                                                                                                          |
| `USER`    | The default user will be `root`. This is not good for security. Have to choose a distribution image that allows you to change user. For example: `USER: nobody:nogroup`. Ubuntu allows for this.                                                                       |
| `LABEL`   | Metadata that you want to add to the image to be viewable when image is inspected, e.g. `LABEL maintainer=tactonbishop@gmail.com`                                                                                                                                      |

### Another example

The following Dockerfile creates a frontend React application:

```Dockerfile
# Use the official Node.js image as the base image
FROM node:14-alpine

# Set the working directory to /app
WORKDIR /app

# Copy the package.json and package-lock.json files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy the rest of the application code
COPY . .

# Expose the port the app will run on
EXPOSE 3000

# Start the React development server
CMD ["npm", "start"]
```

### Two modes of `RUN`: shell and exec

The RUN command in a Dockerfile has two forms: the "exec" form and the "shell"
form. Both forms are used to execute commands during the build process, but they
have different syntax and behavior.

Exec form is written as `RUN ["executable", "param1", "param2", ...]`. This form
executes the command directly without invoking a shell. As a result, shell
processing features like environment variable substitution, pipes, and redirects
are not available. It is the preferred form when you need to run a command
without relying on shell behavior or when you want to avoid shell-related
issues, such as variable substitution or command injection.

Shell form is written as `RUN command param1 param2 ....` This form executes the
command within a shell, which is /bin/sh -c on Linux and cmd /S /C on Windows.
It allows for shell processing, enabling environment variable substitution,
pipes, redirects, and other shell features. This form is preferred when you need
to use shell features or when you want to chain multiple commands together.

## Environmental variables

> ENV instructions in a Dockerfile are used to define environment variables that
> can be accessed by the processes running inside the container. These variables
> can be used to configure the behavior of the application, pass parameters, or
> store sensitive information like API keys.

```Dockerfile
# Use an official Node.js runtime as a parent image
FROM node:14-alpine

# Set the working directory to /app
WORKDIR /app

# Copy package.json and package-lock.json into the container
COPY package*.json ./

# Install any needed packages specified in package.json
RUN npm ci

# Copy the rest of the application code
COPY . .

# Set environment variables
ENV PORT=8000
ENV API_KEY=my_secret_key

# Expose the port the app will run on
EXPOSE 8000

# Run the app
CMD ["npm", "start"]
```

In this example, two environment variables are defined: PORT and API_KEY. These
variables can be accessed in the application code using process.env.PORT and
process.env.API_KEY in Node.js, for example.

To override the environment variables defined in the Dockerfile when running the
container, you can use the -e flag followed by the variable name and its new
value in the docker run command:

```sh
docker run -e PORT=8080 -e API_KEY=new_secret_key -p 8080:8000 <image_name>
```


title: Multiplying_fractions
last_modified: 2024-10-18 18:55:56
body:
# Multiplying fractions

To find the product of two fractions $\frac{a}{b}$ and $\frac{c}{d}$ multiply
their numerators and denominators and then reduce:

$$\frac{a}{b} \cdot \frac{c}{d} = \frac{a \cdot c}{b \cdot d}$$

### Example

$$
\frac{1}{3} \cdot \frac{2}{5} = \frac{1 \cdot 2}{3 \cdot 5} = \frac{2}{15}
$$

## Prime factorisation in place

The example above did not require a reduction, so here is a more complex
example:

$$
\frac{14}{15} \cdot \frac{30}{140} = \frac{420}{2100}
$$

It would be laborious to reduce such a large product using factor trees or the
repeated application of divisors, as defined in
[reducing fractions](Reducing_fractions.md). We can use a more efficient
method. This method can be applied at the point at which we conduct the
multiplication rather than afterwards once we have the product. We express the
the initial multiplicands as prime factors:

$$
\frac{14}{15} \cdot \frac{30}{140} = \frac{(2 \cdot 7) \cdot (2 \cdot 3 \cdot 5) }{(3 \cdot 5) \cdot (2 \cdot  2 \cdot 7 \cdot 5)}
$$

We now have the product in factorised form before we have applied the
multiplication so we can go ahead and cancel:

$$
\frac{\cancel{2}, \cancel{7}, \cancel{2}, \cancel{3}, \cancel{5}}{\cancel{3}, \cancel{5}, \cancel{2}, \cancel{2}, \cancel{7}, 5} = \frac{1}{5}
$$

**Note that in the above case, there was only a single 5 left as a denominator
and no value left as a numerator. This is equivalent to there just being "one
five" so we write $\frac{1}{5}$**

## Example of multiplying fractions with negative fractions containing variables

Calculate: $$- \frac{6x}{55y} \cdot - \frac{110y^2}{105x^2}$$

First multiply in place:

$$
\frac{(3 \cdot 2 \cdot x) \cdot (5  \cdot 2 \cdot 11 \cdot y \cdot y)}{(5 \cdot 11 \cdot y) \cdot (7 \cdot 5 \cdot 3 \cdot x \cdot x)}
$$

Then cancel:

$$
\frac{(\cancel{3} \cdot 2 \cdot \cancel{x}) \cdot (\cancel{5}  \cdot 2 \cdot \cancel{11} \cdot \cancel{y} \cdot y)}{(\cancel{5} \cdot \cancel{11} \cdot \cancel{y}) \cdot (7 \cdot 5 \cdot \cancel{3} \cdot \cancel{x} \cdot x)} =
\frac{2  \cdot 2 \cdot y}{7 \cdot 5 \cdot x}
$$

Then reduce:

$$
\frac{2  \cdot 2 \cdot y}{7 \cdot 5 \cdot x} = \frac{4y}{35x}
$$


title: Event_loop
last_modified: 2024-10-18 18:55:56
body:
# The Event Loop

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

## Multi-threaded synchronous architectures

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

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

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

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

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

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

## Node as a single-threaded asynchronous architecture

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

![async.svg](async.svg)

## The Event Loop

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

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

Node is continually monitoring the Event Loop in the background.

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

### Phases of the Event Loop

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

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

The phases are as follows:

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

## Event _loop_ and event _queue_

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

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

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


title: Accessing_secrets_from_a_Lambda
last_modified: 2024-10-18 18:55:56
body:
# Accessing secrets from a Lambda

If a Lambda connects to a database or calls an API, it is going need access to
authentication tokens/passwords.

You obviously should not store these in your code. Instead you should store them
in AWS Secrets Manager and access them via the `aws-sdk` in your Lambda
function.

In order for your function to be able to access the secrets however, it will
need to be given permission. While the Lambda is not itself a "user" it does
have an identity in the form of its IAM role. This is disclosed by its ARN.

To allow the Lambda to access the secret you should add a resource permission on
the secret that designates the Lambda.

> When you create a Lambda function, you specify an IAM role that AWS Lambda can
> assume to execute the function on your behalf. This role is referred to as the
> execution role. The execution role grants the function the necessary
> permissions to call other AWS services, access resources, and perform other
> operations.

Here is an example of a resource permission giving access to a Lambda:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::885135949562:role/pocket-api-lambda-QueryPocketFunctionRole-GY5ZN3RW31KE"
      },
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:eu-west-2:885135949562:secret:pocket-api-credentials-wEvQMI"
    }
  ]
}
```

See
[Fetch from Secrets Manager](Fetch_from_Secrets_Manager.md)
for a code example of retrieving a value from Secrets Manager.


title: Classes
last_modified: 2024-10-18 18:55:56
body:
# Classes

## Type declarations for classes

TypeScript offers full type annotations for classes. It also introduces several
TypeScript-specific options (control access modifiers, interfaces etc) that do
not exist in JavaScript but which seek to bring it into closer alignment with
more strict object-oriented languages like Java and C#.

A class in JavaScript:

```js
class Age {
  constructor(name, birthYear) {
    this.name = name;
    this.birthYear = birthYear;
  }
  currentYear() {
    return new Date().getFullYear();
  }
  get age() {
    return this.currentYear() - this.birthYear;
  }
  get dataOutput() {
    return `${this.personName} is ${this.age} years old`;
  }
}
```

The same class in TypeScript:

```ts
class Age {
  personName: string;
  birthYear: number;
  constructor(personName: string, birthYear: number) {
    this.personName = personName;
    this.birthYear = birthYear;
  }
  currentYear(): number {
    return new Date().getFullYear();
  }
  get age(): number {
    return this.currentYear() - this.birthYear;
  }
  get dataOutput(): string {
    return `${this.personName} is ${this.age} years old`;
  }
}
```

The main points to note are:

- methods must specify their return type, as with [functions](Functions.md)
- the constructor function must specify its parameters' types
- we must declare the types of any properties we intend to use at the start of
  the class.

### Instantiating a class

In order to create an object instance of `Age`, we can use the standard
constructor function, viz:

```js
const mum = new Age("Mary Jo", 1959);
console.log(mum);

/* Age { personName: 'Mary Jo', birthYear: 1959 } */
```

But given that classes define objects, we can also now use `Age` as a new custom
type and define an object that way

```jsx
const thomas: Age = new Age("Thomas", 1988);
```

### Without constructor

If your class does not use a constructor, you still need to define your class
property types at the top:

```tsx
class Dummy {
  aNum: number = 4;
  get getSquare(): number {
    return this.aNum * this.aNum;
  }
}
```

## Interfaces

In most cases the difference between using the `type` and `interface` keywords
when defining a custom type is marginal however interfaces are specifically
designed for classes and OOP style programming in TypeScript. This is obviously
most apparent in a framework like Angular where interfaces are used heavily.

When we use an interface with a class we are asserting that the class must have
certain properties and methods in order to qualify as that type. This is most
helpful when you are working with several developers and want to ensure
consistency.

Let's say we have the following interface:

```ts
interface Person {
  firstName: string;
  secondName: string;
  age: number;
  employed: () => boolean;
}
```

Now we want to create a class that must share this shape. We go ahead and create
the class and say that it **implements** `Person` :

```ts
class Programmer implements Person {
// If the below are not included, TS will generate an error
	firstName: string,
	secondName: string,
	age: number,
	employed: () => boolean
}
```

## Inheritance

We can extend classes in TypeScript by using sub-classes or abstract classes.

### Sub-classes

In the case of sub-classes, we use the phrase
`[child_class] extends [parent_class]` in the class declaration to designate the
inheritance relationship between the base class and the new class that is
derived from it.

> A derived class has all the properies and methods of its base class but can
> also define additional members.

When you instantiate a child class from a parent class, if the parent class has
constructor values, you must initialise these in the child. You do this by
calling the parent constructor via the `super()` syntax. For example:

```ts
class Employee extends Person {
  constructor(firstName: string, lastName: string, private jobTitle: string) {
    // call the constructor of the Person class:
    super(firstName, lastName);
  }
}
```

To override or alter a method that exists on the parent in the child, you can
use the syntax `super.[methodName]()`.

### Abstract classes

Classes marked `abstract` are similar to parent in the case of sub-classes. The
difference is that they are like templates. Several of their methods and
properties may be directly inherited by classes that derive from them (just like
sub-classes) but they can include 'blank' methods and properties that are
placeholders for methods and properties that are defined in the derivation
class.

I have found this useful for cases where you want to inherit methods from a
parent class but implement a specific method differently in each derivation.

```ts
export abstract class IndexHyperlinksProvider
   implements vscode.TreeDataProvider<TreeItem>
{
   public activeFile: string | undefined
   private outlinks
   private fileSystemUtils: FileSystemUtils

   constructor(
      activeFile: string | undefined,
      workspaceRoot: string | undefined
   ) {
      this.setActiveFile(activeFile)
      this.outlinks = this.generateLinks()
      this.fileSystemUtils = new FileSystemUtils(workspaceRoot)
   }

   abstract generateLinks(): Promise<TreeItem[] | undefined>

   public setActiveFile(activeFile: string | undefined) {
      this.activeFile = activeFile
   }
  ...
```

Above we have a base class with a single abstract method `generateLinks()`. The
class below extends this base. Note that it passes the parent constructor values
to `super` and defines an actual method for the designated `generateLinks()`
template:

```ts
export class IndexOutlinksProvider extends IndexHyperlinksProvider {
  public workspaceFiles: string[];
  public context: vscode.ExtensionContext;
  constructor(
    activeFile: string | undefined,
    workspaceRoot: string | undefined,
    workspaceFiles: string[],
    context: vscode.ExtensionContext
  ) {
    super(activeFile, workspaceRoot);
    this.workspaceFiles = workspaceFiles;
    this.context = context;
  }

  public async generateLinks(): Promise<TreeItem[] | undefined> {
    const indexer = new IndexHyperlinks(this.context, this.workspaceFiles);
    if (typeof this.activeFile === "string") {
      const outlinks = await indexer.indexOutlinks(this.activeFile);
      if (outlinks !== undefined) {
        return this.transformLinksToTreeItem(outlinks);
      }
    }
    return;
  }
}
```

> Importantly, you cannot instantiate abstract classes. You can only `extend`
> them and then instantiate their derivation. This is another important
> difference from sub-classes.

As with sub-classes, you must initialise the properties of the parent
constructor with `super`.

### `implements` vs `extends`

You shouldn't confuse `implements` with `extends`. `implements` just checks the
class as an interface in accordance with the principles of
[duck typing](Custom_types_in_TypeScript.md#duck-typing):
i.e the implementing class should have the same properties and methods. It
doesn't affect anything internal to the methods or properties. So e.g, if you
typed a method parameter as `string` in the base class, this would still default
to `any` in the derived class.


title: Docker_general_overview
last_modified: 2024-10-18 18:55:56
body:
# Docker: general overview

> Docker is an ecosystem of tools for creating, running and transporting
> containers.

The main product of the Docker company is Docker Desktop - a UI for running
containers however the actual `docker` software is free and open source.

Docker standardised the use of containers set up the open-source Open Container
Initiative which is a convention agreed upon by the major tech companies. It
codifies the following:

- Docker runtime specification
- Docker image specification
- Docker registry specification

## Main components

- **Docker Engine**
  - The core component
  - Exposes the Docker API and manages the entire workflow around creating,
    running and deploying containers
- **Docker Client**
  - The Docker CLI
  - The primary means of interacting with the Docker Engine
  - Provides commands for managing images, containers and other Docker objects
- **Docker Compose**
  - A tool for defining and running multi-container applications
  - Define the configuration for all of the containers that make up the
    application in a single YAML file
- **Docker Images**
  - See below
- **Docker Containers**
  - See below
- **Docker Registry**
  - A centralised location where Docker iamges can be stored and shared
  - DockerHub is the registry provided by Docker
  - Organisations can set up their own private registries

## Docker images

An image is a blueprint that contains the instructions to build a container.
It's an immutable snapshot of the file system and configuration of an
application. Images can be easily shared between developers.

They are runnable packages that contain everything needed to run an application
including the binaries, libraries, resources and other dependencies.

It does not contain an operating sytem but may include a particular Linux
distribution.

## Docker containers

A container is a runnign instance of a Docker image. Multiple, identical
containers can be launched from the same, single image.

## Docker architectural overview

See [Docker architecture](Docker_architecture.md).


title: Assembly
last_modified: 2024-10-18 18:55:56
body:
# Assembly

- Assembly is one level up from machine code and provides a more human-friendly
  abstraction layer

- An assembly instruction is a _mneumonic_ that comprises an "op code" plus
  operands

- It is translated back to machine code by an assembler for the computer to
  execute.

## Example instruction

We can translate the machine instruction `e3a07004` into the assembly
instruction `mov r7, #4`. This translates to: _move the value number 4 to the
register r7_.

## Assemblers

Although assembly is useful for humans, computers still work at the level of
machine code. Therefore you need an **assembler** to translate the assembly code
to machine code. An assembly language text file is fed into an assembler and a
binary object file containing machine code is returned.

A disassembler does the opposite: translate machine code into assembly.

## Relation to instruction set architectures

- the ISA defines the hardware capabilities and the instructions that can be run
  on the hardware
- machine code is a binary representation of these instructions and can be
  directly executed by the CPU
- humans use an assembly language version of the machine code which is then
  translated back to machine code for the computer to execute.

## Related notes

[[Hexadecimal_number_system]], [[Instruction_set_architectures]],
[[CPU_architecture]]


title: Bluetooth
last_modified: 2024-10-18 18:55:56
body:
# Bluetooth

## Connect to devices with `bluetoothctl`

```bash
# Start bluetooth first
bluetoothctl power on
```

Then:

```bash
# Launch bluetoothctl
bluetoothctl
```

This initiates `[bluetooth] #` prompt. Then follow this sequence:

```bash
[bluetooth]# agent on
[bluetooth]# default-agent
[bluetooth]# scan on
pair [device_code]
connect [device_code]
```


title: Handling_negative_fractions
last_modified: 2024-10-18 18:55:56
body:
# 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}$$


title: Creating_memory_with_NAND
last_modified: 2024-10-18 18:55:56
body:
# Creating memory with NAND gates

The
[logic circuit](Digital_circuits.md)
below demonstrates how memory can be created using
[NAND](Logic_gates.md#nand-gate)
gates. A single bit is stored in memory.

![](/img/nand_latch_logic_circuit.png) Interactive version of circuit:

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

## Components

- **I** is where we input the bit that we want to remember.
- **O** is the output of the remembered bit
- **S** is an input that tells these gates when to set the memory.
- **A, B, C** are internal wires that represent the individual bit values which
  channel into each gate

## State changes

### First state: S `ON`, I `OFF`

- Start with **S** `ON` and **I** `OFF`.
- Given the logic of NAND, **A** will be on.
- This means **Gate 2** is receiving `ON` from **A** and `ON` from **S**
  therefore `B` is `OFF`
- Moving to **Gate 4** it is receiving `OFF` from **B** and it's other input is
  not activated so is therefore also `OFF`. Consequently **C** is `ON`
- `C` is fed as the second input into **Gate 3**, thus we have both inputs
  (**A** and **C**) as `ON`, therefore the output will make **O** `OFF`

> Upshot: With **S** `ON`, output is the same as input

![](/img/nand-mem-first.gif)

### Second state: both S and I `ON`

- With **S** and **I** both on, Gate A will be `OFF`
- Gate 2 is receiving `OFF` from **A** and `ON` from **S** therefore **B** is
  `ON`
- **A** feeds into Gate 2 which is receiving `ON` from **S**, therefore the
  output (**B**) will be `ON`
- **A** feeds into Gate 3 and the other input is `OFF`, therefore Gate 3 is
  outputting `ON`. Thus **O** is `ON`
- **O** feeds back to Gate 4 which is receiving `ON` from **B** thus we have two
  `ON` inputs going into Gate 4, turning **C** off
- This means the two inputs going into Gate 3 are both `OFF`, keeping **O** `ON`

> Upshot: With **S** on, the output is again the same as the input

![](/img/nand-mem-second.gif)

> So far we have seen that when **S** is `ON` you can change **I** on and off
> and **O** will change with it.

### Memory

When **S** is `ON`, **O** will mirror whatever state **I** is in. However if you
turn **S** `OFF`, **O** will remain in whatever state it was when **S** was
turned `OFF`. You can toggle **I** as much as you like, **O** will remain in its
previous state. Hence creating a memory store of the past value of **I**.

The specific reason for this is that, if **S** is `OFF`, both **A** and **B**
are `ON` since at Gate 1: `ON (I) + OFF (S) = ON` and `OFF (I) + OFF (S) = OFF`
and at Gate 2: `OFF (Gate 1) + OFF (S) = OFF`

This is illustrated in the diagram below. The space occupied by **A** and **B**
remains on (note it is illuminated) regardless of the state of **I**.

![](/img/nand-mem-demonstrated.gif)


title: Binary_number_system
last_modified: 2024-10-18 18:55:56
body:
# Binary number system

## Decimal (denary) number system

Binary is a **positional number system**, just like the decimal number system.
This means that the value of an individual digit is conferred by its position
relative to other digits. Another way of expressing this is to say that number
systems work on the basis of **place value**.

In the decimal system the columns increase by **powers of 10**. This is because
there are ten total integers in the system:

$1, 2, 3, 4, 5, 6, 7, 8, 9$

When we have completed all the possible intervals between $0$ and $9$, we start
again in a new column.

The principle of counting in decimal:

```
0009
0010
0011
0012
0013
...
0019
0020
0021
```

Thus each column is ten times larger than the previous column:

- Ten ($10^1$) is ten times larger than one ($10^0$)
- A hundred ($10^2$) is ten times larger than ten ($10^1$)

We use this knowledge of the exponents of the base of 10 to read integers that
contain multiple digits (i.e. any number greater than 9).

Thus 264 is the sum of

$$[4 \cdot (10^0)] + [6 \cdot (10^1)] + [2 \cdot 10^2]  $$

## Binary number system

In the binary number system, the columns increase by powers of two. This is
because there are only two integers: 0 and 1. As a result, you are required to
begin a new column every time you complete an interval from 0 to 1.

So instead of:

$$ 10^0, 10^1, 10^2, 10^3 ... (1, 10, 100, 1000) $$

You have:

$$ 2^0, 2^1, 2^2, 2^3, 2^4... (0, 2, 4, 8, 16) $$

When counting in binary, we put zeros as placeholders in the columns we have not
yet filled. This helps to indicate when we need to begin a new column. Thus the
counting sequence:

$$ 1, 2, 3, 4 $$

is equal to:

$$ 0001, 0010, 0011, 0100 $$

Counting in binary:

```
000000
000001
000010
000011
000100
000101
000111
```

## Binary prefix

To distinguish numbers in binary from decimal or
[hexadecimal](Hexadecimal_number_system.md)
numbers, it is common to use the prefix `0b`. Thus, e.g, `0b110` for decimal
`6`.

## Converting decimal to binary

Let's convert 6 into binary:

If we have before us the binary place values ($1, 2, 4, 8$). We know that 6 is
equal to: **1 in the two column and 1 in the 4 column → 110**

More clearly:

![](../../img/Pasted_image_20220319135558.png)

And for comparison:

![](../../img/Pasted_image_20220319135805.png)

Or we can express the binary as:

$$ (1 _ 2) + (1 _ 4) $$

Or more concisely as:

$$ 2^1 + 2^2 $$

### Another example

Let's convert 23 into binary:

![](../../img/Pasted_image_20220319135823.png)


title: Enums
last_modified: 2024-10-18 18:55:56
body:
# Enums

In essence an `enum` in TypeScript is an incremented store of immutable
variables. The only way I can understand them is just to think of them as a bag
of constants.

Below is an example of an `enum`:

```ts
enum Continents {
  NorthAmerica,
  SouthAmerica,
  Africa,
  Asia,
  Europe,
  Antartica,
  Australia,
}

// usage
var region = Continents.Africa;
```

Here is an example of a string-based enum from the Studio codebase:

```tsx
export enum HelpLinksEnum {
  composerView = "composerView",
  conditionalHelp = "conditionalHelp",
  configView = "configView",
  dashboard = "dashboard",
  dataView = "dataView",
  functionHelp = "functionHelp",
  previewMode = "previewMode",
}
```

### Main properties

- **Auto-incrementation (numeric enums only):**
  - Enums are auto-incrementing.
  - If we wanted to change the automatic sequence we could do something like the
    below and TS would automatically update the index to reflect our change
  ```ts
  enum NewProjectMenuItems {
      Row = 5
      Table // would automatically become `6` in the index
  }
  ```
- **Enums are types**

  - Enums are interpreted as types by TS. Consequently we can an enum as a type
    annotation. This can be particularly useful when typing function parameters.
    For example:

  ```ts
  enum UserResponse {
    No = 0,
    Yes = 1,
  }

  function respond(recipient: string, message: UserResponse): void {
    // ...
  }
  ```

- **Enums accept computed values**
  - As is the case with objects generally, we are not limited to primitive data
    types when setting values on enums, we can also assign functions.
- **Reverse mapping**

  - Reverse mapping is the ability to access a key via its value in addition to
    the more standard practice of accessing a value via its key.
  - Demonstration:

    ```ts
    enum PrintMedia {
      Newspaper = 1,
      Newsletter,
      Magazine,
      Book,
    }

    PrintMedia.Magazine; // returns  3
    PrintMedia["Magazine"]; // returns  3
    PrintMedia[3]; // returns  Magazine
    ```

- **Useful in switch statements**
  - Enums come in handy when you want to reduce the verbosity and repetition of
    a large switch statement.
  - The following is an example from the codebase:
    ```tsx
    public projectMenuItemClickHandler(e: IUserProjectMenuItemClick): void {
        switch (e.menuItem) {
          case UserProjectMenuItems.Rename:
            e.item.rename = true;
            break;
          case UserProjectMenuItems.Export:
            this.exportProject(e.item);
            break;
          case UserProjectMenuItems.Clone:
            this.cloneProject(e.item);
            break;
        }
      }
    ```

### Main benefits

More generally, by using enums we ensure that a given data structure is
preserved unaltered and intact throughout the codebase which is very useful when
multiple developers are working with the same classes and components. This
creates a high-level canonical form of a given data structure that may be used
in multiple contexts. This reduces the likelihood of different developers
'reinventing the wheel' and duplicating common data structures, helping to
promote modularity and minimise technical debt.

### Constraints and best practices

There are some important constraints that should be borne in mind when using
enums:

- **Initialise string enums!**
  - String enums must be initialised with a string or a reference to another
    enum. They cannot be tacitly defined as is the case with numeric enums
- **Don't mix data types!**
  - It is technically possible to construct an enum that contains both numbers
    and strings but this should be avoided as it can lead to unintended
    side-effects in the incrementation process. It also kind of undermines the
    purpose of enums. In this scenario, a custom type would probably be more
    appropriate.
- **Strings only as keys**
  - In contrast to a `Map` or object an enum cannot have a numeric key. It must
    always be a string.


title: Primes_and_composites
last_modified: 2024-10-18 19:19:17
body:
# Prime and composite numbers

Definition of a **prime number**:

> For any whole number $n$ where $n \neq 1$, $n$ is prime if and only if its
> sole [factors](Factors%20and%20divisors.md) are $1$ and $n$

Definition of a **composite number**:

> For any whole number $n$, $n$ is composite just if $n$ is not prime


title: Modules_in_NodeJS
last_modified: 2024-10-18 18:55:56
body:
# Modules

> Modules are partitioned files where we define our variables and functions.
> Values defined in modules are scoped to that specific module, constituting a
> unique name space. This avoids name clashes in large programs.

- Every file in a Node application is considered a module.

- The variables and methods in a module are equivalent to `private` properties
  and methods in object-oriented programming.

- If you wish to use a function or variable defined in a module outside of its
  modular container you need to explicitly export it and make it public.

## Structure of a module

Node keeps an internal record of the properties of a module. To see this we can
log the property `module` to the console.

```js
// index.js
console.log(module);
```

This gives us:

```plaintext
Module {
 id: '.',
 path: '/home/thomas/repos/node-learning',
 exports: {},
 filename: '/home/thomas/repos/node-learning/index.js',
 loaded: false,
 children: [],
 paths: [
   '/home/thomas/repos/node-learning/node_modules',
   '/home/thomas/repos/node_modules',
   '/home/thomas/node_modules',
   '/home/node_modules',
   '/node_modules'
 ]
}
```

## Exports

- Whenever we export a property or method from a module we are directly
  targeting the `exports` property of the module object.
- Once we add exports to a file they will be displayed under that property of
  the module object.
- We can export the entire module itself as the export (typically used when the
  module is a single function or class) or individual properties.

### Exporting a whole module

_The example below is a module file that consists in a single function_

```js
module.exports = function (...params) {
  // function body
};
```

Note the module is unnamed. We would name it when we import:

```js
const myFunction = require("./filenme");
```

### Exporting sub-components from a module

In the example below we export a variable and function from the same module.
Note only those values prefixed with `exports` are exported.

```js
exports.myFunc = (...params) => {
  // function bod[]()y
};

exports.aVar = 321.3;

var nonExportedVar = true;
```

This time the exports are already name so we would import with the following:

```js
const { myFunc, aVar } = require("./filename");
```

We can also do the exporting at the bottom when the individual components are
named:

```js
const myNamedFunc = (val) => {
  return val + 1;
};

function anotherNamedFunc(val) {
  return val * 2;
}

// This time we export at the bottom
exports.myNamedFunc = myNamedFunc;
exports.differentName = anotherNamedFunc; // We can use different names

// Or we could export them together
module.exports = { myNamedFunc, anotherNamedFunc };
```

The import is the same:

```js
const { myNamedFunc, anotherNamedFunc } = require("./modules/multiExports");
```

## Structuring modules

The techniques above are useful to know but generally you would want to enforce
a stricter structure than a mix of exported and private values in the one file.
The best way to do this is with a single default export.

Here the thing exported could be a composite function or an object that
basically acts like a class with methods and properties.

_Export a composite single function_

```js

module.exports = () => {
	foo() {...}
	bar() {...}
}
```

_Export an object_

```js

module.exports = {
	foo : () => {...},
    bar: () => {...}
}
```

**Both of these structures would be referred to in the same way when importing
and using them.**

Or you could export an actual class as the default. This is practically the same
as the two above other than that you would have to use `new` to initiate an
instance of the class.

```js
export default class {
  foo() {}
  bar() {}
}
```

Every method and property within the export will be public by default, whether
it is an object, class or function. If you wanted to keep certain
methods/properties private, the best approach is to define them as variables and
functions within the module file but outside of the `export` block.


title: Python_application_structure
last_modified: 2024-10-18 18:55:56
body:
# Python application structure

## Directory layout

```
src/
    cli.py OR app.py
    constants.py
    __init__.py
tests/
requirements.txt
setup.py
```

## `requirements.txt`

Dependencies used within the venv for the project, e.g:

```
google-auth==1.35.0
google-auth-httplib2==0.1.0
google-auth-oauthlib==0.4.6
google-api-python-client==2.26.1
gspread==3.7.0
requests==2.25.1
oauth2client==4.1.3
```

## `setup.py`

The declaration file for the application, equivalent to `package.json`, e.g:

```py
from setuptools import setup, find_packages

setup(
    name="eolas-db",
    version="0.1",
    packages=find_packages(where="src"),
    package_dir={"": "src"},
    install_requires=[
        # List your project dependencies here
    ],
    entry_points={
        "console_scripts": [
            "eolas-db=cli:main",
        ],
    },
)
```

The `entry_points` key is important. This is the first execution point of the
application. Typically you would name your entry-point script as `app.py` or
`cli.py` and then specify its `main` function.

E.g. in `cli.py`:

```py
def main():
    print('run the application')

if __name__ == "__main__":
    main()

```

## `__init__.py`

See
[[Python_modules_and_imports#distinguishing-_modules_-from-_packages_]]|Python
modules]]


title: Daemons
last_modified: 2024-10-18 18:55:56
body:
# Daemons

A programme that runs in the background, performing a system function. A daemon
typically starts at boot or login without having to be specifically launched and
keeps running permanently or until manually stopped.

The custom is for the process name of daemons to end in the letter `d`.

> In the original Greek _daemon_ is translated as 'helper', it doesn't have the
> medieval connotation of a dark spirit but was used to denote the actions of a
> god rather than the god itself. A person could have a personal _daemon_ which
> determines their fate.


title: Format_specifiers_in_C
last_modified: 2024-10-18 18:55:56
body:
# Format specifiers in C

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

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


title: Functions
last_modified: 2024-10-18 18:55:56
body:
# 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>
```


title: User_management_on_AWS
last_modified: 2024-10-18 18:55:56
body:
# User management and roles

## IAM

> Identity and Access Management

An IAM user is an identity with long-term credentials that is used to interact
with AWS in an account. An IAM login is different from a root login. The root
user is the one who creates the AWS account and who has the power to delete it.
The root user can create IAM logins so that other users can log in through
dedicated IAM portals. The root user should also create an IAM login for himself
and use that, set to administrator policies, for most activities, reserving root
for the most consequential stuff like adding users etc.

## Users and user groups

- Use to create users and access rights accross the AWS services
- Users get permissions through policies (use template or create your own)
- You can also create user groups and add users to them to simplify role
  attribution

## Roles

Roles are adopted by _services_ rather than users. They run either periodically
(from a `cronjob` say) or when they are hit from an API.

This allows you to assign policies to a role that a service can adopt when
running. A good example is a lambda. Lambdas often need access to other services
such as an S3 bucket or a DynamoDB table. Say you have a service that combines a
lambda with a DynamoDB database. You could assign a role to the lambda and it
would have access to the database.

## Distinction between _principal_ and _identity_

Both “principal” and “identity” refer to entities that can perform actions and
interact with AWS resources. However, there is a subtle difference in their
usage:

> a principal is a specific type of entity that can take actions in AWS, while
> an identity is the unique identifier associated with that principal.

1. Principal: In the context of IAM policies, a principal represents the entity
   that is allowed or denied access to AWS rThe principal is specified in the
   policy statement as the entity to which the permissions are granted or
   deniedesources. It can be an IAM user, an IAM role, an AWS service. The
   principal is specified in the policy statement as the entity to which the
   permissions are granted or denied.
2. Identity: An identity, on the other hand, is a broader term that encompasses
   both the principal and the authentication credentials associated with that
   principal. It refers to the entity’s unique identifier, such as an IAM user’s
   username or an IAM role’s ARN (Amazon Resource Name). An identity is used for
   authentication purposes to verify the entity’s identity and determine its
   permissions.

## Cognito

> Amazon Cognito provides authentication, authorization, and user management for
> your web and mobile apps.

There are **user pools** and **identity pools**.

User pools provide sign-up and sign-in options for your app users. Identity
pools provide AWS credentials to grant your users access to other AWS services.

You would use identity pools to manage access to API endpoints or S3 buckets.
They can be authenticated or unauthenticated. For example if you want to use an
image from S3 in a website you would authenticate for that, but you would
explicitly have to specify that it is general access by applying the policy
`AmazonS3ReadOnlyAccess`.


title: Arrays
last_modified: 2024-10-18 18:55:56
body:
# Arrays

> Arrays are used both on their own and to implement many other data structures
> that place additional restrictions on how the data is manipulated.

## Algorithmic complexity

In terms of data retrieval arrays have a good runtime since retrieving an
element or storing an element takes constant time. Also an entire array takes up
O(n) space.

This only applies in the case of strict arrays that exist in strictly typed and
more advanced languages such as Java and C++. In JavaScript an array is really
closer to a list in other languages. That is to say, it's size (and of course
the types it holds) does not need to be known in advance or specified at all. In
stricter languages, you would cast the type for the array and specify its
length, for example:

```cpp
int anArray[10] = { 1, 2, 3, 4,  5, 6, 7, 8, 9, 10 }
```

Whilst storage and retrieval take place in constant time, deleting and inserting
elements through methods like `pop` and `shift` are more time consuming, since
the index of every item has to be updated in response.

## Random access

As we can access any element of the array at any time through index notation,
arrays offer random and non-sequential access. This is in contrast to stacks and
queues (where only the top/bottom can be accessed) and linked lists.


title: Listing_and_navigating_directories_in_the_Shell
last_modified: 2024-10-18 19:16:18
body:
# Listing and navigating directories in the 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


title: Binary_encoding
last_modified: 2024-10-18 18:55:56
body:
# Binary encoding

We know that everything going on in a computer is the manipulation of binary
digits. Thus all data must ultimately reduce to binary numbers manipulated
through logic circuits.

_Encoding_ is the process of establishing a correspondence between sets of
binary numbers and sets of symbols that are representative of a given type of
data. For certain fundamental data types, for example alphanumeric characters
and colours, there are agreed standards of encoding such that, for example, that
`111111` (binary) and `3F` (hex) always corresponds to the character `?`. The
reverse is obviously _decoding_: deriving the data/ symbol from the binary
format.

> An encoding system maps each symbol to a unique sequence of bits. A computer
> then interprets that sequence of bits and displays the apppropriate symbol to
> the user.

The length of the binary number (sometimes called the _word length_)
corresponding to 8-bit, 16-bit, 32-bit etc. that is used to represent a given
data set is determined by the number of variations that you require to capture
the entire range of the dataset:

> An $n$-bit binary system can encode $2^n$ different things

For example, say we know that there are 18 levels to a computer game. To encode
a reference for each level we would need a binary number that is capable of at
least 18 total variations. In this instance a 32-bit ($2^{5}$ ) number would be
best because the next smallest (16-bit) would not be sufficient. Our levels
would have representations as follows:

```
00001 (1)
00010 (2)
00011 (3)
00100 (4)
...
```

## Related points

Think about when you open a file format in a text editor that cannot decode it.
For example trying to open a Word document in VSCode. The mangled letters it
displays is the encoded binary data. When you open the file in Word, the
decoding is applied and it resembles what you would expect.

When we save a file, the different file extensions denote different formats and
these are encoding formats. For example if you save an image file as `.png`
rather than `.jpg`, you are applying a different encoding algorithm to the data
that compresses the raw binary data in a different way.


title: Stack_memory
last_modified: 2024-10-18 18:55:56
body:
# Stack memory

## Summary

- Along with [[Heap_memory|heap memory]] heap memory, programs make use of
  _stack memory_ during runtime.

- Best suited for small-sized singular values that are temporary/ephemeral
  rather than large or complex data types that need to persist for a longer
  time.

- Specifically used to keep track of memory used during function executions
  within a given scope: control flow, local variables, returned value.

- Uses a LIFO data-structure

Similarly to the [[Stacks|stack data structure]], it works on the basis of LIFO:
the last item put on the stack is the first item to be withdrawn.

Note, this doesn't mean that the data at the memory addresses that comprise the
stack can _only_ be accessed in a LIFO manner. Any item currently on the stack
can be accessed (be read or modified) at any time. LIFO applies when it is is
time to clear memory from the stack: the most recent addition is cleared first
from the top down.

### Stack pointer

A **stack pointer** is used to manage the items in the stack. This value is
stored in a [[CPU_architecture#registers|register]] and its value is the memory
address of the item currently on the top of the stack. Note: _not_ the value
itself, the _address_ of the value.

When a new value is added to the top of the stack, the pointer's value is
adjusted to increase the size of the stack and make room for the new value. So
this will be a new memory address for the latest item. When an item is removed,
the reverse happens. The previously top address is cleared and the pointer
points to the new top item.

## Threads

For every thread that a program spawns, it gets its own stack or "stack frame".
Stack memory within the thread can only be accessed by processes within that
thread which is what makes it well suited for managing scoped function memory.

### Role in program execution

1. Function call management: a stack records the key information pertaining to
   the execution of a function: parameters, local variables, the location of the
   address for the returned value.

2. Local variable storage: storing the variables local to a function's scope and
   removing them once the function returns, avoiding
   [[Memory_leaks|memory leaks]]

3. Control flow: record from where a function is called and where its return
   value should be stored, meaning the right value is always returned no matter
   how nested it is.

4. Recursion: individuating each call of a function that calls itself and its
   given step to avoid infinite loops


title: Lambda_programming_model
last_modified: 2024-10-18 18:55:56
body:
# 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.


title: Increasing_fractions_to_their_highest_terms
last_modified: 2024-10-18 19:15:04
body:
# Increasing fractions to their highest terms

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

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

## Demonstration

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

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

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

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

## Increasing fractions with variables to higher terms

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

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

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


title: OpenSearch
last_modified: 2024-10-18 18:55:56
body:
# OpenSearch

## Background

OpenSearch is the AWS implementation of the opean source Elasticsearch search
engine. It was formally known as "AWS Elasticsearch".

It has many features but the core usage is to create searchable indicies of a
given content domain such as, for example, a website or content management
system. This enables the quick search and retrieval of documents without using
expensive database queries.

## Key concepts

We will introduce the main concepts with the example of an internal intranet for
which we want to create a searchable index. The intranet comprises hundreds of
pages. Each page has the following metadata, conforming to the following
example.

```json
{
  "title": "Internal News",
  "author": "Jane Doe",
  "published_date": "2023-11-01T00:00:00Z",
  "tags": ["news", "internal"],
  "categories": ["communication"],
  "content": "Today's internal news and updates are..."
}
```

### Create domain

The OpenSearch domain is a managed environment which hosts OpenSearch
**clusters**. It can contain one or more clusters.

The domain provides network **endpoints** you use to communicate and send
requests. Typical requests:

- ingest data
- index data
- run search query and return matches

#### Clusters and nodes

A cluster is the highest level of organisation within an OpensSearch domain that
contains your indexed data. It processes all the search queries and handles
tasks like indexing, searching, and managing documents.

![](/img/opensearch-architecture.drawio.svg)

A cluster comprises **nodes**. Nodes are individual servers that hold part of
the cluster's data. Each node participates in the indexing and searching of the
cluster's data.

- This distributed architecture helps in balancing the load and ensuring high
  availability.
- Data can be replicated accross nodes, making the system resiliant against data
  loss
- You can add more nodes to the cluster to handle increased data and traffic,
  making the system adaptable to changing needs.

### Define index and mappings

Assuming the domain has been created. The next step is to create an index for
the data, say `intranet_pages`. An index is basically a collection where the
data is stored, similar to a database. Each entry is a **document** in this
collection.

Our index will store each webpage as a document.

We specify the data that we want to store using an index mapping. For instance
we may not want to store all the metadata for each page, preferring only to
index a subset of the properties. In this example we will store all the data. We
would achieve this with the following:

```json
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "author": { "type": "keyword" },
      "published_date": { "type": "date" },
      "tags": { "type": "keyword" },
      "content": { "type": "text" }
    }
  }
}
```

The mapping will be utilised in the following scenarios:

- storing data:
  - when a new document is added to the `intranet_pages` index, it will adhere
    to the defined mappings
  - it will have to have the properties specified in the mapping in order to be
    added
- searching data:
  - when executing searches, OpenSearch utilizes the mappings to understand the
    type of data in each field and optimizes search queries accordingly
- assessing relevance:
  - proper mappings allow OpenSearch to accurately score and rank search results
    based on relevance.

### Ingest data (bulk import and/or scraper)

In order to create the index that we previously defined with a mapping it is
necessary to implement some sort of mechanism for collating the metadata that
matches the mapping. This would be a crawler or scraper that might be
implemented with a lambda. This is a key part of the ingestion process.

Alternatively the data could be bulk imported in a format that maps to the
index.

### Querying and searching

Having established the crawler (and some kind of search interface), we can now
run queries against the OpenSearch domain.

A basic example of the structure of a query would be as follows:

```sh
GET /intranet_pages/_search
```

```json
{
  "query": {
    "match": {
      "content": "project"
    }
  }
}
```

Here we search against the `content` mapping to find pages that contain the word
"project".

Example of the data returned:

```json
{
  "hits": {
    "total": { "value": 100 },
    "hits": [
      {
        "_index": "intranet_pages",
        "_id": "1",
        "_score": 1.2,
        "_source": {
          "title": "Project ABC Launch",
          "author": "John Doe",
          "published_date": "2023-01-01T00:00:00Z",
          "tags": ["project", "launch"],
          "content": "Details about the launch of Project ABC..."
        }
      }
      // Additional results here
    ]
  }
}
```

## Search patterns

Below are further examples of commonly used search patterns.

### Multiple conditions

Find documents that are authored by Jane Doe that contain the word "meeting".
`must` stands for boolean AND:

```json
{
  "query": {
    "bool": {
      "must": [
        { "match": { "content": "meeting" } },
        { "match": { "author": "Jane Doe" } }
      ]
    }
  }
}
```

Find documents that contain either the word "meeting" or the word "project" in
their content. `should` stands for boolean OR:

```json
{
  "query": {
    "bool": {
      "should": [
        { "match": { "content": "meeting" } },
        { "match": { "content": "project" } }
      ],
      "minimum_should_match": 1
    }
  }
}
```

`minimum_should_match` specifies the number of conditions that should match.

### Query by date ranges

Find pages published after a certain date:

```json
{
  "query": {
    "range": {
      "published_date": {
        "gte": "2023-01-01T00:00:00Z"
      }
    }
  }
}
```

```json
{
  "query": {
    "bool": {
      "should": [
        { "match": { "fileId": "val" } },
        { "match": { "programmeId": "val" } },
        { "match": { "guid": "val" } }
      ],
      "minimum_should_match": 1
    }
  }
}
```


title: File_operations_in_Python
last_modified: 2024-11-03 14:55:44
body:
# File operations in Python

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

// Add directory CRUD operations in Python

## Renaming files (moving)

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

## Deleting files

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

## Check file exists

```python
import os

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

```


title: Commutative_Property_of_Addition_and_Multiplication
last_modified: 2024-11-04 14:25:25
body:
# Commutative Property of Addition and Multiplication

**Let $a$, $b$ represent members of $\mathbb{W}$ or $\mathbb{Z}$ then:**

$$ a + b = b + a $$

$$ a \cdot b = b \cdot a $$


title: Associative_Property_of_Addition_and_Multiplication
last_modified: 2024-11-04 14:20:39
body:
# Associative Property of Addition and Multiplication

**Let $a$, $b$ , $c$ represent members of $\mathbb{W}$ or $\mathbb{Z}$ then:**

$$ (a + b) + c = a + (b + c) $$

$$ a \cdot (b \cdot c) = (a \cdot b) \cdot c $$

When grouping symbols (parentheses, brackets, braces) are used with the
multiplication and addition of whole numbers and integers, the particular
placement of the grouping symbols relative to each of the addends or
multiplicands does not change the sum/product.


title: Fetch_decode_execute
last_modified: 2024-10-28 13:23:37
body:
# Fetch, decode, execute, store

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

## Fetch

### Overview

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

### Specifics

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

## Decode

### Overview

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

### Specifics

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

## Execute

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

## Store

### Overview

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

### Specifics

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


title: Dates_in_Python
last_modified: 2024-10-18 18:55:56
body:
# Dates in Python

Python's built-in `datetime` module provides various classes for manipulating
dates and times.

### Importing `datetime`

```python
import datetime
```

### Getting Current Date and Time

Get the current date and time using `datetime.datetime.now()`.

```python
current_datetime = datetime.datetime.now()
print("Current datetime:", current_datetime)
```

### Creating Date Objects

To create a date object, use `datetime.date`, specifying the year, month, and
day.

```python
some_date = datetime.date(2021, 9, 30)
print("Some date:", some_date)
```

### Creating Time Objects

To create a time object, use `datetime.time`, specifying the hour, minute,
second, and optionally microsecond.

```python
some_time = datetime.time(13, 24, 56)
print("Some time:", some_time)
```

### Creating Datetime Objects

To create a datetime object, use `datetime.datetime`.

```python
some_datetime = datetime.datetime(2021, 9, 30, 13, 24, 56)
print("Some datetime:", some_datetime)
```

### Extracting components

Extract components from a datetime object:

```python
print("Year:", some_datetime.year)
print("Month:", some_datetime.month)
print("Day:", some_datetime.day)
print("Hour:", some_datetime.hour)
print("Minute:", some_datetime.minute)
print("Second:", some_datetime.second)
```

### Formatting `datetime` objects: `strftime`

The `strftime` method (_string format time_) converts a datetime object to a
string according to the specified format.

This would typically be used when we have been working with a computer-friendly
format of a date such as unix seconds which we then want to output in a more
readable format.

Use `strftime` to express the current date as YYYY-MM:

```python
now = datetime.now()
formatted = now.strftime('%Y-%m')
print(formatted)
# 2024-06
```

Express the current date as YYYY-MM-DD H:M:S:

```python
formatted_datetime = some_datetime.strftime('%Y-%m-%d %H:%M:%S')
print("Formatted datetime:", formatted_datetime)
```

Express a [unix timestamp](./Time_and_computers.md) as DD-MM-YYYY:

```py
def convert_timestamp(timestamp):
    date_object = datetime.datetime.fromtimestamp(timestamp)
    formatted_date = date_object.strftime("%d-%m-%Y")
    return formatted_date

converted = convert_timestamp(1689023491)
print(converted)
# 10-07-2023
```

### Parsing `datetime` strings: `strptime`.

The `strptime` (_string_parse_time) method_ parses a string representing a date
and/or a time according to a specified format and returns a `datetime` object.

Typically be used when want to carry out some sort of transformation on time
data that we are sourcing in a particular format.

Receive a date in the format DD-MM-YYYY and convert to datetime object:

```python
date_string = "18-06-2024"
datetime_object = datetime.strptime(date_string, '%d-%m-%Y')
print(date_object) # 2024-06-18 00:00:00
```

Extract the year from a UTC date format:

```python
datetime_obj = datetime.strptime("2024-03-27T10:44:28Z", "%Y%m%dT%H%M%SZ")
print(datetime_obj.strftime('%Y'))
# 2024

```


title: Creating_a_RESTful_API_GET
last_modified: 2024-10-18 18:55:56
body:
# Creating a RESTful API: `GET` requests

With our GET request we will simply return the array of course objects.

We create an [event emitter](Events%20module.md#event-emitters) and listener
that listens for GET requests on a specified port and sends data in response to
requests.

```js
// Return a value as response from specified URI
router.get("/", (req, res) => {
  res.send(courses);
});
```

Our server is now set up:

![](/img/server-listening.png)

> When creating our API this structure of creating handlers for specific routes
> will be iterated. Every endpoint will be specified with
> `[app].[http_request_type]` and followed by a callback.

We can now call the endpoint:

```js
const getAllCourses = async () => {
  try {
    const resp = await axios.get("http://localhost:3000/api/courses");
    console.log(resp.data);
  } catch (err) {
    console.error(err);
  }
};

getAllCourses();
```

Returns:

```js
[
  { id: 1, name: "First course" },
  { id: 2, name: "Second course" },
  { id: 3, name: "Third course" },
];
```

## Parameters

The previous example serves the entire set of our data. But we will also need to
retrieve specific values, we do this by adapting the GET callback to accept
parameters. These parameters will correspond to the specific entry in our main
data array.

```js
router.get("/:id", (req, res) => {
  res.send(req.params.id);
});
```

We use the `:` symbol in the URI to indicate that we looking to parse for a
specific value in the data. Now if we call `/api/courses/2`, we will get the
second item in the array.

The block above is the most basic format but we would want to add some kind of
error handling, for example:

```js
router.get("/:id", (req, res) => {
  const course = courses.find((c) => c.id === parseInt(req.params.id));
  if (!course) res.status(404).send("A course with the given ID was not found");
  res.send(course);
});
```

## Queries

Whereas parameters return specific data points, queries don't get data they
aggregate or present the data that is returned in a certain way, such as for
instance applying a search function. We indicate queries with a `?` in our URI.
For example: `/api/posts/2018/1?sortBy=name`. To facilitate a request like this
we have to create a GET path that allows for it:

```js
router.get("/:year/:month", (req, res) => {
  res.send(req.query);
})[]();
```

We would get the following back:

```json
{
  "sortBy": "name"
}
```

Again a JSON object with key-value pairs is returned.


title: Typing_built_in_React_hooks
last_modified: 2024-11-04 14:24:05
body:
# Typing built-in React hooks

## `useState`

```tsx
const [amount, setAmount] = useState<number | string>(3);
```

### With a ustom type

```tsx
interface IState {
  people: IPerson[];
}

interface IPerson {
  name: string;
  age: number;
}

const [people, setPeople] = useState<IState>({});

// Alternative declaration

interface IState {
  people: {
    name: string;
    age: number;
  }[];
}

const [people, setPeople] = useState<IState["people"]>({});
```

## `useReducer`

Mostly the same. They key thing to type is the reducer function itself. You
don't need to type the intitial state unless it is a pre-existing type, you can
just use type assertion:

```jsx
interface IAction {
  | { type: "increment"; payload: number }
  | { type: "decrement"; payload: string };
}

const initialState = { count: 0 };

function reducer(state: typeof initialState, action: IAction) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}
```

Then the syntax is as normal, i.e:

```js
const [state, dispatch] = useReducer(reducer, initialState);
```

## `useEffect`

> Limited potential for typing here as this hook does not return a value. See if
> there is useful inferred typing from use and update.

## `useContext`

We can use generics but mostly this is untyped:

```jsx
type Theme = "light" | "dark";
const ThemeContext = createContext < Theme > "dark";

const App = () => (
  <ThemeContext.Provider value="dark">
    <MyComponent />
  </ThemeContext.Provider>
);

const MyComponent = () => {
  const theme = useContext(ThemeContext);
  return <div>The theme is {theme}</div>;
};
```

## `useCallback` / `useMemo`

You must only type the parameters that are passed to the callback, the return
value will be inferred.


title: Functions_in_Python
last_modified: 2024-10-18 18:55:56
body:
# 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.


title: 653c0b1d_host_protocols_of_ARPANET
last_modified: 2024-10-25 09:01:54
body:
# 653c0b1d_host_protocols_of_ARPANET

The responsibility for enabling communication between nodes fell to the hosts,
not the [IMPs](e470bf3d_IMPs_in_the_ARPANET.md). At the lowest level (equivalent
to the [link layer](Link_Layer_of_Internet_Protocol.md) in the subsequent
internet?), this consisted in moving raw bits between hosts regardless of what
specific type of data they encoded.

In the book (_Where Wizards Stay Up Late_), there is a great description by
analogy:

> Some water out of the tap is used for making coffee, some for washing dishes,
> some for bathing. The pipe and the faucet do not care about this; they convey
> the water regardless.


title: Working_with_directories_in_Python
last_modified: 2024-11-13 19:32:16
body:
# Working with directories in Python

## List files in a directory

```py
# Here we list the file exts of files in a dir
import os
from pathlib import Path

with os.scandir("/a/directory") as dir:
    for entry in dir:
        print(Path(entry).suffix)
```

## List files without extension

```python

file = "/some/file/with/markdown.md"
return os.path.splitext(os.path.basename(file))[0]
# markdown
```

## Create a directory

```py
import os

os.makedirs("/name/of/directory")
```

### Check if directory exists, remove if so

```py
import os
import shutil

if os.path.isdir("/name/of/directory")
    shutil.rmtree(target_dir)
```

## Copy a nested directory from location to another

```py
import shutil

shutil.copytree("/source/dir", "/target/dir", dir_exist_ok=True)
```


title: Messaging_services
last_modified: 2024-10-18 18:55:56
body:
# AWS Messaging services

## SQS

> SQS: Simple Queue Service

SQS is a service that allows you to send, store and receive messages between
apps and software components built in AWS, with automatic encryption. It helps
with decoupling and scaling.

As the name indicates, its operating mode is that of a
[queue](Queue.md) data structure offering first-in, first-out
and other queue implementations.

An example application of this would be to set up an SQS queue that receives
messages and triggers a lambda whenever a new message is added.

## SNS

> SNS: Simple Notification Service

Similar to SQS but the focus is on notifications rather than messages, i.e
events that fire when something specific happens, not just a message-send event.
It can be used for passing notifications between applications or to persons
through SMS, text, push notifications and email.

**SNS pushes messages out to subscribers while SQS stores the messages until
someone reads them.**


title: Delete_records_in_an_SQL_table
last_modified: 2024-10-18 18:55:56
body:
# Deleting data in SQL

## Deleting records

We use `DELETE` to delete existing records in a table. Once this is applied, the
records are permanently deleted.

To delete **all records** whilst maintaining the table:

```sql
DELETE FROM sales
```

We can also use `TRUNCATE` to achieve this. It is faster but it doesn't keep a
log of each deletion:

```sql
TRUNCATE TABLE sales
```

To delete a **specifc entry** by field:

```sql
DELETE FROM sales WHERE sale_id = 1;
```

## Deleting tables

We use `DROP` to remove an entire table. This will delete the table definition,
and all the data, indexes, triggers, constraints and permission specifications
for that table.

```sql
DROP TABLE sales
```


title: Regular_expressions_in_SQL
last_modified: 2024-10-18 18:55:56
body:
# Regular expressions in SQL

```sql
SELECT name FROM country WHERE name RLIKE '[xy][ai]' ORDER BY name;

Guyana
Mexico
Myanmar
```


title: Secrets_or_env_vars_in_AWS
last_modified: 2024-10-27 11:21:46
body:
# Secrets or environment variables?

Choosing between environment variables and AWS Secrets Manager hinges on factors
such as use case, security needs, and the data type. Environment variables are
easier to use, free, and offer fast access but are less secure and lack features
like versioning. On the other hand, AWS Secrets Manager provides robust security
through encryption, access control, audit trails, and supports secret versioning
and rotation. However, it comes with additional costs and can introduce latency
and rate limits.

General recommendations suggest using AWS Secrets Manager for storing sensitive
data like API keys and passwords, due to its enhanced security features. For
less sensitive, non-critical data such as feature flags or environment-specific
URLs, environment variables are often adequate. Cost-conscious users who don't
need advanced features may find environment variables more economical.
Ultimately, the best choice depends on your specific requirements and
limitations.


title: Reducing_fractions
last_modified: 2024-10-18 19:20:03
body:
# Reducing fractions to their lowest terms

> A fraction is said to be _reduced to its lowest terms_ if the
> [greatest common divisor](Factors%20and%20divisors.md#greatest-common-divisor)
> of the numerator and the denominator is $1$.

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

Thus the fraction $\frac{2}{3}$ is reduced to its lowest terms because the
greatest common divisor is 1. Neither the numerator or the denominator can be
reduced to any lower terms. In contrast, the fraction $\frac{4}{6}$ is not
reduced to its lowest terms because the greatest common divisor of both 4 and 6
is 2, not 1.

### 1. Reducing with repeated application of divisors

The following demonstrates the process of reducing a fraction to its lowest
terms in a series of steps:

$$
\\frac{18}{24} = \frac{18/2}{24/2} = \frac{9}{12} = \frac{9/3}{12/3} = \frac{3}{4}
$$

\_Once we get to $\frac{3}{4}$ the greatest common divisor is 1, therefore
$\frac{18}{24}$ has been reduced to its lowest terms \_.

### 2. Reducing in one step with the highest common divisor

In the previous example the reduction took two steps: first we divided by two
and then we divided by three. There is a more efficient way: find the
[highest common divisor](Factors%20and%20divisors.md#greatest-common-divisor) of
the numerator and denominator and then use this as the basis for the reduction.
With this method, the reduction can be completed in a single step.

The greatest common divisor of 18 and 24 is 6, thus:

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

Note how our earlier two divisors 2 and 3 are
[factors](Factors%20and%20divisors.md#factors) of 6, showing the consistency
between the two methods.

### 3. Reducing with factors and cancellation

The two methods above are not very systematic and are quite heuristic. The third
approach is more systematic and relies on the
[interchangeability of factors and divisors](Factors%20and%20divisors.md).

Instead of thinking asking what is the greatest common divisor of 18 and 24 we
could ask: which single number can we multiply by to get 18 and 24? Obviously
both numbers are in the six times table. This is therefore to say that 6 is a
[factor](Factors%20and%20divisors.md#factors) of both: we can multiply some
number by 6 to arrive at both 18 and 24. The numbers are 3 and 4 respectively:

$$
\\begin{split}
3 \cdot 6 = 18 \\
4 \cdot 6  = 24
\\end{split}
$$

Here, 3 and 4 are the multiplicands of the factor 6. As $\frac{3}{4}$ doesn't
have a lower common factor, it is therefore defined in its lowest terms.

Once we have reached this point, we no longer need the common factor 6, we can
therefore cancel it out, leaving the multiplicands as the reduced fraction:

$$
\\begin{split}
3  \cancel{\cdot6= 18}\\
4  \cancel{\cdot6= 24}
\\end{split}
$$

### 4. Reducing with prime factorisation

This is still a bit long-winded however particularly when finding the factors of
larger numbers because we have to go through the factors of both numbers to find
the largest held in common.

A better method is to utilise [prime factorization](Prime%20factorization.md)
combined with the canceling technique.

First we find the prime factors of both the numerator and denominator:
![drawio-Page-7.drawio.png](drawio-Page-7.drawio.png)

This gives us:

$$
\\frac{18}{24} = \frac{2 \cdot 3 \cdot 3}{2 \cdot 2 \cdot 2 \cdot 3}
$$

We then cancel out the factors held in common between the numerator and
denominator:

$$
\\frac{\cancel{2} \cdot \cancel{3} \cdot 3}{\cancel{2} \cdot 2 \cdot 2 \cdot \cancel{3}}
$$

This gives us:

$$
\\frac{3}{2 \cdot 2}
$$

We then simplify the fraction as normal to its lowest term (conducting any
multiplications required by what is left from the prime factorization):

$$
\\frac{3}{4}
$$

## Reducing fractions that contain variables

Superficially this looks to be more difficult but in fact we can apply the same
prime factorization method to get the result.

### Demonstration

_Reduce the following fraction to its lowest terms: $$\frac{25a^3b}{40a^2b^3}$$_

The prime factors of the numerator and denominator:

$$
\\begin{split}
25 = {5, 5} \\
40 = {2,2,2,5}
\\end{split}
$$

Now we apply canceling but we include the variable parts, treating them exactly
the same as the coefficients. We break them out of their exponents however.

$$\frac{25a^3b}{40a^2b^3} =\frac{5 \cdot 5 \cdot a \cdot a \cdot a \cdot b}{2 \cdot 2 \cdot 2 \cdot 5 \cdot a \cdot a \cdot b \cdot b \cdot b }$$

Canceled:

$$\frac{\cancel{5} \cdot 5 \cdot  \cancel{a} \cdot \cancel{a} \cdot a \cdot \cancel{b}}{2 \cdot 2 \cdot 2 \cdot \cancel{5} \cdot \cancel{a} \cdot \cancel{a} \cdot \cancel{b} \cdot b \cdot b }$$

Which gives us:

$$
\\frac{5 \cdot a}{2 \cdot 2 \cdot 2 \cdot b \cdot b} = \frac{5a}{8b^2}
$$

## Reducing fractions that contain negative values

_Reduce the following fraction to its lowest terms: $$\frac{14y^5}{-35y^3}$$_

- This fraction is an instance of a
  [fraction with unlike terms](Handling%20negative%20fractions.md#fractions-with-unlike-terms).

- Apply [Prime factorization](Prime%20factorization.md):

  ![draw.io-Page-8.drawio.png](draw.io-Page-8.drawio.png)

- Cancel the coefficients and variable parts

  $$
  \\frac{14y^5}{-35y^3}=\frac{5 \cdot 7 \cdot 2 \cdot y \cdot y \cdot y \cdot y \cdot y}{-5 \cdot 7  \cdot y \cdot y \cdot y} = - \frac{2y^2}{5}
  $$

_Reduce the following fraction to its lowest terms:
$$\frac{- 12xy^2}{ - 18xy^2}$$_

- This fraction is an instance of a
  [fraction with like terms](Handling%20negative%20fractions.md#fractions-with-like-terms).

- Apply [Prime factorization](Prime%20factorization.md):

![draw.io-Page-8.drawio 1.png](draw.io-Page-8.drawio%201.png)

- Cancel the coefficients and variable parts

  $$

  * \\frac{12xy^2}{18xy^2}=\frac{3 \cdot 2 \cdot 2 \cdot x \cdot y \cdot y}{3 \cdot 7  \cdot 3 \cdot 2 \cdot x \cdot x \cdot y} = - \frac{2y}{3x}
  $$


title: The_memory_hierarchy
last_modified: 2024-10-18 18:55:56
body:
# The memory hierarchy

The diagram below compares the different forms of memory within a computing
device in terms of speed, monetary cost and capacity:

![Memory hierarchy diagram](/img/Memory-Hierarchy.jpg)


title: AWS_CLI
last_modified: 2024-10-18 18:55:56
body:
# AWS CLI frequent commands

### Retrieve current user

```
aws-sts get-caller-identity
```

### List users

```
aws configure list
aws configure list-profiles
```

### View profile data

```
vim ./aws/credentials
```

## CloudFormation

### List stacks

```
aws cloudformation --list-stacks
```

### Delete a stack

```
aws cloudformation delete-stack --stack-name <my_stack> --region <region>
```

`region` is optional especially if only deployed to one region

## Billing ("Cost explorer")

### Usage for month

```
 aws ce get-cost-and-usage --time-period Start=$(date +%Y-%m-01),End=$(date -d @$(( $(date +%s) + 86400 )) +%Y-%m-%d) --granularity MONTHLY --metrics "UnblendedCost" "UsageQuantity
```

### Retrieve current user

```
aws-sts get-caller-identity
```

### List users

```
aws configure list
aws configure list-profiles
```

### View profile data

```
vim ./aws/credentials
```


