1
\$\begingroup\$

I wanted to practice functional programming (FP) without using any library but using vanilla JS only. So I took a problem from Advent of Code - i.e. Day 2: Corruption Checksum (below is the 1st part of Day 2; My solution for the 2nd part of Day 2 can be found here).

/*jshint esversion: 6*/
{
  'use strict';

  const INPUT =
    `6046   6349    208 276 4643    1085    1539    4986    7006    5374    252 4751    226 6757    7495    2923
1432    1538    1761    1658    104 826 806 109 939 886 1497    280 1412    127 1651    156
244 1048    133 232 226 1072    883 1045    1130    252 1038    1022    471 70  1222    957
87  172 93  73  67  192 249 239 155 23  189 106 55  174 181 116
5871    204 6466    6437    5716    232 1513    7079    6140    268 350 6264    6420    3904    272 5565
1093    838 90  1447    1224    744 1551    59  328 1575    1544    1360    71  1583    75  370
213 166 7601    6261    247 210 4809    6201    6690    6816    7776    2522    5618    580 2236    3598
92  168 96  132 196 157 116 94  253 128 60  167 192 156 76  148
187 111 141 143 45  132 140 402 134 227 342 276 449 148 170 348
1894    1298    1531    1354    1801    974 85  93  1712    130 1705    110 314 107 449 350
1662    1529    784 1704    1187    83  422 146 147 1869    1941    110 525 1293    158 1752
162 1135    3278    1149    3546    3686    182 149 119 1755    3656    2126    244 3347    157 865
2049    6396    4111    6702    251 669 1491    245 210 4314    6265    694 5131    228 6195    6090
458 448 324 235 69  79  94  78  515 68  380 64  440 508 503 452
198 216 5700    4212    2370    143 5140    190 4934    539 5054    3707    6121    5211    549 2790
3021    3407    218 1043    449 214 1594    3244    3097    286 114 223 1214    3102    257 3345`;

  const sum = (a, b) => a + b;
  const diffOfMaxMin = (diff, val) => {
    const row = val.split(/\t/);
    const max = Math.max(...row);
    const min = Math.min(...row);

    return diff.concat(max - min);
  };

  const solution = INPUT.split(/\n/)
    .reduce(diffOfMaxMin, [])
    .reduce(sum);

  console.log("solution ", solution);
}

Is there a better way to write it in FP with pure JavaScript, i.e. no additional FP library? Any improvement suggestions are welcomed.

\$\endgroup\$
2
\$\begingroup\$

Note: The code examples below split lines on 1+ whitespace character instead of tabs - perhaps the way it was pasted in your question converted tabs to spaces...

Removing the array of diffs

Because the overall equation for this problem would be a summation of differences, the two calls to .reduce() can be combined into one- instead of having one function to essentially push a difference into an array and then a separate function to sum those differences, just add the difference to the cumulative total. In addition to reducing the number of function calls by the number of lines, the elimination of the array of differences should drastically reduce memory consumption.

const CumulativeDiffs = (totalSoFar, val) => {
    const row = val.split(/\s+/);    
    const max = Math.max(...row);
    const min = Math.min(...row);
    return totalSoFar + max - min; //add diff to cumulative total
};

const solution = INPUT.split(/\n/)
    .reduce(CumulativeDiffs, 0);

const INPUT =
  `6046   6349    208 276 4643    1085    1539    4986    7006    5374    252 4751    226 6757    7495    2923
1432    1538    1761    1658    104 826 806 109 939 886 1497    280 1412    127 1651    156
244 1048    133 232 226 1072    883 1045    1130    252 1038    1022    471 70  1222    957
87  172 93  73  67  192 249 239 155 23  189 106 55  174 181 116
5871    204 6466    6437    5716    232 1513    7079    6140    268 350 6264    6420    3904    272 5565
1093    838 90  1447    1224    744 1551    59  328 1575    1544    1360    71  1583    75  370
213 166 7601    6261    247 210 4809    6201    6690    6816    7776    2522    5618    580 2236    3598
92  168 96  132 196 157 116 94  253 128 60  167 192 156 76  148
187 111 141 143 45  132 140 402 134 227 342 276 449 148 170 348
1894    1298    1531    1354    1801    974 85  93  1712    130 1705    110 314 107 449 350
1662    1529    784 1704    1187    83  422 146 147 1869    1941    110 525 1293    158 1752
162 1135    3278    1149    3546    3686    182 149 119 1755    3656    2126    244 3347    157 865
2049    6396    4111    6702    251 669 1491    245 210 4314    6265    694 5131    228 6195    6090
458 448 324 235 69  79  94  78  515 68  380 64  440 508 503 452
198 216 5700    4212    2370    143 5140    190 4934    539 5054    3707    6121    5211    549 2790
3021    3407    218 1043    449 214 1594    3244    3097    286 114 223 1214    3102    257 3345`;

const CumulativeDiffs = (totalSoFar, val) => {
  const row = val.split(/\s+/);
  const max = Math.max(...row);
  const min = Math.min(...row);
  return totalSoFar + max - min;
};

const solution = INPUT.split(/\n/)
  .reduce(CumulativeDiffs, 0);
console.log("solution ", solution);

Sorting

I did consider sorting the array of values in each row using Array.sort(), which works, but the values all need to be converted to integers first (e.g. using parseInt(), or just a function that returns the number added to 0), which slows things down a little. But with that change there is no need to call Math.max() and then Math.min() - simply sort the numbers, then add the last element to the cumulative total and subtract the first element.

const CumulativeDiffs = (totalSoFar, val) => {      
  //let row = val.split(/\s+/).map((num) => +num); //still slower than finding min and max
  let row = val.split(/\s+/).map((num) => parseInt(num, 10));
  row.sort((a, b) => a - b); //sort numbers in each row
  return totalSoFar + row[(row.length - 1)] - row[0]; // add difference
};

const INPUT =
  `6046   6349    208 276 4643    1085    1539    4986    7006    5374    252 4751    226 6757    7495    2923
1432    1538    1761    1658    104 826 806 109 939 886 1497    280 1412    127 1651    156
244 1048    133 232 226 1072    883 1045    1130    252 1038    1022    471 70  1222    957
87  172 93  73  67  192 249 239 155 23  189 106 55  174 181 116
5871    204 6466    6437    5716    232 1513    7079    6140    268 350 6264    6420    3904    272 5565
1093    838 90  1447    1224    744 1551    59  328 1575    1544    1360    71  1583    75  370
213 166 7601    6261    247 210 4809    6201    6690    6816    7776    2522    5618    580 2236    3598
92  168 96  132 196 157 116 94  253 128 60  167 192 156 76  148
187 111 141 143 45  132 140 402 134 227 342 276 449 148 170 348
1894    1298    1531    1354    1801    974 85  93  1712    130 1705    110 314 107 449 350
1662    1529    784 1704    1187    83  422 146 147 1869    1941    110 525 1293    158 1752
162 1135    3278    1149    3546    3686    182 149 119 1755    3656    2126    244 3347    157 865
2049    6396    4111    6702    251 669 1491    245 210 4314    6265    694 5131    228 6195    6090
458 448 324 235 69  79  94  78  515 68  380 64  440 508 503 452
198 216 5700    4212    2370    143 5140    190 4934    539 5054    3707    6121    5211    549 2790
3021    3407    218 1043    449 214 1594    3244    3097    286 114 223 1214    3102    257 3345`;

const CumulativeDiffs = (totalSoFar, val) => {
  //let row = val.split(/\s+/).map((num) => +num); //still slower than finding min and max
  let row = val.split(/\s+/).map((num) => parseInt(num, 10));
  row.sort((a, b) => a - b);
  return totalSoFar + row[(row.length - 1)] - row[0];
};

const solution = INPUT.split(/\n/)
  .reduce(CumulativeDiffs, 0);

console.log("solution ", solution);

Compare these approaches with yours in this jsPerf.

Naming Things

Like @Igor mentioned in this answer to one of your previous posts the variable names could changed to better describe the values, and functions could better describe what they do. For example - summer might be a better name than sum, since it provides a sum of values.

\$\endgroup\$
  • \$\begingroup\$ These were really great improvement suggestions. But I wonder whether the function CumulativeDiffs isn't doing "too many" things. They are (1) calculating the diffs as well as (2) sum them up. Aren't "good, clean, maintainable" functions supposed to do only one thing and one thing only? \$\endgroup\$ – thadeuszlay Jan 7 '18 at 13:48
  • 1
    \$\begingroup\$ summer might be syntactically a good description of what the function is doing, but semantically it is kind of a misnomer. summer sounds like the season summer. \$\endgroup\$ – thadeuszlay Jan 7 '18 at 16:11
  • \$\begingroup\$ You could say that function violates the Single Responsibility principle... for more discussion on that topic, see this question and it’s answers; and yeah summer sounds like a season for English speakers... one could try to find an alternative like sumFunction, sumFn, summater, etc... while it is flattering that you accepted this answer, others might be inclined to answer if you wait longer... \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jan 8 '18 at 0:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.