1

Roy wanted to increase his typing speed for programming contests. His friend suggested that he type the sentence "The quick brown fox jumps over the lazy dog" repeatedly. This sentence is known as a pangram because it contains every letter of the alphabet.

After typing the sentence several times, Roy became bored with it so he started to look for other pangrams.

Given a sentence, determine whether it is a pangram. Ignore case.

It should return the string pangram if the input string is a pangram. Otherwise, it should return not pangram.

SAMPLE INPUTS

We promptly judged antique ivory buckles for the next prize

// pangram

We promptly judged antique ivory buckles for the prize

// not pangram (missing letter x)

CODE

function pangrams(s) {

   const exp = /[a-z]/gi; 

   if (s.includes(exp)) {
     return 'pangram';
   } else {
     return 'not pangram';
   } 
   
}

TypeError: First argument to String.prototype.includes must not be a regular expression

QUESTION

If I'm solving it correctly otherwise, how can I work around this while still being able to use the regular expression?

Jack Bashford
  • 43,180
  • 11
  • 50
  • 79
and1
  • 307
  • 2
  • 10

3 Answers3

2

Your regular expression is wrong. It will only check if the string contains one letter from a to z. You also aren't testing it properly - String.prototype.includes checks for substrings being included in the larger string, not for regular expression validation.

If you want to test whether a regular expression matches a string, use .test. To check that all letters exist in a string with a regular expression, you'll have to repeat (?=.*a) for every character, eg:

const pattern = /^(?=.*a)(?=.*b)(?=.*c)(?=.*d)(?=.*e)...../i;
return s.test(pattern);

But that's really ugly and repetitive.

I don't think a regular expression is the way to go here. Consider iterating over character codes to get the characters from A to Z and using .includes instead:

const test = (str) => {
  const strUpper = str.toUpperCase();
  // start at A
  for (let i = 65; i <= 90; i++) {
    if (!strUpper.includes(String.fromCharCode(i))) {
      return false;
    }
  }
  return true;
};
console.log(test('The quick brown fox jumps over the lazy dog'));
console.log(test('The quick brown fox jumps over the lazy do'));

If you have to use a regular expression, you can construct it dynamically by iterating over all the character codes:

const test = (str) => {
  const pattern = new RegExp(
    '^' +
    Array.from(
      { length: 26 },
      (_, i) => `(?=.*${String.fromCharCode(i + 65)})`
    ).join(''),
    'i'
  );
  return pattern.test(str);
};
console.log(test('The quick brown fox jumps over the lazy dog'));
console.log(test('The quick brown fox jumps over the lazy do'));
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • gotcha on what im doing wrong. and as for your example, is there a less verbose way of doing with a regular expression to solve this problem or is it better to use a for loop or something instead? – and1 May 11 '20 at 06:47
2

The [a-z] notation checks for any single alphabet character - in your case, you could firstly remove case sizes with lowerCase, then add an alphabetic character regex to match all unique characters, then make a Set out of it to remove duplicates, and finally check if the length is 26:

const pangram = s => {

  const lower = s.toLowerCase();
  const letters = lower.match(/[a-z]/gi);
  const uniques = new Set(letters);
  
  if (uniques.size == 26) return "Pangram";
  else return "Not pangram";

};

console.log(pangram("The quick brown fox jumps over the lazy dog"));
console.log(pangram("Not a pangram lol"));

The above code can also be condensed:

const pangram = s => new Set(s.toLowerCase().match(/[a-z]/gi)).size == 26 ? "Pangram" : "Not pangram";

console.log(pangram("The quick brown fox jumps over the lazy dog"));
console.log(pangram("Not a pangram lol"));
Jack Bashford
  • 43,180
  • 11
  • 50
  • 79
1

I wouldn't use regex here, because it is probably not the most optimal solution. Instead, consider converting your input string to a map of letters, and then assert that there are 26 keys present:

var letters = {};
var input = "The quick brown fox jumps over the lazy dog";
input = input.replace(/[^A-Za-z]+/g, "");
for (var i=0; i < input.length; i++) {
    letters[input.charAt(i).toLowerCase()] = 1;
}

if (Object.keys(letters).length == 26)
    console.log("PANGRAM");
else
    console.log("NOT A PANGRAM");

The approach here first removes all non alpha characters. It then builds a map where each key is a lowercase version of whatever letter appeared in the input. A matching pangram is one which has 26 keys present, implying that it contains all letters of the alphabet.

Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
  • nice solution, i get the `letters[input.charAt(i)...` part but cannot put into words what is going on there and why did you assign that to a value of 1? – and1 May 12 '20 at 04:36
  • @and1 All I am doing is basically populating a hashmap where the keys are letters, and the values are just the dummy value of 1. We don't care about the 1, we just want some set to store all letter keys, so that we may assert whether or not all 26 appear. – Tim Biegeleisen May 12 '20 at 04:56
  • gotcha, since we're generating a hashmap, there needs to be some sort of value, but since it's not even being used at this time, it doesn't matter what that value is. just doing `letters[input.charAt(i).toLowerCase()]` would not work right? – and1 May 12 '20 at 05:28
  • Correct. We need some dummy value, which doesn't matter, because we never check it. Instead, we only count the number of keys present in the map. – Tim Biegeleisen May 12 '20 at 05:36
  • so just for my sake, `letters[input.charAt(i).toLowerCase()]` represents the key and the `1` in this case is the value for all the keys, which is arbitrary for this question. – and1 May 12 '20 at 16:01
  • Also, which part of the code is accounting for finding 26 unique letters? `charAt(i)`? – and1 May 12 '20 at 16:08
  • `if (Object.keys(letters).length == 26)` ... this is checking the number of keys in the hashmap. – Tim Biegeleisen May 12 '20 at 16:08
  • so it's converting it to an array and checking the length to see if it's 26 or not? is each key unique by default? or i guess im just confused on how it knows to find out if it contains each letter of alphabet and not just if it has 26 random letters or something – and1 May 12 '20 at 16:11
  • 1
    Line #3 of my solution removes all characters which are not letters. So, therefore only a max of 26 keys can be present. Lookup `Object.key()` to see what it does. – Tim Biegeleisen May 12 '20 at 16:12