Split array into two different arrays using functional JavaScript

collateBy

I just shared a similar answer here

I like this solution better because it abstracts away the collation but allows you to control how items are collated using a higher-order function.

Notice how we don't say anything about animal.length or < 4 or animals[0].push inside collateBy. This procedure has no knowledge of the kind of data you might be collating.

// generic collation procedure
const collateBy = f => g => xs => {
  return xs.reduce((m,x) => {
    let v = f(x)
    return m.set(v, g(m.get(v), x))
  }, new Map())
}

// custom collator
const collateByStrLen4 =
  // collate by length > 4 using array concatenation for like elements
  // note i'm using `[]` as the "seed" value for the empty collation
  collateBy (x=> x.length > 4) ((a=[],b)=> [...a,b])

// sample data
const arr = ['horse','elephant','dog','crocodile','cat']

// get collation
let collation = collateByStrLen4 (arr)

// output specific collation keys
console.log('greater than 4', collation.get(true))
console.log('not greater than 4', collation.get(false))

// output entire collation
console.log('all entries', Array.from(collation.entries()))

Check out that other answer I posted to see other usage varieties. It's a pretty handy procedure.


bifilter

This is another solution that captures both out outputs of a filter function, instead of throwing away filtered values like Array.prototype.filter does.

This is basically what your reduce implementation does but it is abstracted into a generic, parameterized procedure. It does not use Array.prototype.push but in the body of a closure, localized mutation is generally accepted as OK.

const bifilter = (f,xs) => {
  return xs.reduce(([T,F], x, i, arr)=> {
    if (f(x, i, arr) === false)
      return [T, [...F,x]]
    else
      return [[...T,x] ,F]
  }, [[],[]])
}

const arr = ['horse','elephant','dog','crocodile','cat']

let [truthy,falsy] = bifilter(x=> x.length > 4, arr)
console.log('greater than 4', truthy)
console.log('not greater than 4', falsy)

Though it might be a little more straightforward, it's not nearly as powerful as collateBy. Either way, pick whichever one you like, adapt it to meet your needs if necessary, and have fun !


If this is your own app, go nuts and add it to Array.prototype

// attach to Array.prototype if this is your own app
// do NOT do this if this is part of a lib that others will inherit
Array.prototype.bifilter = function(f) {
  return bifilter(f,this)
}

A shorter .reduce() version would be:

const split = arr.reduce((animArr, animal) => {
  animArr[animal.length < 4 ? 0 : 1].push(animal);
  return animArr
}, [ [], [] ]);

Which might be combined with destructuring:

const [ lessThanFour,  fourAndMore ] = arr.reduce(...)

The function you are trying to build is usually known as partition and can be found under that name in many libraries, such as underscore.js. (As far as I know its not a builtin method)

var threeFourArr = _.partition(animals, function(x){ return x.length < 4 });

I don't like this too much, because it seems that the data structure is going to give a bit of problems, seeing that it is an array of arrays

Well, that is the only way to have a function in Javascript that returns two different values. It looks a bit better if you can use destructuring assignment (an ES6 feature):

var [smalls, bigs] = _.partition(animals, function(x){ return x.length < 4 });

Look at it as returning a pair of arrays instead of returning an array of arrays. "Array of arrays" suggests that you may have a variable number of arrays.

I've managed to look at similar questions online as well as Stack Overflow, but many of these break the idea of immutability by using push() or they have very unreadable implementations, which in my opinion breaks the expressiveness of functional programming.

Mutability is not a problem if you localize it inside a single function. From the outside its just as immutable as before and sometimes using some mutability will be more idiomatic than trying to do everything in a purely functional manner. If I had to code a partition function from scratch I would write something along these lines:

function partition(xs, pred){
   var trues = [];
   var falses = [];
   xs.forEach(function(x){
       if(pred(x)){
           trues.push(x);
       }else{
           falses.push(x);
       }
   });
   return [trues, falses];
}