Mutations(Spoiler: Solution)

Hi FCC,

I just solved mutations and it seemed failry difficult compared to the rest of the challenges I’ve done so far. Could somebody please review my code and see If I came up with a solution that is overly complex and if so, help me with a simpler, more elegant solution? Thanks!

function mutation(arr) {
  var newArr = [];
  var indices = [];
  arr.forEach(function(item) {
    var newItem = item.toLowerCase();
    newItem = newItem.split('');
    newArr.push(newItem);
  });
  for (var i = 0; i < newArr[1].length; i++) {
    for (var x = 0; x < newArr[0].length; x++) {
      if (newArr[1][i] === newArr[0][x]) {
        //check [0] for any elements of [1];
        indices.push(newArr[1][i]);
        break;
      }
    }
  }
  newArr[1] = newArr[1].join('');
  indices = indices.join('');
  console.log(indices);
  console.log(newArr[1]);
  if ( indices === newArr[1]) {
  return true;
  } else {
  return false;
  }
} 

Hello.
First of all, never use such code

if ( indices === newArr[1]) { return true; } else { return false; }

It’s just

return (indices === newArr[1]);

Then,
I’ve used the next algorithm: I looped arr[1] and searched each char in arr[0] with String.prototype.indexOf().

1 Like

Yeah my return statement is a bit verbose. Other than clean looking code, why should one ‘never’ use code like that? Also, could you elaborate on the second part of your comment? Perhaps post the code you used.

Bad code style. Of course you can use such code, but you souldn’t.

My solution as you’ve asked:

function mutation(arr) {
  for (var i = 0; i < arr[1].length; i++){
    if (arr[0].toLowerCase().indexOf(arr[1][i].toLowerCase()) == -1)
      return false;
  }
 
  return true;
}
3 Likes

You can even replace the code in the if condition with this:

if (!arr[0].toLowerCase().includes(arr[1][ci].toLowerCase()))

I like this because this is more declarative (although it’s kind of hard to see the ! at the start).

3 Likes

I know, thank you :slight_smile:
indexOf() always returns Number so I think it doesn’t necessary to compare types.

As for your code, it works like mine with one difference: my code returns false then it meets the first char that arr[0] does not contain but your code look over all chars of arr[1]. However, it is really much better than the first variant on this page.

I’ve been playing around with this and came up with this solution:

function mutation(arr) {
  var first = arr[0].toLowerCase();
  var second = arr[1].toLowerCase();
  return Array.from(second)
    .every(function(c) { return first.includes(c); });
}
5 Likes

Very nice @kevcomedia - I used a for loop originally, and created a flag var ‘found’, but this was before I knew about ‘every’. As far as I understand it, every will exit when even one test fails, so am I correct in thinking this would be the most efficient?

My first solution is nearly the same as @dvainf’s, and many of my first solutions to the challenges used a similar approach. I only found out about every() a few days ago but never used it until now.

I’m not sure about how every() runs under the hood (like if it checks every element no matter what or immediately exits when one test fails as you mentioned), so I can’t say anything about efficiency (I’m fairly new to JS myself :slight_smile: ).

BTW, there’s also the some() function, which returns true if at least one test passes.

EDIT. I checked out every() in the MDN and it said

The every method executes the provided callback function once for each element present in the array until it finds one where callback returns a falsy value (a value that becomes false when converted to a Boolean). If such an element is found, the every method immediately returns false. Otherwise, if callback returned a true value for all elements, every will return true.

So I guess it is efficient in that regard.

1 Like

Hi

My code was almost identical to yours (although I went with true first then false, have changed that now). Thing is, the code doesn’t solve the first case - Hey vs. Hello (it’s returning true) and I can’t see why. Any ideas?

Here’s my code:

function mutation(arr) {

for (i = 0; i < arr[1].length; i++) {
if (arr[0].toLowerCase().indexOf(arr[1][i].toLowerCase()) === -1) {
return false;
}
else {
return true;
}
}
}

mutation([“hello”, “hey”]);

I’d appreciate any help you could offer - I thought it might be a bug, but I checked my javascript on another site and hey also returned true.

Thanks

Laura

1 Like

Of course!!!

Thank you! I think I stared so long at it I couldn’t see the problem.

Makes total sense, thank you thank you!

1 Like

Hi.

I found myself having a bit of trouble with this challenge.
I was looking at it as if there was the possibility that the first string of the array might be shorter than the second string.
I found the solutions above would return false on something such as:

 mutation(["men", "women"])

but would return true on:

 mutation(["women", "men"])

This is what I ended up with that would return true for both scenarios:

function mutation(arr) {
	var i = 0;
	if (arr[0].length <= arr[1].length) {
		for (i = 0; i < arr[0].length; i++) {
			if (arr[1].toLowerCase().indexOf(arr[0].toLowerCase().charAt(i)) < 0) {
				return (false);
			}
			return (true);
		}
	} else {
		for (i = 0; i < arr[1].length; i++) {
			if (arr[0].toLowerCase().indexOf(arr[1].toLowerCase().charAt(i)) < 0) {
				return (false);
			}
			return (true);
		}
	}
}

I know this is quite verbose, is there a more efficient way to write this?

Any and all answers are greatly appreciated.

Hi. It’s guaranteed in the challenge that the first string is at least longer than the second. But you can still go for a more general solution.

Notice that in both branches, the code is identical except for arr[0] in the first and arr[1] in the second. You can capture these in variables called shorter and longer, then use if to set them up appropriately. You can then factor the for-loop out of the if-else and use these variables instead.

var shorter;
var longer;
// Take the chance to make the strings lowercase here.
if (arr[0].length <= arr[1].length) {
  shorter = arr[0].toLowerCase();
  longer = arr[1].toLowerCase();
}
else {
  shorter = arr[1].toLowerCase();
  longer = arr[0].toLowerCase();
}

// Now we only have one for-loop, which is nice.
for (var i = 0; i < shorter.length; i++) {
  // You can also use `shorter[i]`.
  // In cases like these I prefer `if (!longer.includes(shorter[i])) {`.
  if (longer.indexOf(shorter.charAt(i)) < 0) {
    return false;  // parentheses can be omitted
  }
}
return true;  // you should also move this line out of the loop

hello everyone,
can you criticize my solution

function mutation(arr) {
var str1 = arr[0].toLowerCase();
var str2 = arr[1].toLowerCase();

var str1Arr = str1.split('');
var str2Arr = str2.split('');

str1Arr = deleteCopies(str1Arr);
str2Arr = deleteCopies(str2Arr);

var ct = 0;
for(var i=0; i<str2Arr.length; i++){
	for(var j=0; j<str1Arr.length; j++){
		if (str1Arr[j]===str2Arr[i]){
			ct +=1;
		}
	}
}

return ct === str2Arr.length;
}

function deleteCopies(arr){
var tmpArr = [];
var i, j, ct;

tmpArr.push(arr[0]);
for(i=1; i<arr.length; i++){
	ct = 0;
	for(j=0; j<tmpArr.length; j++){
		if(arr[i]===tmpArr[j]){
			ct +=1;
		}
	}
	if(ct===0){
		tmpArr.push(arr[i]);
	}
}

return tmpArr;

}

Nice approach. You first removed duplicate letters then checked each letter from the second array if it matches a letter from the first. A match increments the counter, then the function returns whether the counter is the same as the second array’s length.

Now let’s examine the code more closely :slight_smile: Let’s start with deleteCopies().
tmpArr is an array that contains unique values from the arr argument. So you scan each element, then if that element is not yet in tmpArr, that element is pushed to tmpArr.

Luckily there’s a built-in function in JS that checkes whether some value is already present in an array: Array.prototype.includes(). Using this we can replace the big chunk of code in the for-loop with more concise and descriptive code:

function deleteCopies(arr) {
  var tmpArr = [];
  // For each item in `arr`, if that item is not included in `tmpArr`,
  // push that item to `tmpArr`.
  for (var i = 0; i < arr.length; i++) {
    if (!tmpArr.includes(arr[i])) {
      tmpArr.push(arr[i]);
    }
  }
  // Now we have only an `if` in the for-loop and removed a bunch of variables.
  return tmpArr;
}

Now let’s move to the mutation() function. Here we note that we’re basically doing the same thing we did in deleteCopies(): see if a letter from the second array is found in the first or not. Again we can use includes() to do the job.

function mutation(arr) {
  // ...
  // code at the beginning omitted for this snippet...
  // ...

  // For each item in `str2Arr`, if that item is not included in
  // `str1Arr`, immediately return `false`.
  for(var i = 0; i < str2Arr.length; i++){
    if (!str1Arr.includes(str2Arr[i])) {
      return false;
    }
  }
  // After looking through all items in `str2Arr`, we found that 
  // all of its items are included in `str1Arr`, so we return `true`
  // after the loop.
  return true;
}

Above, if we found that a letter in str2Arr is not included in str1Arr, we immediately return false. This effectively stops the for-loop, but who cares? A letter in str2Arr is not found in str1Arr; there’s no more reason to loop through. That’s enough for us to return false right on the spot.

Now, after looping through str2Arr and we find that all of it’s letters are included in str1Arr, we can return true right after the for-loop.


Whew that’s a long post :sweat_smile: ! I’d like to say “Here’s a potato,” but this is not 9gag :smiley: . Overall your’s is a good working solution. Good job :thumbsup: ! But remember to come back to your solutions every now and then and improve them with newer things that you learned.

Cheers!

3 Likes

Thank you very much kevcomedia, you taught me a lot of things. I really appreciate your feedback.:slight_smile:

:smiley: I felt so happy when I solved this challenge, then came here and saw how elegantly many of you fellow campers solved it. It’s fun how many ways there are to solve a problem. Here’s my, rather verbose, solution. Any suggestions for improvement much appreciated :slight_smile:

function mutation(arr) {

// create arrays to house characters of strings we want to compare
var arr1 = ;
var arr2 = ;

// create array for characters that are present in both strings
var arr1_1 = ;

// split strings to compare into arrays
arr1 = arr[0].toLowerCase().split(“”);
arr2 = arr[1].toLowerCase().split(“”);

for (var i = 0; i < arr2.length; i++) {

// check if arr1 includes each character from arr2
if (arr1.includes(arr2[i])) {
  
  // if arr2(i) is found in arr1 push it to a new array
  arr1_1.push(arr2[i]);
}

}

// join arr1_1 into a string
var strToCompare = arr1_1.join([separator = “”]);

// compare the new string to arr[1]
if (arr[1].toLowerCase() == strToCompare) {
return true;
}
return false;

}

Hi, @jvalonen! The thrill of solving a problem feels good, doesn’t it :smiley:?

Ok, let’s look at that code. The thing is that, there’s actually no need to convert the strings into arrays, because strings also have an include() function, and you can access characters like you do with arrays. You can remove the .split("") in arr1 = arr[0].toLowerCase().split("");, and your code will work fine (better, even). While you’re at it you can give arr1 and arr2 more descriptive names such as first and second, respectively.

CODE
function mutation(arr) {
  // create array for characters that are present in both strings
  var arr1_1 = [];

  // Store lowercase versions of the string inputs.
  var first = arr[0].toLowerCase();
  var second = arr[1].toLowerCase();
  
  for (var i = 0; i < second.length; i++) {
    // check if `first` includes each character from `second`
    if (first.includes(second[i])) {
      // if `second[i]` is found in `first` push it to a new array
      arr1_1.push(second[i]);
    }
  }
  
  // join arr1_1 into a string
  var strToCompare = arr1_1.join([separator = '']);
  // compare the new string to `second`
  if (second == strToCompare) {
    return true;
  }
  return false;
}

Next, this will definitely be a source of confusion:
var strToCompare = arr1_1.join([separator = '']);

Just .join(''); will do.

Finally, note this part:

if (second == strToCompare) {
  return true;
}
return false;

This is a fairly common occurrence in code. It says,

If the condition is true, return true. Else (if the condition is false), return false.

But wait, if this is the case, why not just return the boolean value of the condition itself?

return second == strToCompare;
Now we have this code:
function mutation(arr) {
  // create array for characters that are present in both strings.
  // I could give this a more meaningful name, but naming things is hard. :)
  var arr1_1 = [];

  // Store lowercase versions of the string inputs.
  var first = arr[0].toLowerCase();
  var second = arr[1].toLowerCase();
  
  for (var i = 0; i < second.length; i++) {
    // check if `first` includes each character from `second`
    if (first.includes(second[i])) {
      // if `second[i]` is found in `first` push it to a new array
      arr1_1.push(second[i]);
    }
  }
  
  // join arr1_1 into a string
  var strToCompare = arr1_1.join('');
  return second == strToCompare;
}

There you go. Good work :thumbsup: !

2 Likes

At first I tried to solve this with a regular expression (having pretty much zero experience with those). In my head it made sense to match two sets of characters that way… I quickly hit a brick wall though.

Can a RegEx ninja chime in on why it’s not a good idea to solve this problem that way? Or if it is, how would you do it?

Thanks! After stumbling around I got the same feeling (about “did match” vs “did any of these not match”). Good to see that confirmed, it teaches me something about regex and when to use it :slight_smile:

I ended up using a loop as well. Didn’t even come close to that regex solution!