From 8ad08af5dc7f6ea405c42b9aaaaed1d715ac93e7 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Sun, 27 Aug 2023 12:52:46 +0530 Subject: [PATCH] approach for nth prime --- .../nth-prime/.approaches/config.json | 21 ++++++ .../.approaches/generator-fun/content.md | 51 +++++++++++++++ .../.approaches/generator-fun/snippet.txt | 6 ++ .../nth-prime/.approaches/introduction.md | 46 +++++++++++++ .../nth-prime/.approaches/tracking/content.md | 65 +++++++++++++++++++ .../.approaches/tracking/snippet.txt | 8 +++ 6 files changed, 197 insertions(+) create mode 100644 exercises/practice/nth-prime/.approaches/config.json create mode 100644 exercises/practice/nth-prime/.approaches/generator-fun/content.md create mode 100644 exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt create mode 100644 exercises/practice/nth-prime/.approaches/introduction.md create mode 100644 exercises/practice/nth-prime/.approaches/tracking/content.md create mode 100644 exercises/practice/nth-prime/.approaches/tracking/snippet.txt diff --git a/exercises/practice/nth-prime/.approaches/config.json b/exercises/practice/nth-prime/.approaches/config.json new file mode 100644 index 0000000000..00bbaff5fc --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "c97a3f8e-a97d-4e45-b44f-128bcffb2d3a", + "slug": "generator-fun", + "title": "Generator Fun", + "blurb": "Utilize Python library and generators", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "e989fdd2-3f39-4195-9d7c-120a6d6376b6", + "slug": "tracking", + "title": "Tracking", + "blurb": "Track previous prime values and return the nth one", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/content.md b/exercises/practice/nth-prime/.approaches/generator-fun/content.md new file mode 100644 index 0000000000..39290335a0 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/generator-fun/content.md @@ -0,0 +1,51 @@ +# Generator Fun +The key of this approach is to not store the elements you do not need. + +This is a code representation: +```python +from itertools import islice, count + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(lambda counter: all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)), count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` + +Let's dissect it! `itertools.count` is like `range` without un upper bound - calling it returns a generator, and `for ... in count_obj` will result in an infinite loop. + +Using a lambda expression, we `filter` out any numbers above two that are prime. +Doesn't this result in an infinite loop? +No - `filter` _also_ returns a generator object (which are [evaluated lazily][generator]), so while it's too will produce values infinitely if evaluated, it doesn't hang to program at the time of instantiation. + +`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evalutes until that end count_. + +The next line exhausts all the values in the generator except the end, and we finally return the last element. + +We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. The added bonus is that a very long line of code is cleant up. + +```python +from itertools import islice, count +from functools import cache + +@cache +def is_prime(counter): + return all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)) + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(is_prime, count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` + +~~~~exercism/note +Note that this that not create a list anywhere, and thus is both memory and time efficient. +~~~~ + +Read more on `itertools` on the [Python docs][itertools]. + +[itertools]: https://docs.python.org/3/library/itertools.html +[generator]: https://www.programiz.com/python-programming/generator \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt b/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt new file mode 100644 index 0000000000..d073667a4c --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt @@ -0,0 +1,6 @@ +from itertools import islice, count + +def prime(number): + gen = islice(filter(lambda n: all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)), count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/introduction.md b/exercises/practice/nth-prime/.approaches/introduction.md new file mode 100644 index 0000000000..e20541778e --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/introduction.md @@ -0,0 +1,46 @@ +# Introduction +Nth Prime in Python is a very interesting exercise that can be solved in multiple ways. + +## General guidance +Every approach has to a) validate the input, b) calculate the prime numbers until n - 1, and c) return the nth prime number. + +As the previous numbers are calculated multiple times, it's advisable to extract that piece of code as a function and use `functools.cache` for greater speeds at higher numbers. + +## Approach: Tracking +Using this approach, we manually track the primes/number of primes until the nth prime, after which we quit the loop and return the last number in the list/currently tracked prime. +There are many possible code implementations, such as this one: +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in primes): + primes.append(counter) + return primes[-1] +``` + +If you're interested in learning more about this approach (and discover a lesser known Python feature!), go [here][approach-tracking]. + +## Approach: Generator Fun +A far more idiomatic approach that utilizes Python's powerful standard library is to use `itertools` and generator expression related functions. + +```python +from itertools import islice, count + +def is_prime(n): + return not any(n % k == 0 for k in range(2, int(n ** 0.5) + 1)) + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(is_prime, count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` +If you'd like to understand this approach better, [read more][approach-generator-fun]. + +[approach-tracking]: https://exercism.org/tracks/python/exercises/nth-prime/approaches/tracking +[approach-generator-fun]: https://exercism.org/tracks/python/exercises/nth-prime/approaches/generator-fun \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/tracking/content.md b/exercises/practice/nth-prime/.approaches/tracking/content.md new file mode 100644 index 0000000000..7e6398c92f --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/tracking/content.md @@ -0,0 +1,65 @@ +# Tracking +This approach includes building a list of all the previous primes until it reaches the nth number, after which it quits the loop and return the last number in the list. +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in range(2, counter)): + primes.append(counter) + return primes[-1] +``` +Efficiency can be improved slightly by reducing the factor check to the square root of the number... +```python +... +if all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)): + ... +``` +... or even better, checking whether a new number is merely not divisible by any of the primes (in which case it's a prime itself): +```python +... +if all(counter % test != 0 for test in primes): + ... +``` +Instead of building the list, it's more memory efficient to just save the number of primes found until now, and return the currently stored number when the nth prime is found. +However, this elongates the code. +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + prime_count = 0 + while True: + isprime = True + for test in range(2, int(counter ** 0.5) + 1): + if counter % test == 0: + isprime = False + break + if isprime: + prime_count += 1 + if prime_count == number: + return counter + counter += 1 +``` + +~~~~exercism/advanced +Tip: you can use `for... else` to make your code more idiomatic here. +```python +... +for test in range(2, int(counter ** 0.5) + 1): + if counter % test == 0: + break +else: + prime_count += 1 +if prime_count == number: + return counter +``` +The else block is executed if the `for` loop completes normally - that is, without `break`ing. +Read more on [for/else][for-else] +~~~~ + + +[for-else]: https://book.pythontips.com/en/latest/for_-_else.html \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/tracking/snippet.txt b/exercises/practice/nth-prime/.approaches/tracking/snippet.txt new file mode 100644 index 0000000000..1e8b3ab883 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/tracking/snippet.txt @@ -0,0 +1,8 @@ +def prime(number): + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in range(2, counter)): + primes.append(counter) + return primes[-1] \ No newline at end of file