Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 14 additions & 7 deletions exercises/practice/acronym/.approaches/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,56 @@
"slug": "functools-reduce",
"title": "Functools Reduce",
"blurb": "Use functools.reduce() to form an acronym from text cleaned using str.replace().",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "d568ea30-b839-46ad-9c9b-73321a274325",
"slug": "generator-expression",
"title": "Generator Expression",
"blurb": "Use a generator expression with str.join() to form an acronym from text cleaned using str.replace().",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "da53b1bc-35c7-47a7-88d5-56ebb9d3658d",
"slug": "list-comprehension",
"title": "List Comprehension",
"blurb": "Use a list comprehension with str.join() to form an acronym from text cleaned using str.replace().",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "abd51d7d-3743-448d-b8f1-49f484ae6b30",
"slug": "loop",
"title": "Loop",
"blurb": "Use str.replace() to clean the input string and a loop with string concatenation to form the acronym.",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "9eee8db9-80f8-4ee4-aaaf-e55b78221283",
"slug": "map-function",
"title": "Map Built-in",
"blurb": "Use the built-in map() function to form an acronym after cleaning the input string with str.replace().",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "8f4dc8ba-fd1c-4c85-bcc3-8ef9dca34c7f",
"slug": "regex-join",
"title": "Regex join",
"blurb": "Use regex to clean the input string and form the acronym with str.join().",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "8830be43-44c3-45ab-8311-f588f60dfc5f",
"slug": "regex-sub",
"title": "Regex Sub",
"blurb": "Use re.sub() to clean the input string and create the acronym in one step.",
"authors": ["bethanyg"]
"authors": ["bethanyg"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "0ce3eaf7-da79-403d-a481-5dd8f476d286",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
from string import ascii_letters


VALID_CHARS = {' ', '-'} | set(ascii_letters)
VALID_CHARS = {" ", "-"} | set(ascii_letters)


def abbreviate(to_abbreviate):
to_abbreviate = ''.join(' ' if char == '-' else char
to_abbreviate = "".join(" " if char == "-" else char
for char in to_abbreviate
if char in VALID_CHARS)

return ''.join(word[0] for word in to_abbreviate.split()).upper()
return "".join(word[0] for word in to_abbreviate.split()).upper()
```

One way someone might try to increase performce is to use a single [generator expression][generator-expression] to clean the input, rather than using multiple calls to [`str.replace()`][str-replace].
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
VALID_CHARS = {' ', '-'} | set(ascii_letters)
VALID_CHARS = {" ", "-"} | set(ascii_letters)

def abbreviate(to_abbreviate):
to_abbreviate = ''.join(' ' if char == '-' else char
to_abbreviate = "".join(" " if char == "-" else char
for char in to_abbreviate
if char in VALID_CHARS)

return ''.join(word[0] for word in to_abbreviate.split()).upper()
return "".join(word[0] for word in to_abbreviate.split()).upper()
35 changes: 18 additions & 17 deletions exercises/practice/acronym/.approaches/functools-reduce/content.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,47 @@
# Scrub with `replace()` and join via `functools.reduce()`
# Clean with `replace()` and join via `functools.reduce()`


```python
from functools import reduce


def abbreviate(to_abbreviate):
phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split()
phrase = to_abbreviate.replace("-", " ").replace("_", " ").upper().split()

return reduce(lambda start, word: start + word[0], phrase, "")
```


- This approach begins by using [`str.replace()`][str-replace] to "scrub" (_remove_) non-letter characters such as `'`,`-`,`_`, and white space from `to_abbreviate`.
- The phrase is then upper-cased by calling [`str.upper()`][str-upper],
- This approach begins by using [`str.replace()`][str-replace] on `to_abbreviate` to convert non-letter characters such as `-` and `_` into spaces.
- The phrase is then upper-cased by calling [`str.upper()`][str-upper].
- Finally, the phrase is turned into a `list` of words by calling [`str.split()`][str-split].

The three methods above are all [chained][chaining] together, with the output of one method serving as the input to the next method in the "chain".
This works because both `replace()` and `upper()` return strings, and both `upper()` and `split()` take strings as arguments.
However, if `split()` were called first, `replace()` and `upper()` would fail, since neither method will take a `list` as input.
The three methods above are all [chained][chaining] together, with each method operating on the output of the method before it in the "chain".
This works because both `replace()` and `upper()` _operate on_ strings (as they are `str` methods) and _return_ strings.
If `split()` was called first, `replace()` and `upper()` would fail, since they cannot operate on the `list` returned by `split()`.

~~~~exercism/note
`re.findall()` or `re.finditer()` can also be used to "scrub" `to_abbreviate`.
These two methods from the `re` module will return a `list` or a lazy `iterator` of results, respectively.
As of this writing, both of these methods benchmark slower than using `str.replace()` for scrubbing.
`re.findall()` or `re.finditer()` can also be used to clean `to_abbreviate`.
These two methods from the [`re` module][re-module] will return a `list` or a lazy `iterator` of results, respectively.
As of this writing, both of these methods benchmark slower than using `str.replace()` for cleaning.

[re-module]: https://docs.python.org/3/library/re.html
~~~~


Once the phrase is scrubbed and turned into a word `list`, the acronym is created via `reduce()`.
Once the phrase is cleaned and turned into a word `list`, the acronym is created via `reduce()`.
`reduce()` is a method from the [`functools`][functools] module, which provides support for higher-order functions and functional programming in Python.


[`functools.reduce()`][reduce] applies an anonymous two-argument function (_the [lambda][python lambdas] in the code example_) to the items of an iterable.
The application of the function travels from left to right, so that the iterable becomes a single value (_it is "reduced" to a single value_).
The application of the function travels from left to right, so that the iterable becomes a single value (_it is "reduced" to a single value_).


Using code from the example above, `reduce(lambda start, word: start + word[0], ['GNU', 'IMAGE', 'MANIPULATION', 'PROGRAM'])` would calculate `((('GNU'[0] + 'IMAGE'[0])+'MANIPULATION'[0])+'PROGRAM'[0])`, or `GIMP`.
The left argument, `start`, is the _accumulated value_ and the right argument, `word`, is the value from the iterable that is used to update the accumulated 'total'.
The optional 'initializer' value '' is used here, and is placed ahead/before the items of the iterable in the calculation, and serves as a default if the iterable that is passed is empty.
Using code from the example above, `reduce(lambda start, word: start + word[0], ["GNU", "IMAGE", "MANIPULATION", "PROGRAM"])` would calculate `((("GNU"[0] + "IMAGE"[0]) + "MANIPULATION"[0]) + "PROGRAM"[0])`, or `GIMP`.
The left argument, `start`, is the _accumulated value_ and the right argument, `word`, is the value from the iterable that is used to update the accumulated 'total'.
The optional 'initializer' value `""` is used here, and is placed before the items of the iterable in the calculation, and serves as a default if the iterable that is passed is empty.


Since using `reduce()` is fairly succinct, it is put directly on the `return` line to produce the acronym rather than assigning and returning an intermediate variable.
Since using `reduce()` is fairly succinct, it is put directly on the `return` line to produce the acronym, rather than assigning and returning an intermediate variable.


In benchmarks, this solution performed about as well as both the `loops` and the `list-comprehension` solutions.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from functools import reduce

def abbreviate(to_abbreviate):
phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split()
phrase = to_abbreviate.replace("-", " ").replace("_", " ").upper().split()
Comment thread
Yrahcaz7 marked this conversation as resolved.

return reduce(lambda start, word: start + word[0], phrase, "")
Original file line number Diff line number Diff line change
@@ -1,43 +1,44 @@
# Scrub with `replace()` and join via `generator-expression`
# Clean with `replace()` and join via `generator-expression`


```python
def abbreviate(to_abbreviate):
phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split()
phrase = to_abbreviate.replace("-", " ").replace("_", " ").upper().split()

# note the lack of square brackets around the comprehension.
return ''.join(word[0] for word in phrase)
# Note the lack of square brackets around the comprehension.
return "".join(word[0] for word in phrase)
```


- This approach begins by using [`str.replace()`][str-replace] to "scrub" (_remove_) non-letter characters such as `'`,`-`,`_`, and white space from `to_abbreviate`.
- The phrase is then upper-cased by calling [`str.upper()`][str-upper],
- This approach begins by using [`str.replace()`][str-replace] on `to_abbreviate` to convert non-letter characters such as `-` and `_` into spaces.
- The phrase is then upper-cased by calling [`str.upper()`][str-upper].
- Finally, the phrase is turned into a `list` of words by calling [`str.split()`][str-split].

The three methods above are all [chained][chaining] together, with the output of one method serving as the input to the next method in the "chain".
This works because both `replace()` and `upper()` return strings, and both `upper()` and `split()` take strings as arguments.
However, if `split()` were called first, `replace()` and `upper()` would fail, since neither method will take a `list` as input.
The three methods above are all [chained][chaining] together, with each method operating on the output of the method before it in the "chain".
This works because both `replace()` and `upper()` _operate on_ strings (as they are `str` methods) and _return_ strings.
If `split()` was called first, `replace()` and `upper()` would fail, since they cannot operate on the `list` returned by `split()`.

~~~~exercism/note
`re.findall()` or `re.finditer()` can also be used to "scrub" `to_abbreviate`.
These two methods from the `re` module will return a `list` or a lazy `iterator` of results, respectively.
As of this writing, both of these methods benchmark slower than using `str.replace()` for scrubbing.
`re.findall()` or `re.finditer()` can also be used to clean `to_abbreviate`.
These two methods from the [`re` module][re-module] will return a `list` or a lazy `iterator` of results, respectively.
As of this writing, both of these methods benchmark slower than using `str.replace()` for cleaning.

[re-module]: https://docs.python.org/3/library/re.html
~~~~


A [`generator-expression`][generator-expression] is then used to iterate through the phrase and select the first letters of each word via [`bracket notation`][subscript notation].


Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory.
Generator expressions are short-form [generators][generators] lazy iterators that produce their values _on demand_, instead of saving them to memory.
This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string.
Other "separator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples.
Since the generator expression and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym.
Other "separator" strings can be used with `str.join()` see [concept:python/string-methods]() for some additional examples.
Since the generator expression and `join()` are fairly succinct, they are put directly on the `return` line, rather than assigning and returning an intermediate variable for the acronym.


In benchmarks, this solution was surprisingly slower than the `list comprehension` version.
[This article][Oscar Alsing] from Oscar Alsing briefly explains why.
[This article][Oscar-Alsing-article] from Oscar Alsing briefly explains why.

[Oscar Alsing]: https://www.oscaralsing.com/list-comprehension-vs-generator-expression/#:~:text=List%20comprehensions%20are%20usually%20faster,difference%20is%20often%20quite%20small.
[Oscar-Alsing-article]: https://www.oscaralsing.com/list-comprehension-vs-generator-expression/#:~:text=List%20comprehensions%20are%20usually%20faster,difference%20is%20often%20quite%20small.
[chaining]: https://pyneng.readthedocs.io/en/latest/book/04_data_structures/method_chaining.html
[generator-expression]: https://dbader.org/blog/python-generator-expressions
[generators]: https://dbader.org/blog/python-generators
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def abbreviate(to_abbreviate):
phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split()
phrase = to_abbreviate.replace("-", " ").replace("_", " ").upper().split()

# note the lack of square brackets around the comprehension.
return ''.join(word[0] for word in phrase)
# Note the lack of square brackets around the comprehension.
return "".join(word[0] for word in phrase)
Loading
Loading