3
\$\begingroup\$

This is an interview question. Take a list of strings that represent port ranges like these:

const systemPorts = '11000-11001, 11100-11101, 12000-12010, 13000'; 
const customerports = '11000-11003, 11100-11106, 12000-12015, 13000-13003'; 

And return a merged list of ports in the form of a string:

'11100-11106,12000-12015,13000-13003,11000-11003'

Please review with an eye on commenting and code density, I feel I may have gone too far.

//ranges is a list of objects with a min and max property
//range is an object with a min and max property that will be merged in to ranges
function mergeRange(ranges, range){
  const [min,max] = range.split("-").map(s=>Number(s));
  //Does any existing range contain either min or max?
  const matches = ranges.filter(o=>(o.min <= min && o.max >= min) || (o.min <= max && o.max >= max) || (o.min > min && o.max < max));
  //Handle the different cases
  if(matches.length == 0){
    //Nothing matches, just add the range
    ranges.push({min,max});
  }else if(matches.length == 1){
    //One match, extend if needed
    matches[0].min = Math.min(matches[0].min, min);
    matches[0].max = Math.max(matches[0].max, max);
  }else{
    //More than one match
    //Remove the matches that are there
    for(const match of matches){
      const index = ranges.indexOf(match);  
      ranges.splice(index, 1);
    }
    matches = matches.concat([min,max]);
    //Add a new range which contains all the matches
    ranges.push({min: Math.min(...matches.map(match=>match.min)) , max: Math.max(...matches.map(match=>match.max))});
  }
}

//['11000-11001, 11100-11101, 12000-12010, 13000', '11000-11003, 11100-11106, 12000-12015, 13000-13003'] 
//becomes '11100-11106,12000-12015,13000-13003,11000-11003'
function mergePortRanges(portRangesList){
  let ranges = [];
  for(const portRanges of portRangesList){
    for(let range of portRanges.split(",").sort().map(s=>s.trim())){
      //make a single number a range (13000 -> 13000-13000)
      range = range.includes('-') ? range : `${range}-${range}`;
      mergeRange(ranges, range);
    }
  }
  return ranges.map(range => `${range.min}-${range.max}`).join(",");
}

const systemPorts = '11000-11001, 11100-11101, 12000-12010, 13000'; 
const customerports = '11000-11003, 11100-11106, 12000-12015, 13000-13003'; 
console.log(mergePortRanges([systemPorts, customerports]));

console.log(mergePortRanges(['11000-11121, 11100-11101']));
\$\endgroup\$
4
  • 1
    \$\begingroup\$ "I feel I may have gone too far" - I'd say you only have started and there are lots of spaces left that you can condense further. \$\endgroup\$
    – wOxxOm
    Nov 15, 2019 at 13:10
  • \$\begingroup\$ Could there be intersected ranges like '11000-11121, 11100-11101, ...? If so, how it would effect the final result? \$\endgroup\$ Nov 15, 2019 at 14:04
  • \$\begingroup\$ Yup, did not test for that case, this now returns 11000-11121 \$\endgroup\$
    – konijn
    Nov 15, 2019 at 14:40
  • \$\begingroup\$ What about more cases like '5-10, 4-12, 1-13' should output 1-13 and '5-10, 4-12, 13' should output 4-13 \$\endgroup\$
    – Rager
    Nov 15, 2019 at 22:56

1 Answer 1

1
\$\begingroup\$

One criticism I will give you is I think you went overboard with the comments. Some of the code will speak for itself if it's well written. Not saying my code is well written, I'm just saying.

Here's what I came up with. I'm afraid it's not much cleaner than your version, if at all. I spent a good amount of time on it too.

function mergePortRanges(input){

    //Group all ranges into one array
    let portList = [];
    input.forEach( list => {
        portList.push(...list.split(',').map( range => range.trim()));
    });

    //Break the ranges into objects with min max integers
    portList = portList.map( range => {
        range = range.split('-').map( s => +s );
        if(range.length === 1)
            range.push(range[0]);
        return { min: range[0], max: range[1] };
    });

    //Sort by mins
    portList.sort((a,b) => (a.min > b.min) ? 1 : -1);

    //Group ranges starting with same min and grab the highest one.
    let newPortList = [];
    let curRangeMin = 0;
    for(let x = 0; x < portList.length; x++){
        let range = portList[x];

        if(curRangeMin === range.min) continue;
        else curRangeMin = range.min;

        let group = portList.filter( old => range.min == old.min); //Get full range
        group.map( (a,b) => (a.max > b.max) ? 1 : -1 ); //Sort by max
        newPortList.push( group[0] ); //Grab highest in range and push.
    }

    return newPortList.map( range => {
        if(range.min == range.max)
            return range.min;
        else
            return `${range.min}-${range.max}`;
    }).join(',');
}

I still feel like this could be refined quite a bit, but I've already spent so much time on it lol. Maybe I'll try again when I'm smarter.

\$\endgroup\$
2
  • \$\begingroup\$ Yeah... this last for loop won't work on certain inputs. I'm working to refine it. \$\endgroup\$
    – Rager
    Nov 15, 2019 at 20:58
  • \$\begingroup\$ Alright, I think it's looking alright. Based on what I did just now, I feel like you did a good job on yours. \$\endgroup\$
    – Rager
    Nov 15, 2019 at 21:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.