Reputation: 12900
I'm trying to take an array like this:
[1999, 2000, 2001, 2002, 2003, 2004, 2006, 2008, 2009, 2010]
and make it output:
1999 - 2004, 2006, 2008 - 2010
It appeared to be a simple task, but can't seem to get it right. I'm assuming Array.reduce
is the right tool for this:
const years = [1999, 2000, 2001, 2002, 2003, 2004, 2006, 2008, 2009, 2010];
const str = years.reduce((acc, curr, idx) => {
const prevYear = years[idx - 1];
const nextYear = years[idx + 1];
if (!prevYear) {
acc.push(curr.toString());
return acc;
}
if (prevYear === curr - 1 && nextYear === curr + 1) {
if (acc[acc.length - 1] !== '-') {
acc.push('-');
}
return acc;
} else {
acc.push(curr.toString());
if (nextYear) {
acc.push(',');
}
return acc;
}
return acc;
}, []);
console.log(str.join(''))
I'm almost there but I'm trying to make it look as clean as possible without a bunch of if
statements.
I'm aware I can take the poor-mans route and just run a str.replace(',-', '-')
to fix my last little hurdle, but I don't like that.
Upvotes: 3
Views: 102
Reputation: 2355
Using a traditional for loop, you could write:
const years = [1999, 2000, 2001, 2002, 2003, 2004, 2006, 2008, 2009, 2010];
const sliceInterval = 5;
const summary = [];
let sliceCounter = 0;
let segment = "";
for (var i = 0; i < years.length; i++) {
const currentYear = years[i];
if (sliceCounter === 0) segment = currentYear + " - ";
if (sliceCounter !== 0 && (i === years.length - 1 || sliceCounter % sliceInterval === 0)) {
segment += currentYear;
summary.push(segment);
sliceCounter = 0;
segment = "";
} else sliceCounter++;
}
console.log(summary.join(", "));
Upvotes: 0
Reputation: 829
Should be something like this
const years = [1999, 2001, 2000, 2002, 2003, 2004, 2006, 2008, 2009, 2010]
const getGroups = (years) => {
const groups = []
let lastYear = null;
let currentGroup = {
from: null,
to: null
}
years.sort().forEach(year => {
if (currentGroup.from === null) {
currentGroup.from = year
currentGroup.to = year
} else if (lastYear && lastYear + 1 === year) {
currentGroup.to = year
} else {
if (currentGroup.to === null) {
currentGroup.to = lastYear
}
groups.push(currentGroup)
currentGroup = {
from: year,
to: null
}
}
lastYear = year
})
groups.push(currentGroup)
return groups
}
const printGroups = (groups) => {
const groupsString = groups.map(group => {
if(group.from === group.to)
return `${group.from}`
else
return `${group.from} - ${group.to}`
}).join(', ')
console.log(groupsString)
}
const groups = getGroups(years)
printGroups(groups)
Upvotes: 0
Reputation: 370699
An alternative (which I think is a bit easier to conceptualize) is to iterate over the range rather than over the items in the array. When finding an item, track forward until no more found years in a row exist. If more than one year in a row was found, add that range, else just add the one year:
const getRanges = (arr) => {
const set = new Set(arr); // for less computational complexity later
const min = Math.min(...arr);
const max = Math.max(...arr);
const output = [];
for (let i = min; i <= max; i++) {
if (!set.has(i)) {
continue;
}
const thisMin = i;
while(set.has(i)) {
i++;
}
output.push(i === thisMin + 1 ? thisMin : `${thisMin} - ${i - 1}`);
}
return output;
};
console.log(getRanges([1999, 2000, 2001, 2002, 2003, 2004, 2006, 2008, 2009, 2010]));
An issue with using reduce
here is that you need to keep track of additional state (the number of items that are in the so-far-consecutive range, and their values) other than the acc
output array, which gets really messy if you're trying to encapsulate everything inside the reduce
callback. It's possible to do, but I wouldn't recommend it.
Upvotes: 6
Reputation: 1497
years.reduce((acc, cur, idx, src) => {
if ((idx > 0) && ((cur - src[idx - 1]) === 1))
acc[acc.length - 1][1] = cur;
else acc.push([cur]);
return acc;
}, []).map(range => range.join(' - '));
I created an array of values and added indexes as a 2d array and then joined them using the separator.
Upvotes: 2