Skip to content

Commit 0c42758

Browse files
authored
Make ArbitraryBase Unicode-aware (TheAlgorithms#1299)
* Make ArbitraryBase Unicode-aware https://mathiasbynens.be/notes/javascript-unicode#counting-symbols * Fix performance bug and add Unicode test * Add BigInt version and push output chars to array
1 parent 6aa3314 commit 0c42758

File tree

2 files changed

+89
-20
lines changed

2 files changed

+89
-20
lines changed

Conversions/ArbitraryBase.js

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
11
/**
2-
* Converts a string from one base to other
2+
* Divide two numbers and get the result of floor division and remainder
3+
* @param {number} dividend
4+
* @param {number} divisor
5+
* @returns {[result: number, remainder: number]}
6+
*/
7+
const floorDiv = (dividend, divisor) => {
8+
const remainder = dividend % divisor
9+
const result = Math.floor(dividend / divisor)
10+
11+
return [result, remainder]
12+
}
13+
14+
/**
15+
* Converts a string from one base to other. Loses accuracy above the value of `Number.MAX_SAFE_INTEGER`.
316
* @param {string} stringInBaseOne String in input base
417
* @param {string} baseOneCharacters Character set for the input base
518
* @param {string} baseTwoCharacters Character set for the output base
619
* @returns {string}
720
*/
8-
const convertArbitraryBase = (stringInBaseOne, baseOneCharacters, baseTwoCharacters) => {
9-
if ([stringInBaseOne, baseOneCharacters, baseTwoCharacters].map(arg => typeof arg).some(type => type !== 'string')) {
21+
const convertArbitraryBase = (stringInBaseOne, baseOneCharacterString, baseTwoCharacterString) => {
22+
if ([stringInBaseOne, baseOneCharacterString, baseTwoCharacterString].map(arg => typeof arg).some(type => type !== 'string')) {
1023
throw new TypeError('Only string arguments are allowed')
1124
}
12-
[baseOneCharacters, baseTwoCharacters].forEach(baseString => {
13-
const charactersInBase = [...baseString]
25+
26+
const baseOneCharacters = [...baseOneCharacterString]
27+
const baseTwoCharacters = [...baseTwoCharacterString]
28+
29+
for (const charactersInBase of [baseOneCharacters, baseTwoCharacters]) {
1430
if (charactersInBase.length !== new Set(charactersInBase).size) {
1531
throw new TypeError('Duplicate characters in character set are not allowed')
1632
}
17-
})
33+
}
1834
const reversedStringOneChars = [...stringInBaseOne].reverse()
1935
const stringOneBase = baseOneCharacters.length
2036
let value = 0
@@ -27,24 +43,57 @@ const convertArbitraryBase = (stringInBaseOne, baseOneCharacters, baseTwoCharact
2743
value += (digitNumber * placeValue)
2844
placeValue *= stringOneBase
2945
}
30-
let stringInBaseTwo = ''
46+
const outputChars = []
3147
const stringTwoBase = baseTwoCharacters.length
3248
while (value > 0) {
33-
const remainder = value % stringTwoBase
34-
stringInBaseTwo = baseTwoCharacters.charAt(remainder) + stringInBaseTwo
35-
value /= stringTwoBase
49+
const [divisionResult, remainder] = floorDiv(value, stringTwoBase)
50+
outputChars.push(baseTwoCharacters[remainder])
51+
value = divisionResult
3652
}
37-
const baseTwoZero = baseTwoCharacters.charAt(0)
38-
return stringInBaseTwo.replace(new RegExp(`^${baseTwoZero}+`), '')
53+
return outputChars.reverse().join('') || baseTwoCharacters[0]
3954
}
4055

41-
export { convertArbitraryBase }
56+
/**
57+
* Converts a arbitrary-length string from one base to other. Doesn't lose accuracy.
58+
* @param {string} stringInBaseOne String in input base
59+
* @param {string} baseOneCharacters Character set for the input base
60+
* @param {string} baseTwoCharacters Character set for the output base
61+
* @returns {string}
62+
*/
63+
const convertArbitraryBaseBigIntVersion = (stringInBaseOne, baseOneCharacterString, baseTwoCharacterString) => {
64+
if ([stringInBaseOne, baseOneCharacterString, baseTwoCharacterString].map(arg => typeof arg).some(type => type !== 'string')) {
65+
throw new TypeError('Only string arguments are allowed')
66+
}
4267

43-
// > convertArbitraryBase('98', '0123456789', '01234567')
44-
// '142'
68+
const baseOneCharacters = [...baseOneCharacterString]
69+
const baseTwoCharacters = [...baseTwoCharacterString]
4570

46-
// > convertArbitraryBase('98', '0123456789', 'abcdefgh')
47-
// 'bec'
71+
for (const charactersInBase of [baseOneCharacters, baseTwoCharacters]) {
72+
if (charactersInBase.length !== new Set(charactersInBase).size) {
73+
throw new TypeError('Duplicate characters in character set are not allowed')
74+
}
75+
}
76+
const reversedStringOneChars = [...stringInBaseOne].reverse()
77+
const stringOneBase = BigInt(baseOneCharacters.length)
78+
let value = 0n
79+
let placeValue = 1n
80+
for (const digit of reversedStringOneChars) {
81+
const digitNumber = BigInt(baseOneCharacters.indexOf(digit))
82+
if (digitNumber === -1n) {
83+
throw new TypeError(`Not a valid character: ${digit}`)
84+
}
85+
value += (digitNumber * placeValue)
86+
placeValue *= stringOneBase
87+
}
88+
const outputChars = []
89+
const stringTwoBase = BigInt(baseTwoCharacters.length)
90+
while (value > 0n) {
91+
const divisionResult = value / stringTwoBase
92+
const remainder = value % stringTwoBase
93+
outputChars.push(baseTwoCharacters[remainder])
94+
value = divisionResult
95+
}
96+
return outputChars.reverse().join('') || baseTwoCharacters[0]
97+
}
4898

49-
// > convertArbitraryBase('129', '0123456789', '01234567')
50-
// '201'
99+
export { convertArbitraryBase, convertArbitraryBaseBigIntVersion }

Conversions/test/ArbitraryBase.test.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { convertArbitraryBase } from '../ArbitraryBase'
1+
import { convertArbitraryBase, convertArbitraryBaseBigIntVersion } from '../ArbitraryBase'
22

33
test('Check the answer of convertArbitraryBase(98, 0123456789, 01234567) is 142', () => {
44
const res = convertArbitraryBase('98', '0123456789', '01234567')
@@ -34,3 +34,23 @@ test('Check the answer of convertArbitraryBase(111, 0123456789, abcdefgh) is bfh
3434
const res = convertArbitraryBase('111', '0123456789', 'abcdefgh')
3535
expect(res).toBe('bfh')
3636
})
37+
38+
test('Unicode awareness', () => {
39+
const res = convertArbitraryBase('98', '0123456789', '💝🎸🦄')
40+
expect(res).toBe('🎸💝🎸🦄🦄')
41+
})
42+
43+
test('zero', () => {
44+
const res = convertArbitraryBase('0', '0123456789', 'abc')
45+
expect(res).toBe('a')
46+
})
47+
48+
test('BigInt version with input string of arbitrary length', () => {
49+
const resBigIntVersion = convertArbitraryBaseBigIntVersion(
50+
String(10n ** 100n),
51+
'0123456789',
52+
'0123456789abcdefghijklmnopqrstuvwxyz'
53+
)
54+
55+
expect(resBigIntVersion).toBe((10n ** 100n).toString(36))
56+
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy