13

I have a string denoting page nos like 1,2,3,4,8,9,10,15.

I want this to be shown as 1-4,8-10,15 i.e numbers in sequence are separated by hyphen enclosed by smallest and largest number in sequence.

If break in sequence, the range is to be separated by comma.

string pageNos = "5,6,7,9,10,11,12,15,16";
string result=string.Empty;
string[] arr1 = pageNos.Split(',');
int[] arr = new int[arr1.Length];

for (int x = 0; x < arr1.Length; x++) // Convert string array to integer array
{
    arr[x] = Convert.ToInt32(arr1[x].ToString());
}

for (int i = 0; i < arr.Length;i++)
{
    for (int j = i + 1; ; j++)
        if (arr[i] == (arr[j] - 1))
            result += arr[i].ToString() + "-" + arr[j].ToString();
        else
            result += arr[i].ToString() + ",";
}

Console.WriteLine(result);
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Mudassir Hasan
  • 28,083
  • 20
  • 99
  • 133

9 Answers9

9

I think the loop-within-loop is making things more confusing. Try using just a single loop, because you only need to iterate over the entire list once.

int start,end;  // track start and end
end = start = arr[0];
for (int i = 1; i < arr.Length; i++)
{
    // as long as entries are consecutive, move end forward
    if (arr[i] == (arr[i - 1] + 1))
    {
        end = arr[i];
    }
    else
    {
        // when no longer consecutive, add group to result
        // depending on whether start=end (single item) or not
        if (start == end)
            result += start + ",";
        else if (end == (start + 1))
            result += start + "," + end + ",";
        else
            result += start + "-" + end + ",";

        start = end = arr[i];
    }
}

// handle the final group
if (start == end)
    result += start;
else
    result += start + "-" + end;

Demo: http://ideone.com/7HdpS7

Donny V.
  • 22,248
  • 13
  • 65
  • 79
mellamokb
  • 56,094
  • 12
  • 110
  • 136
5

A little bit of LINQ will tidy this up:

static IEnumerable<Tuple<int, int>> GetRanges(IEnumerable<int> source)
{
   bool started = false;
   int rangeStart = 0, lastItem = 0;

   foreach (int item in source)
   {
      if (!started)
      {
         rangeStart = lastItem = item;
         started = true;
      }
      else if (item == lastItem + 1)
      {
         lastItem = item;
      }
      else
      {
         yield return new Tuple<int, int>(rangeStart, lastItem);
         rangeStart = lastItem = item;
      }
   }

   if (started)
   {
      yield return new Tuple<int, int>(rangeStart, lastItem);
   }
}

static string FormatRange(Tuple<int, int> range)
{
   string format = (range.Item1 == range.Item2) ? "{0:D}" : "{0:D}-{1:D}";
   return string.Format(format, range.Item1, range.Item2);
}

string pageNos = "5,6,7,9,10,11,12,15,16";
int[] pageNumbers = Array.ConvertAll(pageNos.Split(','), Convert.ToInt32);
string result = string.Join(",", GetRanges(pageNumbers).Select(FormatRange));
Richard Deeming
  • 29,830
  • 10
  • 79
  • 151
3
string pageNos = "5,6,7,9,10,11,12,15,16";
string[] arr1 = pageNos.Split(',');
int[] arr = new int[arr1.Length];

for (int x = 0; x < arr1.Length; x++) // Convert string array to integer array
{
    arr[x] = Convert.ToInt32(arr1[x]);
}

StringBuilder sb = new StringBuilder();
bool hyphenOpen = false;
for (int i = 0; i < arr.Length - 1; i++)
{
    if (arr[i] + 1 == arr[i+1])
    {
        if (!hyphenOpen)
        {
            hyphenOpen = true;
            sb.Append(arr[i] + "-");
        }
    }
    else
    {
        hyphenOpen = false;
        sb.Append(arr[i] + ",");
    }
}
sb.Append(arr[arr.Length-1]);
Console.WriteLine(sb.ToString());

This is long and clunky, but it works.

P.S. - I left the OP's original string->int as is, see comment by JonB on question for much cleaner method.

Rotem
  • 21,452
  • 6
  • 62
  • 109
  • +1 Nice solution. Note that you have an extra `}` after the `for` loop, otherwise it works well. **Edit:** Fixed :) – mellamokb Nov 29 '12 at 14:52
3

You could use this method to get adjacent groups of numbers where each group is represented by a custom Range-class:

class Range
{
    public int? Start { get; set; }
    public int? End { get; set; }
}

private static IEnumerable<Range> getAdjacentRanges(IEnumerable<int> nums)
{
    var ranges = new List<Range>();
    if (!nums.Any())
        return ranges;

    var ordered = nums.OrderBy(i => i);
    int lowest = ordered.First();
    int last = lowest;
    ranges.Add(new Range { Start = lowest });

    foreach (int current in ordered)
    {
        lastRange = ranges[ranges.Count - 1];
        if (current > last + 1)
        {
            lastRange.End = last;
            ranges.Add(new Range { Start = current });
        }
        last = current;
    }

    return ranges;
}

The rest is easy:

var arr = new[] { 1, 2, 3, 4, 8, 9, 10, 15 };
var ranges = getAdjacentRanges(arr)
    .Select(r => r.End.HasValue ? string.Format("{0}-{1}", r.Start, r.End) : r.Start.ToString());
Console.Write(string.Join(",", ranges));

output: 1-4,8-10,15

DEMO

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
3

The following JS code will also help

  1. Remove duplicates
  2. Sort the array
  3. Use 2 pointers
  4. Start at the same place
  5. Advance the second pointer until its next element is a successor
  6. Print the element at i and j with spaces and comma
  7. Don’t print element at j if the index are same
  8. Remove the trailing comma

const givenArray = [1, 6, 6, 8, 44, 45, 47, 55, 9, 11, 12, 1, 6, 88, 13, 14, 2, 3, 5, 22, 33, 57, 88];

const input = [...new Set(givenArray)].sort((a, b) => a - b);
let i = 0;
let j = 0;
let output = '';

while (i < input.length) {
  while (j < input.length && (input[j] + 1) === input[j + 1]) {
    j++;
  }
  output += `${input[i]}`;
  if (i !== j) {
    output += ` - ${input[j]}, `;
  } else {
    output += ', ';
  }
  i = j + 1;
  j = i;
}
console.log(output.substring(0, output.lastIndexOf(",")));
Kishan
  • 51
  • 1
2

Use this helper class to convert back and forth between number lists and range strings.

This copies implementation of ConvertRangeStringToNumberList() from here and ConvertNumberListToRangeString() from here with slight improvements.

using System;
using System.Collections.Generic;
using System.Linq;

public static class NumberRangeHelper
{
    /// <summary>
    /// Converts a string of comma separated list of numbers and ranges to the list of individual numbers it represents.
    /// </summary>
    /// <param name="numbers">Range in form of <c>"2,4-8,11,15-22,39"</c></param>
    /// <returns>A list of numbers</returns>
    public static List<int> ConvertRangeStringToNumberList(string numbers)
    {
        var numbersSplit = numbers.Split(',');
        var convertedNumbers = new SortedSet<int>();
        foreach (var strNumber in numbersSplit)
        {
            int number;
            if (int.TryParse(strNumber, out number))
            {
                convertedNumbers.Add(number);
            }
            else
            {
                // try and delimited by range
                if (strNumber.Contains('-'))
                {
                    var splitRange = strNumber.Split('-');
                    if (splitRange.Length == 2)
                    {
                        int firstNumber;
                        int secondNumber;

                        if (Int32.TryParse(splitRange[0], out firstNumber) &&
                            Int32.TryParse(splitRange[1], out secondNumber))
                        {
                            for (var i = firstNumber; i <= secondNumber; ++i)
                            {
                                convertedNumbers.Add(i);
                            }
                        }
                    }
                }
            }
        }
        return convertedNumbers.ToList();
    }

    /// <summary>
    /// Converts a list of numbers to their concise range representation.
    /// </summary>
    /// <param name="numbers">A list of numbers such as <c>new[] { 1, 2, 3, 4, 5, 12, 13, 14, 19 }</c></param>
    /// <returns>A string like <c>"1-5, 12-14, 19"</c></returns>
    public static string ConvertNumberListToRangeString(IEnumerable<int> numbers)
    {
        var items = new SortedSet<int>(numbers)
            .Select((n, i) => new { number = n, group = n - i })
            .GroupBy(n => n.group)
            .Select(g => (g.Count() >= 3)
                    ? g.First().number + "-" + g.Last().number
                    : String.Join(", ", g.Select(x => x.number))
            )
            .ToList();

        return String.Join(", ", items);
    }

}

Test:

Action<IEnumerable<int>> DumpList = l => Console.WriteLine("\t[{0}]", String.Join(", ", l));
Action<string> DumpRange = s => Console.WriteLine("\t\"{0}\"", s);

var numbers = new[] { 1, 1, 2, 3, 4, 5, 12, 13, 19, 19, 6, 7 };
DumpList(numbers);
var str = ConvertNumberListToRangeString(numbers);
DumpRange(str);
var list = ConvertRangeStringToNumberList(str);
DumpList(list);

Console.WriteLine();    

str = "1-5, 12, 13, 19, 20, 21, 2-7";
DumpRange(str);
list = ConvertRangeStringToNumberList(str);
DumpList(list);
str = ConvertNumberListToRangeString(list);
DumpRange(str);

Output:

[1, 1, 2, 3, 4, 5, 12, 13, 19, 19, 6, 7]
"1-7, 12, 13, 19"
[1, 2, 3, 4, 5, 6, 7, 12, 13, 19]

"1-5, 12, 13, 19, 20, 21, 2-7"
[1, 2, 3, 4, 5, 6, 7, 12, 13, 19, 20, 21]
"1-7, 12, 13, 19-21"
Community
  • 1
  • 1
orad
  • 15,272
  • 23
  • 77
  • 113
1

i am not a C# person, but i guess here you have problem:

 if (arr[i] == (arr[j] - 1))
            result += arr[i].ToString() + "-" + arr[j].ToString();

you shouldn't add that in your result. but set a flag (boolean maybe), to indicate that now I start counting.

if the flag==ture and the number is not continuous any longer, that is the time to add to your result, of course with "-".

Kent
  • 189,393
  • 32
  • 233
  • 301
1

Here's a different solution that creates a List<Tuple<int, int>> with each non-sequential value and the number of sequential values that follow it. This is then turned into a string using string.Join.

string pageNos = "1,2,3,4,8,9,10,15";

// Get list of numbers as ints
var list = pageNos.Split(',').Select(i => Convert.ToInt32(i)).ToList();

// Get a list of numbers and ranges of consecutive numbers
var ranges = new List<Tuple<int, int>>();
int start = 0;

for (int i = 0; i < list.Count; i++)
{
    // First item always starts a new range
    if (i == 0)
    {
        start = list[i];
    }

    // Last item always ends the current range
    if (i == list.Count - 1)
    {
        if (list[i] == list[i - 1] + 1)
        {
            ranges.Add(new Tuple<int, int>(start, list[i] - start));
        }
        else
        {
            ranges.Add(new Tuple<int, int>(start, list[i - 1] - start));
            ranges.Add(new Tuple<int, int>(list[i], 0));
        }
    }

    // End the current range if nonsequential
    if (i > 0 && i < list.Count - 1 && list[i] != list[i - 1] + 1)
    {
        ranges.Add(new Tuple<int, int>(start, list[i - 1] - start));
        start = list[i];
    }
}

// Craete the result string
var result = string.Join(", ", ranges.Select(r => r.Item2 == 0 ? r.Item1.ToString() : string.Format("{0}-{1}", r.Item1, r.Item1 + r.Item2)));
Jon B
  • 51,025
  • 31
  • 133
  • 161
1
public static string HyphenateRanges(this string input)
{
    if (string.IsNullOrEmpty(input))
    {
        return "";
    }

    var orderedDistinct = input.Split(',')
                                .Select(Int32.Parse)
                                .Distinct()
                                .OrderBy(x => x)
                                .ToArray();

    Func<int, int, string> replaceRangeValuesWithDash =
        (x, i) =>
        i == 0 || // first
        i == orderedDistinct.Length - 1 || // last
        orderedDistinct[i + 1] - orderedDistinct[i - 1] != 2 // not in a range
            ? x.ToString()
            : "-";

    var rangeValuesDashed = orderedDistinct
        .Select(replaceRangeValuesWithDash)
        .ToList();

    var extraDashesRemoved = rangeValuesDashed
        .Where((x, i) => i == 0 || rangeValuesDashed[i - 1] != x)
        .ToArray();

    var formattedString = String.Join(",", extraDashesRemoved)
                                .Replace(",-,", "-");

    return formattedString;
}
Handcraftsman
  • 6,863
  • 2
  • 40
  • 33