Pual
Pual

Reputation: 105

Flatten and sort arrays with sub arrays

I have some arrays, each of which can consist of a number of sub arrays or objects. For example

var array = [{id: 1, gpa: 3.11}, [{id: 2, gpa: 2.9}, {id: 11, gpa: 3.9}, [{id: 9, gpa: 2.11}]], {id: 7, gpa: 3.31}]

What I want to do is to flatten the elements of this array into one level, so it will be as follows:

[{id: 1, gpa: 3.11}, {id: 2, gpa: 2.9}, {id: 11, gpa: 3.9}, {id: 9, gpa: 2.11}, {id: 7, gpa: 3.31}]

Then order them by gpa:

[{id: 11, gpa: 3.9}, {id: 7, gpa: 3.31}, {id: 1, gpa: 3.11}, {id: 9, gpa: 2.11}, {id: 2, gpa: 2.9}]

Currently, the way I am using is as follows

function search(arr, obj){
 if(Array.isArray(obj)){
  arr.forEach(sub=>search(arr, sub));
 }else{
  arr.push(obj);
 }
}

var array = [{id: 1, gpa: 3.11}, [{id: 2, gpa: 2.9}, {id: 11, gpa: 3.9}, [{id: 9, gpa: 2.11}]], {id: 7, gpa: 3.31}];
var flatten = [];
search(flatten, array);
console.log(flatten);

Then, I use a sort comparator to sort flatten.

Is there a better way to do that using the builtin methods of Javascript?

Upvotes: 2

Views: 919

Answers (2)

Jecfish
Jecfish

Reputation: 4189

Option 1 - Example using lodash/fp - flattenDeep and sortBy:

const arr = [{id: 1, gpa: 3.11}, [{id: 2, gpa: 2.9}, {id: 11, gpa: 3.9}, [{id: 9, gpa: 2.11}]], {id: 7, gpa: 3.31}];

const flattenArr = _.flattenDeep(arr);
const orderByGpa = _.sortBy('gpa');
const answer = orderByGpa(flattenArr);


console.log(answer);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js"></script>

Option 2: vanilla Javascript using reduce and sort.

const arr = [
  {id: 1, gpa: 3.11}, 
  [
    {id: 2, gpa: 2.9}, 
    {id: 11, gpa: 3.9}, 
    [
      {id: 9, gpa: 2.11}
    ]
  ], 
    {id: 7, gpa: 3.31}
 ];
 
function flattenDeep(list, initial = []) {
  const reducer = (accumulator, currentValue) => accumulator.concat(currentValue);
  
  const result = list.reduce(reducer, initial);
  const shouldFlatten = result.find(x => Array.isArray(x));
  
  return shouldFlatten ? flattenDeep(result) : result;
};

function sortBy(list, field, direction = 'asc') {
  return list.sort((a, b) => {
    const func = {
      asc: () => a[field] > b[field],
      desc: () => a[field] < b[field],
    };
    
    return func[direction]();
    
  });
}

const flattenArr = flattenDeep(arr);
const sortedArr = sortBy(flattenArr, 'gpa', 'desc');

console.log(sortedArr);

Recommend option 1. Using lodash/fp.

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074028

The way you're doing it is just fine (if you make the arr => obj fix). You're always going to need the branch to handle arrays vs. objects (unless we get into creating unnecessary arrays/overhead). So your solution is simple and efficient.

With the fix:

function search(arr, obj){
 if(Array.isArray(obj)){
  obj.forEach(sub=>search(arr, sub));
 }else{
  arr.push(obj);
 }
}

var array = [{id: 1, gpa: 3.11}, [{id: 2, gpa: 2.9}, {id: 11, gpa: 3.9}, [{id: 9, gpa: 2.11}]], {id: 7, gpa: 3.31}];
var flatten = [];
search(flatten, array);
console.log(flatten);
.as-console-wrapper {
  max-height: 100% !important;
}

If you wanted to throw some ES2015 features at it, we could use a for-of instead of forEach, but it involves the creation of an iterator object and multiple iteration result objects and multiple hidden function calls, so it might be better in terms of clarity, but probably not in terms of efficiency (clarity is usually more important, but there isn't a big clarity difference here in my view):

function search(arr, obj){
 if(Array.isArray(obj)){
  let child; // No need to recreate it on every loop iteration
  for (child of obj) {
    search(arr, child);
  }
 }else{
  arr.push(obj);
 }
}

var array = [{id: 1, gpa: 3.11}, [{id: 2, gpa: 2.9}, {id: 11, gpa: 3.9}, [{id: 9, gpa: 2.11}]], {id: 7, gpa: 3.31}];
var flatten = [];
search(flatten, array);
console.log(flatten);
.as-console-wrapper {
  max-height: 100% !important;
}

Upvotes: 1

Related Questions