diff options
-rw-r--r-- | lib/unitsToRanges.js | 70 | ||||
-rw-r--r-- | test/unitsToRanges.js | 48 |
2 files changed, 114 insertions, 4 deletions
diff --git a/lib/unitsToRanges.js b/lib/unitsToRanges.js index 8ffd7b0..eebd3ef 100644 --- a/lib/unitsToRanges.js +++ b/lib/unitsToRanges.js @@ -1,23 +1,85 @@ /** - * Convert a list of unit numbers into an addr:flats list. eg. converts 1,2,3,5 into 1-3;5 + * Convert a list of unit numbers into an addr:flats list. Examples: + * 1,2,3,5 => 1-3;5 + * 1a,2a,3a,5 => 1a-3a;5 + * 1,2,3-5 => 1-5 + * 1,2,3a-5a => 1-2;3a-5a * * @param {Array} units * * @returns {string} addr:flats list */ module.exports = (units) => { + const regexp = /^(?<pre>\D*)(?<num>\d*)(?<suf>\D*)$/ + + // expand out any existing ranges which may be mixed into the input + const expandedUnits = units + .slice() + .reduce((acc, cur) => { + const rangeParts = cur.split('-') + if (rangeParts.length === 2) { + // was a range, pull out prefix and suffix + const fromMatch = rangeParts[0].match(regexp) + const toMatch = rangeParts[1].match(regexp) + + // matching prefix and suffix + if (fromMatch.groups.pre === toMatch.groups.pre && fromMatch.groups.suf === toMatch.groups.suf) { + for (let i = fromMatch.groups.num; i <= toMatch.groups.num; i++) { + acc.push(`${fromMatch.groups.pre}${i}${fromMatch.groups.suf}`) + } + } else { + // prefix/suffix don't match in the from-to, so just pass as is + console.log(`passed a range with different prefix/suffix: ${rangeParts[0]}-${rangeParts[1]}`) + acc.push(cur) + } + } else if (rangeParts.length > 2) { + // 1-2-3 not sure if this ever occures, but just pass as is + console.log(`Unsupported range ${cur}`) + acc.push(cur) + } else { + // was not a range + acc.push(cur) + } + return acc + }, []) + + // combine individual unit values into ranges + const existingRanges = [] + // adapted from https://stackoverflow.com/a/54973116/6702659 - const unitRanges = units + const formedRanges = expandedUnits .slice() + .map(unit => { + if (unit.split('-').length > 1) { + existingRanges.push(unit) + return [] + } else { + return [unit] + } + }) + .flat() .sort((a, b) => a - b) .reduce((acc, cur, idx, src) => { - if ((idx > 0) && ((cur - src[idx - 1]) === 1)) { - acc[acc.length - 1][1] = cur + const curParts = cur.match(regexp) + const prevParts = idx > 0 ? src[idx - 1].match(regexp) : null + + const curNum = curParts.groups.num + const prevNum = prevParts ? prevParts.groups.num : null + + if ((idx > 0) && ((curNum - prevNum) === 1)) { + if (prevParts ? (curParts.groups.pre === prevParts.groups.pre && curParts.groups.suf === prevParts.groups.suf) : true) { + acc[acc.length - 1][1] = cur + } else { + acc.push([cur]) + } } else { acc.push([cur]) } return acc }, []) .map(range => range.join('-')) + + const unitRanges = [...formedRanges, ...existingRanges] + return unitRanges.length ? unitRanges.join(';') : null } diff --git a/test/unitsToRanges.js b/test/unitsToRanges.js index 53d08aa..2702b4a 100644 --- a/test/unitsToRanges.js +++ b/test/unitsToRanges.js @@ -39,5 +39,53 @@ test('units list to addr:flats', t => { 'range and singular' ) + t.same( + unitsToRanges(['3', '1']), + '1;3', + 'singular sorted' + ) + + t.same( + unitsToRanges(['1', '2', '5', '4']), + '1-2;4-5', + 'range sorted' + ) + + t.same( + unitsToRanges(['1-2', '3-4']), + '1-4', + 'accepted ranged input' + ) + + t.same( + unitsToRanges(['1A', '2A']), + '1A-2A', + 'with suffix' + ) + + t.same( + unitsToRanges(['1A', '2A', '3']), + '1A-2A;3', + 'partially with suffix' + ) + + t.same( + unitsToRanges(['1A', '2B']), + '1A;2B', + 'different suffix not merged' + ) + + t.same( + unitsToRanges(['A1b', 'A2b']), + 'A1b-A2b', + 'prefix merged' + ) + + t.same( + unitsToRanges(['A1b', 'C2d']), + 'A1b;C2d', + 'different prefix not merged' + ) + t.end() }) |