chrisl-921fb74d
chrisl-921fb74d

Reputation: 23100

functional programming javascript chain filter operation defaults

Is there a way to filter an array and if it returns no objects default to the default option in a single chain?

e.g.

[1,2,3]
    .filter(isDivisibleByTen)
    // otherwise return whatever

I could write it like so

const result [1,2,3].filter(isDivisibleByTen) 
result ? result [0]

Upvotes: 1

Views: 72

Answers (4)

Finesse
Finesse

Reputation: 10801

Solution 1. Without side effects.

Maybe reduce is the only Array method that provides the array and can return an arbitrary value so you can use it:

[1,2,3]
    .filter(isDivisibleByTen)
    .reduce((_1, _2, _3, array) => array, 'defaultValue');

This solution is a bit processor-time-wasting (loops over the array without a benefit), but it can be combined with any chain easily.

Solution 2. The elegant one.

You can add a custom method to the Array prototype to make it available in a chain:

Array.prototype.filledOrDefault = function(defaultValue) {
    return this.length ? this : defaultValue;
}

[1,2,3]
    .filter(isDivisibleByTen)
    .filledOrDefault('defaultValue');

This solution is not recommended because it modifies a global value (Array) which is used by other scripts on a page.

Upvotes: 1

intentionally-left-nil
intentionally-left-nil

Reputation: 8274

I'll add a more verbose option here. This solution makes use of 3 pure functions, and then combines it in a pipeline. This is pretty typical of functional programming, and has the side benefit of being able to test the functions in isolation to make sure they do exactly what you want.

(As a sidebar other languages such as elixir make the chaining more syntactically pleasing, but I digress)

const defaultValue = "foobar";

const isDivisibleBy10 = n => n % 10 === 0;

const enforceDefault = numbers => numbers.length ? numbers : [defaultValue];

const unpackSingleton = numbers => numbers.length === 1 ? numbers[0] : numbers;


const pipeline = numbers =>
  unpackSingleton(
    enforceDefault(
      numbers.filter(isDivisibleBy10)
    )
  )

console.log(pipeline([2])); // no matches so returns default
console.log(pipeline([20])) // one match so returns the item

console.log(pipeline([1, 20, 10, 3])) // general case

Upvotes: 0

Nick
Nick

Reputation: 147146

You could try reduce(). This allows you to pass an initial value (the default return value) and then add values to the return array if they pass the test. So if no values pass the test, the function will return the default value.

console.log([1,21,30].reduce((t, v) => { return v % 10 === 0 ? (t = 'default' ? [v] : t.concat(v)) : t; }, 'default'))

    console.log([1,21,35].reduce((t, v) => { return v % 10 === 0 ? (t = 'default' ? [v] : t.concat(v)) : t; }, 'default'))

Upvotes: 1

zer00ne
zer00ne

Reputation: 43880

Here's what I think you are looking for in one line, but if a result is false, filter() will return nothing. In Demo 1 filter() is used. In Demo 2 map() is used with a return of an arbitrary value of "default" as the alternate return.


Demo 1


console.log([1, 21, 30].filter(a => a % 10 === 0));

Demo 2


console.log([1,21,30].map(a => a % 10 === 0 ? a : 'default'));

Upvotes: 1

Related Questions