Skip to content

Commit

Permalink
Merge pull request #490 from sir-gon/feature/euler003
Browse files Browse the repository at this point in the history
Feature/euler003
  • Loading branch information
sir-gon authored Sep 27, 2024
2 parents 9af81b3 + 9638733 commit c598b58
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 4 deletions.
63 changes: 63 additions & 0 deletions docs/hackerrank/projecteuler/euler003-solution-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# About the **Largest prime factor** solution

## Brute force method

> [!WARNING]
>
> The penalty of this method is that it requires a large number of iterations as
> the number grows.
The first solution, using the algorithm taught in school, is:

> Start by choosing a number $ i $ starting with $ 2 $ (the smallest prime number)
> Test the divisibility of the number $ n $ by $ i $, next for each one:
>
>> - If $ n $ is divisible by $ i $, then the result is
>> the new number $ n $ is reduced, while at the same time
>> the largest number $i$ found is stored.
>>
>> - If $ n $ IS NOT divisible by $ i $, $i$ is incremented by 1
> up to $ n $.
>
> Finally:
>>
>> - If you reach the end without finding any, it is because the number $n$
>> is prime and would be the only factual prime it has.
>>
>> - Otherwise, then the largest number $i$ found would be the largest prime factor.
## Second approach, limiting to half iterations

> [!CAUTION]
>
> Using some test entries, quickly broke the solution at all. So, don't use it.
> This note is just to record the failed idea.
Since by going through and proving the divisibility of a number $ i $ up to $ n $
there are also "remainder" numbers that are also divisible by their opposite,
let's call it $ j $.

At first it seemed attractive to test numbers $ i $ up to half of $ n $ then
test whether $ i $ or $ j $ are prime. 2 problems arise:

- Testing whether a number is prime could involve increasing the number of
iterations since now the problem would become O(N^2) complex in the worst cases

- Discarding all $ j $ could mean discarding the correct solution.

Both problems were detected when using different sets of test inputs.

## Final solution using some optimization

> [!WARNING]
>
> No source was found with a mathematical proof proving that the highest prime
> factor of a number n (non-prime) always lies under the limit of $ \sqrt{n} $
A solution apparently accepted in the community as an optimization of the first
brute force algorithm consists of limiting the search to $ \sqrt{n} $.

Apparently it is a mathematical conjecture without proof
(if it exists, please send it to me).

Found the correct result in all test cases.
43 changes: 43 additions & 0 deletions docs/hackerrank/projecteuler/euler003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# [Largest prime factor](https://www.hackerrank.com/contests/projecteuler/challenges/euler003)

- Difficulty: #easy
- Category: #ProjectEuler+

The prime factors of $ 13195 $ are $ 5 $, $ 7 $, $ 13 $ and $ 29 $.

What is the largest prime factor of a given number $ N $ ?

## Input Format

First line contains $ T $, the number of test cases. This is
followed by $ T $ lines each containing an integer $ N $.

## Constraints

- $ 1 \leq T \leq 10 $
- $ 10 \leq N \leq 10^{12} $

## Output Format

Print the required answer for each test case.

## Sample Input 0

```text
2
10
17
```

## Sample Output 0

```text
5
17
```

## Explanation 0

- Prime factors of $ 10 $ are $ {2, 5} $, largest is $ 5 $.

- Prime factor of $ 17 $ is $ 17 $ itselft, hence largest is $ 17 $.
4 changes: 1 addition & 3 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ const jestConfig = {
coverageDirectory: 'coverage',

// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
coveragePathIgnorePatterns: ['/node_modules/', 'src/hackerrank/lib'],

// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sonar.organization=sir-gon

# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
sonar.sources=src
sonar.exclusions=**/*.json,**/*.test.ts,**/*.bruteforce-test.ts,src/logger.ts
sonar.exclusions=**/*.json,**/*.test.ts,**/*.bruteforce-test.ts,**/lib/*,src/logger.ts

# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
Expand Down
81 changes: 81 additions & 0 deletions src/hackerrank/lib/BigIntMath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* istanbul ignore file */

export class BigIntMath {
static max(...values: bigint[]): bigint | null {
if (values.length === 0) {
return null;
}

if (values.length === 1) {
return values[0];
}

let max = values[0];
for (let i = 1; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
}
}
return max;
}

static min(...values: bigint[]): bigint | null {
if (values.length === 0) {
return null;
}

if (values.length === 1) {
return values[0];
}

let min = values[0];
for (let i = 1; i < values.length; i++) {
if (values[i] < min) {
min = values[i];
}
}
return min;
}

static sign(value: bigint): bigint {
if (value > 0n) {
return 1n;
}
if (value < 0n) {
return -1n;
}
return 0n;
}

static abs(value: bigint): bigint {
if (this.sign(value) === -1n) {
return -value;
}
return value;
}

// https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/58863398#58863398
static rootNth(value: bigint, k = 2n): bigint {
if (value < 0n) {
throw Error('negative number is not supported');
}

let o = 0n;
let x = value;
let limit = 100;

while (x ** k !== k && x !== o && limit > 0) {
limit -= 1;
o = x;
x = ((k - 1n) * x + value / x ** (k - 1n)) / k;
}

return x;
}

static sqrt(value: bigint): bigint {
return BigIntMath.rootNth(value);
}
}

export default { BigIntMath };
33 changes: 33 additions & 0 deletions src/hackerrank/projecteuler/euler003.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from '@jest/globals';
import { logger as console } from '../../logger';

import { euler003 } from './euler003';

import TEST_CASES from './euler003.testcases.json';

describe('euler003', () => {
it('euler003 JSON Test cases', () => {
expect.assertions(2);

TEST_CASES.forEach((test) => {
const calculated = euler003(test.n);
console.log(`euler003(${test.n}) solution found: ${test.expected}`);

expect(`${calculated}`).toBe(`${test.expected}`);
});
});

it('euler003 Edge case', () => {
expect.assertions(2);

const expectedMessage = 'n must be greater than 2';

expect(() => {
euler003(0);
}).toThrow(expectedMessage);

expect(() => {
euler003(1);
}).toThrow(expectedMessage);
});
});
4 changes: 4 additions & 0 deletions src/hackerrank/projecteuler/euler003.testcases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
{ "n": 10, "expected": 5 },
{ "n": 17, "expected": 17 }
]
39 changes: 39 additions & 0 deletions src/hackerrank/projecteuler/euler003.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @link Problem definition [[docs/hackerrank/projecteuler/euler003.md]]
*/

import { BigIntMath } from '../lib/BigIntMath';

export function primeFactor(n: bigint): bigint {
if (n < 2) {
throw new Error('n must be greater than 2');
}

let divisor: bigint = n;
let maxPrimeFactor: bigint = divisor;
let mpfInitialized = false;

let i = 2n;

while (i <= BigIntMath.sqrt(divisor)) {
if (divisor % i === 0n) {
divisor /= i;
maxPrimeFactor = divisor;
mpfInitialized = true;
} else {
i += 1n;
}
}

if (!mpfInitialized) {
return n;
}

return maxPrimeFactor;
}

export function euler003(n: number): bigint {
return primeFactor(BigInt(n));
}

export default { euler003 };

0 comments on commit c598b58

Please sign in to comment.