aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/unitsToRanges.js70
-rw-r--r--test/unitsToRanges.js48
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()
})