Skip to main content
frameexpert
Community Expert
Community Expert
December 4, 2012
해결됨

Converting consecutive "numbers" to ranges

  • December 4, 2012
  • 1 답변
  • 2407 조회

I would like some feedback on an algorithm that identifies ranges in a list of "numbers". Any feedback will be appreciated.

I have a series of strings indicating section numbers like this:

102.2, 102.3, 102.4, 102.5, 102.6, 102.16, 102.17, 102.18, 102.19

What I want to do is collapse three or more consecutive numbers into ranges:

102.2-102.6, 102.16-102.19

First, I convert the string to an array of sections numbers. Next, I get two lists of numbers; one with the hundreds, the other with the tens. Then, I go through these lists, identifying ranges and return a list of positions in the array. So, in this example, I get:

1

5

6

9

If the length of positions is less than the array, I know there is at least one range. Now I go through the ranges to figure out which members to eliminate from the array. I do this by starting at the end of the positions list and subtract - [x-1]. So

9 - 6 = 3

6 - 5 = 1

5 - 1 = 4

Any result > 1 is a range. I subtract 1 from the result and that tells me how many members to eliminate from the array. So for the first one, I delete two members after the 6, which are 7 and 8. I skip the second one because the result is not greater than 1. The third one gives me 4 - 1, so I delete 3 members after position 1 (2, 3, 4). The array is left with 102.2, 102.6, 102.16, 102.19.

Note that the way I get the hyphens in the correct position is not important here. Also, my descriptions of positions above assume 1 as the first position, not 0 as they are in JavaScript arrays.

Below is my code in JavaScript. Any suggestions for simplifying the algorithm are appreciated. Thanks.

Rick Quatro

var refs = "102.2, 102.3, 102.4, 102.5, 102.6, 102.16, 102.17, 102.18, 102.19";

// Convert the string into an array.

var refsArray = refs.split(", ");

// Split the numbers into hundreds and tens places.

var numbers = getNumberSets (refsArray);

// Return a list of positions in the array, indicating ranges.

var positions = getRangePositions (numbers);

// Delete the unneeded members from the refsArray.

removeRedundantMembers (positions, refsArray); alert (refsArray);

function removeRedundantMembers (positions, refsArray) {

    var count = positions.length - 1, span = 0;

    // Loop from the end of the array to the beginning

    for (var i = count; i > 0; i -= 1) {

        // Get the distance between to consecutive positions.

        span = positions - positions[i-1];

        if (span > 1) {

             // Remove the unneeded members.

             refsArray.splice(positions[i-1] + 1, span - 1);

        }

    }

}

function getRangePositions (numbers) {

    // Make an array of positions.

    var positions = [];

    // Make variables to store the current hundreds and tens values.

    var hundred = -99, ten = -99;

    for (var i = 0, count = numbers.hundreds.length; i < count; i += 1) {

        if (numbers.hundreds !== hundred) {

            // hundreds value changed; store this position.

            positions.push (i);

            // Update current hundreds and tens values.

            hundred = numbers.hundreds;

            ten = numbers.tens;

        } else {

            // hundreds stayed the same; check tens place.

            if (numbers.tens !== (ten + 1)) {

                // More than one tens places skipped; store this position and the previous.

                positions.push (i-1);

                positions.push (i);

            }

            ten = numbers.tens;

        }

    }

    // The last position is always needed.

    positions.push (count-1);

    return positions;

}

function getNumberSets (refsArray) {

    // Make an object of arrays for the number parts.

    var numbers = {hundreds: [], tens: []};

    // Make a regular expression to split each number.

    var regex = /^(\d+)\.(\d+)$/, match;

    // Loop through the references and split each one at the dot.

    for (var i = 0, count = refsArray.length; i < count; i += 1) {

        if (regex.test (refsArray)) {

            match = regex.exec (refsArray);

            // Convert the individual strings to integers and add them to the arrays.

           numbers.hundreds.push (parseInt(match[1], 10));

           numbers.tens.push (parseInt(match[2], 10));

        } else {

            // Regex doesn't match, so add zeros to the arrays.

           numbers.hundreds.push (0);

           numbers.tens.push (0);

        }

    }

    return numbers;

}

이 주제는 답변이 닫혔습니다.
최고의 답변: Jongware

I think I got it.

You get into trouble because you may have either one digit after the full stop, or two (or possibly even more). The trick, then, is not to treat it as a decimal number with a fraction, but as two separate numbers.

When comparing two numbers, as long as the first number in the first is not the same as the first number in the second set, skip. Then, count same-first-number increasing values for the second number. Only if this set is more than 2 counts long, emit the first and last number with an hyphen in between. For every other case, emit just the original starting number, and a comma plus space.

In code:

itemlist = app.selection[0].contents;

separated = itemlist.split(', ');

for (i=0; i<separated.length; i++)

          separated = [Number(separated.match(/^\d+/)),Number(separated.match(/\d+$/)), false];

// first is always okay

result = separated[0][0]+'.'+separated[0][1];

i = 1;

l = separated.length;

while (i < l)

{

          while (i < l && separated[0] != separated[i-1][0])

          {

                    result += ', '+separated[0]+'.'+separated[1];

                    i++;

          }

          c = 1;

          j = i;

          while (j < l && separated[0] == separated[j-1][0] &&

                    separated[1] == separated[j-1][1]+1)

                    c++, j++;

          if (c > 2)

          {

                    result += '-'+separated[j-1][0]+'.'+separated[j-1][1];

                    i = j;

          } else

          {

                    result += ', '+separated[0]+'.'+separated[1];

                    i++;

          }

}

alert (result);

1 답변

Jongware
Community Expert
Community Expert
December 4, 2012

Rick, an interesting challenge! I'm going to ponder about it.

Whilst doing that, perhaps you could take a peek in Peter Kahrel's script for concatenating index numbers: http://www.kahrel.plus.com/indesign/index_update.html

-- it seems to me this is a similar job.

frameexpert
Community Expert
frameexpertCommunity Expert작성자
Community Expert
December 4, 2012

Thanks for your response. There is one wrinkle that I don't think I mentioned in my original post: the client does not want ranges where there are only two consecutive numbers. For example, if you have 102.1, 102.2, 102.4, they don't want 102.1-102.2, 102.4; they want to leave it as is.

I will have a closer look at Peter's script, as it looks like it has some useful functions. Thanks again.

Rick

www.frameexpert.com
Jongware
Community Expert
Community Expert
December 4, 2012

I think I got it.

You get into trouble because you may have either one digit after the full stop, or two (or possibly even more). The trick, then, is not to treat it as a decimal number with a fraction, but as two separate numbers.

When comparing two numbers, as long as the first number in the first is not the same as the first number in the second set, skip. Then, count same-first-number increasing values for the second number. Only if this set is more than 2 counts long, emit the first and last number with an hyphen in between. For every other case, emit just the original starting number, and a comma plus space.

In code:

itemlist = app.selection[0].contents;

separated = itemlist.split(', ');

for (i=0; i<separated.length; i++)

          separated = [Number(separated.match(/^\d+/)),Number(separated.match(/\d+$/)), false];

// first is always okay

result = separated[0][0]+'.'+separated[0][1];

i = 1;

l = separated.length;

while (i < l)

{

          while (i < l && separated[0] != separated[i-1][0])

          {

                    result += ', '+separated[0]+'.'+separated[1];

                    i++;

          }

          c = 1;

          j = i;

          while (j < l && separated[0] == separated[j-1][0] &&

                    separated[1] == separated[j-1][1]+1)

                    c++, j++;

          if (c > 2)

          {

                    result += '-'+separated[j-1][0]+'.'+separated[j-1][1];

                    i = j;

          } else

          {

                    result += ', '+separated[0]+'.'+separated[1];

                    i++;

          }

}

alert (result);


.. these are my test strings (just two, but I think the code is sound). First line is original, second is concatenated.

Test items: 102.2, 102.3, 102.4, 102.5, 102.6, 102.8, 102.16, 102.17, 102.18, 102.19

-> 102.2-102.6, 102.8, 102.16-102.19

More items: 102.2, 103.3, 103.4, 104.5, 104.6, 104.7, 105.16, 105.17, 105.18, 105.19

-> 102.2, 103.3, 103.4, 104.5-104.7, 105.16-105.19

Message was edited by: [Jongware] You can omit the 'false' in line #5, it wasn't necessary after all. I tried to build the list in memory at first, and this was the Hyphen marker, but it's just easier to build the output while you scan.