# entries

title: Hardware_abstraction_and_modularity
last_modified: 2024-10-18 18:55:56
body:
# Hardware abstraction and modularity

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

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

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

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

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

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

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


title: Memory_versus_processor
last_modified: 2024-10-18 19:17:01
body:
# Memory versus processor

> Would a more powerful processor with average or reduced memory capacity
> perform better than, equal to, or less than a less powerful processor with
> more memory, in principle?


title: Elastic_Compute_Cloud
last_modified: 2024-10-18 18:55:56
body:
# Elastic Compute Cloud (EC2)

- Cloud-based or virtual server (virtual machine), basically a hypervisor
  (compare [[zk/Docker_architecture]])
- It is virtual because you are not responsible for its physical implementation
- Still needs considerable configuration compared to a serverless
  [[zk/Lambda_programming_model]]
- Like lambdas has the benefit of easy scaling: you can add compute capacity on
  demand (elasticity)

The following needs to be considered:

- Naming
- Application and OS image (known as "Amazon Machine Image"):
  - Which OS you want to use (Linux distribution, Windows, etc)
  - Applications you want pre-installed
  - Block device mapping
- Instance type an size (basically the type of processor and how powerful you
  want it to be)
- An encrypted key-pair for login
- Your network (typically managed via Amazon VPC (Virtual Private Cloud)) and
  network security
- Storage: size and type (eg. GP3)
- Location and latency

> EC2 is more expensive copared to serverless options. Accordingly it is better
> to run smaller servers (in terms of processor and memory) at higher capacity
> than larger servers under-capacity.


title: Mutations_with_Apollo_Client
last_modified: 2024-10-18 18:55:56
body:
# Mutations with Apollo Client

Queries are read-only operations. Mutations are write-only operations.

Just like the `Query` type, the `Mutation` type serves as an entrypoint to the
schema.

## Naming convention

- Use verb: `add`, `create`, `delete`
- Refer to the datatype

For example `addSpaceCat(){}`

## Demonstration mutation

We are going to create a mutation that increments the `numberOfViews` field on
the `Track` type:

## Updating the schema

```js
type Mutation {
    incrementTrackViews(id: ID!): IncrementTrackViewsResponse!
}


// Define a specific response type that specifically matches our needs
type IncrementTrackViewsResponse {
    code: Int! // status code
    success: Boolean! // whether mutation was successful
    message: String! // what to say if mutation successful
    track: Track   // not nullable because might error
}
```

Based on this schema, the mutation will recieve a `Track` id and increment the
specified `Track`. It will return an object comprising the newly updated `Track`
and a bundle of properties that provide feedback on the status of the
operations: a status code, whether it succeeded, and a message.

## Updating the data source

Remember that our sole data source in the demonstration project is a REST API.
We handle it within GraphQL using Apollos `RESTDataSource` class. We need to add
a method to this class that will increment the track views. We wil use the
`PATCH` REST method:

```js
class TrackAPI extends RESTDataSource {
  constructor() {...}
  getTracksForHome() {...}
  getAuthor(authorId) {...}
  getTrack(trackId){...};

  incrementTrackViews(trackId) {
    return this.patch(`track/${trackId}/numberOfViews`);
  }
}
```

The `patch()` method is procided by the `RESTDataSouce` class that `TrackAPI`
inherits from

## Adding resolver

Next we need a resolver that corresponds to the mutation we have defined in the
schema. We will need to handle successful responses as well as errors.

### Success case

As always we match the shape of the schema:

```js
const resolvers = {
  Query: {
    // ...query resolvers
  },
  Mutation: {
    // increments a track's numberOfViews property
    incrementTrackViews: async (_, { id }, { dataSources }) => {
      const track = await dataSources.trackAPI.incrementTrackViews(id);
      return {
        code: 200,
        success: true,
        message: `Successfully incremented number of views for track ${id}`,
        track,
      };
    },
  },
};
```

There's more going on with this resolver than the previous one. As is standard,
we call the API using the `TrackAPI` class. However we don't just immediately
return this when it executes. This is because the schema specifies that the
return type `IncrementTrackViewsResponse` requires more than just the updated
`Track`. So we wait this and return it with the cluster of metadata about the
mutation response (`code`, `success`, and `message`).

### Error case

We can extend the Mutation resolver to allow for errors. We'll do this by
refactoring the resolver into a `try...catch` block and adding the error
handling in the `catch`.

We'll harness the details that are provided by Apollos' own `err` object which
is returned by the `RESTDataSource` class that our resolver ultimately traces
back to:

```js
const resolvers = {

    Query: {
        // ...query resolvers
    }

    Mutation: {
        incrementTrackViews: async (_, {id}, {dataSources}) => {
            try {
                const track = await dataSources.trackAPI.incrementTrackViews(id);
                return {
                    code: 200,
                    success: true,
                    message: `Successfully incremented number of views for track ${id}`,
                    track
                };
            } catch (err) {
                return {
                    code: err.extensions.response.status,
                    success: false,
                    message: err.extensions.response.body,
                    track: null
                };
            }
        },
    }
}
```

## The `useMutation` hook

We invoke the `useMutation` hook to issue mutations from the client-side.

As with queries and
[query constants](Apollo_Client.md#query-constants) we
wrap our mutation in a `gql` template string:

```js
const INCREMENT_TRACK_VIEWS = gql`
  mutation IncrementTrackViews($incrementTrackViewsId: ID!) {
    incrementTrackViews(id: $incrementTrackViewsId) {
      code
      success
      message
      track {
        id
        numberOfViews
      }
    }
  }
`;
```

We then pass it to the `useMutation` hook including an options object with our
variables. (This time the specific variable is named):

```js
import { gql, useMutation } from "@apollo/client";

useMutation(INCREMENT_TRACK_VIEWS, {
  variables: { incrementTrackViewsId: id },
});
```

`useMutation` returns an array of two elements:

1. The mutation function that actually executes
2. An object comprising (`loading`, `error`, `data`) - this is the same as is
   the return value of `useQuery`.

So we can destructure like so (we don't always need the second element);

```js
const [incrementTrackViews, dataObject] = useMutation(INCREMENT_TRACK_VIEWS...)
```

Given that we can isolate the mutation function as the first destructured
element of the array, we could then attach `incrementTrackViews` to a button or
other frontend interaction.


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: Tags_in_Git
last_modified: 2024-10-18 18:55:56
body:
# Tags in Git

Tags exist to reference specific commits in a more meaningful way than using a
SHA reference. They are a **named reference to a commit**. As they refer to a
singular commit they do not update but are a fixed point in the Git history.

> Tags do not care about branches. They are an objective reference to a commit
> regardless of which branch it occured on. You can reference a tag without
> needing to be in the branch where the tagging originally took place.

Tags are frequently used to mark software release versions. But they can also be
used for other designations such as the addition of a key feature or bug.

## Creating a tag

```
git tag -am "Version 1.0" v1.0 dd5c4539a0
```

`am` is for "annotated + message". The actual tag name is `v1.0`

Now you can refer to that commit with `v1.0` rather than use the SHA

## Deleting a tag

```
git tag -d v1.0
```

> When you delete the tag you are only deleting the tag reference not the commit
> that it refers to.

## Listing tags

```
git tag --list
```

## Examples of use

```
git show v1.1

git diff v1.0..v1.1
```

## Pushing tags to a remote

In the examples so far, all the tags have been **local tags**. They only exist
in our local workflow and are not accessible to our collaborators. If we do a
`git push`, this doesn't transfer our local tags. You have to do:

```
git push origin [tag-name]
```

Push all tags:

```
git push origin --tags

# Delete remote tag

git push -d origin [tag-name]
```

## Getting tags from the remote

```
git fetch
```

This automatically fetches tags too.


title: Passing_arguments_to_Python_scripts
last_modified: 2024-10-18 18:55:56
body:
# Passing arguments to Python scripts

`sys.argv` is a list that contains the command-line arguments passed to a Python
scripts.

- `sys.argv[0]` = the name of script
- `sys.argv[1]` = the first argument
- `sys.argv[2]` = the second argument, and so on

Example invocation:

```sh
python3 ./my_script.py argument_one argument_two

```

```python
import sys

print(sys.argv[0])
print(sys.arg)
print(sys.argv[1])

# my_script.py
# ['my_script.py', 'argyment_one', 'argument_two']
# argument_one

```


title: Key_characteristics_of_GraphQL
last_modified: 2024-10-18 18:55:56
body:
# Key characteristics of GraphQL

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

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

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

## Architecture

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

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

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

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

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

## Implementation overview

### Queries, mutations, subscriptions

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

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

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

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

### Frontend

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

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

### Backend: structure and behaviour

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

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

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

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

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

### GraphQL endpoint

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

## Benefits

### Single requests

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

The REST scenario:

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

The GraphQL scenario:

![](/img/graphQL_request_load.png)

### Abstraction of multiple services

![](/img/graphql_multiple_resources.png)

### Stops overfetching

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

### Single endpoint

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


title: 0716531c_rewilding_the_internet
last_modified: 2024-11-19 17:43:28
body:
# 0716531c_rewilding_the_internet

The following
[analogy](https://www.noemamag.com/we-need-to-rewild-the-internet/) can be
useful in understanding how the internet has become such a negative environment.
How it has transmogrified from its original promise of a humanistic space that
would facilitate the connection of individuals based on shared interests and
values to an environment that is entirely mediated by capital, subject to
control, censorship and the violation of privacy.

In the 18th-century in the northern German states, officials decided to
rationalise the production of timber to enable more systematic extraction.
(Clearly a product of Enlightenment principles of applying reason and science to
agriculture similar to Jethro Tull's invention of the seed drill and the
suspension of three-field rotation to four-field all-round harvesting.)

Instead of a sprawling forest ecosystem that had developed naturally over
hundreds of thousands of years, which allowed for felling without upsetting the
continuance of the system, the forests were stripped and trees were planted in
rows and grids with the arrangement designed to maximise yield and access. This
would be broadly analogous to the factory-farming of chickens compared to
free-range rearing.

For the first cycle it appeared as if this restructuring was a huge success.
Yields were high and the timber was of excellent quality. This was a false dawn,
however. The subsequent yields were a complete failure compared to the past.
Fewer trees grew and those that did had a reduced yield and were subject to
disease. The overall health of the forests declined precipitously.

What explains the initial success? It was made possible by the preceding
centuries of free-range growth and husbandry. The seeming success of the new
system was a product of the generations of investment in the natural ecosystem.
This was exhausted in one cycle of the new regime.

Taking this back to the Web, the analogy is that the web was initially a natural
ecosystem of different communities and technologies developing organically out
of the original open and decentralised protocols. From this diversity emerged a
formidably rich informational space.

Since then, the Web has become highly centralised and homogenised with a tiny
cadre of corporations determining how individuals communicate, share and access
information (Google for search, Android or Apple for phone, Twitter or Facebook
etc.).

The initial fruits of this centralisation (just like the forest 'optimisation')
appeared to be beneficial in the initial 'heyday' of the 2010s when social-media
was new and full of prospect (the political benefits of Twitter and enriched
public sphere, the 'Arab Spring', WikiLeaks etc). But this actually proved to be
a nadir. The vitality on display was a product of the preceding era of the
natural growth of the Web ecosystem.

This decline in quality should be apparent to any reflective user:

- the saturation of advertising to the point where many websites are unusable
- the confection of low-quality content to game 'organic' search rankings
- the surveillance of users across sites and devices to enable the extraction of
  profitable marketing data
- the censorship of political content for the 'violation' of bogus terms of use
  on social media platforms if it does not the serve the broader interests of
  capital and western imperialism
- the saturation of pornography and dehumanising media more generally

Thbs has also involved the reshaping of Internet users themselves, from active
and independent _agents_, to drone-like passive _consumers_ of drivel engineered
to maximise 'engagement' (the monopolisation of attention), where users are like
the viewers of _Infinite Jest_. See, e.g TikTok and its clones.

There are many further points that can be made and ideas that can be
extrapolated from this. Two examples:

- the emergence of supposed 'AI' is going to lead to greater content
  homogenisation and the intensification of informational entropy as the
  technological mechanism consists in feeding the regurgitated content of the
  internet back into itself

- reality itself has become infected by the disease that killed the Web
  (ecocide). The distinction between offline and online is becoming increasingly
  hard to draw (politics - the normalisation and ascendancy of the far right and
  subsequent destabilisation of western states)


title: Memory_addresses
last_modified: 2024-10-18 18:55:56
body:
# Memory addresses

> Computers assign numeric addresses to bytes of memory and the CPU can read or
> write to those addresses

We can think of the internals of RAM as grids of memory cells.

Each single-bit cell in the grid can be identified using two dimensional
coordinates, like in a graph. The coordinates are the location of that cell in
the grid.

Handling one bit at a time isn't very efficient so RAM accesses **multiple
grids** of 1-bit memory cells in parallel. This allows for reads or writes of
multiple bits at once, such as a whole byte.

The location of a set of bits in memory is known as a **memory address**.

### Demonstration

Let's imagine we have a computer system that can address up to 64KB of memory
and our system is byte addressable. This means there are
$64 \cdot 1024 = 65,536$ bytes of memory because 1KB = 1024 bytes.

We therefore have 65,536 addresses and each address can store one byte. So our
addresses go from 0 to 65, 535.

We now need to consider how many bits we need to uniquely represent an address
on this system.

What does this mean? Although there are approximately 64 thousand bytes of
memory, to refer to each byte we can't just use 1, 2, 3... because computers use
binary numbers. We need a binary number to refer to a given byte in the the 64KB
of memory. The question we are asking is: how long does this binary number need
to be to be able to represent each of the 64 thousand bytes?

1 bit can represent two addresses: 0 and 1. 2 bits can represent four addresses:
00, 01, 10, 11. The formula is as follows: number of addresses = $2^n$ where $n$
is the number of bits.

We need to reverse this formula to find out how many bits we need to represent a
given number of addresses. We can do this with a [logarithm](Logarithms.md).

We can reverse the formula as follows: number of bits = $\log_2$ (number of
addresses).

In our case we have 65,536 addresses so we need $\log_2(65,536)$ bits to
represent each address. This is approximately 16 bits. Thus a 16 bit memory
iaddress is needed to address 65, 546 bytes.

Using memory addresses we end up with tables like the following:

| Memory address   | Data             |
| ---------------- | ---------------- |
| 0000000000000000 | 1010101010101010 |
| 0000000000000001 | 0010001001001011 |
| 0000000000000010 | 0010001001001010 |

This is hard to parse so we can instead use
[hexadecimal numbers](Hexadecimal_number_system.md) to represent the addresses:

| Memory address (as hex) | Data (as binary) |
| ----------------------- | ---------------- |
| 0x0000                  | 1010101010101010 |
| 0x0001                  | 0010001001001011 |
| 0x0002                  | 0010001001001010 |

By itself, the the data is meaningless but we know from
[binary encoding](Binary_encoding.md) that the binary data will correspond to
some meaningful data, such as a character or a colour, depending on the encoding
scheme used. The above table could correspond to the characters for 'A', 'B' and
'C' in the ASCII encoding scheme:

| Memory address (as hex) | Data (as binary) | Data (as ASCII) |
| ----------------------- | ---------------- | --------------- |
| 0x0000                  | 1010101010101010 | A               |
| 0x0001                  | 0010001001001011 | B               |
| 0x0002                  | 0010001001001010 | C               |


title: Signed_magnitude_representation
last_modified: 2024-10-18 18:55:56
body:
# Signed magnitude representation

## Summary

## Detail

The concept behind SMR is similar to how we designate positive and negative
integers in denary mathematics: we use a dedicated symbol (`-`) to signpost that
the integer is negative. In the binary application the dedicated symbol is one
of the binary digits that comprise the number.

Typically `0` is used to indicate an unsigned (positive) integer and `1` to
indicate a signed (negative) number. The rest of the bits are the magnitude (the
actual numerical value).

We can demonstrate with an 8-bit binary system, encoding `5`: `0000 0101`. Here
the furthest bit (aka. the "most significant bit" (MSB)) at the $2^8$ position
is `0`, designating that the number is unsigned. The signed equivalent is
`1000 0101`, with the MSB being `1` designating the number as signed. When we
are working with signed numbers, the MSB is known as the **signed bit**.

### Advantages

### Limitations

## Applications

## Related notes

[[Signed_and_unsigned_numbers|signed_and_unsigned_numbers]]


title: AWS_SNS
last_modified: 2024-11-25 07:19:10
body:
# AWS SNS

The Simple Notification Service is another form of messaging service in AWS,
similar to [SQS](AWS_SQS.md). The key difference is that SNS pushes messages out
to subscribers whereas SQS stores the messages until they are processed.

Before a message can be sent, and SNS topic must exist. One service such as a
lambda will _publish_ to this topic. Another service, will _subscribe_ to the
topic and be alerted when a message is sent to it.

With SNS, one message can be sent to multiple subscribers simultaneously. In
addition there is message filtering at the subscription level so subscribers can
choose to receive only the messages they are interested in.

Many other AWS services can act as SNS subscribers. Some common examples:

- SQS queues
- Lambda functions (trigger on receipt of a message)
- HTTP endpoints
- Email/SMS/Push notifications to mobile apps

## Combining with SQS

Utilising SNS with SQS is a common pattern in serverless applications.

We use SNS to decouple publishers from subscribers and then use SQS to handle
the different subscriber tasks. This is known as the "Fanout-Queue" pattern.

![SQS fanout pattern](../img/sns-example.png)

For example, an order is placed which results in a publication event to an SNS
topic.

Multiple systems subscribe to this topic, e.g. an inventory system, a shipping
system and an analytics system.

Each system has its own queue for processing the message.


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: Boolean_algebra
last_modified: 2024-10-18 18:55:56
body:
# Boolean algebra

## Algebraic laws

Many of the laws that obtain in the mathematical realm of algebra also obtain
for Boolean expressions.

### The Commutative Law

$$
    x \land y = y \land x \\
$$

$$

    x \lor y = y \lor x
$$

Compare the
[Commutative Law](Whole_numbers.md#the-commutative-property)
in the context of arithmetic.

### The Associative Law

$$
    x   \land (y \land z) = (x \land y) \land z
$$

$$
    x   \lor (y \lor z) = (x \lor y) \lor z
$$

Compare the
[Associative Law](Whole_numbers.md#the-associative-property)
in the context of arithmetic.

### The Distributive Law

$$
    x \land (y \lor z) = (x \land y) \lor (x \land z)
$$

$$
    x \lor (y \land z) = (x \lor y) \land (x \lor z)
$$

Compare how the
[Distributive Law applies in the case of algebra based on arithmetic](Distributivity.md):

$$
    a \cdot (b + c) = a \cdot b + a \cdot c
$$

### Double Negation Elimination

$$
    \lnot \lnot x = x
$$

### Idempotent Law

$$
    x \land x = x
$$

> Combining a quantity with itself either by logical addition or logical
> multiplication will result in a logical sum or product that is the equivalent
> of the quantity

### DeMorgan's Laws

In addition we have
[DeMorgan's Laws](DeMorgan's_Laws.md) which express
the relationship that obtains between the negations of conjunctive and
disjunctive expressions:

$$
\lnot(x \land y)  = \lnot x \lor \lnot y
$$

$$
    \lnot (x \lor y) = \lnot x \land \lnot y
$$

## Applying the laws to simplify complex Boolean expressions

Say we have the following expression:

$$
    \lnot(\lnot(x) \land \lnot (x \lor y))
$$

We can employ DeMorgan's Laws to convert the second conjunct to a different
form:

$$
    \lnot (x \lor y) = \lnot x \land \lnot y
$$

So now we have:

$$
    \lnot(\lnot(x) \land (\lnot x \land \lnot y ))
$$

As we have now have an expression of the form _P and (Q and R)_ we can apply the
Distributive Law to simplify the brackets (_P and Q and R_):

$$
    \lnot( \lnot(x) \land \lnot(x) \land \lnot(y))
$$

Notice that we are repeating ourselves in this reformulation. We have
$\lnot(x) \land \lnot(x)$ but this is just the same $\lnot(x)$ by the principle
of **idempotence**. So we can reduce to:

$$
    \lnot(\lnot(x) \land \lnot(y))
$$

This gives our expression the form of the first DeMorgan Law
($\lnot (P \land Q)$), thus we can apply the law ($\lnot P \lor \lnot Q$) to
get:

$$
\lnot(\lnot(x)) \lor \lnot(\lnot(y))
$$

Of course now we have two double negatives. We can apply the double negation law
to get:

$$
    x \lor y
$$

### Truth table

Whenever we simplify an algebraic expression the value of the resulting
expression should match that of the complex expression. We can demonstrate this
with a truth table:

| $x$ | $y$ | $\lnot(\lnot(x) \land \lnot (x \lor y))$ | $x \lor y$ |
| --- | --- | ---------------------------------------- | ---------- |
| 0   | 0   | 0                                        | 0          |
| 0   | 1   | 1                                        | 1          |
| 1   | 0   | 1                                        | 1          |
| 1   | 1   | 1                                        | 1          |

### Significance for computer architecture

The fact that we can take a complex Boolean function and reduce it to a simpler
formulation has great significance for the development of computer
architectures, specifically
[logic gates](Logic_gates.md). It
would be rather resource intensive and inefficient to create a gate that is
representative of the complex function. Whereas the simplified version only
requires a single
[OR gate](Logic_gates.md#or-gate).


title: Package_management_in_NodeJS
last_modified: 2024-10-18 18:55:56
body:
# Package management

## List installed packages

```bash
npm list
```

This will return a recursive tree that lists dependencies, dependences of
dependencies, ... and so on. To limit the depth you can add the `--depth=` flag.
For example to see only your installed packages and their versions use
`npm list --depth=0`.

## View `package.json` data for an installed package

We could go to the NPM registry and view details or we can quickly view the
`package.json` for the dependency with the command `npm view [package_name]`

We can pinpoint specific dependencies in the `package.json`, e.g.
`npm view [package_name] dependencies `

## View outdated modules

See whether your dependency version is out of date use `npm outdated`. This
gives us a table, for example:

![Pasted image 20220411082627.png](Pasted_image_20220411082627.png)

- _Latest_ tells us the latest release available from the developers
- _Wanted_ tells us the version that our `package.json` rules target. To take
  the first dependency as an example. We must have set our SemVer syntax to
  `^0.4.x` since it is telling us that there is a minor release that is more
  recent than the one we have installed but is not advising that we update to
  the latest major release.
- _Current_ tells us which version we currently have installed regardless of the
  version that our `package.json` is targeting or the most recent version
  available.

## Updating

`npm update` only updates from _current_ to _wanted_. In other words it only
updates in accordance with your caret and tilde rules applied to
[semantic versioning](Semantic_versioning.md).


title: Data_types_in_MySQL
last_modified: 2024-10-18 18:55:56
body:
# Data types in MySQL

An understanding of the differences between data types is important because it
obviously affects the amount of memory the database uses.

## Numeric

### Integer (`INT`)

- No fractional part, fixed number of digits
- `TINYINT`, `BIGINT` etc are all instances of `INT`, the difference is the size
  of the number stored.

### Fixed point (`DECIMAL`)

Contains decimal point. Use when accuracy is more important that representing
very large or very small values. Hence you would use this for monetary amounts.

### Floating point (`FLOAT`)

Contains decimal point. Use when the ability to represent very large and very
small values is more important than precision. Hence you would not use this for
monetary amounts.

## String

### Fixed-length character strings ()

Spaces are stripped in storage but represented with a character set.

An example would be `CHAR(10)` or `CHAR(3)`. Here we set the upper limit but it
must be the case that no string exceeds it. For either of these, if you add a
value that is, e.g, two characters in length, it will add spaces to pad it out
and make up the upper limit.

### Variable-length character strings

As above but allowing for variable-length strings.

A common example is `VARCHAR(255)`. The 255 refers to the maximal character
length, not the byte length. It is the largest number of characters that can be
accommodated by an 8-bit number (byte). We must put `255` as the parameter even
if our character lengths will be below this but where we don't know the minimum
and maximum length.

## Large object storage

### Blob

For storing images, audio and other binary media. Variants: `TINYBLOB`, `BLOB`,
`MEDIUMBLOB` `LONGBLOB`

### Text

Like blob but has character set Variants: `TINYTEXT`, `TEXT`, `MEDIUMTEXT`,
`LONGTEXT`

## Date and time

- `DATE`
- `DATETIME`
- `TIMESTAMP`

## Speciality

### Enumerations

- `ENUM`: a single value from a list
- `SET`: several values from a list

## Storage limits

Each row can have a maximum of 65,535 bytes.


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

## Shells

Electrons orbit in concentric circuits around the nucleus of the atom. Each
orbit is called a **shell**.

Each shell can accommodate a maximum number of electrons. The shells are
designated by letters and filled in sequence moving out from the shell nearest
to the nucleus.

_The diagram below demonstrates shell naming conventions and the maximum number
of electrons per shell._

![](/img/valence-shell.svg)

## Valence

The outer shell called the **valence shell** and the number of electrons it
contains, the **valence**. This part of the atom is the most important from the
point of view of electricity because it is from here that electrons can escape
the atom and where electrons from other atoms may join.

> The farther the valence shell is from the nucleus, the less attraction the
> nucleus has on each valence electron. Thus the potential for the atom to gain
> or lose elections increases if the valence shell is not full and is located
> far enough away from the nucleus.

## Conductivity and insularity

The **conductivity** of a material is an expression of its capacity to channel
electrical charge. Where electrical charge is the _flow of free electrons from
one atom to another_. The **insularity** of a material is the opposite: its
_capacity to resist the flow of electrical charge_.

Electrons in the valence shell can gain energy from external forces. If these
electrons gain enough energy, they can leave the atom and become **free
electrons**, moving randomly from atom to atom.

We call materials that contain a large number of free electrons **conductors**.
In contrast **insulators** are atoms that stabilize themselves by absorbing
valence electrons from other atoms to fill their valence shells, eliminating the
free electrons.

> The propensity for the electrons within a conductor to move about and jump
> between atoms, swapping charge, is not something that is only activated when a
> voltage is applied. In fact, the electrons are always doing this, even in a
> piece of inert copper. The difference is they are doing it randomly and in all
> kinds of directions. It is only when a voltage is applied that the motion and
> exchange of electrons is forced in one consistent direction.

### Semiconductors

Semiconductive materials are midway between conductors and insulators: they are
neither good conductors or insulators but can be altered to function in the
manner of either.

## Ionization

Ionization is the process of atoms gaining and losing electrons.

When an atom has an equal number of protons and electrons it is said to be
**electrically balanced**.

A balanced atom that receives one or more extra electrons gives way to an
overall negative charge. An atom in this state is a **negative ion**. Conversely
if a balanced atom loses one or more electrons it becomes positively charged and
is thus called a **positive ion**.

> The process of ionization is a constitutive part of the flow of current but
> not the only part. We also must factor the force that triggers ionization and
> the resistance that impedes it.


title: Conditional_Elimination
last_modified: 2024-10-18 18:55:56
body:
# Conditional elimination

If we have a conditional and we have independently derived its antecedent, we
may invoke its consequent. This is often referred to as _Modus ponens_
(affirming the antecedent).

![](/img/cond-elim.png)


title: Containerization
last_modified: 2024-10-18 18:55:56
body:
# Containerizaton

> In this entry we look at containerization as a general technology that is
> distinct from its particular implementation by Docker.

We can understand the role and significance of containers and containerization
by analogy with shipping containers. Before the invention of shipping containers
as a **standardised modular unit** for transporting goods, shipping and supply
chains were chaotic: inefficient, complex, frictional. With the advent of the
shipping container, the same artefact could be used for transportation via
ships, trains, lorries etc.

With containers we can share and transport software via a single standardised
unit that works regardless of the host operating system or server. We can also
iterate different software environments easily: local, production, test etc. As
with the shipping of goods, standardisation has driven down running costs and
increased the speed of transport.

## Containers and software

- Containers are a technology that allow you to package and isolate applications
  with their entire runtime environment. This makes it easy to move the
  contained application between various environments whilst retaining full
  functionality.

- The technology of containerization is distinct from _Docker_. Docker is a
  particular implementation of containerization that simplifies the process and
  bases it on a standardised specification.

- Containers are native to the Linux [kernal](The_kernel.md)
  and are key part of how it works. Thus when you run containers on Linux, you
  are using native capability. When you use containers on Windows or Mac you
  have to run a virtual version of Linux in order to exploit the capabilities of
  that kernel. (Docker provides this)

## How containers work

A key feature of the Linux kernal is the existence of **cgroups** (control
groups).

> Containerization = resource control + process isolation

- A cgroup **groups a series of processes together and contrains their access to
  system resources (CPU, memory, disk)**
- Cgroups use namespaces to provide **process isolation**: groups of processes
  are isolated so that they cannot access other processes on the system. For
  example a container is ignorant of the underlying operating system and
  network, by default.

In ordinary [user space](User_Space.md) applications share
the _same_ processor, memory and file system resources. This increases the
likelihood of resourcing challenges, dependency conflicts and security threats.
Without modularisation and the titration of resources, you are opened up to much
greater possibility of failure.

For example one application could fill up the harddrive preventing other
applications from writing to it. One application can "bring down" another
applicaton.

Prior to containerization, in an enterprise environment most of the system
administration consisted in managing resouces to avoid excessive resource
expenditure and security challenges.

_Standard userspace_

![](/img/standard-userspace.png)

_Userspace with containerization_

![](/img/containers-in-userspace.png)

## Differences with virtual machines

Containers are similar in many regards to VMs. They both offer a virtualized
environment in which to run software and they both provide isolation of
resources. (When you start up a VM you specify the available RAM and disk
space.)

The central difference is that VMs require the existence of a full operating
system. Containers can run without an OS although it is often convenient to run
them with stripped-down Linux distributions.

A VM contains a complete installation of a guest OS and requires a hypervisor on
the host OS to manage the different operating environments. This makes them
slower and consume more resources.

A containerized system radically reduces this overhead. It just starts up the
container and after that it's job is done since the Linux kernel handles the
rest as it would any other process on the OS.

| Virtual Machine                             | Container                               |
| ------------------------------------------- | --------------------------------------- |
| Isolation of resources and operating system | Isolation of resources only             |
| Require a hypervisor to run                 | Require a container runtime to run      |
| Slower due to additional abstraction        | Faster due to less overhead             |
| Less portable                               | More portable                           |
| Slower and more difficult to run            | Scale rapidly due to lightweight nature |

![](/img/container-versus-vm.png)

## Why use containers?

- portability
- scalability
- efficiency
- isolation
- DevOps agility


title: Call_stack
last_modified: 2024-11-04 14:24:25
body:
# Call-stack

A [stack](Stacks.md) data structure that holds the information of the functions
called within a program that allows transfer of the application control from
these functions to the main process after code inside the functions has been
executed.

## Example

```js
function greet(who) {
  console.log("Hello " + who);
}

greet("Harry");

console.log("Bye");
```

### Breakdown

1. Interpreter receives call to `greet()`
2. Goes to the definition of this function (`function greet(who)...`)
3. Executes the `console.log` within this function
4. Returns to the location that called it (`greet("Harry")`)
5. Finds that there is nothing else to do in this function so moves to next
   function: the `console.log("bye")`
6. Executes
7. Returns to line that called it. Finds nothing else to do. Exits program.

## Related notes

![Stack memory](Stack_memory.md)


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: c8173d17_TIMPs
last_modified: 2024-10-25 09:09:15
body:
# c8173d17_TIMPs

After the initial ARPANET was complete, the next major milestone was to enable
access to the network regardless of one's proximity to a host node with an IMP
connection.

The idea was to allow a computer to access resources on another computer
directly without connecting to a host first. This connective computer would
connect to an IMP directly (but still transparently) as a 'dumb terminal' as
with time-sharing and would not be a fully-fledged computing device. These
devices would be called _Terminal_ Information Processors (TIMPs) for this
reason.

The development of TIMPs makes it clearer that the host machines on the ARPANET
were not just connection and transmission nodes for their own purpose, they were
loci for other non-host computers to gain access to an IMP, and thereby, the
broader network. In other words computers would connect to a host which
sustained a connection to an IMP.


title: Interactive_staging
last_modified: 2024-10-18 18:55:56
body:
# Interactive staging

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

## Basic use

We enter interactive mode with:

```
git add -i
```

This opens an interface:

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

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

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

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

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

## Working with patches and hunks

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

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

We access these via **patch mode**:

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

### Splitting hunks

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

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

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

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

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

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

### Accessing patch mode directly

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

```
git add -p
```

We can also work with patches in the following commands:

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


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: Custom_types_in_TypeScript
last_modified: 2024-10-18 18:55:56
body:
# Custom types

Objects and [classes](Classes.md) are where TypeScript becomes most useful and
powerful. In TypeScript, objects and classes are by definition custom types.

When typing objects, you do not write the types alongside the actual data as you
would with primitive types, classes and functions. Instead you write a custom
type which is a type-annotated object. This is then applied to the object or
class, where you create instances that **match the shape** of the custom
declaration.

So say we have this object:

```js
const age = {
  name: "Thomas",
  yearOfBirth: 1988,
  currentYear: 2021,
  ageNow: function () {
    return this.currentYear - this.yearOfBirth;
  },
};
```

We could write this as type with the following:

```ts
let Age: {
  name: string;
  yearOfBirth: number;
  currentYear: number;
  ageNow: () => number;
};
```

(We use `:` because we are declaring a type not intialising a value of the
type.)

We could now re-write the first `age` object as an object of type `Age` :

```tsx
let thomas: typeof Age;

thomas = {
  name: "Thomas",
  yearOfBirth: 1988,
  currentYear: 2021,
  ageNow: function () {
    return this.currentYear - this.yearOfBirth;
  },
};
```

In practice, defining the type and then asserting that a new variable is of this
type and then initialising it is rather long-winded. It is better practice to
simplify the process by creating a **type alias**.

```tsx
type Age = {
  name: string;
  yearOfBirth: number;
  currentYear: number;
  ageNow(): number; // we type the method on the basis of the value it returns
};
```

We could then create objects based on this:

```tsx
const thomas: Age = {
  name: "Thomas",
  yearOfBirth: 1988,
  currentYear: 2021,
  ageNow: function () {
    return this.currentYear - this.yearOfBirth;
  },
};
```

Note that we pass in `:Age` as our type declaration, using the custom type in
the same way as we would use `:string` or `number[]` . We can now use this
custom type as a type annotation anywhere we use type annotations; it can be
used exactly the same way as a primitive type in our code.

`Age` is an alias for the type that `thomas` conforms to.

The benefit is that TS will correct you if:

- attempt to assign a type to a value that does not match the custom type
  declaration (for instance: assigning a string value to a property you have
  typed as number)
- attempt to add a property that is not specified in the custom type declaration

Although you can subsequently _extend_ the custom type (detailed below).

## Duck typing

> Types are defined by the collection of their properties not their name.

Typescript's implementation of types is as a **structural type system**, which
contrasts with a nominal type system. This is often referred to colloquially as
'duck typing': _if it looks like a duck, walks like a duck, and sounds like a
duck, it probably is a duck_.

With custom (object types) this means that the following expression of an object
of type `Age` doesn't generate an error, TS is satisfied that the shapes of each
match.

```tsx
const martha = {
  name: "Martha",
  yearOfBirth: 1997,
  currentYear: 2021,
  gender: "female",
};

const addition: Age = martha;
```

But if we tried to add this extra property whilst defining `martha` as an
instance of the custom type `Age` , we would get an error:

```tsx
const martha: Age = {
  name: "Martha",
  yearOfBirth: 1997,
  currentYear: 2021,
  gender: "female",
};
```

```
Type '{ name: string; yearOfBirth: number; currentYear: number; ageNow: () => number; gender: string; }' is not assignable to type 'Age'. **Object literal may only specify known properties, and 'gender' does not exist in type 'Age'.**
```

It means that even though in the following, the variable `point` is never
declared to be of the type `Point` , it matches the shape of the custom type. As
the structural integrity is maintained, it can be passed to the function without
error.

```tsx
interface Point {
  x: number;
  y: number;
}

function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);
```

Shape matching only requires a subset of the object's fields to match:

```tsx
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"

const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect);
```

## Interfaces

For most purposes the keywords `type` and `interface` are interchangeable in
TypeScript.

(The main difference that I have encountered is that Angular specifies that
interfaces should be used over types, but this is because it is class-based and
object oriented).

For me, the main decider is that Angular favours `interface` over `type`. The TS
handbook recommends using the `type` keyword over `interface`.

An interface is concept that crosses over from strict OOP and it doesn't quite
apply to interfaces in TS because an interface is a declaration of a class's
functions alone, not its data (i.e properties).


title: Linux_disk_partitions
last_modified: 2024-10-18 18:55:56
body:
# Linux disk partitions

A disk is divided up into partitions which are subsections of the overall disk.
The kernel presents each partition as a [block device](Devices.md#Devices) as it
would with an entire disk.

The disk dedicates a small part of its contents to a **partition table**: this
defines the different partitions that comprise the total disk space.

## Viewing current partitions

Whenever you install a Linux distribution on a real or virtual machine, you must
partition the drive. There are three main tools to choose from: `parted`,
`g(raphical)parted`, `fdisk`.

For a top-level overview of your disks and their main partitions you can run
`lsblk` (_list block devices_):

```bash
$ lsblk

NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 465.7G  0 disk
├─sda1        8:1    0   200M  0 part
└─sda2        8:2    0 465.4G  0 part /run/media/thomas/ganesh
nvme0n1     259:0    0 476.9G  0 disk
├─nvme0n1p1 259:1    0   488M  0 part
├─nvme0n1p2 259:2    0  27.9G  0 part /
└─nvme0n1p3 259:3    0 448.5G  0 part /home
```

We can use `parted -l` to view the partition table for the current machine:

```bash
Model: SKHynix_HFS512GDE9X081N (nvme)
Disk /dev/nvme0n1: 512GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name  Flags
 1      1049kB  513MB   512MB   fat32              boot, esp
 2      513MB   30.5GB  30.0GB  ext4
 3      30.5GB  512GB   482GB   ext4
```

We can use `fdisk -l` to get slightly more info:

```bash
disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
Disk model: SKHynix_HFS512GDE9X081N
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 08175E77-CB9F-C34A-9032-DF29A3F8F0FE

Device            Start        End   Sectors   Size Type
/dev/nvme0n1p1     2048    1001471    999424   488M EFI System
/dev/nvme0n1p2  1001472   59594751  58593280  27.9G Linux filesystem
/dev/nvme0n1p3 59594752 1000214527 940619776 448.5G Linux filesystem
```

## Standard Linux partitions

The two tools disclose that the main harddrive is `/dev/nvme0n1` (equivalent to
`sda` on older machines running Linux) and it has the standard three partitions:

- Boot partition (`/dev/nvme0n1p1`)
- Root dir (`/dev/nvme0n1p2`)
- Home dir (`/dev/nvme0n1p3`)

### Boot partition

- Takes up the smallest amount of space
- Contains the Linux kernel and the bootloader (GRUB, usually) and any other
  files required for booting.
- Once the BIOS has initialized the hardware components it hands control to the
  bootloader stored in this partition.
- The bootloader loads the kernel contained in its partition, into memory

### Root partition (`/`)

- This houses everything to do with the operation of the operating system apart
  from the initial booting process. Once the kernel is in memory it accesses the
  resources in the root partition to get the OS up and running. These resources
  include system libraries, utilities, system-wide configs and application
  software.

- The root partition is vital for the system's functionality. Without it, the
  system cannot boot or operate. It needs to be  
   large enough to accommodate the operating system, installed programs, and system
  libraries, but it doesn't need to store user data or  
   additional applications, which can be located on other partitions.

- This is the domain of the [superuser](User_Space.md#root-user-superuser). The
  part of the filesystem that you need sudo priveleges to access and where you
  manage users

### Home partition

- This is presented as `/home/`, i.e. that it is a descendent of `/`. But this
  is because the Linux file system is hierarchical. Typically `/home/` is
  usually its own partition.
- Separating user data and system data in this way protects user data during
  upgrades or reinstallation. Reinstalling or upgrading the OS can be done
  without affecting these files.
- Each user has their own directory off of `/home`, e.g `/home/thomas`.
- While the root partition (`/`) is essential for the operation of the OS,
  placing `/home` on a separate partition allows for better management of user
  data, enhancing the system's flexibility and the user's control over their own
  files, without compromising the integrity or performance of the OS. This
  approach exemplifies the Linux philosophy of giving the user control while
  maintaining a clear distinction between system  
  resources and user data.

## Types of partition table

In the Linux world there are two main types: MBR and GPT. The type of table used
determines how the OS boots. So although partition tables are also responsible
for the partitioning of non-bootable sectors of a disk, **they are distinguished
by the boot system they implement**. If we look at the output from `parted` and
`fdisk` above we see that the harddrive uses the GPT partition type.

#### Primary, extended and logical partitions

Most standard partition tables allow for primary, extended and logical
partitions. The primary partition is the part of the harddisk that contains the
operating system and is thus described as 'bootable' and may be called the 'boot
partition'. During the bootstrapping process this is injected into memory as the
[kernel](The_kernel.md).

The extended partition is basically everything other than the primary partition.
This is typically subdivided into other partitions that are called _logical_
partitions. This is because they physically reside in the same sector of the
disk (the extended partition) but are treated as virtual and independent disks.

In our example above:

- `/dev/nvme0n1p1` is the primary/boot partition
- `/dev/nvme0n1p2` and `/dev/nvme0n1p3` comprise the extended partition and by
  themselves are each logical partitions.

<dl>
  <dt>MBR</dt>
  <dd> 
    <ul>
      <li>Stands for Master Boot Record</li> 
      <li>Uses BIOS in the boot process</li> 
      <li>Can only works with disks up to 2TB in size</li> 
      <li>Only supports 4 primary partitions. This means the number of operating systems you install is limitied to this number.</li> 
      <li>This is the first 512 bytes of a storage device, preceding the first partition.</li>
    </ul> 
  </dd>
  <dt>GPT</dt>
  <dd>
  <ul>
      <li>Stands for GUID Partition Table </li>
      <li>Gradually replacing MBR</li>
      <li>Uses UEFI instead of BIOS</li>
      <li>As name indicates, every partition on disk has its own globally-unique identifier</li>
      <li>Vastly more partitions available than with MBR (dependent on operating system)</li>
      <li>Offers greater recovery options and anti-corruption safeguards</li> 
    </ul>
</dd>
</dl>

## Related notes

![Creating a Linux partition table](./Creating_a_Linux_partition_table.md)


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: Using_SQLite_with_Python
last_modified: 2024-11-14 14:43:27
body:
# Using SQLite with Python

SQLite is part of the core library, you do not require a separate package

```py
import sqlite3
```

## Connect to database

Connect to an existing SQLite `.db` file or create a new database with the same
command:

```py
connection = sqlite3.connect("my_database.db")
```

## Disconnect from database

```python
connection.close()
```

## Querying

`connect` exposes a `cursor` entity. This is used for executing changes against
the database file.

### Example: creating a table

```python
CREATE_TAGS_TABLE = """
CREATE TABLE IF NOT EXISTS tags (
    name TEXT PRIMARY KEY
)
"""

cursor = connection.cursor()
cursor.execute(CREATE_TAGS_TABLE)
cursor.commit()

# Confirm changes

cursor.execute("SELECT name from sqlite_master WHERE type='table'")
print(cursor.fetchall())
# (tags)
```

Note that it is necessary to `commit` the changes. The cursor object just stores
the operation in memory and does not execute until you specifically commit them.

## Prepared statements and parameterised queries

### Parameterised queries

```py
username = "thomas"
password = "123"
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?",
            (username, password))
```

Alternatively, pass the [tuple](Tuples_in_Python.md) directly:

```python
login_data = ("thomas", "123")
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?",
login_data)
```

### Prepared statements

> With prepared statements you have to pass in the parameters as a dictionary

```python
user_data = {
    "username": "thomas",
    "email": "thomas@email.com"
}

cursor.execute("INSERT INTO users (username, email) VALUES (:username, :email)",
               user_data)
```

In the example above it so happens that we already have a dictionary we are
working with. If you want to use a prepared statement and you don't have a
dictionary already, you will need to construct one as part of the query, e.g:

```python
cursor.execute("INSERT INTO table_name (value_one, value_two) VALUES (:value_one, :value_two)", { value_one: 'foo', value_two: 'bar'})
```


title: Git_rebasing
last_modified: 2024-10-18 18:55:56
body:
# Rebasing

Rebasing is a way to integrate changes from one branch into another. In this
regarding it is like merging a branch B into another branch A. However rebasing
differs from normal merging in the way in which it modifies the Git history.

In a normal merge of branch B into branch A, Git creates a new commit that
combines the changes of branches B and A. This is known as the merge commit and
is evident from the following automatic commit message that is generated:

```
Merge branch B of github.com:thomasabishop/remote-repository into A
```

![](/img/normal-merge-again.svg)

In this scenario the merge commit has two or more parent commits each
representing the history of the merged branches. The resulting history of A will
include the commits of B. Basically the two histories are combined.

This would give us a history that looks like the following, with different
colours for the separate SHAs of each merged branch:

![](/img/combined-merge-hist.svg)

If we were to create a rebase branch of A from B, there would be a new singular
history without distinguishing multiple parents that combines the commits of A
and B. The rebased branch's commits are recreated with new commit IDs and new
timestamps, based on the current state of the branch that you are rebasing onto.
This is obviously potentially destructive because it does not preserve the
respective branch's separate history. It rewrites history as a continuous stream
of commits in a single branch.

When a rebase is applied, it will put the diverging B commits at the tip of A
like so:

![](/img/rebase-tip-chage.svg)

And then rebrand the previous A commits to be continuous with B presenting a
flat and linear Git history like the following:

![](/img/single-git-history-rebase.svg)

## Benefits, use-cases

> The purpose of rebasing is to ensure that the commit history of a project is
> as linear and simple as possible, by incorporating changes from one branch
> into another, as if the changes had been made on the other branch all along.

A common use-case is with feature branches as it makes the features fit more
seamlessly with the `main` or `develop` branch.

It is also a technique that can be used to integrate recent commits without
merging.

When to use one over the other:

- **Merge** to allow commits to stand out or to be clearly grouped
- **Merge** to bring large feature branches back into main
- **Rebase** to add minor commits in a main branch into a feature branch
- **Rebase** when you need to move commits from one branch to another

So in terms of my personal workflow, I should be using

- Merge when I want to bring a feature into develop
- Merge when I want to bring develop into main for a release
- Merge any time the feature branch is already public and being used by others.
- Rebase to capture small changes in the parent branch
- Rebase when I have branched from a branch because a refactor or something has
  become more complex than originally anticipated.

## Syntax

For example if we are working on a feature branch off of `main`, we would run
the following from this feature branch:

```
git rebase main
```

This will move the feature branch to the tip of `main`.

If this completes successfully, we would then go on to merge the feature with
main.

If conflicts occur, the rebase will halt. You fix the conflicts and then run:

```
git rebase --continue
```

Or if you don't want to resolve the conflict (not advised):

```
git rebase --skip
```

Or stop the whole process:

```
git rebase --abort
```

### Isolate the point at which one branch diverges from another

This can be useful to check before you create a rebase:

```
git merge-base main feature/some-feature

c33acc84f06fcb94e0e87d9adb240c038da6d71c
```

## Golden rule of rebasing

When we rebase we remove the additional merge commit but we are changing
history. The commits are still there but the SHAs are changed. For this reason
**you should not rebase a public branch**, otherwise you will mess up your
collaborators history. You should do your rebasing locally and then make a pull
request.

## Difference from cherry-picking

The main difference between the two approaches is that
[cherry-picking](Cherry_picking_a_branch.md) is a more selective
process, where you can pick and choose specific commits that you want to include
in another branch. This can be useful when you only want to apply specific
changes or fixes from one branch to another, without including all the changes
made in the original branch.

On the other hand, rebasing is a more comprehensive process that can include all
the changes from one branch to another, but it modifies the commit history,
making it more linear and easier to understand. Unlike rebasing, cherry-picking
does not modify the commit history of either branch.

## Squashing commits

This is a handy feature of rebasing. It allows you to combine multiple commits
into one. This is useful when you have been doing a lot of trial and error to
resolve a bug and you have lots of small commits that are not that important in
themselves.

We use the interactive rebase tool for this.

Checkout the branch where the commit you want to squash is located.

Then run:

```
git rebase -i HEAD~n
```

Where `n` is the number of commits you want to squash starting from the most
recent. This will open an interactive rebase window, listing the commits. You
can then use the keywords to decide what you want to do with them. In our case
this will be `s` for squash.

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

### Example

I made a small fix commit because something went wrong during build. I do not
want this commit to be in the history because it is so minor. Instead I want to
incorporate these changes into the last public commit.

Trivial commit is `xy` Previous commit is `aa`

```
git rebase -i HEAD~2
```

This displays:

```
pick xy
pick aa
```

Change this to:

```
pick xy
squash aa
```

Now `xy == aa`

### Another example

Here we will rebase the interim commits into the last feature commit

```sh
git rebase -i HEAD~10
```

```
pick a691a7667 feature (shared-nav) add active app check
pick 74a07fdb1 interim: add logging
pick 9af4e2652 interim: more logging
pick 51b36f667 interim: use href not origin
pick 33f161198 interim : try catch handler
pick 41c144ddf interim: remove didLoadCheck
pick ebd1c9f24 interim: fix flash star
pick 77af452e7 interim: rm redundant active app check
pick 79a9b737a iterim: test ternary
pick a90d23223 interim: further tidying
```

```
pick a691a7667 feature (shared-nav) add active app check
squash 74a07fdb1 interim: add logging
squash 9af4e2652 interim: more logging
squash 51b36f667 interim: use href not origin
squash 33f161198 interim : try catch handler
squash 41c144ddf interim: remove didLoadCheck
squash ebd1c9f24 interim: fix flash star
squash 77af452e7 interim: rm redundant active app check
squash 79a9b737a iterim: test ternary
squash a90d23223 interim: further tidying
```

## `git pull rebase`

We can use this command to combine a rebase with a pull. This way we silently
update our local version of a branch with the remote, without adding a merge
commit.

```
git pull --rebase
```

Add `--rebase=preserve` to keep merges that have been done _locally_. It is
generally a good idea to do this.


title: Dividing_fractions
last_modified: 2024-10-18 18:55:56
body:
# Dividing fractions

Suppose you have the following shape:

![draw.io-Page-9.drawio 1.png](draw.io-Page-9.drawio.png)

One part is shaded. This represents one-eighth of the original shape.

![one-eighth-a.png](one-eighth-a.png)

Now imagine there are four instances of the shape and one-eighth remains shaded.
How man one-eighths are there in four?

![draw.io-Page-9.drawio 2.png](draw.io-Page-9.drawio.png)

The shaded proportion represents $\frac{1}{8}$ of the shape. Imagine four of
these shapes, how many eighths are there?

This is a division statement: to find how many one-eighths there are we would
calculate:

$$
4 \div \frac{1}{8}
$$

But actually it makes more sense to think of this as a multiplication. There are
four shapes of eight parts meaning there are $4 \cdot 8$ parts in total, 32. One
of these parts is shaded making it equal to $\frac{1}{32}$.

From this we realise that when we divide fractions by an amount, we can express
the calculation in terms of multiplication and arrive at the correct answer:

$$
4 \div \frac{1}{8} = 4 \cdot 8 = 32
$$

Note that we omit the numerator but that technically the answer would be
$\frac{1}{32}$.

### Formal specification of how to divide fractions

We combine the foregoing (that it is easier to divide by fractional amounts
using multiplication) with the concept of a [reciprocol](Reciprocals.md) to
arrive at a definitive method for dividing two fractions. It boils down to:
_invert and multiply_:

If $\frac{a}{b}$ and $\frac{c}{d}$ are fractions then:
$$\frac{a}{b} \div \frac{c}{d} = \frac{a}{b} \cdot \frac{d}{c}$$

We invert the divisor (the second factor) and change the operator from division
to multiplication.

#### Demonstration

Divide $\frac{1}{2}$ by $\frac{3}{5}$

$$
\begin{split}
\frac{1}{2} \div \frac{3}{5}  = \frac{1}{2} \cdot \frac{5}{3}  \\
= \frac{5}{5}
\end{split}
$$

Divide $\frac{-6}{x}$ by $\frac{-12}{x^2}$

$$
\begin{split}
\frac{-6}{x} \div \frac{12}{x^2} = \frac{-6}{x} \cdot \frac{x^2}{-12} \\ =
\frac{(\cancel{3} \cdot \cancel{2} )}{\cancel{x}} \cdot \frac{(\cancel{x} \cdot \cancel{x} )}{\cancel{3} \cdot \cancel{2} \cdot 2} \\ =
\frac{x}{2}

\end{split}


$$


title: Conjunction_Elimination
last_modified: 2024-10-18 18:55:56
body:
# Conditional Elimination

If a conjunction exists, it means that both conjuncts are the case; therefore we
can legitimately extract either one of them. Also known as _Simplification_.

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


title: Network_card
last_modified: 2024-10-18 19:18:10
body:
# Network card

The network card (technically know as the Network Interface Card (NIC)) or
_network adapter_ is the hardware device that allows a computer to connect to a
network.

They are the physical interface between the computer and the network
[link](./Link_Layer_of_Internet_Protocol.md), e.g. WiFi or ethernet.

They can be internal and built into the [motherboard](./Motherboard.md) or
external as in the case of USB adaptors.

Each network card has a unique [MAC address](./MAC_addresses.md) to identify the
device on the network.

Network cards on consumer-grade devices typically utilise the devices resources
and the kernel manages its actions via a driver. More high-end devices
(typically used in servers) have their own processors and memory.


title: Basic_properties_of_sets
last_modified: 2024-10-18 19:11:22
body:
# Basic properties of sets

## Set theory

Set theory is a sub-discipline of both mathematics and formal logic. In
mathematics it is used as a universal framework for describing other
mathematical theories. It is also utilised in computer science and linguistics.

It is useful because it provides tools for modelling an extraordinary variety of
structures.

> Set theory and the theory of infinite sets was created by Georg Cantor
> (1845-1918), a German mathematician.

## Method of formalisation

We can use the symbols of predicate logic to simplify and clarify natural
language expression of set-theoretic principles. There are different ways to do
this but we will use the standard quantifiers and:

- variables $a,b,c,...$ to range over sets
- variables $x,y,z$ to range over ordinary objects as well as sets.

More generally we will use capital Latin letters ($A, B, ...$) to denote some
specific set, i.e not a generalised/quantified notion of a set.

### Example

'Everything is a member of some set or another:

$$ \forall x \exists a (x\in a) $$

## What are sets?

A set is a collection of objects. In mathematics the objects are mathematical
objects.

A **finite set:**

$$ BG = { \textsf{Barry, Maurice, Robin}} $$

An **infinite set:**

$$ I = {1, 2, 3, 4, ...} $$

> When we use braces to indicate the members of a set we are providing a **list
> description** of the set.

## Set membership

If a set S is a collection of objects, to say that object x is a member of S is
just to say that x is one of those objects.

We might also express this in natural language as:

- the object x is an element of the set S
- the object x belongs to S
- the set S contains the object x

Formally, we use epsilon to express set membership:

$$ x \in A $$

This asserts that x is a member of the set A.

The negation of this proposition is expressed:

$$ x \notin A $$

This asserts that x is not a member of the set A.

### Subsets

> Set A is a subset of set B if every member of A is also a member of B.

For example the set of women is a subset of the set of humans because every
woman is a human. We express subset relations like so:

$$ A \subseteq B $$

This asserts that set A is a subset of set B.

The negation of this proposition is expressed:

$$ A \not\subset B $$

We must not confuse the relation of being a subset with being a member. Jane is
a member of the set of women but Jane is not a subset of the set of women since
Jane is not herself a set, she is an object/individual member.

There is also the notion of a **proper subset.**

> If subset _A_ of _B_ is a proper subset of _B_ then _B_ contains some elements
> that are not in _A_.

In other words, if B contains objects other than/ in addition to A.

$$ A \subset B $$

This asserts that set A is a proper subset of set B.

For example, the set of women is a proper subset of the set of humans because
the set of humans also includes the set of men. If there were only women and no
men, then the set of women would be a subset of the set of humans.

### Supersets

If A is a subset of B then we say that B is a **superset** of A. Being a
superset, B contains every object of A and may also contain other objects in
addition to A. This is just a different way of asserting that A is a proper
subset of B.

$$ B \supseteq A $$

This asserts B is a superset of A. The negation:

$$ B \not\supset A $$

This asserts that B is not a superset of A.

## Resources

[Set symbols](symbols.html)


title: What_is_memory
last_modified: 2024-10-18 18:55:56
body:
# What is memory ?

> A CPU is just an operator on memory. It reads its instructions and data from
> the memory and writes back out to the memory. (Ward 2021)

When a [CPU](CPU_architecture.md) executes a program, it needs a place to store
the program's **instructions** and **related data**. This is the role of memory.

The data that comprises a program is a series of bits. The basic unit of memory
storage is a **memory cell**: a circuit that can store a single bit.

[RAM](./DRAM_and_SRAM_memory.md) memory is _volatile_ : the memory is only
retained whilst the computer has a power supply and is wiped when the computer
is rebooted. This contrasts with the memory of the harddisk which is
non-volatile and is retained after a reboot.

Programs that are executing are loaded into memory because the chips that
comprise memory can read and store data much faster than the harddisk. It would
be possible to run a program from the harddisk but it would be 500 - 1000 times
slower than memory.


title: Natural_numbers
last_modified: 2024-10-18 19:17:38
body:
# Natural numbers

## The set of natural numbers

$$ \mathbb{N} = {1, 2, 3, ...} $$

Natural numbers are most simply expressed as **the set of numbers we use for
counting**. The set of natural numbers start at one and continuity to infinity.


title: Bolt_Beranek_and_Newman
last_modified: 2024-10-30 13:24:38
body:
# Bolt, Beranek and Newman

![BBN logo](../img/bbn-logo.png)

Founded by Bolt and Beranek, two MIT students and hired some of the leading
scientists of the day to lead departments. E.g. J.C.R Licklider, John McCarthy
and Marvin Minsky.

- Tested the original prototype of the PDP-1 on behalf of DEC.
- Working for BBN, McCarthy developed one of the first time-sharing systems (the
  "BBN Time-Sharing System") for the PDP-1 provided by DEC.
- Sponsored by DARPA to create the IMP computers which served as the main
  routers of the ARPANET.

Eventually subsumed into Raytheon and now known as "Raytheon BBN".


title: Disjunction_Elimination
last_modified: 2024-10-18 18:55:56
body:
# Disjunction Elimination

This rule is sometimes also referred to as _Constructive Dilemma_. This can be a
bit tricky to understand because the goal is to derive or _introduce_ a new
proposition separate from the disjunction you start out with. This may be
disjunction, a single proposition or a proposition containing any other logical
connective. You do this by constructing two sub-proofs, one for each of the
disjuncts comprising the disjunction you start out with. If you can derive your
target proposition as the conclusion of each subproof then you may invoke the
conclusion in the main proof and take it to be derived.

![](/img/disjunc-elim.png)

_Here is an example where Disjunction Elimination is used to derive a new
disjunction._

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

_Here are two further examples that use Disjunction Elimination to derive
singular propositions_

![](/img/ORelim1.png) ![](/img/ORelim2.png)


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

> Awk is a programming language designed for text processing and data
> extraction. It was created in the 1970s and remains widely used today for
> tasks such as filtering and transforming text data, generating reports, and
> performing basic calculations. Awk is known for its simplicity and
> versatility, making it a popular tool for Unix system administrators and data
> analysts.

## Invocation

We can use `awk` directly in `stdin` or we can reference `.awk` files for more
elaborate scripts

```bash
# CLI
awk [program] file1, file2, file3

# Script file
awk -f [ref_to_script_file] file1, file2, file3
```

We can also pipe to it. This piped command receives output from the `echo`
command and prints the value in the last field for each record:

```bash
echo -e "1 2 3 5\n2 2 3 8" | awk '{print $(NF)}'
```

## Syntactic structure

`awk` is a line-oriented language.

An `awk` program consists in a sequence of **pattern: action** statements and
optional functional definitions.

For most of the examples we will use this list as the input:

```
cloud
existence
ministerial
falcon
town
sky
top
bookworm
bookcase
war
Peter 89
Lucia 95
Thomas 76
Marta 67
Joe 92
Alex 78
Sophia 90
Alfred 65
Kate 46
```

> `awk` particularly lends itself to inputs that are structured by whitespace or
> in columns, like what you get from commands like `ls` and `grep`

### Patterns and actions

The basic structure of an `awk` script is as follows:

```
pattern {action}
```

A **pattern** is what you want to match against. It can be a literal string or a
regex. The **action** is what process you want to execute against the lines in
the input that match the pattern.

The following script prints the line that matches `Joe`:

```bash
awk '/Joe/ {print}' list.txt
```

`/Joe/` is the patttern and `{print}` is the action.

### Lines, records, fields

![](/img/awk-outline.png)

When `awk` receives a file it divides the lines into **records**.

Each line `awk` receives is broken up into a sequence of **fields**.

The fields are accessed by special variables:

- `$1` reads the first field, `$2` reads the second field and so on.

- The variable `$0` refers to the whole record

So, in the picture `cloud existence ministerial` corresponse to `$1` `$2` `$3`

## Basic examples

**_Match a pattern_**

```bash
awk '/book/ { print }' list.txt
# bookworm
# bookcase
```

**_Print all words that are longer that five characters_**

```bash
awk 'length($1) > 5 { print $0 }' list.txt
```

For the first field of every line (we only have one field per line), if it is
greater than 5 characters print it. The "every line" part is provided for via
the all fields variable - `$0`.

We actually don't need to include the `{ print $0 }` action, as this is the
default behaviour. We could have just put `length($1) > 5 list.txt`

**_Print all words that do not have three characters_**

```bash
awk '!(length($1) == 3)' list.txt
```

Here we negate by prepending the pattern with `!` and wrapping it in
parentheses.

**_Return words that are either three characters or four characters in length_**

```
awk '(length($1) == 3) || (length($1) == 4)' list.txt
```

Here we use the logical OR to match against more than one pattern. Notice that
whenever we use a Boolean operator such as NOT or OR, we wrap our pattern in
parentheses.

**_Match and string-interpolate the output_**

```bash
awk 'length($1) > 0  {print $1, "has", length($1), "chars"}' list.txt

# storeroom has 9 chars
# tree has 4 chars
# cup has 3 chars
```

**_Match against a numerical property_**

```bash
awk '$2 >= 90 { print $0 }' scores.txt

# Lucia 95
# Joe 92
# Sophia 90
```

This returns the records where there is a secondary numerical field that is
greater than 90.

**_Match a field against a regular expression_**

```bash
awk '$1 ~ /^[b,c]/ {print $1}' words.txt
```

This matches all the fields in the `$1` place that begin with 'b' or 'c'.

The tilde is the regex match operator. You must be passing a regex to use it,
otherwise use `==`.

## Syntactic shorthands

- For a statement like `awk 'length($1) > 5 { print $0 }' list.txt`. We actually
  don't need to include the `{ print $0 }` action, as this is the default
  behaviour and it is implied. We could have just put `length($1) > 5 list.txt`.

https://zetcode.com/lang/awk/

## Built-in variables

### `NF`

The value of `NF` is the **number** of **fields** in the current record. `Awk`
automatically updates the value of `NF` every time it reads a record.

No matter how many fields there are, the last value in a record can always be
represented by `$NF`.

### `NR`

`NR` represents the **number** of **records**. It is set at the point at which
the file is read.

### `FS`

`FS` represents the **field separator**. The default field separator is a space.
We can specify a different separator with the `-F` flag. E.g to separate by
comma:

```bash
awk -F, '{print $1 }' list.txt
```


title: Queue
last_modified: 2024-10-18 18:55:56
body:
# Queues

_Visualization of the queue data structure_

![queue.svg](queue.svg)

## A queue is a sequential data structure and most similar to a stack

A queue is basically a stack inverted and can be visualised as a line of people
waiting to be served. The person that is first into the queue is served first.
The people who join the queue behind this person will be served after them.

As a result it can be summarised as **'first in, first out'** or FIFO.

Just like a stack it is a sequential data structure without random access. You
cannot access all the elements at one instant, you can only access the oldest
element. If you wish to access the newest element, you have to move through all
the others that are ahead of it first, at which point it becomes the oldest.

It differs from a stack in that a stack only has one point of transaction: the
front or 'top' of the stack. With a stack you add and remove from the top or
front. With a queue, you have two points of transaction: the front of the queue
for removing elements and the back of the queue for adding elements.

We can however add a 'peek' method to see which is the next element in line to
come out.

As we are removing the first element added, we use an `array shift` method to
remove items from the front of the array.

Removing an element from the queue is called **dequeuing**. Adding an element to
the queue is called **enqueuing**. In terms of the tail/head nomenclature, the
end of the queue where elements are enqueued is the **tail** and front of the
queue, where elements are removed is the **head**.

```js
class Queue {
  items = []; // array to store the elements comprising the queue
  enqueue = (element) => this.items.push(element); // add element to back
  dequeue = () => this.items.shift(); // remove element from the front

  // Optional helper methods:
  isEmpty = () => this.items.length === 0; // return true if the queue is empty
  clear = () => (this.items.length = 0); // empty the queue
  size = () => this.items.length; // count elements in queue
  peek = () => (!this.isEmpty() ? this.items[0] : undefined); // check which element is next in line
}
```

## Use cases

- A queue sequences data in the order that it was first received. Thus it is
  most beneficial in scenarios where receipt time is a factor. For example,
  imagine a service whereby tickets go on sale at a certain time for a limited
  period. You may want to prioritise those who sent their payment earliest over
  those who arrived later.
- Serving requests on a single shared resource like a printer or CPU task
  scheduling.


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: Disk_info
last_modified: 2024-10-18 18:55:56
body:
# Disk info routines

## Current disk capacity

```bash
df -h
```

## Disk last modified

```bash
stat [disk_name]
```


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: Python_package_management
last_modified: 2025-01-14 17:44:24
body:
# Package management

- You can use `conda` (the package manager that comes with `anaconda`). This
  makes it easier to work with conflicting package libraries (a bit like a
  package lock).

- The alternative is the native `pip` but you have to create virtual
  environments (`venv`) to manage packages at different versions.

## venv

To make use of virtual environments in `pip` you have to create the virtual
environment before installing anything:

```
python3 -m venv venv
```

This will create a `venv` directory in your project that will manage the
handling of modules.

> This is especially important in ArchLinux since without a virtual environment
> it will ask you to install packages with `pacman` which is annoying for local
> packages.

You then activate the environment with:

```
source venv3/bin/activate
```

Now you can install packages:

```
pip [library_name]
```

### Using venv after a system upgrade

If you update your system's version of Python, this can cause `venv` to stop
working. Resolve as follows:

```
deactivate  # leave venv env if in it
rm -rf venv  # remove venv
python -m venv venv # reinstall venv
source venv/bin/activate
python -m ensurepip --upgrade # explicitly install pip
pip install - e.
```

## requirements.txt

The `requirements.txt` file is similar to the `package.json` in Node projects.

Each line of the `requirements.txt`` file specifies a package that your project
depends on and, optionally, the desired version of that package. When you share
your project with others or move it between different environments (e.g., from
development to production), this file makes it easy to set up your environment
with the right dependencies.

### Example

```
Flask==1.1.2
requests>=2.24.0
numpy~=1.19.2
pandas
```

### Generate requirements file

```
pip freeze > requirements.txt

```

## Pypy

- pypi.org is package registry like NPM


title: Local_AWS_development_with_SAM
last_modified: 2024-10-18 18:55:56
body:
# Local AWS development with SAM

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

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

## Basic set up

Enter your project directory.

First build your SAM application:

```sh
sam build
```

We then run:

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

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

This will be indicated by:

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

If we want to invoke the function directly we use:

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

## Using environment variables

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

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

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

## Create secret

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

## Create local ENV file

> These must be in JSON to work with SAM:

### Local env

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

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

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

### Update `template.yaml`

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

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

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

### Pass in the environment variable at invocation:

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

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

### Create handler within the Lambda itself

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

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

const secretsManager = new AWS.SecretsManager();

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


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: Lists_in_Python
last_modified: 2024-11-01 16:03:11
body:
# Lists in Python

Lists are the equivalent of a simple array in JavaScript.

Lists have the following properties:

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

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

## Duplicating lists

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

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

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

## Basic usage

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

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

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

## Slicing

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

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

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

## Adding additional values to existing list

### Single value

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

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

### Multiple values

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

### Specific index

```python

## Insert at specific index

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

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

## Removing elements

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

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

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

### `del`

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

We can remove multiple items at once via a slice:

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

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

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

### `remove()`

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

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

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

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

### pop()

Remove and return the element removed

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

```

## Return index of a list element

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

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

## Nesting lists

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

## Merging lists

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

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

## Flattening a two-dimensional list (matrix)

Use [list comprehension](List_comprehension_in_Python.md) for each row in the
matrix:

```python
 matrix = [
    [9, 3, 8, 3],
    [4, 5, 2, 8],
    [6, 4, 3, 1],
    [1, 0, 4, 5],
]

flat = [item for row in matrix for item in row]
# [9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
```

## See also

[Sorting lists in Python](Sorting_lists_in_Python.md)


title: Algebra_key_terms
last_modified: 2025-01-16 20:15:41
body:
# Algebra key terms

- **Variable**
  - A symbol that stands for a value which may vary
- **Equation**
  - A mathematical statement that equates two mathematical expressions (states
    that they are the same/ establishes an identity relation)
- **Solution** ^678811
  - A numerical value that **satisfies** an equation. When the variable in the
    equation is replaced by the solution, a true statement results

### Example

$$ 4 = y - 11 $$

The example above is an **equation**. $y$ is the variable. This can be replaced
by $15$ which is the **solution** to the equation:

$$ 4 = 15 -11 $$


title: IP_addresses
last_modified: 2025-01-10 17:09:28
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: http_in_Node
last_modified: 2024-10-18 18:55:56
body:
# `http` module

The HTTP Module allows us to create a web server that listens for HTTP requests
on a given port. It is therefore perfect for creating backends for client-side
JavaScript.

## Creating a server

An HTTP server is another instance of an
[event emitter](events.md)). It
therefore has all the same methods as the `EventEmitter` class: `on`, `emit`,
`addListener` etc. This demonstrates again how much of Node's core functionality
is based on event emitters.

_Creating a server_

```js
const http = require("http");

const server = http.createServer(); // Create server as emitter

// Register functions to run when listener is triggered
server.on("connection", (socket) => {
  console.log("new connection...");
});

server.listen(3000);
console.log("Listening on port 3000");
```

This server is functionally equivalent to a generic event emitter:

```js
// Raise an event
const emitter = new EventEmitter("messageLogged");

// Register a listener
emitter.on("messagedLogged", function () {
  console.log("The listener was called.");
});
```

Whenever a request is made to this server, it raises an event. We can therefore
target it with the `on` method and make it execute a function when requests are
made.

If we were to start the server by running the file and we then used a browser to
navigate to the port, we would see `new connection` logged every time we refresh
the page.

### Sockets and `req, res`

A socket is a generic protocol for client-server communication. Crucially it
**allows simultaneous communication both ways**. The client can contact the
server but the server can also contact the client. Our listener function above
uses a socket as the callback function but in most cases this is quite
low-level, not distinguishing responses from requests. It is more likely that
you would initiate a `request, resource` architecture in place of a socket:

```js
const server = http.createServer((req, res) => {
  if (req.url === "/") {
    res.write("hello");
    res.end();
  }
});
```

#### Return JSON

Below is an example of using this architecture to return JSON to the client:

```js
const server = http.createServer((req, res) => {
  if (req.url === "/products") {
    res.write(JSON.stringify(["shoes", "lipstick", "cups"]));
    res.end();
  }
});
```

### Express

In reality you would rarely use the `http` module directly to create a server.
This is because it is quite low level and each response must be written in a
linear fashion as with the two URLs in the previous example. Instead we use
Express which is a framework for creating servers and routing that is an
abstraction on top of the core HTTP module.


title: Disjunction_Introduction
last_modified: 2024-10-18 18:55:56
body:
# Disjunction Introduction

This rule can seem a little odd: like we are randomly introducing an additional
proposition without giving any justification. However this is just a consequence
of the fact if $P$ is true, so is $P \lor Q$ since disjunction is not the same
as conjunction: only one disjunct needs to be true for the compound disjunction
to be true. This is represented in the context of
[truth-trees](Truth-trees.md#disjunction-decomposition) by the fact that truth
can pass up via either branch of a disjunction pattern. This rule is sometimes
also referred to (confusingly) as _Addition_.

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


title: Classes_in_Python
last_modified: 2024-11-16 13:35:40
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

## Private, protected members and methods

As Python is not strictly typed the property accessors are not actually real and
won't throw errors if violated. However there is a syntax convention.

- Private

  - `__self.property`
  - `def __private method(self)...`

- Protected

- `_self.property`
- `def _protected method(self)...`

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

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

## Contact holes

Beneath the plastic casing, the contact holes are connected to one another
making a continuous circuit connection

## Terminal strips

The contact holes in the middle section, arranged in groups of five and
designated by letters

The terminal strips are not connected accross the ditch thus each of the two
strips are electrically separate regions (A-E and F-J).

## Bus strips

The contact holes on the outer edges of the breadboard, designated by numbers
(1-30). The chief role of the bus strips is to manage the power to the circuit.
You will typically use the positive bus strip on one side for the voltage source
and the negative bus strip on the other side for the ground outlet.


title: Union_types_in_TS
last_modified: 2024-10-18 18:55:56
body:
# Union types

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

Intersection types work well when you have complete knowledge of what form your
data will take. This approach only works if you know in advance that your data
will be and that it will share properties from more than one type. But what if
you don't have this knowledge? You only know that it will be one of the three
possible types? This is where union types become helpful. Instead of saying that
event X will be of type A we say that event X may be one of types A or B or C.

> A union type is a type formed from two or more other types, representing
> values that may be any one of those types. We refer to each of these types as
> the union’s members.

## Demonstration

```ts
type Petrol = {
  maxSpeed: number;
  tankVolume: number; // unique property
};

type Electric = {
  maxSpeed: number;
  maxCharge: number; // unique property
};

type Car = Petrol | Electric;

const tesla: Car = {
  maxSpeed: 120,
  maxCharge: 22,
};
```

- We have defined two custom types: `Petrol` and `Electric`
- We have unified them to make a new custom type of `Car`
- `tesla` is an instance of the union type

## Usage with functions

Union type syntax is not limited to the declaration phase. You can also use them
as parameters to functions or function return types:

```ts
function generateCar(car: Petrol | Electric): void {
  console.log(`The car has a max speed of ${car.maxSpeed}`);
}
```

## Unifying primitive and custom types

We can also use unions with primitive types as well as custom types:

```ts
function printId(id: number | string): void {
  console.log(`Your id is ${id}`);
}
```

We can also combine primitive and custom types in the same union:

```ts
function printStringOrNumber(): string | Petrol {}
```

## Set theory: understanding errors

In JavaScript we have the primitive types of string , number , boolean etc. In
TS, these types are distinct and exclusive: if you declare that x : string you
cannot redefine it as boolean or expect Boolean properties and methods to belong
to a string type. For instance a string type is incapable of having as its value
false.

This is not true in the case of unions.

With a union of `string | boolean` a value `x` could be `'banana'` or `true`. We
are allowing for all values that are either from the set string or the set
boolean. Logically this is equivalent to the OR operator: a value of type
`string | boolean` could be one of infinite possible strings or true/false. With
union types then, we radically widen the scope of possible values for the type.
a

These constraints don't just apply to the values themselves but the methods and
properties you can use with the values. For instance a boolean doesn't have the
property of `length`. Therefore if you have a union type of `string|boolean` you
are going to get an error if you apply `length` to it since **the property must
exist on both types that comprise the union**. The way round this is to use type
narrowing and execute control flow based on the parameter that is passed to a
function that is declared to be of this mixed type.

Here is an example of this:

```ts
function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}
```


title: Hardware_Description_Language
last_modified: 2024-10-18 18:55:56
body:
# Hardware Description Language

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

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

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

## Usage in _NAND to Tetris_

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

## Demonstration of HDL program

### Boolean function to enact

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

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

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

Here is our HDL file:

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

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

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

#### Implementation (`PARTS`)

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

#### Pins

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

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

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

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

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

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

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


title: Monitor_DNS_resolution_and_internet_connectivity_script
last_modified: 2025-01-13 20:54:38
body:
# Monitor DNS resolution and internet connectivity script

```bash
 while true; do
    date
    echo "DNS check:"
    host google.com
    echo "Ping check:"
    ping -c 1 google.com | grep "time="
    echo "---"
    sleep 5
done
```

See [ping](./Ping.md) for more details on what this is doing.

The `host` command contacts a DNS server and does a reverse lookup for the
IP-address of the human-readable domain name.


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

The `useReducer` hook is best used in scenarios where you are manipulating state
in a way that is too complex for the trivial [React_useState](React_useState.md) use case.
`useState` is best employed when you are updating a single value or toggling a
boolean. If you are updating the state of an object or more complex data
structure, it is often more efficient to employ `useReducer`.

This makes the code more manageable and also helps with separating state
management from rendering.

## Syntax

```jsx
const [state, dispatch] = useReducer(reducer, initialState);
```

- `initialState`
  - The starting state, typically an object
- `reducer`
  - A pure function that accepts two parameters:
    - The current state
    - An action object
  - The reducer function must update the current state (immutably) and return
    the new state
  - We can think of the reducer as working in the same manner as
    `state`/`setState` in the `useState` hook. The functional role is the same,
    it is just that the reducer offers more than one type of update.

### Example reducer

```js
function reducer(state, action) {
  let newState;
  switch (action.type) {
    case "increase":
      newState = { counter: state.counter + 1 };
      break;
    case "descrease":
      newState = { counter: state.counter - 1 };
      break;
    default:
      throw new Error();
  }
  return newState;
}
```

In this example we are updating an object with the following shape:

```js
{
    counter: 0,
}
```

This would be the `initialState` that we pass to the `useReducer` hook along
with a reference to `reducer` above.

To update the state we would invoke the `dispatch` function which applies one of
the actions defined in the reducer. For example the following dispatch
increments the counter by one:

```js
dispatch({ type: "increase" });
```

To view the updated value:

```js
console.log(state.counter);
```

### Refining the syntax

Because React doesn't mutate state, the reducer doesn't directly modify the
current state in the `state` variable, it creates a new instance of the state
object on each update.

In the reducer example above this is achieved by declaring a variable `newState`
that is updated by each `action` type and then returned. There is a more elegant
way of doing this using spread syntax:

```js
function reducer(state, action) {
  switch (action.type) {
    case "increase":
      return { ...state, counter: state.counter + 1 };
      break;
    case "decrease":
      return { ...state, counter: state.counter - 1 };
      break;
    default:
      throw new Error();
  }
}
```

### Including payloads

In the examples so far, we have updated the the state directly via the action
type however it is also possible to pass data along with the `action.type` as
`action.payload`.

For example:

```js
dispatch(
  {
    type: 'increase_by_payload'
    payload: 3,
  });
```

Then we would update our reducer to handle this case:

```js
function reducer(state, action) {
  switch (action.type) {
    ...
    case 'increase_by_payload':
    return {...state, counter: state.counter + action.payload}
    default:
      throw new Error();
  }
}
```


title: Function_overloads
last_modified: 2024-10-19 09:57:01
body:
# Function overloads

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

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

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

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

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

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

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

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

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


title: Network_requests_in_Python
last_modified: 2024-10-18 18:55:56
body:
# Making network requests in Python

We can use the `requests` package to make API requests to
[RESTful](RESTful_APIs.md) resources and handle the data as JSON.

```sh
pip install requests
```

Here is a basic architecture for making a `GET` request in Python.

```python
import requests

def get_data(url):
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(
            f"Failed to fetch data from API. Status code: {response.status_code}")

def main():
    url = "https://dummyjson.com/products/1"

    try:
        data = get_data(url)
        print(data)

    except Exception as e:
        prin(e)

if __name__ == "__main__":
    main()
```

## Parsing returned data

Running `main` returns:

```python
{
   "id":1,
   "title":"iPhone 9",
   "description":"An apple mobile which is nothing like apple",
   "price":549,
   "discountPercentage":12.96,
   "rating":4.69,
   "stock":94,
   "brand":"Apple",
   "category":"smartphones",
   "thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg",
   "images":[
      "https://i.dummyjson.com/data/products/1/1.jpg",
      "https://i.dummyjson.com/data/products/1/2.jpg",
      "https://i.dummyjson.com/data/products/1/3.jpg",
      "https://i.dummyjson.com/data/products/1/4.jpg",
      "https://i.dummyjson.com/data/products/1/thumbnail.jpg"
   ]
}
```

This is JSON but in Python is a [dictionary](Dictionaries_in_Python.md)

We can use standard dictionary methods to handle the data. For example, we'll
add to the existing `try` block:

```python
example_key = "brand"  # Replace with the key you want to access from the JSON data
    if example_key in data:
        print(f"Value of '{example_key}':", data[example_key])
    else:
        print(f"'{example_key}' not found in the JSON data")
```

Which, if successful, outputs:

```
Value of 'brand': Apple
```


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: Axioms_of_set_theory
last_modified: 2024-10-18 19:10:26
body:
# Axioms of set theory

The basic notions of set theory are defined in
[Basic properties of sets](Basic%20properties%20of%20sets.md). There we
introduced a formal syntax that will be utilised to define the axioms. For easy
reference:

- variables $a,b,c,...$ to range over sets
- variables $x,y,z$ to range over ordinary objects as well as sets.

## Axiom of Extensionality

Sets which contain the same members are the same set. If sets A and B contain
the same elements then A = B.
$$\forall a \forall b [\forall x (x \in a \longleftrightarrow x \in b) \rightarrow a =b]$$

[link to test](beta.md)


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: 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: The_kernel
last_modified: 2024-10-18 18:55:56
body:
# The kernel

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

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

## Process management

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

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

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

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

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

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

## Memory management

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

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

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

## Device drivers

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

## System calls

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

In Linux there are two particularly important system calls:

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

Example with a terminal program like `ls`:

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

## Controlling processes

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


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

The `zip` function returns a zip object when you pass in two iterator data types
(typically two lists). This is an iterator of tuples where the first item in
each passed iterator is paired together, and then the second item in each passed
iterator are paired together, and so on.

## Example

```py
a = ("John", "Charles", "Mike")
b = ("Jenny", "Christy", "Monica", "Vicky")

x = zip(a,b)

print(x)
# <zip object at 0x1514af34c340>

# To display the resulting tuple in a readable format:

print(tuple(x))

(('John', 'Jenny'), ('Charles', 'Christy'), ('Mike', 'Monica'))
```

As indicated above, if the input iterators are of equal lengths, Python will
ignore the value in iterator B that doesn't have an equivalent index in iterator
A.

## Real-life example

```py
prompts = [
    "Enter title",
    "Enter author",
    "Enter publication date",
    "Enter year you read the book"
]

keys = ["Title", "Author", "Publication_date", "Date_read"]

book = {}

for key, prompt in zip(keys,prompts):
    book[key] = input(prompt + ": ")

```

Here I take the `prompts` and `keys` lists, collate them via `zip` and then loop
through them to populate the `book` dictionary, which contains properties
sourced from the `keys` list and values from the user's answers to the
`prompts`.


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: Frequency_counters
last_modified: 2024-10-18 18:55:56
body:
# Frequency counters

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

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

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

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

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


title: Class_inheritance_in_Python
last_modified: 2024-10-18 18:55:56
body:
# Class inheritance in Python

We distinguish the **parent/source/superclass** class and the **subclass** that
**extends** it.

A subclass can have more than one parent class that it extends, but this is
atypical.

> The subclass will inherit all attributes and methods of the superclass unless
> it overrides them.

In addition to overriding the methods of the parent we can extend them in the
child. This is a bit like using an abstract.

## Example of class inheritance

```py

class Person:
    """ Our superclass
    """
    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)

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


class Employee(Person):
    """ Our subclass that extends `Person`
    """
    def __init__(self, name, age, id):
        super().__init__(name, age)
        self.id = id

    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 __str__(self):
        return super().__str__() + ' - id(' + str(self.id) + ')'
```

### Key points

- We pass the name of the parent class to the child class as a parameter
- We must include all of the parent class's attributes in the constructor of the
  subclass
- We must use a special `super().__init__(..,...)` method to enact the extension
  of the parent. This method references the `__init__` class in the parent.

## Protected/private methods and attributes

> Protected methods and attributes are private to the class but can be accessed
> from any subclass

> Private methods cannot be accessed from anywhere outside of the class

We declare private methods and attributes with a double underscore (`__`). We
declare protected methods and attributes with a single underscore (`_`).

This is **convention**, there are not actually private and protected attributes
or properties.


title: Algorithmic_complexity
last_modified: 2024-10-19 09:56:34
body:
# Algorithmic complexity

![Screenshot_2021-05-11_at_18.55.23.png](Screenshot_2021-05-11_at_18.55.23.png)
_Summary of the main classes of algorithmic complexity_

## Distinguish algorithms from programs

Test commit Algorithms are general sets of instructions that take data in one
state, follow a prescribed series of steps and return data in another state.
Programs are a specific application of one or more algorithms to achieve an
outcome in a specific context. With algorithms, the actual detail of the steps
is mostly abstracted and it is irrelevant to what end the algorithm is being
put. For instance you may create a program that returns addresses from a
database using a postcode. It is irrelevant to the efficiency or value of the
algorithm whether or not you are looking up postcodes or some other form of
alphanumeric string.

## Algorithmic efficiency

Algorithms can be classified based on their efficiency. Efficiency is function
of the runtime speed. However this doesn't always mean that the fastest
algorithms are best.

If we are landing the Curiosity Rover on Mars we may choose an algorithm that is
slower on average for a guarantee that it will never take longer than we find
acceptable. In other cases for example a video game, we may choose an algorithm
that keeps the average time down, even if this occasionally leads to processes
that need to be aborted because they take too long.

We need a generalised measure of efficiency to compare algorithms, across
variant hardware. We can't simply use the number of steps, since some steps will
be quicker to complete than others in the course of the overall algorithm and
may take longer on different machines. Moreover the same algorithm could run at
different speeds on the same machine, depending on its internal state at the
given time that it ran. So we use the following: **the number of steps required
relative to the input.**

> Two given computers may differ in how quickly they can run an algorithm
> depending on clock speed, available memory and so forth. They will however
> tend to require approximately the same number of instructions and we can
> measure the rate at which the number of instructions increases with the
> problem size.

This is what **asymptotic runtime** means: the rate at which the runtime of an
algorithm grows compared to the size of its input. For precision and accuracy we
use the worst case scenario as the benchmark.

So: the efficiency of algorithm _A_ can be judged relative to the efficiency of
algorithm _B_ based on the rate at which the runtime of _A_ grows compared to
its input, compared to the same property in _B_, assuming the worst possible
performance.

From now on we will use the word 'input' to denote the data that the algorithm
receives (in most cases we will envision this as an array containing a certain
data type) and 'execution' to denote the computation that is applied by the
algorithm to each item of the data input. Rephrasing the above with these terms
we can say that 'algorithmic efficiency' is a measure that describes the rate at
which the execution time of an algorithm increases relative to the size of its
input.

We will find that for the runtime of some algorithms, the size of the input does
not change the execution time. In these cases, the runtime is proportional to
the input quantity. In this case, regardless of whether the input is an array of
one hundred elements or an array of ten elements, the amount of work that is
executed on each element is the same.

For other cases, this will not hold true. We will find that there is a
relationship between input size and execution time such that the length of the
input affects the amount of work that needs to be performed on each item at
execution.

## Linear time

Let's start with linear time, which is the easiest runtime to grasp.

We need an example to make this tangible and show how an algorithm's runtime
changes compared to the size of its input. Let's take a simple function that
takes a sequence of integers and returns their sum:

```js
function findSum(arr){
	let total = 0;
	for (let i = 0; i < arr.length; i++){
		total = total += arr[i];
	)
	return total
}
```

The input of this function is an array of integers. It returns their sum as the
output. Let's say that it takes 1ms for the function to sum an array of two
integers.

If we passed in an array of four integers, how would this change the runtime?
The answer is that, providing that the time it takes to sum two integers doesn't
change, it would take twice as long.

As the time it takes to execute `findSum` doesn't change, we can say confidently
that the runtime is as long as the number of integers we pass in.

A more general way to say this is that the runtime is equal to size of the
input. For algorithms of the class of which `findSum` is a member: **the total
runtime is proportional to the number of items to be processed**.

## Introducing asymptotic notation

If we say that it takes 1ms for two integers to be summed, this gives us the
following data set:

| Length of input | Runtime |
| :-------------- | ------: |
| 2               |       2 |
| 3               |       3 |
| 4               |       4 |
| 5               |       5 |

If we plotted this as a graph it is clear that this is equivalent to a linear
distribution:![lin.svg](lin.svg)

Algorithms which display this distribution are therefore called **linear
algorithms**.

The crucial point is that the amount of time it takes to sum the integers does
not increase as the algorithm proceeds and the input size grows. This time
remains the same. If it did increase, we would have a fluctuating curve on the
graph. This aspect remains constant, only the instructions increase. This is why
we have a nice steadily-advancing distribution in the graph.

We can now introduce notation to formalise the algorithmic properties we have
been discussing.

## Big O notation

To express linear time algorithms formally, we say that:

> it takes some constant amount of time ($C$) to sum one integer and n times as
> long to sum n integers

Here the constant is the time for each execution to run and n is the length of
the input. Thus the complexity is equal to that time multiplied by the input.

The algebraic expression of this is $cn$ : the constant multiplied by the length
of the input. In algorithmic notation, the reference to the constant is always
removed. Instead we just use n and combine it with a 'big O' which stands for
'order of complexity'. Likewise, if we have an array of four integers being
passed to `findSum` we could technically express it as O(4n), but we don't
because we are interested in the general case not the specific details of the
runtime. So a linear algorithm is expressed algebraically as $O(n)$ which is
read as "oh of n" and means

> $O(n)$ = with an order of complexity equal to (some constant) multiplied by n

Applied, this means an input of length 6 ($n$) where runtime is constant ($c$)
at 1ms has a total runtime of 6 x 1 = 6ms in total. Exactly the same as our
table and graph. O n is just a mathematical way of saying _the runtime grows on
the order of the size of the input._

> It's really important to remember that when we talk about the execution
> runtime being constant at 1ms, this is just an arbitrary placeholder. We are
> not really bothered about whether it's 1ms or 100ms: 'constant' in the
> mathematical sense doesn't mean a unit of time, it means 'unchanging'. We are
> using 1ms to get traction on this concept but the fundamental point being
> expressed is that the size of the input doesn't affect the execution time
> across the length of the execution time.

## Constant time

Constant time is another one of the main classes of algorithmic complexity. It
is expressed as O(1). Here, we do away with n because with constant time we are
only ever dealing with a single execution so we don't need a variable to express
nth in a series or 'more than one'. Constant time covers all singular processes,
without iteration.

An example in practice would be printing `array[0]` . Regardless of the size of
the array, it is only ever going to take one step, or constant times one. On a
graph this is equivalent to a flat line along the time axis. Since it only
happens for one instant, it doesn't persist over time or have multiple
iterations.

### Relation to linear time

If you think about it, there is a clear logical relationship between constant
and linear time: because the execution time of a linear algorithm is constant,
regardless of the size of n, each execution of O(n) is equal to O(1). Thus O(n)
is simply O(1) writ large or iterated. At any given execution of an O(n)
algorithm n is going to be equal to 1.

## Quadratic time

With the examples of constant and linear time, the total number of instructions
doesn't change the amount of work that needs to be performed for each item, but
this only covers one subset of algorithms. In cases other than O(1) and O(n),
the length of the input **can** affect the amount of work that needs to be
performed at execution. The most common example of this scenario is known as
quadratic time, represented as $O(n^2)$.

Let's start with an example.

```js
const letters = ['A', 'B', 'C'];

function quadratic(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length j++) {
      console.log(arr[i]);
    }
  }
}

quadratic(letters);
```

This function takes an array . The outer loop runs once for each element of the
array that is passed to the function. For each iteration of the outer loop, the
inner loop also runs once for each element of the array.

In the example this means that the following is output:

```
A A A B B B C C C (length: 9)
```

Mathematically this means that n (the size of the input) grows at a rate of n2
or the input multiplied by itself. Our outer loop (`i`) is performing n
iterations (just like in linear time) but our inner loop (`j`) is also
performing n iterations, three `j`s for every one `i` . It is performing n
iterations for every nth iteration of the outer loop. So runtime here is
directly proportional to the squared size of the input data set. As the input
array has a length of 3, and the inner array runs once for every element in the
array, this is equal to 3 x 3 or 3 squared (9).

If the input had length 4, the runtime would be 16 or 4x4. For every execution
of linear time (the outer loop) the inner loop runs as many times as is equal to
the length of the input.

This is not a linear algorithm because as n grows the runtime increases as a
factor of it. Therefore the runtime is not growing proportional to the size of
the input, it is growing proportional to the size of the input squared.

Graphically this is represented with a curving lines as follows:
![square.svg](square.svg)

We can clearly see that as n grows, the runtime gets steeper and more
pronounced,

## Logarithmic time (log n)

A logarithm is best understood as the inverse of exponentiation:

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

When we use log in the context of algorithms we are always using the binary
number system so we omit the 2, we just say log.

> With base two logarithms, the logarithm of a number roughly measures the
> number of times you can divide that number by 2 before you get a value that is
> less than or equal to 1

So applying this to the example of $\log 8$ , it is borne out as follows:

- 8 / 2 = 4 — count: 1
- 4 / 2 = 2 — count: 2
- 2 / 2 = 1 — count: 3

As we are now at 1, we can't divide any more, so $\log 8$ is equal to 3.

Obviously this doesn't work so neatly with odd numbers, so we approximate.

For example, with $\log 25$:

- 25 / 2 = 12.5 — count: 1

- 12.5 / 2 = 6.25 — count: 2

- 6.25 / 2 = 3.125 — count: 3

- 3.125 / 2 = 1.5625 — count: 4

- 1.5625 / 2 = 0.78125

  Now we are lower than 1 so we have to stop. We can only say that the answer to
  $\log 25$ is somewhere between 4 and 5.

  The exact answer is $\log 25 \approx 4.64$

Back to algorithms: $O(\log n)$ is a really good complexity to have. It is close
to O(1) and in between O(1) and O(n). Represented graphically, it starts of with
a slight increase in runtime but then quickly levels off:

![Screenshot_2021-05-11_at_18.51.02.png](Screenshot_2021-05-11_at_18.51.02.png)

Many sorting algorithms run in log n time, as does recursion.

## Reducing O complexity to the general case

When we talk about big O we are looking for the most general case, slight
deviations, additions or diminutions in n are not as important as the big
picture. We are looking for the underlying logic and patterns that are
summarised by the classes of O(1), O(n), O(n2) and others.

For example, with the following function:

```js
function sumAndAddTwo(arr) {
  let total = 0;
  for (let i = 0; i < arr.length; i++) {
    total += arr[i];
  }
  total = total += 2;
}
```

The formal representation of the above complexity would be O(n) + O(1). But it's
easier just to say O(n), since the O(1) that comes from adding two to the result
of the loop, makes a marginal difference overall.

Similarly, with the following function:

```js
function processSomeIntegers(integers){
	let sum, product = 0;

	integers.forEach(function(int){
		return sum += int;
	}

	integers.forEach(function(int){
		return product *= int;
	}

	console.log(`The sum is ${sum} and the product is ${product}`);
}
```

It might appear to be more complex than the earlier summing function but it
isn't really. We have one array (`integers` ) and two loops. Each loop is of
O(n) complexity and does a constant amount of work. If we add O(n) and O(n) we
still have O(n), not O(2n). The constant isn't changed in any way by the fact
that we are looping twice through the array in separate processes, it just
doubles the length of n. So rather than formalising this as O(n) + O(n), we just
reduce it to O(n).

When seeking to simplify algorithms to their most general level of complexity,
we should keep in mind the following shorthands:

- Arithmetic operations always take constant time
- Variable assignment always takes constant time
- Accessing an element in an array by index or an object value by key is always
  constant
- in a loop the complexity is the length of the loop times the complexity of
  whatever happens inside of the loop

With this in mind we can break down the `findSum` function like so:

![breakdown.svg](breakdown.svg)

This gives us:

$$ O(1) + O(1) + O(n) $$

Which, as noted above can just be reduced to O(n).

## Space complexity

So far we have talked about time complexity only: how the runtime changes
relative to the size of the input. With space complexity, we are interested in
how much memory (conceived as an abstract spatial quantity corresponding to the
machine's hardware) is required by the algorithm. We can use Big O notation for
space complexity as well as time complexity.

Space complexity in this sense is called 'auxiliary space complexity'. This
means the space that the algorithm itself takes up, independent of the the size
of the inputs. We are not focusing on the space that each input item takes up,
only the overall space of the algorithm.

Again there are some rules of thumb:

- Booleans, `undefined`, and `null` take up constant space
- Strings require O(n) space, where n is the sting length
- Reference types take up O(n): an array of length 4 takes up twice as much
  space as an array of length 2

So with space complexity we are not really interested in how many times the
function executes, if it is a loop. We are looking to where data is stored: how
many variables are initialised, how many items there are in the array.


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: 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: The_Little_Man_computer
last_modified: 2024-10-19 09:59:33
body:
# The Little Man Computer

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

![LMC_5.gif](LMC_5.gif)

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

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

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

### Working through a basic computation

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

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


title: Python_modules_and_imports
last_modified: 2024-10-18 18:55:56
body:
# Python modules

## Distinguishing _modules_ from _packages_

Modules and packages in Python are very similar to modules in Node.js: a
combination of several pieces of functionality (often written in a mix of
different syntactic styles) bundled together to be used as a unit.

- A **module** is basically just a script - a single Python file that you can
  import using the `import` statement
- A **package** is a _collection of modules_

A package is a whole directory which has an `__init__.py` at its root which
designates it as a package (this can be empty, but it is necessary to have this
file for the interpreter to understand that the directory is a package).

## Creating modules

### Naming modules

The name of a module is the name of its file, e.g. `utils.py` defines the module
`utils`. When we import a module we do not need to specify the extension.

### Example module

Here is an example of a module.

```py
"""utils module"""

print('Hello I am the utils module')


def printer(some_object):
    print('printer')
    print(some_object)
    print('done')


class Shape:

    def __init__(self, id):
        self.id = id

    def __str__(self):
        return 'Shape - ' + self.id


default_shape = Shape('square')


def _special_function():
    print('Special function')

```

## Importing modules

> Whether we are importing our own local module, a third-party module or a
> built-in module native to Python, the syntax is the same.

- If the module is in the same directory as the file you wish to import it into,
  you can just directly import with:
  ```py
  import utils
  utils.specialFunction
  shape = utils.Shape('circle')
  ```
- If the module you want to import is in a subdirectory, you will need to make
  that subdirectory a package in order to be able to import from it. This means
  adding an `__init__.py`. Then you can use the following import syntax:
  ```py
  from subdir import utils
  ...
  ```

### Different import syntax

- If you don't want to have to prepend the imported module's name when using it
  (e.g. `utils.specialFunction`). You can import like so:

  ```py
  from utils import *
  specialFunction()
  ```

- By default an import makes everything in the module available, however you can
  choose to import only a subset. E.g.:
  ```py
  from utils import Shape
  #  or with an alias:
  from utils import Shape as lorem
  ```

## `__main__`

The `__main__` attribute is important in Python.

### Usage in scripts/modules

When you run a script (module) Python assigns the string `__main__` to the
`__name__` attribute to the script that is being executed.

If you run the script as an import into another script, the `__name__` attribute
of the imported module is set to the module name, not `__main__`. This is why,
when we import we say `import [module_name]` and not `import __main__`.

When used with a module, `__main__` indicates code that should be run straight
away when the given script file is run. You won't always want to execute
everything in the module file on execution so you can put this stuff outside of
`__main__`

Typically you will only want immediate execution to happen when **the module is
the entrypoint** to the program.

We indicate this with `__main__` as follows:

```py
if __name__ == "__main__":
    print("This runs only if the module is the main module")
```

### Usage in packages

The only real difference with packages is that you can also define a
`__main__.py` file in the root of the package directory.

This functions in the same manner as `__main__` in an individual module however
this time it is not the entry point for a single script but the entrypoint of
the package as a whole.

> When the package is run directly as a script, the code in `__main__.py` is
> executed, just like it would be if it were a standalone script. This allows a
> package to be run as a script, as well as letting it contain modules that can
> be imported separately.

You can of course still have individual entrypoints for the modules that
comprise the package.

### Running the package as a package

If you want to run the whole package at once, rather than individually importing
the modules that belong to it. You can run the following from the command-line:

```
python -m package_name
```

With this, Python will try to locate the `__main__.py` file and execute it as
though it were a standalone script.

### Using a `main()` function

To ensure that certain code runs on initialisation we can define a special
`main` function or class method that runs when the module or package is
loaded.We would put the main set-up logic in `main` and coordinate the execution
of the module or package through it.

```py
def main():
    printer(default_shape)
    shape = Shape("circle")
    printer(shape)

if __name__ == "__main__":
    main()
```

### Example

```py
'''palindrome module'''

def is_palindrome(inp):
    inp_list = list(inp)
    inp_list_len = len(inp_list)
    for i in range(inp_list_len):
        if inp_list[i] != inp_list[inp_list_len - 1 - i]:
            return False
    return True


def main():
    palindrome_one = "soros"
    palindrome_two = "torot"
    not_palindrome = "chair"

    print(is_palindrome(palindrome_one))
    print(is_palindrome(palindrome_two))
    print(is_palindrome(not_palindrome))


if __name__ == '__main__':
    main()
```


title: Process_module_in_NodeJS
last_modified: 2024-10-18 18:55:56
body:
# The `process` module in Node.js

`process` is a core module and a global object accessible from anywhere in a
Node.js application. It contains functionality that allows us to interact with
information about the current process instance of Node.

For example, we can use it to get environment information, read environment
variables, parse arguments that are passed to the run command, communicate with
the terminal and exit the current process.

## Managing runtime environments

See
[Managing Environments](Managing_environments_in_NodeJS.md).

## Accessing arguments: `process.argv`

We can use `process.argv` to return an array containing the command-line
arguments passed when a Node process is launched. This could be a whole-app
entrypoint (i.e. `index.js`) or a single file we are running.

For instance if we run the following file:

```js
// process-demo.js
console.log(3 + 3);
console.log(process.argv);
```

We get:

```bash
6
[
  '/Users/thomasbishop/.nvm/versions/node/v16.10.0/bin/node',
  '/Users/thomasbishop/prepos/testNode.js'
]
```

The first item is the last executed process. The second is the array of
arguments.

In the array, the first value is a reference to the Node runtime binary. The
second is the file that was passed to Node.

If we passed in a parameter we would get that too:

```
node process-demo.js --fakeFlag
```

Gives us:

```bash
[
  "/Users/thomasbishop/.nvm/versions/node/v16.10.0/bin/node",
  "/Users/thomasbishop/prepos/testNode.js",
  "--fake-flag",
]
```

When writing Node CLI applications we can write functions that parse standard
input. For example:

```js
function parseStandardInput(flag) {
  let indexAfterFlag = process.argv.indexOf(flag);
  console.log(process.argv[indexAfterFlag]);
}
```

Say we ran a program that took in a username:

```bash
node userName.js --user Thomas
```

Then `parseStandardInput("--user")` would give us `"thomas"`

## Standard input and output

Below is the most basic use case: writing to the output.

```js
// alpha.js
process.stdout.write("Hello from file \n");
```

```
node alpha.js
Hello from file
```

Typically we want to write and read data that the user provides. To do this we
need to wait on that event. So we use Node's asynchonous
[event listeners](events.md). We wait
on the `data` event:

```js
process.stdin.on("data", (data) => {
  process.stdout.write(data.toString().trim());
});

process.stdout.write("\n What is your name? \n");
process.stdout.write(` \n > `);
```

This waits for whatever input is given and then echoes it.

Currently there is no way to end this process; it will just keep echoing the
input.

So we add an exit clause:

```js
process.stdin.on("data", (data) => {
  process.stdout.write(data.toString().trim());
  process.exit();
});
```

We can also execute functionality on the exit event:

```js
process.on("exit", () => {
  // Do something
});
```

> Timings can also obviously be managed via the `setTimeout` and `setInterval`
> methods.

### Additional useful methods

```js
process.stdout.clearLine();
process.stdout.cursorTo([int]);
```

### `readline`

The core `readline` module simplifies the handling of standard input and output
by providing a wrapper. It reads the input stream line by line. Basic usage:

```js
var readline = require("readline");

var rl = readline.createInterface(process.stdin, process.stdout);

rl.question("What is your age? ", (age) => {
  console.log("Your age is: " + age);
});
```

Usage scenarios at: https://www.geeksforgeeks.org/node-js-readline-module/


title: Fetch_from_Secrets_Manager
last_modified: 2024-10-18 18:55:56
body:
# Fetch from Secrets Manager

## TypeScript

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

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

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

## Python

```py
import json
import boto3

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


title: Creating_a_RESTful_API_Introduction
last_modified: 2024-10-18 18:55:56
body:
# Creating a RESTful API: Introduction

We are going to use Express to create a
[RESTful API](RESTful_APIs.md) in Node.js.

## Request types

Express provides us with methods corresponding to each of the
[HTTP request types](HTTP_request_types.md):

- `.get()`
- `.post()`
- `.put()`
- `.delete()`

## Our data set

> Typically when you create a RESTful API you are going to be returning data
> from a database. For simplicity we are just going simulate this with a simple
> data array so that we can focus on the Express syntax rather than database
> handling. Later we will integrate this with a
> [MongoDB database](05_%20Integrating_the_database.md).

We will mainly work with the following array of objects:

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

## Creating an Express instance

We first create an instance of Express within `index.js`. This will be the main
coordinating file and we will aim to minimise the amount of business logic we
have in this file. It should really just be for initialization and managing
[middleware](Middleware_in_NodeJS.md).

```js
const express = require("express");
const app = express();
```

## Routing

We are going to receive all our HTTP requests at the path `/api/courses` and we
will place all code related to this route in a dedicated Node module
(`routes/courses.js`) rather than within `index.js` which should remain as
uncluttered as possible.

First we need to declare this in `index.js`:

```js
const courses = require("./routes/courses");
app.use("/api/courses", courses);
```

Then within `courses.js` we instantiate an express router that `app.js` will
route to:

```js
const router = express.Router();
```

Our REST requests will all follow the following pattern:

```js
router.get("/", (req, res) => {
  res.send(courses);
});
```

We target the `Router` instance we created and all paths will be expressed as
`/` since the app will route by default to `/courses/api` from `index.js`

## Create listener

With the routing established and the Express instance created we can now listen
for requests:

```js
app.listen(3000, () => console.log("Listening on port 30000..."));
```

## REST endpoints

We can now proceed to set up our RESTful endpoints:

[GET requests](1_GET.md)

[POST requests](2_POST.md)

[PUT requests](3_PUT.md)

[DELETE requests](4_DELETE.md)


title: Loops_in_Python
last_modified: 2024-10-18 18:55:56
body:
# Loops in Python

## For

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

1. Traditional `for` loop with range:

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

   This is equivalent to the following in JavaScript:

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

2. `for` loop with iterable:

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

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

### Loop in increments greater than 1

The following loop increments by 2 on each iteration

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

# 0  2  4  6  8
```

### Break and continue

#### Break

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

#### Continue

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

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

## While

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

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

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

> There are no `do while` loops in Python


title: Zero_property_of_multiplication
last_modified: 2024-10-19 10:02:28
body:
# Zero property of multiplication

**Let $a$ represent any member of $\mathbb{W}$ or $\mathbb{Z}$ then:**

$$ a \cdot 0 = 0 $$


title: Twos_complement
last_modified: 2024-10-18 18:55:56
body:
# Two's complement

## Summary

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

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

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

## Detail

### Procedural steps

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

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

The procedure for deriving the complement is as follows.

To derive the complement of an unsigned number:

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

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

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

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

### Formal expression

$$
    2^n - x
$$

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

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

$$
    16 - 5 = 11
$$

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

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

$$

  1011 + 0101 = 0000
$$

### Advantages

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

### Limitations

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

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

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

## Related notes

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


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: Find_Bash_command
last_modified: 2024-10-18 18:55:56
body:
# `find`

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

## Main syntax

### No options

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

### Sub-directory

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

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

### Filters

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

#### Type

Filter by type: file or directory

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

Within a specified directory:

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

#### Filename

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

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

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

The same, but case insensitive: `iname`

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

#### Path

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

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

### Operators

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

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

Applied to a directory:

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

## Run programs against results

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

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

### Examples

This script deletes the files that match the filter criteria:

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

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

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


title: a771a6d9_attributes_of_internet_enclosure
last_modified: 2024-11-24 10:52:28
body:
# a771a6d9_defining_internet_enclosure

## Defining _enclosure_

The attempt by capital interests to encircle, control and contain networked
digital communication with the object of extracting financial value from the
exchanges and the community from which they arise.

This constitutes the commodification of a human-constituted resource, like
labour rather than a natural resource, like oil.

## Main characteristics

### Strategic denial of alternative communication media

Where there is a _multiplicity_ of media and within media, a multiplicity of
channels, capital steers communication toward a _single_ entity.

In a "democratic free-market society", capital cannot simply ban alternative
media, it can however work to diminish the influence of alternatives through the
strategic denial of interoperability and the aquisition and subsumption of
rivals into its system.

### Tyrannical control of the rules of communication

Where the standards of communication are elective and established organically
and collaboratively between peers (possibly even democratically), capital
enforces rules derived externally and applied in an authoritarian manner. These
rules are mutable but changes are enacted unilaterally and arbitrarily by the
owners of the medium, not its users. Hence, tyranny in its technical political
sense.

### Total control of information and content

Where individuals share information freely in collaborative enterprise with
diminished concern for ownership (typically favouring citation and iteration as
models), capital takes full possession of the information generated such that it
can be removed and destroyed at will without consultation.

### Surveillance

Where communication is anonymised (loosly or strictly (i.e. through
cryptographic means)), capital _surveils_ all exchanges and the behaviour of
community members.

Typically this is not _personal_ in the sense of being interested in the
specific user's identity, rather it is abstracted into a composite of the
person's preferences which may or may not disclose their legal identity (i.e
metadata).

This characteristic is both an _attribute_ of internet enclosure and its
ultimate reason for existence. It is critical to the mechanism of extraction
since it provides both data that can be sold to other capital interests and
content that can be used to sell to community members as consumers.

### Artificial promotion of commercial content and prolongation of engagement

Where engagement with media is idiosyncratic and organic, capital circumscribes
the field. This is done to prolong engagement.

At one pole this is achieved through the excitation of visceral responses by
artificially favouring content that titillates, disgusts or angers the user. At
the other, it is achieved through mollification and the coddling of the user.
The user encounters only like-minded fellows who affirm and repeat his existing
beliefs and values, creating community and a sense of safety through the
othering of contrary viewpoints. Both techniques are demagogic in nature
familiar from the control mechanisms of cults and extreme political ideologies.


title: Schema_Definition_Language_in_GraphQL
last_modified: 2024-10-18 18:55:56
body:
# Schema Definition Language

SDL is the formal name for the syntax of GraphQL schemas.

## Types

A schema is a collection of object types that contain fields. Each field has a
type of its own. A field's type can be a primitive/scalar value (such as an
`Int` or a `String`), or it can be another object type (just like a custom type
in TS).

A schema's type can be non-nullable which is to say, a required field. We
indicate this with `!`.

A type for a field can be a collection/array of a given type.

The following example indicates these properties:

```gql
type Person {
  age: Int
  name: String
  pets: [Pet]!
}

type Pet {
  species: String
  name: String
  age: Int
}
```

## Queries

A query is also a schema type but of a special sort.

> The fields of this type are entry points into the rest of the schema. These
> are the top-level fields that the client can query for.

For example if we had this type:

```graphql
type Track {
  id: ID!
    author: Author!
  thumbnail: String
  length: Int
  modulesCount: Int
}
```

We could define a type to access a give `Track` as follows:

```graphql
type Query {
  tracksForHomePage: [Track!]!
}
```

Then use this type as the basis for a query:

```

```

## Descriptions

Descriptions are comments that allow you to document your Schema

Single line:

```gql
"Single line comment"
```

```gql
"""
Multi
line
comment
"""
```


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

`any` is a TS-specific type that we can think of it as a higher level parent to
all the other types that exist in TypeScript.

It means in effect that either no type declaration has been asserted or that the
TS compiler cannot infer the type that you mean. Because `any` does not have a
data type it is equivalent to all the individual scalar and reference types
combined. In TS this kind of type is called a **supertype**, and specific types
that actually correspond to a scalar or reference type are known as
**subtypes**. `any`is the supertype of all types and `string` for example is a
subtype of `any`.

> Every value of `string` can be assigned to its supertype`any` but not every
> value of `any` can be assigned to its subtype `string`

You can declare `any` as a type if you wish however it is discouraged because it
effectively undermines the whole purpose of TS. Doing so is basically the same
thing as declaring a value in normal JS - there is no designation at left hand
assignation of which type the data belongs to.

> `any` reflects JavaScript's overarching flexibility; you can see it as a
> backdoor to a world where you want neither tooling nor type safety.

`any` means you can escape errors during development. If you are using custom
types/interfaces and you keep getting an annoying error saying that property X
doesn't exist on type,, `any` will allow you to overcome it until you go back
later and refine.


title: Viewing_remote_changes_without_merging
last_modified: 2024-10-18 18:55:56
body:
# Viewing remote changes without merging

```
git fetch origin/master
git log origin/main
git show origin/master
```


title: RESTful_APIs
last_modified: 2024-10-18 18:55:56
body:
# RESTful APIs

## Definition of an API

An application programming interface (API) is a set of definitions and protocols
for building and integrating application software. It can be thought of as a
contract between an information provider and an informational consumer. The API
is a mediator between the clients and the resources they wish to acquire from a
server or database.

## REST

REST stands for **Representational State Transfer**. It is a set of
_architectural constraints_ on the structure of an API rather than a fixed
protocol. It is a particular way of implementing client-server interaction over
HTTP.

When a request is made from a client to resources via RESTful API, the API
transfers a representation of of the state of the resource to the requester or
endpoint. The information is delivered via HTTP. The format can be of several
types (HTML, XML, plaintext, Python, PHP etc) but is generally JSON because of
its broad compatibility with multiple programming languages.

### Key constraints

In order to qualify as RESTful, an API must meet the following constraints:

1. **Uniform interface**: Possess a client-server architecture with request
   manage through HTTP
1. **Client-server decoupling** : The client and server applications must be
   completely independent of one another. The _only_ information the client
   should know about the server is the URI it uses to request the resource, it
   can't interact with the server in any other way. Likewise, the server
   shouldn't modify the client application in any way (contrast for example SSR)
   other than passing the requested data via HTTP.
1. **Statelessness** Server applications should not be able to store any data
   related to a client request. The request alone should contain all the
   information necessary for processing it, without recourse to any specifics of
   the client application. For example, a specification of POST with a certain
   JSON body and header authentication will be all that is provided to the
   server.
1. **Cacheability** Where possible, resources should be cacheable on the client
   or server side. Server responses must contain information about whether
   caching is allowed for the delivered resource (you see this in the headers in
   the DevTools console). The goal here is to improve performance on the client
   side whilst increasing scaleability on the server side.
1. **Layered system architecture** It may be the case that the data flow between
   the client and the server is not direct. For instance the request may be
   funneled through middleware or another program before it is received by the
   server. Similarly there may be several steps before the client receives the
   requested data. Whilst one should not assume a direct correspondence, REST
   APIs need to be designed so that neither the client nor the server can tell
   whether it communicates with the end application or an intermediary.

## Example

A basic example of a REST API would be a series of methods corresponding to the
main [HTTP request types](HTTP_request_types.md).

| HTTP request type | URI                 | Action                      | Body ?                   |
| ----------------- | ------------------- | --------------------------- | ------------------------ |
| GET               | /api/customers      | Retrieve customers as array | No                       |
| GET               | /api/customers/guid | Get a specific customer     | No, data comes from GUID |
| PUT               | /api/customers/guid | Update an existing customer | Yes                      |
| DELETE            | /api/customers/1    | Delete a customer           | No, data comes from GUID |
| POST              | /api/customers      | Create a new customer       | Yes                      |

// Add: PATCH and explain differenct from PUT


title: Generics_in_TypeScript
last_modified: 2024-10-18 18:55:56
body:
# Generics

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

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

## Basic usage

### Functions

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

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

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

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

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

#### Restricting the available types

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

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

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

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

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

## Interfaces and classes

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

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

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

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

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

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

The following class uses the `KeyValuePair` interface:

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

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

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

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

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

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

Here is an example of instantiating the class:

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

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

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

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

console.log(allItems);

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

## Real examples

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

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

export class GraphQlClient {
  private endpoint: string;

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

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

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

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

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

### VSCode extension TreeView generator

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

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


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

**What is torrenting? Is this the same thing as a peer-to-peer network? How do
these technologies fit within the client-server paradigm?**

Torrenting is a specific application of
[peer_to_peer_networking](Peer_to_peer_network.md) applied to file-sharing.

Each peer on the network both consumes resources and makes resources available
to others.

## Procedure

Large files are divided into small equal sized pieces and each piece is assigned
a unique hash identifier. The `.torrent` file details the number of pieces,
their sizes and hash values.

Clients request different pieces from different peers simultaneously. As pieces
are received, they are verified against their hash. Corrupted or incorrect
pieces are discarded and re-downloaded.

The client software reassembles the pieces in the correct order and the final
file will be identical to the original which can be verified by comparing
hashes.

## Key terms

- _tracker_
  - a server that coordinates communication between peers (so there is a server
    but its role is more lightweight than client-server, coordinating the peers
    rather than hosting or distributing file content)
- _swarm_
  - a group of peers sharing the same file
- _seeding_
  - uploading file pieces to other peers
- _leeching_
  - downloading file pieces from other peers

> An alternative to a tracker server is to use trackerless methods such as
> distributed hash tables

## Terminological clarification

- _BitTorrent_ is the communications [protocols](Network_protocols.md) for P2P
  file sharing. It was also a client software developed by the company
  BitTorrent Inc.

- _torrenting_ is the general terms for that act of sharing files using the
  BitTorrent protocol

## Napster

Napster pre-dated the emergence of the BitTorrent protocol. It did not segment
files and files were downloaded directly from peers. It was a centralised P2P
network because there was a central server that indexed all available files
which kept a database of all connected users and their files.

When you searched Napster you would be querying this DB on the server. The
server would facilitate a direct connection between the consumer and provider
peer for the data exchange.

When users share with BitTorrent the `.torrent` file is the source of truth and
a swarm is created around the users who have possess the file.


title: HTTP_request_types
last_modified: 2024-10-18 19:14:47
body:
# HTTP request types

## GET

- Get data

## POST

- Create data

## PUT

- Update data

## DELETE

- Remove data


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: Application_state_management_with_React_hooks
last_modified: 2024-10-18 18:55:56
body:
# Application state management

Although [React_useReducer](React_useReducer.md) and [React_useContext](React_useContext.md) have
many sound use cases by themselves, when they are combined they offer a way to
acheive a system of global state management, without utilising third-party
libraries like Redux.

## Requirements

The Context API combined with a reducer addresses the following needs:

- be able to consume global state from anywhere in the component hierarchy
  without prop-drilling
- update global state from within child components from anywhere in the
  component hierarchy
- access, maintain and update complex structured data (arrays/objects) not just
  single values, as global state

## Implementation

In essence the approach is as follows:

- Create a context provider that houses the global state object
- Attach this state to a reducer function that operates on the state object
  through dispatch methods

First off, our initial state and overall state object:

```js
const initialState = {
  count: 0,
};
```

Next, we create our context. This is what we will invoke when we want to consume
our state.

```js
export const CounterContext = React.createContext({});
```

Now we need a reducer that will handle the state updates. We will just use the
same setup as we used in the example of
[React_useReducer](React_useReducer.md#refining-the-syntax):

```js
function reducer(state, action) {
  switch (action.type) {
    case "increase":
      return { ...state, counter: state.counter + 1 };
      break;
    case "decrease":
      return { ...state, counter: state.counter - 1 };
      break;
    case "increase_by_payload":
      return { ...state, counter: state.counter + action.payload };
    default:
      throw new Error();
  }
  return newState;
}
```

Finally, we need to make a provider as a top-level component that receives the
reducer's `state` and `dispatch` methods as props:

```js
export const CounterProvider = ({children}) => {
    // We pass our reducer function and initial state to useReducer as params
    const [state, dispatch] = React.useReducer(reducer, initialState)

    // We create our provider and pass the reducer state and update method as props. This is the provider to the CounterContext consumer
    <CounterContext.Provider value={{state, dispatch}}>
        {children}
    </CounterContext.Provider>
}
```

Now whenever we want to consume or update our global state, we can invoke the
context within a lower-level component, for example:

```js
const {state, dispatch} = useContext(CounterContext);

dispatch({
    type 'increase_by_payload',
    payload: 4
})

console.log(state.counter) // 4

```


title: Docker_compose_and_Dockerfile_difference
last_modified: 2024-10-18 18:55:56
body:
# Docker compose and Dockerfile difference

### Key Differences

- **Scope**: A `Dockerfile` is for building a single Docker image, while
  `docker-compose.yml` is for orchestrating multiple containers (that might be
  built from `Dockerfiles`) to work together as a unified application.
- **Use Case**: A `Dockerfile` is necessary when you need to create a custom
  image. In contrast, `docker-compose.yml` is used when you want to deploy and
  manage an application that consists of multiple containers.
- **Command**: You build an image from a `Dockerfile` using `docker build`. You
  start the application defined by `docker-compose.yml` using
  `docker-compose up`.

### Dockerfile

- **Purpose**: A `Dockerfile` is a text document that contains all the commands
  a user could call on the command line to assemble an image. It automates the
  process of creating Docker images by specifying a sequence of steps and
  configurations such as setting environment variables, installing software, and
  copying files from the local directory into the image.
- **Functionality**: It is essentially a blueprint for building Docker images.
  You define the base image, software packages, scripts, files, environment
  variables, and other components you need in your Docker image.
- **Usage**: You use a `Dockerfile` when you need to create a custom Docker
  image based on your application's specific dependencies and runtime
  environment. After defining your `Dockerfile`, you use the `docker build`
  command to create an image.

  Example structure of a Dockerfile:

  ```Dockerfile
  FROM python:3.8
  WORKDIR /app
  COPY . /app
  RUN pip install -r requirements.txt
  CMD ["python", "./my_script.py"]
  ```

### docker-compose.yml

- **Purpose**: A `docker-compose.yml` file is used to define and run
  multi-container Docker applications. With a single command, you can configure
  all of your application's services, networks, and volumes in this YAML file
  and bring everything up or down.
- **Functionality**: It simplifies the deployment and management of application
  components that are meant to run in separate containers while still needing to
  communicate or work together. For example, a web application might consist of
  a web server, a database, and a caching system, each running in its container.
- **Usage**: `docker-compose` is used for defining and running complex
  applications with Docker. Instead of using lengthy `docker run` commands with
  many flags, you write a `docker-compose.yml` file and use `docker-compose up`
  to start the application.

  Example structure of a docker-compose.yml file:

  ```yaml
  version: "3"
  services:
    web:
      build: .
      ports:
        - "5000:5000"
    db:
      image: postgres
      volumes:
        - ./data:/var/lib/postgresql/data
  ```


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

## `cronie`

In Arch Linux I use `cronie` for cron jobs. (There is no cron service installed
by default). Install `cronie` and then enable it in systemd with:

```bash
systemctrl enable --now cronie.service
```

## commands

### List cron jobs

```
crontab -l
```

### Open cron file

```
crontab -e
```

### Check cron log

```bash

journalctl | grep CRON
# Different distros have different loggers
```

## Syntax

```bash
m h d mon dow command
# minute, hour, day of month, day of week, bash script/args
# 0-59, 0-23, 1-31, 1-12, 0-6
```

**Examples**

Run on the hour every hour

```
0 * * * * mysqlcheck --all-databases --check-only-changed --silent
```

At 01:42 every day:

```
42 1 * * * mysqlcheck --all-databases --check-only-changed --silent
```

Every half hour:

```
0,30 * * * * ${HOME}/bash_scripts/automate_commit.sh

```

**Shorthands**

- `@reboot` – Run once, at startup
- `@yearly` – Run once a year, “0 0 1 1 \*”.\</>
- `@annually` – same as @yearly
- `@monthly` – Run once a month, “0 0 1 \* \*”
- `@weekly` – Run once a week, “0 0 \* \* 0”
- `@daily` – Run once a day, “0 0 \* \* \*”
- `@midnight` – same as @daily
- `@hourly` – Run once an hour, “0 \* \* \* \*”

**Examples**

```
@hourly mysqlcheck --all-databases --check-only-changed --silent

```

**View the logs**

```bash
sudo grep crontab syslog

```


title: Network_packet_analysis_tools
last_modified: 2024-12-13 14:41:45
body:
# Network packet analysis tools

- `tcpdump`: View raw packet data (`tcpdump -i wlan0 -XX` for full packet
  contents)
- tshark: Wireshark's CLI for detailed packet analysis (`tshark -i wlan0 -V`)
- `nethogs`: Monitor per-application network usage
- `iftop`: Show bandwidth usage per connection


title: Map_and_filter_to_reduce_in_JS
last_modified: 2024-10-18 18:55:56
body:
# Expressing combined map and filter operations to a single reduce

## `map` + `filter`

The following combines a `map` and a `filter` to square a list of numbers and
then filter the ones that are greater than or equal to 10:

```js
const arr = [1, 2, 3, 4];

const filteredSquaredNumbers = arr.map((x) => x * x).filter((y) => y >= 10);
```

Using `reduce` we can combine these operations more concisely:

```js
const filteredSquaredNumbers = arr.reduce((acc, x) => {
  const squared = x * x;
  if (squared >= 10) {
    acc.push(squared);
  }
  return acc;
}, []);
```

## `filter` + `map`

Here we use a filter and then a map to filter the even numbers and then double
them:

```js
const arr = [1, 2, 3, 4];

const evenDoubled = arr.filter((x) => x % 2 === 0).map((y) => y * 2);
```

Using `reduce` :

```js
const evenDoubled = arr.reduce((acc, x) => {
  if (x % 2 === 0) {
    acc.push(x * 2);
  }
  return acc;
}, []);
```

> The empty array [] is the initial value of the accumulator. As the callback
> function processes each element of the array, it can add or modify the
> accumulator based on the desired logic. By providing an empty array as the
> initial value for the accumulator, we ensure that the results will be
> collected in a new array without modifying the original array.


title: ICMP_Protocol
last_modified: 2025-01-13 20:40:58
body:
# ICMP Protocol

The Internet Control Message Protocol operates alongside the
[Internet Protocol](Internet_Layer_of_Internet_Protocol.md) at the
[Transport Layer](Transport_Layer_of_Internet_Protocol.md).

It doesn't carry any actual user data and thus there is no
[Application Layer](Application_Layer_of_Internet_Protocol.md) processes above
it. It's usage is primarily for diagnostics and debugging. When network problems
occur, ICMP generates messages to report errors back to the source.

Probably the most familiar tools related to ICMP is [Ping](Ping.md).


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: Singleton_pattern
last_modified: 2024-10-18 18:55:56
body:
# Singleton pattern

As the name suggests, the Singleton pattern is best used in scenarios where you
only want to use a single, unique instance of a class. This is for functionality
that should only be executed once in the lifecycle of an application. The
canonical case would be some kind of bootstrapping process required at
initialisation, or initial configuration for an app.

## Use-cases

- Establishing connection to backend database
- API connection (managing tokens so that you are not sending multiple
  unnecessary calls)
- Configuration settings for an app
- Global state stores in Redux and the React Context API

> With this pattern only one instance of the class can exist. If no instance of
> the singleton class exists then a new instance is created and returned, but if
> an instance already exists, then the reference to the existing instance is
> returned. A singleton should be immutable by the consuming code, and there
> should be no danger of instantiating more than one of them.

## Implementation

This pattern can be implemented either with a object or a class.

In the example below, a global store for a modern JS application is created
using an object.

### Using an object

We ensure singularity by:

- using `const` for the object so that it cannot be reassigned
- using `Object.freeze` so that the fields cannot be overwritten, deleted, or
  additional fields added

```js
const _data = [];

// Methods to add and retrieve data from the store
const UserStore = {
  add: (item) => _data.push(item),
  get: (id) => _data.find((d) => d.id === id),
};

Object.freeze(UserStore);
export default UserStore;
```

### Using a class

The same process can be created using classes:

```js
class UserStore {
  constructor() {
    this._data = [];
  }

  add(item) {
    this._data.push(item);
  }

  get(id) {
    return this._data.find((d) => d.id === id);
  }
}

const instance = new UserStore();
Object.freeze(instance);

export default instance;
```

Bear in mind that this is really just an ES6 module with `freeze` applied. The
only real difference between a singleton and a module is that there should only
be one instance of it.

With classes, there is a further step that can be taken to prevent more than
once instance of the class being created. This is necessary if you want to be
really strict because it is of course possible for a well motivated person to
get around the safeguards we established above. We do this by having the class
itself check whether there is already an instance of itself. If so, it just
returns the already existing instance. We do this by using the self-reference
inherent in `this` :

```jsx
class UserStore {
  constructor() {
    if (!UserStore.instance) {
      this._data = [];
      UserStore.instance = this;
    }

    return UserStore.instance;
  }

  //rest is the same code as preceding example
}

const instance = new UserStore();
Object.freeze(instance);

export default instance;
```


title: cfbef1c4_web_precursors
last_modified: 2024-10-24 17:28:51
body:
# cfbef1c4_web_precursors

## Early precursors to the commercial internet

Prior to the commercial internet and the advent of the web, there were services
in the 1980s which allowed home computers to connect to limited public networks.
Example services: checking one's bank balance, paying bills, transferring money,
booking holidays.

(Presumably, the services would be hosted on a providers'
[mainframe](Mainframe_computers.md) or mini-computer, which the consumer would
access through something like time-sharing?)

![BT telephone modem](../img/modem-bt.png)

The mechanism of connection was via a modem. A device that connected to your
phone's cradle. Digital signals from your home computer would be converted into
sound signals ("tones") that would travel through the phone transmitter to a
receiver at the service provider which you would "dial-up" beforehand.

In the US the main provider was Micronet in the 1970s, which became Compuserve
in the 1980s and eventually AOL in the 1990s. In the UK there was Prestel
provided by BT, first introduced in 1971.

These services had an expensive subscription model and consequently uptake was
limited.

In addition there were bulletin board systems (BBS), typically administrated by
hobbyists. Their personal computers acted as servers that other users would
dial-up to access. They permitted the sharing of news and messages and later
chat-rooms for realtime communication. Some of the biggest BBSs later became the
first ISPs.


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: File_permissions_and_execution_in_Bash
last_modified: 2024-10-18 18:55:56
body:
# File permissions and executables

Every Unix file has a set of permissions that determine whether you can read,
write or run (execute) the file.

## Viewing file permissions

In order to see file permissions within the terminal, use the `-l` or `-rfl`
with the `ls` command. Remember this command can be applied at both the
directory and single-file level. For example:

```bash
drwxr-xr-x  7 thomas thomas 4096 Oct  2 19:22 angular-learning-lab
drwxr-xr-x  5 thomas thomas 4096 Oct 17 18:05 code-exercises
drwxr-xr-x  5 thomas thomas 4096 Sep  4 16:15 js-kata
drwxr-xr-x  9 thomas thomas 4096 Sep 26 18:10 sinequanon
drwxr-xr-x 12 thomas thomas 4096 Sep 19 17:41 thomas-bishop
drwxr-xr-x  5 thomas thomas 4096 Sep  4 19:24 ts-kata
```

### What the output means

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

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

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

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

## Modifying permissions: `chmod`

We use `chmod` for transferring ownership and file permissions quickly from the
command-line.

### Octal notation

`chmod` uses octal notation. Each numeral refers to a permission set. There are
three numerals. The placement denotes the user group. From left to right this
is:

- user
- group
- everyone else.

If you are working solo and not with group access to files, you can disregard
assigning the other numerals, by putting zeros in as placeholders.

[Permission codes](685254916b2642f189e6316b876e09c9)

### Example

```bash
$ chmod -v 700 dummy.txt
$ ls -l dummy.txt
$ -rwx------  1 thomasbishop  staff  27 13 May 15:42 dummy.txtExample
```

### Useful options

`-v` → verbose: tell the user what `chmod` is doing

`-r` → work recursively, i.e apply the action to directories as well as files

`-f` →silent: suppress most error messages

## Running bash files

In most cases, especially when you are working alone, the most frequent codes
you are going to need are 700 and 600. When shell scripting, you need to make
your scripts executable for them to work, therefore you should always
`chmod 700` when creating a `.sh` file.

Then to invoke the script from the shell you simply enter:

```bash
./your-bash-script.sh
```


title: Inversion_of_operators
last_modified: 2024-10-18 19:15:36
body:
# Inversion of operators

## Addition, subtraction

Addition is the inverse of subtraction: $$(x - a) + a = x$$ $$ (6 - 2) + 2 = 6

$$

Subtraction is the inverse of addition: $$(x + a) - a = x$$ $$ (3 + 2) - 2 = 3$$

Division is the inverse of multiplication $$ \frac{a \cdot x}{a} = x$$
$$ \frac{6
\cdot 3}{6} = 3
$$

Multiplication is the inverse of division $$ a \cdot \frac{x}{a} = x$$

$$
2 \cdot
\frac{8}{2} = 8
$$


title: Internet_fundamentals
last_modified: 2024-10-18 18:55:56
body:
# Internet fundamentals

The internet is a _[network](Network_fundamentals.md) of networks_. A globally
connected set of computer networks that all use a suite of common
[protocols](Network_protocols.md).

The protocols governing the communication between networks on the internet are
specified in the **Internet Protocol Suite**.

This combines two protocols:

- **TCP**
  - The Transmission Control Protocol
- **IP**
  - The Internet Protocol

Hence the Internet Protocol Suite is also known as "TCP/IP".

The TCP/IP is a network stack comprising four layers, each with its own
protocols. From the top:

![TCP/IP stack diagram](../img/TCP_IP.png)

- The [Link Layer](Link_Layer_of_Internet_Protocol.md) concerns communication
  between devices on the same local network. The transfer of information once it
  has been received from the internet or its transfer between devices before it
  sent out into the internet. Ethernet and WiFi are two core protocols utilised
  at this layer.

- The [Internet Layer](Internet_Layer_of_Internet_Protocol.md) is concerned with
  communication accross networks. It is responsible for identifying hosts on the
  inter-network by their addresses and routing data from network to network.

- The [Transport Layer](Transport_Layer_of_Internet_Protocol.md) creates a
  commmunication channel between two hosts using ports and sockets.

- The [Application Layer](Application_Layer_of_Internet_Protocol.md) concerned
  with software that utilises information received and sent over the internet,
  for instance web pages and emails. This is the highest level of abstraction
  for which all the lower levels serve as "plumbing". Examples of protocols
  operative at the Application Layer include HTTP and SSH.

See:
[Example_scenario_internet_data_transfer](Example_scenario_internet_data_transfer.md)


title: What_can_the_ISP_see
last_modified: 2024-12-13 14:38:36
body:
# What can the ISP see?

## Plain HTTP

The ISP can see all content exchanged: DNS lookups, IP address of the server you
connect to, requests and body content.

## Just HTTPS

With HTTPS alone, the content of the messages is encrypted but the ISP can see:

- DNS queries
- IP addresses you connect to
- Traffic patterns and metadata (when, how often, amount of data exchanged etc)

## HTTPS and encrypted DNS server

With HTTPS and an encrypted DNS server (e.g. Quad9) the DNS queries are hidden
but the IP addresses are not. So they could still derive your DNS lookups from
the IPs you end up connecting to.

## HTTPS and VPN

The ISP only sees the encrypted traffic to the VPN server. Your real IP and
destination IP is hidden.

However the VPN provider can potentially see DNS queries, depending on their
policy. Although a good VPN provider will encrypt DNS lookups like an encrypted
DNS server.

The VPN can also still see what IPs you are connecting to. You are basically
swiching trust from the ISP to the VPN provider. However reputable VPNs do not
have your billing and identity info (and payment can use cryptocurrencies) and
don't keep logs. Many also have policies about refusal to cooporate with
authorities in different juristictions. Also the VPN doesn't know your real IP.

## HTTPS, VPN, and encrypted DNS

DNS queries hidded from both ISP and VPN. Traffic is fully encrypted and routed
through VPN.

## Fingerprinting

Note that none of the above stops fingerprinting. Even with full encryption
there are methods of determining online behaviour through encrypted packet
analysis and usage patterns.

A way round this is to use SOCKS proxies which add hops or to use TOR. TOR
effectively solves the "have to trust VPN provider" problem.

With TOR:

- Traffic routed through 3+ nodes
- Each node only knows previous and next hop
- No single node knows both source and destination. Even Tor nodes can't see the
  full path

It also uses:

- Fixed packet sizes
- Timing obfuscation
- Traffic padding

Which makes pattern analysis and fingerprinting much harder than just a VPN.

Furthermore there is also the following protection from VPN weaknesses:

- No single provider to trust/compromise
- Decentralized network
- Free to use and no account needed
- No permanent exit node like with VPN


title: Step_functions_and_state_machines
last_modified: 2024-10-18 18:55:56
body:
# AWS Step functions and state machines

_AWS Step Functions_ is an AWS service that allows you to coordinate distributed
applications and microservices by creating workflows.

Workflows are made up of a series of steps and are known as **state machines**.
A state machine defines:

- the states of the workflow
- the transitions between states
- the inputs and outputs of each state

State machines are defined via a JSON object which specifies the states and the
transitions between them. It also includes error catchers and retry logic.

At the beginning you define a `StartAt` state which is the entrypoint of the
state machine. This can be manually triggered, or more likely, triggered by
another AWS service such as a
[Lambda](Lambda_programming_model.md), an
[API Gateway](AWS_API_Gateway.md) request or a messaging/queue
event.

The state machine ultimately ends at an end state. In between are various
intermediary states which can include:

- Task states (do something)
- Choice states (do something based on conditions)
- Wait states
- Success states (if preceding state succeeds do X)
- Fail states (if preceding state fails do Y)
- Parallel states

The diagram below shows the definition of a given state machine. On the left is
the JSON specification. On the right is a diagramatic representation that shows
the control flow and all possible steps that comprise a given pathway / state:

![](/img/state-machine-definition.png)

For each execution of the state machine (each time it is triggered) you can
review the runtime. The flow diagram will highlight green to show the given
pathway, and you can also see the inputs and outputs for each step and any
errors:

![](/img/state-machine-execution.png)


title: Python_interpreter
last_modified: 2024-10-18 18:55:56
body:
# Python interpreter

## What is an implementation of a programming language?

An implementation of Python:

> means a program or environment which provides support for the execution of
> programs written in the Python language

As Python is interpreted, any implementation will perforce involve an
interpreting language.

There are three main implementations:

- CPython
  - The default implementation, written in C
- Jython
  - Python implemented by Java and running on the JVM
- PyPy
  - A subset of Python which is faster due to machine code compilation

## CPython

In the standard implementation, the language which interprets Python code is C.

When Python runs in this implementation, code written in C converts it to
bytecode (so-called because each instruction is
[8-bits long](Binary_units_of_measurement.md)). This is a
lower-level transliteration of Python is not meant to be understood by the CPU
(since it is not binary) but rather to be run in the Python virtual machine
which is equipped to understand bytecode. The Python Virtual Machine is
therefore also known as "bytecode interpreter" or the CPython VM. The VM then
converts bytecode into machine-executable code.

The fact that Python does not compile to binary means that it can run on a
greater variety of hardwares however it is slower than a language that compiles
to machine code. PyPy is a solution for this because it is both interpreted and
compiled to optimised machine code at run time.

Where CPython is the interpreter, Python extensions can be written in C.

## PyPy

PyPy is an alternative implementation to CPython. The main difference is that it
uses a JIT-compiler and doesn't convert Python to the intermediary byte code. It
is also written in Python itself, specifically RPython ('restricted Python').
This is capable of converting Python directly to machine code without the
CPython virtual machine.

## Jython

Jython is an implementation of Python written in Java. Like CPython it converts
Python to bytecode but this runs on the JVM. As it is written in Java, a Jython
program can import and use any Java class.

Where Jython is the interpreter, Python extension can be written in Java.


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

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

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

## What is a latch?

A latch is a circuit component that wor

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

The SR Latch goes through the following state changes:

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

This is represented more clearly in the table below:

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

The most succinct account of a latch:

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

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

![](/img/sr_latch_diagram.png)

## Creating a latch circuit

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

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

The circuit is created as follows:

![](/img/sr_latch_logic_circuit.png)

Interactive version:

<iframe src="https://circuitverse.org/simulator/embed/nor-latch-0869192c-7d7b-4161-b13f-3f72c1bce8e9" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="400" width="600" allowFullScreen></iframe>

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

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


title: Stale_branches_and_pruning
last_modified: 2024-10-18 18:55:56
body:
# Stale branches and pruning

A **stale branch** is a
[remote tracking branch](Remote_tracking_branches.md) that **no
longer tracks anything** because the actual branch on the remote has been
deleted.

If _you_ delete the remote branch, the remote tracking branch will also be
deleted. However if a _collaborator_ deletes the remote branch, your remote
tracking branch will remain, becoming a stale branch.

In order to get rid of it, we need to manually remove it. We can do this with
the general `prune` command:

```sh
git remote prune origin

# or run a preview of the action

git remote prune origin --dry-run
```


title: Stacks
last_modified: 2024-10-18 18:55:56
body:
_A stack visualised vertically_ ![stack2.svg](stack2.svg)

_A stack visualised horizontally_ ![stack1.svg](stack1.svg)

## A stack is a linear data structure that observes LIFO

Think of a stack like a pile of books: the last book that you add is the nearest
to the top and therefore the first one that you can remove.

If you want to get a book that is not at the top, you have to first remove the
books that are above it to get to it.

This type of data structure is linear and only allows sequential, not random,
access. It is an example of 'last one in first one out'.

## We can build a stack from an array

A stack is an example of a data structure that can be built by adapting an
array. If you think about it, all that is needed is an array to store the data,
an `array push` method to add elements to the 'end' or the 'bottom' of the stack
and an `array pop` method to remove the element at the top.

## Demonstration

Below we create a stack constructor, using a class. An object created from this
template will have the following properties and methods:

- `items[]` → an array to store the data
- `push()` → a method to add an element to the end of the stack
- `pop()` → a method to remove an element from the front

In addition we have the following helpers, which allow us to check the status of
the stack and retrieve information about it:

- `isEmpty()` → check if the stack is populated or not
- `clear()` → empty the stack of its content (therefore making `isEmpty()`
  return `true`)
- `size` → a property corresponding to the stack's length

```js
class Stack {
  items = []; // the array that will store the elements that comprise the stack
  push = (element) => this.items.push(element); // add an element to the end of the stack
  pop = () => this.items.pop(); // remove and return the last element from the stack

  // We can add some useful helper methods, that return info about the state of the stack:
  isEmpty = () => this.items.length === 0; // return true if the stack is empty
  clear = () => (this.items.length = 0); // empty the stack
  size = () => this.items.length; // count elements in stack
}
```

## Run through

```js
let stack = new Stack();
test.push(1); // Add some elements to the stack
test.push(2);
test.push(3);

// Stack now looks like:
console.log(stack.items); // [1, 2, 3]

// Let's try removing the last element
stack.pop(); // 3 -> this was the last element we added, so it's the first one that comes out

// Now the stack looks like this:
// [1,2]

// Let's add a new element
test.push(true);

// Now the stack looks like:
// [1,2, true]
```

## Practical applications

- Any application that wants to got 'back in time' must utilise a stack. For
  example, the 'undo' function in most software is a function of a stack. The
  most recent action is at the top, and under that is the second most recent and
  so on all the way back to the first action.
- Recursive functions: a function that calls itself repeatedly until a boundary
  condition is met is using a stack structure. As you drill down through the
  function calls you start from the most recent down to the last.
- Balancing parentheses. Say you want to check if the following string is
  balanced `[()]` . Every time you find an opening parentheses. You push that to
  the front of a stack. You then compare the closing parentheses with the order
  of the stack. The same could be done when seeking to find palindromes. This
  sort of thing could be a code challenge so build an example.


title: Network_scanning
last_modified: 2024-10-18 18:55:56
body:
# Network scanning

## List all connected devices on local WiFi network

```sh
arp-scan --interface=wlan0 --localnet
```

## Scan subnet for all connected devices (in case hiding)

```sh
nmap -sn 192.168.0.0/24
```

## Check DNS resolution

```sh
dig google.com
```

Key output:

```
;; Query time: 6 msec
;; SERVER: 192.168.0.4#53(192.168.0.4) (UDP)
;; WHEN: Wed Jun 12 18:27:27 BST 2024
;; MSG SIZE  rcvd: 55
```


title: 3f1b9fe5_motivation_Berners_Lee
last_modified: 2024-10-28 15:41:28
body:
# 3f1b9fe5_motivation_Berners_Lee

TBL describes his motivation in creating the WWW as the desire to marry two
separate concepts/technologies: hypertext and the Internet, after both had come
of age.


title: Clock_signals
last_modified: 2024-10-18 18:55:56
body:
# Clock signals

In the examples of digital circuits so far (i.e
[adders](Half_adder_and_full_adder.md)
and [latches](Latches.md)) everything
happens in a single instant or over several repeated instances. This is because
of how simple the circuits are. In the case of latches only a single bit is
updated. And even with rippled adders they are just a series of 1-bit updaters
in a chain.

With more complex circuits that use multiple memory devices which store a series
of bits at once, we need a way to ensure that the bits are set at the same time.
We do this by sequencing the execution with the pulses of the system clock.

A single iteration of the volatage rising and falling is a **pulse**. A complete
oscillation from low to high and back to low is a **cycle**. As with all
[electromagnetic](Electromagnetism.md)
signals we measure the frequency of the wave in Hertz: cylcles per second. We
also further distinguish the rising and falling edge of a pulse. Rising
represents the signal passing from ground to its maximum voltage and falling is
the reverse (the electrons moving from the voltage source to ground).

The diagram below shows a pulse cycle of 2Hz.

![](/img/clock_pulses.png)

## Linking components to the clock

- All components that need to be synchronised are connected to the clock
- State changes in the component occur only when a clock pulse occurs
- Clock-driven components will typically trigger their state changes on either
  the rising edge or the falling edge of the pulse.
- Components that trigger state changes on the rising pulse are **positive
  edge-triggered**
- Components that trigger state changes on the falling pulse are **negative
  edge-triggered**

The role of the clock is essential in the functioning of the
[CPU](CPU_architecture.md#the-system-clock). It is
the system clock that gives CPUs their performance rating: how many processes
can execute within a given clock cycle.


title: Database_indices
last_modified: 2024-10-18 18:55:56
body:
# database indices

A database index is best understood by analogy with physical data. Imagine
needing to find information about a specific topic in a very long book. Without
being able to turn to the back and consult the index we would need to scan every
page of the book looking for information on the topic we are seeking.

Similarly with a database if we are looking for a specific value in a very long
table it can be computationally costly and slow to iterate through every row
looking for a specific value (this is known as a "full table scan").

For this reason we use indexes. In a database an index is like a shortcut or
quick reference guide. However instead of page numbers as in the book analogy,
the database index has pointers to the actual location of the rows in the table.
If an index is applied to a given column, when an element in that column is
requested, the database will not scan every row, instead it will go straight to
the element using the index.

Say we are looking for the surname "Bishop" in a table that consists of
`lastName` and `phoneNumber` . If we apply an index to the `lastName` column,
the database will have a direct reference to the row that "Bishop" is in and
thus can return the `phoneNumber` value more quickly.

Although the programmer can explicitly create indexes, the way the database
utilises it is largely abstracted from view. Most RDMSs will automatically
decide which index to use for query optimisation however the programmer can also
use hints to force the use of a specific index.

The main thing is to think about data points that would benefit from an index,
for instance for the most heavily used queries or those that run against the
longest tables.

While indices make lookups more performant, they should be used judiciously as
they increase the storage space the database requires and can incresae the
execution time of queries that insert or update records. (This makes sense, as
any update to a row, requires a reindexation of the table.)

The best use case for an index is for data retrieval that utilises `WHERE`
clauses.

## SQL syntax

```sql
CREATE INDEX index_name ON table_name(column_name)
```


title: Type_hinting
last_modified: 2024-12-29 13:56:37
body:
# Type hinting in Python

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

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

## Basic syntax

### Variables

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

### Functions

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

### Classes

// TODO: Add examples

## Complex data types

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

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

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

## Custom dictionary types

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

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

We could then use it thus:

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

### Explicit type construction

Sometimes, simply asserting that custom type will be returned by a function is
not sufficient to pass the type checks. In these cases you need to explicitly
construct the type within the function.

For example this generates an error as Python sees the `tags` type as
`[Dictionary]`, even though it matches the type:

```python
class IGraphNode(TypedDict):
    id: str
    type: str

```

```py
tags = [{"id": f"#{tag[0]}", "type": "tag"} for tag in tags]
```

So you have to explicitly generate the type like this:

```python
tags = [IGraphNode(id=f"#{tag[0]}", type="tag") for tag in tags]
```

Then you will be able to type the return of the function as
`-> list[IGraphNode]`

## Optional types

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

```py
from typing import Optional

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

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

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

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

## Union types

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

```py
from typing import Union

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

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

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

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

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

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

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

```py

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


title: User_Space
last_modified: 2024-10-18 18:55:56
body:
# User space

User space is the portion of the main memory that the kernel allocates for user
processes.

There is a hierarchy within the user space with several different groups of user
processes:

<dl>

<dt>Basic services</dt>
<dd>Run at the bottom level and therefore are closest to ther kernel. Comprises small components that perform single, uncomplicated tasks. Examples include network configuration, the communication bus, and diagnostic logging</dd> 
<dt>Utiility services</dt>
<dd>The middle level: larger components such as mail, print, and database services</dd>
<dt>Services controllable by the user</dt>
<dd>The top level: complicated tasks that the user controls directly such as using a web-browser, editing a text file</dd>
</dl>

## What is a user?

> A user is an entity that can run processes and own files.

At the level of users, a user is associated with a user name however the kernel
does not manage usernames, it identifies users via numeric identifiers called
**user IDs**.

Users exist in order to support permissions and access boundaries. A user may
terminate or modify the behaviour of its own processes (within limits) but
cannot interfere with another user's proceses. In addition, users may own files
and choose whether to share them with other users.

### Root user (superuser)

A Linux system normally has a number of users in addition to the ones that
correspond to the real human users. The most important is **root**. The root
user _can_ terminate and alter another user's processes and access any file on
the local system.

A person who can operate as root is an administrator on a typical user system.


title: Shell_sessions
last_modified: 2024-10-19 09:57:41
body:
# Shell sessions

## **Types of shell session**

Shell sessions can be one of or several instances of the following types:

- **login shell**
  - A session that must be authenticated such as when you access remote
    resources using SSH
- **non-login shell**
  - Not the above
- **interactive shell**
  - A shell session that runs in the terminal and thus that the user can
    _interact_ with
- **non-interactive shell**
  - A shell session that runs without a terminal

If you are working with a remote server you will be in an **interactive login
shell**. If you run a script from the command line you will be in a
**non-interactive non-login shell**.

## Shell sessions and access

The type of shell session that you are currently in affects the
[environmental and shell variables](Environmental-and-shell-variables-04d5ec7e8e2b486a93f002bf686e4bbb)
that you can access. This is because the order in which configuration files are
read on initialisation differs depending on the type of shell.

- a session defined as a non-login shell will read `/etc/bash.bashrc` and then
  the user-specific `~/.bashrc` file to build its environment.
- A session started as a login session will read configuration details from the
  `/etc/profile` file first. It will then look for the first login shell
  configuration file in the user’s home directory to get user-specific
  configuration details.

In Linux, if you want the environmental variable to be accessible from both
login and non-login shells, you must put them in `~/.bashrc`


title: Push_buttons
last_modified: 2024-10-18 18:55:56
body:
# Push buttons

Push buttons are push-to-make switches. This means that they are **on when they
pressed and off when they are not pressed**. A classic example would be the
switches on a keyboard.

Push buttons have four legs that are connected in signal-in and signal-out
pairs.

To create a breakable circuit we would connect a signal-in cable to the top left
at row 23 and a signal-out cable to the bottom-right at row 25.

![Push button legs diagram](../img/push-button-legs.png)

_The correct use of a push button where the button breaks the circuit_

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

To override the switch functionality and just have the button work as a
connector we would connect the signal-in to the row 23 input and the row 23
output.

_The push button being used as simple connector which does not break the
circuit:_

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

## Related notes

https://projects.raspberrypi.org/en/projects/button-switch-scratch-pi/1


title: Wildcards_in_SQL
last_modified: 2024-10-18 18:55:56
body:
# Wildcards in SQL

SQL does not use Regex. Instead it has a simpler glob-like syntax for carrying
out string matching.

In order to signal that you wish to compare by a wildcard and not a value, you
have to use the `LIKE` keyword. The actual wildcard operator is `%` .

In an SQL statement, the `%` wild card will match any number of occurrences of
any character. Any characters can appear before or after ‘MacBook’ and the
record will still be returned:

```sql
SELECT name, cores, release_date
FROM model
WHERE name LIKE '%MacBook%';
```

This wildcard only filters characters that come after `Raspberry` :

```sql
SELECT name, cores, release_date
FROM model
WHERE name LIKE 'Raspberry%';
```


title: Memoization_in_React
last_modified: 2024-10-18 18:55:56
body:
# Memoization with useCallback and useMemo

## Rationale

In the lifecycle of a component, React re-renders the component when an update
is made. When React checks for any changes in a component, it may detect an
unintended or unexpected change due to how JavaScript handles equality and
shallow comparisons. This change in the React application will cause it to
re-render unnecessarily.

Additionally, if that re-rendering is an expensive operation, like a long for
loop, it can hurt performance. Expensive operations can be costly in either
time, memory, or processing. In addition to potential technical issues, this may
lead to poor user experience.

If one part re-renders, it re-renders the entire component tree. `useCallback`
and `useMemo` can be used reduce this performance impact.

## useCallback

The `useCallback` hook is used to wrap functions. It tells React to not
re-create a wrapped function when a component re-renders, unless any of the
useCallback's dependencies change.

`useCallback` returns a memoized version of the callback function it is passed.
This means that the function object returned from useCallback will be the same
between re-renders.

Remember that in JavaScript, functions are objects and components are functions.
As a result, every time a component containing a function re-renders, it create
a new instance of the function in memory.

> Given the same dependency value, the `useCallback` hook returns the same
> function instance between renderings (aka memoization).

This said, for small functions that are not intensive, it doesn't really matter
if they are not memoized.

### Syntax

A standard case of this would be a function that runs on a button click, for
instance when sending data from a form to a server. In the example below there
is quite a lot going on, and most of it is independent of the actual UI-cycle of
the component.

```jsx
const handleSubmit = useCallback(
  async (formValues) => {
    setPendSaveConfig(true);
    const payload = new GenerateConfig({
      workflowId: project_id,
      blockId: blockId,
      config: formValues,
    });
    axios
      .post(`${process.env.REACT_APP_ENDPOINT}/save-block-config`, payload)
      .then((res) => console.log(res))
      .finally(() => setPendSaveConfig(false))
      .catch((err) => console.error(err));
  },
  [blockId, project_id]
);
```

Note that the syntax is similar to [React_useEffect](React_useEffect.md): there is a
dependency array. The effect is the same: the function contained within
`useCallback` will only re-rendered if one of these dependencies changes.
However (see next section) the function will run in its memoized form on every
click.

### Reference versus result

`useCallback` only memoizes the function _object_ (the reference) not the value
that is _returned_ by the function (the result).

In the example below, the `calculatePi()` function reference will not change
between renders but each time the click event fires, the value returned by
`calculatePi()` will change. In other words, it will be assigned fresh memory.

```jsx
function ParentComponent() {
  const onHandleClick = useCallback(() => {
    const special = calculatePi();
  });

  return <SubComponent handleClick={onHandleClick} />;
}
```

### Use cases

You should not apply `useCallback` in a blanket fashion, this can reduce
performance rather than improve it. The best scenarios are:

1. A functional component wrapped inside React.memo() accepts a function object
   prop

2. When the function object is a dependency to other hooks, e.g.
   `useEffect(..., [callback])`

3. When the function has some internal state, e.g. when the function is
   debounced or throttled.

## useMemo

We can think of `useMemo` as a complement to `useCallback`. The main difference
is that whilst `useCallback` memoizes the function reference, `useMemo` memoizes
the function result; the value that is returned.

> In memoization, the result is “remembered” when the same parameters are
> passed-in subsequently. If we have a function compute 1 + 1, it will return 2.
> But if it uses memoization, the next time we run 1’s through the function, it
> won’t add them up; it will just remember the answer is 2 without executing the
> adding function.

Like `useCallback`, `useMemo` takes a function and an array of dependencies for
the same reason: if one of the dependencies changes then the function will
recalculate (and re-memoize) the result but they don't the same memoized value
will be returned.

As with `useCallback`, `useMemo` is best applied to complex functions that are
computationally expensive, it shouldn't be used indiscriminately.

It should not be used with asynchronous functions, in these instances,
`useEffect` would be a better choice.

### Syntax

```jsx
const List = React.useMemo(
  () =>
    listOfItems.map((item) => ({
      ...item,
      itemProp1: expensiveFunction(props.first),
      itemProp2: anotherPriceyFunction(props.second),
    })),
  [listOfItems]
);
```


title: API_Gateway
last_modified: 2024-10-18 18:55:56
body:
# AWS API Gateway

We can use Gateway as the front-door to our application. It will create an
[HTTP](HTTP_request_types.md) endpoint that you can call from a
client. In response to a client request you can then call a
[lambda function](Lambda_handler_function.md) that
executes a backend process.

![](/img/gateway-services.png)

See
[using API Gateway as Lambda trigger](Practical_walkthrough_Lambda_creation_within_AWS.md)
for a basic example of usage.


title: Bell_System
last_modified: 2024-10-30 13:34:21
body:
# Bell System

Denotes the family of companies run by the Bell Telephone Company during its 100
year outright monopoly on the telecommunications industry and infrastructure in
the US. Inclusive of the American Telephone and Telegraph Company (AT&T), AT&T
Long Lines (line network), the Western Electric Company (manufacturing
telephonic equipment), and Bell Labs (the R&D section). Each major city/ region
had a subsidiary, e.g. Michigan Bell, Northwestern Bell, the New York Telephone
Company etc.

Colloquially known as "Ma Bell" ("mother Bell").

The monopoly was broken up in 1983. Today, after the breakup the remaining (now
separate) companies are AT&T (phone network, mobile network), Verizon (mobile
network), and Lumen (cloud computing, networking services).


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: 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: Connecting_a_frontend_to_a_Docker_backend
last_modified: 2024-10-18 18:55:56
body:
# Connecting a frontend to a Docker backend

Building on from
[NodeJS backend with MySQL database](Node_and_MySQL_db.md)
we can add a frontend by adapting the existing
[Docker Compose](Docker_Compose.md) files (one for each
environment) to accept an additional dependency.

We won't create a container for the frontend as this is not necessary.

We would add the following to the `services` section of the compose file:

```yml
services:
  ...
  frontend:
    build: ./frontend
    container_name: frontend_app
    ports:
      - "3000:3000"
    environment:
      - REACT_APP_API_URL=http://localhost:3001
    depends_on:
      - app
```

The `REACT_APP_API_URL` environment variable tells the frontend application
where to find the backend API. This will then be accessbile via `process.env`
and can be used directly in the frontend, e.g:

```js
fetch(`${process.env.REACT_APP_API_URL}/api/data`)
  .then((response) => response.json())
  .then((data) => {
    // Process the data from the backend API
  });
```


title: Biconditional_Elimination
last_modified: 2024-10-18 18:55:56
body:
# Biconditional Elimination

Give that the biconditional means that if $P$ is the case, $Q$ must be the case
and if $Q$ is the case, $P$ must be the case, if we have $P \leftrightarrow Q$
and $P$, we can derive $Q$ and vice versa.

![](/img/biconditional-elim.png)


title: Cells_and_batteries
last_modified: 2024-10-18 18:55:56
body:
# Cells and batteries

Cells are a
[voltage source](Voltage.md) that
generate a difference of potential via a positive and negative electrode
separated by an electrolytic solution. The electrolytes pull free electrons from
one of the materials which creates a positive charge. The other material gains
the free electrons creating a negative charge.

> A battery is a combination of two or more cells.

> Cells which cannot be recharged are called **primary cells**. Cells which can
> be recharged are called **secondary cells**.

## Connecting batteries

Cells and batteries can be connected to each other in electrical ciruits to
increase the overall voltage and current that is produced. There are three main
connection types:

- series
- parallel
- series-parallel

> The key thing to remember: **cells configured in series increases the overall
> voltage available** and **cells configured in parallel increases the overall
> current available**

The table below summarises the relative differences:

![](/img/cell-comparison.svg)

### Series connections

With series connections we distinguish **series aiding** and **series opposing**
configurations.

In the case of **series aiding**, cells are connected one in front of another
with the positive terminal connecting to the negative terminal of the other in a
line.

In this configuration the same current flows through all the cells; it is not
cumulative. We represent this as follow> However the voltage is cumulative: it
is the _sum_ of the individual cell voltages, represented below as
[electrical field](Voltage.md#distinguishing-voltage-from-electric-field):

$$
E_{T} = E_{1} + E_{2} + E_{3} \\
$$

Thus series connections increase voltage but keep current constant.

_Series battery connection:_

![](/img/series-battery-diagram.svg)

_Can be represented in a circuit diagram in one of the following two ways: as a
series of cells or as a single battery:_

![](/img/series-battcircuit.svg)

In the case of **series opposing**, negative terminals are connected to each
other and positive terminals are connected to each other in a series. This
doesn't have many applications.

### Parallel connections

In parallel connections all positive terminals are connected to each other and
all negative terminals are connected to each other.

This time the voltage is the same as each individual cell but the current is the
sum of the individual cell currents. So the voltage is constant but the current
is cumulative:

$$
E_{T} = E_{1} = E_{2} = E_{3} \\
$$

$$
I_{T} = I_{1} + I_{2} + I_{3}
$$

_Parallel battery connection:_

![](/img/parallel-battery-diagram.svg)

_Parallel battery circuit diagram:_

![](/img/circ-batt-final.svg)

### Series-parrallel

If we want both a higher voltage and a higher current we can use series-parallel
configurations. Connecting cells in series increases the voltage and connecting
cells in parellel increases the current so doint both boosts the amount of both
quantities.


title: Docker_storage
last_modified: 2024-10-18 18:55:56
body:
# Docker storage

The following problems related to storage face containerized environments:

- Container storage is ephemeral. When the container is removed the read-write
  layer is removed. How can we persist data?

- Container storage is isolated: containers cannot access each other's file
  systems. If they need to share data, this could be a problem

### Docker volumes

Docker volumes are the solution to this. Volumes:

- store data on the host file system (i.e. outside the container)
- persist data beyond the lifecycle of the container

> A Docker volume is a logical volume that exists outside the container file
> system. It maps to a directory on the host and this location is managed by
> Docker - not much control over this.

Can be named or anonymous. Anonymous vols are created by Docker as needed. Named
come from the user.

Volumes can be **named** or **anonymous**. Anonymous volumes are created by
Docker as needed. Named volumes are named by the user. In both cases Docker
decides where to create the volume.

### Creating a volume

```sh
docker run -d -rm -v /usr/share/nginx/html nginx:latest
```

The `-v` flag indicates volume. The file reference is the mount point _within_
the container. This is how the persistent storage it will appear. The _actual_
volume exists outside of the container anonymously in a place decided by Docker.

To create a named volume you first have to create:

```
docker volume create volume_name
```

Then you can designate it:

```
docker run -d -rm -v volume_name:/usr/share/nginx/html nginx:latest
```

The actual location is still hidden and decided by Docker.

As these are anonymous they will show ups as GUIDs in the volume viewer in
Docker Desktop:

// Add: screenshot

### Commands

```
docker volume ls
docker volume rm
docker volume inspect
docker volume prune // remove all unused volumes
```

Inspect is only really informative if you are on Linux because it will show you
the actual physical path. With Windows and Mac the referernce is to a virtual
machine.


title: Logical_equivalence
last_modified: 2024-10-18 18:55:56
body:
# Logical equivalence

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

### Informal expression

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

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

### Formal expression

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

### Truth-tables

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

### Derivation

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

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

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


title: Docker_images
last_modified: 2024-10-18 18:55:56
body:
# Docker images

Docker needs an image to run a container. Images are stored in the local
registry. If it can't find it, it will try and pull it from Docker Hub.

## View your local images

```
docker images
```

```sh
REPOSITORY                     TAG       IMAGE ID       CREATED         SIZE
debian                         latest    e6d9c9c3e100   12 days ago     118MB
ubuntu                         latest    bab8ce5c00ca   6 weeks ago     69.2MB
cda/mysql-5.7                  latest    312ce2a6cea5   3 months ago    495MB
cda/node-16                    latest    b26e54c8fa11   3 months ago    1.44GB
bitnami/minio                  latest    ef6d14df2158   3 months ago    229MB
opensearchproject/opensearch   1.3.7     0f8ef57c3629   4 months ago    831MB
hello-world                    latest    46331d942d63   13 months ago   9.14kB
```

## Repository, registry, tag

When you specify the name of an image, you are referring to the image's
repository. The registry is the list of repositories available on the Docker
Hub. Each repository has a tag, indicating the current production branch.

For example:

```
docker run -it ubuntu:18:04 /bin/bash
```

- `ubuntu` is the image repository
- `18.04` is the image tag

> The Image ID distinguishes the image globally and is not local to you.

To get detailed info on an image:

```
docker image inspect debian
```

## "Dangling" images

A "dangling" image is an unused image - one that is not associated with a
container. Re move dangling images with

```
docker image prune
```

## Anatomy of a Docker image: layering

The process of constructing a Docker image involves creating a series of
intermediate layers, each representing a change or addition to the filesystem.
These layers are stacked on top of one another, forming the final image. This
approach is called layering.

Layering in Docker images offers several benefits:

1. Reusability: Common layers can be shared between multiple images, reducing
   disk space usage and build time. For example, if multiple images use the same
   base operating system, they can all share the same base layer.

2. Caching: During the build process, Docker caches layers that have already
   been built. If a layer hasn't changed, Docker will reuse it in subsequent
   builds, speeding up the build process.

3. Incremental updates: When updating an image, only the layers that have
   changed need to be updated and redistributed. This reduces the size of
   updates and makes the process more efficient.

Each instruction in the Dockerfile creates a new layer in the image. Some common
instructions include:

- `FROM`: Specifies the base image to start with (e.g., an operating system or
  an existing image).

- `RUN`: Executes a command, often used for installing packages or configuring
  the system.

- `COPY`: Copies files or directories from the host machine to the image.

- `ADD`: Similar to COPY but can also handle URLs and automatic extraction of
  archives.

- `ENV`: Sets environment variables that will be available inside the container.

- `CMD`: Specifies the default command to run when a container is started from
  the image.

- `ENTRYPOINT` : Similar to CMD, but allows the container to be run as an
  executable, with additional arguments.

When building an image, Docker starts with the base image specified in the FROM
instruction and applies each subsequent instruction, creating a new layer for
each step. The final result is a layered image that contains the application and
its dependencies, ready to be run as a container.

### The read-write layer

The final layer at the "top" of the image is a thin read-write layer. In
contrast to the intermediate layers it can execute read-write functionality and
is not read-only.

Files in the intermediate layer cannot be modified, only the read-write layer
can be modified. Files are copied over from intermediate layers when requested
by the read-write layer, rather being modified themselves.

Tip for writing build files:

Things that change less should be higher up (earlier). Things that change more
should be twoardds the end. This allows for better layer caching. Particularly
bear this in mind with the commands: RUN, COPY, ADD.


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: Middleware_in_NodeJS
last_modified: 2024-10-18 18:55:56
body:
# Middleware

## What is middleware?

- Anything that terminates the `req, res` cycle counts as middleware. It is
  basically anything that acts as an intermediary once the request is received
  but before the resource is sent. A good example would be the
  `app.use(express.json()` or `app.use(bodyParser.json)` functions we call in
  order to be able to parse JSON that is sent from the client.
- You will most likely have multiple middleware functions running at once. We
  call this intermediary part of the cycle the **request processing pipeline**.
- Generally all middleware will be added as a property on the Express `app`
  instance with the `app.use(...)` syntax.

## Creating custom middleware functions

### Basic schema

```js
app.use((req, res, next) => {
  // do some middleware
  next();
});
```

### `next`

The `next` parameter is key, it allows Express to move onto the next middleware
function once the custom middleware executes. Without it, the request processing
pipeline will get blocked.

Middleware functions are basically asynchronous requests and as such they use a
similar syntax as Promises (e.g `then`) for sequencing processes.

### Example of sequence

```js
app.use((req, res, next) => {
  console.log("Do process A...");
  next();
});

app.use((req, res, next) => {
  console.log("Do process B...");
  next();
});
```

> It makes more sense of course to define our middleware within a function and
> then pass it as an argument to `app.use()`

## Including middleware based on environment

With a full-scale Node application you will typically run three environments:

- Development
- Testing
- Production

We will not want to run certain types of middleware in all environments. For
example, it would be costly to run logging in the app's production environment.
It would make more sense to run this only in development.

### Accessing current Node environment

We can control which middleware we run via the Node envrionment variables:
`process.env` (see for instance [ports](Ports_in_NodeJS.md)).

We could set
[Morgan](Morgan.md) to run
only in development with:

```js
if (app.get("env") === "development") {
  app.use(morgan("common"));
  console.log("Morgan enabled");
}
```


title: React_application_structure
last_modified: 2024-10-18 18:55:56
body:
# React application structure

When you use `create-react-app` it creates a default directory structure that is
a sensible one to follow:

```
build/
public/
	index.html
src/
	index.js
	App.js
```

## `public/`

- `public` constains the `index.html` file via which React connects to the
  traditional DCOM. Within `index.html` you have `<div id="root">...</div>` as
  the entry point where your application is injected into the standard DOM.

- Everything in this folder is as per standard web development practice.

- You shouldn't include React code here but you can paste necessary links and
  metadata to the `head` of `index.html`

## `src/`

- It automatically contains `index.js` . This imports `React` and `ReactDOM` and
  contains the `ReactDOM.render()` method:
  ```js
  ReactDOM.render(
    <App />
  document.getElementById('root')
  );
  ```
- The `App` component is your parent component and the container for your app.
  It is where you will import your sub-components.
- If you are using React Router `App` becomes the index for the different page
  components. See [Routing](Routing.md) for more detail.

## `build`

`build` houses the compiled version of your application, ready to be deployed to
a server. All contents are minified, compressed and optimised using Webpack.

Once you have executed `npm run build` you can test the output by using `serve`
via the console:

```bash
serve -s build
```

`build` will comprise a minified version of `index.html` and a `static/`
directory which holds your compiled CSS and JS that `index.html` links to, as
per a standard static website.


title: Logical_consistency
last_modified: 2024-10-18 18:55:56
body:
# Logical consistency

## Informal definition

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

### Demonstration

The following set of propositions form an inconsistent set:

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

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

## Formal definition

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

### Informal expression

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

### Formal expression

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

### Truth table

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

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

## Derivation

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

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

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

![](/img/derivation_from_contradiction.png)

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

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


title: Sorting_lists_in_Python
last_modified: 2025-01-17 13:56:26
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]
```

> This method is best used for sorting a list of string values

### 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]
```

### Combine sorting with comprehension

```python
tags = sorted([tag[0] for tag in tags])
```

### 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: 4da22434_from_ARPANET_to_Internet
last_modified: 2024-10-28 15:54:00
body:
# 4da22434_from_ARPANET_to_Internet

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

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

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

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

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

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

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

_The ARPANET in 1973_

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

_The ARPANET as part of an inter-network 1977_

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

_The ARPANET in 1977_

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

_The Internet in 1984_

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


title: Using_arguments_with_Apollo_Client
last_modified: 2024-10-18 18:55:56
body:
# Using arguments with Apollo Client

## What are arguments?

- An argument is a value you provide for a particular field in a query.
- The schema must define the arguments that a field accepts (if any)
- The resolver invokes a field's provided arguments to determine how to return
  specific data
- Some example use cases of arguments:
  - retrieve specific objects
  - filter through a set of objects
  - transform a field's returned value

## Updated schema

In order to demonstrate arguments we need to expand the
[original schema](Apollo_Server.md#example-schema).

Remember a Track is a group of modules that teaches about a specific topic. We
are going to add:

- `description` and `numberOfViews` fields to the original `Track` type
- A new `Module` type
- A field `modules` to the `Track` type that will be an array of type `Module`

```js
type Query {
  tracksForHome: [Track!]!
}

type Track {
    id: ID!
        authorId: String! // They added this on the sly
    thumbnail: String
    length: Int
    modulesCount: Int
    description: String
    numberOfViews: Int
    modules: [Module!]!
}

type Module {
    id: ID!
        length: Int
}

type Author {
    id: ID!
    name: String!
    photo: String
}
```

## Adding additional query

> Currently we only have one query in the schema (`tracksForHome`) and this
> returns an array of all the tracks. To demonstrate arguments we want to return
> a specific track by its `id`. We are going to add a query that enables this.

```js
type Query {
    track(id: ID!):  Track
}
```

This query will take an `id` as an argument and the resolver will match the `id`
to a given track and return that `Track`.

To define an argument for a field in the schema we add parentheses after the
field name. In the parentheses we write the name of the argument and its type.
If we have more than one argument we can separate them with commas.

## Create resolver for new query

Now we have to create a resolver for our new `track` query. We will quickly run
through the [server-side process](Apollo_Server.md).

### Update our `RESTDataSource`

```js
class TrackAPI extends RESTDataSource {
  constructor() {...}
  getTracksForHome() {...}
  getAuthor(authorId) {...}

  getTrack(trackId){
    return this.get(`track/${trackId}`);
  }
}

```

### Add resolver to `resolvers.js`

```js
const resolvers = {
  Query: {
    tracksForHome: (_, __, { dataSources }) => {
      return dataSources.trackApi.getTracksForHome();
    },
    track: (_, { id }, { dataSources }) => {
      return dataSources.trackAPI.getTrack(id);
    },
  },
  Track: {
    author: ({ authorId }, _, { dataSources }) => {
      return dataSources.trackApi.getAuthor(authorId);
    },
  },
};
```

### Making a query

With the server changes complete, we can now issue a query with an argument from
the client:

```js
query track(id: 'xyz'){
  title
}
```

This will return the `title` field from the track with the specific id. This
query complies with the schema since the `Track` type has the field `title`

### Resolver chains

What about the following query:

```js
query track(id: 'xyz'){
  title
  author {
    name
  }
}
```

It's not obvious how the resolver should respond to the nested query here since
author name is not a field on `Track`, it is a field on `Author`:

```js
type Track {
  id: ID!
    authorId: String!
  thumbnail: String
  length: Int
  modulesCount: Int
  description: String
  numberOfViews: Int
  modules: [Module!]!
}

type Author {
  id: ID!
  name: String!
  photo: String
}
```

This is where we use a **resolver chain**. Because `authorId` exists on `Track`
we can use this to get the `name` part of the query. We already have a resolver
for `author` under the `Track` resolver:

```js
Track: {
    author: ({ authorId }, _, { dataSources }) => {
      return dataSources.trackApi.getAuthor(authorId);
    },
  },
```

Notice that `authorId` is used in the place of the `parent` parameter. It
already exists on the `Track` type that wraps the resolver. So this can be
invoked to fulfill `author` and thereby access the author name from the graph.

This process is also required for our extended schema. The `Track` type now has
a `modules` field that comprises an array of the `Module` type.

Here's a reminder of the `Module` type:

```js
type Module {
    id: ID!
        length: Int
}
```

The modules for a `Track` can be sourced from the track `id`. So we can again
add a resolver chain function to the resolvers file:

```js
const resolvers = {
  Query: {
    tracksForHome: (_, __, { dataSources }) => {
      return dataSources.trackApi.getTracksForHome();
    },
    track: (_, { id }, { dataSources }) => {
      return dataSources.trackAPI.getTrack(id);
    },
  },
  Track: {
    author: ({ authorId }, _, { dataSources }) => {
      return dataSources.trackApi.getAuthor(authorId);
    },
    modules: ({ id }, _, { dataSources }) => {
      return dataSources.trackAPI.getTrackModules(id);
    },
  },
};
```

Once again we are using a property on the parent `Track` type to use as a
parameter in the resolver function.

This setup would enables like the following to be run:

```js
query track(id: 'xyz'){
  title
  modules
}
```

The `id` parameter would be used by the `modules` resolver to return the array
of the `Modules` type.

## Variables in arguments

Instead of writing the following within our query constants on the client-side:

```js
query GetTrack {
  track(id: 'xyz'){
    title
  }
}
```

We can make the code more reusable by using variables instead of the hardcoded
`id` argument:

```js
query GetTrack($trackId: ID!) {
  track(id: $trackId){
    title
  }
}
```

This way we do not need to write a new query constant every time we want to
return a specific track.

## Send query with arguments using Apollo `useQuery`

We can now write a proper query using the
[useQuery hook](Apollo_Client.md#usequery-hook) from
Apollo Client, with variables.

First define our query constant:

```js
export const GET_TRACK = gql`
  query GetTrack($trackId: ID!) {
    track(id: $trackId) {
      id
      title
      author {
        name
      }
      description
      modules {
        id
        title
        length
      }
    }
  }
`;
```

Then to employ in React:

```jsx
const trackId = "xyz";

const { loading, error, data } = useQuery(GET_TRACK, {
  variables: {
    id: trackId,
  },
});
```

Note that in contrast to the
[simple example](Apollo_Client.md#query-constants)
because we are using variables, we have to pass-in an additional options object
with the query constant that specifies our variables.


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: Network_fundamentals
last_modified: 2024-10-18 18:55:56
body:
# Network fundamentals

> A network is a system that allows computing devices to communicate and
> exchange information with each other.

In order for devices to be able to communicate they must share a common
**communication protocol**.

A protocol specifies **rules determining how information is to be exchanged**.
(Simply connecting two devices is not sufficient for them to be able to
communicate, they must have a shared language.)

The nodes of a network are called [hosts](Network_hosts.md).


title: Peer_to_peer_network
last_modified: 2024-10-18 18:55:56
body:
# Peer to peer network

A decentralised network model where each participant (peer) acts as both a
client and a server.

Resources are shared directly between peers rather than being coordinated via a
server.

Under a server architecture, multiple requests are made from different hosts for
resources. The server manages this load and is in control of what is being
shared, to whom, and when. With P2P there is no central authority equivalent to
this. Each peer both shares and consumes resources and in this sense is both a
client and a server.

A practical example of this is [torrenting](Torrenting.md) - an applicaton of
P2P technology to file sharing.

## Benefits

- Decentralisation, no central authority, also means no single point of failure.
- Scalable: the network capacity grows with the number of users (contrast
  servers)
- Efficiency: idle resources of peers are put to use
- Improved performance for popular content

## Drawbacks

- Security: potential for malicious peers and content
- Inconsistent availability of resources


title: Multiplexers_and_demultiplexers
last_modified: 2024-10-18 18:55:56
body:
# Multiplexers (MUX) and demultiplexers (DMUX)

## Multiplexer

> Multiplexing is the generic term used to describe the operation of sending one
> or more analogue or digital signals over a common transmission line at
> different times or speeds.

A multiplexer selects one of several input signals and forwards ths selected
input to a single output line.

We have two inputs (A,B) plus a third input SEL (for "select"). Applying a value
to SEL toggles the output between A and B.

![](/img/MUX.png)

Multiplexers can be used to build larger circuits by connecting the output of
one multiplexer to the input of another. They are often used to implement data
selection and switching in digital systems

### Programable gates

One of the main use cases of multiplexers is to implement programmable gates.
These are gates where the logic can be switched. For example an ANDMUXOR gate
uses the SEL value to toggle the operation of a gate between AND and OR for its
two inputs A and B

## Demultiplexer

As the name suggests, a demultiplexer reverses the functionality of a
multiplexer. It receives a single input and based on the selection of the SEL
input it channels it to either an A or a B output.

![](/img/DMUX.png)

We can think of it as a distributor of a value into one of several possible
channels.


title: Negation_Elimination
last_modified: 2024-10-18 18:55:56
body:
# Negation Elimination

Like the [introduction](Negation_Introduction.md) rule for
negation, the elimination rule also works by deriving a contradiction. It is
basically _Negation Introduction_ in reverse. Instead of starting the sub-proof
with a true proposition from which you derive a contradiction, you start with
the negation of a proposition, derive a contradiction and then assert the
positive of the negated proposition you started out with.

![](/img/negate-elim.png)


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: Troubleshooting_MongoDB
last_modified: 2024-10-18 18:55:56
body:
# Troubleshooting MongoDB connection issues

## Arch Linux troubleshooting

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

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

Otherwise try the below.

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

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

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

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


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: Weaving_the_Web_Berners_Lee
last_modified: 2025-01-09 18:51:19
body:
# Weaving the Web (Berners-Lee, 1999)

Tim did not really conceive of the possibility of the centralising and
totalising effects of social media:

_The Web's universality leads to a thriving richness and diversity. If a company
claims to give access to the world of information, then presents a filtered
view, the Web loses its credibility._ (p.143)

_Happily, the Web is so huge that there's no way any one company can dominate
it. All the human effort people and organisations have put in all over the world
to create Web sites and home pages is astoundingly large, and most of the effort
has to do with what's in the Web, not the software used to browse it. The Web's
content, and thus value, will continue despite any one company's actions._
(p.144)

Apropos the "alt-right" etc:

_The analogy of a global brain is tempting, because Web and brain both involve
huge numbers of elements - neurons and Web pages - and a mixture of structure
and apparent randomness. However, a brain has an intelligence that emerges on
quite a different level from anything that a neuron could be aware
of...[W]riters have contemplated an 'emergent property' arising from the mass of
humanity and computers. But remember that such a phenomenon would have its own
agenda. We would not as individuals be ware of it, let alone control it, any
more than the neuron controls the brain._ (p.222)


title: Git_bisect
last_modified: 2024-10-18 18:55:56
body:
# Bisect

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

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

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

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

## Procedure

```
git bisect start

git bisect good <SHA, tag, branch>

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


title: Architecture_of_the_World_Wide_Web
last_modified: 2024-12-13 15:16:12
body:
# Architecture of the World Wide Web

## Terms

<dl>
<dt>web</dt>
<dd>a set of resources delivered over the Internet using the HTTP protocol</dd>

<dt>web resource</dt>
<dd>any information that can be accessed using the web (document, image, video)</dd>

<dt>web server</dt>
<dd>a software program on a computer that hosts web resources</dd>

<dt>web browser</dt>
<dd>an application used to acccess web resources on web servers</dd>

</dl>

## The web is _distributed_, _addressable_, and _linked_

### Distributed

There is no centralised organisation or system that decides the content that can
be published. Any computer connected to the Internet can run a web server and
the person running it can make available any content they wish.

### _Addressable_

Every resource on the web has a unique address that includes both its location
and how to access it (URL):

![](../img/web-url.png)

- scheme
  - specifies the [Application Layer](Application_Layer_of_Internet_Protocol.md for
    accessing the resource. In the example this is HTTP but it could be FTP or another
    protocol, always bracketed with a colon
- authority
  - comprises the [DNS](Transport_Layer_of_Internet_Protocol.md) hostname of the
    server containing the resource. This could also be an
    [IP address](IP_addresses.md) and could also include a port number
- path
  - the location of the resource on the web server, organised hierarchically and
    analagous to a file path
- query
  - a modifier of the resource returned to the client. Its usage varies accross
    sites but is typically deterimined by backend implementation details

> Note a URL does not have to always contain _all_ this information. Consider an
> index page.


title: Binary_encoding_of_sound
last_modified: 2025-01-02 17:22:27
body:
# Binary encoding of sound

Sound is a change in air pressure.

To record sound, we convert the change in air pressure into an electrical signal
by means of a microphone. Effectively, the same natural waveform is converted
from one medium (the vibration of air molecules) into another medium (electrical
charge in a field) whilst retaining the same morphology. See
[Recording and playback of sound](Recording_and_playback_of_sound.md).

The resulting electrical signal is analogue: it has a continuous and variable
frequency and amplitude and thus can have any value at any given moment.

To encode this information digitally we need to make the continuous analogue
single discrete, so that it can represented with a finite set of symbols (bits).

This process is known as **quantization** and consists in sampling the analogue
waveform. This means taking a measurement of it at different points in time -
each second.

CDs sample the audio source at a rate of 44.1 kHz: 44, 100 measurements of the
signal per second.

The measurements are then stored in a digital format: bits. The amount of bits
used to record the measurement is the **bit-depth**. Each measurement is rounded
to the nearest available digital value. CDs have a depth of 16 bits. Thus each
measurement can be one of $16^2 = 65,536$ possible values.

With sampling at a given bit-depth established, the **bit-rate** can be derived.

This is the sampling rate (number of measurements per second) divided by the
number of bits per sample (bit-depth), multiplied by the number of channels.

Thus for stereo CDs this is:

$$
(44100 / 16 ) \cdot 2 = 1411200
$$

Or 1.4 Mb/s

> Even with a high bit-rate (as with CDs) the process still necessarily involves
> a loss of information when converting sound from analogue to digital. However,
> given a high enough value for the sampling rate and bit-depth, the differences
> are imperceptible to the human ear.


title: Modelling_relationships_in_MongoDB
last_modified: 2024-10-18 18:55:56
body:
# Modelling relationships between data

So far we have taken the values of a document to be simple: the `author` value
is just a string but what if `author` was itself another collection for instance
a collection with the proprties `age` and `publications` and the author name,
say `Tosh Gnomay` was a document in this collection? This means we would have an
interaction between two collections. In this entry we will look at how to work
with interrelated collections. This is equivalent to establishing
[joins](10_Joins.md) in a relational database.

There are two main approaches to modelling relationships between data:
**normalisation** and **denormalisation**.

## Modelling relationships with normalisation: using references

In the case of normalisation we use a UUID from one document to connect it to
another document.

Let's say that we have a course document as follows which is an instance of the
`courses` collection:

```js
{
    _id: "ceds89e"
    name: "Java course",
    author: "Tosh Gnomay"
}
```

And we have a document which is an instance of another type of collection called
`authors`. One instance of it could be:

```js
{
    _id: "de89w9",
    name: "Tosh Gnomay",
    courses: ["Java course", "C# course"]
}
```

Using normalisation, we would establish the relationship by using the value of
the `_id` propety in the `courses` document as the value of the `name` property
in the `authors` document. We would rewrite the `courses` document like so:

```js
{
    _id: "ceds89e"
    name: "Java course",
    author: "de89w9"
}
```

## Modelling relationships with denormalisation: embedding

With denormalisation we achieve the same outcome but instead of using a
reference we embed one value in another:

```js
{
    _id: "ceds89e"
    name: "Java course",
    author: {
        name: "Tosh Gnomay",
        courses: ["Java course", "C# course"]
    }
}
```

## Tradeoffs: which approach to use?

There are drawbacks and benefits with each approach.

- One benefit of normalisation is consistency. Because we are using the UUID and
  referencing it in another document, if we make a change to the values in the
  source document, it will automatically apply to all documents that reference
  it. We make a change in one place and it is reflected everywhere.

- Conversely, a drawback of denormalisation is that it invites inconsistency
  between documents. Embedded values can easily go out of sync with their usage
  elsewhere as you don't have a direct reference correspondence.

- On the other hand a drawback of normalisation is performance when querying.
  Any time you have a document that references another document than this
  document also has to be retrieved. Therefore for every query you are returning
  two documents. As denormalised references embed the second document, only one
  document is being returned on each query.


title: 861cc26e_ARPA_origins
last_modified: 2024-10-24 07:30:17
body:
# 861cc26e_ARPA_origins

After Sputnik, Eisenhower wanted a dedicated, cross-departmental division within
the US government to focus on advanced technological research so that they would
not be caught out again. ARPA was the result.

Originally under the control of the Department of Defence (Pentagon).

Once projects reached maturity, they would be handed over to the armed forces.

It's name switched back and forth between ARPA/DARPA depending on congressional
factors and funding. The switch to DARPA (and the associated re-focusing on
military applications) indirectly precipitated a brain-drain in the late 1970s
which contributed to the birth of the personal computer around the same time.

Main technologies it contributed to or invented include the #ARPANET,
time-sharing and GPS systems.


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: Formal_conventions
last_modified: 2024-10-18 19:14:07
body:
# Formal conventions

## Grouping symbols

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

## Writing mathematical statements: placement of $=$

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

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

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


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

A bus is a communication system that transfers data between components inside a
computer, or between computers.

**A bus can be thought of as a kind of pipeline that allows different components
of the computer to communicate with each other.**

Note that the use of the word "bus" varies somewhat in points of emphasis. A bus
can mean:

- The physical wires that make up the bus
- The communication channel that is established over those wires

## Main buses

| Bus type      | Description                                                                                                                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| System bus    | The primary pathway between the CPU and [memory](Memory.md). It comprises the **data bus** that transfers data from the memory to the CPU and the **address bus** which transmits requests from the CPU to memory. |
| Internal bus  | Connects local devices for example the harddisk to the CPU.                                                                                                                                                        |
| External bus  | Connects peripheral devices such as disks and printers to the [motherboard](Motherboard.md)                                                                                                                        |
| Expansion bus | Allows expansion boards to access the CPU and memory.                                                                                                                                                              |
| Frontside bus | Main computer bus that determines data transfer rate speed and is the primary data transfer path between the CPU, RAM and other [motherboard](Motherboard.md) devices.                                             |
| Backside bus  | Transfers secondary cache (L2 cache) data at faster speeds, allowing more efficient CPU operations                                                                                                                 |

## Bus standards

### Parallel and serial transmission types

There is a distinction between buses that use **serial** and buses that use
**parallel transmission**. They are distinguished by the way in which the bits
are transferred from one point to another, along the bus.

Serial Transmission is the type of transmission in which a single communication
link is used to transfer the data from an end to another. On other hand Parallel
Transmission is the transmission in which multiple parallel links are used that
transmit each bit of data simultaneously.

<img src="../img/serial-transmission.jpg" width="800px"/>
<img src="../img/parallel-transmission.jpg" width="800px"/>

- Serial buses are cheaper to implement than parallel buses
- Serial buses operate at greater [latency](Bus.md#latency) than parallel buses

## Latency

Latency means _delay_: the delay from the time the data is requested until the
time it arrives. In the context of a microprocesser and buses a key locus of
latency would be between the time a request takes to travel accross the system
bus from the CPU to the memory.

### Bandwidth

In general, bandwidth is a measure of the capacity for a communications channel
to transmit data. A bus is a channel between two components thus we can talk
about the bandwidth of buses. The speed and efficiency of a bus is a function of
its bandwidth, although other factors such as its length and proximity to the
source and receiver are also important.

Bandwidth can be calculated in terms of how many bits can be transferred per
second. The wider the bus, the more bits that can be transferred. If we have a
bus that can run at 66MHz and transfer 64 bits, 64 bits is 8Mb thus the
bandwidth is 8 x 66 = 528Mb.

## Standards

The mode of transmission is used to distinguish the different bus standards.

SCSI (Small Computer System Interface) and ATA (Advanced Technology Attachment)
are used for hard drive devices (i.e. internal buses). USB (Univeral Serial Bus)
and SATA (Serial Advanced Technology Attachment) are used for peripheral
devices, keyboards and modem devices (i.e external buses).


title: Killing_processes
last_modified: 2024-10-18 19:16:00
body:
# Killing processes

## Kill a process running on a port

For example a local server.

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

$ 7890
# Gives you process id

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

## Alterntive

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

Returns:

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

Kill with:

```
kill 10493
```


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: Loops_in_bash
last_modified: 2024-10-18 18:55:56
body:
# Loops in Bash

## Traditional for loop

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

# 1 2 3 4 5
```

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

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

## While loop

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

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

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

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

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

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

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

## Until loop

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

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

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


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`


