8
\$\begingroup\$

I have these strings:

)hello(
this has ]some text[
flip }any{ brackets
even with )))]multiple[((( brackets

As you can see, the brackets are all in the wrong direction.

I have a function called flipBracketsDirection() for each string. Here is an example of input and output of the above strings:

flipBracketsDirection(')hello(');
// should return:  (hello)

flipBracketsDirection('this has ]some text[');
// should return:  this has [some text]

flipBracketsDirection('flip }any{ brackets');
// should return:  flip {any} brackets

flipBracketsDirection('even with )))]multiple[((( brackets');
// should return:  even with ((([multiple]))) brackets

Note: The direction is flipped at all times. So this is fine too:

flipBracketsDirection('flip (it) anyway');
// should return:  flip )it( anyway

Here is my solution.

function flipBracketsDirection(str: string) {
  return str
    // flip () brackets
    .replace(/\(/g, 'tempBracket').replace(/\)/g, '(').replace(/tempBracket/g, ')')

    // flip [] brackets
    .replace(/\[/g, 'tempBracket').replace(/\]/g, '[').replace(/tempBracket/g, ']')

    // flip {} brackets
    .replace(/\{/g, 'tempBracket').replace(/\}/g, '{').replace(/tempBracket/g, '}')
    ;
}

Is this the best way to create this function?

\$\endgroup\$
18
\$\begingroup\$

Your function seems to work fine, but you can condense it a bit by search for all bracket types in one regex replace with the following pattern:

/[\(\)\[\]\{\}]/g

and then use the replace function that takes a replace function as argument:

const brs = "()({}{[][";
function flipBracketsDirection(str) {
  return str.replace(/[\(\)\[\]\{\}]/g, br => brs[brs.indexOf(br) + 1]);
}

brs holds all the replaceable brackets with the opening brackets twice, so brs[brs.indexOf(')') + 1] finds '(' as the next char in brs;

You could also let brs be an object like:

const brs =
{
  "(": ')',
  ")": '(',
  "{": '}',
  "}": '{',
  "[": ']',
  "]": '[',
};

and then cquery it as:

function flipBracketsDirection(str) {
  return str.replace(/[\(\)\[\]\{\}]/g, br => brs[br]);
}

Your version actually query the string nine times, where the above only iterates it once.

As an alternative to the 'sophisticated' brs dictionary-solution, you could just create a function with a switch statement:

function swapBracket(br) {
  switch (br) {
    case '(': return ')';
    case ')': return '(';
    case '{': return '}';
    case '}': return '{';
    case '[': return ']';
    case ']': return '[';
  }
}

And call that instead, this way:

function flipBracketsDirection(str: string) {
  return str.replace(/[\(\)\[\]\{\}]/g, swapBracket);
}

flipBracketsDirection('}hello{');
// will return {hello}

Your tests cases could also be simplified, so it is easier to maintain - for instance like:

  let strs = [
    ')hello(',
    'this has ]some text[',
    'flip }any{ brackets',
    'even with )))]multiple[((( brackets'
  ];

  for (let s of strs) {
    let t = flipBracketsDirection(s);
    console.log(t);
    console.log(flipBracketsDirection(t));
    console.log("");
  }

Both the above suggestions should conform to the DRY principle.

| improve this answer | |
\$\endgroup\$
  • 3
    \$\begingroup\$ What's you're opinion on brs = '(){}[]', brs[brs.indexOf(br) ^ 1]? \$\endgroup\$ – Peilonrayz Jun 17 at 13:16
  • 3
    \$\begingroup\$ Too clever for me @Peilonrayz. Sure, it works, but coming back to that in a month..? \$\endgroup\$ – Gerrit0 Jun 17 at 13:20
  • 3
    \$\begingroup\$ @Gerrit0 I've never heard anyone say xor is 'too clever' before. \$\endgroup\$ – Peilonrayz Jun 17 at 13:23
  • 1
    \$\begingroup\$ @Elron bitwise XOR \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jun 17 at 23:22
  • 1
    \$\begingroup\$ @Peilonrayz: For what it's worth, I find your version much clearer, and less error-prone. The next dev might not notice that the brackets need to be written 3 times otherwise, and would simply add brs = "<>()({}{[][". \$\endgroup\$ – Eric Duminil Jun 18 at 18:28
8
\$\begingroup\$

It may seem unlikely but it is not impossible for an input string to contain tempBracket so a solution that doesn't involve adding and replacing that string would be ideal.

function flipBracketsDirection(str) {
  return str
    // flip () brackets
    .replace(/\(/g, 'tempBracket').replace(/\)/g, '(').replace(/tempBracket/g, ')')

    // flip [] brackets
    .replace(/\[/g, 'tempBracket').replace(/\]/g, '[').replace(/tempBracket/g, ']')

    // flip {} brackets
    .replace(/\{/g, 'tempBracket').replace(/\}/g, '{').replace(/tempBracket/g, '}')
    ;
}
console.log(flipBracketsDirection(')will tempBracket get replaced?('))

The answer by @Henrik already suggests using a single regular expression to replace any characters in the group of brackets.

A mapping of characters seems an ideal solution in terms of performance. The mapping can be frozen using Object.freeze() to avoid alteration.

const BRACKET_MAPPING = Object.freeze({
  "(": ')',
  ")": '(',
  "{": '}',
  "}": '{',
  "[": ']',
  "]": '[',
});
const mapBracket = (bracket: string) => BRACKET_MAPPING[bracket];
const flipBracketsDirection = (str: string) => str.replace(/[\(\)\[\]\{\}]/g, mapBracket);
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ This looks clear and concise. Would it be possible to generate the regex from the keys of BRACKET_MAPPING? \$\endgroup\$ – Eric Duminil Jun 18 at 18:27
  • \$\begingroup\$ yes- likely with the Regexp constructor \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jun 18 at 18:32
5
\$\begingroup\$

This is an answer that I posted to the original question on StackOverflow. It does away with regular expressions entirely, and uses a similar case-statement to the one suggested in @Henrik's answer

The original code performs 6 regular expression substitutions (which require 1 pass each), and fails on strings that contain the text tempBracket (as noted by @Kaiido in comments to the StackOverflow question).

This should be quicker because it makes a single pass, and requires no regular expressions at all. If all characters are ASCII, the flip function could be rewritten to use a look-up table, which would make it branch-free and potentially even faster.

function flipBracketsDirection(str) {
  function flip(c) {
    switch (c) {
      case '(': return ')';
      case ')': return '(';
      case '[': return ']';
      case ']': return '[';
      case '{': return '}';
      case '}': return '{';
      default: return c;
    }
  }
  return Array.from(str).map(c => flip(c)).join('');
}    

// testcases
let test = (x) => console.log(flipBracketsDirection(x));
test('flip (it) anyway');
test(')hello(');
test('this has ]some text[');
test('flip }any{ brackets');
test('even with )))]multiple[((( brackets');

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Can you also post this in a new post, with the tag rags-to-riches? I have some thoughts 😁. \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jun 18 at 13:56
  • \$\begingroup\$ @SᴀᴍOnᴇᴌᴀ you hereby have my permission to post my answer as a question to then answer it with your answer :-). I'll be happy to look at it (answer the comment so that I know to look). \$\endgroup\$ – tucuxi Jun 18 at 14:20
  • \$\begingroup\$ I wouldn't really be able to (morally) post your code in a question because I wouldn't be able to answer "yes" to all the questions in the What topics can I ask about here? page... (hint/protip: we can both gain more rep that way) \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jun 18 at 14:28
  • 2
    \$\begingroup\$ How bad is the performance hit of converting from string to array and back again, compared to regular expression replacement? I may be mistaken, but it seems like this would be more expensive than a simple regex \$\endgroup\$ – Charlie Harding Jun 19 at 0:40
  • 3
    \$\begingroup\$ It seems like the regex wins out by 5-10% (jsperf.com/reverse-brackets) \$\endgroup\$ – Charlie Harding Jun 19 at 0:59
1
\$\begingroup\$

One for the fun of it

const flipBrackets = BracketFlipper();
[ ')hello(', 
'this has ]some text[',
'flip }any{ brackets',
'even with )))]multiple[((( brackets',
'flip (it) anyway',
'>Pointy stuff<', 
'/slashed\\'].forEach(s => console.log(flipBrackets(s)));;

function BracketFlipper() {
  const bracks = "(),{},[],<>,\\\/".split(",");
  const brackets = [
    ...bracks, 
    ...bracks.reverse()
      .map(v => [...v].reverse().join("")) ]
    .reduce( (a, v) => ({...a, [v[0]]: v[1] }), {} );
  const re = new RegExp( `[${Object.keys(brackets).map(v => `\\${v}`).join("")}]`, "g" );
  return str => str.replace(re, a => brackets[a]);
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.