Published on

My Experiments with Reversing a String in JavaScript

Authors

There are many ways to do a simple thing in JavaScript, like reversing a string. But the point is,  how do you understand, what is the best or fastest way in all of them?

So, I made a quick search in Google, and found the most common solutions available, and made a script to compare them all. This article is all about that script and the lessons I learned from its output.

How did I test?

I took inspiration from one or two scripts and then built my script for testing. The core function is as follows:

function test(repeats, description, func, expected) {
  if (func() !== expected) {
    console.info('\x1b[31m%s\x1b[0m', `${description}: Result not valid`)
    return
  }

  const t1 = Date.now()
  for (let i = 0; i < repeats; ++i) {
    func()
  }
  const t2 = Date.now()
  const dt = t2 - t1
  console.log(`${description}: total time is ${dt} ms`)
  results[description] = dt
}

The test function, which calculates the time taken by the function to run repeatedly The function checks if the output we are getting is correct. And if that's fine, it will run the function multiple times and measure the time.

const results = {}

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args)
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2))
      }
    }
  }
}

const curriedTest = curry(test)
const repeatedTest = curriedTest(1_00_000)

Futher, I made some modifications to suit my needs. They're irrelevant to the topic we are discussing here.

Functions To Reverse A String

Method 1:  Split And Reverse

function arraySplitAndReverse(str) {
  return str.split('').reverse().join('')
}

Method 2: Spread And Reverse

function arraySpreadAndReverse(str) {
  return [...str].reverse().join('')
}

Method 3: Split And Reduce

function arraySplitAndReduce(str) {
  return str.split('').reduce((x, y) => y.concat(x))
}

Method 4: Spread and Reduce

function arraySpreadAndReduce(str) {
  return [...str].reduce((x, y) => y.concat(x))
}

Method 5: Straight 'for' Loop With String

function forLoopStraight(str) {
  let reverse = ''
  for (let i = 0; i < str.length; i++) {
    reverse += str[str.length - 1 - i]
  }
  return reverse
}

Method 6: Inverse 'for' Loop With String

function forLoopInverse(str) {
  let reverse = ''
  for (let i = str.length - 1; i >= 0; i--) {
    reverse += str[i]
  }
  return reverse
}

Method 7: Inverse 'for' Loop With Array

function forLoopInverseWithArray(str) {
  const reverse = []
  for (let i = str.length - 1; i >= 0; i--) {
    reverse.push(str[i])
  }
  return reverse.join('')
}

Method 8: Swapping Indices With 'while' Loop

function reverseBySwappingIndices(str) {
  const strArr = [...str]
  let leftIndex = 0
  let rightIndex = strArr.length - 1

  while (leftIndex < rightIndex) {
    let temp = strArr[leftIndex]
    strArr[leftIndex] = strArr[rightIndex]
    strArr[rightIndex] = temp

    leftIndex++
    rightIndex--
  }

  return strArr.join('')
}

Method 9: Swapping Indices With 'for' Loop

function divideAndConquer(str) {
  const strArr = [...str]
  let n = strArr.length - 1

  for (let i = 0; i <= n / 2; i++) {
    let temp = strArr[i]
    strArr[i] = strArr[n - i]
    strArr[n - i] = temp
  }

  return strArr.join('')
}

Method 10: Array Popping With 'while' Loop

function reverseWithWhileAndPop(str) {
  const output = []
  const strArr = [...str]
  while (strArr.length) {
    output.push(strArr.pop())
  }

  return output.join('')
}

Method 11: Array Map With Preserving Array

function mapPreservingArray(str) {
  const strArr = [...str]
  const reverse = strArr.map(strArr.pop, [...strArr])
  return reverse.join('')
}

Method 12: Array Map Without Preserving Array

function mapWithoutPreservingArray(str) {
  const strArr = [...str]
  const reverse = [...strArr].map(strArr.pop, strArr)
  return reverse.join('')
}

If you are a coder, the codes are self-explanatory. Now, its time to test these functions. I did test them each 100,000 times.

// Test Functions
const testString =
  "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"

const reverseString =
  "muspI meroL fo snoisrev gnidulcni rekaMegaP sudlA ekil erawtfos gnihsilbup potksed htiw yltnecer erom dna ,segassap muspI meroL gniniatnoc steehs tesarteL fo esaeler eht htiw s0691 eht ni desiralupop saw tI .degnahcnu yllaitnesse gniniamer ,gnittesepyt cinortcele otni pael eht osla tub ,seirutnec evif ylno ton devivrus sah tI .koob nemiceps epyt a ekam ot ti delbmarcs dna epyt fo yellag a koot retnirp nwonknu na nehw ,s0051 eht ecnis reve txet ymmud dradnats s'yrtsudni eht neeb sah muspI meroL .yrtsudni gnittesepyt dna gnitnirp eht fo txet ymmud ylpmis si muspI meroL"

repeatedTest('split() and .reverse() method', () => arraySplitAndReverse(testString), reverseString)
repeatedTest(
  '...spread and .reverse() method',
  () => arraySpreadAndReverse(testString),
  reverseString
)
repeatedTest('split() and .reduce() method', () => arraySplitAndReduce(testString), reverseString)
repeatedTest(
  '...spread and .reduce() method',
  () => arraySpreadAndReduce(testString),
  reverseString
)
repeatedTest('For loop straight', () => forLoopStraight(testString), reverseString)
repeatedTest('For loop inverse', () => forLoopInverse(testString), reverseString)
repeatedTest(
  'For loop inverse with Array',
  () => forLoopInverseWithArray(testString),
  reverseString
)
repeatedTest(
  'Swapping Indices with temp variable',
  () => reverseBySwappingIndices(testString),
  reverseString
)
repeatedTest('Divide and conquer swapping', () => divideAndConquer(testString), reverseString)
repeatedTest('while loop and .pop()', () => reverseWithWhileAndPop(testString), reverseString)
repeatedTest('.map() preserving original', () => mapPreservingArray(testString), reverseString)
repeatedTest(
  '.map() without preserving original',
  () => mapWithoutPreservingArray(testString),
  reverseString
)

The Result

Just to get comparative results, I wrote code to sort and add comparison in the form of 'slowness'.

let fastestTime = Infinity
const sortedResults = Object.entries(results)
  .map(([desc, time]) => {
    if (time < fastestTime) {
      fastestTime = time
    }

    return {
      desc,
      time,
    }
  })
  .map((result) => {
    result.slowness = Number.parseFloat(result.time / fastestTime).toFixed(2) + 'x'
    return result
  })
  .sort((a, b) => a.time - b.time)

console.table(sortedResults)
Results of Reversing a String

Conclusion

  • Methods, where we don't need to use Arrays, are the fastest, obviously because we save time in converting strings to arrays and vice versa.
  • .reduce() performed better than the built-in method .reverse() to reverse an array.
  • .split("") performed better than ...spread converting a string into an array.
  • Working with indices is a comparatively heavy operation.