将列表转换为数字范围字符串

这个问题几乎与这个问题相反: C#是否内置支持解析页码字符串?

所以给定

1,3,5,6,7,8,9,10,12: 

我会输出:

 1,3,5-10,12 

这是我的第一次尝试。 它看起来有点像hacky,可能是我写过的最糟糕的代码。 你能提出一个改进的方法吗?更好的办法吗?

 static string numListToRangeStr(List numList) { StringBuilder retString = new StringBuilder(); numList.Sort(); bool inRangeFind = false; int firstInRange = numList[0]; int lastNumber = firstInRange; bool first = true; for (int i = 1; i < numList.Count; i++) { if (numList[i] == (lastNumber + 1)) { inRangeFind = true; } else { if (inRangeFind) { if (!first) { retString.Append(","); } retString.Append(firstInRange); retString.Append("-"); } else { if (!first) { retString.Append(","); } } retString.Append(lastNumber); firstInRange = numList[i]; inRangeFind = false; first = false; } lastNumber = numList[i]; } if (inRangeFind) { if (!first) { retString.Append(","); } retString.Append(firstInRange); retString.Append("-"); } else { if (!first) { retString.Append(","); } } retString.Append(lastNumber); return retString.ToString(); } 

当某些东西有这样的几个运动部件时,我认为将它分解成很小的逻辑单元然后将它们组合在一起是有帮助的。 小逻辑单元甚至可以单独使用。 下面的代码将问题分解为:

  • 将异构的顺序和非顺序数集转换为一组同质的范围(可能包括以相同数字开始和结束的“退化”范围)
  • 一种“漂亮印刷”这种范围的方法:(x,y)打印为“xy”; (x,x)打印为“x”
  • 一种在可枚举元素之间插入分隔符并将结果转换为字符串的方法。

该计划是:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication37 { public static class Program { static void Main(string[] args) { var numList=new[] {1, 3, 5, 6, 7, 8, 9, 10, 12}; Console.WriteLine(numListToPossiblyDegenerateRanges(numList).Select(r => PrettyRange(r)).Intersperse(",")); } ///  /// eg 1,3,5,6,7,8,9,10,12 /// becomes /// (1,1),(3,3),(5,10),(12,12) ///  public static IEnumerable> numListToPossiblyDegenerateRanges(IEnumerable numList) { Tuple currentRange=null; foreach(var num in numList) { if(currentRange==null) { currentRange=Tuple.Create(num, num); } else if(currentRange.Item2==num-1) { currentRange=Tuple.Create(currentRange.Item1, num); } else { yield return currentRange; currentRange=Tuple.Create(num, num); } } if(currentRange!=null) { yield return currentRange; } } ///  /// eg (1,1) becomes "1" /// (1,3) becomes "1-3" ///  ///  ///  public static string PrettyRange(Tuple range) { if(range.Item1==range.Item2) { return range.Item1.ToString(); } return string.Format("{0}-{1}", range.Item1, range.Item2); } public static string Intersperse(this IEnumerable items, string interspersand) { var currentInterspersand=""; var result=new StringBuilder(); foreach(var item in items) { result.Append(currentInterspersand); result.Append(item); currentInterspersand=interspersand; } return result.ToString(); } } } 

这是一个老线程,但这是一个新的答案。 我已经将它构建为扩展方法。 这将返回范围数组,其中每个“范围”是单个数字( '13' )或一对数字( '5-12' ):

 public static class EnumExt { public static string[] ToRanges(this List ints) { if (ints.Count < 1) return new string[] { }; ints.Sort(); var lng = ints.Count; var fromnums = new List(); var tonums = new List(); for (var i = 0; i < lng - 1; i++) { if (i == 0) fromnums.Add(ints[0]); if (ints[i + 1] > ints[i] + 1) { tonums.Add(ints[i]); fromnums.Add(ints[i + 1]); } } tonums.Add(ints[lng - 1]); return Enumerable.Range(0, tonums.Count).Select( i => fromnums[i].ToString() + (tonums[i] == fromnums[i] ? "" : "-" + tonums[i].ToString()) ).ToArray(); } } 

如果你想加入它们,只需使用内置string.Join

 var intlist = new List() { 1, 2, 3, 6, 7, 8, 9, 10, 14 }; Console.WriteLine(string.Join(", ", intlist.ToRanges())); // Prints: 1-3, 6-10, 14 

不得不解决同样的问题。 找到我的解决方案的替代品,我认为看起来更合乎逻辑。 因此分享它。 如果要对未排序的列表进行排序,请将第二个参数设置为true。

 public string ToRangeString(List list, bool withSort) { list = list.Distinct().ToList(); if(withSort) list.Sort(); StringBuilder result = new StringBuilder(); int temp; for (int i=0; i 

这应该工作得很好,但并未针对所有情况进行测试。

  string s = "1,2,3,4,5,7,8,9,10,11,12,13"; string[] ints = s.Split(','); StringBuilder result = new StringBuilder(); int num, last = -1; bool dash = false; for (int ii = 0; ii < ints.Length; ii++) { num = Int32.Parse(ints[ii]); if (num - last > 1) { if (dash) { result.Append(last); dash = false; } if (result.Length > 0) { result.Append(","); } result.Append(num); } else { if (dash == false) { result.Append("-"); dash = true; } } last = num; if (dash && ii == ints.Length - 1) { result.Append(num); } } Console.WriteLine(result); 

这是一个稍微修改过的RedFilter版本。

它返回一个String而不是一个字符串数组,它删除0,如果在列表中,如果列表中只有一个值,它将避免exception。

  public static string ToRanges(this List ints) { ints.Remove(0); // Note: Remove this if you like to include the Value 0 if (ints.Count < 1) return ""; ints.Sort(); var lng = ints.Count; if (lng == 1) return ints[0].ToString(); var fromnums = new List(); var tonums = new List(); for (var i = 0 ; i < lng - 1 ; i++) { if (i == 0) fromnums.Add(ints[0]); if (ints[i + 1] > ints[i] + 1) { tonums.Add(ints[i]); fromnums.Add(ints[i + 1]); } } tonums.Add(ints[lng - 1]); string[] ranges = Enumerable.Range(0, tonums.Count).Select( i => fromnums[i].ToString() + (tonums[i] == fromnums[i] ? "" : "-" + tonums[i].ToString()) ).ToArray(); if (ranges.Length == 1) return ranges[0]; else return String.Join(",", ranges); } 

我知道这是一个老线程,但我想我会分享我的方法。 这将生成一个范围列表,可以轻松转换为单个字符串。

 var numbers = new List() { 1, 3, 5, 6, 7, 8, 9, 10, 12 }; var ranges = new List(); if (numbers.Count == 0) return ranges; numbers = numbers.Distinct().ToList(); numbers.Sort(); int start = numbers[0]; string range = start.ToString(); for (int i = 1; i <= numbers.Count; i++) { if (i < numbers.Count && numbers[i] == numbers[i - 1] + 1) { range = $"{start} - {numbers[i]}"; continue; } ranges.Add(range); if (i < numbers.Count) { start = numbers[i]; range = start.ToString(); } } var rangeString = string.Join(", ", ranges); // Outputs: "1, 3, 5 - 10, 12"