This question is pretty much the opposite of this question: Does C# have built-in support for parsing page-number strings?
So given
1,3,5,6,7,8,9,10
This is an old thread, but here's a new answer. I've constructed it as an extension method. This returns the array of ranges, where each 'range' is either a single number ('13'
) or a pair of numbers ('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();
}
}
If you wish to join them, just use built-in 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
Had to solve same problem. Was finding alternatives to my solution which I think looks more logical. Therefore sharing it. Set second parameter to true if you want to sort an unsorted list.
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();
}
Here a slightly modifed Version of RedFilter's version.
It returns a String instead of an Array of Strings, it Removes 0 ,if in the list, it avoids the Exception if only one Value is in the List.
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);
}
This should work pretty well, not tested for all cases though.
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);
When something has several moving parts like this, I think it helps to decompose it into little logical units and then combine them together. The little logical units might even be usable separately. The code below breaks the problem down into:
The program is:
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();
}
}
}
I know this is an old thread but thought I'd share my approach. This generates a list of ranges which can be easily converted to a single string.
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"