diff --git a/concepts/anonymous-functions/.meta/config.json b/concepts/anonymous-functions/.meta/config.json index 9b9e8da5a9..b6cf77edce 100644 --- a/concepts/anonymous-functions/.meta/config.json +++ b/concepts/anonymous-functions/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "A lambda expression, or an anonymous function, is a shorter way to create functions than using def.", + "authors": ["Steffan153"], "contributors": [] } diff --git a/concepts/anonymous-functions/introduction.md b/concepts/anonymous-functions/introduction.md index 54ff7637ac..d9bb69958e 100644 --- a/concepts/anonymous-functions/introduction.md +++ b/concepts/anonymous-functions/introduction.md @@ -1 +1,5 @@ -#TODO: Add introduction for this concept. +# Anonymous Functions / Lambdas + +A [`lambda`][lambdas] expression (also called an anonymous function) is a shorter way to create functions than using `def`. Unlike `def`, they are nameless, or **anonymous**, which means it is created without a name. + +[lambdas]: https://docs.python.org/3/howto/functional.html?highlight=lambda%20expression#small-functions-and-the-lambda-expression \ No newline at end of file diff --git a/concepts/anonymous-functions/links.json b/concepts/anonymous-functions/links.json index eb5fb7c38a..bec0b4a373 100644 --- a/concepts/anonymous-functions/links.json +++ b/concepts/anonymous-functions/links.json @@ -1,18 +1,9 @@ [ - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - } + { "description": "Python Docs: Defining Functions", "url": "https://docs.python.org/3/tutorial/controlflow.html#defining-functions" }, + { "description": "Python Docs Tutorial: Lambda Expressions", "url": "https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions" }, + { "description": "Functions as Objects in Python", "url": "https://medium.com/python-pandemonium/function-as-objects-in-python-d5215e6d1b0d" }, + { "description": "Composing Programs: Higher-Order Functions", "url": "https://composingprograms.com/pages/16-higher-order-functions.html" }, + { "description": "Learn by Example: Python Lambda Function", "url": "https://www.learnbyexample.org/python-lambda-function/" }, + { "description": "Real Python: How to Use Python Lambda Fuctions", "url": "https://realpython.com/python-lambda/" }, + { "description": "Trey Hunner: Overusing Lambda expressions in Python", "url": "https://treyhunner.com/2018/09/stop-writing-lambda-expressions/" } ] diff --git a/config.json b/config.json index 8b4fad9c49..66bff60c1d 100644 --- a/config.json +++ b/config.json @@ -175,6 +175,32 @@ "concepts": ["sets"], "prerequisites": ["basics", "dicts", "lists", "loops", "tuples"], "status": "beta" + }, + { + "slug": "secrets", + "name": "Secrets", + "uuid": "1e636fd3-6143-484e-a4fc-0ed6157fdfa1", + "concepts": ["anonymous-functions", "lambdas"], + "prerequisites": [ + "basics", + "booleans", + "comparisons", + "dicts", + "dict-methods", + "functions", + "function-arguments", + "higher-order-functions", + "iteration", + "lists", + "list-methods", + "numbers", + "sequences", + "sets", + "strings", + "string-methods", + "tuples" + ], + "status": "wip" } ], "practice": [ diff --git a/exercises/concept/secrets/.docs/hints.md b/exercises/concept/secrets/.docs/hints.md new file mode 100644 index 0000000000..98755868a6 --- /dev/null +++ b/exercises/concept/secrets/.docs/hints.md @@ -0,0 +1,33 @@ + +# Hints + +## General + +- Make use of [lambdas][lambdas]. + +## 1. Create an adder + +- Return a lambda which adds the argument from the lambda to the argument passed in to `secret_add`. + +## 2. Create a multiplier + +- Return a lambda which multiplies the argument from the lambda to the argument passed in to `secret_multiply`. + +## 3. Create a "max"-er + +- Return a lambda which returns either the argument from the lambda or the argument passed in to `secret_max`, whichever one's product is bigger. +- Use the `key` argument with the [`max`][max] function. + +## 4. Create a sorter + +- Return a lambda which takes one argument, a list of lists of an unknown amount of numbers. The lambda should return the list of lists, but sorted by `sublist[secret_index]`. +- Use the `key` argument with the [`sorted`][sorted] function. + +## 5. Create a function combiner + +- Return a lambda which [composes the functions][fn-composition] passed in to `secret_combine`. + +[fn-composition]: https://en.wikipedia.org/wiki/Function_composition_(computer_science) +[lambdas]: https://docs.python.org/3/howto/functional.html?highlight=lambda%20expression#small-functions-and-the-lambda-expression +[max]: https://docs.python.org/3/library/functions.html#max +[sorted]: https://docs.python.org/3/library/functions.html#sorted \ No newline at end of file diff --git a/exercises/concept/secrets/.docs/instructions.md b/exercises/concept/secrets/.docs/instructions.md new file mode 100644 index 0000000000..1c83107384 --- /dev/null +++ b/exercises/concept/secrets/.docs/instructions.md @@ -0,0 +1,59 @@ +# Instructions + +In this exercise, you've been tasked with writing the software for an encryption device that works by performing transformations on data. You need a way to flexibly create complicated functions by combining simpler functions together. + +For each task, return a function (using a `lambda`). + + +## 1. Create an adder + +Implement `secret_add`. It should return a function which takes one argument and adds to it the argument passed in to `secret_add`. + +```python +>>> adder = secret_add(2) +>>> adder(2) +4 +``` + +## 2. Create a multiplier + +Implement `secret_multiply`. It should return a function which takes one argument and multiplies it by the secret passed in to `secret_multiply`. + +```python +>>> multiplier = secret_multiply(7) +>>> multiplier(3) +21 +``` + +## 3. Create a "max"-er + +Implement `secret_max`. It should return a function which takes one argument (a list of exactly two numbers) and returns either the secret passed into `secret_max`, or the argument passed into the function that `secret_max` returns, whichever one's **product** is bigger. + +```python +>>> maxer = secret_max([7, 3]) +>>> maxer([2, 11]) +[2, 11] +``` + +## 4. Create a sorter + +Implement `secret_sort`. It should take a number, `secret_index`, and return a lambda which takes one argument, a list of lists of an unknown amount of numbers. The lambda should return the list of lists, but sorted by `sublist[secret_index]`. + +```python +>>> sorter = secret_sort(0) +>>> sorter([[3, 120, 5], [1, 5, 2], [8, 2, 23]]) +[[1, 5, 2], [3, 120, 5], [8, 2, 23]] +``` + +## 5. Create a function combiner + +Implement `secret_combine`. It should return a lambda which takes one argument and applies to it the two functions passed in to `secret_combine` in order. + +```python +>>> multiply = secret_multiply(7) +>>> divide = secret_add(3) +>>> combined = secret_combine(multiply, add) + +>>> combined(6) +25 +``` diff --git a/exercises/concept/secrets/.docs/introduction.md b/exercises/concept/secrets/.docs/introduction.md new file mode 100644 index 0000000000..d3dc6667cb --- /dev/null +++ b/exercises/concept/secrets/.docs/introduction.md @@ -0,0 +1,24 @@ +# Lambdas + +Python has a [`lambda`][lambdas] expression, which is a shorter way to create functions than using `def`. They are nameless, or **anonymous**, which means it is created without a name, unlike `def`. Since they are nameless, you can pass them directly into another function without giving it a name. It looks like this: + +```python +lambda num: num + 1 +``` + +In short, a `lambda expression` starts with the keyword `lambda`, followed by parameters (_separated by a comma, as you would do with a `def`-defined function_), a colon, and a `return` value. + +```exercism/caution +Be warned: unlike functional-first programming languages, Python's [lambdas][lambdas] are quite limited. They can only contain code that is computable as a single expression, and be written on a single line. [They can't contain statements.][statements] +``` + +It is customary to only use them in very constrained situations -- most often as [`sort`][sort], [`min`][min], or [`max`][max] keys, or as arguments to [`map()`][map], [`filter()`][filter] and [`functools.reduce()`][reduce]. They also execute in their own frame, which makes error handling and stack traces more effort, and often slows code execution if you're not careful. + +[lambdas]: https://docs.python.org/3/howto/functional.html?highlight=lambda%20expression#small-functions-and-the-lambda-expression +[statements]: https://docs.python.org/3/faq/design.html#why-can-t-lambda-expressions-contain-statements +[sort]: https://realpython.com/python-sort/ +[min]: https://docs.python.org/3/library/functions.html#min +[max]: https://docs.python.org/3/library/functions.html#max +[map]: https://realpython.com/python-map-function/ +[filter]: https://realpython.com/python-filter-function/ +[reduce]: https://realpython.com/python-reduce-function/ \ No newline at end of file diff --git a/exercises/concept/secrets/.meta/config.json b/exercises/concept/secrets/.meta/config.json new file mode 100644 index 0000000000..a8778e8de7 --- /dev/null +++ b/exercises/concept/secrets/.meta/config.json @@ -0,0 +1,11 @@ +{ + "blurb": "Learn about lambdas by writing the software for an encryption device.", + "authors": ["Steffan153"], + "contributors": ["bethanyg"], + "forked_from": ["elixir/secrets"], + "files": { + "solution": ["secrets.py"], + "test": ["secrets_test.py"], + "exemplar": [".meta/exemplar.py"] + } +} diff --git a/exercises/concept/secrets/.meta/design.md b/exercises/concept/secrets/.meta/design.md new file mode 100644 index 0000000000..469bbea10d --- /dev/null +++ b/exercises/concept/secrets/.meta/design.md @@ -0,0 +1,59 @@ +# Design + +## Goal + +This concept exercise is meant to teach an understanding/creation/use of `lambda` or `anonymous functions` in python. + +## Learning objectives + +- Understand what an `anonymous function` is, and how to create one + - The syntax of creating a `lambda` + - Using different `function argument` flavors with `lambda` +- Understand the differences between `lambdas` and Pythons "regular" `functions` +- Understand what problems are solved by using a `lambda` +- The pitfalls of `lambdas`, and when to avoid them +- Using `lambdas` as `key functions` in other situations such as `sort()` , `sorted()`, `min()`, and `max()` +- Applying arguments to a `lambda` via IIFE (_immediately invoked function expression_) +- Anti-patterns when using `lambdas` + +## Out of scope + +- `comprehensions` +- `comprehensions` in `lambdas` +- using a `decorator` on a `lambda` +- `functools` (_this will get its own exercise_) +- `generators` +- `map()`, `filter()`, and `reduce()` (_these will get their own exercise_) +- using an `assignment expression` or "walrus" operator (`:=`) in a `lambda` + +## Concepts + +- `anonymous-functions` +- `lambdas` +- `functions` +- `higher-order functions` +- `functions as arguments` +- `functions as returns` +- `nested funcitons` + +## Prerequisites + +These are the concepts/concept exercises the student needs to complete/understand before solving this concept exercise. + +- `basics` +- `booleans` +- `comparisons` +- `dicts` +- `dict-methods` +- `functions` +- `function-arguments` +- `higher-order functions` +- `iteration` +- `lists` +- `list-methods` +- `numbers` +- `sequences` +- `sets` +- `strings` +- `string-methods` +- `tuples` \ No newline at end of file diff --git a/exercises/concept/secrets/.meta/exemplar.py b/exercises/concept/secrets/.meta/exemplar.py new file mode 100644 index 0000000000..db203608ce --- /dev/null +++ b/exercises/concept/secrets/.meta/exemplar.py @@ -0,0 +1,45 @@ +def secret_add(secret): + """ + Return a lambda that adds the argument from the lambda to the argument passed into secret_add. + + :param secret: secret number to add (integer) + :return: lambda that takes a number and adds it to the secret + """ + return lambda addend: secret + addend + +def secret_multiply(secret): + """ + Return a lambda that multiplies the argument from the lambda to the argument passed into secret_multiply. + + :param secret: secret number to multiply (integer) + :return: lambda that takes a number and multiplies it to the secret + """ + return lambda multiplicand: secret * multiplicand + +def secret_max(secret): + """ + Return a lambda that returns either the argument from the lambda or the argument passed in to secret_max, whichever one's product is bigger. + + :param secret: secret list of two numbers (list) + :return: lambda that takes a list of two numbers + """ + return lambda num: max(secret, num, key=lambda l: l[0] * l[1]) + +def secret_sort(secret_index): + """ + Return a lambda that takes one argument, a list of lists of an unknown amount of numbers. The lambda should return the list of lists, but sorted by sublist[secret_index]. + + :param secret_index: secret index to sort by (integer) + :return: lambda that takes a list of lists of numbers and sorts them + """ + return lambda l: sorted(l, key=lambda x: x[secret_index]) + +def secret_combine(secret_function1, secret_function2): + """ + Return a lambda that takes one argument and applies to it the two functions passed in to secret_combine in order. + + :param secret_function1: function + :param secret_function2: function + :return: lambda that composes the two functions + """ + return lambda x: secret_function2(secret_function1(x)) diff --git a/exercises/concept/secrets/secrets.py b/exercises/concept/secrets/secrets.py new file mode 100644 index 0000000000..ea1d0d6836 --- /dev/null +++ b/exercises/concept/secrets/secrets.py @@ -0,0 +1,45 @@ +def secret_add(secret): + """ + Return a lambda that adds the argument from the lambda to the argument passed into secret_add. + + :param secret: secret number to add (integer) + :return: lambda that takes a number and adds it to the secret + """ + pass + +def secret_multiply(secret): + """ + Return a lambda that multiplies the argument from the lambda to the argument passed into secret_multiply. + + :param secret: secret number to multiply (integer) + :return: lambda that takes a number and multiplies it to the secret + """ + pass + +def secret_max(secret): + """ + Return a lambda that returns either the argument from the lambda or the argument passed in to secret_max, whichever one's product is bigger. + + :param secret: secret list of two numbers (list) + :return: lambda that takes a list of two numbers + """ + pass + +def secret_sort(secret_index): + """ + Return a lambda that takes one argument, a list of lists of an unknown amount of numbers. The lambda should return the list of lists, but sorted by sublist[secret_index]. + + :param secret_index: secret index to sort by (integer) + :return: lambda that takes a list of lists of numbers and sorts them + """ + pass + +def secret_combine(secret_function1, secret_function2): + """ + Return a lambda that takes one argument and applies to it the two functions passed in to secret_combine in order. + + :param secret_function1: function + :param secret_function2: function + :return: lambda that composes the two functions + """ + pass diff --git a/exercises/concept/secrets/secrets_test.py b/exercises/concept/secrets/secrets_test.py new file mode 100644 index 0000000000..74dfeb99ff --- /dev/null +++ b/exercises/concept/secrets/secrets_test.py @@ -0,0 +1,53 @@ +import unittest +import pytest +from secrets import secret_add, secret_multiply, secret_max, secret_sort, secret_combine + + +class TestLambdas(unittest.TestCase): + @pytest.mark.task(taskno=1) + def test_add_3(self): + self.assertEqual(secret_add(3)(3), 6, msg="secret_add(3)(3) should be 6") + + @pytest.mark.task(taskno=1) + def test_add_6(self): + self.assertEqual(secret_add(6)(9), 15, msg="secret_add(6)(9) should be 15") + + @pytest.mark.task(taskno=2) + def test_multiply_by_3(self): + self.assertEqual(secret_multiply(3)(6), 18, msg="secret_add(3)(6) should be 18") + + @pytest.mark.task(taskno=2) + def test_multiply_by_6(self): + self.assertEqual(secret_multiply(6)(7), 42, msg="secret_add(6)(7) should be 42") + + @pytest.mark.task(taskno=3) + def test_max_1(self): + self.assertEqual(secret_max([2, 11])([7, 3]), [2, 11], msg="secret_max([2, 11])([7, 3]) should be [2, 11]") + + @pytest.mark.task(taskno=3) + def test_max_2(self): + self.assertEqual(secret_max([4, 6])([5, 5]), [5, 5], msg="secret_max([4, 6])([5, 5]) should be [5, 5]") + + @pytest.mark.task(taskno=3) + def test_max_3(self): + self.assertEqual(secret_max([25, 73])([56, 32]), [25, 73], msg="secret_max([25, 73])([56, 32]) should be [25, 73]") + + @pytest.mark.task(taskno=4) + def test_sort_1(self): + self.assertEqual(secret_sort(0)([[3, 120, 5], [1, 5, 2], [8, 2, 23]]), [[1, 5, 2], [3, 120, 5], [8, 2, 23]], msg="secret_sort(0)([[3, 120, 5], [1, 5, 2], [8, 2, 23]]) should return the list sorted by the first item of every list inside the list") + + @pytest.mark.task(taskno=4) + def test_sort_2(self): + self.assertEqual(secret_sort(1)([[3, 15], [520, 1, 4], [64, 7, 3, 9, 2], [0, 27, 1, 5, 4, 2]]), [[520, 1, 4], [64, 7, 3, 9, 2], [3, 15], [0, 27, 1, 5, 4, 2]], msg="secret_sort(1)([[3, 15], ...]) should return the list sorted by the second item of every list") + + @pytest.mark.task(taskno=5) + def test_combine_1(self): + self.assertEqual(secret_combine(secret_add(4), secret_multiply(7))(6), 70, msg="secret_combine(secret_add(4), secret_multiply(7))(6) should be 70") + + @pytest.mark.task(taskno=5) + def test_combine_2(self): + self.assertEqual(secret_combine(secret_multiply(6), secret_add(4))(11), 70, msg="secret_combine(secret_multiply(6), secret_add(4))(11) should be 70") + + @pytest.mark.task(taskno=5) + def test_combine_3(self): + self.assertEqual(secret_combine(secret_max([3, 8]), sum)([4, 5]), 11, msg="secret_combine(secret_max([3, 8]), sum)([4, 5]) should be 11") \ No newline at end of file