0

I'm generating a list of numbers:

[1,2,3,4,6,7,8,9,11,12,13,14,16,17,18,19]

Note how some numbers are missing (in this case, every 5th number). I want to transform consecutive numbers into ranges, delimited by a dash.

In the above case, I'd like the output to be

"1-4,6-9,11-14,16-20"

How can I go about solving this problem?

royhowie
  • 11,075
  • 14
  • 50
  • 67
badjr13
  • 15
  • 3
  • I've done numerous google searches and I keep coming across RegEx which is something I haven't learned yet, but after briefly reviewing it, I'm not 100% sure that it is the solution to my problem based on the examples I keep coming across. I ran into one solution that involved numerous if else statements inside of the loop that generated the numbers, and the idea was almost clicking in my head, however the solution was written in PHP, and trying to decipher and translate over to JS only made my brain hurt more. Essentially, I haven't tried much yet, as an approach hasn't clicked yet. – badjr13 Apr 30 '15 at 20:10

2 Answers2

6
var convertToRanges = function (str) {
    // split the string at the commas and map it to an array of ints
    // NOTE: if you are passing an array, skip this step
    var pieces = str.split(",").map(Number)
    // ranges will be an array of arrays
    // each inner array will have 2 dimensions, representing the start/end
    // of a range
    // we want to initialize our first range to pieces[0], pieces[0],
    // or (only the first element)
      , ranges = [[pieces[0], pieces[0]]]
    // last index we accessed (so we know which range to update)
      , lastIndex = 0;

    for (var i = 1; i < pieces.length; i++) {
        // if the current element is 1 away from the end of whichever range
        // we're currently in
        if (pieces[i] - ranges[lastIndex][1] === 1) {
            // update the end of that range to be this number
            ranges[lastIndex][1] = pieces[i];
        } else {
            // otherwise, add a new range to ranges
            ranges[++lastIndex] = [pieces[i], pieces[i]];
        }
    }
    return ranges;
}

This will return an array of arrays:

console.log(convertToRanges("1,2,3,4,6,7,8,9,11,12,13,14,16,17,18,19"));
// -> [ [1, 4], [6, 9], [11, 14], [16, 19] ]

I'll leave it to you to figure out how to transform this to look like "1-4,6-9,11-14,16-20"

Hint: use Array.prototype.map and Array.prototype.join

royhowie
  • 11,075
  • 14
  • 50
  • 67
  • Thank you. I will work on this approach now. I should have noted that this is actually my very first piece of JavaScript I've written and this is my first question on Stackoverflow. My only prior experience is working through various online courses. The "range" api appears to be the missing link that I couldn't quite find. Thank you very much for your help! – badjr13 Apr 30 '15 at 20:18
  • @bdorrance what do you mean by `"range" api`? – royhowie Apr 30 '15 at 20:25
  • @bdorrance you've probably seen variables declared like this: `var a = 1, b = 2, c = 3;`. When you declare them on different lines, it's (now) considered good practice/convention to put the comma on the next line. This is to [prevent implicit globals](http://stackoverflow.com/questions/5786851/define-global-variable-in-a-javascript-function) (which you should never use). – royhowie Apr 30 '15 at 20:35
  • ahhh. that makes sense. so the "var" used on line 4 is also declaring the variables on lines 10 and 12. Excellent! Thank you so much for your help! – badjr13 Apr 30 '15 at 20:38
  • Hey man. So I've gotten my code to output the array of arrays. This is my first time working with a multidimensional array and also my first time working with the .map method. I've been messing around with this for days trying to figure this out before I came back to you. I don't want to copy and paste any code, but if you could give me further guidance on how to use the .map method to turn [ [1, 4], [6, 9], [11, 14], [16, 19] ] into "1-4,6-9,11-14,16-20", I would greatly appreciate it. I believe I understand what the .join method does and why it will be used in this situation. – badjr13 May 12 '15 at 19:20
  • Hint: what does `[1, 2, 3, 4].map(function (d) { return d*2;})` do? What does `[ [1, 4], [6, 9], [11, 14], [16, 19] ].map(function (d) { console.log(d); return d; })` print? `.map` goes through each item in the array and applies the `function` to it. So you need to transform each item in the array however you see fit. – royhowie May 12 '15 at 22:50
1

Solution returns completed string and handles isolated numbers that aren't part of a range

function convert(input) {
    var res = [],
        arr = typeof input == 'string' ? input.split(',').map(Number) : input;
    while (arr.length) {
        var curr = arr.shift(),
            lastIncIdx = null;

        if (arr.length && curr == arr[0] - 1) {
            var next = arr.length ? arr.reduce(function (last, curr, idx, arr) {
                if (curr == last + 1) {
                    lastIncIdx = idx;
                    return curr;
                } else {
                    return last;
                }
            }) : curr;

            if (next != curr) {
                arr.splice(0, lastIncIdx + 1);
                res.push(curr + '-' + next)
            }
        } else {
            res.push(curr)
        }
    }
    return res.join();
}

DEMO

charlietfl
  • 170,828
  • 13
  • 121
  • 150