From ccb7c9d3356527b5fffc1071bdebc815987b4ea0 Mon Sep 17 00:00:00 2001 From: Michael Morehouse <640167+yawpitch@users.noreply.github.com> Date: Thu, 27 Feb 2020 13:01:04 +0000 Subject: [PATCH] Concept extraction / references mega merge * Create common working area * Extract Concepts from v2 exercise: reverse-string * Create reverse-string.md * Update reverse-string.md * Add Concepts from v2 exercise: variable-length-quantity * Add first concepts group * Improved concepts as per PR review * Adds concept from binary-search-tree * Add initial list First pass concepts for `allergies` to address #460 * Initial list of concepts First pass list of concepts to address #459 * Add Concepts for v2 exercise: phone-number * Add phone-number Python concepts * Small update to index access and slice topics. * Add notes from review. - more information about classes, inheritance - flesh out privacy, public and non-public - clarify wording around iterables and index/slice access * One more note about brackets and strings. * Add Concepts for v2 exercise: hamming * Add concepts for hamming * Add note about tuple unpacking. * Add notes about polymorphism, builtins, and dunder methods. * Some whitespace fixes. * [WIP] `clock` exercise concepts. * Extract Concepts from v2 exercise: markdown * Initial commit for markdown exercise concepts. * Concept starter for markdown * Added detail to Markdown concepts * Final edits before harmonization Final Markdown edits before we merge and harmonize. * Add Concepts for v2 exercise: matrix * `matrix` exercise concepts (issue #386) First pass of concepts for `matrix ` exercise in python. Pretty sure this is too detailed, but wanted to get something for review before proceeding with additional exercises. * Edits to better match #290 Formatting Edited concepts to better match the formatting of issue #290 * Typo correction * added title * Extract Concepts from v2 exercise: rna-transcription * Beginning of Concepts for rna-transcription * More detailed concepts for rna-trranscription More detailed concepts for rna-transcription exrcise. * Added title * Extract Concepts from v2 exercise: robot-simulator * Beginning of concepts for robot-simulator. * WIP Concepts * Additional detail for concepts * Detail third pass Third pass on adding concept detail. * Additional detail for concepts. * Edits per PR Feedback Numerous spelling corrections. Additional edits to address comments from last review. * [WIP] Concept implementation instructions * Adds instructions for exercise implementation * Adds correction as per PR reviews * Harmonize, part 1 * fix relative links in references/README.md * First pass at harmonization Shifts all documents to a common format, adds minimal link tagging to the "concept" currently listed in each file. These will really need multiple more passes, as they diverge from each other even when describing the same topic. Many extraneous topics have crept in, added in an "aspirational" fashion to the exercises; we may need to trim some of that. * Pulling in examples from BethanyG * [WIP] Extracted concept unification * Unification of extracted concepts * Typos and duplicates remove * Duplicates concept unification * Concepts have now links to original file * Update languages/reference/README.md Co-Authored-By: Erik Schierboom Co-authored-by: khoivan88 <33493502+khoivan88@users.noreply.github.com> Co-authored-by: David G Co-authored-by: Ashley Drake Co-authored-by: Pedro Romano Co-authored-by: BethanyG Co-authored-by: Erik Schierboom --- reference/concepts/pep_8_style_guide.md | 15 ++ .../concepts/python_enhancement_proposals.md | 10 + reference/concepts/pythonic.md | 18 ++ reference/concepts/zen_of_python.md | 27 +++ reference/exercise-concepts/allergies.md | 81 ++++++++ .../exercise-concepts/binary-search-tree.md | 83 ++++++++ reference/exercise-concepts/clock.md | 45 +++++ reference/exercise-concepts/hamming.md | 45 +++++ reference/exercise-concepts/leap.md | 27 +++ reference/exercise-concepts/markdown.md | 177 ++++++++++++++++++ reference/exercise-concepts/matrix.md | 79 ++++++++ reference/exercise-concepts/phone-number.md | 59 ++++++ reference/exercise-concepts/reverse-string.md | 33 ++++ .../exercise-concepts/rna-transcription.md | 22 +++ .../exercise-concepts/robot-simulator.md | 93 +++++++++ .../variable-length-quantity.md | 61 ++++++ reference/implementing-a-concept-exercise.md | 60 +++++- 17 files changed, 932 insertions(+), 3 deletions(-) create mode 100644 reference/concepts/pep_8_style_guide.md create mode 100644 reference/concepts/python_enhancement_proposals.md create mode 100644 reference/concepts/pythonic.md create mode 100644 reference/concepts/zen_of_python.md create mode 100644 reference/exercise-concepts/allergies.md create mode 100644 reference/exercise-concepts/binary-search-tree.md create mode 100644 reference/exercise-concepts/clock.md create mode 100644 reference/exercise-concepts/hamming.md create mode 100644 reference/exercise-concepts/leap.md create mode 100644 reference/exercise-concepts/markdown.md create mode 100644 reference/exercise-concepts/matrix.md create mode 100644 reference/exercise-concepts/phone-number.md create mode 100644 reference/exercise-concepts/reverse-string.md create mode 100644 reference/exercise-concepts/rna-transcription.md create mode 100644 reference/exercise-concepts/robot-simulator.md create mode 100644 reference/exercise-concepts/variable-length-quantity.md diff --git a/reference/concepts/pep_8_style_guide.md b/reference/concepts/pep_8_style_guide.md new file mode 100644 index 0000000000..90d43b403a --- /dev/null +++ b/reference/concepts/pep_8_style_guide.md @@ -0,0 +1,15 @@ +# PEP 8 Style + +Unlike most earlier languages -- and many newer -- Python has an "official" style guide, and has had one for quite some time. + +> One of [Python creator Guido von Rossum]'s key insights is that code is read much more often than it is written. The guidelines provided here are intended to improve the readability of code and make it consistent across the wide spectrum of Python code. As [PEP 20](zen_of_python.md) says, "Readability counts". + +The various conventions within it might at times seem arbitrary, but they've been carefully chosen to enhance the readability of Python code, and should generally be adopted and adhered to unless explicitly overridden by a project-specific style guide, which itself should be a derivative of PEP 8. + +## Tools + +Various tools exist in the Python ecosystem for ensuring that your code doesn't grossly violate PEP 8 conventions. Currently the two most recommended (if opinionated) ones would be [Pylint](https://www.pylint.org/), which will analyze your code for style as well as logical errors, and [Black](https://black.readthedocs.io/en/stable/), which will autoformat your code to take the guesswork out of readability. + +## Resources + +- [PEP-8](https://www.python.org/dev/peps/pep-0008/) diff --git a/reference/concepts/python_enhancement_proposals.md b/reference/concepts/python_enhancement_proposals.md new file mode 100644 index 0000000000..b9398d9c8e --- /dev/null +++ b/reference/concepts/python_enhancement_proposals.md @@ -0,0 +1,10 @@ +# Python Enhancement Proposals (PEPs) + +Python is an incredibly popular language with a _great_ many users across an impressive number of domains; as such evolving the language is a complex undertaking that can't be rushed. The Python Enhancement Proposal (commonly known as PEP) process exists to provide a formal mechanism by which the community can propose, debate, vote on, and ultimately adopt significant changes to the language itself. + +Even if you doubt you'll ever submit a PEP it's a good idea to keep abreast of what PEPs are out there and what changes are coming to a Python near you. + +Two PEPs in particular will serve as a useful jumping off point: + +- [PEP 0](https://www.python.org/dev/peps/) lists all current and historic PEPs. +- [PEP 1](https://www.python.org/dev/peps/pep-0001/) explains the PEP process. diff --git a/reference/concepts/pythonic.md b/reference/concepts/pythonic.md new file mode 100644 index 0000000000..d2ded93d36 --- /dev/null +++ b/reference/concepts/pythonic.md @@ -0,0 +1,18 @@ +# Pythonic + +An adjective used to describe well-constructed, high-quality Python code that is as easily understood and well designed as is possible for the task at hand. A piece of code that is "more Pythonic" will be easier to read and understand, and therefore more pleasurable to use, then "less Pythonic" code that accomplishes the same task. + +A quality admired in a Pythonista -- the community's name for an adept Python user -- is the desire to always write code that is as Pythonic as possible. + +## Qualities + +Any given piece of code tends to be considered more Pythonic if it: + +1. Adheres to the principles of the [Zen of Python](zen_of_python.md). +1. Uses the current "best practice" idioms of the language, as the language and those practices evolve. +1. Uses the common style conventions described in [PEP 8](pep_8_style_guide.md) + +## Further Reading + +- [What is Pythonic](https://blog.startifact.com/posts/older/what-is-pythonic.html) +- [Python Guide](https://docs.python-guide.org/writing/style/) diff --git a/reference/concepts/zen_of_python.md b/reference/concepts/zen_of_python.md new file mode 100644 index 0000000000..dac7aeb214 --- /dev/null +++ b/reference/concepts/zen_of_python.md @@ -0,0 +1,27 @@ +# The Zen of Python + +The _Zen of Python_ by Tim Peters, and also known as [PEP-20](https://www.python.org/dev/peps/pep-0020/) is a philosophical statement of Python's foundational principles and ideals. It's neither exhaustive nor binding, but it's often quoted in whole or in part when there's any need to determine which of two functionally identical idioms or pieces of code is _better_ or more [Pythonic](pythonic.md). + +- Beautiful is better than ugly. +- Explicit is better than implicit. +- Simple is better than complex. +- Complex is better than complicated. +- Flat is better than nested. +- Sparse is better than dense. +- Readability counts. +- Special cases aren't special enough to break the rules. +- Although practicality beats purity. +- Errors should never pass silently. +- Unless explicitly silenced. +- In the face of ambiguity, refuse the temptation to guess. +- There should be one-- and preferably only one --obvious way to do it. +- Although that way may not be obvious at first unless you're Dutch. +- Now is better than never. +- Although never is often better than *right* now. +- If the implementation is hard to explain, it's a bad idea. +- If the implementation is easy to explain, it may be a good idea. +- Namespaces are one honking great idea -- let's do more of those! + +## Easter Egg + +If you ever need a refresher, just `import this`. And if you need an example of code that is explicitly _not_ Zen, check out the source of [this](https://github.com/python/cpython/blob/master/Lib/this.py). diff --git a/reference/exercise-concepts/allergies.md b/reference/exercise-concepts/allergies.md new file mode 100644 index 0000000000..7e0236ed96 --- /dev/null +++ b/reference/exercise-concepts/allergies.md @@ -0,0 +1,81 @@ +# Concepts of `allergies` + +## Example implementation + +After Python 3.4 an `enum.Flag` is the "one obvious" approach: + +```python +from enum import Flag, auto + +class Allergens(Flag): + eggs = auto() + peanuts = auto() + shellfish = auto() + strawberries = auto() + tomatoes = auto() + chocolate = auto() + pollen = auto() + cats = auto() + +class Allergies: + + def __init__(self, score): + mask = sum(a.value for a in Allergens) + self.flags = Allergens(score & mask) + + def allergic_to(self, item): + return Allergens[item] in self.flags + + @property + def lst(self): + return [a.name for a in Allergens if a in self.flags] +``` + +In prior versions an OrderedDict was necessary to _reliably_ ensure sort order and also O(1) lookup: + +```python +from collections import OrderedDict + +Allergens = OrderedDict( + eggs = 1, + peanuts = 2, + shellfish = 4, + strawberries = 8, + tomatoes = 16, + chocolate = 32, + pollen = 64, + cats = 128, +) + +class Allergies: + + def __init__(self, score): + mask = sum(Allergens.values()) + self.score = score & mask + + def allergic_to(self, item): + return bool(self.score & Allergens[item]) + + @property + def lst(self): + return [a for a in Allergens if self.allergic_to(a)] +``` + +There are also various solutions involving lists, but these all come with O(N) lookup. + +## Concepts + +- [Classes][classes]: the exercise relies on the `class` statement to create a custom class +- [Methods][methods]: the exercise relies on the `def` statement to create an instance method +- [Implied Argument][implied-argument]: the exercise relies on the implied passing of `self` as the first parameter of bound methods +- [Dunder Methods][dunder-methods]: the exercise relies on the `__init__` dunder method to control class instantiation +- [Enumerated Values][enumerated-values]: the exercise relies on a fixed enumeration of possible values in a data structure +- [Data Structures][data-structures]: the exercise requires the use of a collection like enum.Flag or collections.OrderedDict +- [Imports][imports]: a reasonably readable solution will require importing from the standard library +- [Powers of Two][powers-of-two]: the exercise relies on the use of powers of two in fundamental binary (bitwise) operations +- [Bitflags][bitflags]: a general understanding of bitflags is required to solve this exercise +- [Bitwise Operators][bitwise-operators]: this exercise relies on bitwise AND (`&`) and potentially bitwise LSHIFT (`<<`) to inspect the Boolean value of individual bits in a bitflag +- [Property Decorator][property-decorator]: this exercise relies on the `@property` decorator to provide read-only dynamic access to the current list of allergens +- [Membership Testing][membership-testing]: this exercise relies on testing membership of a value in a collection of values +- [Lookup Efficiency][lookup-efficiency]: an efficient solution requires knowing that membership testing is O(1) in **dict** and the **enum.Enum** variants, but is O(N) in **list** and other sequential types + diff --git a/reference/exercise-concepts/binary-search-tree.md b/reference/exercise-concepts/binary-search-tree.md new file mode 100644 index 0000000000..2d41938544 --- /dev/null +++ b/reference/exercise-concepts/binary-search-tree.md @@ -0,0 +1,83 @@ +# Concepts of binary-search-tree + +## Example implementation + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/binary-search-tree/example.py): + +```python +class TreeNode: + def __init__(self, data, left=None, right=None): + self.data = data + self.left = left + self.right = right + + def __str__(self): + fmt = 'TreeNode(data={}, left={}, right={})' + return fmt.format(self.data, self.left, self.right) + + +class BinarySearchTree: + def __init__(self, tree_data): + self.root = None + for data in tree_data: + self.add(data) + + def add(self, data): + if self.root is None: + self.root = TreeNode(data) + return + inserted = False + cur_node = self.root + + while not inserted: + if data <= cur_node.data: + if cur_node.left: + cur_node = cur_node.left + else: + cur_node.left = TreeNode(data) + inserted = True + elif data > cur_node.data: + if cur_node.right: + cur_node = cur_node.right + else: + cur_node.right = TreeNode(data) + inserted = True + + def _inorder_traverse(self, node, elements): + if node is not None: + self._inorder_traverse(node.left, elements) + elements.append(node.data) + self._inorder_traverse(node.right, elements) + + def data(self): + return self.root + + def sorted_data(self): + elements = [] + self._inorder_traverse(self.root, elements) + return elements +``` + +## Concepts + +- [class][class]: a general comprehension of class concept and and how it works is required, `class` statement +- [Implied Argument][implied-argument]: student needs to know how to use statement `self` in a class +- [class members][class-members]: student must know how members of a class work +- [class methods][class-methods]: student must know how methods of a class work inside and outside the class, the use and meaning of `def` statement +- [arguments][arguments]: concept over arguments of a function and how to use them is required +- [return value][return-value]: the knowledge of `return` statement could be a useful concept in this exercise +- [Dunder Methods][dunder-methods]: student needs to know when to use dunder methods `__init__` and `__str__` +- [overload][overload]: students need to overload methods and specifically dunder methods in this exercise +- [Constructor][constructor]: student needs to know how to build an object using its constructor +- [None][none]: student needs to know the meaning of `None` and how and when assign it to a variable +- [Identity][identity]: the best way to check if an element is `None` is via the _identity_ operator, `is` +- [Boolean][boolean]: concept required to solve the exercise +- [in][in]: use of the `in` statement is useful to look for an object into a list +- [for][for]: the `for ... in` concept is useful to loop over the lists +- [Loops][loops]: concept required to solve the exercise +- [Integer comparison][integer-comparison]: concept required to solve the exercise +- [Recursion][recursion]: recursion is a core concept in this exercise +- [Lists][lists]: knowledge of lists and iteration on lists is required for this exercise +- [Conditional structures][conditional-structures]: knowledge of conditional conceptis and `if...else` statements are required +- [Methods of list][methods-of-list]: the use of methods of list could be useful in this exercise. Methods like `append`, `pop`... + diff --git a/reference/exercise-concepts/clock.md b/reference/exercise-concepts/clock.md new file mode 100644 index 0000000000..8f02778b3d --- /dev/null +++ b/reference/exercise-concepts/clock.md @@ -0,0 +1,45 @@ +# Concepts of `clock` + +# Example implementation + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/clock/example.py): + +```python +class Clock: + 'Clock that displays 24 hour clock that rollsover properly' + + def __init__(self, hour, minute): + self.hour = hour + self.minute = minute + self.cleanup() + + def __repr__(self): + return "{:02d}:{:02d}".format(self.hour, self.minute) + + def __eq__(self, other): + return repr(self) == repr(other) + + def __add__(self, minutes): + self.minute += minutes + return self.cleanup() + + def __sub__(self, minutes): + self.minute -= minutes + return self.cleanup() + + def cleanup(self): + self.hour += self.minute // 60 + self.hour %= 24 + self.minute %= 60 + return self +``` + +## Concepts + +- [PEP 8 Style][pep-8-style]: PEP 8 is the Python official style guide. Black is emerging as the defacto "pyfmt" tool: should we recommend it? (since the advent of `gofmt` and then `rustfmt`, I'm totally sold on opinionated auto-format tools: saves time and no more bikeshedding) +- [Constants][constants]: Avoid "magic numbers", defining instead meaningfully named constants. PEP 8 convention for constants: `UPPER_SNAKE_CASE` +- [Classes][classes]: use of `class` to create a custom data structure +- [Methods][methods]: use of `def` to define a class's methods +- [Operator overloading][operator-overloading]: How to overload the `+` and `-` operators using the `__add__` and `__sub__` special methods. +- [Rich comparison methods][rich-comparison-methods]: The `__eq__` method is overloaded +- [String formatting][string-formatting]: How to format strings, ie `%` operator, `str.format`, f-strings diff --git a/reference/exercise-concepts/hamming.md b/reference/exercise-concepts/hamming.md new file mode 100644 index 0000000000..fca782f8cd --- /dev/null +++ b/reference/exercise-concepts/hamming.md @@ -0,0 +1,45 @@ +# Concepts of `hamming` + +## Example implementation + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/hamming/example.py): + +``` python +def distance(s1, s2): + if len(s1) != len(s2): + raise ValueError("Sequences not of equal length.") + + return sum(a != b for a, b in zip(s1, s2)) +``` + +## Concepts + +- [Function definition][function-definition]: functions are defined and named using the `def` keyword +- [Function signature][function-signature]: functions take named arguments which are accessible within the body of the function; this one requires the student to make a function that accepts 2 +- [Return value][return-value]: the function must return a number (int) +- [Strings][strings]: strings are used generally +- [Builtin functions][builtin-functions]: strings have a length, accessible by calling `len()`, a builtin python function +- [Iterable][iterable]: strings are iterable, which provides a lot of opportunity to leverage Python functions against them +- [Immutable][immutable]: strings are immutable (*immutability*) +- [Booleans][booleans]: this solution uses Boolean values (`True` / `False`) +- [Inequality][inequality]: this solution checks if `a` is not equal to `b`. +- [Booleans are integers][booleans-are-integers]: Booleans values are just named aliases for the integers 1 (`True`) and 0 (`False`) +- [Zip][zip]: builtin that joins multiple iterables into a single one +- [Enumeration][enumeration]: `zip()` in this solution creates an iterable, which is iterated over by using the `for ... in ` syntax +- [Sum][sum]: another builtin that operates on iterables +- [Tuple unpacking][tuple-unpacking]: the values in an iterable can be unpacked into variables and used, i.e. `for a, b in zip(s1, s2)` +- [Exception handling][exception-handling]: the exercise requires Exception handling +- [Raise][raise]: the student is required to raise an `Exception` for incorrect input +- [Exception hierarchy][exception-hierarchy]: the idiomatic `Exception` type is a `ValueError`, meaning the input is incorrect +- [Exception catching][exception-catching]: `Exceptions` can be caught from outside the scope where they are raised, using the `try/except` syntax. All `Exceptions` types inherit from the base class, `Exception` and thus can be caught by either checking specifically for the type of Exception, or for any Exception +- [Exception message][exception-message]: Custom error messages can (and should) be supplied to an Exception when raised +- [Operators][operators]: `!=` is "not equal", which is not the same thing as `is`, or an identity check, but is the inverse of `==`, which is equality +- [Loops][loops]: the `for ... in` syntax is useful for looping through a list or other iterable object +- [Generators][generators]: generators calculate then `yield` a value one at a time, as opposed to lists which calculate and return all values in memory at once. A generator will pick up where it leaves off, and generate one item at a time, on demand +- [Generator comprehension][generator-comprehension]: a generator comprehension is passed to `sum()` to drive summation without storing all the values in a list first +- [Tuple unpacking][tuple-unpacking]: iterating through a list of tuples, i.e. [(1, 2), (2,3)], each piece of each tuple can be unpacked into a separate variable (syntax: `a, b = (1, 2)`); this works for any sort of iterable (lists, for example, and even strings!) but is commonly used with tuples because they are typically of a known size/length, and so can be safely unpacked into N variables, with names. +- [Dunder Methods][dunder-methods]: "dunder" -> "double under", referring to the names of these methods being prefixed with two underscores, e.g. `__init__`. There is no formal privacy in Python, but conventionally a single underscore indicates a private method, or one that the programmer should assume may change at any time; methods without an underscore are considered part of an object's public API. Double underscores are even more special - they are used by Python's builtin functions like `len()`, for example, to allow objects to implement various interfaces and functionality. They can also be used for operator overloading. If you have a custom class that you would like to be able to compare to other instances of the same class, implementing `__lt__`, `__gt__`, `__eq__` etc. allow programmers to use the `>`, `<`, `=` operators. Dunder methods allow programmers to build useful objects with simple interfaces, i.e. you can add two instances together using `+` instead of writing something like `instance1.add(instance2)`. +- [Builtin Function][builtin-functions]: Python has several handy builtin functions in the stdlib that can operate on many types of data, e.g. `len()`, `max()`, `min()`. Under the hood these are implemented via dunder methods - if an object (and everything in Python is an object) implements the correct dunder methods (see that topic for more information), it can support use in these functions. (For example, if an object implements `__len__`, the len() will return that value.) Because these functions are not strictly tied to any data type, they can be used almost anywhere, and will crop up again and again as we learn Python. Docs: https://docs.python.org/3/library/functions.html +- [Polymorphism][polymorphism]: Python is "dynamically typed," meaning that variable names are bound to objects only, not to a particular type. You can assign `foo` to a string, and then reassign it to an `int` with no issues. "Polymorphism" formally means that different types respond to the same function - so the ability to add custom class instances together using `+`, for example, shows how Python can define the same function against different types. +- [Duck Typing][duck-typing]: Python is also a good example of "Duck typing," to wit, "if it walks like a duck, talks like a duck, it's a duck.". This is accomplished partly with "magic" or "dunder" methods (double-under) that provide various interfaces to an object. If an object implements `__iter__` and `__next__`, it can be iterated through; it doesn't matter what type the object actually is. + diff --git a/reference/exercise-concepts/leap.md b/reference/exercise-concepts/leap.md new file mode 100644 index 0000000000..deb14475f9 --- /dev/null +++ b/reference/exercise-concepts/leap.md @@ -0,0 +1,27 @@ +# Concepts of `leap` + +## Example implementation + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/leap/example.py): + +```python +def leap(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +``` + +## Concepts + +- [Functions][functions]: the exercise relies on a `def` statement to create a named function +- [Parameters][parameters]: the exercise requires a single positional parameter in the function signature +- [Return Value][return-value]: the exercise must use a `return` statement to return a value to the caller +- [Expressions][expressions]: the exercise relies on writing an expression that will be evaluated to a return value +- [Modular Division][modular-division]: the exercise relies on the `%` operator to check if one number is evenly divisible by another +- [Boolean Operators][boolean-operators]: the exercise relies on `and`, `or`, and (optionally) `not` to form Boolean predicates +- [Boolean Logic][boolean-logic]: the exercise relies on `and` and `or` to combine Boolean predicates into a single logical answer +- [Comparision][comparision]: the exercise relies on the `==` and `!=` operators to make binary comparisons between values +- [Equivalence][equivalence]: the exercise relies on the `==` and `!=` operators to check that two values are equivalent (or not) +- [Order of Evaluation][order-of-evaluation]: the exercise relies on parentheses to explicitly modify the normal order of evaluation of an expression +- [Operator Precedence][operator-precedence]: the exercise is most simply stated when the student understands the operator precedence binding rules of Python +- [Short-Circuiting][short-circuiting]: the exercise relies on short-circuiting to avoid unnecessary calculations +- [Generics][generics]: the exercise is polymorphic across numerical types (ie int, float, Decimal) +- [Duck Typing][duck-typing]: the exercise supports any argument that supports modular division and comparison to integers (ie int, float, Decimal) diff --git a/reference/exercise-concepts/markdown.md b/reference/exercise-concepts/markdown.md new file mode 100644 index 0000000000..db509b67df --- /dev/null +++ b/reference/exercise-concepts/markdown.md @@ -0,0 +1,177 @@ +# Concepts for `markdown` + +## Example implementation + +A less than ideal approach from the current [example.py](https://github.com/exercism/python/blob/master/exercises/markdown/example.py): + +```python +import re + +def parse(markdown): + lines = markdown.split('\n') + html = '' + in_list = False + in_list_append = False + for line in lines: + result = parse_line(line, in_list, in_list_append) + html += result['line'] + in_list = result['in_list'] + in_list_append = result['in_list_append'] + if in_list: + html += '' + return html + +def wrap(line, tag): + return '<{tag}>{line}'.format(line=line, tag=tag) + +def check_headers(line): + pattern = '# (.*)' + for index in range(6): + if re.match(pattern, line): + return wrap(line[(index + 2):], 'h' + str(index + 1)) + pattern = '#' + pattern + return line + +def check_bold(line): + bold_pattern = '(.*)__(.*)__(.*)' + bold_match = re.match(bold_pattern, line) + if bold_match: + return bold_match.group(1) + wrap(bold_match.group(2), 'strong')\ + + bold_match.group(3) + else: + return None + +def check_italic(line): + italic_pattern = '(.*)_(.*)_(.*)' + italic_match = re.match(italic_pattern, line) + if italic_match: + return italic_match.group(1) + wrap(italic_match.group(2), 'em')\ + + italic_match.group(3) + else: + return None + +def parse_line(line, in_list, in_list_append): + result = check_headers(line) + + list_match = re.match(r'\* (.*)', result) + + if (list_match): + if not in_list: + result = '
    ' + wrap(list_match.group(1), 'li') + in_list = True + else: + result = wrap(list_match.group(1), 'li') + else: + if in_list: + in_list_append = True + in_list = False + + if not re.match(')(.*)()(.*)', + r'\1\2

    \3

    \4\5', result) + + while check_bold(result): + result = check_bold(result) + while check_italic(result): + result = check_italic(result) + + if in_list_append: + result = '
' + result + in_list_append = False + + return { + 'line': result, + 'in_list': in_list, + 'in_list_append': in_list_append + } +``` + +An alternate example using [regular expressions](https://exercism.io/tracks/python/exercises/markdown/solutions/daf30e5227414a61a00bac391ee2bd79): + +```python +import re + + +def parse(markdown): + s = markdown + s = re.sub(r'__([^\n]+?)__', r'\1', s) + s = re.sub(r'_([^\n]+?)_', r'\1', s) + s = re.sub(r'^\* (.*?$)', r'
  • \1
  • ', s, flags=re.M) + s = re.sub(r'(
  • .*
  • )', r'
      \1
    ', s, flags=re.S) + for i in range(6, 0, -1): + s = re.sub(r'^{} (.*?$)'.format('#' * i), r'\1'.format(i), s, flags=re.M) + s = re.sub(r'^(?!<[hlu])(.*?$)', r'

    \1

    ', s, flags=re.M) + s = re.sub(r'\n', '', s) + return s +``` + +Another alternate example using [Python with Regex](https://exercism.io/tracks/python/exercises/markdown/solutions/a1f1d7b60bfc42818b2c2225fe0f8d7a) + +```python +import re + +BOLD_RE = re.compile(r"__(.*?)__") +ITALICS_RE = re.compile(r"_(.*?)_") +HEADER_RE = re.compile(r"(#+) (.*)") +LIST_RE = re.compile(r"\* (.*)") + + +def parse(markdown: str) -> str: + """ + Parse a simple markdown-formatted string to HTML. + """ + result = [] + for line in markdown.splitlines(): + # expand inline bold tags + line = BOLD_RE.sub(r"\1", line) + # expand inline italics tags + line = ITALICS_RE.sub(r"\1", line) + + # line may be a header item or a list item + is_header = HEADER_RE.match(line) + is_list = LIST_RE.match(line) + + # a header is not itself a paragraph + if is_header: + result.append("{1}".format(len(is_header.group(1)), + is_header.group(2))) + # neither is any part of a list + elif is_list: + # we may be appending to an existing list + if result and result[-1] == "": + result.pop() + # or starting a new one + else: + result.append("
      ") + result.extend(["
    • " + is_list.group(1) + "
    • ", "
    "]) + # everything else is a paragraph + else: + result.append("

    " + line + "

    ") + return "".join(result) +``` + +## Concepts + +- [Refactor][refactor]: Reviewing and rewriting (or re-organizing) code for clarity and efficiency. This exercise requires a re-write of pre-existing code that uses functions to parse passed-in text in markdown. +- [Functions][functions]: Tests for this exercise expect a function named `parse` that can be called to transform the _markdown_ formatted text and return HTML formatted text. +- [Function arguments][function-arguments]: The example solutions use functions that take function arguments to operate on passed in markdown strings. +- [Regular Expressions][regular-expressions]: Both the original code to be refactored for this exercise and the example solution import and use the `re` module for Regular Expressions in python. +- [Importing][importing]: Both the original code to be refactored for the exercise and the example solution use the `import` keyword to import the `re` module in support of Regular Expressions in python. +- [String Splitting][string-splitting]: The example solution uses `str.split()` to break the passed in markdown string into a list of lines broken up by the `\n` character. The alternate Python example solution uses `str.splitlines()` for the same effect across all line end characters. +- [Regular Expressions][regular-expressions]: the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compliled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or subsitituting. +- [Regular expressions][regular-expressions]: A Domain Specific Language (DSL) for text processing. Like many other programming languages in use, python supports a quasi-dialect of PCRE (_Perl compatible regular expressions_). `Regular expressions` can be used via the core python `re` module, or the third-party `regex` module. Both the original code to be refactored for this exercise and the example solutions use the core `re` module to access `regular expressions` functionality. +- [Return value][return-value]: Most of the functions in the example solution specify a _return_ value using the `return` keyword. +- [None][none]: Pythons null type, referred to when a null or "placeholder" is needed. It is in and of itself a singleton in any given python program. +- [Booleans][booleans]: True and False of type `bopl`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. +- [Assignment][assignment]: The example solution uses assignment for variables and other values. +- [Regular Expressions][regular-expression]: the `re.sub()` function of the `re` module that replaces a `regular expression` match with a new value. The example solutions use this function in various places to substitute _markdown_ syntax for _HTML_ syntax in the passed in markdown text. +- [Dictionaries][dictionaries]: Mapping type. The example solution employes a dictionary to return values from the `parse_line()` function. +- [For loops][for-loops]: The example solution uses `for` loops to iterate over various function inputs. +- [Iteration][iterable]: The example solution uses the `for _ in _` syntax to iterate over a list of lines. This is possible because a list is an `iterable`. +- [Conditionals][conditionals]: The example solution uses `if` to check for pattern matching and membership conditions in different functions for processing different markdown patterns. +- [Regular Expressions][regular-expressions]: Various functions in the re module return a `re.Match` _instance_ which in turn has a `Match.group` method. `Match.group` exists even if there are no groups specified in the pattern. See the [Match.group docs](https://docs.python.org/3/library/re.html#re.Match.group) for more detail. +- [Lists][lists]: The example uses lists in several places to hold text to be processed or searched - or for tracking the state of pieces of the passed-in text. +- [Range][range]: the `range()` built-in represents an immutable sequence of numbers (or any object that implements the __index__ magic method). Used in the example to control the number of loops while iterating through a passed-in line or list. diff --git a/reference/exercise-concepts/matrix.md b/reference/exercise-concepts/matrix.md new file mode 100644 index 0000000000..23dea60992 --- /dev/null +++ b/reference/exercise-concepts/matrix.md @@ -0,0 +1,79 @@ +# Concepts of `matrix` + +## Example implementation: + +From the existing [example.py](https://github.com/exercism/python/blob/master/exercises/matrix/example.py): + +```python +class Matrix: + def __init__(self, s): + self.rows = [[int(n) for n in row.split()] + for row in s.split('\n')] + self.columns = [list(tup) for tup in zip(*self.rows)] + + def row(self, index): + return self.rows[index - 1] + + def column(self, index): + return self.columns[index - 1] +``` + +An alternate implementation [processing columns in a method rather than an instance property](https://exercism.io/tracks/python/exercises/matrix/solutions/e5004e990ddc4582a50ecc1f660c31df): + +```python +class Matrix(object): + + def __init__(self, matrix_string): + self.matrix = [[int(ele) for ele in row.split(' ') ] for row in matrix_string.split('\n')] + + def row(self, index): + return self.matrix[index - 1] + + def column(self, index): + return [row[index - 1] for row in self.matrix] +``` + +An extended implementation [using `.copy()` to protect against accidental data mutation](https://exercism.io/tracks/python/exercises/matrix/solutions/b6a3486a35c14372b64fdc35e7c6f98f): + +```python +class Matrix(object): + def __init__(self, matrix_string): + # have to use "List Comprehension" to make lists in a list + self.matrix = [[int(num) for num in tmp.split()] for tmp in matrix_string.splitlines()] + + def row(self, index): # grab which ever row requested + return self.matrix[index - 1].copy() # use .copy() to protect accidental data issues + + def column(self, index): # grab the first number in each array + return [row[index - 1] for row in self.matrix] + +``` + +## Concepts + +- [Classes][classes]: the exercise objective is to define a `matrix` type. Tested methods are linked to a `matrix` class +- [Objects][objects]: creating different instances with different data representing different `matrices` is tested +- [Constructor][constructor]: customizing object initalization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property +- [Dunder Methods][dunder-methods]: the example uses the `__init__` magic method as its constructor for the class +- [Return Values][return-value]: "row" and "column" list values are expected from defined instance method(s) +- [Implicit Argument][implicit-argument]: the example uses the `self` implicit argument for methods and properties linked to a specific instance of the class +- [Namespaces][namespaces]: knowing to use `self`.`` for instance properties and `self` as first argument to instance methods in a class +- [Instance Methods][instance-methods]: tests for this exercises require one or more instance methods that will return a specified row or column list of the `matrix`. +- [Instance Properties][instance-properties]: this exercise rquires one or more instance properties to persist passed in data. +- [Mutability][mutability]: in the extended example, knowing there are no protected or private properties in python and adjusting coding patterns +- [Assignment][assignment]: instance properties need to be assigned passed in data +- [Method Arguments][method-arguments]: the methods returning "row" and "column" need to take both `self` and an integer as arguments +- [Lists][lists]: this exercise requires "row" or "column" be returnd as a `list`. A `list` of `lists` is also the reccommended way to process and store the passed-in data. +- [Indexing][indexing]: the "rows" and "columns" of this exercise need to be retrieved from a list of lists via index +- [Bracket Notation][bracket-notation]: knowing that `[]` should be used to refer to a value at a specific index in a list +- [Slicing][slicing]: the extended solution to this exercise can employ a slice (returns a copy) instead of calling `.copy()`. +- [Iteration][iteration]: the passed-in string is iterated over, and split into rows. The row lists are iterated over and split into elements +- [Iterables][iterables]: understanding that strings, lists, and other data structures can be iterated over in the same fashion +- [Iterators][iterators]: the example solution for this exercise uses `zip()`, which returns an _iterator_. +- [For Loop][for-loop]: iterating over the passed in `matrix` string using a `for` loop to extract "rows" and "columns" that are appended to a list +- [Comprehension Syntax][comprehension-syntax]: knowing that this is equivelent to a `for loop` - putting the row or column creation code _inside_ the list literal instead of using loop + append. +- [Zip][zip]: the example solution for this exercise uses this function to aggregage the column-wise elements of each rown list to form the matrix "columns". +- [Argument Unpacking][Argument Unpacking]: the example solution for this exercise uses `splat` (`*`) to unpack rows for the `zip()` function. +- [String Splitting][string-splitting]: the example uses `str.split` with and without seperators to break the passed in string into "rows" and then "elements" +- [Type Conversion][type-conversion]: the passed in data is in `str` format but the output is expected as a list of type `int`. +- [Int][int]: the example converts the parsed `str` elements into `int` diff --git a/reference/exercise-concepts/phone-number.md b/reference/exercise-concepts/phone-number.md new file mode 100644 index 0000000000..8f587c0f53 --- /dev/null +++ b/reference/exercise-concepts/phone-number.md @@ -0,0 +1,59 @@ +# Concepts of `phone-number` + +## Example implementation: + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/phone-number/example.py): + +```python +import re + + +class PhoneNumber: + def __init__(self, number): + self.number = self._clean(number) + self.area_code = self.number[:3] + self.exchange_code = self.number[3:6] + self.subscriber_number = self.number[-4:] + + def pretty(self): + return "({}) {}-{}".format( + self.area_code, self.exchange_code, self.subscriber_number + ) + + def _clean(self, number): + return self._normalize(re.sub(r"[^\d]", "", number)) + + def _normalize(self, number): + if len(number) == 10 or len(number) == 11 and number.startswith("1"): + valid = number[-10] in "23456789" and number[-7] in "23456789" + else: + valid = False + + if valid: + return number[-10:] + else: + raise ValueError("{} is not a valid phone number".format(number)) +``` + +## Concepts + +- [Class][class]: classes are defined with the `class :` syntax +- [Dunder Methods][dunder-methods]: User defined classes can (and generally do) overload the `__init__` method, whose first argument is `self`, because the result of `__init__` is a class *instance*. +- [Inheritance][inheritance]: The default `__str___` method is inherited from `Object`, which every class in Python inherits from. (See: inheritance) +- [Methods][methods]: classes can have instance *methods* which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid uneeded computation +- [Non-Public Methods][non-public-methods]: Methods or attributes (including those of an imported module) prefixed with an underscore, `_`, are conventionally treated as "non-public" methods. Python does not support data privacy in the way a language like Java does. Instead convention dictates that methods and attributes that are not prefixed with a single underscore can be expected to remain stable along with semver, i.e. a public method will be backwards compatible with minor version updates, and can change with major version updates. Generally, importing non-public functions or using non-public methods is discouraged, though Python will not explicitly stop the programmer from doing so. +- [Implied Argument][implied-argument]: within the class definition, methods and properties can be accessed via the `self.` notation +- [Inheritance][inheritance]: a "subclass" will inherit all methods, attributes from it's parent class, and can then override methods as needed. Overriding means the logic in the parent class is not used. The `super` builtin function (not shown here) exists to allow the programmer to defer logic up the inheritance chain to the parent class when needed. +- [Standard Library][standard-library]: the `re` module is an example of the Python stdlib (standard library), or included code libraries and tools that are frequently used in Python +- [Import][import]: to use the module, the `import` syntax can be used +- [Iterables][iterables]: characters in a string are *iterables* and are subject to index and slice access as described below +- [Immutable][immutable]: strings are immutable, and so cannot have values assigned; new strings can be created, however +- [String Formatting][string-formatting]: `str.format` can be used to format a string +- [Membership Testing][membership-testing]: the `in` keyword, as in `"s" in "string`, allows the user to check membership in the longer string +- [String Methods][string-methods]: strings (and other types) have built in instance methods - in this case, `"string".startswith("s")` which are called from the instance of the string itself +- [Indexing][indexing]: for iterables, individual items can be accessed with `stringname[x]` notation. Negative numbers start to count backwards +- [Slicing][slicing]: a slice within an iterable, i.e. the slice of items from `[x]` to `[y]`, can be accessed via `[x:y]` notation; a third parameter allows "skipping" by `z`, i.e. `stringname[x:y:z]` +- [Regular Expressions][regular-expressions]: regular expressions is a language of sorts that can detect substrings and extract groups from a string, as well as replace them with something else +- [Conditionals][conditionals]: `if ... else` and `elif` allow a programmer to switch code branches depending on some condition +- [Boolean Logic][boolean-logic]: the `or` and `and` keywords are used +- [Raise][raise]: an appropriate Exceptions must be raised with the `raise` keyword diff --git a/reference/exercise-concepts/reverse-string.md b/reference/exercise-concepts/reverse-string.md new file mode 100644 index 0000000000..81f9920e43 --- /dev/null +++ b/reference/exercise-concepts/reverse-string.md @@ -0,0 +1,33 @@ +# Concepts of `reverse-string` + +## Example implementation: + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/reverse-string/example.py): + +```python +def reverse(text: str = "") -> str: + """Reverse a given string""" + return text[::-1] +``` + +## Concepts + +- [Function][function]: `def` to create a function in Python +- [Immutability][immutability]: `text` str in Python is [immutable](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str). +In this exercise, you return a new string, the old string `text` is not changed. +- [Return Value][return-value]: this function return a string by this line: `return text[::-1]` +- [Slicing][slicing]: becase `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: + - `start`: 0-index of the start position, `start=0` by default (i.e., not specified) (start from the beginning) + - `stop`: 0-index of the stop position, `stop=-1` by default (i.e., not specified) (stop at the end) + - `stride`: number of skip step. For example, + ```python + >>> string = 'ABCDEF'[::2] + >>> print(string) + 'ACE' + ``` + - In this exercise, `stride = -1` means start from the end + Together effectively, slicing of `[::-1]` gives the reversed string + [Extra material for string slicing.](https://www.digitalocean.com/community/tutorials/how-to-index-and-slice-strings-in-python-3) + +- [Docstrings][docstrings]: used to document the function, normally situated right below `def func():` +- [Type hinting][type-hinting]: In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not neccessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. diff --git a/reference/exercise-concepts/rna-transcription.md b/reference/exercise-concepts/rna-transcription.md new file mode 100644 index 0000000000..b2b1294681 --- /dev/null +++ b/reference/exercise-concepts/rna-transcription.md @@ -0,0 +1,22 @@ +# Concepts of `rna-transcription` + +## Example implementation + +Modified from the existing [example.py](https://github.com/exercism/python/blob/master/exercises/rna-transcription/example.py) to remove Python 2 compatiblity noise: + +```python +DNA_TO_RNA = str.maketrans("AGCT", "UCGA") + +def to_rna(dna_strand): + return dna_strand.translate(DNA_TO_RNA) +``` + +## Concepts + +- [Static Methods][static-methods]: Distinct from built-in functions, instance methods, and class methods, these are methods that are bound to a class, rather than an instance, and called _without_ explicitly or implicitly passing in an object of the class. The example solution for this exercise uses the `static` `str` method `maketrans`. +- [String Methods][string-methods]: this exercise uses `str.maketrans()` (a static method of `str` that returns a dictionary to create a _translation table_ as required by the `str.translate()` instance method. This method is unusual in that it takes either a single dictionary or two strings of equal length. The example solution for this exercise uses `str.maketrans()` with a two-string argument. +- [Dictionary][dictionary]: mapping type that has key-value pairs. Returned by `str.maketrans` in the example code. Also one of the argument types accepted by `str.maketrans()`. +- [String Translation][string-translation]: the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the inital string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. +- [Function][function]: A named (_and often reusable_) section of code that performs a specific task. It may or may not have _arguments_ passed in, and may or may not _return_ data. Created using the `def` keyword. +- [Function Arguments][function-arguments]: Parameters passed into a function. In python, these are noted in the `()` following a function name. The example code uses a function named `to_rna()` with an argument of `dna_strand`. +- [Return Value][return-value]: the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an expicit `return` keyword and statment will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` diff --git a/reference/exercise-concepts/robot-simulator.md b/reference/exercise-concepts/robot-simulator.md new file mode 100644 index 0000000000..4094dd2e11 --- /dev/null +++ b/reference/exercise-concepts/robot-simulator.md @@ -0,0 +1,93 @@ +# Concepts of `robot-simulator` + +## Example implementation + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/robot-simulator/example.py): + +```python +NORTH, EAST, SOUTH, WEST = range(4) + + +class Compass: + compass = [NORTH, EAST, SOUTH, WEST] + + def __init__(self, direction=NORTH): + self.direction = direction + + def left(self): + self.direction = self.compass[self.direction - 1] + + def right(self): + self.direction = self.compass[(self.direction + 1) % 4] + + +class Robot: + def __init__(self, direction=NORTH, x=0, y=0): + self.compass = Compass(direction) + self.x = x + self.y = y + + def advance(self): + if self.direction == NORTH: + self.y += 1 + elif self.direction == SOUTH: + self.y -= 1 + elif self.direction == EAST: + self.x += 1 + elif self.direction == WEST: + self.x -= 1 + + def turn_left(self): + self.compass.left() + + def turn_right(self): + self.compass.right() + + def move(self, commands): + instructions = {'A': self.advance, + 'R': self.turn_right, + 'L': self.turn_left} + for cmd in commands: + if cmd in instructions: + instructions[cmd]() + + @property + def direction(self): + return self.compass.direction + + @property + def coordinates(self): + return (self.x, self.y) +``` + + +## Concepts + +- [Constants][constants]: are not enforced by the runtime, but are used via the convention of `UPPER_CASE` to signal that these values are expected to remain unchanged. Preferably, constants are defined at a module level. The example solution uses the `UPPER_CASE` convention to define NORTH, SOUTH, EAST, and WEST constants. +- [Multiple Assignment][multiple-assignment]: Python allows multiple assignment, assigning the items on the left of `=` _in order_ to the values on the right of `=` by forming tuples and then "unpacking" them. This exercise solution uses multiple assignment for the constant values NORTH, EAST, SOUTH, WEST. +- [Range][range]: the `range()` built-in type represents an immutable sequence of numbers (or any object that implements the `__index__` dunder method). Used in the example to represent the values from zero to 3 as assigned to NORTH, EAST, SOUTH, WEST. +- [Class][class]: the exercise objective is to define a `robot` type. Tested methods are linked to a `robot` class. +- [Instantiation][instantiation]: creating different instances of the `robot` class with different data representing different starting positions and bearing are tested. +- [Initialization][initialization]: customizing object instatiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. +- [Return Value][return-value]: knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statment and all return `None`. +- [Implicit Argument][implicit-argument]: the example uses `self` for methods and properties linked to a specific instance of the class. +- [Namespaces][namespaces]: knowing to use `self.` for instance attributes and `self` as first argument to instance methods in a class. Additionally, the example uses `self.()` to call a previously stored method name. +- [Instance Methods][instance-methods]: tests for this exercises require one or more instance methods that will take in a set of starting coordinates and a bearing and then accept a series of instructions that "move" the instance to a new set of coordinates and bearing. +- [Function Decorator][function-decorator]: a higher-order function that takes another function as an argument. The "decorating" function extends the behavior of the "decorated" function without explicitly modifying it (i.e. it _wraps_ or _decorates_ it). Called in python by using the `@` symbol and the function name ahead of the function being decorated. The example uses pythons built-in `property()` as a decorator (`@property`) to return a "compound" read-only instance property made up of two separate instance attributes. +- [Higher-Order Function][higher-order-function]: a function that takes one or more other functions as arguments, _returning_ a function as its return value. The example uses the built-in `property()` as a higher-order function through `@property`. +- [Property][property]: the `property()` built-in is a function that returns a property attribute. When used as a decorator, this transforms the passed-in method into a _getter_ method for read-only attribute with the same name and docstring. +- [Assignment][assignment]: the example uses assignment for all the instance properties and `instructions` dictionary. +- [Instance Attributes][instance-attributes]: this exercise rquires one or more instance attributes to persist passed in data. +- [Mutability][mutability]: in the example, knowing there are no protected or private properties in python and so consciously mutating `self.x`, `self.y` and `self.compass` through the called instance methods. +- [Method Parameters][method-parameters]: the example `__init__` method has `self`, direction, x, and y (coordinates) as parameters. It also uses `self` and `commands` (a string) for parameters of the `move()` method. +- [Default Arguments][default-arguments]: pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initally passed. +- [Dictionary][dictionary]: the example uses a dictionary to map paassed in move arguments to methods that perform the moves. The example also uses a dictionary/mapping created by calling `str.maketrans()`. +- [Indexing][indexing]: finding a value by key in a dictionary using `[]` The example uses passed in move arguments as `keys` to look up corresponding `values` (_method names_) for moving the robot in the _instructions_ dictionary. +- [Iteration][iteration]: the example uses a `for loop` to iterate through the letters of the passed-in `commands` string and looks up the corresponding values in a dictionary, so that the appropriate methods can be called to move the `robot`. +- [Composition][composition]: adding functionality from a class by incorporating an instance of that class in a class you are creating. The example creates a `robot` by instantiating a `compass` and assigning it to the `self`.compass attribute of `robot`. +- [Modular Division][modular-division]: the example uses the modulus operator to calculate the appropriate _compass_ setting when calling the `right` compass method +- [Lists][lists]: the example stores compass direction constants in a `list` +- [Call Semantics][call-semantics]: knowing that appending `()` to the name of an instance method _calls_ it, since instance methods are _callable_. +- [Equality Operator][equality-operator]: the `==` operator calls the dunder method `__eq__()`. By default, objects of different types are never considered equal to each other unless they are numerical types (ie int, float) or have otherwise overloaded the default implementation of the `__eq__` dunder method. `==` is always defined, but for some object types (_like class objects_) it is equivalent to calling `is`. See [`__eq__`](https: //docs.python.org/3/reference/datamodel.html#object.__eq__) for additional details. This exercise uses the equality operator to test that the `self.direction` attribute is equal to one of the constant values defined. +- [Dunder Methods][dunder-methods]: The example uses `__init__` as a constructor for the class, which also calls `__new__`. In addition, the example uses `__call__()` via the appending of `()` to instance method names, and `__eq__()` (_rich compairison_) via the use of `==` + diff --git a/reference/exercise-concepts/variable-length-quantity.md b/reference/exercise-concepts/variable-length-quantity.md new file mode 100644 index 0000000000..647f9bd568 --- /dev/null +++ b/reference/exercise-concepts/variable-length-quantity.md @@ -0,0 +1,61 @@ +# Concepts of `variable-length-quantity` + +## Example implementation + +From the current [example.py](https://github.com/exercism/python/blob/master/exercises/variable-length-quantity/example.py): + +```python +EIGHTBITMASK = 0x80 +SEVENBITSMASK = 0x7f + + +def encode_single(n): + bytes_ = [n & SEVENBITSMASK] + n >>= 7 + + while n > 0: + bytes_.append(n & SEVENBITSMASK | EIGHTBITMASK) + n >>= 7 + + return bytes_[::-1] + + +def encode(numbers): + return sum((encode_single(n) for n in numbers), []) + + +def decode(bytes_): + values = [] + n = 0 + + for i, byte in enumerate(bytes_): + n <<= 7 + n += (byte & SEVENBITSMASK) + + if byte & EIGHTBITMASK == 0: + values.append(n) + n = 0 + elif i == len(bytes_) - 1: + raise ValueError('incomplete byte sequence') + + return values +``` + +## Concepts +- [Loops][loops]: the knowledge of `while` and `for` loops are useful to solve this exercise +- [Comparison][comparison]: concept required to solve the exercise, `==`, `>`, `<` +- [Builtin Functions][builtin-functions]: the `sum()` built-in function is a useful concept to solve the exercise +- [Lists][lists]: knowledge of lists and iteration on lists is required for this exercise +- [Builtin Functions][builtin-functions]: the `len()` built-in function is a useful concept in this exercise +- [Conditionals][conditionals]: knowledge of `if...else` statements is required +- [List Methods][list-methods]: the use of methods of list could be useful in this exercise. Methods like `append`, `pop`... +- [Parameters][parameters]: concept over arguments of a function and how to use them is required +- [Return Value][return-value]: the knowledge of `return` statement could be a useful concept in this exercise +- [List Comprehensions][list-comprehensions]: the use of list comprehension can be useful in this exercise +- [Binary Numbers][binary-numbers]: binary numbers are a core important concept for this exercise +- [Bitwise Operators][bitwise-operators]: bitwise operators such as `<<`, `>>`, `&`, `|`, `~`, `^` are central to this exercise +- [Iteration][iteration]: the `for ... in` concept is useful to loop over the lists +- [Builtin Functions][builtin-functions]: the `enumerate` built-in function is a useful concept in this exercise +- [Raise][raise]: the user could find useful the `Exception` concept to `raise` a `ValueError` for incorrect input +- [Exception Message][exception-message]: the user can use a custom error message inside the mentioned `ValueError` +- [Inheritance][inheritance]: the knowledge of inheritance can be useful in this exercises because all `Exceptions` types inherit from the base class diff --git a/reference/implementing-a-concept-exercise.md b/reference/implementing-a-concept-exercise.md index 81fad710a6..4d7a5eb584 100644 --- a/reference/implementing-a-concept-exercise.md +++ b/reference/implementing-a-concept-exercise.md @@ -1,5 +1,59 @@ -# How to implement an Python concept exercise +# How to implement a Python concept exercise -TODO: describe how to implement a concept exercise for the Python track. For inspiration, check out the [C# version of this file][csharp-implementing]. +This document describes the steps required to implement a concept exercise for the Python track. As this document is generic, the following placeholders are used: -[csharp-implementing]: ../../csharp/reference/implementing-a-concept-exercise.md +- ``: the name of the exercise in kebab-case (e.g. `anonymous-methods`). +- ``: the name of the exercise in snake_case (e.g. `anonymous_methods`). + +Before implementing the exercise, please make sure you have a good understanding of what the exercise should be teaching (and what not). This information can be found in the exercise's GitHub issue. + +To implement a concept exercise, the following files need to be created: + +``` +languages +└── python + └── exercises + └── concept + └── + ├── .docs + | ├── instructions.md + | ├── introduction.md + | ├── hints.md + | └── after.md + ├── .meta + | ├── config.json + | └── example.py + ├── .py + └── _test.py + └── practice + └── + ├── .docs + | ├── instructions.md + | ├── introduction.md + | ├── hints.md + | └── after.md + ├── .meta + | ├── config.json + | └── example.py + ├── .py + └── _test.py + +``` + +## Step 1: add track-specific files + +These are files specific to the Python track: +- `src/.py`: the stub implementation file, which is the starting point for students to work on the exercise. +- `test/_test.py`: the test suite. +- `.meta/example.py`: an example implementation that passes all the tests. + +## Step 2: add common files + +How to create the files common to all tracks is described in the [how to implement a concept exercise document][how-to-implement-a-concept-exercise]. + +## Inspiration + +When implementing an exercise, it can be very useful to look at already implemented Python exercises. You can also check the exercise's [general concepts documents][reference] to see if other languages have already implemented an exercise for that concept. + +[reference]: ../../../reference +[how-to-implement-a-concept-exercise]: ../../../docs/maintainers/generic-how-to-implement-a-concept-exercise.md