Skip to content

Commit d466be9

Browse files
merge: Upgrade checkAnagram function & Fixes (TheAlgorithms#902)
* pref: optimize the algo via reduce & replace method * feat: add TypeError for invalid types * test: upgrade test case for invalid types * docs: add js doc * test: modify the case-sensitive test case * pref: Optimize algo & add case-insensitive mode * style: format with standard style * docs: fix the js doc * docs: rename function name & add comments * feat: add chackAnagramViaMap function * test: add test case for checkAnagramViaMap func * fix: remove **Via** from functions name * style: fix alignment of js doc * chore: grammar fix Co-authored-by: Rak Laptudirm <raklaptudirm@gmail.com>
1 parent 0178efd commit d466be9

File tree

2 files changed

+168
-46
lines changed

2 files changed

+168
-46
lines changed

String/CheckAnagram.js

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,76 @@
1-
// An [Anagram](https://en.wikipedia.org/wiki/Anagram) is a string that is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.
2-
// Anagram check is case sensitive; i.e. Aba and aba is not a anagram.
3-
// inputs are strings i.e. str1 and str2
4-
const checkAnagram = (str1, str2) => {
1+
// An [Anagram](https://en.wikipedia.org/wiki/Anagram) is a string that is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. Anagram check is not case-sensitive;
2+
/**
3+
* @function checkAnagramRegex
4+
* @param {string} str1
5+
* @param {string} str2
6+
* @returns {boolean}
7+
* @description - check anagram with the help of Regex
8+
* @example - checkAnagramRegex('node', 'deno') => true
9+
* @example - checkAnagramRegex('Eleven plus two', 'Twelve plus one') => true
10+
*/
11+
const checkAnagramRegex = (str1, str2) => {
512
// check that inputs are strings.
613
if (typeof str1 !== 'string' || typeof str2 !== 'string') {
7-
return 'Not string(s)'
14+
throw new TypeError('Both arguments should be strings.')
815
}
916

1017
// If both strings have not same lengths then they can not be anagram.
1118
if (str1.length !== str2.length) {
1219
return false
1320
}
1421

15-
// Use hashmap to keep count of characters in str1
22+
/**
23+
* str1 converted to an array and traverse each letter of str1 by reduce method
24+
* reduce method return string which is empty or not.
25+
* if it returns empty string '' -> falsy, with Logical !(NOT) Operator, it's will be converted to boolean and return true else false
26+
*/
27+
return ![...str1].reduce(
28+
(str2Acc, cur) => str2Acc.replace(new RegExp(cur, 'i'), ''), // remove the similar letter from str2Acc in case-insensitive
29+
str2
30+
)
31+
}
1632

17-
const str1CharCount = new Map()
33+
/**
34+
* @function checkAnagramMap
35+
* @description - check anagram via using HashMap
36+
* @param {string} str1
37+
* @param {string} str2
38+
* @returns {boolean}
39+
* @example - checkAnagramMap('node', 'deno') => true
40+
* @example - checkAnagramMap('Eleven plus two', 'Twelve plus one') => true
41+
*/
42+
const checkAnagramMap = (str1, str2) => {
43+
// check that inputs are strings.
44+
if (typeof str1 !== 'string' || typeof str2 !== 'string') {
45+
throw new TypeError('Both arguments should be strings.')
46+
}
1847

19-
for (let i = 0; i < str1.length; i++) {
20-
let previousCount = 0
21-
if (str1CharCount.has(str1[i])) {
22-
previousCount = str1CharCount.get(str1[i])
23-
}
24-
str1CharCount.set(str1[i], previousCount + 1)
48+
// If both strings have not same lengths then they can not be anagram.
49+
if (str1.length !== str2.length) {
50+
return false
2551
}
2652

27-
// Now check if second string has same characters?
53+
const str1List = Array.from(str1.toUpperCase()) // str1 to array
2854

29-
for (let i = 0; i < str2.length; i++) {
30-
let previousCount = 0
31-
// if str1CharCount has no key for str2[i] then not anagram.
32-
if (!str1CharCount.has(str2[i])) return false
55+
// get the occurrences of str1 characters by using HashMap
56+
const str1Occurs = str1List.reduce(
57+
(map, char) => map.set(char, map.get(char) + 1 || 1),
58+
new Map()
59+
)
3360

34-
previousCount = str1CharCount.get(str2[i])
35-
str1CharCount.set(str2[i], previousCount - 1)
36-
}
61+
for (const char of str2.toUpperCase()) {
62+
// if char has not exist to the map it's return false
63+
if (!str1Occurs.has(char)) {
64+
return false
65+
}
3766

38-
// Now check if all entries in hashmap has zeros.
67+
let getCharCount = str1Occurs.get(char)
68+
str1Occurs.set(char, --getCharCount)
3969

40-
for (const key in str1CharCount) {
41-
if (str1CharCount[key] !== 0) return false
70+
getCharCount === 0 && str1Occurs.delete(char)
4271
}
4372

4473
return true
4574
}
4675

47-
export { checkAnagram }
76+
export { checkAnagramRegex, checkAnagramMap }

String/test/CheckAnagram.test.js

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { checkAnagram } from '../CheckAnagram'
1+
import { checkAnagramMap, checkAnagramRegex } from '../CheckAnagram'
22

3-
describe('checkAnagram', () => {
3+
describe('Testing checkAnagramRegex', () => {
44
it.each`
55
inputOne | inputTwo
66
${123456} | ${'abcd'}
@@ -10,79 +10,172 @@ describe('checkAnagram', () => {
1010
${'abcd'} | ${[1, 2, 3, 4, 5, 6]}
1111
${'abcd'} | ${{ test: 'test' }}
1212
`(
13-
'expects to return "Not string(s)" given values $inputOne and $inputTwo',
13+
'expects to throw the type Error given values $inputOne and $inputTwo',
1414
({ inputOne, inputTwo }) => {
15-
const SUT = checkAnagram(inputOne, inputTwo)
16-
expect(SUT).toBe('Not string(s)')
15+
expect(
16+
() => checkAnagramRegex(inputOne, inputTwo)
17+
).toThrowError()
1718
}
1819
)
1920

2021
it('expects to return false if the arguments have different lengths', () => {
21-
const SUT = checkAnagram('abs', 'abds')
22+
const SUT = checkAnagramRegex('abs', 'abds')
2223
expect(SUT).toBe(false)
2324
})
2425

2526
it('expects to return false if the arguments are not anagrams', () => {
26-
const SUT = checkAnagram('abcs', 'abds')
27+
const SUT = checkAnagramRegex('abcs', 'abds')
2728
expect(SUT).toBe(false)
2829
})
2930

3031
it('expects to return true if the arguments are anagrams', () => {
31-
const SUT = checkAnagram('abcd', 'bcad')
32+
const SUT = checkAnagramRegex('abcd', 'bcad')
3233
expect(SUT).toBe(true)
3334
})
3435

3536
it('expects to return true if the arguments of length 1 and are the same letter', () => {
36-
const SUT = checkAnagram('a', 'a')
37+
const SUT = checkAnagramRegex('a', 'a')
3738
expect(SUT).toBe(true)
3839
})
3940

4041
it('expects to return true if the arguments of are both empty strings', () => {
41-
const SUT = checkAnagram('', '')
42+
const SUT = checkAnagramRegex('', '')
4243
expect(SUT).toBe(true)
4344
})
4445

4546
it('expects to return true if the arguments are anagrams with an odd length', () => {
46-
const SUT = checkAnagram('abcde', 'edcab')
47+
const SUT = checkAnagramRegex('abcde', 'edcab')
4748
expect(SUT).toBe(true)
4849
})
4950

5051
it('expects to return true if the arguments are anagrams with an even length', () => {
51-
const SUT = checkAnagram('abcdef', 'fedcab')
52+
const SUT = checkAnagramRegex('abcdef', 'fedcab')
5253
expect(SUT).toBe(true)
5354
})
5455

5556
it('expects to return false if either argument is an empty string while the other is not', () => {
56-
const SUT = checkAnagram('', 'edcab')
57+
const SUT = checkAnagramRegex('', 'edcab')
5758
expect(SUT).toBe(false)
58-
const SUT2 = checkAnagram('edcab', '')
59+
const SUT2 = checkAnagramRegex('edcab', '')
5960
expect(SUT2).toBe(false)
6061
})
6162

62-
it('expects to return false if the arguments contain the same letters but have unequal case', () => {
63-
const SUT = checkAnagram('ABDCE', 'abcde')
63+
it('expects to return true if the arguments contain the same letters but have unequal case', () => {
64+
const SUT = checkAnagramRegex('ABDCE', 'abcde')
65+
expect(SUT).toBe(true)
66+
const SUT2 = checkAnagramRegex('AbCdE', 'aBCdE')
67+
expect(SUT2).toBe(true)
68+
const SUT3 = checkAnagramRegex('Eleven plus two', 'Twelve plus one')
69+
expect(SUT3).toBe(true)
70+
})
71+
72+
it('expects to return true if the arguments are anagrams and contain number characters', () => {
73+
const SUT = checkAnagramRegex('a1b2', '12ba')
74+
expect(SUT).toBe(true)
75+
})
76+
77+
it('expects to return true if the arguments are anagrams and contain space characters', () => {
78+
const SUT = checkAnagramRegex('a1 b2', '1 2ba')
79+
expect(SUT).toBe(true)
80+
})
81+
82+
it('expects to return true if the arguments are anagrams and contain punctuation characters', () => {
83+
const SUT = checkAnagramRegex('a!1b@2', '1@2ba!')
84+
expect(SUT).toBe(true)
85+
})
86+
87+
it('expects to return false if the arguments contain the same letters but contain a different amount of space characters', () => {
88+
const SUT = checkAnagramRegex('ea cb', 'e cba')
6489
expect(SUT).toBe(false)
65-
const SUT2 = checkAnagram('AbCdE', 'aBCdE')
90+
})
91+
})
92+
93+
describe('Testing checkAnagramMap', () => {
94+
it.each`
95+
inputOne | inputTwo
96+
${123456} | ${'abcd'}
97+
${[1, 2, 3, 4, 5, 6]} | ${'abcd'}
98+
${{ test: 'test' }} | ${'abcd'}
99+
${'abcd'} | ${123456}
100+
${'abcd'} | ${[1, 2, 3, 4, 5, 6]}
101+
${'abcd'} | ${{ test: 'test' }}
102+
`(
103+
'expects to throw the type Error given values $inputOne and $inputTwo',
104+
({ inputOne, inputTwo }) => {
105+
expect(
106+
() => checkAnagramMap(inputOne, inputTwo)
107+
).toThrowError()
108+
}
109+
)
110+
111+
it('expects to return false if the arguments have different lengths', () => {
112+
const SUT = checkAnagramMap('abs', 'abds')
113+
expect(SUT).toBe(false)
114+
})
115+
116+
it('expects to return false if the arguments are not anagrams', () => {
117+
const SUT = checkAnagramMap('abcs', 'abds')
118+
expect(SUT).toBe(false)
119+
})
120+
121+
it('expects to return true if the arguments are anagrams', () => {
122+
const SUT = checkAnagramMap('abcd', 'bcad')
123+
expect(SUT).toBe(true)
124+
})
125+
126+
it('expects to return true if the arguments of length 1 and are the same letter', () => {
127+
const SUT = checkAnagramMap('a', 'a')
128+
expect(SUT).toBe(true)
129+
})
130+
131+
it('expects to return true if the arguments of are both empty strings', () => {
132+
const SUT = checkAnagramMap('', '')
133+
expect(SUT).toBe(true)
134+
})
135+
136+
it('expects to return true if the arguments are anagrams with an odd length', () => {
137+
const SUT = checkAnagramMap('abcde', 'edcab')
138+
expect(SUT).toBe(true)
139+
})
140+
141+
it('expects to return true if the arguments are anagrams with an even length', () => {
142+
const SUT = checkAnagramMap('abcdef', 'fedcab')
143+
expect(SUT).toBe(true)
144+
})
145+
146+
it('expects to return false if either argument is an empty string while the other is not', () => {
147+
const SUT = checkAnagramMap('', 'edcab')
148+
expect(SUT).toBe(false)
149+
const SUT2 = checkAnagramMap('edcab', '')
66150
expect(SUT2).toBe(false)
67151
})
68152

153+
it('expects to return true if the arguments contain the same letters but have unequal case', () => {
154+
const SUT = checkAnagramMap('ABDCE', 'abcde')
155+
expect(SUT).toBe(true)
156+
const SUT2 = checkAnagramMap('AbCdE', 'aBCdE')
157+
expect(SUT2).toBe(true)
158+
const SUT3 = checkAnagramMap('Eleven plus two', 'Twelve plus one')
159+
expect(SUT3).toBe(true)
160+
})
161+
69162
it('expects to return true if the arguments are anagrams and contain number characters', () => {
70-
const SUT = checkAnagram('a1b2', '12ba')
163+
const SUT = checkAnagramMap('a1b2', '12ba')
71164
expect(SUT).toBe(true)
72165
})
73166

74167
it('expects to return true if the arguments are anagrams and contain space characters', () => {
75-
const SUT = checkAnagram('a1 b2', '1 2ba')
168+
const SUT = checkAnagramMap('a1 b2', '1 2ba')
76169
expect(SUT).toBe(true)
77170
})
78171

79172
it('expects to return true if the arguments are anagrams and contain punctuation characters', () => {
80-
const SUT = checkAnagram('a!1b@2', '1@2ba!')
173+
const SUT = checkAnagramMap('a!1b@2', '1@2ba!')
81174
expect(SUT).toBe(true)
82175
})
83176

84177
it('expects to return false if the arguments contain the same letters but contain a different amount of space characters', () => {
85-
const SUT = checkAnagram('ea cb', 'e cba')
178+
const SUT = checkAnagramMap('ea cb', 'e cba')
86179
expect(SUT).toBe(false)
87180
})
88181
})

0 commit comments

Comments
 (0)