Pattern finding function for years between 1998 and 2017 with several test cases Pattern matching for years, I need to be able to return all years from a string. 


When there is a dash "-" I need to return all numbers between. For example, '2004-2006' should return an array of numbers [2004,2005,2006];
If the number at the beginning of the dash is larger than the number following it, nothing should be printed out. For example '2006-2005' should return [].
years between commas should simply be returned as long as they do not exceed the current year, and are not less than 1999. For example '1998,2020' should return an empty array [].  
There should be no duplicates and the values should be ordered.


I believe my code could be written in a more elegant and consise way. 



Here are my test cases:



assert.deepEqual(parseRange('2002-2005, 2002-2005, 2002 - 2005'), [2002, 2003, 2004, 2005]);
assert.deepEqual(parseRange('2017'), [2017]);
assert.deepEqual(parseRange('2017 - 2015'), []);
assert.deepEqual(parseRange('2015 - 2015'), [2015]);
assert.deepEqual(parseRange('1999 , 2000,    , 2008'), [1999, 2000, 2008]);
assert.deepEqual(parseRange('2015, 2014, 2010'), [2010, 2014, 2015]);
assert.deepEqual(parseRange('1999, 3000'), [1999]);
assert.deepEqual(parseRange('1998, 2020'), []);






Here is my working Code



function parseRange(string) {
  return [...new Set(string.split(',').map(range =&gt; range.includes('-') ? range.split('-').map(num =&gt; +num) : +range)
    .filter(set =&gt; Array.isArray(set) ? set[0] &lt;= set[1] : set &gt; 1998 &amp;&amp; set &lt;= new Date().getFullYear()).reduceRight((arr, val) =&gt;
      arr.concat(Array.isArray(val) ? Array(val[1] - val[0] + 1).fill().map((_, num) =&gt; val[0] + num) : val), []))].sort();
}

console.log(parseRange('2002-2005, 2002-2005, 2002 - 2005'));
console.log(parseRange('2017'));
console.log(parseRange('2017 - 2015'));
console.log(parseRange('2015 - 2015'));
console.log(parseRange('1999 , 2000,    , 2008'));
console.log(parseRange('2015, 2014, 2010'));
console.log(parseRange('1999, 3000'));
console.log(parseRange('1998, 2020'));
You're asking for elegance and conciseness. Elegance doesn't always mean short/hackish, and neither does conciseness. You should never sacrifice readability for brevity unless you're codegolfing. As per your comment, just because you can read it doesn't mean the next maintainer or developer can. Someone (maybe you) revisiting this in 5 years should know what the code does without having to pick through it. Let's start by breaking apart the code into its components.

function parseRange2(string) {
  var expr = string.split(',').map(range =&gt; range.includes('-') ? range.split('-').map(num =&gt; +num) : +range)
  expr = expr.filter(set =&gt; Array.isArray(set) ? set[0] &lt;= set[1] : set &gt; 1998 &amp;&amp; set &lt;= new Date().getFullYear())
  expr = expr.reduceRight((arr, val) =&gt;
      arr.concat(Array.isArray(val) ? Array(val[1] - val[0] + 1).fill().map((_, num) =&gt; val[0] + num) : val), [])
  return [...new Set(expr)].sort()
}


This looks a little more readable. However, someone reading this for a production codebase would have to actually figure out what each expression did. Let's rename some variables and make this easier to digest.

function parseRange2(string) {
  // This expression is still kind of pushing it in terms of readability.
  // Split string in ranges and values
  var ranges = string.split(',').map(range =&gt; range.includes('-') ? range.split('-').map(num =&gt; +num) : +range)
  // Remove invalid ranges
  var validRanges = ranges.filter(set =&gt; Array.isArray(set) ? set[0] &lt;= set[1] : set &gt; 1998 &amp;&amp; set &lt;= new Date().getFullYear())
  // Expand range arrays
  var expandedRanges = validRanges.reduceRight((arr, val) =&gt;
      arr.concat(Array.isArray(val) ? Array(val[1] - val[0] + 1).fill().map((_, num) =&gt; val[0] + num) : val), [])
  // Return sorted and unique years
  return [...new Set(expandedRanges)].sort()
}


Now let's refactor some of this stuff into helper functions.

const getYearRange = (start, end) =&gt; {
  return Array(end - start + 1).fill().map((_, num) =&gt; start + num)
}

const isValidEntry = entry =&gt; {
  return Array.isArray(entry) ? entry[0] &lt;= entry[1] : entry &gt; 1998 &amp;&amp; entry &lt;= new Date().getFullYear()
}

const parseRange2 = string =&gt; {
  // This expression is still kind of pushing it in terms of readability.
  // Split string in ranges and values
  var ranges = string.split(',').map(range =&gt; range.includes('-') ? range.split('-').map(num =&gt; +num) : +range)
  // Remove invalid ranges
  var validRanges = ranges.filter(isValidEntry)
  // Expand range arrays
  var expandedRanges = validRanges.reduceRight((arr, val) =&gt;
      arr.concat(Array.isArray(val) ? getYearRange(val[0], val[1]) : val), [])
  // Return sorted and unique years
  return [...new Set(expandedRanges)].sort()
}


You get the point. You should refactor further to make the code easier to maintain and develop.

Now on the other hand, if you're looking to use every JavaScript hack in the book to golf your code, you could use reduce() instead of reduceRight().

For -2 characters, you could use the following to generate your date range:

[...Array(val[1] - val[0] + 1)].map((_, num) =&gt; val[0] + num)


as opposed to:

Array(val[1] - val[0] + 1).fill().map((_, num) =&gt; val[0] + num)


You can use the truthiness of arr[0] instead of Array.isArray(arr) to check if an object is an array, which yields the following code:

function parseRange2(string) {
  return [...new Set(string.split(',').map(range =&gt; range.includes('-') ? range.split('-').map(num =&gt; +num) : +range)
    .filter(set =&gt; set[0] ? set[0] &lt;= set[1] : set &gt; 1998 &amp;&amp; set &lt;= new Date().getFullYear()).reduce((arr, val) =&gt;
      arr.concat(val[0] ? [...Array(val[1] - val[0] + 1)].map((_, num) =&gt; val[0] + num) : val), []))].sort();
}


Addendum: Have you tried '1-10, 1999, 2002-2005, 2002-2005, 2002 - 2005, 2010' as a test case?

Result: [ 1, 10, 1999, 2, 2002, 2003, 2004, 2005, 2010, 3, 4, 5, 6, 7, 8, 9 ]