Y
Published on

BigFrontEnd Category 17 String Implementation Questions

Authors
  • avatar
    Name
    Yinhuan Yuan
    Twitter

Introduction

This blog post summarizes the String implementation related questions found on BigFrontEnd.Dev.

1.convert HEX color to RGBA

78.https://bigfrontend.dev/problem/convert-HEX-color-to-RGBA

Suppose you write some CSS code, you need to set colors. You can choose hexadecimal notation #fff or Functional notation rgba(255,255,255,1).

Can you write a function to convert hexadecimal notation to functional notation?

hexToRgb('#fff')
// 'rgba(255,255,255,1)'
  1. Alpha channel should have maximum 2 digits after decimal point, round up if needed.
  2. Don't forget to do input validation

Solution:

/**
 * @param {string} hex
 * @return {string}
 */
function hexToRgba(hex) {
  // Helper function to validate the hex color input
  function isValidHex(hex) {
    return /^#([0-9A-Fa-f]{3,4}|[0-9A-Fa-f]{6,8})$/.test(hex)
  }

  // Helper function to expand shorthand hex notation to full form
  function expandShorthand(hex) {
    if (hex.length === 4) {
      return '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]
    }
    if (hex.length === 5) {
      return '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3] + hex[4] + hex[4]
    }
    return hex
  }

  // Validate input
  if (!isValidHex(hex)) {
    throw new Error('Invalid hexadecimal color format')
  }

  // Expand shorthand notation if necessary
  hex = expandShorthand(hex)

  // Extract the red, green, blue, and alpha components
  let r = parseInt(hex.slice(1, 3), 16)
  let g = parseInt(hex.slice(3, 5), 16)
  let b = parseInt(hex.slice(5, 7), 16)
  let a = 1

  if (hex.length === 9) {
    a = parseInt(hex.slice(7, 9), 16) / 255
    a = Math.round(a * 100) / 100 // Round to two decimal places
  }

  return `rgba(${r},${g},${b},${a})`
}
// Example usage:
console.log(hexToRgb('#fff')) // 'rgba(255,255,255,1)'
console.log(hexToRgb('#ffffff')) // 'rgba(255,255,255,1)'
console.log(hexToRgb('#ff00ff80')) // 'rgba(255,0,255,0.5)'
console.log(hexToRgb('#123')) // 'rgba(17,34,51,1)'
console.log(hexToRgb('#1234')) // 'rgba(17,34,51,0.27)'

2.convert snake_case to camelCase

79.https://bigfrontend.dev/problem/convert-snake_case-to-camelCase

Do you prefer snake_case or camelCase ?

Anyway, please create a function to convert snake_case to camcelCase.

snakeToCamel('snake_case')
// 'snakeCase'
snakeToCamel('is_flag_on')
// 'isFlagOn'
snakeToCamel('is_IOS_or_Android')
// 'isIOSOrAndroid'
snakeToCamel('_first_underscore')
// '_firstUnderscore'
snakeToCamel('last_underscore_')
// 'lastUnderscore_'
snakeToCamel('_double__underscore_')
// '_double__underscore_'

contiguous underscore __, leading underscore _a, and trailing underscors a_ should be kept untouched.

Sliding window: x_x => xX;

Solution:

/**
 * @param {string} str
 * @return {string}
 */
function snakeToCamel(str) {
  const n = str.length
  // n - 3, n - 2, n -1
  if (n === 0) {
    return str
  }
  const ans = [str[0]]
  let i = 0
  while (i < n - 2) {
    // conside i, i + 1, i + 2
    if (str[i] !== '_' && str[i + 1] === '_' && str[i + 2] !== '_') {
      ans.push(str[i + 2].toUpperCase())
      i += 2
    } else {
      ans.push(str[i + 1])
      i += 1
    }
  }
  ans.push(str[n - 1])
  return ans.join('')
}

3.decode message

9.https://bigfrontend.dev/problem/decode-message

Your are given a 2-D array of characters. There is a hidden message in it.

I B C A L K A
D R F C A E A
G H O E L A D

The way to collect the message is as follows

  1. start at top left
  2. move diagonally down right
  3. when cannot move any more, try to switch to diagonally up right
  4. when cannot move any more, try switch to diagonally down right, repeat 3
  5. stop when cannot neither move down right or up right. the character on the path is the message
  6. for the input above, IROCLED should be returned.

notes

if no characters could be collected, return empty string

State change function.

Solution:

/**
 * @param {string[][]} message
 * @return {string}
 */
function decode(message) {
  const result = []
  let x = 0
  let y = 0
  let dir = 'down'

  const nextState = (x, y, dir) => {
    if (dir === 'down') {
      x++
    } else {
      x--
    }

    if (x === 0) {
      dir = 'down'
    } else if (x === message.length - 1) {
      dir = 'up'
    }

    y++
    return [x, y, dir]
  }
  const isValidState = (x, y, dir) => message[x] && message[x][y]
  while (isValidState(x, y, dir)) {
    result.push(message[x][y])
    ;[x, y, dir] = nextState(x, y, dir)
  }

  return result.join('')
}

4.longest substring with unique characters

87.https://bigfrontend.dev/problem/longest-substring-with-unique-characters

Given a string, please find the longest substring that has no repeated characters.

If there are multiple result, any one substring is fine.

longestUniqueSubstr('aaaaa')
// 'a'
longestUniqueSubstr('abcabc')
// 'abc', or 'bca', or 'cab'
longestUniqueSubstr('a12#2')
// 'a12#'

Follow-up

What is the time & space cost of your solution ? Could you do it better?

Solution:

/**
 * @param {string} str
 * @return {string}
 */
function longestUniqueSubstr(s) {
  const n = s.length
  let i = 0
  let j = 0
  let ans = ''
  const freq = {}
  const meetCondition = (freq) => Math.max(...Object.values(freq)) < 2
  while (i < n) {
    // Move the right index as far as possible until the condition is not met.
    while (i < n && meetCondition(freq)) {
      freq[s[i]] = (freq[s[i]] || 0) + 1
      i += 1
    }
    // [j, i - 2] is the range which meets the condtion.
    // freq contains the data from [j, i - 1]
    if (meetCondition(freq)) {
      // [j, i - 1] (freq) meet the condition
      if (ans.length < i - 1 - j + 1) {
        ans = s.substring(j, i)
      }
      break
    } else {
      // [j, i - 2] meet the condition
      if (ans.length < i - 2 - j + 1) {
        ans = s.substring(j, i - 2 + 1)
      }
    }
    // move the left index as far as possible until the condition is met.
    while (!meetCondition(freq)) {
      freq[s[j]] -= 1
      j += 1
    }
  }
  return ans
}

5.implement String.prototype.trim()

95.https://bigfrontend.dev/problem/implement-String-prototype-trim

String.prototype.trim() is commonly used when processing strings.

It is very easy, can you implement your own one?

There are many ways to do it, can you think of different approaches?

Solution:

\u3000 and are the spaces.

/**
 * @param {string} str
 * @return {string}
 */
function trim(str) {
  const n = str.length
  const isWhiteSpace = (char) => char === ' ' || char === '\u3000'
  let i = 0
  while (i < n && isWhiteSpace(str[i])) {
    i += 1
  }
  // i is the first non-empty character.
  let j = n - 1
  while (j >= 0 && isWhiteSpace(str[j])) {
    j -= 1
  }
  // j is the last non-empty character.
  return str.slice(i, j + 1)
}
/**
 * @param {string} str
 * @return {string}
 */
function trim(str) {
  return str.replace(/^[\s\u3000]+|[\s\u3000]+$/g, '')
}

6.compress a string

97.https://bigfrontend.dev/problem/compress-a-string

Given a string, compress the repeating letters with count number

compress('a') // 'a'
compress('aa') // 'a2'
compress('aaa') // 'a3'
compress('aaab') // 'a3b'
compress('aaabb') // 'a3b2'
compress('aaabba') // 'a3b2a'

Solution:

Fixed size sliding window.

function compress(str) {
  const n = str.length
  if (n <= 1) return str
  let count = 1
  let ans = []
  for (let i = 0; i < n - 1; i++) {
    // window i, i + 1
    if (str[i] === str[i + 1]) {
      count += 1
    } else {
      // str[i] ends and str[i + 1] start
      ans.push(str[i])
      if (count > 1) {
        ans.push(String(count))
      }
      count = 1
    }
  }
  ans.push(str[n - 1])
  if (count > 1) {
    ans.push(String(count))
  }
  return ans.join('')
}
/**
 * @param {string} str
 * @return {string}
 */
function compress(str) {
  let ans = ''
  let pre = ''
  let count = 0
  for (const char of str) {
    if (pre === '') {
      pre = char
      count = 1
    } else if (pre === char) {
      count += 1
    } else {
      if (count === 1) {
        ans += pre
      } else {
        ans += `${pre}${count}`
      }
      pre = char
      count = 1
    }
  }
  if (count === 1) {
    ans += pre
  } else {
    ans += `${pre}${count}`
  }
  return ans
}

7.validate an IP address

98.https://bigfrontend.dev/problem/validate-an-ip-address

IPv4

An IPv4 Address is represented in dot-decimal notation, consisting of 4 numbers( called 'octet'), each ranging from 0 to 255, like 1.22.33.255. It has 32 bit, so there are maximum 2^32 = 4,294,967,296 IPv4 addresses.

Leading zeroes are not allowed in IPv4, like 01.01.01.01 is invalid.

IPv6

An IPv6 Address have 128 bits (which is a lot), separated by 8 groups (called 'hexadectet', or 'hextet', not fixed yet). Each group has 16 bits, so could be represented by a hexadecimal string at 4 digits, such as 2001:0db8:85a3:0000:0000:8a2e:0370:7334.

Notice that leading zeroes are allowed in IPv6.

There are other valid format of IPv6 addresses, like Zero compression, but it is beyond the scope here, you can ignore them.

Task

You are given some random string, please write a function if it is valid IPv4 or IPv6 address.

Follow-up

Can you solve it with regular expressions ?

Solution:

/**
 * @param {string} str
 * @return {boolean}
 */
function isValidIPv4(ip) {
  const ipv4Pattern =
    /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/
  return ipv4Pattern.test(ip)
}

function isValidIPv6(ip) {
  const ipv6Pattern = /^([0-9a-fA-F]{1,4})(:[0-9a-fA-F]{1,4}){7}$/
  return ipv6Pattern.test(ip)
}

function isValidIP(ip) {
  return isValidIPv4(ip) || isValidIPv6(ip)
}

8.validate string of parentheses

102.https://bigfrontend.dev/problem/validate-parenthesis

Given a string containing only following characters:

parentheses : ( or ) brackets: [ or ] braces: { or } write a function to determine if they are valid.

By 'valid', it means all should be rightly paired, and with the valid order.

validate('{}[]()')
// true
validate('{[()]}')
// true
validate('{[}]')
// false, they are not in the right order
validate('{}}')
// false, last `}` is not paired with `{`

Follow-up

What is time & space complexity of your approach ? Can you do it better?

Solution:

/**
 * @param {string} str
 * @return {boolean}
 */
function validate(str) {
  const stack = []
  for (let i = 0; i < str.length; i++) {
    const ch = str[i]
    if (['{', '[', '('].includes(ch)) {
      stack.push(ch)
    } else {
      if (stack.length === 0) {
        return false
      }
      const matched = `${stack.pop()}${ch}`
      if (!['()', '[]', '{}'].includes(matched)) {
        return false
      }
    }
  }
  return stack.length === 0
}

9.find the first duplicate character in a string

105.https://bigfrontend.dev/problem/find-the-first-duplicate-character-in-a-string

Given a string which might have duplicate letters, write a function to find the first duplicate.

firstDuplicate('abca')
// 'a'
firstDuplicate('abcdefe')
// 'e'
firstDuplicate('abcdef')
// null

What is the time & space cost of your approach ? Can you do better?

Solution:

/**
 * @param {string} str
 * @return {string | null}
 */
function firstDuplicate(str) {
  const seen = new Set()
  for (const char of str) {
    if (seen.has(char)) {
      return char
    }
    seen.add(char)
  }
  return null
}

10.Count palindromic substrings

111.https://bigfrontend.dev/problem/Count-palindromic-substrings

A palindromic string reads the same backward as forward, such as 'madam'.

Now given a string, count how many substrings it has?

Like 'madam', it has following palindromic strings:

'm'
'a'
'd'
'a'
'm'
'ada'
'madam'

What is the time and space cost of your solution ? Could you improve it ?

Solution:

/**
 * @param {string} str
 * @return {number}
 */
function countPalindromicSubstr(str) {
  const n = str.length
  if (n === 0) return 0
  const memo = new Map()
  let count = 0
  for (let i = 0; i < n; i++) {
    memo.set(`${i},${i}`, true)
    count += 1
  }
  for (let i = 0; i < n - 1; i++) {
    memo.set(`${i},${i + 1}`, str[i] === str[i + 1])
    if (str[i] === str[i + 1]) {
      count += 1
    }
  }
  const helper = (i, j) => {
    const key = `${i},${j}`
    if (memo.has(key)) {
      return memo.get(key)
    }
    const ans = str[i] === str[j] && helper(i + 1, j - 1)
    memo.set(key, ans)
    if (ans) {
      count += 1
    }
    return ans
  }
  for (let i = 0; i < n; i++) {
    for (j = i + 1; j < n; j++) {
      helper(i, j)
    }
  }
  return count
}
function countPalindromicSubstr(s) {
  const n = s.length
  let count = 0
  const dp = Array(n).fill(Array(n).fill(false))
  // const dp = Array.from({ length: n }, () => Array(n).fill(false));

  for (let i = 0; i < n; i++) {
    dp[i][i] = true
    count++
  }

  for (let length = 2; length <= n; length++) {
    for (let i = 0; i <= n - length; i++) {
      const j = i + length - 1
      if (s[i] === s[j]) {
        if (length == 2 || dp[i + 1][j - 1]) {
          dp[i][j] = true
          count++
        }
      }
    }
  }

  return count
}

11.remove duplicate characters in a string

112.https://bigfrontend.dev/problem/remove-duplicate-letters-in-a-string

Given a string, write a function to remove the duplicate characters to make sure that each character only occurs once.

For example

'xyzabcxyzabc'

Each character appears twice, we could make it unique as follows

'xyzabc'
'xyabcz'
'xabcyz'
'abcxyz'
'abxyzc'
.....

Above all substrings subsequences (*) contains unique characters, but you need to return the smallest one in lexicographical order( 'a' -> 'z'), which is 'abcxyz'.

All input only contains valid lowercase alphabets only.

Solution:

function removeDuplicateLetters(s) {
  const charFrequency = {}
  const includedInResult = new Set()
  const result = []

  // Count the frequency of each character in the string
  for (const char of s) {
    if (charFrequency[char]) {
      charFrequency[char]++
    } else {
      charFrequency[char] = 1
    }
  }

  for (const char of s) {
    // Decrease the frequency count for the current character
    charFrequency[char]--

    // If the character is already included in the result, skip it
    if (includedInResult.has(char)) {
      continue
    }

    // Remove characters from the result if the current character is smaller
    // than the last character in the result and the last character can still appear later
    while (
      result.length > 0 &&
      char < result[result.length - 1] &&
      charFrequency[result[result.length - 1]] > 0
    ) {
      includedInResult.delete(result.pop())
    }

    // Add the current character to the result and mark it as included
    result.push(char)
    includedInResult.add(char)
  }

  // Join the result array to form the final string
  return result.join('')
}

const input = 'xyzabcxyzabc'
console.log(removeDuplicateLetters(input)) // Output: 'abcxyz'

12.the angle between hour hand and minute hand of a clock

132.https://bigfrontend.dev/problem/the-angle-between-hour-hand-and-minute-hand-of-a-clock

Given a time string in format HH:mm, please return the angle between hour hand and minute hand.

You should return rounded integer representing the smaller angle in degrees.

angle('12:00')
// 0
angle('23:30')
// 165

Solution:

/**
 * @param {string} time
 * @returns {number}
 */
function angle(time) {
  const [hours, minutes] = time.split(':').map(Number)
  const hoursDegree = ((hours >= 12 ? hours - 12 : hours) / 12) * 360 + (minutes / 60) * 30
  const mintuesDegree = (minutes / 60) * 360
  console.log(`${hoursDegree}-${mintuesDegree}`)
  const angle = Math.abs(hoursDegree - mintuesDegree)
  return Math.round(Math.min(angle, 360 - angle))
}
console.log(angle('12:15'))

13.roman numerals to integer

133.https://bigfrontend.dev/problem/roman-numerals-to-integer

Roman numerals are represented by combinations of following seven symbols, each with a fixed integer value.

Symbol	I	V	X	L	C	D	M
Value	1	5	10	50	100	500	1000

For Standard form, subtractive notation is used, meaning 4 is IV rather than IIII, 9 is IX rather than VIIII. Same rule applies to 40(XL) and 900(CM) .etc.

Simply speaking, the roman numerals in standard form follow these rules.

  1. symbols are listed from highest to lowest, from left to right
  2. from left to right, if the next symbol value is bigger than current one, it means subtracting, otherwise adding.

Please implement romanToInteger(). The input are all valid strings.

romanToInteger('CXXIII')
// 123
romanToInteger('MCMXCIX')
// 1999
romanToInteger('MMMCDXX')
// 3420

Solution:

/**
 * @param {string} str - roman numeral string
 * @returns {number} integer
 */
function romanToInteger(roman) {
  const romanToIntMap = {
    I: 1,
    V: 5,
    X: 10,
    L: 50,
    C: 100,
    D: 500,
    M: 1000,
  }

  let result = 0
  for (let i = 0; i < roman.length; i++) {
    const currentVal = romanToIntMap[roman[i]]
    const nextVal = romanToIntMap[roman[i + 1]]

    if (nextVal > currentVal) {
      result -= currentVal
    } else {
      result += currentVal
    }
  }

  return result
}

14.integer to roman numerals

163.https://bigfrontend.dev/problem/integer-to-roman

Roman numerals are represented by combinations of following seven symbols, each with a fixed integer value.

Symbol	I	V	X	L	C	D	M
Value	1	5	10	50	100	500	1000

For Standard form, subtractive notation is used, meaning 4 is IV rather than IIII, 9 is IX rather than VIIII. Same rule applies to 40(XL) and 900(CM) .etc.

Simply speaking, the roman numerals in standard form follow these rules.

  1. symbols are listed from highest to lowest, from left to right
  2. from left to right, if the next symbol value is bigger than current one, it means subtracting, otherwise adding. Please implement integerToRoman(). The input are all integers within valid range.
integerToRoman(123)
// 'CXXIII'
integerToRoman(1999)
// 'MCMXCIX'
integerToRoman(3420)
// 'MMMCDXX'

Solution:

/**
 * @param {number} integer
 * @returns {string} str - roman numeral string
 */
function integerToRoman(num) {
  // Define a list of tuples with Roman numeral symbols and their corresponding values.
  const romanSymbols = [
    { value: 1000, symbol: 'M' },
    { value: 900, symbol: 'CM' },
    { value: 500, symbol: 'D' },
    { value: 400, symbol: 'CD' },
    { value: 100, symbol: 'C' },
    { value: 90, symbol: 'XC' },
    { value: 50, symbol: 'L' },
    { value: 40, symbol: 'XL' },
    { value: 10, symbol: 'X' },
    { value: 9, symbol: 'IX' },
    { value: 5, symbol: 'V' },
    { value: 4, symbol: 'IV' },
    { value: 1, symbol: 'I' },
  ]

  let result = ''

  // Iterate over the romanSymbols array
  for (let i = 0; i < romanSymbols.length; i++) {
    const { value, symbol } = romanSymbols[i]
    // Subtract the value from num and append the symbol to result until num < value
    while (num >= value) {
      num -= value
      result += symbol
    }
  }

  return result
}

15.implement btoa()

141.https://bigfrontend.dev/problem/implement-btoa

btoa() accepts a binary string and returns a Base64-encoded ASCII string from it. Characters in a binary string are the ASCII character for each byte of the binary data.

Please read Base64 wiki and implement your own btoa().

myBtoa('BFE')
// 'QkZF'
myBtoa('BFE.dev')
// 'QkZFLmRldg=='

note

  1. Please don't use window.btoa() in your code.
  2. The binary string passed to your function are all valid ASCII characters, there will be another problem on the general Base64 encoding/decoding.

Solution:

  1. convert the string to binary format.
  2. Pad 0 behind to make sure the length can be divided by 6.
  3. split the string to 6 bits and convert it to the index and lookup for the base64 chars.
  4. Add "=" to make sure the length of result can be divided by 4.
/**
 * @param {string} str - binary string
 * @returns {string}
 */
function myBtoa(binaryString) {
  const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

  let binary = [...binaryString]
    .map((char) => char.charCodeAt(0).toString(2).padStart(8, '0'))
    .join('')
  // Pad zeros to make sure the length can be divided by 6
  if (binary.length % 6 !== 0) {
    binary += '0'.repeat(6 - (binary.length % 6))
  }
  const n = binary.length
  let base64 = Array(Math.floor(n / 6))
    .fill(0)
    .map((_, index) => binary.slice(index * 6, index * 6 + 6))
    .map((chunk) => base64Chars[parseInt(chunk, 2)])
    .join('')

  // Ensure input length is a multiple of 4
  const padding = base64.length % 4
  if (padding > 0) {
    base64 += '===='.slice(padding)
  }
  return base64
}

16.implement atob()

160.https://bigfrontend.dev/problem/implement-atob

atob() decodes a string of data which has been encoded using Base64 encoding.

Please implement your own myAtob()

myAtob('QkZFLmRldg==') // 'BFE.dev'
myAtob('Q') // Error

Please don't use atob() directly in your code.

Solution:

atob() decodes a string of data which has been encoded using Base64 encoding.

/**
 * @param {string} encoded
 * @return {string}
 */
function myAtob(input) {
  const createBase64Lookup = () => {
    // Base64 characters
    const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    // 26 + 26 + 10 + 2 = 64
    const base64Lookup = Object.fromEntries(
      base64Chars.split('').map((char, index) => [char, index])
    )
    return base64Lookup
  }
  // Ensure input length is a multiple of 4
  if (input.length % 4 !== 0) {
    throw new Error('Invalid base64 string')
  }

  // Remove padding characters
  input = input.replace(/=+$/, '')

  const base64Lookup = createBase64Lookup()
  // Convert each character to its 6-bit binary representation
  const binaryString = [...input]
    .map((char) => base64Lookup[char].toString(2).padStart(6, '0'))
    .join('')

  if (binaryString.length > 0 && binaryString.length < 8) {
    throw new Error()
  }

  // Split the binary string into 8-bit chunks and convert to ASCII characters
  const n = binaryString.length
  return Array(Math.floor(n / 8))
    .fill(0)
    .map((_, index) => binaryString.substring(8 * index, 8 * index + 8))
    .map((byte) => String.fromCharCode(parseInt(byte, 2)))
    .join('')
}

17.most frequently occurring character

145.https://bigfrontend.dev/problem/most-frequently-occurring-character

Given a non-empty string, return the most frequently ocurring character.

If there are multiple characters with same occurrance, return an array of them.

count('abbccc')
// 'c'
count('abbcccddd')

Follow-up: What is the time & space complexity of your approach?

Solution:

/**
 * @param {string} str
 * @returns {string | string[]}
 */
function count(str) {
  const calFreq = (str) => {
    const freq = {}
    for (const char of str) {
      const val = freq[char] || 0
      freq[char] = val + 1
    }
    return freq
  }
  const freq = calFreq(str)
  const maxFreq = Math.max(...Object.values(freq))
  const ans = Object.keys(freq).filter((key) => freq[key] === maxFreq)
  return ans.length === 1 ? ans[0] : ans
}

18.semver compare

157.https://bigfrontend.dev/problem/semver-compare

Please implement a function to compare 2 semver strings.

compare('12.1.0', '12.0.9')
// 1, meaning first one is greater
compare('12.1.0', '12.1.2')
// -1, meaning latter one is greater
compare('5.0.1', '5.0.1')
// 0, meaning they are equal.

Solution:

/**
 * @param {string} v1
 * @param {string} v2
 * @returns 0 | 1 | -1
 */
function compare(v1, v2) {
  const items1 = v1.split('.').map(Number)
  const items2 = v2.split('.').map(Number)
  if (items1.length !== 3 || items2.length !== 3) {
    throw new Error('input is not correct.')
  }
  for (let i = 0; i < 3; i++) {
    if (items1[i] !== items2[i]) {
      return items1[i] < items2[i] ? -1 : 1
    }
  }
  return 0
}

19.remove characters

165.https://bigfrontend.dev/problem/remove-characters

Given a string contaning only a, b and c, remove all b and ac.

removeChars('ab') // 'a'
removeChars('abc') // ''
removeChars('cabbaabcca') // 'caa'

What is the time and space complexity of your approach?

Solution:

/**
 * @param {string} input
 * @returns string
 */
function removeChars(input) {
  let ans = input
  let reducedSize = input.length
  while (reducedSize > 0) {
    const size = ans.length
    ans = ans.replaceAll('b', '').replaceAll('ac', '')
    reducedSize = size - ans.length
  }
  return ans
}

20.validate number string

166.https://bigfrontend.dev/problem/validate-number-string-1

Give a number string, check if it is valid number.

By "valid", we mean if it validates as one of below formats:

  1. integer, such as '0', '-1'
  2. decimal number like '1.0', '-2.335'
  3. exponential notation -12.3e45

Formats such as BigInt, Infinity, NaN, octal and hexadecimal .etc are out of scope, you can treat them as invalid.

Pay attention to the sign + -.

Note

The test cases are not covering all the possible cases, since this is not a problem to test your knowledge against JavaScript language spec.

You should confirm with your interviewer about the scope and those edge cases.

isNaN() seems to be a nice trick, but could you solve without it?

Solution:

function validateNumberString(str) {
  // Define the regular expression pattern for valid numbers
  // const pattern = /^[\+\-]?(\d+)?(\.)?(\d+)?(e[\+\-]?\d+)?$/i;
  const pattern = /^[\+\-]?(\d+(\.\d*)?|\.\d+)(e[\+\-]?\d+)?$/i
  // Test the input string against the pattern
  // [\+\-]?
  // (\d+(\.\d*)?|\.\d+) => \d+(\.\d*)? or \.\d+
  // (e[\+\-]?\d+)? => e, [\+\-]?, \d+
  return pattern.test(str)
}

21.uncompress string

173.https://bigfrontend.dev/problem/uncompress-string

Given a compressed string, return its original form.

For example.

uncompress('3(ab)') // 'ababab'
uncompress('3(ab2(c))') // 'abccabccabcc'
  1. a number k followed by a pair of parenthesis, meaning to repeat the substring inside the parenthesis by k times, k is positive integer.
  2. inputs are guaranteed to be valid input like above example, there is no numerical digit in original form.

Solution:

The basic structure is a structure like ab2(c). The first part is letters part: ab. The second part is a numbers part: 2. The last part is wrapped with () and contains the similar structure.

When a ( is met, we need to push ab and 2 to the stack. and reset curStr and curNum. When a ) is met, we need pop out, curStr and curNum and calculate the result.

/**
 * @param {string} str
 * @returns {string}
 */
function uncompress(s) {
  let stack = []
  let currNum = 0
  let currStr = '' // Track the current string between ( and )

  for (let char of s) {
    if (char === '(') {
      stack.push([currStr, currNum])
      currStr = ''
      currNum = 0
    } else if (char === ')') {
      const [prevStr, num] = stack.pop()
      currStr = prevStr + currStr.repeat(num)
    } else if (Number.isNaN(parseInt(char))) {
      currStr += char
    } else {
      currNum = currNum * 10 + parseInt(char)
    }
  }

  return currStr
}

22.interpolation

149.https://bigfrontend.dev/problem/interpolation

Have you ever added i18n support to your projects?

Take i18next as an example, generally the keys and translations are kept separately, like this JSON below.

{
  "evaluation": "BFE.dev is {{evaluation}}"
}

At places where this key is used, we can then interpolate the string by passing a data object.

t('evaluation', { evaluation: 'fantastic' })
// "BFE.dev is fantastic"

Now, please create a similar t() function which accepts the translation directly.

  1. it supports {{ and }} as delimiters Let's make it clearer and simpler, when a new pair {{ is met, characters until the following }} are treated as the property name.

For all the other cases, they should not be treated as delimiters.

t('BFE.dev is {{{evaluation}', { evaluation: 'fantastic' })
// "BFE.dev is {{{evaluation}"
t('BFE.dev is {{{evaluation}}}', { '{evaluation': 'fantastic' })
// "BFE.dev is fantastic}"
t('BFE.dev is {{evalu ation}}', { 'evalu ation': 'fantastic' })
// "BFE.dev is fantastic"
  1. if no data is passed or no property exists, just leave it empty
t('BFE.dev is {{evaluation}}')
// "BFE.dev is "

Solution:

We can use findIndex to locate the positions of {{ and }}, then extract and translate the key.

/**
 * @param {string} translation
 * @param {any} data
 * @returns {string}
 */
function t(translation, data) {
  let chars = [...translation];
  const n = translation.length;
  const index1 = chars.findIndex((val, index) => val === "{" && index < n - 1 && chars[index + 1] === "{");
  if (index1 === -1) return translation;
  const slicedChars = chars.slice(index1, n);
  const index2 = slicedChars.findIndex((val, index) => val === "}" && index < n - 1 && slicedChars[index + 1] === "}");
  if (index2 === -1) return translation;
  let str1 = translation.slice(0, index1);
  const key = translation.slice(index1 + 2, index1 + index2);
  let str2 = t(translation.slice(index1 + index2 + 2), data);
  if (!data || !data[key]) return `${str1}${str2}`;
  return `${str1}${data[key]}${str2}`;
}

23.uglify CSS class names

153.https://bigfrontend.dev/problem/unique-class-name

If you use css-loader in your webpack project, localIdentName could be used to transform class names, like below:

localIdentName: "[path][name]__[local]--[hash:base64:5]", Or you can create your own function to generate class names by setting getLocalIdent.

Now please create a function to generate unique class names following rules below.

only use alphabets: a to z , A to Z return one unique class name each time function is called the class name sequence should first be in order of length, then in Alphabetical order(lowercase in front). should provide a function to reset the sequence

getUniqueClassName()
// 'a'
getUniqueClassName()
// 'b'
getUniqueClassName()
// 'c'
// skip cases till 'Y'
getUniqueClassName()
// 'Z'
getUniqueClassName()
// 'aa'
getUniqueClassName()
// 'ab'
getUniqueClassName()
// 'ac'
// skip more cases
getUniqueClassName()
// 'ZZ'
getUniqueClassName()
// 'aaa'
getUniqueClassName()
// 'aab'

getUniqueClassName()
// 'aac'
getUniqueClassName.reset()
getUniqueClassName()
// 'a'

Solution:

We can extract the character codes and simulate the process of adding 1.

let cur = 'a';
/**
 * @returns {string}
 */
function getUniqueClassName() {
  const chars = [];
  const ans = cur;

  const n = cur.length;
  let carry = 0;
  for (let i = n - 1; i >= 0; i--) {
    const charCode = cur.charCodeAt(i);
    let newCharCode = charCode + carry;
    if (i === n - 1) {
      newCharCode += 1;
    }
    if (newCharCode - 1 === 'z'.charCodeAt(0)) {
      newCharCode = 'A'.charCodeAt(0);
      carry = 0;
    } else if (newCharCode - 2 === 'z'.charCodeAt(0)) {
      newCharCode = 'B'.charCodeAt(0);
      carry = 0;
    } else if (newCharCode - 1 === 'Z'.charCodeAt(0)) {
      newCharCode = 'a'.charCodeAt(0);
      carry = 1;
    } else if (newCharCode - 2 === 'Z'.charCodeAt(0)) {
      newCharCode = 'b'.charCodeAt(0);
      carry = 1;
    } else {
      carry = 0;
    }
    chars.unshift(String.fromCharCode(newCharCode));
  }
  if (carry === 1) {
    chars.unshift("a");
  }
  cur = chars.join("");
  return ans;
}

getUniqueClassName.reset = function() {
  cur = 'a';
}