Skip to content

Commit

Permalink
Initial version taken from exercism/python
Browse files Browse the repository at this point in the history
  • Loading branch information
reingart committed Apr 9, 2024
0 parents commit 190913a
Show file tree
Hide file tree
Showing 504 changed files with 23,195 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Exercism

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 2 additions & 0 deletions accumulate/accumulate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def accumulate(collection, operation):
pass
39 changes: 39 additions & 0 deletions accumulate/accumulate_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest

from accumulate import accumulate


class AccumulateTest(unittest.TestCase):
def test_empty_sequence(self):
self.assertEqual(accumulate([], lambda x: x / 2), [])

def test_pow(self):
self.assertEqual(
accumulate([1, 2, 3, 4, 5], lambda x: x * x), [1, 4, 9, 16, 25])

def test_divmod(self):
self.assertEqual(
accumulate([10, 17, 23], lambda x: divmod(x, 7)),
[(1, 3), (2, 3), (3, 2)])

def test_composition(self):
inp = [10, 17, 23]
self.assertEqual(
accumulate(
accumulate(inp, lambda x: divmod(x, 7)),
lambda x: 7 * x[0] + x[1]), inp)

def test_capitalize(self):
self.assertEqual(
accumulate(['hello', 'world'], str.upper), ['HELLO', 'WORLD'])

def test_recursive(self):
inp = ['a', 'b', 'c']
out = [['a1', 'a2', 'a3'], ['b1', 'b2', 'b3'], ['c1', 'c2', 'c3']]
self.assertEqual(
accumulate(
inp, lambda x: accumulate(list('123'), lambda y: x + y)), out)


if __name__ == '__main__':
unittest.main()
22 changes: 22 additions & 0 deletions accumulate/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Instructions

Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.

Given the collection of numbers:

- 1, 2, 3, 4, 5

And the operation:

- square a number (`x => x * x`)

Your code should be able to produce the collection of squares:

- 1, 4, 9, 16, 25

Check out the test suite to see the expected function signature.

## Restrictions

Keep your hands off that collect/map/fmap/whatchamacallit functionality provided by your standard library!
Solve this one yourself using other basic tools instead.
2 changes: 2 additions & 0 deletions acronym/acronym.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def abbreviate(words):
pass
43 changes: 43 additions & 0 deletions acronym/acronym_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# These tests are auto-generated with test data from:
# https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json
# File last updated on 2023-07-20

import unittest

from acronym import (
abbreviate,
)


class AcronymTest(unittest.TestCase):
def test_basic(self):
self.assertEqual(abbreviate("Portable Network Graphics"), "PNG")

def test_lowercase_words(self):
self.assertEqual(abbreviate("Ruby on Rails"), "ROR")

def test_punctuation(self):
self.assertEqual(abbreviate("First In, First Out"), "FIFO")

def test_all_caps_word(self):
self.assertEqual(abbreviate("GNU Image Manipulation Program"), "GIMP")

def test_punctuation_without_whitespace(self):
self.assertEqual(abbreviate("Complementary metal-oxide semiconductor"), "CMOS")

def test_very_long_abbreviation(self):
self.assertEqual(
abbreviate(
"Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"
),
"ROTFLSHTMDCOALM",
)

def test_consecutive_delimiters(self):
self.assertEqual(abbreviate("Something - I made up from thin air"), "SIMUFTA")

def test_apostrophes(self):
self.assertEqual(abbreviate("Halley's Comet"), "HC")

def test_underscore_emphasis(self):
self.assertEqual(abbreviate("The Road _Not_ Taken"), "TRNT")
17 changes: 17 additions & 0 deletions acronym/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Instructions

Convert a phrase to its acronym.

Techies love their TLA (Three Letter Acronyms)!

Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG).

Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input.

For example:

| Input | Output |
| ------------------------- | ------ |
| As Soon As Possible | ASAP |
| Liquid-crystal display | LCD |
| Thank George It's Friday! | TGIF |
160 changes: 160 additions & 0 deletions acronym/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Introduction

There are multiple Pythonic ways to solve the Acronym exercise.
Among them are:

- Using `str.replace()` to scrub the input, and:
- joining with a `for loop` with string concatenation via the `+` operator.
- joining via `str.join()`, passing a `list-comprehension` or `generator-expression`.
- joining via `str.join()`, passing `map()`.
- joining via `functools.reduce()`.

- Using `re.findall()`/`re.finditer()` to scrub the input, and:
- joining via `str.join()`, passing a `generator-expression`.

- Using `re.sub()` for both cleaning and joining (_using "only" regex for almost everything_)`


## General Guidance

The goal of the Acronym exercise is to collect the first letters of each word in the input phrase and return them as a single capitalized string (_the acronym_).
The challenge is to efficiently identify and capitalize the first letters while removing or ignoring non-letter characters such as `'`,`-`,`_`, and white space.


There are two idiomatic strategies for non-letter character removal:
- Python's built-in [`str.replace()`][str-replace].
- The [`re`][re] module, (_regular expressions_).

For all but the most complex scenarios, using `str.replace()` is generally more efficient than using a regular expression.


Forming the final acronym is most easily done with a direct or indirect `loop`, after splitting the input into a word list via [`str.split()`][str-split].
The majority of these approaches demonstrate alternatives to the "classic" looping structure using various other iteration techniques.
Some `regex` methods can avoid looping altogether, although they can become very non-performant due to excessive backtracking.

Strings are _immutable_, so any method to produce an acronym will be creating and returning a new `str`.


## Approach: scrub with `replace()` and join via `for` loop

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

for word in phrase:
acronym += word[0]

return acronym
```

For more information, take a look at the [loop approach][approach-loop].


## Approach: scrub with `replace()` and join via `list comprehension` or `Generator expression`


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

return ''.join([word[0] for word in phrase])

###OR###

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

# note the parenthesis instead of square brackets.
return ''.join((word[0] for word in phrase))
```

For more information, check out the [list-comprehension][approach-list-comprehension] approach or the [generator-expression][approach-generator-expression] approach.


## Approach: scrub with `replace()` and join via `map()`

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

return ''.join(map(lambda word: word[0], phrase))
```

For more information, read the [map][approach-map-function] approach.


## Approach: scrub with `replace()` and join via `functools.reduce()`

```python
from functools import reduce


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

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

For more information, take a look at the [functools.reduce()][approach-functools-reduce] approach.


## Approach: filter with `re.findall()` and join via `str.join()`

```python
import re


def abbreviate(phrase):
removed = re.findall(r"[a-zA-Z']+", phrase)

return ''.join(word[0] for word in removed).upper()
```

For more information, take a look at the [regex-join][approach-regex-join] approach.


## Approach: use `re.sub()`

```python
import re


def abbreviate_regex_sub(to_abbreviate):
pattern = re.compile(r"(?<!_)\B[\w']+|[ ,\-_]")

return re.sub(pattern, "", to_abbreviate.upper())
```

For more information, read the [regex-sub][approach-regex-sub] approach.


## Other approaches

Besides these seven idiomatic approaches, there are a multitude of possible variations using different string cleaning and joining methods.

However, these listed approaches cover the majority of 'mainstream' strategies.


## Which approach to use?

All seven approaches are idiomatic, and show multiple paradigms and possibilities.
All approaches are also `O(n)`, with `n` being the length of the input string.
No matter the removal method, the entire input string must be iterated through to be cleaned and the first letters extracted.

Of these strategies, the `loop` approach is the fastest, although `list-comprehension`, `map`, and `reduce` have near-identical performance for the test data.
All approaches are fairly succinct and readable, although the 'classic' loop is probably the easiest understood by those coming to Python from other programming languages.


The least performant for the test data was using a `generator-expression`, `re.findall` and `re.sub` (_least performant_).

To compare performance of the approaches, take a look at the [Performance article][article-performance].

[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/acronym/approaches/functools-reduce
[approach-generator-expression]: https://exercism.org/tracks/python/exercises/acronym/approaches/generator-expression
[approach-list-comprehension]: https://exercism.org/tracks/python/exercises/acronym/approaches/list-comprehension
[approach-loop]: https://exercism.org/tracks/python/exercises/acronym/approaches/loop
[approach-map-function]: https://exercism.org/tracks/python/exercises/acronym/approaches/map-function
[approach-regex-join]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-join
[approach-regex-sub]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-sub
[article-performance]: https://exercism.org/tracks/python/exercises/isogram/articles/performance
6 changes: 6 additions & 0 deletions affine-cipher/affine_cipher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def encode(plain_text, a, b):
pass


def decode(ciphered_text, a, b):
pass
Loading

0 comments on commit 190913a

Please sign in to comment.