Skip to content

Commit e65a8b9

Browse files
authored
Merge pull request #410 from sir-gon/feature/sherlock_and_anagrams
Feature/sherlock and anagrams
2 parents 58ba86a + 2f7f7e3 commit e65a8b9

File tree

4 files changed

+150
-52
lines changed

4 files changed

+150
-52
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# [Sherlock and Anagrams](https://www.hackerrank.com/challenges/sherlock-and-anagrams)
2+
3+
- Difficulty: `#medium`
4+
- Category: `#ProblemSolvingMedium` `#DictionariesAndHashmaps` `#Strings`
5+
6+
## About solution
7+
8+
To answer the question of "how many pairs" of words can be anagrammed
9+
using fragments from adjacent letters of an initial word, two steps are needed:
10+
11+
1) Obtain all possible fragment candidates to be anagrams,
12+
from each of the possible fragments that can be generated
13+
from adjacent letters of a word.
14+
15+
2) For each list of candidate anagrams,
16+
calculate all possible permutations and add them up.
17+
The total gives the answer.
18+
19+
The second part of this problem can be solved with the binomial coefficient formula:
20+
21+
<https://en.wikipedia.org/wiki/Binomial_coefficient>
22+
23+
But the entire cost of this formula falls on the "factorial" function.
24+
25+
In javascript, the factorial quickly reaches results that return large numbers,
26+
in scientific notation, losing precision.
27+
This loss of precision can result in an erroneous result
28+
in the final calculation of permutations.
29+
30+
To avoid this problem, it is necessary to introduce large number handling using BigInt.

src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.test.ts

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,14 @@ import { describe, expect, it } from '@jest/globals';
22
import { logger as console } from '../../../logger';
33

44
import { sherlockAndAnagrams } from './sherlock_and_anagrams';
5-
6-
const TEST_CASES = [
7-
{
8-
title: 'Sample Test Case 0',
9-
tests: [
10-
{
11-
input: 'abba',
12-
expected: 4
13-
},
14-
{
15-
input: 'abcd',
16-
expected: 0
17-
}
18-
]
19-
},
20-
{
21-
title: 'Sample Test Case 1',
22-
tests: [
23-
{
24-
input: 'ifailuhkqq',
25-
expected: 3
26-
},
27-
{
28-
input: 'kkkk',
29-
expected: 10
30-
}
31-
]
32-
},
33-
{
34-
title: 'Sample Test Case 1',
35-
tests: [
36-
{
37-
input: 'cdcd',
38-
expected: 5
39-
}
40-
]
41-
}
42-
];
5+
import TEST_CASES from './sherlock_and_anagrams.testcases.json';
436

447
describe('sherlock_and_anagrams', () => {
458
it('sherlockAndAnagrams test cases', () => {
46-
expect.assertions(5);
9+
expect.assertions(15);
4710

4811
TEST_CASES.forEach((testSet) => {
49-
testSet.tests.forEach((test) => {
12+
testSet?.tests.forEach((test) => {
5013
const answer = sherlockAndAnagrams(test.input);
5114

5215
console.debug(
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
[
2+
{
3+
"title": "Sample Test Case 0",
4+
"tests": [
5+
{
6+
"input": "abba",
7+
"expected": 4
8+
},
9+
{
10+
"input": "abcd",
11+
"expected": 0
12+
}
13+
]
14+
},
15+
{
16+
"title": "Sample Test Case 1",
17+
"tests": [
18+
{
19+
"input": "ifailuhkqq",
20+
"expected": 3
21+
},
22+
{
23+
"input": "kkkk",
24+
"expected": 10
25+
}
26+
]
27+
},
28+
{
29+
"title": "Sample Test Case 1",
30+
"tests": [
31+
{
32+
"input": "cdcd",
33+
"expected": 5
34+
}
35+
]
36+
},
37+
{
38+
"title": "Test case 3",
39+
"tests": [
40+
{
41+
"input":
42+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
43+
"expected": 166650
44+
},
45+
{
46+
"input":
47+
"bbcaadacaacbdddcdbddaddabcccdaaadcadcbddadababdaaabcccdcdaacadcababbabbdbacabbdcbbbbbddacdbbcdddbaaa",
48+
"expected": 4832
49+
},
50+
{
51+
"input":
52+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
53+
"expected": 166650
54+
},
55+
{
56+
"input":
57+
"cacccbbcaaccbaacbbbcaaaababcacbbababbaacabccccaaaacbcababcbaaaaaacbacbccabcabbaaacabccbabccabbabcbba",
58+
"expected": 13022
59+
},
60+
{
61+
"input":
62+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
63+
"expected": 166650
64+
},
65+
{
66+
"input":
67+
"bbcbacaabacacaaacbbcaabccacbaaaabbcaaaaaaaccaccabcacabbbbabbbbacaaccbabbccccaacccccabcabaacaabbcbaca",
68+
"expected": 9644
69+
},
70+
{
71+
"input":
72+
"cbaacdbaadbabbdbbaabddbdabbbccbdaccdbbdacdcabdbacbcadbbbbacbdabddcaccbbacbcadcdcabaabdbaacdccbbabbbc",
73+
"expected": 6346
74+
},
75+
{
76+
"input":
77+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
78+
"expected": 166650
79+
},
80+
{
81+
"input":
82+
"babacaccaaabaaaaaaaccaaaccaaccabcbbbabccbbabababccaabcccacccaaabaccbccccbaacbcaacbcaaaaaaabacbcbbbcc",
83+
"expected": 8640
84+
},
85+
{
86+
"input":
87+
"bcbabbaccacbacaacbbaccbcbccbaaaabbbcaccaacaccbabcbabccacbaabbaaaabbbcbbbbbaababacacbcaabbcbcbcabbaba",
88+
"expected": 11577
89+
}
90+
]
91+
}
92+
]

src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/**
22
* @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md]]
3+
* @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md]]
34
*/
45

5-
function factorial(n: number): number {
6-
if (n == 0) {
7-
return 1;
8-
}
9-
return n * factorial(n - 1);
6+
import { logger as console } from '../../../logger';
7+
8+
function extraLongFactorials(n: number): bigint {
9+
const rs = [...Array(n)].reduce((a, b, i) => a * BigInt(i + 1), 1n);
10+
return rs;
1011
}
1112

1213
export function sherlockAndAnagrams(s: string): number {
@@ -16,6 +17,9 @@ export function sherlockAndAnagrams(s: string): number {
1617
for (let i = 0; i < size; i++) {
1718
for (let j = 0; j < size - i; j++) {
1819
const substr = s.substring(i, size - j);
20+
console.debug(
21+
`i: ${i}, size: ${size}, size - j: ${size - j} | substr: ${substr}`
22+
);
1923

2024
// Add substrings to a candidate list.
2125
// two strings are anagrams if sorted strings are the same.
@@ -32,7 +36,8 @@ export function sherlockAndAnagrams(s: string): number {
3236
}
3337
}
3438

35-
let count = 0;
39+
let total: bigint = BigInt(0);
40+
let q_candidates = 0;
3641
// Final Anagram list
3742
for (const word of Object.keys(candidates)) {
3843
const quantity_of_anagrams = candidates[word].length;
@@ -42,15 +47,23 @@ export function sherlockAndAnagrams(s: string): number {
4247
delete candidates[word];
4348
} else {
4449
// Binomial coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient
45-
count += Math.floor(
46-
factorial(quantity_of_anagrams) /
47-
(factorial(k) * factorial(quantity_of_anagrams - k))
48-
);
50+
q_candidates += quantity_of_anagrams;
51+
52+
const count =
53+
extraLongFactorials(quantity_of_anagrams) /
54+
(extraLongFactorials(k) *
55+
extraLongFactorials(quantity_of_anagrams - k));
56+
total += count;
57+
58+
console.debug(`'Partial anagrams of ${word}: ${count}`);
4959
}
5060
}
51-
console.debug(`filtered candidates: ${count}`);
61+
console.debug(
62+
`'sherlockAndAnagrams(${s}) Filtered # candidates: ${q_candidates}`
63+
);
64+
console.debug(`'sherlockAndAnagrams(${s}) # anagrams: ${total}`);
5265

53-
return count;
66+
return Number(total);
5467
}
5568

5669
export default { sherlockAndAnagrams };

0 commit comments

Comments
 (0)