BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Algorithms and Data Structures
.NET 3.5+

Ungrouping Page Numbers

When users provide a set of page numbers, or similarly numbered items, it is common to allow the information to be entered as a comma-delimited list. Each item in that list could represent a single page or range of adjacent pages that will need to be converted to a simple list.

Page Number Groups

In an earlier article I described how a list of individual pages, provided as an array of integers, could be converted into a set of page groups. Each group represented a single page or a range of adjacent pages. When combined in a comma-separated string, this is much easier for a person to comprehend than a large list containing every page number. For example, the following list includes four ranges and four single pages:

1-2, 4, 6-10, 12, 17, 19-21, 24-25, 30

If you are requesting a set of numbers, it is helpful to the user to allow them to be provided in the above format. This is commonly seen in Microsoft Windows print dialog boxes, where the user can specify which pages to print. Once you have the information, you will generally need to use it to generate a collection containing all of the numbers, including those implied by the ranges. For example, the list above includes all of the values shown below:

1, 2, 4, 6, 7, 8, 9, 10, 12, 17, 19, 20, 21, 24, 25, 30

In this article we'll create a method that reverses the process described in the earlier one. We will take a string containing a list of values and ranges and produce an array containing the individual numbers.

Implementation

Before we implement the ungrouping method, let's create a program that calls it and shows the results. Create a new console application solution and replace the automatically generated Main method with the code shown below:

static void Main(string[] args)
{
    string groups = "1-2, 4, 6-10, 12, 17, 19-21, 24-25, 30";

    int[] pages = UngroupPageNumbers(groups);

    Console.WriteLine("Grouped: {0}", groups);
    Console.WriteLine("Pages:   {0}", BuildList(pages));
}

static string BuildList(int[] items)
{
    return string.Join(", ", items.Select(x => x.ToString()).ToArray());
}

The new Main method creates a string containing a set of grouped page numbers. It then calls the method that we are about to create to generate an integer array fro the string. Finally, the original string and the numbers in the array are outputted to the console.

Creating the Method

We can now create the method that generates the page number array. The method will accept a string containing the grouped list and return an integer array.

Start by adding the empty method:

static int[] UngroupPageNumbers(string groups)
{
}

The first step to process the string is to split it into groups, based upon the position of commas. We can do this using the string class's Split method. This generates an array of smaller strings, which we'll loop through and process individually.

Add the following code to the UngroupPageNumbers method. It creates a generic list to hold the integers, splits the string and passes each item in turn to another method. The second method will process each group and add the page numbers to the list. The final line of the following code builds an array using the items from the completed list. This is returned as the method's final result.

var pages = new List<int>();

foreach (string group in groups.Split(','))
{
    AddGroup(group, pages);
}

return pages.ToArray();

Finally, we can create the method that processes a single group. The argument passed to the method will either contain a single number or a range, with the lower and upper bounds separated by a hyphen (-). We can use the string's Split method again, splitting on the hyphen. If it returns an array with one element, we know that the string contained a single value. If there are two elements, the string was a range. NB: This assumes that the input string is valid. In a real program you should include the validation that is omitted from these examples.

If the call to Split returns an array with two values, we'll convert both to integers that represent the lower and upper boundaries for a range. If the array contains a single element, the lower and upper bounds are the same. In either case, we can use these values to generate a continuous range with Enumerable.Range.

Add the following method to the code. This performs the above actions and adds the generated values to the collection.

static void AddGroup(string group, List<int> pages)
{
    var items = group.Split('-');
    int lower = int.Parse(items[0]);
    int upper = items.Length == 1 ? lower : int.Parse(items[1]);

    foreach (int i in Enumerable.Range(lower, 1 + upper - lower))
    {
        pages.Add(i);
    }
}

Testing the Program

You can now run the program to test the method. You should see the original string and the extracted numbers outputted to the console:

Grouped: 1-2, 4, 6-10, 12, 17, 19-21, 24-25, 30
Pages:   1, 2, 4, 6, 7, 8, 9, 10, 12, 17, 19, 20, 21, 24, 25, 30
6 March 2016