Skip to content

Commit cd68424

Browse files
Add approaches to pangram (#1987)
Add approaches to pangram Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 840f88c commit cd68424

8 files changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Bit field
2+
3+
```javascript
4+
const A_LCASE = 97;
5+
const A_UCASE = 65;
6+
const ALL_26_BITS_SET = 67108863;
7+
8+
export function isPangram(input) {
9+
let phrasemask = 0;
10+
[...input].forEach((letter) => {
11+
if (letter >= 'a' && letter <= 'z')
12+
phrasemask |= 1 << (letter.charCodeAt(0) - A_LCASE);
13+
else if (letter >= 'A' && letter <= 'Z')
14+
phrasemask |= 1 << (letter.charCodeAt(0) - A_UCASE);
15+
});
16+
return phrasemask == ALL_26_BITS_SET;
17+
}
18+
```
19+
20+
This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position.
21+
22+
Some [const][const]ants are defined for readability in the function.
23+
The ASCII value for `a` is `97`.
24+
The ASCII value for `A` is `65`.
25+
The value for all of the rightmost 26 bits being set is `67108863`.
26+
27+
- [Spread syntax][spread-syntax] is used to make an [`Array`][array] of the characters in the `input`.
28+
- The `Array` method [`forEach`][foreach] loops through the characters and looks for a character being `a` through `z` or `A` through `Z`.
29+
- If a letter is found, then its ASCII value is taken by the [`charCodeAt`][charcodeat] method.
30+
31+
```exercism/note
32+
`charCodeAt` actually returns the UTF-16 code unit for the character, which is an integer between `0` and `65535`.
33+
For the letters `a`-`z` and `A`-`Z`, the UTF-16 number is the same value as the ASCII value.
34+
```
35+
36+
- If the lowercase letter is subtracted by `97`, then `a` will result in `0`, because `97` minus `97` equals `0`.
37+
`z` would result in `25`, because `122` minus `97` equals `25`.
38+
So `a` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `z` would have `1` shifted left 25 places.
39+
- If the uppercase letter is subtracted by `A`, then `A` will result in `0`, because `65` minus `65` equals `0`.
40+
`Z` would result in `25`, because `90` minus `65` equals `25`.
41+
So `A` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `Z` would have `1` shifted left 25 places.
42+
43+
In that way, both a lowercase `z` and an uppercase `Z` can share the same position in the bit field.
44+
45+
So, for an integer, if the values for `a` and `Z` were both set, the bits would look like
46+
47+
```
48+
zyxwvutsrqponmlkjihgfedcba
49+
00000010000000000000000000000001
50+
```
51+
52+
We can use the [bitwise OR operator][or] to set the bit.
53+
After the loop completes, the function returns if the `phrasemask` value is the same value as when all `26` bits are set, which is `67108863`.
54+
55+
[ascii]: https://www.asciitable.com/
56+
[const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
57+
[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
58+
[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
59+
[foreach]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
60+
[charcodeat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt
61+
[shift-left]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift
62+
[or]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[...input].forEach((letter) => {
2+
if (letter >= "a" && letter <= "z")
3+
phrasemask |= 1 << (letter.charCodeAt(0) - A_LCASE);
4+
else if (letter >= "A" && letter <= "Z")
5+
phrasemask |= 1 << (letter.charCodeAt(0) - A_UCASE);
6+
});
7+
return phrasemask == ALL_26_BITS_SET;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"]
4+
},
5+
"approaches": [
6+
{
7+
"uuid": "2495f79d-9ef9-4f38-abad-f3af8f1f217e",
8+
"slug": "every-includes",
9+
"title": "every with includes",
10+
"blurb": "Use every with includes.",
11+
"authors": ["bobahop"]
12+
},
13+
{
14+
"uuid": "5d19ca5a-9eb2-42fc-bf23-ebf66b7e4959",
15+
"slug": "set-size",
16+
"title": "Set with size",
17+
"blurb": "Use Set with size.",
18+
"authors": ["bobahop"]
19+
},
20+
{
21+
"uuid": "44eff693-c28f-4928-9f45-7a9c7e79b843",
22+
"slug": "bitfield",
23+
"title": "Bit field",
24+
"blurb": "Use a bit field to keep track of used letters.",
25+
"authors": ["bobahop"]
26+
}
27+
]
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# `every` with `includes` on lowercased letters
2+
3+
```javascript
4+
export function isPangram(input) {
5+
const inputLowered = input.toLowerCase();
6+
return [...'abcdefghijklmnopqrstuvwxyz'].every((c) =>
7+
inputLowered.includes(c)
8+
);
9+
}
10+
```
11+
12+
- This begins by lowercasing the input by using the [`String`][string] [`toLowerCase`][tolower] method.
13+
- It uses [spread syntax][spread-syntax] to make an [Array][array] out of a `string` of the alphabet.
14+
- It then checks if all letters in the alphabet are contained in the input,
15+
using the `Array` method [`every`][every] with the `String` method [`includes`][includes].
16+
17+
If all letters of the alphabet are in the `input`, then the function returns `true`.
18+
19+
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
20+
[tolower]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase
21+
[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
22+
[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
23+
[every]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
24+
[includes]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export function isPangram(input) {
2+
const inputLowered = input.toLowerCase();
3+
return [..."abcdefghijklmnopqrstuvwxyz"].every((c) =>
4+
inputLowered.includes(c)
5+
);
6+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Introduction
2+
3+
There are various idomatic approaches to Pangram.
4+
You can use the `every` method with the `includes` method.
5+
Or you can use the `Set` object with the `size` property.
6+
7+
## General guidance
8+
9+
The key to solving Pangram is determining if all of the letters in the alphabet are in the `string` being tested.
10+
The occurrence of either the letter `a` or the letter `A` would count as the same letter.
11+
12+
## Approach: `every` with `includes`
13+
14+
```javascript
15+
export function isPangram(input) {
16+
const inputLowered = input.toLowerCase();
17+
return [...'abcdefghijklmnopqrstuvwxyz'].every((c) =>
18+
inputLowered.includes(c)
19+
);
20+
}
21+
```
22+
23+
For more information, check the [`every` with `includes` approach][approach-every-includes].
24+
25+
## Approach: `Set` with `size`
26+
27+
```javascript
28+
export function isPangram(input) {
29+
return new Set(input.toLowerCase().match(/[a-z]/g)).size === 26;
30+
}
31+
```
32+
33+
For more information, check the [`Set` with `size`approach][approach-set-size]
34+
35+
## Other approaches
36+
37+
Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows:
38+
39+
### Other approach: Bit field
40+
41+
Another approach can use a bit field to keep track of used letters.
42+
For more information, check the [Bit field approach][approach-bitfield].
43+
44+
## Which approach to use?
45+
46+
Testing `"the _1_ quick brown fox jumps over the _2_ lazy dogs"` on [JSBench.me][jsbench-me]:
47+
48+
- The `every` with `includes` approach benched fastest.
49+
- Although the bit field approach is often faster in other languages, it was about 45% slower.
50+
- `Set` with `size` was about 75% slower.
51+
52+
[approach-every-includes]: https://exercism.org/tracks/javascript/exercises/pangram/approaches/every-includes
53+
[approach-set-size]: https://exercism.org/tracks/javascript/exercises/pangram/approaches/set-size
54+
[approach-bitfield]: https://exercism.org/tracks/javascript/exercises/pangram/approaches/bitfield
55+
[jsbench-me]: https://jsbench.me/
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# `Set` with `size`
2+
3+
```javascript
4+
export function isPangram(input) {
5+
return new Set(input.toLowerCase().match(/[a-z]/g)).size === 26;
6+
}
7+
```
8+
9+
This approach creates a [`Set`][set] of the unique letters in the `input` and tests its [size][size] to determine the result.
10+
11+
- It first creates a new `Set` made from the [lowercased][tolowercase] characters of the `input`
12+
that only [match][match] the [regular expression][regex] pattern for letters from `a`-`z`.
13+
- The function returns if the `size` of the `Set` is `26`.
14+
If the number of unique letters in the `Set` is equal to the `26` letters in the alphabet, then the function will return `true`.
15+
16+
## Shortening
17+
18+
When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so
19+
20+
```javascript
21+
export const isPangram = (input) =>
22+
new Set(input.toLowerCase().match(/[a-z]/g)).size === 26;
23+
```
24+
25+
Notice that `return` and the curly braces are not needed.
26+
27+
[set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
28+
[size]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size
29+
[tolowercase]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase
30+
[match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match
31+
[regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
32+
[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function isPangram(input) {
2+
return new Set(input.toLowerCase().match(/[a-z]/g)).size === 26;
3+
}

0 commit comments

Comments
 (0)