Javascript Codewars Challenge(isMerge)

后端 未结 4 1380
天涯浪人
天涯浪人 2021-01-29 15:53

I have asked this question before (the first link to my question) but couldn\'t get the complete solution for the question. Write an algorithm to check if a given string, s, can

相关标签:
4条回答
  • 2021-01-29 16:21

    Here is a non-recursive approach.

    function checkIt (str, str1, str2){
        if(str.length !== str1.length + str2.length){
            return false;
        }
    
        var str1_index = -1
        var str2_index = -1;
        var curr_index1, curr_index2;
    
        for(var i=0; i<str.length; i++){
            curr_index1 = str1.indexOf(str[i]);
            curr_index2 = str2.indexOf(str[i]);
    
            if(curr_index1 == -1 && curr_index2 == -1){
                return false;
            }
    
            if(curr_index1 > str1_index){
                str1_index = curr_index1;
            }
            else if(curr_index1 !== -1 && curr_index1 < str1_index) {
                return false;
            }
    
            if(curr_index2 > str2_index){
                str2_index = curr_index2;
            }
            else if(curr_index2 !== -1 && curr_index2 < str2_index) {
                return false;
            }   
        }
        return true;
    }
    
    
    console.log(checkIt('codewars', 'cdw', 'oears')); // true
    console.log(checkIt('codewars', 'cdw', 'ears'));  // false
    console.log(checkIt('codewars', 'cwd', 'oears')); // false
    console.log(checkIt('codewars', 'cdw', 'oeasr')); // false
    
    0 讨论(0)
  • 2021-01-29 16:23

    code review

    function isMerge(s, part1, part2) {
      // length comparison is not required
      // this is a roundabout way of saying A === B
      // more comments on this below
      if(part1.length + part2.length != s.length)
      {
        return false;
      }
      if (s.length == '')
      {
        return true;
      }
      for(var i = 0, len = s.length; i < len; i++)
      {
        // you don't have to loop over part1 or part2
        for (var j=0, jen = part1.length; j<jen; j++)
        {
          // s[0] and part1[0] will always be the same char
          // i'm certain you intended s[i] and part1[j]
          if(s[0i] === part1[0j])
          {
            // if you `return` here, you will return lots of false positives
            // the condition above only checks for two matching letters,
            // you have to finish looping thru `s` to know we checked all letters
            return true;
          }
        }
        // this loop is broken the same way as the one above
        for(var p = 0, pen = part2.length; p <l pen; p++)
        {
          if(s[0] === part2[0])
          {
            return true;
          }
        }
      }
      // this is also wrong too.
      // what we will do is, if at anytime there is a non-match in the loop,
      // it means we can `return false` early and stop looping
      // otherwise if the loop finished without finding any non-matches,
      // then we will return true
      return false;
    }

    length is not a good way to compare strings if we're actually interested in comparing the contents of the strings. Sure, it's a short-cut to a false value in some cases, but it's a premature optimization.

    Start with a minimal program that describes your intent. If you profile the code later and learn that improvements could be made in your function, then you can write this sort of check, and be sure to leave a comment why the length comparison was added.

    But until you need it, leave it out. It's only making this harder on you by giving you more to think/worry about.


    fixer upper

    I don't mean to be brutal, but we just scrapped most of that function. We will salvage what we can and work from there

    function isMerge(s, part1, part2) {
      for (let i = 0, len = s.length; i < len; i++) {
        if (s[i] ...)
          return false
      }
      return true;
    }
    

    So in that loop we want to search s[i] where it matches the first letter in part1 or part2. But not always the first letter, right? Once we match a letter in part1, we can't reuse that one.

    I'm going to add two variables which keeps track of which position is currently unchecked in part1 and part2. We will advance the position of the respective variable when either part matches.

    function isMerge(s, part1, part2) {
      // i keeps track of position in s
      // p1 keeps track of position in part1
      // p2 keeps track of position in part2
      for (let i = 0, len = s.length, p1 = 0, p2 = 0; i < len; i++) {
        // check current letter for match in part1
        if (s[i] === part1[p1]) {
          p1++     // increment p1 so we don't reuse this char
          continue // move on to next letter in s
        }
        // check current letter for match in part2
        else if (s[i] === part2[p2]) {
          p2++     // increment p1 so we don't reuse this char
          continue // move on to next letter in s
        }
        // current letter didn't match part1 or part2 !
        else {
          return false
        }
      }
      // we got thru the loop without resulting in false
      // so it must be true that s isMerge of part1 and part2
      return true
    }
    
    
    // example 1
    {
      let str = 'codewars'
      let part1 = 'cdw'
      let part2 = 'oears'
      console.log(isMerge(str, part1, part2)) // true
    }
    
    // example 2
    {
      let str = 'codewars'
      let part1 = 'cdb'
      let part2 = 'oears'
      console.log(isMerge(str, part1, part2)) // false
    }


    But really, I find that solution to be hard to reason about. If you dissect the scope of that function, you'll see many variables we have to keep track of in our heads: s, part1, part2, p1, p2, i, len. Not to mention i, p1, and p2 need to change as the loop runs, so you have to make sure you get all of that right. It's a lot to manage.

    We have some comments in there to help the reader understand our intent, but the code should be expressing our intent and not the other way around. In fact, I think comments often make it harder to read code because there's just more cruft in the way of the code.

    There's a better way …


    a better way

    Hey look ! We've already arrived at a better way. That was quick.

    This is a basic recursive procedure which checks first letter of s against first letter of x or y. If either is a match, it recurses using the remaining letters in the matching strings. Otherwise it returns false.

    We use several other small procedures which serve to help us describe our program more effectively. You'll see that there are no comments in this code and it remains quite readable – this time the code does the explaining …

    const shead = s => s.substr(0,1)
    const stail = s => s.substr(1)
    const isEmpty = s => s === ''
    const firstCharEqual = (x,y) => shead(x) === shead(y)
    
    const isMerge = (s,x,y) => {
      if (isEmpty(s))
        return isEmpty(x) && isEmpty(y)
      else if (firstCharEqual(s, x) && firstCharEqual(s, y))
        return isMerge(stail(s), stail(x), y) || isMerge(stail(s), x, stail(y))
      else if (firstCharEqual(s, x))
        return isMerge(stail(s), stail(x), y)
      else if (firstCharEqual(s, y))
        return isMerge(stail(s), x, stail(y))
      else
        return false
    }
    
    // example 1; correct inputs; should be true
    {
      let str = 'codewars'
      let part1 = 'cdw'
      let part2 = 'oears'
      console.log(isMerge(str, part1, part2)) // true
    }
    
    // example 2; mismatched input; should be false
    {
      let str = 'codewars'
      let part1 = 'cdb'
      let part2 = 'oears'
      console.log(isMerge(str, part1, part2)) // false
    }
    
    // example 3; extra input; should be false (apparently)
    {
      let str = 'codewars'
      let part1 = 'cdw_'
      let part2 = 'oears'
      console.log(isMerge(str, part1, part2)) // false
    }
     
    // example 3; extra input; should be false (apparently)
    {
      let str = 'Bananas from Bahamas'
      let part1 = 'Bahas'
      let part2 = 'Bananas from am'
      console.log(isMerge(str, part1, part2)) // true
    }


    failure

    Apparently you've found some conditions under which my last bit of code fails to meet certain criteria that you did not mention in your original post.

    I found the quiz on codewars and here's where the code failed the most

    let s = 'Bananas from Bahamas',
    let x = 'Bahas'
    let y = 'Bananas from am'
    isMerge(s,x,y); // should be true
    

    I've edited the above code from …

    if (isEmpty(s) && isEmpty(x) && isEmpty(y))
      return true
    

    … to …

    if (isEmpty(s))
      return isEmpty(x) && isEmpty(y)
    else if (firstCharEqual(s, x) && firstCharEqual(s, y))
      return isMerge(stail(s), stail(x), y) || isMerge(stail(s), x, stail(y))
    

    This will ensures that part1 and part2 have been completely consumed (emptied) at the same time s is consumed.

    And more importantly, it will ensure that if part1 and part2 match simultaneously, we fork the recursion and check that either branch of the fork results in a true

    The edited code passes all tests

    0 讨论(0)
  • 2021-01-29 16:28

    Try something like:

    function mergeCheck(s, p1, p2, i, j, k) {
        if (i == s.length) {
            return true;
        } else {
            if (s[i] == p1[j]) {
                j++;
            } else if (s[i] == p2[k]) {
                k++;
            } else {
                return false;
            }
            i++;
            return mergeCheck(s, p1, p2, i, j, k);
        }
    }
    
    function isMerge(s, p1, p2) {
        if (s.length == (p1.length + p2.length)) {
            return mergeCheck(s, p1, p2, 0, 0, 0);
        }
        else {
        	return false;
        }
    }
    console.log(isMerge("codewars", "cdw", "oears"))
    console.log(isMerge("codewars", "cw", "oears"))
    console.log(isMerge("codewars", "cdwx", "oears"))

    It starts at the beginning of the s string and compares the letter in question with the letters next in sequence for p1 or p2. If a match is found in either of them, it moves forward and checks the subsequent letters until it finds a mismatch or reaches the end of s

    0 讨论(0)
  • 2021-01-29 16:37
    function isMerge(s, part1, part2) {
                if (part1.length === 0) {
                    return (s == part2);
                }
                if (part2.length === 0) {
                    return (s == part1);
                }
                if (s.length === 0) {
                    return false;
                } /*because if you are still here, part 1 and part 2 are not empty*/
                /*handle empty strings first both as "base case" and to not have to worry about expressions like s[0]*/
                return ((s[0] == part1[0]) && isMerge(s.substr(1), part1.substr(1), part2)) || ((s[0] == part2[0]) && isMerge(s.substr(1), part1, part2.substr(1)));
            }
            // example 1
            {
                let str = 'codewars'
                let part1 = 'cdw'
                let part2 = 'oears'
                console.log(isMerge(str, part1, part2)) // true
            }
    
            // example 2
            {
                let str = 'codewars'
                let part1 = 'cdb'
                let part2 = 'oears'
                console.log(isMerge(str, part1, part2)) // false
            }
    
    0 讨论(0)
提交回复
热议问题