将一个字符串/数字按每N个字符/数字拆分?

77

我需要将一个数字分成均等的部分,例如:

32427237 需要变成 324 272 37
103092501 需要变成 103 092 501

如何分割它并处理奇数情况,如分割成这些部分,例如: 123 456 789 0?


1
你想把字符串分成三个独立的字符串,还是想要插入空格? - Dirk Vollmar
2
将字符串拆分为等长的子字符串 (Java)所有答案都是用Java编写的,但你可以很容易地将其移植到C#中。 - Emil
它们将是三个独立的字符串。 - RoguePlanetoid
17个回答

139

如果您需要在代码中的多个位置执行此操作,则可以创建一个漂亮的扩展方法:

static class StringExtensions {

  public static IEnumerable<String> SplitInParts(this String s, Int32 partLength) {
    if (s == null)
      throw new ArgumentNullException(nameof(s));
    if (partLength <= 0)
      throw new ArgumentException("Part length has to be positive.", nameof(partLength));

    for (var i = 0; i < s.Length; i += partLength)
      yield return s.Substring(i, Math.Min(partLength, s.Length - i));
  }

}

然后您可以像这样使用它:

var parts = "32427237".SplitInParts(3);
Console.WriteLine(String.Join(" ", parts));

最终输出为324 272 37,与期望相符。

当您将字符串分成部分时,即使这些子字符串已经存在于原始字符串中,也会分配新的字符串。通常情况下,您不必过于担心这些分配,但是使用现代 C#,您可以通过稍微修改扩展方法来使用“范围”来避免这种情况:

public static IEnumerable<ReadOnlyMemory<char>> SplitInParts(this String s, Int32 partLength)
{
    if (s == null)
        throw new ArgumentNullException(nameof(s));
    if (partLength <= 0)
        throw new ArgumentException("Part length has to be positive.", nameof(partLength));

    for (var i = 0; i < s.Length; i += partLength)
        yield return s.AsMemory().Slice(i, Math.Min(partLength, s.Length - i));
}

返回结果:
返回类型已更改为public static IEnumerable<ReadOnlyMemory<char>>,并通过在源上调用Slice创建子字符串,从而避免了分配。
请注意,如果您在某个时刻必须将ReadOnlyMemory<char>转换为用于API中的string,则必须分配新的字符串。幸运的是,许多.NET Core API除了string之外还使用ReadOnlyMemory<char>,因此可以避免分配。

2
在我看来,在这里使用扩展方法有些过头了。需要拆分的“数字”应该是一种特定类型的事物,例如订单号。如果你可以将扩展方法限制只适用于订单号,则可以接受。但现在你可以将其应用于任何存在于字符串中的实体,这会破坏封装性。 - Binary Worrier
实际上,我自己想到了一种方法,可以将字符串分割成任意大小的部分 - 我会尝试这个! - RoguePlanetoid
2
关于扩展方法的一点提醒,来自编程指南: "总的来说,我们建议您谨慎使用扩展方法,只在必要的情况下使用。" 对不起,我只是觉得扩展方法被滥用了。 - steinar
2
如果您不想使用扩展方法,请删除this关键字并适当重命名类和方法。我认为这个特定的方法非常通用(而不是特定于订单号之类的内容),是扩展方法的一个很好的候选。将类放在单独的命名空间中使开发人员能够决定是否启用扩展方法,就像添加using System.Linq一样,可以向IEnumerable<T>添加一堆扩展方法。 - Martin Liversage
有人能告诉我是否有理由使用大写字母S的String吗?或者我可以只使用普通的string吗?谢谢。 - Jhonnatan Eduardo
@JhonnatanEduardo: C#中的String和string有什么区别? System.String是类型名称,而string是C#的别名。个人认为,在创建新标识符时,原始类型的正确类型名称效果更好。它是Object.ToString()而不是Object.Tostring(),以及Convert.ToInt64()而不是Convert.Tolong()。然而,大多数C#开发人员使用别名。 - Martin Liversage

10
您可以使用简单的for循环在每个第n个位置插入空格:
string input = "12345678";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < input.Length; i++)
{
    if (i % 3 == 0)
        sb.Append(' ');
    sb.Append(input[i]);
}
string formatted = sb.ToString();

这与我最初要创建的类似,但很有兴趣看到其他选择。 - RoguePlanetoid
1
此解决方案在第一个字符之前插入一个空格。 - Joan Charmant

7

有一种非常简单的方法可以做到这一点(虽然不是最有效的,但也不比最有效的慢得多)。

    public static List<string> GetChunks(string value, int chunkSize)
    {
        List<string> triplets = new List<string>();
        while (value.Length > chunkSize)
        {
            triplets.Add(value.Substring(0, chunkSize));
            value = value.Substring(chunkSize);
        }
        if (value != "")
            triplets.Add(value);
        return triplets;
    }

这是一个备选方案。
    public static List<string> GetChunkss(string value, int chunkSize)
    {
        List<string> triplets = new List<string>();
        for(int i = 0; i < value.Length; i += chunkSize)
            if(i + chunkSize > value.Length)
                triplets.Add(value.Substring(i));
            else
                triplets.Add(value.Substring(i, chunkSize));

        return triplets;
    }

一个通用的 n 版本会更好。 - st0le
这两种方法都会生成错误的输出,例如String.Join(", ", GetChunks("20000", 3).ToArray())将生成200, 00,这对于货币是不正确的(应该是20, 000),拆分应该从末尾开始。另外请注意,问题本身要求以这种方式拆分数字,这也是货币拆分不正确的原因。 - AaA

7

LINQ规则:

var input = "1234567890";
var partSize = 3;

var output = input.ToCharArray()
    .BufferWithCount(partSize)
    .Select(c => new String(c.ToArray()));

更新:

string input = "1234567890";
double partSize = 3;
int k = 0;
var output = input
    .ToLookup(c => Math.Floor(k++ / partSize))
    .Select(e => new String(e.ToArray()));

2
System.Linq.EnumerableEx.BufferWithCount 是 .NET (Rx) 反应式扩展的一部分:http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx。您需要下载并安装此扩展程序,以使代码运行。 - Martin Liversage
1
谢谢,我之前没有见过 BufferWithCount - 现在我知道了为什么 - 我已经安装了 Rx,所以可能对其他事情有用。 - RoguePlanetoid

7
这已经晚了五年,但是:
int n = 3;
string originalString = "32427237";
string splitString = string.Join(string.Empty,originalString.Select((x, i) => i > 0 && i % n == 0 ? string.Format(" {0}", x) : x.ToString()));

3
没来得及回答,但现在也不晚了。自从那时以来,我一直在想是否有一个LINQ解决方案,现在终于找到了! - RoguePlanetoid

5

如果您知道整个字符串的长度恰好可以被部分大小整除,则使用以下方法:

var whole = "32427237!";
var partSize = 3;
var parts = Enumerable.Range(0, whole.Length / partSize)
    .Select(i => whole.Substring(i * partSize, partSize));

但是如果有可能整个字符串末尾会有一个小数块,你需要更加复杂的处理方式:

var whole = "32427237";
var partSize = 3;
var parts = Enumerable.Range(0, (whole.Length + partSize - 1) / partSize)
    .Select(i => whole.Substring(i * partSize, Math.Min(whole.Length - i * partSize, partSize)));

在这些示例中,parts将是一个IEnumerable,但是如果您想要一个string[]或List<string>值,可以在结尾处添加.ToArray()或.ToList()。


如果您认为自己的回答可以为已接受的答案增加价值,那么您应该在已有答案的问题中添加自己的答案。但是看起来并非如此。 - Marco Scabbiolo
我不明白。这是对这个问题的第10个回答,只有第3个可以使用Linq在单个语句中完成,而且我认为这是这3个答案中最有效率的。我是否做错了,添加了这个不同但可能更好的答案? - Dave Lampert

4
分割方法:
public static IEnumerable<string> SplitInGroups(this string original, int size) {
  var p = 0;
  var l = original.Length;
  while (l - p > size) {
    yield return original.Substring(p, size);
    p += size;
  }
  yield return original.Substring(p);
}

要将字符串以空格为分隔符重新连接起来:

var joined = String.Join(" ", myNumber.SplitInGroups(3).ToArray());

编辑:我更喜欢Martin Liversage的解决方案 :)

编辑2:修复了一个错误。

编辑3:添加了代码以重新连接字符串。


2
我会这样做,虽然我相信还有其他方法。应该表现得很好。
public static string Format(string number, int batchSize, string separator)
{      
  StringBuilder sb = new StringBuilder();
  for (int i = 0; i <= number.Length / batchSize; i++)
  {
    if (i > 0) sb.Append(separator);
    int currentIndex = i * batchSize;
    sb.Append(number.Substring(currentIndex, 
              Math.Min(batchSize, number.Length - currentIndex)));
  }
  return sb.ToString();
}

2

我喜欢这个因为它很酷,虽然不是特别高效:

var n = 3;
var split = "12345678900"
            .Select((c, i) => new { letter = c, group = i / n })
            .GroupBy(l => l.group, l => l.letter)
            .Select(g => string.Join("", g))
            .ToList();

我个人偏爱Linq,但效率是必须的 - 谢谢你提供的例子。 - RoguePlanetoid

1

试试这个:

Regex.Split(num.toString(), "(?<=^(.{8})+)");

有趣的回答,但你能解释一下正则表达式吗? - kotchwane
我认为这个不起作用。(?<=_pattern_)在匹配_pattern_后的点进行匹配。.{8}匹配8个字符。因此,这应该从字符串开头匹配8、16、24、32等字符。但是我发现这个模式会重复输出第一组..? - FSCKur

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