Skip to content

Commit 58ba86a

Browse files
authored
Merge pull request #408 from sir-gon/feature/frequency-queries
Feature/frequency queries
2 parents 2ae1873 + 610f6a7 commit 58ba86a

File tree

6 files changed

+226
-29
lines changed

6 files changed

+226
-29
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# [Dictionaries and Hashmaps: Frequency Queries](https://www.hackerrank.com/challenges/frequency-queries)
2+
3+
- Difficulty: `#medium`
4+
- Category: `#ProblemSolvingIntermediate` `#DictionariesAndHashmaps`
5+
6+
## First solution
7+
8+
The first solution is based on the idea of ​​storing the values
9+
​​of insert and delete operations in a dictionary.
10+
11+
For the "select" operation, a search loop is made to find the expected frequency,
12+
which in the best case is cut off when the value is found,
13+
but in the worst case it goes through the entire dictionary.
14+
15+
## Optimized solution
16+
17+
The optimized solution tries to reduce the worst case problem above,
18+
reducing the select operation to direct access to the expected frequency.
19+
20+
To achieve this, it is necessary to maintain an "inverted" dictionary
21+
where the frequency values ​​are the keys and the values ​​of each element
22+
are stored in a list of values ​​that have the same frequency.
23+
24+
To maintain this structure, any operation that alters the data (insert, delete),
25+
must update "in which frequency group" the element will be.
26+
27+
This solution transfers the cost of the search to the data update operations.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, expect, it } from '@jest/globals';
2+
import { logger as console } from '../../../logger';
3+
4+
import { freqQuery } from './frequency_queries_bruteforce';
5+
import SMALL_TEST_CASES from './frequency_queries_testcases.json';
6+
7+
describe('frequency_queries', () => {
8+
it('freqQuery test cases', () => {
9+
expect.assertions(4);
10+
11+
SMALL_TEST_CASES.forEach((value) => {
12+
const answer = freqQuery(value.input);
13+
14+
console.debug(`freqQuery(${value.input}) solution found: ${answer}`);
15+
16+
expect(answer).toStrictEqual(value.expected);
17+
});
18+
});
19+
20+
it('freqQuery border case', () => {
21+
expect.assertions(1);
22+
23+
expect(() => {
24+
freqQuery([[4, 1]]);
25+
}).toThrow('Invalid operation');
26+
});
27+
});

src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/frequency_queries.ts renamed to src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/frequency_queries_bruteforce.ts

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

56
// Complete the freqQuery function below.
@@ -12,6 +13,9 @@ export function freqQuery(queries: number[][]): number[] {
1213
const __DELETE__ = 2;
1314
const __SELECT__ = 3;
1415

16+
const __NOT_FOUND__ = 0;
17+
const __FOUND__ = 1;
18+
1519
queries.forEach((query) => {
1620
const [operation, data] = query;
1721

@@ -24,18 +28,15 @@ export function freqQuery(queries: number[][]): number[] {
2428
case __DELETE__:
2529
data_map[data] = Math.max(0, current - 1);
2630
break;
27-
case __SELECT__:
28-
for (const [key, value] of Object.entries(data_map)) {
29-
console.log(key, value);
30-
if (value == data) {
31-
result.push(1);
32-
break;
33-
}
34-
}
35-
if (result.length == 0) {
36-
result.push(0);
31+
case __SELECT__: {
32+
const uniqueDatavalues = new Set(Object.values(data_map));
33+
if (uniqueDatavalues.has(data)) {
34+
result.push(__FOUND__);
35+
} else {
36+
result.push(__NOT_FOUND__);
3737
}
3838
break;
39+
}
3940
default:
4041
throw new Error('Invalid operation');
4142
}

src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/frequency_queries.test.ts renamed to src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/frequency_queries_optimized.test.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
11
import { describe, expect, it } from '@jest/globals';
22
import { logger as console } from '../../../logger';
33

4-
import { freqQuery } from './frequency_queries';
5-
6-
const TEST_CASES = [
7-
{
8-
title: 'Sample Test Case 0',
9-
input: [
10-
[1, 5],
11-
[1, 6],
12-
[3, 2],
13-
[1, 10],
14-
[1, 10],
15-
[1, 6],
16-
[2, 5],
17-
[3, 2]
18-
],
19-
expected: [0, 1]
20-
}
21-
];
4+
import { freqQuery } from './frequency_queries_optimized';
5+
import TEST_CASES from './frequency_queries_testcases.json';
226

237
describe('frequency_queries', () => {
248
it('freqQuery test cases', () => {
25-
expect.assertions(1);
9+
expect.assertions(4);
2610

2711
TEST_CASES.forEach((value) => {
2812
const answer = freqQuery(value.input);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/frequency-queries.md]]
3+
* @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/frequency-queries-solution-notes.md]]
4+
*/
5+
6+
export function updateFrequency(
7+
frequencyMap: Record<number, number[]>,
8+
data: number,
9+
currentFreq: number,
10+
newFreq: number
11+
): Record<number, number[]> {
12+
const freqMap = frequencyMap;
13+
14+
if (newFreq > 0) {
15+
if (freqMap?.[newFreq]) {
16+
freqMap[newFreq].push(data);
17+
} else {
18+
freqMap[newFreq] = [data];
19+
}
20+
}
21+
22+
if (freqMap?.[currentFreq]) {
23+
freqMap[currentFreq] = freqMap[currentFreq].filter(
24+
(f: number) => f !== data
25+
);
26+
27+
if (freqMap[currentFreq].length === 0) {
28+
delete freqMap?.[currentFreq];
29+
}
30+
}
31+
32+
return freqMap;
33+
}
34+
35+
export function freqQuery(queries: number[][]): number[] {
36+
const result: number[] = [];
37+
const dataMap: Record<number, number> = {};
38+
const freqMap: Record<number, number[]> = {};
39+
40+
const __INITIAL__ = 0;
41+
const __INSERT__ = 1;
42+
const __DELETE__ = 2;
43+
const __SELECT__ = 3;
44+
45+
const __NOT_FOUND__ = 0;
46+
const __FOUND__ = 1;
47+
48+
queries.forEach((query) => {
49+
const [operation, data] = query;
50+
51+
const currentFreq = dataMap?.[data] ?? __INITIAL__;
52+
let newFreq = currentFreq + 1;
53+
54+
switch (operation) {
55+
case __INSERT__:
56+
// map of values
57+
dataMap[data] = currentFreq + 1;
58+
59+
// map of frequencies
60+
newFreq = currentFreq + 1;
61+
break;
62+
case __DELETE__:
63+
// map of values
64+
dataMap[data] = Math.max(0, currentFreq - 1);
65+
66+
// map of frequencies
67+
newFreq = currentFreq - 1;
68+
69+
break;
70+
case __SELECT__: {
71+
if (freqMap?.[data]) {
72+
result.push(__FOUND__);
73+
} else {
74+
result.push(__NOT_FOUND__);
75+
}
76+
break;
77+
}
78+
default:
79+
throw new Error('Invalid operation');
80+
}
81+
82+
if (operation === __INSERT__ || operation === __DELETE__) {
83+
updateFrequency(freqMap, data, currentFreq, newFreq);
84+
}
85+
});
86+
87+
return result;
88+
}
89+
90+
export default { freqQuery };
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
[
2+
{
3+
"title": "Sample Test Case 0",
4+
"input": [
5+
[1, 5],
6+
[1, 6],
7+
[3, 2],
8+
[1, 10],
9+
[1, 10],
10+
[1, 6],
11+
[2, 5],
12+
[3, 2]
13+
],
14+
"expected": [0, 1]
15+
},
16+
{
17+
"title": "Sample Test Case 1",
18+
"input": [
19+
[3, 4],
20+
[2, 1003],
21+
[1, 16],
22+
[3, 1]
23+
],
24+
"expected": [0, 1]
25+
},
26+
{
27+
"title": "Sample Test Case 2",
28+
"input": [
29+
[1, 3],
30+
[2, 3],
31+
[3, 2],
32+
[1, 4],
33+
[1, 5],
34+
[1, 5],
35+
[1, 4],
36+
[3, 2],
37+
[2, 4],
38+
[3, 2]
39+
],
40+
"expected": [0, 1, 1]
41+
},
42+
{
43+
"title": "Sample Test Case 3",
44+
"input": [
45+
[1, 3],
46+
[1, 38],
47+
[2, 1],
48+
[1, 16],
49+
[2, 1],
50+
[2, 2],
51+
[1, 64],
52+
[1, 84],
53+
[3, 1],
54+
[1, 100],
55+
[1, 10],
56+
[2, 2],
57+
[2, 1],
58+
[1, 67],
59+
[2, 2],
60+
[3, 1],
61+
[1, 99],
62+
[1, 32],
63+
[1, 58],
64+
[3, 2]
65+
],
66+
"expected": [1, 1, 0]
67+
}
68+
]

0 commit comments

Comments
 (0)