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+

Page Number Grouping

When listing page numbers in document indexes or for printing, it is common to group adjacent pages. This article describes a method that creates such groupings from a simple collection of integers.

Page Numbers

If you have a large set of numbers that are used as references, it can be useful to group them into a series of smaller ranges, each containing the adjacent numbers. This approach is commonly seen in document indexes. It is also used in document editing software when specifying that you wish to print a specific set of pages.

For example, you might have the following set of page numbers:

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

The above list can be expressed as a series of groups and individual numbers, as shown below:

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

In this article we will create a method that will convert an array of integer values into string array, where each element of the returned array represents either a single number or a group of adjacent values.

Implementation

Before we create the method that groups values let's create a program that can be used to call the method and see the results. Create a new console application and replace the empty Main method with the following code:

static void Main(string[] args)
{
    var pages = new int[] { 1, 2, 4, 6, 7, 8, 9, 10, 12, 17, 19, 20, 21, 24, 25, 30 };

    string[] groups = GroupPageNumbers(pages);

    Console.WriteLine("Original: {0}", BuildList(pages.Select(p => p.ToString())));
    Console.WriteLine("Grouped:  {0}", BuildList(groups));
}

static string BuildList(IEnumerable<string> items)
{
    return string.Join(", ", items.ToArray());
}

The above creates an array of discrete integers that we want to group. It calls the GroupPageNumbers method to generate the string array then outputs the original and formatted arrays for comparison.

Creating the Method

We can now build the key method. We'll expect the caller to pass a sorted array of integers and return a string array, so create the method with the following signature. NB: We could add sorting within the method but this has been excluded to simplify the example code.

static string[] GroupPageNumbers(int[] pages)
{
}

The method will build the list of single numbers or groups of adjacent values in a generic list of strings, so that we can increase the size of the collection automatically. This will be converted to an array before it is returned, once we know the final number of groups.

Add the following line to the method to initialise the list:

var groups = new List<string>();

The main part of the method is a loop that processes the entire input array. We'll use a for loop, rather than a foreach loop, as it allows us to modify the loop control variable within the command's code block, as well as at the end of each iteration.

for (int i = 0; i < pages.Length; i++)
{
}

Within the body of the loop we will identify single values and adjacent groups and advance the loop control variable manually. This will mean that every time the loop starts, the value at the current index will be the start value for a new group. By the end of the loop iteration we will have identified the end value for the group. If the two match, we have identified a single value.

To store the current position's value as the start of a range, add the following command within the loop's code block:

int start = pages[i];

With the start value recorded, we need to look for adjacent integers. To achieve this, we can use a while loop that continues looping until the value of the next position in the for loop is not exactly one higher than the current position, or until the array is exhausted. Once the current and next values are not adjacent, or we have run through the entire array, the value at the current position must be the end of the group. The start and end values can then be combined in a string and added to the groups array.

Add the following while loop within the for loop. Note that the group is formatted using another method. We'll add this shortly.

while (i < pages.Length - 1 && pages[i + 1] == pages[i] + 1)
{
    i++;
}
groups.Add(FormatGroup(start, pages[i]));

Once the loop has completed, the groups variable holds the final result. Return this using the following code, added after the closing brace of the for loop:

return groups.ToArray();

The final step is to add the method that formats a range. This checks if the start and end values match using the conditional operator. If they do, it returns the single value as a string. If they differ, the two values are combined into a single string.

Add the new method:

static string FormatGroup(int start, int end)
{
    return start == end ? start.ToString() : string.Format("{0}-{1}", start, end);
}

Testing the Program

You can now run the program to see the results. You should see a line of text showing the original array of numbers. A second line shows the grouped values, as shown below:

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

NB: The reverse of the process is described in the article, "Ungrouping Page Numbers".

29 February 2016