Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Euler 070 partial replacement of numpy loops. #9055

Merged
merged 5 commits into from
Sep 11, 2023

Conversation

quant12345
Copy link
Contributor

@quant12345 quant12345 commented Sep 11, 2023

Describe your change:

Removed loop for filling 'totients' values. Instead, use: np.arange. The cycle for division without a remainder has also been removed: totients[j] // i. Instead, at each iteration, an array of indexes is created to select from the 'totients' array.
Speeds up calculations by 30%.

tests
"""
Project Euler Problem 70: https://projecteuler.net/problem=70

Euler's Totient function, φ(n) [sometimes called the phi function], is used to
determine the number of positive numbers less than or equal to n which are
relatively prime to n. For example, as 1, 2, 4, 5, 7, and 8, are all less than
nine and relatively prime to nine, φ(9)=6.

The number 1 is considered to be relatively prime to every positive number, so
φ(1)=1.

Interestingly, φ(87109)=79180, and it can be seen that 87109 is a permutation
of 79180.

Find the value of n, 1 < n < 10^7, for which φ(n) is a permutation of n and
the ratio n/φ(n) produces a minimum.

-----

This is essentially brute force. Calculate all totients up to 10^7 and
find the minimum ratio of n/φ(n) that way. To minimize the ratio, we want
to minimize n and maximize φ(n) as much as possible, so we can store the
minimum fraction's numerator and denominator and calculate new fractions
with each totient to compare against. To avoid dividing by zero, I opt to
use cross multiplication.

References:
Finding totients
https://en.wikipedia.org/wiki/Euler's_totient_function#Euler's_product_formula
"""
from __future__ import annotations
import numpy as np
import datetime

def get_totients_new(max_one: int) -> list[int]:
    """
    Calculates a list of totients from 0 to max_one exclusive, using the
    definition of Euler's product formula.

    >>> get_totients(5)
    [0, 1, 1, 2, 2]

    >>> get_totients(10)
    [0, 1, 1, 2, 2, 4, 2, 6, 4, 6]
    """

    totients = np.arange(max_one)

    for i in range(2, max_one):
        if totients[i] == i:
           x = np.arange(i, max_one, i)#array of indexes to select
           totients[x] -= totients[x] // i


    return totients.tolist()

def get_totients(max_one: int) -> list[int]:
    """
    Calculates a list of totients from 0 to max_one exclusive, using the
    definition of Euler's product formula.

    >>> get_totients(5)
    [0, 1, 1, 2, 2]

    >>> get_totients(10)
    [0, 1, 1, 2, 2, 4, 2, 6, 4, 6]
    """

    totients = [0] * max_one

    for i in range(max_one):
        totients[i] = i

    for i in range(2, max_one):
        if totients[i] == i:
            for j in range(i, max_one, i):
                totients[j] -= totients[j] // i

    return totients


def has_same_digits(num1: int, num2: int) -> bool:
    """
    Return True if num1 and num2 have the same frequency of every digit, False
    otherwise.

    >>> has_same_digits(123456789, 987654321)
    True

    >>> has_same_digits(123, 23)
    False

    >>> has_same_digits(1234566, 123456)
    False
    """

    return sorted(str(num1)) == sorted(str(num2))


def solution_new(max_n: int = 10000000) -> int:
    """
    Finds the value of n from 1 to max such that n/φ(n) produces a minimum.

    >>> solution(100)
    21

    >>> solution(10000)
    4435
    """

    min_numerator = 1  # i
    min_denominator = 0  # φ(i)
    totients = get_totients_new(max_n + 1)

    for i in range(2, max_n + 1):
        t = totients[i]

        if i * min_denominator < min_numerator * t and has_same_digits(i, t):
            min_numerator = i
            min_denominator = t

    return min_numerator


def solution(max_n: int = 10000000) -> int:
    """
    Finds the value of n from 1 to max such that n/φ(n) produces a minimum.

    >>> solution(100)
    21

    >>> solution(10000)
    4435
    """

    min_numerator = 1  # i
    min_denominator = 0  # φ(i)
    totients = get_totients(max_n + 1)

    for i in range(2, max_n + 1):
        t = totients[i]

        if i * min_denominator < min_numerator * t and has_same_digits(i, t):
            min_numerator = i
            min_denominator = t

    return min_numerator

if __name__ == "__main__":
    import doctest

    doctest.testmod()

    now = datetime.datetime.now()

    print(f"{solution() = }")

    print('old_time', datetime.datetime.now() - now)

    now = datetime.datetime.now()

    print(f"{solution_new() = }")

    print('new_time', datetime.datetime.now() - now)

Output:

solution() = 8319823
old_time 0:00:16.189952
solution_new() = 8319823
new_time 0:00:10.938119
  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeper algorithms-keeper bot added awaiting reviews This PR is ready to be reviewed tests are failing Do not merge until tests pass labels Sep 11, 2023
@quant12345
Copy link
Contributor Author

@cclauss two errors occur:

Error: project_euler/problem_070/sol1.py:31:1: I001 Import block is un-sorted or un-formatted
ERROR project_euler/problem_070/sol1.py - ModuleNotFoundError: No module named 'numpy'

numpy import connected why error? And it is not clear what the first error means?

@cclauss
Copy link
Member

cclauss commented Sep 11, 2023

@dhruvmanila is the use of numpy no allowed in the solution of Euler projects?

@dhruvmanila
Copy link
Member

I think it's fine to use external libraries but usually it's better to implement the solution on our own.

That said, the CI is failing because it doesn't install any of the dependencies from requirements.txt. The reason is to speed up the job and that no external libraries have been used in any of the solutions.

If we were to go the route of installing external dependencies, I would take it case by case and declare a new requirements.txt. This will also involve adding cache to the Project Euler and Validate solutions job because it currently doesn't have it.

@quant12345
Copy link
Contributor Author

@cclauss @dhruvmanila
I looked through all sol and none of them contain numpy. I think that pure Python is used together with numba and it speeds up the code a lot. But the first start is warming up and it is slow. I made comparisons: the first launch on numba lasted 2 seconds (even with a small array). And if the algorithm is accessed once, then it makes sense in numpy. Also, not all numpy functions work with numba, just like the built-in functions that come out of the Python box.

@cclauss cclauss enabled auto-merge (squash) September 11, 2023 10:06
@algorithms-keeper algorithms-keeper bot removed the tests are failing Do not merge until tests pass label Sep 11, 2023
Copy link
Member

@cclauss cclauss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! Let's land this one!!

@cclauss cclauss merged commit 97e2de0 into TheAlgorithms:master Sep 11, 2023
4 checks passed
@algorithms-keeper algorithms-keeper bot removed the awaiting reviews This PR is ready to be reviewed label Sep 11, 2023
@quant12345
Copy link
Contributor Author

Do I understand correctly that my changes have been accepted? If so, then this is my first pull request)

@cclauss
Copy link
Member

cclauss commented Sep 11, 2023

This is merged into master so this code has been assimilated into the codebase.

@quant12345 quant12345 deleted the euler-070 branch October 5, 2023 14:07
sedatguzelsemme pushed a commit to sedatguzelsemme/Python that referenced this pull request Sep 15, 2024
* Euler 070 partial replacement of numpy loops.

* Update project_euler/problem_070/sol1.py

* project_euler.yml: Upgrade actions/checkout@v4 and add numpy

* Update project_euler.yml

---------

Co-authored-by: Christian Clauss <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants