wyc
wyc

Reputation: 55273

JavaScript: Arguments with more than one value

This function loops through JavaScript nested arrays (recursively) and replaces the strings inside them:

function replaceRecur(tree, str, newStr) {
  for (var i = 1; i < tree.length; i++) {
    if (Array.isArray(tree[i])) {
      replaceRecur(tree[i], str, newStr)
    } else {
      tree[i] = tree[i].replace(str, newStr)
    }
  }
}

Usage example:

function replaceQuotes(tree, callback) {
  var str1 = /"(?=\b)/g
    , str2 = /"(?!\b)/g
    , newStr1 = '“'
    , newStr2 = '”'

  replaceRecur(tree, str1, newStr1)
  replaceRecur(tree, str2, newStr2)

  callback(null, tree) 
}

How should I modify replaceRecur so I allow two values per argument?

Example:

function replaceQuotes(tree, callback) {
  var str = ['/"(?=\b)/g/', '"(?!\b)/g']
    , newStr = '“ ”' // not sure whether to use arrays or strings
                     // what's more common?

  replaceRecur(tree, str, newStr)

  callback(null, tree) 
}

(The reason is, I don't want to repeat replaceRecur, str, and newStr twice. I want to keep the code DRY.)

EDIT:

Example input (just in case):

[ 'markdown',
  [ 'para', '“a paragraph”' ],
  [ 'hr' ],
  [ 'para', '\'another paragraph\'' ],
  [ 'para', 'test--test' ],
  [ 'para', 'test---test' ],
  [ 'bulletlist',
    [ 'listitem', '“a list item”' ],
    [ 'listitem', '“another list item”' ] ] ]

Upvotes: 1

Views: 125

Answers (6)

georg
georg

Reputation: 214949

If you want to abstract your code a bit more, here's one possible way:

function maprec(x, callback) {
    return x.map ? x.map(function(x) {
        return maprec(x, callback);
    }) : callback(x);
}

function pipe() {
    var fns = arguments;
    return function(x) {
        return [].reduce.call(fns, function(a, f) { return f(a) }, x);
    }
}

// test/demo:

tree = ['foo "bar" !', ['baz', ['ccc "hi" ', 'd']], 'eee', ['"foo?"']];

converted = maprec(tree, pipe(
    function(x) { return x.replace(/"(?=\b)/g, '{') },
    function(x) { return x.replace(/"(?=\B)/g, '}') }
));

document.write("<pre>" + JSON.stringify(converted));

Ok, what we did here? First, we define maprec, a recursive mapper, which is the same as map, but respects nested structures. The second utility, pipe, is the function composer that takes a bunch of functions and returns a new function that applies these functions, in order, to the argument, in way similar to unix pipelines like grep | sort | uniq (hence the name). Note that this is different from the usual compose, which is right-associative. Finally, we use maprec(tree, pipe(replacer1, replacer2)) to do the actual job.

(I use {}'s instead of fancy quotes to make them look more obvious in the console window).

To illustrate the power of pipelining, here's a more advanced example:

fancyQuotes = pipe(
    function(x) { return x.replace(/"(?=\b)/g, '&laquo;') },
    function(x) { return x.replace(/"(?=\B)/g, '&raquo;') }
);

trim = "".trim.call.bind("".trim);

wrap = function(x) { return this.replace(/\$/g, x)};
wrap.in = wrap.bind;

converted = maprec(tree, pipe(
    fancyQuotes,
    trim,
    wrap.in("<p>$</p>")));

Upvotes: 1

Bergi
Bergi

Reputation: 664433

not sure whether to use arrays or strings

Use arrays, you might want to replace multiple things. And you wouldn't alter replaceRecur, it is fine as it is. Rather introduce a new function

function replaceMultipleRecur(tree, strArr, newStrArr) {
    … // (in the simplest occasion a loop over the arrays with calls to replaceRecur)
}
function replaceQuotes(tree) {
    return replaceMultipeRecur([/"(?=\b)/g, /"(?!\b)/g], ['“', '”']);
}

I don't want to repeat replaceRecur, str, and newStr twice

You can simply use your exsting function, by passing a regex that matches all your cases and a replacer callback instead of strings.

function replaceQuotes(tree) {
  replaceRecur(tree, /("\b)|("\B)/g, function(m, l, r) {
      return l ? '“' : '”';
  });
  return tree
}

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074238

Preface: I'm with Bergi re his comment above, just calling a function twice doesn't mean you're repeating yourself. And adding complexity to a simple function to avoid calling it twice isn't necessarily a good idea.


But to your question: You could just add two arguments to the function, ignoring them if not given:

function replaceRecur(tree, str1, newStr1, str2, newStr2) {
  for (var i = 1; i < tree.length; i++) {
    if (Array.isArray(tree[i])) {
      replaceRecur(tree[i], str1, newStr1, str2, newStr2);
    } else {
      tree[i] = tree[i].replace(str1, newStr1);
      if (typeof str2 !== "undefined") {
        tree[i] = tree[i].replace(str2, newStr2);
      }
    }
  }
}

In fact, you can just keep doing this endlessly using a loop.

function replaceRecur(tree, str, newStr/*, str2, newStr2, ...*/) {
  for (var i = 1; i < tree.length; i++) {
    if (Array.isArray(tree[i])) {
      replaceRecur.apply(this, arguments);
    } else {
      for (var arg = 1; arg + 1 < arguments.length; arg += 2) {
        tree[i] = tree[i].replace(arguments[arg], arguments[arg + 1]);
      }
    }
  }
}

Note that on some JavaScript engines using the arguments pseudo-array has a negative impact on performance. On modern engines it doesn't matter at all.

Usage:

replaceRecur(tree, /"(?=\b)/g, '“', /"(?!\b)/g, '”');

Upvotes: 0

peterdotjs
peterdotjs

Reputation: 1556

Unfortunately there is no overloading in JavaScript.

One clean solution would to use replaceRecur but internally check the typeof arguments to determine whether you want to use _replaceRecur2 or _replaceRecur1

function replaceRecur(tree, arg1, arg2) {
  if(typeof(arg1) === "function"){
    _replaceRecur2(tree, arg1);
  } else {
    _replaceRecur1(tree, arg1, arg2);
  }
}

function _replaceRecur1(tree, str, newStr) {
  for (var i = 1; i < tree.length; i++) {
    if (Array.isArray(tree[i])) {
      _replaceRecur1(tree[i], str, newStr)
    } else {
      tree[i] = tree[i].replace(str, newStr)
    }
  }
}

function _replaceQuotes2(tree, callback) {
  var str1 = /"(?=\b)/g
    , str2 = /"(?!\b)/g
    , newStr1 = '“'
    , newStr2 = '”'

  _replaceRecur1(tree, str1, newStr1)
  _replaceRecur1(tree, str2, newStr2)

  callback(null, tree) 
}

Upvotes: 1

Pointy
Pointy

Reputation: 413720

Create a function that performs the recursive traversal of the structure and invokes callbacks for values. Then you can write your replacement functions as callbacks you pass in to that:

function traverse(tree, callback) {
  for (var i = 0; i < tree.length; ++i) {
    if (Array.isArray(tree[i]))
      traverse(tree[i], callback);
    else
      tree[i] = callback(tree[i]);
  }
}

function replaceTwo(tree, s1from, s1to, s2from, s2to) {
  traverse(tree, function(element) {
    return element.replace(s1from, s1to).replace(s2from, s2to);
  });
}

You can now write all sorts of different functions to transform tree contents without having to rewrite the recursion part.

Upvotes: 3

Joe Fitter
Joe Fitter

Reputation: 1309

just alter the else to apply each regex in the array.

else {
    //array is the array of regex's passed
    array.forEach(function(item) {
        tree[i] = tree[i].replace(item, newStr);
    });
}

Upvotes: 0

Related Questions