5

Trying to solve this question on Codewars.

I've seen other articles that deal with shuffling / scrambling a string randomly.

But what about scrambling a string according to the values in a given array?

I.e. abcd given the array [0, 3, 2, 1] will become acdb because:

  • a moves to index 0
  • b moves to index 3
  • c moves to index 2
  • d moves to index 1

My guess is to start out by splitting the string into an array. And then we want to get the index value of the array that's passed into the scramble function, and push the character at the index value from that array into the new array. And finally join the array:

function scramble(str, arr) {

  let newArray = str.split("");
  let finalArray = [];

  for (let i = 0; i < str.length; i++) {
    console.log(newArray);
    finalArray.push(newArray.splice(arr[i], 1));
  }
  return finalArray;
}

console.log(scramble("abcd", [0, 3, 1, 2]));

But the problem with this logic is that .splice() removes the character from the newArray every time.

Is there another method that will remove the character at the specified index without modifying the original array?

I don't think slice will work either.

HappyHands31
  • 4,001
  • 17
  • 59
  • 109
  • Your question is not consistent; you ask how to remove something from an array with affecting the array. The answer is "you can't" -- that's the definition of "remove". If you just want to get a character at a specific index, that's what `slice` is for... – Heretic Monkey Jun 03 '19 at 21:50
  • What happens if you provide an array that is not the same length as the string? Or would this never happen? – zfrisch Jun 03 '19 at 21:53
  • @zfrisch according to the coding challenge, this doesn't happen. – HappyHands31 Jun 03 '19 at 22:00

4 Answers4

3

You can make a separate array to store the letters.

var str = "abcd";
var arr = [0, 3, 2, 1];
function scramble(s, a) {
    var ar = Array(a.length);
    a.forEach((e, i) => {
        ar[e] = s[i];
    });
    return ar.join('');
}
console.log(scramble(str, arr));
Yousername
  • 1,012
  • 5
  • 15
  • I didn't know that you could make a "blank" array of a specified length. And like the other answer, _assigning_ the string index values to the newArray values is much more efficient. Thanks! – HappyHands31 Jun 03 '19 at 22:33
2

Answer

Use reduce on the array and as the array is iterated assign the character of the iterator index(i) in the string(s) to the value index(v) of the new array(ra). After the reduce completes, use join to turn the returned array(ra) back into a string.

let scramble = (s, a) => a.reduce((ra,v,i)=> (ra[v] = s[i], ra), []).join("");

Example:

let scramble = (s, a) => a.reduce((ra,v,i)=> (ra[v] = s[i], ra), []).join("");

console.log( scramble("abcd", [0,3,2,1]) );

Clarification Code:

I realize the above code may be hard to wrap your head around. Let me provide you with the same exact functionality, but in a standard function. Keep in mind this is exactly what is happening in the above code, but it may be simpler to comprehend if you're not used to the concision of ES6:

function scramble(my_string, my_array) { 
   // create an array to return
   let returnable_array = [];

   // loop through the provided array. 
   // string index is the array key: 0,1,2,3
   // array_index is the value of the array keys: 0,3,2,1

   for(let [string_index, array_index] of my_array.entries()) {

   // we assign the character at string index 
   // to the value index inside the returnable array

    returnable_array[array_index] = my_string[string_index];
   } 

   // we turn the array into a string
   let returnable_string = returnable_array.join("");

   // we return the string
   return returnable_string
}

Example:

    function scramble(my_string, my_array) { 
       let returnable_array = [];
       for(let [string_index, array_index] of my_array.entries()) {
        returnable_array[array_index] = my_string[string_index];
       } 
       returnable_string = returnable_array.join("");
       return returnable_string
    }


console.log(scramble("abcd", [0,3,1,2]));
zfrisch
  • 8,474
  • 1
  • 22
  • 34
1

You can loop over the input string get the character at the current position using string.charAt(position) and put it into a new array into the position retrieved from the positions array.

function scramble (str, arr) {
let newArray = [];
for (let i = 0; i < str.length; i++) {
    newArray[arr[i]]=str.charAt(i);
  }
  return newArray.join();
}

console.log(scramble("abcd", [0, 3, 1, 2]));
obscure
  • 11,916
  • 2
  • 17
  • 36
  • 1
    You can also just use `str[i]`, there's no need to call a function. – Barmar Jun 03 '19 at 21:54
  • I did not realize you could simply _assign_ the string values to the `newArray` like that. That is much simpler than trying to push into a new array. With a `return newArray.join()` at this end, this works. – HappyHands31 Jun 03 '19 at 21:58
  • @Barmar - cool! Good to know - though I'm not sure what is happening behind the scenes if I use array access on a string. – obscure Jun 03 '19 at 22:00
  • @HappyHands31 - Glad I could help! =) – obscure Jun 03 '19 at 22:00
  • Nothing special is happening. EcmaScript 5 simply added this as an equivalent feature. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Character_access – Barmar Jun 03 '19 at 22:02
  • Thanks, but why the second edit? Function must return a string. – HappyHands31 Jun 03 '19 at 22:02
  • Ah sorry @HappyHands31 I didn't realize you edited it. I was fixing a small typo - I forgot a semicolon. – obscure Jun 03 '19 at 22:05
1

I think the best approach would be to put the string into a new array:

    function scramble(str, arr) {
        //validate if the array has elements
        if (arr && arr.length) {
            //create a new array
            const strArr = []
            arr.forEach(index => {
                //push each character by index
                //As noted by Barmar you could either use
                //str.charAt(index) or str[index]
                //as both will return the character at the specified index
                strArr.push(str.charAt(index))
            })
            //return a new string
            return strArr.join('');
        }
    }
Fernando Bravo Diaz
  • 551
  • 1
  • 4
  • 11