Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDC Tools sprints 3 and 4 #1276

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9751f76
SDC Tools sprints 3 and 4
illicitonion Dec 24, 2024
e16d252
Add module success criteria
illicitonion Dec 30, 2024
a5c60c6
Simplify themes
illicitonion Dec 30, 2024
3a37c85
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion Jan 22, 2025
706d7a2
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion Jan 22, 2025
beffbc7
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion Jan 22, 2025
9f63e52
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion Jan 22, 2025
964da99
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion Jan 22, 2025
6f09666
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion Jan 22, 2025
165b2d6
Update common-content/en/module/tools/converted-program/index.md
illicitonion Jan 22, 2025
bd744cb
Update common-content/en/module/tools/converted-program/index.md
illicitonion Jan 22, 2025
d59e0ac
Update common-content/en/module/tools/converting-javascript-to-python…
illicitonion Jan 22, 2025
8670f27
Update common-content/en/module/tools/counting-words/index.md
illicitonion Jan 22, 2025
83b06ba
Update common-content/en/module/tools/first-nodejs-program/index.md
illicitonion Jan 22, 2025
ffbf68c
Update common-content/en/module/tools/first-nodejs-program/index.md
illicitonion Jan 22, 2025
246882f
Update common-content/en/module/tools/single-use-data-analysis/index.md
illicitonion Jan 22, 2025
8de321f
Update common-content/en/module/tools/using-python-dependencies/index.md
illicitonion Jan 22, 2025
5e78448
Update common-content/en/module/tools/using-python-dependencies/index.md
illicitonion Jan 22, 2025
9c2e465
Update common-content/en/module/tools/using-python-dependencies/index.md
illicitonion Jan 22, 2025
b3dca09
Tooltip convention
illicitonion Jan 23, 2025
1891137
Review feedback
illicitonion Jan 24, 2025
72b11df
Review feedback
illicitonion Jan 24, 2025
d6b8570
Fix typo
illicitonion Jan 24, 2025
c771e22
Feedback on converting exercise
illicitonion Jan 24, 2025
ee3ede4
Much of Sally's first round of feedback
illicitonion Jan 27, 2025
febb904
Most of Ali's review comments
illicitonion Jan 27, 2025
0baae4b
Clarify the fs.promises import
illicitonion Jan 30, 2025
3b3d403
Add prompt to think about why fs is async
illicitonion Jan 30, 2025
bc3246f
Add link to docs
illicitonion Jan 30, 2025
3dfc99e
Compare virtual environments to node_modules
illicitonion Jan 30, 2025
70abc83
Convert filter+lambda to an exploration exercise
illicitonion Jan 30, 2025
ab96743
Merge branch 'main' into sdc-tools-sprint3and4
SallyMcGrath Feb 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
+++
title = "Comparing JavaScript and Python"
headless = true
time = 40
facilitation = false
emoji= "📖"
objectives = [
"Identify and explain equivalences between JavaScript and Python",
"Compare and contrast differences between JavaScript and Python",
"Distinguish between essential and accidental complexity"
]
+++

JavaScript and Python have many things in common.

Most differences are "cosmetic". Here are some examples of cosmetic differnces:
* Some functions and operators have different names. But often there are functions/operators which do exactly the same thing.
* JavaScript uses `{}` around blocks of code and we _choose_ if we indent code. Python uses `:` and indentation is required.
* In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case`. In both langues we _could_ do either; this is called a {{<tooltip title="convention">}}A convention is something a group (maybe a team, or a company, or most users of a programming language) agree to do. It's not quite a rule - things _could_ work another way. But we agree one way we'll all do it anyway.<br /><br />e.g. in Python you could name one variable `firstName` and another `middle_name` and another `LASTname`, but if everyone agrees to use `first_name` and `middle_name` and `last_name` it makes it a bit easier for everyone to read because they know what to expect.{{</tooltip>}}.

Recall our "count containing words" JavaScript code. Now think about what it would look like in Python.

```js
import { program } from "commander";
import { promises as fs } from "node:fs";
import process from "node:process";

program
.name("count-containing-words")
.description("Counts words in a file that contain a particular character")
.option("-c, --char <char>", "The character to search for", "e");

program.parse();

const argv = program.args;
if (argv.length != 1) {
console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`);
process.exit(1);
}
const path = argv[0];
const char = program.opts().char;

const content = await fs.readFile(path, "utf-8");
const countOfWordsContainingChar = content
.split(" ")
.filter((word) => word.includes(char))
.length;
console.log(countOfWordsContainingChar);
```

{{<tabs name="Exercise">}}
===[[Exercise]]===
Think about what we're doing in this code.

Try to list the high-level ideas. This means describing in English what we're achieving, using sentences like like "Reading a file".

We're not trying to think about the programming concepts we're doing here (we aren't talking about things like "Assigning a variable" or "An if statement"). Think about what a non-programmer would want to understand about our program.

===[[Answer]]===

You may have slightly different answers, but the programme is doing roughly the following things:

* Parsing command line flags - writing down what flags we expect to be passed, and reading values for them based on the actual command line.
illicitonion marked this conversation as resolved.
Show resolved Hide resolved
* Validating the flags (i.e. checking that exactly one path was passed).
* Reading a file.
* Splitting the content of the file up into words.
* Counting how many of the words contained a particular character.
* Printing the count.

{{</tabs>}}

These are the meaningful things we needed to do. To solve the same problem with Python, we'd still do all of these things.

We did some other things in our code to make it work. For example, we imported some modules. To write this code in Python, we might need modules or we might not. Importing modules isn't one of our _goals_, it was just something we needed to do to help us.

We split up things we need to do into two categories: essential and accidental.

**Essential** means it is a core part of the problem. e.g. in order to count how many words are in a file, it is _essential_ that we read the file.

**Accidental** means it isn't what we _care_ about doing, but we may need to do it anyway. e.g. importing the `process` module isn't _essential_ to our problem, but we needed to do it anyway so we could report errors.

{{<note type="Think about real life">}}
Imagine we want to post a parcel, so we take the bus to the post office.

_Essential_ to our goal is getting the parcel to someone who will deliver it.

_Accidental_ to this, we took the bus. There may be ways we could achieve our essential goal without getting the bus. Maybe we could walk or cycle to the post office. Maybe we could arrange for someone from the post office to come to our home and collect the parcel.

The accidental things we did were important - they helped us get our essential goal done. But we shouldn't get too attached to the accidental things - maybe we will replace them later.
{{</note>}}


When we're thinking about how we use different languages, it's useful to think about what parts of our problem are _essential_ (we'll need to do them in any language), and which parts are _accidental_ (it's just something we had to do on the way to achieve our aim).

Whether we write the JavaScript `someArray.length` or the Python `len(some_array)` isn't a big difference. Both lines do the same thing, they just express it differently.

This file was deleted.

35 changes: 35 additions & 0 deletions common-content/en/module/tools/converted-program/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
+++
title = "Putting it all together"
headless = true
time = 30
facilitation = false
emoji= "📖"
hide_from_overview = true
objectives = [
]
+++

Instead of calling `console.log`, in Python we call `print`.

```python
import argparse

parser = argparse.ArgumentParser(
prog="count-containing-words",
description="Counts words in a file that contain a particular character",
)

parser.add_argument("-c", "--char", help="The character to search for", default="e")
parser.add_argument("path", help="The file to search")

args = parser.parse_args()

with open(args.path, "r") as f:
content = f.read()
count_of_words_containing_char = len([word for word in content.split(" ") if args.char in word])
print(count_of_words_containing_char)
```

This looks similar to the JavaScript version. The shape is the same, but every line is a little bit different.

Some programming languages are very different, as different as Mandarin and English. But JavaScript and Python are, essentially, quite similar, like Spanish and Portuguese.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
+++
title = "Converting JavaScript to Python"
headless = true
time = 90
facilitation = false
emoji= "📖"
objectives = [
"Rewrite JavaScript code as Python"
]
+++

### Parsing command line flags

In JavaScript, we wrote this code:

```js
import { program } from "commander";

program
.name("count-containing-words")
.description("Counts words in a file that contain a particular character")
.option("-c, --char <char>", "The character to search for", "e");

program.parse();

const argv = program.args;
const path = argv[0];
const char = program.opts().char;
```

Which of the following are _essential_ goals in this code, and which are _accidental_ goals?

{{<label-items heading="Drag essential/accidental from 👆🏾 onto each goal 👇🏽">}}
[LABEL=Essential] Allow a user to pass a `-c` argument (defaulting to `e` if they don't).
[LABEL=Accidental] Made a `const` variable called `argv`.
[LABEL=Accidental] Import `program` from the `commander` library.
[LABEL=Essential] Allow a user to pass a path as a positional argument.
[LABEL=Accidental] Looked up element `0` in the `program.args` array.
[LABEL=Essential] Supply a nice `--help` implementation to help a user if they don't know how to use our tool.
[LABEL=Accidental] Use the commander library.
[LABEL=Accidental] Called the function `program.name()`.
{{</label-items>}}

If we want to work out how to do this in Python, we should focus on the essential goals. We may want to search for things like "Parse command line flags Python" and "Default argument values Python" because they get to the essential problems we're trying to solve.
illicitonion marked this conversation as resolved.
Show resolved Hide resolved

Searching Google for "Parse command line flags Python" brought us to [the Python argparse documentation](https://docs.python.org/3/library/argparse.html). The example code looks pretty similar to what we were doing in JavaScript. We can probably write something like:

```python
import argparse

parser = argparse.ArgumentParser(
prog="count-containing-words",
description="Counts words in a file that contain a particular character",
)

parser.add_argument("-c", "--char", help="The character to search for", default="e")
parser.add_argument("path", help="The file to search")

args = parser.parse_args()
```

There are some differences here.
* With commander we were calling functions on a global `program`, whereas with argparse we construct a new `ArgumentParser` which we use.
* `add_argument` takes separate parameters for the short (`-c`) and long (`--char`) forms of the option - `commander` expected them in one string.
* The Python version uses a lot of named arguments (e.g. `add_argument(...)` took `help=`, `default=`), whereas the JavaScript version (`option(...)`) used a lot of positional ones.
* The Python version handles positional arguments itself as arguments with names (`path`), whereas the JavaScript version just gives us an array of positional arguments and leaves us to understand them.

### Validating command line flags

In our JavaScript code, we needed to check that there was exactly one positional argument.

We don't need to do this in our Python code. Because `argparse` treats positional arguments as arguments, it actually already errors if we pass no positional arguments, or more than one.

So we can tick this essential requirement off our list. Different languages or libraries do things differently, and that's ok!

> [!TIP]
> We don't need to convert every line.
>
> We're trying to convert _essential requirements_.

{{<note type="Exercise">}}
Identify all of the essential requirements from our JavaScript program, and finish implementing the Python version.
{{</note>}}
44 changes: 44 additions & 0 deletions common-content/en/module/tools/counting-words/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
+++
title = "Counting words containing a character"
headless = true
time = 45
facilitation = false
emoji= "📖"
hide_from_overview = true
objectives = [
]
+++

In JavaScript we wrote:

```js
content
.split(" ")
.filter((word) => word.includes(char))
.length
```

What JavaScript calls arrays, Python calls lists. Arrays and lists are basically the same.

Googling for "Python filter list" suggests there are two things we can use - a `filter` function, or something called a "list comprehension". Some people prefer one, other people prefer the other.

Let's try out both approaches. We can do this in a standalone program, rather than in the whole word-counting program. This gives us a lot more control, and makes it easier for us to experiment.

{{<note type="Exercise">}}
Create a new file, `filter.py`. Start it with:

```python
content = "this is a list of words"
char = "i"

filtered = TODO

print(filtered)
```

Now fill in the TODO. First, use a list comprehension. Run the file and make sure you get the expected output.

Next, replace your list comprehension with some code that calls the global function `filter`. (`filter` takes a function, and it may be useful to know that `lambda` is a keyword for making an anonymous function in Python, similar to arrow functions in JavaScript). Run the file and make sure you get the expected output.
{{</note>}}

Now that we've learnt how to do the filtering, we can apply what we've learnt to the program we're converting.
Loading
Loading