1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
/**
* 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
* @param {Array} [sourceAddresses] - the source addresses where these units came from, used for debugging
*
* @returns {string} addr:flats list
*/
module.exports = (units, sourceAddresses) => {
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]}`)
if (sourceAddresses) {
console.log(JSON.stringify(sourceAddresses, null, 2))
}
acc.push(cur)
}
} else if (rangeParts.length > 2) {
// 1-2-3 not sure if this ever occurs, but just pass as is
console.log(`Unsupported range ${cur}`)
if (sourceAddresses) {
console.log(JSON.stringify(sourceAddresses, null, 2))
}
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 formedRanges = [...new Set(expandedUnits)]
.slice()
.map(unit => {
if (unit.split('-').length > 1) {
existingRanges.push(unit)
return []
} else {
return [unit]
}
})
.flat()
.sort(sortNumbers)
.reduce((acc, cur, idx, src) => {
const curParts = cur.match(regexp)
const prevParts = idx > 0 ? src[idx - 1].match(regexp) : null
if (!curParts) {
console.log(`"${cur}" didn't match regexp for prefix number suffix`)
if (sourceAddresses) {
console.log(JSON.stringify(sourceAddresses, null, 2))
}
acc.push([cur])
return acc
}
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
}
/* custom sort function where 2 goes before 1A and 1A goes before 1B */
function sortNumbers(a, b) {
if (Number.isInteger(Number(a)) && Number.isInteger(Number(b))) {
// both are integers
return a - b
} else if (Number.isInteger(Number(a)) && !Number.isInteger(Number(b))) {
// a is integer but b isn't, so a goes before b
return -1
} else if (!Number.isInteger(Number(a)) && Number.isInteger(Number(b))) {
// a isn't integer but b is, so a goes after b
return 1
} else {
// neither are integers
return a.localeCompare(b)
}
}
|