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

14

这个问题与这个问题几乎完全相反: C#是否具有解析页面编号字符串的内置支持?

所以考虑到

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

我将输出:

1,3,5-10,12

这是我的第一次尝试。看起来有点笨拙,可能是我编写过的最差的代码。你能建议一种更好的方式来做吗?

static string numListToRangeStr(List<int> 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();
}

2
一个非常适合放在代码审查网站上的问题。 - Daniel
一个状态机会使这个变得更加容易。 - Daniel
2
我在这里发布了问题,因为确切相反的问题在SO上:https://dev59.com/QXVD5IYBdhLWcg3wQJOT - Paligulus
你为什么需要双向进行这个操作? - Daniel
仅供显示目的 - 如果我没有因为尝试实现它而变得如此沮丧,我可能已经忘记了它,因为它似乎是一项简单的任务! - Paligulus
我知道这不是一個C++問題,但如果你想要一個良好的參考實現,你應該看一下Boost區間容器庫,它支持這樣做。http://www.boost.org/doc/libs/1_47_0/libs/icl/doc/html/index.html - phooji
6个回答

11

当一个问题有多个运作部分时,我认为将它分解成小的逻辑单元并将它们组合在一起会有所帮助。这些小的逻辑单元甚至可以单独使用。下面的代码将问题分解为:

  • 将顺序和非顺序数字的异构集合转换为一组同构范围(可能包括从同一个数字开始和结束的“退化”范围)
  • 一种“漂亮地打印”这样的范围的方法:(x,y) 打印为 "x-y";(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(","));
    }

    /// <summary>
    /// e.g. 1,3,5,6,7,8,9,10,12
    /// becomes
    /// (1,1),(3,3),(5,10),(12,12)
    /// </summary>
    public static IEnumerable<Tuple<int,int>> numListToPossiblyDegenerateRanges(IEnumerable<int> numList) {
      Tuple<int, int> 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;
      }
    }

    /// <summary>
    /// e.g. (1,1) becomes "1"
    /// (1,3) becomes "1-3"
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public static string PrettyRange(Tuple<int,int> 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<string> 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();
    }
  }
}

1
这很好 - 利用 .net 构造的优势。与“C”风格的答案相反 - Paligulus
2
“Intersperse” 只是 string.Join 的重新实现。 - Tim S.

6

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

public static class EnumExt {
    public static string[] ToRanges(this List<int> ints) {
        if (ints.Count < 1) return new string[] { };
        ints.Sort();
        var lng = ints.Count;
        var fromnums = new List<int>();
        var tonums = new List<int>();
        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<int>() { 1, 2, 3, 6, 7, 8, 9, 10, 14 };
Console.WriteLine(string.Join(", ", intlist.ToRanges()));
// Prints: 1-3, 6-10, 14

1
接近了,但有一个偏差错误。在我的编辑中已经修复了。旧代码在 {1,3,4,5} 上失败了。 - D'Arcy Rittich
2
当我只传递列表中的一个数字时,似乎会出现异常? - Paul C

5

我曾经遇到了同样的问题。在寻求我的解决方案的替代品时,我认为我的解决方案更为合理。因此分享给大家。如果你想对一个未排序的列表进行排序,请将第二个参数设置为true。

public string ToRangeString(List<int> list, bool withSort) {
  list = list.Distinct().ToList();
  if(withSort) list.Sort();

  StringBuilder result = new StringBuilder();
  int temp;

  for (int i=0; i<list.Count(); i++) {
    temp = list[i];

    //add a number
    result.Append(list[i]);

    //skip number(s) between a range
    while(i<list.Count()-1 && list[i+1] == list[i]+1)
      i++;

    //add the range
    if(temp != list[i])
      result.Append("-").Append(list[i]);

    //add comma
    if(i != list.Count()-1)
      result.Append(", ");

  }
  return result.ToString();
}

+1 这似乎总是取决于你的大脑如何运作,但对我来说,这也是一种清晰而逻辑的变体。 - Mike Fuchs

3

我知道这是一个旧的帖子,但是我想分享我的方法。这会生成一系列范围,可以轻松地转换为单个字符串。

var numbers = new List<int>() { 1, 3, 5, 6, 7, 8, 9, 10, 12 };
var ranges = new List<string>();

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"

1

这应该工作得相当不错,尽管没有测试所有情况。

        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);

1
是的 - 这对我来说似乎工作正常。比我的代码更简洁,因为你的循环后面没有任何垃圾代码。 - Paligulus
1
该程序失败,例如,字符串s="0,1"。 - Corey Kosak
@Corey,是的,我假设数字从1开始。 - Jayanta Dey

0
这里是 RedFilter 版本的稍微修改版。
它返回一个字符串而不是字符串数组,如果列表中包含 0,则会将其删除;如果列表只有一个值,则避免异常。
 public static string ToRanges(this List<int> 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<int>();
        var tonums = new List<int>();
        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);
    }

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