Simple string scrambling

Posted on

Problem

Task: Take every 2nd char from the string, then the other chars, that are not every 2nd char, and concat them as new String.
Do this n times!

Examples:

"This is a test!", 1 -> "hsi  etTi sats!"
"This is a test!", 2 -> "hsi  etTi sats!" -> "s eT ashi tist!"

Code:

const split = (text) => {
  const firstPart = text.split('').filter((element, index) => index % 2 != 0).join('')
  const secondPart = text.split('').filter((element, index) => index % 2 == 0).join('')

  return firstPart + secondPart
}

const encrypt = (text, n) => {
  if (text == null) {
    return null
  }

  let result = text

  for (let i = 0; i < n; i++) {
    result = split(result)
  }

  return result
}

const decrypt = (text, n) => {
  if (text == null) {
    return null
  }

  for (let i = 0; i < n; i++) {
    let firstPartLength = text.length % 2 == 0 ? text.length / 2 : (text.length - 1) / 2 + 1
    let secondPartLenght = text.length % 2 == 0 ? text.length / 2 : (text.length - 1) / 2
    let firstPart = text.substring(0, firstPartLength)
    let secondPart = text.substring(secondPartLenght, text.length)

    secondPart = secondPart.split('').map(element => element + ' ').join('')
    let position = -1
    secondPart = secondPart.split('').map((element, index) => index % 2 != 0 ? firstPart[++position] : element).join('').substring(0, text.length)

    text = secondPart
  }

  return text
}

console.log(decrypt(encrypt('This is a test!', 3), 3))

console.log(encrypt('Some text!', 4))

Solution

Here’s a declarative take on it:

const is2ndChar = (c, i) => i % 2;
const isNot2ndChar = (c, i) => !(i % 2)

const scramble = s => 
  s.split('').filter(is2ndChar)
  .concat(s.split('').filter(isNot2ndChar))
  .join('');

const test1 = scramble('This is a test!');
const test2 = scramble(scramble('This is a test!'));

console.log(test1, test1 === 'hsi  etTi sats!');
console.log(test2, test2 === 's eT ashi tist!');

The filter-functions are only used once each, so they could just go directly into filter, but it’s a little more legible this way.

const split = (text) => (text.replace(/.(.)?/g, '$1') + ("d"+text).replace(/.(.)?/g, '$1')

The regex matches every other char. Do it twice, add any arbitrary char to the start of the string the second time to offset it.

  1. Modification on the split function should be a little more efficient since you only go through the variable once.
  2. Added join function using similar logic as split. Again, should be more efficient.
  3. Removed let result = text in the encrypt function and used text. No need to create an extra variable here.
const split = (text) => {
  let n = Math.floor(text.length / 2)
  return text.split('').reduce((a, v, i) => {
    a[i % 2 ? (i - 1) / 2 : n + (i / 2)] = v
    return a
  }, []).join('')
}

const join = (text) => {
  let n = Math.floor(text.length / 2)
  return text.split('').reduce((a, v, i) => {
    a[i < n ? (i + 1) * 2 - 1 : (i - n) * 2] = v
    return a
  }, []).join('')
}

const encrypt = (text, n) => {
  if (text == null) {
    return null
  }

  for (let i = 0; i < n; i++) {
    text = split(text)
  }

  return text
}

const decrypt = (text, n) => {
  if (text == null) {
    return null
  }

  for (let i = 0; i < n; i++) {
    text = join(text)
  }

  return text
}

console.log(decrypt(encrypt('This is a test!', 3), 3))

console.log(encrypt('Some text!', 4))

Leave a Reply

Your email address will not be published. Required fields are marked *