鸟食轩

 Microsoft .NET[C#] MVP 2003
随笔 - 424, 文章 - 233, 评论 - 5417, 引用 - 344
数据加载中……

单扫描的JScript版String.Format方法

    前天写了一个"JScript版的String.Format方法",本来都已经使用遍历法来替换格式化字符串了,结果却使用了RegExp和substr之类的憋脚方法。后来问题男很热心的给出了一个全扫描的方案,更郁闷的是由于自己对测试的认识不足,居然只使用了期望数据来测试代码,搞得bug一大坨

    于是在问题男的建议基础上,自己又作了一些优化,把整个替换操作一次扫描高定。代码如下: 
 // StringHelper.Format('{0}, {2}, {1}', 'abc', 'def', 'ghi');
 //
 return "abc, ghi, def".
 
StringHelper.Format = function(format)
 {
    
if ( arguments.length == 0 )
    {
        
return '';
    }
    
if ( arguments.length == 1 )
    {
        
return String(format);
    }

    
var strOutput = '';
    
for ( var i=0 ; i < format.length-1 ; )
    {
        
if ( format.charAt(i) == '{' && format.charAt(i+1!= '{' )
        {
            
var index = 0, indexStart = i+1;
            
for ( var j=indexStart ; j <= format.length-2 ; ++j )
            {
                
var ch = format.charAt(j);
                
if ( ch < '0|| ch > '9' ) break;
            }
            
if ( j > indexStart )
            {
                
if ( format.charAt(j) == '}' && format.charAt(j+1!= '}' )
                {
                     
for ( var k=j-1 ; k >= indexStart ; k-- )
                     {
                         index 
+= (format.charCodeAt(k)-48)*Math.pow(10, j-1-k);
                     }  
                    
var swapArg = arguments[index+1];
                    strOutput 
+= swapArg;
                    i 
+= j-indexStart+2;
                    
continue;
                }
            }
            strOutput 
+= format.charAt(i);
            i
++;
        }
        
else
        {
            
if ( ( format.charAt(i) == '{' && format.charAt(i+1== '{' )
                
|| ( format.charAt(i) == '}' && format.charAt(i+1== '}' ) )
            {
                i
++
            }
            strOutput 
+= format.charAt(i);
            i
++;
        }
    }
    strOutput 
+= format.substr(i);
    
return strOutput;
 }    

    相对上一版本的改进:
    1、不再使用RegExp和substr|substring;
    2、一次扫描完成所有替换和转义;
    3、修复了对"}}"扫描未做正确处理的bug;
    4、修复了取格式化条目编号可能出错的bug。

    新的测试数据:
 alert(StringHelper.Format('{0}', 'abc'));
 alert(StringHelper.Format('{
0}}{0}, {{2}, {1}}', 'abc', 'def', 'ghi'));    
 alert(StringHelper.Format('{
000}, {{{{2}}}}, {001}', 'abc', 'def', 'ghi'));    
 alert(StringHelper.Format('{{
0}}\r\n2, {2}\r\n, {1}', 'abc', 'def', 'ghi'));    
 alert(StringHelper.Format('{
0}{0}{0}, {0{1}0}, {{{{{2}}}', 'abc', 'def'));

    测试所得结果:
 No.1 alert: abc

 No.2 alert: {0}abc, {2}, {1}


 No.3 alert: abc, {{2}}, def

 No.4 alert: {
0}
                2, ghi
                , def

 No.5 alert: abcabcabc, {0def0}, {{{2}}

    继续征集更优化方案:)

    BTW: 代码中使用了一个JavaScript的Syntax Sugar来减少代码,你看循环:
 for ( var i=0 ; i < format.length-1 ; )
 {
    
if ( ... )
    {
        
// . . .
    }
    
else
    {
        format.charAt(i
+i)
    }
 }

    显然i+1已经溢出了字符串的长度了,不过这时根本不用管它,JavaScript会返回一个undefined,这个值完全不会影响我们的程序逻辑。如果是C#就需要啰里啰唆的去判断i+1是不是小于format.length,否则就Index Out of Range Exception了

posted on 2005-03-10 00:09 birdshome 阅读(3036) 评论(17)  编辑 收藏 所属分类: Jscript&Dhtml开发

评论

#1楼    回复  引用  查看    

测试结果2是不是有错啊,我感觉不应该是这个结果啊
2005-03-10 11:18 | 湘南和也      

#2楼 [楼主]   回复  引用  查看    

忘了说了,"{{"和"}}"是转意符。
2005-03-10 11:27 | birdshome      

#3楼    回复  引用    

这样的算法不好。
regexp还可以有更灵活的用法的~我做过一个for delphi版本的format,你可以改改。
delphi使用%s和%n这样的语法来做替换子。下面这个函数可以做更多的扩展的~
----------------
var _r_strfmt = /%(.)/g; // format()中用于查找匹配字符串
String.prototype.format = function() {
var i=0, args=arguments, len=args.length;
return ((!len) ? this : this.replace(_r_strfmt,
function($0,$1) {
if (i==len) return $0;
switch ($1) {
case 's':
case 'S': return args[i++];
case '%': return $1;
default : return (+$1) ? args[$1] : $0;
}
})
)
}
----------------
2005-03-10 12:04 | Aimingoo [未注册用户]

#4楼    回复  引用    

我是这个意思,呵呵:
StringHelper.Format = function(format)
{
if(arguments.length == 0)
{
return '';
}

if(arguments.length == 1)
{
return String(format);
}

var strOutput = '';
var i;
var iStart = -1;
var iEnd = 0;
var iLen = format.length;
var iIndex = 0;
var cCur;

for(i = 0; i < iLen; i++)
{
cCur = format.charAt(i);

// 2、看当前有无标记,无标记,转3;有标记,转4
if(iStart != -1)
{
// 4、(有标记)扫描当前字符是否为数字,是转8,不是转6
if(cCur < '0' || cCur > '9')
{
// 6、看当前字符是否为“{”,不是则转7,是则修正标记位置,转8
if(cCur != '{')
{
// 7、看当前字符是否为“}”,是且位置和记录的“{”位置差大于1,处理并清标记,否则清标记
if(cCur == '}' && i - iStart > 1 && iIndex < arguments.length - 1)
{
// do it
strOutput += format.slice(iEnd, iStart);
strOutput += arguments[iIndex + 1];
iEnd = i + 1;
iIndex = 0;
}

iStart = -1;
}
else
{
iStart = i;
}
}
else
{
iIndex *= 10;
iIndex += cCur.charCodeAt(0) - 48;
}
}
// 3、(无标记)扫描当前字符是否为“{”,是转5,不是转8
else if(cCur == '{')
{
// 5、置“{”开始标记,并记录位置,转8
iStart = i;
}
}

return strOutput + format.substr(iEnd);
}
2005-03-10 23:35 | 问题男 [未注册用户]

#5楼 [楼主]   回复  引用  查看    

     由于高级语言的条件表达式都提供"短路"特性,所以超前搜索并不会带来实际开销,像:( format.charAt(i) == '{' && format.charAt(i+1) == '{' ) 或 ( format.charAt(i) == '}' && format.charAt(i+1) == '}' )这样的语句,都只有在遇到"{"或"}"的情况下才会执行第二个判断。
     但是这样的语句可以减少本程序中flag的使用复杂度,所以我除了线形递增量i外,就使用了一个flag(indexStart),外加一个回退循环变量j。
 
     PS:你的这种思路和表示方式,有种把我带回了课堂的感觉
2005-03-11 00:41 | birdshome      

#6楼    回复  引用    

1、上面有朋友提到正则式,诚然,在功能扩充方面,正则式的灵活性是硬编码无法比拟的,但首先,转义规则一般不易变,二来Format这样的函数常常是对付些小串且调用频繁,这种情况下,正则式则显得大材小用了。“审时度势”讲的就是这个道理,呵呵

@食轩兄:
2、突然发现可能我误解了你定义的转义规则的含义了,我是这么以为的:format中的{x}将被第x + 1个参数替代,除非第x + 1个参数不存在,则不替换。那么
Format('{0}}{0}, {{2}, {1}}', 'abc', 'def', 'ghi')结果应该是"abc}abc, {ghi, def}"而不是"{0}abc, {2}, {1}"
Format('{0}{0}{0}, {0{1}0}, {{{{{2}}}', 'abc', 'def')结果应该是"abcabcabc, {0def0}, {{{{{2}}}"而不是"abcabcabc, {0def0}, {{{2}}"吧
不知还有其他什么规则没有,是不是有其他escape sequence

3、鄙人也离开学校多年了,本职是使用c/c++,擅长c/c++和asm,为了开阔视野,阅读其他方面的技术文章和源码,诸如java、python、pascal、basic、c#、php、perl、js等也比较熟悉,谈不上精通,来看看.net、web技术纯属业余,可能思维总带着c/c++的惯性,望见谅,呵呵。随着实践经验的增长,我越发觉得书本知识的重要,理论的东西是根,没有足够的功力很难体会其中的奥妙,很难感觉到其实万变不离其本

4、你的算法最坏情况下,在处理类似"{{{{{{{{{{"这样的串时,得扫描近两遍,纵然有“短路”也未能幸免,虽不会总是最坏情况,但“平均扫一遍多”应该还是有的。既然选择硬编码,那么状态机就是词法分析的最好选择,呵呵

拙见,欢迎拍砖
2005-03-11 15:09 | 问题男 [未注册用户]

#7楼    回复  引用    

版主,有兴趣可以到我的网站上看看
没想到自己也会如此做广告:(
http://www.softlaputa.com.cn

刚刚成立的网站,我把网络上见到的软件供需的咚咚都放上去
如果版主对接一些项目有兴趣的话,可以去看看咯
2005-03-12 20:41 | sandycs [未注册用户]

#8楼    回复  引用    

哈哈,今天工作时正好写了一个,使用方法和C#了的一样。

function Format()
{
var numargs = arguments.length;
if (numargs < 1) return;
if (numargs < 2) return arguments[0];
var str = arguments[0];
for (i=1;i<numargs;i++)
{
var tmpStr = "re = /\\{" + (i-1) + "\\}/g";
eval(tmpStr);
str = str.replace(re, arguments[i]);
}
return str;
}
//---------------------------------------------------
// 使用方法
//-------------------------------------------------
var a = "中华{1}共和国-{0}-今天{2}了";
var b = Format(a,"中央人民政府","人民","成立");
alert(b);

2005-12-14 23:17 | yhxiang49 [未注册用户]

#9楼    回复  引用    

来段微软的写法(C#反编译mscore.dll后<b>改写</b>为JS的):

/// 空白字符集
String.WhitespaceChars = [ ' ', '\f', '\n', '\r', '\t', '\v' ];

/// <type>修整方式[0:头,1:尾,2:两者]</type>
String.Trim = function( str, type, chars ){
chars = chars || String.WhitespaceChars;
type = type == null ? 2 : type;

if( typeof chars == "string" )
{
chars = chars.ToCharArray();
}
else if ( !(chars instanceof Array) )
{
throw new Error( 1, "参数错误: [chars] 类型不匹配." );
}

var num1 = str.length - 1;
var num2 = 0;
if (type != 1)
{
for (num2 = 0; num2 < str.length; num2++)
{
var num3 = 0;
var ch1 = str.charAt(num2);
while (num3 < chars.length)
{
if (chars[num3] == ch1)
{
break;
}
num3++;
}
if (num3 == chars.length)
{
break;
}
}
}
if (type != 0)
{
for (num1 = str.length - 1; num1 >= num2; num1--)
{
var num4 = 0;
var ch2 = str.charAt(num1);
num4 = 0;
while (num4 < chars.length)
{
if (chars[num4] == ch2)
{
break;
}
num4++;
}
if (num4 == chars.length)
{
break;
}
}
}
var num5 = (num1 - num2) + 1;
if (num5 == str.length)
{
return str;
}
if (num5 == 0)
{
return "";
}
return str.substr(num2, num5);
};
2006-04-24 16:22 | 黄宗银 [未注册用户]

#10楼    回复  引用    

忘记一个东西:

String.prototype.ToCharArray = function( start, length ){
start = start || 0;
length = length || this.length;

var end = start + length;
end = end < this.length ? end : this.length;

var ary = [];
for( var i = start; i < end; i++ )
{
ary.push( this.charAt(i) );
}
return ary;
};
2006-04-24 16:31 | 黄宗银 [未注册用户]

#11楼    回复  引用    

这样的扫描对于c#适用,对于js就太慢了。js应该充分利用正则替换。
See: http://community.csdn.net/Expert/TopicView3.asp?id=5188073
2006-11-28 09:24 | hax [未注册用户]

#12楼 [楼主]   回复  引用  查看    

@hax
你那个format写的挺有意思,不过好像不能处理$n后面是数字的情况,比如:StringFormat("{0}1", "a"),的结果为:a1。而你的那个format得不到a1这个结果,因为整个$11被看成了俘获引用。
2006-11-28 18:00 | birdshome      

#13楼    回复  引用    

如果你需要$1后面跟一个1,可以写成 '$011'.format('a') 返回就是a1。
2006-11-30 13:02 | hax [未注册用户]

#14楼    回复  引用    

还有另一个在重复使用的时候更快(通过把pattern“编译”成函数后缓存),但是会消耗内存的版本:

http://blog.csdn.net/hax/archive/2006/11/30/1421834.aspx
2006-11-30 13:04 | hax [未注册用户]

#15楼    回复  引用    

BTW, it's trivial to support syntax {n} instead of $n, if u like :)
2006-11-30 13:34 | hax [未注册用户]

#16楼    回复  引用    

String.format = function(){
if(arguments.length == 0)
return null;
var str = arguments[0];
for(var i=1;i<arguments.length;i++){
var re = new RegExp('\\{(0)*' + (i-1) + '\\}','gm');
str = str.replace(re, arguments[i]);
}
return str;
}
document.write(String.format('aa {0} {1}1 {00} {0001} {{{{{{00} {0001000}} {{020}} {{02}}.', "bb", "tt", "ggg"));
2008-04-03 16:39 | fangle [未注册用户]

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-03-10 02:58 编辑过


相关链接: