如何在字符串中计算格式字符的数量?

4

[更新]

我有一个Java字符串,其中可能包含格式字符(那些带有%的字符)。

是否有一种方法可以在调用String.format(String, args..)时计算此字符串所需的参数数量?

例如:

String placeholderString = "the %s jumped over the %s, %d times";
String.format(placeholderString, "cow", "moon", 2);

需要至少3个参数,否则它会抛出MissingFormatArgumentException异常。

String placeholderString = "the %1$s jumped over the %1$s, %2$d%% %n";
String.format(placeholderString, "cow", 2);

需要至少2个参数。

如何以优雅高效的方式计算具有格式字符的字符串所需的最小参数数量?

我需要这样做是因为我想在运行时向方法提供参数。如果参数超过了最小数量,我想将那些“未使用”的参数附加到字符串末尾。您还可以建议其他满足此要求的替代方法。

例如:我可以逐步提供参数,直到不再抛出MissingFormatArgumentException。然后我可以取剩下的参数并追加它们。这解决了我的问题,但我想知道这是否是最好的方法。


你目前考虑了什么?为什么对那些选项不满意?为什么仅仅计算“%”的出现次数是“不够好”的?你想要覆盖哪些“边缘情况”? - Dima
好的,我会更新我的问题。 - Amulya Khare
也许可以这样写? Pattern p = Pattern.compile("[^%]%([+-]?\\d*.?\\d*)?[sdf]"); Matcher m = p.matcher(input); int count = 0; while(m.find()) count++; - Dima
好的,我希望问题现在更清晰了。我提出这个问题并不是为了减少我的工作量。我已经做了足够的研究,只是想找一个有足够知识(关于字符串格式)的人来验证或建议改进。没有其他重复的问题。谢谢 :) - Amulya Khare
我们能否知道您需要这些统计数据的原因(占位符数量可能与格式所需的参数数量不同,例如:System.out.printf("%1$d %1$d %n",42);)。此外,%n 不是变量的占位符,而是操作系统相关的换行符。 - Pshemo
显示剩余2条评论
2个回答

2

这也接受不正确的格式,但格式解析器显然太昂贵(即,工作量太大)-尽管这可能比正则表达式匹配更有效。我不能说这是否优雅-可能输入变化太复杂,无法采用流畅或光滑的方法。

我已经更改了我的第一个解决方案,它仅返回最少数量的参数,因为添加错误类型的参数可能会导致异常:您应该使用与转换代码匹配的类的参数。使用null会导致警告和丑陋的输出。

Pattern pat = Pattern.compile( "(?!<%)%" +
                  "(?:(\\d+)\\$)?" +
                  "([-#+ 0,(]|<)?" +
                  "\\d*" +
                  "(?:\\.\\d+)?" +
                  "(?:[bBhHsScCdoxXeEfgGaAtT]|" +
                  "[tT][HIklMSLNpzZsQBbhAaCYyjmdeRTrDFc])" );

Class<?> classOf( String conv ){
    if( conv.startsWith( "t" ) ){
        return Date.class;
    }
    switch( conv.charAt( 0 ) ){
    case 'b':
        return Boolean.class;
    case 'h': case 'd': case 'o': case 'x':
        return Integer.class;
    case 's':
        return String.class;
    case 'c':
        return Character.class;
    case 'e': case 'f': case 'g': case 'a':
        return Double.class;
    default:
        return Void.class;
    }
}

List<Class<?>> count( String fmt ){
    List<Class<?>> res = new ArrayList<>();
    Matcher m = pat.matcher( fmt );
    while( m.find() ){
        if( m.group(1) != null ){
            String dec = m.group(1);
            int ref = Integer.parseInt( dec );
            if( res.size() < ref ){
                while( res.size() < ref - 1 ){
                    res.add( Void.class );
                }
                res.add( classOf( m.group(3).toLowerCase() )) ;
            } else {
                Class<?> clazz = classOf( m.group(3).toLowerCase() );
                res.set( ref - 1, clazz );
            }
        } else if( m.group(2) != null && "<".equals( m.group(2) ) ){
            // ignore
        } else {
            res.add( classOf( m.group(3).toLowerCase() ));
        }
    }
    return res;
}

测试使用

void demo( String... formats ){
    for( String fmt: formats ){
        List<Class<?>> res = count( fmt );
        System.out.print( fmt + ": " + res.size() + ":" );
        for( Class<?> clazz: res ){
            System.out.print( " " + clazz.getSimpleName() );
        }
        System.out.println();
    }
}

输出:

%d: 1: Integer
%1$d: 1: Integer
the %s jumped over the %s, %d times: 3: String String Integer
the %1$s jumped over the %1$s, %2$d%% %n: 2: String Integer
Duke's Birthday: %1$tm %1$te,%1$tY: 1: Date
Duke's Birthday: %1$tm %<te,%<tY: 1: Date
%4$s %3$s %2$s %1$s %4$s %3$s %2$s %1$s: 4: String String String String
%s %s %<s %<s: 2: String String
%s %s %s %s: 4: String String String String
%2$s %s %<s %s: 4: Void String String String

这是简单版本,只返回计数:

int count( String fmt ){
    Matcher m = pat.matcher( fmt );
    int np = 0;
    int maxref = 0;
    while( m.find() ){
        if( m.group(1) != null ){
            String dec = m.group(1);
            int ref = Integer.parseInt( dec );
            maxref = Math.max( ref, maxref );
        } else if( m.group(2) != null && "<".equals( m.group(2) ) ){
            // ignore
        } else {
            np++;
        }
    }
    return Math.max( np, maxref );
}

这看起来相当有前途。对于无效的格式,接受不正确的答案。我会试一下,测试它并让您知道最新情况。谢谢 :) - Amulya Khare
我已经改进了第一个解决方案。在运行时添加任意参数可能会产生不良影响 - 这应该进行彻底的调查。 - laune

0
如果你只想要计数,你可以使用正则表达式(?<!%)%(?!%)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接