Skip to content

Commit 4908c04

Browse files
authored
Merge pull request #292 from sir-gon/feature/sherlock_and_anagrams
[Hacker Rank] Interview Preparation Kit: Dictionaries and Hashmaps: S…
2 parents 3683849 + 3875d74 commit 4908c04

File tree

5 files changed

+392
-0
lines changed

5 files changed

+392
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package ae.hackerrank.interview_preparation_kit.dictionaries_and_hashmaps;
2+
3+
import java.math.BigInteger;
4+
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.util.HashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
10+
11+
/**
12+
* SherlockAndAnagrams.
13+
*
14+
* @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md]]
15+
* @link Solution notes [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md]]
16+
*/
17+
public class SherlockAndAnagrams {
18+
private SherlockAndAnagrams() {}
19+
20+
/**
21+
* factorial().
22+
*/
23+
public static BigInteger factorial(int number) {
24+
BigInteger result = BigInteger.ONE;
25+
for (int i = 1; i <= number; i++) {
26+
result = result.multiply(new BigInteger(Integer.toString(i)));
27+
}
28+
29+
return result;
30+
}
31+
32+
/**
33+
* sherlockAndAnagrams.
34+
*/
35+
public static int sherlockAndAnagrams(String sWord) {
36+
37+
Map<String, List<String>> candidates = new HashMap<>();
38+
int size = sWord.length();
39+
40+
for (int i = 0; i < size; i++) {
41+
for (int j = 0; j < size - i; j++) {
42+
String substr = sWord.substring(i, size - j);
43+
44+
// Add substrings to a candidate list.
45+
// two strings are anagrams if sorted strings are the same.
46+
String anagramCandidate = Arrays.stream(substr.split(""))
47+
.sorted()
48+
.collect(Collectors.joining());
49+
50+
51+
// Append candidates to dictionary by "sorted string" key
52+
if (candidates.containsKey(anagramCandidate)) {
53+
candidates.get(anagramCandidate).add(substr);
54+
} else {
55+
ArrayList<String> anagrams = new ArrayList<>();
56+
anagrams.add(substr);
57+
candidates.put(anagramCandidate, anagrams);
58+
}
59+
}
60+
}
61+
62+
int total = 0;
63+
64+
// Final Anagram list
65+
for (Map.Entry<String, List<String>> entry : candidates.entrySet()) {
66+
int quantityOfAnagrams = entry.getValue().size();
67+
int k = 2;
68+
69+
if (quantityOfAnagrams > 1) {
70+
// Binomial coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient
71+
int count = factorial(quantityOfAnagrams).divide(
72+
(factorial(k).multiply(factorial(quantityOfAnagrams - k)))
73+
).intValue();
74+
total += count;
75+
}
76+
}
77+
78+
return total;
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package ae.hackerrank.interview_preparation_kit.dictionaries_and_hashmaps;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.io.IOException;
6+
import java.util.List;
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.TestInstance;
10+
import org.junit.jupiter.api.TestInstance.Lifecycle;
11+
import util.JsonLoader;
12+
13+
/**
14+
* SherlockAndAnagrams.
15+
*
16+
* @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/two-strings.md]]
17+
*/
18+
@TestInstance(Lifecycle.PER_CLASS)
19+
class SherlockAndAnagramsTest {
20+
21+
/**
22+
* SherlockAndAnagramsTestCase.
23+
*/
24+
public static class SherlockAndAnagramsTestCase {
25+
/**
26+
* SherlockAndAnagramsTestCase.TestCase.
27+
*/
28+
public static class TestCase {
29+
public String input;
30+
public Integer expected;
31+
}
32+
33+
public String title;
34+
public List<TestCase> tests;
35+
}
36+
37+
List<SherlockAndAnagramsTestCase> testCases;
38+
39+
/**
40+
* Sets up the test environment by loading test cases from a JSON file.
41+
* The JSON file is located in the specified path relative to the project structure.
42+
*
43+
* @throws IOException if an error occurs while reading the JSON file.
44+
*/
45+
@BeforeAll
46+
void setup() throws IOException {
47+
String path = String.join("/", "hackerrank",
48+
"interview_preparation_kit",
49+
"dictionaries_and_hashmaps",
50+
"sherlock_and_anagrams.testcases.json");
51+
52+
this.testCases = JsonLoader.loadJson(path, SherlockAndAnagramsTestCase.class);
53+
}
54+
55+
private SherlockAndAnagramsTest() {}
56+
57+
/**
58+
* sherlockAndAnagrams.
59+
*/
60+
@Test void sherlockAndAnagrams() {
61+
for (SherlockAndAnagramsTestCase _testCases : this.testCases) {
62+
63+
for (SherlockAndAnagramsTestCase.TestCase test : _testCases.tests) {
64+
Integer solutionFound = SherlockAndAnagrams.sherlockAndAnagrams(test.input);
65+
66+
assertEquals(test.expected, solutionFound,
67+
"%s(%s) answer must be: %s".formatted(
68+
"SherlockAndAnagrams.sherlockAndAnagrams",
69+
test.input,
70+
test.expected
71+
)
72+
);
73+
}
74+
}
75+
}
76+
}
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+
]
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.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# [Sherlock and Anagrams](https://www.hackerrank.com/challenges/sherlock-and-anagrams)
2+
3+
- Difficulty: `#medium`
4+
- Category: `#ProblemSolvingMedium` `#DictionariesAndHashmaps` `#Strings`
5+
6+
Two strings are [http://en.wikipedia.org/wiki/Anagram](anagrams) of each other
7+
if the letters of one string can be rearranged to form the other string.
8+
Given a string, find the number of pairs of substrings of the string that are
9+
anagrams of each other.
10+
11+
## Example
12+
13+
`s = mom`
14+
15+
The list of all anagrammatic pairs is `[m, m]`, `[mo, om]`
16+
at positions `[[0], [2]]`, `[[0, 1], [1, 2]]` respectively.
17+
18+
## Function Description
19+
20+
Complete the function sherlockAndAnagrams in the editor below.
21+
22+
*sherlockAndAnagrams* has the following parameter(s):
23+
24+
- `string s`: a string
25+
26+
## Returns
27+
28+
- `int`: the number of unordered anagrammatic pairs of substrings in **`s`**
29+
30+
## Input Format
31+
32+
The first line contains an integer `q`, the number of queries.
33+
Each of the next `q` lines contains a string `s` to analyze.
34+
35+
## Constraints
36+
37+
- $ 1 \leq 10 \leq 10 $
38+
- $ 2 \leq $ lenght of `s` $ \leq 100 $
39+
40+
`s` contains only lowercase letters in the range ascii[a-z].
41+
42+
## Sample Input 0
43+
44+
```text
45+
2
46+
abba
47+
abcd
48+
```
49+
50+
## Sample Output 0
51+
52+
```text
53+
4
54+
0
55+
```
56+
57+
## Explanation 0
58+
59+
The list of all anagrammatic pairs is `[a, a]`, `[ab, ba]`,
60+
`[b, b]` and `[abb, bba]` at positions `[[0], [3]]`, `[[0, 1]], [[2, 3]]`,
61+
`[[1], [2]]` and `[[0, 1, 2], [1, 2, 3]]` respectively.
62+
63+
No anagrammatic pairs exist in the second query as no character repeats.
64+
65+
## Sample Input 1
66+
67+
```text
68+
2
69+
ifailuhkqq
70+
kkkk
71+
````
72+
73+
## Sample Output 1
74+
75+
```text
76+
3
77+
10
78+
```
79+
80+
## Explanation 1
81+
82+
For the first query, we have anagram pairs `[i, i]`, `[q, q]`
83+
and `[ifa, fai]` at positions `[[0], [3]]`, `[[8], [9]]`
84+
and `[[0, 1, 2], [1, 2, 3]]` respectively.
85+
86+
For the second query:
87+
88+
There are `6` anagrams of the form `[k, k]` at positions `[[0, 1]]`,
89+
`[[0], [2]]`, `[[0], [3]]`, `[[1], [2]]`, `[[1], [3]]` and `[[2], [3]]`.
90+
91+
There are 3 anagrams of the form `[kk, kk]` at positions `[[0, 1], [1, 2]]`,
92+
`[[0, 1], [2, 3]]` and `[[1, 2], [2, 3]]`.
93+
94+
There is 1 anagram of the form `[kkk, kkk]` at position `[[0, 1, 2], [1, 2, 3]]`.
95+
96+
## Sample Input 2
97+
98+
```text
99+
1
100+
cdcd
101+
```
102+
103+
## Sample Output 2
104+
105+
```text
106+
5
107+
```
108+
109+
## Explanation 2
110+
111+
There are two anagrammatic pairs of length `1`: `[c, c]` and `[d, d]`.
112+
There are three anagrammatic pairs of length `2`:
113+
`[cd, dc]`, `[cd, cd]`, `[dc, cd]` at positions
114+
`[[0, 1] [1, 2]]`, `[[0, 1], [2, 3]]`, `[1, 2], [2, 3]` respectively.

0 commit comments

Comments
 (0)