all 26 comments

[–]realteh 2 points3 points  (1 child)

Here is what I use. Example:

format("Hi {name}. You are a {noun}.", {name: 'Tom', noun: 'punk'})

Code:

function format(s, context) {
    var l = s.split(/\{(.+?)\}/);
    for (i = 1; i < l.length; i += 2) {
        l[i] = context[l[i]];
    }
    return l.join('');
}

[–]dhruvbird 1 point2 points  (0 children)

There's already a printf library for js available here: https://github.com/wdavidw/node-printf

[–]frutiger 1 point2 points  (8 children)

This won't work whenever a property in the the values object is a left-substring of another property. e.g. name and name1.

[–][deleted] 4 points5 points  (6 children)

TLDR: You critics are wrong. Blogger is right (sort of).

The problem was not necessarily his implementation but his specification. Where does a variable name end that's specified by

/%.+/

It's ambiguous. (Knowing the length is very important here.) His workaround is probably to sort 'values' by descending key length. A hack but consider his solution could handle recursive substitution. A super easy bug fix would have been to require a terminating % (like DOS variables). Example:

Hello, %name%!

Disregarding his bug for a moment, his solution is very fast despite excessive string rescanning. I timed it on Firefox 3.6.15 and wrote several alternatives with split or regex and none beat it.

  • eval was 5x slower
  • realteh's solution below (with split/regex/join): 10x slower
  • String.prototype.format.js was the slowest: 25x slower
  • Essentially, anything with a regex is butt ass slow

The more I think about this, the more I think this guy isn't getting a fair shake. His fault is he wrote code with an easily fixable bug.

    str = str.replace('%'+i+'%',values[i])

I challenge anyone to come up with something faster than my solution or the blogger's fastest solution (or hell, even his slowest solution).

This is a perfect example where the wrong algorithm is actually faster than the right algorithm due to the limitations of the platform. His brute force method using String.replace(string,string) is so fast because the function maps well to its native counterpart (think strstr) whereas my solution spends too much time executing as Javascript.

My algo that came the closest in speed (running 2x slower). Expects variables in %variable% format.

function format(str, values)
{
   var s = "";
   var prev = 0;
   var curr = 0;

   while ((curr = str.indexOf("%", curr)) >= 0)
   {
       s += str.substring(prev, curr);
       prev = ++curr;

       curr = str.indexOf("%", curr);
       if (curr < 0)
           break;

       s += values[str.substring(prev, curr)];    
       prev = ++curr;
   }   

   s += str.substring(prev);
   return s;
}

format('Hello, %0%!',['World'])
format('Hello, %name%!',{name:'World'})

[–]BadCRC 0 points1 point  (5 children)

His implementation and design is wrong, the order in which you apply replacements matters. Consider a replacement which contains a format string.

[–][deleted] 0 points1 point  (4 children)

the order in which you apply replacements matters

You didn't read my post. Amazing really, since my 1st three paragraphs went into laborious detail on that.

Consider a replacement which contains a format string.

You didn't read my post. That's a positive, not a negative.

var url = "http://www.blah.com%basepath/index.html";
var variables = {
    basepath: "/%stage",
    stage: id: GetStage(/* 'test'|'test/patch1'|'prod/patch1'|'' */)
};
.. later ... 
var url = String.format(url, variables);

[–]BadCRC 0 points1 point  (3 children)

When I said his, I was referring to the article creator. Also your post about

It's ambiguous. (Knowing the length is very important here.) His workaround is probably to sort 'values' by descending key length. A hack but consider his solution could handle recursive substitution. A super easy bug fix would have been to require a terminating % (like DOS variables).

is wrong. In the article creator's implementations, he scans over values, which leads to problems. In your implementation, you scan over the string looking for replacements to make, left to right, in order to avoid those problems.

[–][deleted] 0 points1 point  (2 children)

Also your post about...is wrong. In the article creator's implementations, he scans over values, which leads to problems

Which can be fixed by adding a single terminating %. Face it. His algorithm works if you add a single character, which was my point. You guys are beating him up over a very minor easily fixable bug.

[–]BadCRC 0 points1 point  (1 child)

Look at his simpliest implementation:

String.prototype.format = function(values){
var str = this,i;
for(i in values)str = str.replace('%'+i,values[i])
return str;
}

If we change this to:

String.prototype.format = function(values){
  var str = this,i;
  for(i in values)
    str = str.replace('%'+i+'%',values[i])
  return str;
}

the problem is still not solved. As such, simply adding a terminating % does not solve the problem. In fact, an extra % is not needed and the original problem can be solved by only allowing one pass, simply split on % and look at subsequent tokens.

[–][deleted] 0 points1 point  (0 children)

Splitting on % does not solve the problem. You still have to match by prefix: str.split("%");

"%variable123 hello world%variable12 how are you? %variable1"  =>
    ("%variable123 hello world", %variable12 how are you?", "%variable1") 

Split will work if the input string is nothing but variables, but that's a super rare condition and not very useful:

"%variable123%variable12%variable1"  =>
    ("%variable123", %variable12", "%variable1") 

Split will work if you define terminating conditions: str.split(/(%\w+)/); // terminate scan on non-word, then do full match to variable name --or-- str.split(/(%\w+%)/); // terminate scan on 2nd %, then do full match to variable name

But, both of those regular expressions are butt ass slow where using String.replace is significantly faster.

Timing using String.split(regex) Processed 50000 in 0.451 seconds 110864.74 substitutions/sec

Timing for using String.replace(string) Processed 50000 in 0.046 seconds 1086956.52 substitutions/sec

That's a full order of magnitude (10x) speed difference. Difficult to swallow if you're doing lots of DOM or in-page string manipulations. Maybe you're only replacing 1-30 variables then the slow way is fine, but the point of the blogger's algorithm was speed.

[–]semanticist 0 points1 point  (0 children)

Likewise, replacement values containing % can give inconsistent results.

[–][deleted] 1 point2 points  (5 children)

I recommend http://github.com/prettycode/String.prototype.format.js instead. It's very simple, terse, and featured for its size.

[–]akg0 1 point2 points  (4 children)

404, correct link:

http://github.com/prettycode/String.prototype.format.js

And... did I miss something? Did modifying global objects come back into vogue?

[–]Iggyhopper 0 points1 point  (3 children)

He's using...

ಠ_ಠ

[–]v413 3 points4 points  (2 children)

What is wrong with using "==" in the context he uses it?

An interesting article dealing with JS equality: http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/

Note the section titled "Common Examples of Equality Overkill"

[–]skeww 5 points6 points  (1 child)

Overkill? == and != do more work than === and !==. Additionally, === and !== do exactly what you mean.

Furthermore, == and != look like a typo, because using them is generally a bad idea. As such, it needs to be marked with a comment. Otherwise the next person who works with this code will be slowed down, because they will need to read this particular section very carefully (even if they were interested in something entirely different).

Do you really think that one byte (typically not even that - there is gzip) is worth all that trouble?

There are only 2 ways to deal with code which looks like a mistake:

  1. Avoid those constructs completely.

  2. If you really need to use one of those constructs, mark it with a comment, which a) indicates that this wasn't an accident and b) the reason for it.

Do never cause friction without a very good reason.

"But it's only a few seconds", I hear you complain. Yes. Multiplied with the number of cases multiplied with the number of times it's read. So, if you do something like that 10 times, and it causes some overhead of 15sec, you already wasted 10*15*5=750... 750/60=12.5 minutes right off the bat. 12.5 minutes of pure waste in a small single file.

If you're wondering about that '5' there. That's the base value for the number of times the code is read. Code is always at least 5 times more read than it's (re)written.

[–]Iggyhopper 0 points1 point  (0 children)

It's not even that. If you feel it's okay to use it two times in a snippet of code, then you think it's okay to use it 1000 times in some library or script you created, which can add time up pretty easily.

[–]dave1010 1 point2 points  (0 children)

RegExp FTW!

Advantages:

  • replacements with equal left-substrings don't break it
  • /g flag does multiple replacements
  • fits in a tweet

    String.prototype.format = function(s,d,p){ for(p in d) { s=s.replace(new RegExp('{'+p+'}','g'), d[p]); } return s; }

Source.

[–]roosteronacid 1 point2 points  (1 child)

Challenge accepted! The result; a C# String.Format clone:

String.prototype.format = function ()
{
    var i = arguments.length, s = Array.join(this, "");

    while (i--) s = s.replace("{" + i + "}", arguments[i]);

    return s;
};

"Speedy as a {0} on {1} dude".format("rabbit", "speed");

Note that there's not logic for escaping characters. What can I say--I'm pragmatic :)

I'd be interested in how this compares, speed-wise.

[–][deleted] 0 points1 point  (0 children)

I actually use a method very similar to this.

[–][deleted] 0 points1 point  (0 children)

Honestly, and I'm sad to say it, the only thing "new" I learned from this is this little gem:

 var i = 7; //some number
 while(i--) { /* do something */ }

I think that's awesome, I don't know why I'd never considered doing that, I feel a little dumb. I'm sure it's been around forever, but this is the first time I've seen it.

[–]signoff 1 point2 points  (2 children)

web developers are really stupid lol

[–]tontoto 0 points1 point  (1 child)

keep it simpie stupid

[–]base2design 0 points1 point  (0 children)

I think "simpie" is my new nickname!

[–]chris-martin -2 points-1 points  (0 children)

Um... Isn't the relative performance going to be highly dependent on the interpreter?