Преобразование списка в строку диапазона номеров
Этот вопрос в значительной степени противоположный этому вопросу:
Имеет ли С# встроенную поддержку для разбора строк номера страницы?
Итак, данный
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();
}
Ответы
Ответ 1
Когда что-то имеет несколько движущихся частей, подобных этому, я думаю, что это помогает разложить его на несколько логических единиц, а затем объединить их. Маленькие логические единицы могут даже использоваться отдельно. Приведенный ниже код разбивает проблему на:
- превращение гетерогенного набора последовательных и несущественных чисел в однородный набор диапазонов (возможно, включая "вырожденные" диапазоны, которые начинаются и заканчиваются с одинаковым числом)
- способ "довольно-печатать" такие диапазоны: (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();
}
}
}
Ответ 2
Это старый поток, но вот новый ответ. Я построил его как метод расширения. Это возвращает массив диапазонов, где каждый "диапазон" представляет собой либо одно число ('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
Ответ 3
Придется решить ту же проблему. Было найти альтернативы моему решению, которое, по моему мнению, выглядит более логичным. Поэтому делиться им. Установите второй параметр в значение 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();
}
Ответ 4
Это должно работать очень хорошо, но не проверено для всех случаев.
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);
Ответ 5
Здесь немного измененная версия версии 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);
}