3

I have a requirement to check if a string is formed with any combination of strings given in a array example : we have array of ["for","car","keys","forth"] and a string of "forthcarkeys", the result should be true.If the string is "forthcarxykeys", the result should be false as xy is not in the array. Order of words in array doesnt matter. its not necessary that all strings from array should be matched but the teststring should only be made of any of the strings in array. If it contain any string other than the ones in array, return false

my approach:

var str = "forthcarkeys";
var arr = ["for","car","keys","forth"];
for(var i=0;i<arr.length;i++)
{
   if(str.indexOf(arr[i]) !== -1)
   {
      str.replace(arr[i],"");
   }
}

if(str !== "")
{
  console.log("yes");
} else 
{
  console.log("no");
}

but this approach is not efficient and failing.

6
  • Why it is failing? What does not work? Commented May 30, 2017 at 12:38
  • if we have repetition in array , it fails as we are replacing the matched string and also if there is much better way for doing this , I m open for it Commented May 30, 2017 at 12:39
  • Order of words in array doesn't matter well if you sorted the array by the length of the words, your code would work. Commented May 30, 2017 at 12:40
  • @George can you please explain why we need to sort the array by length of words Commented May 30, 2017 at 12:48
  • @kiranreddy you don't. It just works for this example, but ou have to try every order. Test with arr = [ "abcd", "def", "abc" ] and str = "abcdef" Commented May 30, 2017 at 12:50

7 Answers 7

2

One possible approach is to check for each prefix whether it can be expressed using the input strings.

The idea is that if we can easily compute whether the prefix with length i can be expressed with the input strings, if we already have this information for shorter prefixes (this can be done by checking whether any of the allowed strings lead to a shorter expressible prefix) -- see code below.

var str = "forthcarkeys";
var arr = ["for","car","keys","forth"];

// isPossible[i] indicates whether we can express the 
// prefix str.substring(0, i) using strings in arr.
var isPossible = Array(str.length + 1).fill(false);

// it is always possible to construct the empty string
isPossible[0] = true;

// consider each prefix of str, from shortest to longest
for (var i = 1; i <= str.length; i++) {
  // try to reach this prefix using an allowed string s_allowed,
  // by appending s_allowed to a shorter prefix
  for (var j = 0; j < arr.length; j++) {
    // "start" is the position where the current string 
    // would need to be appended
    start = i - arr[j].length;

    if (start >= 0 && isPossible[start]) {
      if (str.substring(start, i) == arr[j]) {
        isPossible[i] = true;
        // we break the loop over j, because we already found a
        // solution to express the current prefix str.substring(0,i)
        break;
      }
    }
  }
}

for (var i = 1; i <= str.length; i++) {
  console.log(str.substring(0, i) + " - " + isPossible[i] + "\n")
}

if (isPossible[str.length]) {
  console.log("yes");
} else {
  console.log("no");
}

To further detail how this works, consider a smaller example:

  • str = "abcd"
  • arr = ["ab", "a", "cd"]

The approach described here tests all prefixes of str, in increasing order of their length:

Step 0: empty prefix -- this is considered as always fine (it can be expressed using 0 strings).


Step 1: prefix "a":

We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:

  • "ab" can not be appended to a shorter prefix in order to get "a" (because the start position would need to be -1).
  • "a" can be appended to the empty prefix, which is always fine -- thus we get that prefix "a" is fine (can be expressed using the allowed strings).

Step 2: prefix "ab":

We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:

  • "ab" can be appended to the empty prefix, which is always fine -- thus we get that prefix "ab" is fine (can be expressed using the allowed strings).

Step 3: prefix "abc":

We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:

  • "ab" -- to append this one to a shorter prefix and get the current "abc" prefix, we would need to start at position 1, but the substring from that start position is "bc", thus we can not append the string "ab" to get prefix "abc".
  • "a" -- similar to above, we can not append "a" to get prefix "abc".
  • "cd" -- similar to above, we can not append "cd" to get prefix "abc".

We exhausted all allowed strings, thus prefix "abc" can not be expressed using the allowed strings.


Step 4: prefix "abcd" (entire string):

We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:

  • "ab" -- to append this one to a shorter prefix and get the current "abcd" prefix, we would need to start at position 2, but the substring from that start position is "cd", thus we can not append the string "ab" to get prefix "abcd".
  • "a" -- similar to above, we can not append "a" to get prefix "abcd".
  • "cd" -- we can append this allowed string to the "ab" prefix. In a previous step we found out that the "ab" prefix is fine (can be expressed with given strings), thus it is fine to append "cd" from there.

Thus, we get that the prefix "abcd" (which corresponds to the entire string) can be expressed using the input strings.

Sign up to request clarification or add additional context in comments.

1 Comment

@kiranreddy I added comments in the code and posted an example where I detailed how it works.
0

you need sort array by length Dec order and change if condition for yes is str==""

function check(str){
var arr = ["for", "car", "keys", "forth"];
arr= arr.sort((a,b)=> b.length-a.length) // sort with length dec order
console.log(arr) //longest length string is first then to lower length
for (var i = 0; i < arr.length; i++) {
    str = str.replace(arr[i], ""); 
}

if (str.trim()== "") { //empty 
  console.log("yes");
} else {
  console.log("no");
}
}

check("forthcarkeys")
check("forthcarxykeys")

5 Comments

arr.sort((a,b)=> b.length-a.length) can you explain this logic behind finding the difference between the lengths
Descending length was my first thought as well, but this would fail if the array also contains for example 'the' and the string is forthecar
also can you please clarify why we need to sort before comparing
This will not work with arr = [ "abc", "def", "abcd" ] and str = "abcdef"
@inetphantom yes , can you suggest a way to make it work
0

If all words are matched first, non occurring words can be omitted at the start. Then if the matches are in order of occurrence in the string, recursion can be used to find all matches after the match:

function eval(str, wordList = ["for","car","keys","forth", "the"]){	//note, added 'the' for testing
	if(!str)return false; //empty string -> false
  
  const words = wordList.map(w=> ({word:w, index: str.indexOf(w)})) //map all words with their occurence index inside the string
  	.filter(w=>w.index !== -1) //get rid of non occuring words alltogether
  	.sort((w1,w2) => w1.index - w2.index); //sort by index of occurence
  
  const check = (arr,ind) => {
  	if(ind>=str.length)return ind === str.length; //end of string reached -> match if exactly at end (false if greater than)
  	let w;    
    while(arr.length){
  	 	[w,...arr] = arr; //destructure: w = next word (index 0), arr is set to the remaining elements
      if(w.index > ind) return false; //gap since last match -> no match
      if(w.index===ind && check(arr,ind + w.word.length)) //if match is at the expected index, check the next indices
      	return true; //word started at the 'current' index and remaining words match as well   	
      //if code arrives here, try further with next word (while)
		}
    return false;
  };
  return check(words,0); //start recursive function with all words at string index 0
}

//test
function test(str, words){
	console.log(str,':', eval(str, words));
}
test("forthcarkeys");
test("forthcarxykeys");
test("forthecar");
test("abcdef",[ "abc", "def", "abcd" ]);

Comments

0

You could make an extended try and search for every words and use the temporary result set for filtering out if the words are in the string.

function check(string, array) {

    function fork(i, t) {
        var s = t.slice(), j;
        if (i === possibilities.length) {
            result.push(t.join(''));
            return;
        }
        if (possibilities[i].word.split('').every(function (c, j) { return s[j + possibilities[i].position] !== ''; })) {
            for (j = 0; j < possibilities[i].word.length; j++) {
                s[j + possibilities[i].position] = ''
            }
        }
        fork(i + 1, s);
        fork(i + 1, t);
    }

    var possibilities = array.reduce(function (r, a) {
            var p = string.indexOf(a);
            while (p !== -1) {
                r.push({ word: a, position: p });
                p = string.indexOf(a, p + 1);
            }
            return r;
        }, []),
        result = [];

    console.log(possibilities);
    fork(0, string.split(''));
    console.log(result);
    return result.some(function (a) { return !a; });
}

console.log(check("forthcarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"]));    // true
console.log(check("forthxycarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"]));  // false
.as-console-wrapper { max-height: 100% !important; top: 0; }

Version as above with early exit.

function check(string, array) {

    function fork(i, t) {
        var s = t.slice(), j;
        if (i === possibilities.length) {
            return !t.join('');
        }
        if (possibilities[i].word.split('').every(function (c, j) { return s[j + possibilities[i].position] !== ''; })) {
            for (j = 0; j < possibilities[i].word.length; j++) {
                s[j + possibilities[i].position] = '';
            }
        }
        return fork(i + 1, s) || fork(i + 1, t);
    }

    var possibilities = array.reduce(function (r, a) {
            var p = string.indexOf(a);
            while (p !== -1) {
                r.push({ word: a, position: p });
                p = string.indexOf(a, p + 1);
            }
            return r;
        }, []);

    return fork(0, string.split(''));
}

console.log(check("forthcarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"]));    // true
console.log(check("forthxycarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"]));  // false

Comments

0

Here is a more robust function that finds all the possible elements and ways that were used to compose it. If the length of the result is zero, then the original text cannot be made from the pool.

function decompose(orignal, pool) {  // recurisve function to find combinations of text
  var results = [];
  for (var element of pool) {     // for each element in pool
    if (orignal == element) {     // resursive base case, stop when orignal == element
      results.push([element]);    // * add solution
    } else {
      if (orignal.indexOf(element) == 0) {                    // if original text starts with element
        var remaining = orignal.slice(element.length);        // ready remaining text to be scanned
        var subresults = decompose(remaining, pool); // recursive call: findCombinationsOf remaining
        for (subresult of subresults) {
          results.push([element].concat(subresult));          // * add solution
        }
      }
    }
  }
  return results;
}

console.log(JSON.stringify(decompose("forthcarkeys", ["for","car","keys","forth"])));
console.log(JSON.stringify(decompose("forthcarkeys", ["for","car","keys","forth", "th"])));
console.log(JSON.stringify(decompose("nowaydude!", ["for","car","keys","forth", "th"])));

Comments

0

Here's my solution

Details:

  1. transform the given string "forthcarxykeys" into an array and assign it to a variable chars
  2. iterate over the given array ["for","car","keys","forth"]
  3. per every iteration, check if the word ( i.e. "for") exists in the array
  4. if it exists, obtain the index for each letter found and mark it as true in the char array
  5. return true if all values in chars are true, false if not.

JS:

// arr = ["for", "car", "keys", "forth"];
// str = "forthcarxykeys";

function check(arr, str) {
    let chars = str.split('');
    for (let i = 0; i < arr.length; i++) {
        let word = arr[i];
        let index = str.indexOf(word);
        let wordExists = index !== -1;
        if (wordExists) {
            let endIndex = index + word.length;
            for (index; index < endIndex; index++) {
                chars[index] = true;
            }
        }
    }
    return chars.every(i => i === true);
}

Comments

-1

Here is updated code. String replace wasn't working, hence used regex to achieve that. var re = new RegExp(arr[i], 'g');

function check(str, arr) {
  var flag = true;
  for (var i = 0; i < arr.length; i++) {
    if (str.indexOf(arr[i]) === -1) {
      flag = false;
    }
  }

  if (!flag) {
    console.log("Some keys didn't matched.");
  } else {
    console.log("Nothing remains. All matched.");
  }
}

var str = "forcarxy";
var arr = ["for", "car", "keys", "forth"];
check(str, arr);
var arr = ["abcd", "def", "abc"];
var str = "abcdef";
check(str, arr);
var arr = [ "abcd", "cdef" ];
var str = "abcdef";
check(str, arr);
var str = "aabc";
var arr = ["a", "bc"];
check(str, arr);

Code has been updated to consider the commented case by @inetphantom

14 Comments

This will not work with arr = [ "abcd", "def", "abc" ] and str = "abcdef"
Made the necessary correction for your case @inetphantom
if it should fit any combination that would also mean with one key not used. Your way to do it is invalid. An other case for you: arr = [ "abcd", "cdef" ] and ` str = "abcdef"` you should not be able to create str using the whole blocks of arr
@MilanChheda .can you add one false statement with your code. like forcarxy
your code now does not work with str = "aabc" and arr = ["a", "bc"]
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.