如何将String与字符序列交错

时间:2009-11-05 11:33:44

标签: java string

将java String与给定字符序列交错的最佳方法是什么。交错间隔应该是可以改变的。

示例:

String s = " .... 0000000000000 ..."; // length random
String b = interleave(s, 3, "-");

结果:

... 000-000-000-000-000 ...

另一个例子:

String s = " .... we all we all we all ...";
String b = interleave(s, 7, "rock ");

结果:

... we all rock we all rock we all rock ...

如果字符串长度不是交错距离的倍数,该函数也应该有效。 有什么建议?是否(再次)采用'公地'的方式来做这件事?

5 个答案:

答案 0 :(得分:14)

这是非常简单且相当可读的实现(我在下面的基准测试中将其称为 StringBuilder ):

public static String interleave(String s, int interval, String separator)
{
    StringBuilder sb = new StringBuilder(s);
    for (int pos = (s.length()-1) / interval; pos > 0; pos--)
    {
        sb.insert(pos * interval, separator);
    }
    return sb.toString();
}

如果您关注简单的 StringBuilder 实现的效率,那么这个实现可能会更好地满足您的需求(我在下面的基准测试中将其称为 Arrays ):

public static String interleave(String string, int interval, String separator)
{
    char[] src = string.toCharArray();
    char[] sep = separator.toCharArray();
    int count = (src.length-1)/interval;
    char[] dst = new char[src.length + count * sep.length];
    int srcpos = 0, dstpos = 0;
    for (int i = 0; i < count; i++)
    {
        System.arraycopy(src, srcpos, dst, dstpos, interval);
        srcpos += interval;
        dstpos += interval;
        System.arraycopy(sep, 0, dst, dstpos, sep.length);
        dstpos += sep.length;
    }
    if (dstpos < dst.length)
    {
        System.arraycopy(src, srcpos, dst, dstpos, dst.length - dstpos);
    }
    return String.valueOf(dst);
}

注意:我可能只在J2ME环境下使用这种实现,但在巨大的字符串上它应该更快。虽然可读性很差......

当然,总是有一种RegExp做事的方式,在你爬过编译RegExp本身就不再存在问题的长度之后会出乎意料地快得多(你不能预编译一个RegExp,因为它是在飞行取决于间隔,感谢Rubens Farias指出这一点,不知何故错过了我自己)。所以这里(我在下面的基准测试中称之为 RegExp ):

public static String interleave(String string, int interval, String separator)
{
    return string.replaceAll("(.{"+interval+"})", "$1"+Matcher.quoteReplacement(separator));
}

注意:如果字符串的长度是间隔的倍数,则此实现在末尾插入分隔符(而其他实现则不是)。我不喜欢RegExps,因为它们不可读也不太快。哦,你可以很容易地忘记“quoteReplacement”部分,如果分隔符包含“$ 1”甚至更糟 - 如果它来自用户,那么你就会遇到大麻烦。

基准

此时我做了一些基准测试,所以第一个实现字符串长度100000需要0.002643秒,第二个实现 - 0.000010,第三个 - 0.000071,但一切都取决于字符串长度。

Length    StringBuilder   Arrays       RegExp
  10000     0.000012     0.000001     0.000054
 100000     0.002643     0.000010     0.000071
1000000     0.315413     0.000026     0.000199

这绝不是一个严肃的基准测试,但它仍然显示了所涉算法的趋势和复杂性。

注意:尽管使用这些想法很有趣,但在使用大小小于1M的字符串时,我们仍然在谈论亚秒级改进 。因此,如果你只处理大小达1K的字符串(它将是0ms对0ms),那么你走哪条路并不重要。最重要的是它应该是可读的,直截了当的,并且不需要花太多时间来编写,因为我确信你有更多重要的问题需要解决,除非你正在编写一个通用库供所有人在最奇怪的情况下使用。记住 - 你的时间比CPU时间更有价值。

左对齐和右对齐交错

我将采用 Arrays 实现,因为这似乎最容易改变:

public static String interleave(String string, int interval, String separator, boolean fromRight)
{
    char[] src = string.toCharArray();
    char[] sep = separator.toCharArray();
    int count = (src.length-1)/interval;
    char[] dst = new char[src.length + count * sep.length];
    int srcpos = 0, dstpos = 0;
    if (fromRight)
    {
        srcpos = dstpos = src.length - count * interval;
        if (srcpos > 0) System.arraycopy(src, 0, dst, 0, srcpos);
        if (count > 0)
        {
            System.arraycopy(sep, 0, dst, dstpos, sep.length);
            dstpos += sep.length;
            count--;
        }
    }
    for (int i = 0; i < count; i++)
    {
        System.arraycopy(src, srcpos, dst, dstpos, interval);
        srcpos += interval;
        dstpos += interval;
        System.arraycopy(sep, 0, dst, dstpos, sep.length);
        dstpos += sep.length;
    }
    if (dstpos < dst.length)
    {
        System.arraycopy(src, srcpos, dst, dstpos, dst.length - dstpos);
    }
    return String.valueOf(dst);
}

答案 1 :(得分:1)

我认为这个解决方案非常有效。不涉及数组副本或StringBuilder扩展:

public static String interleave(String input, int interval, String sep) {
    StringBuilder sb = new StringBuilder(input.length() + (((input.length() -1) / interval) * sep.length()));
    char[] array = input.toCharArray();
    for (int i = 0; i < array.length; i += interval) {
        int span = i + interval;
        for (int j = i; j < Math.min(span, array.length); j++) {
            sb.append(array[j]);
        }
        if (span < array.length)
            sb.append(sep);
    }
    return sb.toString();
}

答案 2 :(得分:1)

这是C#,但我确信Java有类似的方法:

public static string interleave(string input, int interval, string separator)
{
    if (String.IsNullOrEmpty(input)     || 
        String.IsNullOrEmpty(separator) || interval <= 0)
        return input;

    int length = input.Length + // original length + added chars - last occur
        ((input.Length / interval) * separator.Length) -
         (input.Length % interval == 0 ? separator.Length : 0);
    return Regex.Replace(      // magic happens here
        input, String.Format("(.{{{0}}})", interval),
        "$1" + separator.Replace("$", "$$")).Substring(0, length);
}

答案 3 :(得分:0)

这是有效和明确的:

public static String interleave(String s, int interval, String separator) {
        StringBuffer b = new StringBuffer();
        int length = s.length();

        for (int start = 0; start < length - 1; start += interval) {
            int end = Math.min(length, start + interval);
            b.append(s.substring(start, end));
            b.append(separator);
        }

        if (length % interval > 0) {
            b.append(s.substring(length - (length % interval)));
        }

        return b.toString();
    }

答案 4 :(得分:0)

使用the prerelease Google Guava libraries

Joiner.on("-").join(Splitter.fixedLength(3).split(inputString));

简短,清晰,富有表现力。喜欢它!