4

I'm writing a typescript function that accepts a numeric array (i.e., type: number[]) and calculates its mean. In addition, I want to account for when the input array might contain some null values. To this end, I added an argument, that when set to true, tells the function to remove nulls before calculating the mean.

But I can't figure out the proper way to do this, as I can't override the input within the function.

Here's my code for calcMean()

function calcMean(arr: number[], nullRemove: boolean = true): number {
    if (nullRemove) { // if TRUE, which is the default, then throw out nulls and re-assign to `arr`
        const arr: number[] = arr.filter((elem) => elem !== null);
    }
    // then simply calculate the mean of `arr`
    return arr.reduce((acc, v, i, a) => acc + v / a.length, 0); // https://stackoverflow.com/a/62372003/6105259
}

I then get an error:

Block-scoped variable 'arr' used before its declaration.ts(2448)

I also tried using let in addition or instead of const but it didn't solve the problem.

What am I missing here?

Emman
  • 3,695
  • 2
  • 20
  • 44
  • What should happen when `nullRemove` is `false`? – jsejcksn Mar 03 '22 at 09:48
  • @jsejcksn if `nullRemove` is `false` then the IF block shouldn't be executed, thus `return arr.reduce((acc, v, i, a) => acc + v / a.length, 0);` is the only thing the function does. – Emman Mar 03 '22 at 13:12

3 Answers3

5

Two options for you:

1. Don't redeclare it, just reassign it:

function calcMean(arr: number[], nullRemove: boolean = true): number {
    if (nullRemove) { // if TRUE, which is the default, then throw out nulls and re-assign to `arr`
        arr = arr.filter((elem) => elem !== null);
        // ^^^ No `const` here
    }
    // then simply calculate the mean of `arr`
    return arr.reduce((acc, v, i, a) => acc + v / a.length, 0); // https://stackoverflow.com/a/62372003/6105259
}

Some folks believe reassigning parameters is poor style (I'm not one of them provided the function is quite small as in your case, but I understand the argument), so alternatively:

2. Assign to a different variable:

function calcMean(arr: number[], nullRemove: boolean = true): number {
    // Remove `null` if requested
    const a = nullRemove ? arr.filter(elem => elem !== null) : arr;
    // then simply calculate the mean of `arr`
    return a.reduce((acc, v, i, a) => acc + v / a.length, 0); // https://stackoverflow.com/a/62372003/6105259
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    I like that you addressed the topic of parameter immutability. Also: using inverted ternary conditionals objectively increases cognitive load when reading source code. – jsejcksn Mar 03 '22 at 09:14
  • @jsejcksn - Re the inversion: The purpose there was to avoid making it hard to find the `arr` at the end. But sure, it could absolutely be `const a = nullRemove ? arr.filter(elem => elem !== null) : arr;` I waffled on it when writing it. You're probably right I should swap it around. – T.J. Crowder Mar 03 '22 at 09:32
1

The code in your question doesn't currently allow for null values to be included in the number[] parameter.

I like for my code to be explicit about what's happening, so in the case where there might be nulls mixed in the array of numbers, I would explicitly convert them to 0 in the case that they are not removed:

TS Playground

/** Each null element is either omitted or converted to 0 */
function handleNullValues (arr: readonly (number | null)[], omit = true): number[] {
  return omit ?
    arr.filter((value): value is number => value !== null)
    : arr.map(value => value === null ? 0 : value);
}

function calcMean (arr: readonly (number | null)[], nullRemove = true): number {
  const numbers = handleNullValues(arr, nullRemove);
  return numbers.reduce((sum, n) => sum + n) / numbers.length;
}


// Test
console.assert(calcMean([null, 1, 3, 7, 9]) === 5); // ok
console.assert(calcMean([null, 10, 1, 3, 7, 9], false) === 5); // ok


Edit: Updated in response to your comment.

TS Playground

function calcMean (arr: readonly (number | null)[]): number {
  let count = 0;
  return arr.filter((value): value is number => {
    const isNumber = typeof value === 'number';
    if (isNumber) count += 1;
    return isNumber;
  }).reduce((sum, n) => sum + n) / count;
}


// Test
console.assert(calcMean([1, 3, 7, 9]) === 5); // ok
console.assert(calcMean([null, 1, 3, 7, 9]) === 5); // ok
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • Thanks. Converting `null` to `0` is mathematically problematic because those `0`s are still counted in the denominator. Thus, dropping `null` before calculation is the only sensible pre-step. – Emman Mar 03 '22 at 13:18
  • @Emman In that case (`null` values should always be removed), the additional argument is not necessary since there is no alternate logic branch needed. See my updated answer. – jsejcksn Mar 03 '22 at 23:50
1

In my case for typescript, I also received the message

Block-scoped variable 'filename' used before its declaration.ts(2448)

exportAsExcel () {
   const format = 'xlsx'
   const exportSelectedOnly = true
   const filename = 'test'
   this.$refs.grid.exportTable(format, exportSelectedOnly, filename)
}

Solved by

const format = 'xlsx' as string
const exportSelectedOnly = true as boolean
const filename = 'test' as string
BoMBxDEV
  • 21
  • 2