5

I am pretty new to javascript, and I was trying to build this function I found in a challenge. I have managed to solve this, but I feel like this is a very complicated way of doing things, what would be the fastest method here?

Basically answer(array) should transform this:

const array = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];

into this:

newArray = [[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591];

Here is my code so far:

const array = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];

const duplicates = arr =>
    arr.reduce((a, b) => ({ ...a,
        [b]: (a[b] || 0) + 1
    }), {})

array.sort((a, b) => a - b);
let array1 = duplicates(array);
var values = Object.values(array1);
var keys = Object.keys(array1);
var newArray = [];

for (let i = 0; i < keys.length; i++) {
    let tempArray = [];
    for (let j = 0; j < values[i]; j++) {
        tempArray.push(keys[i]);
    }
    newArray.push(tempArray);
}
console.log(newArray);

duplicates function comes from This post about finding duplicate values

Rahul Bhobe
  • 4,165
  • 4
  • 17
  • 32
Marelons
  • 150
  • 6

5 Answers5

4

Have a look at this one-liner. Guess this is what you need.

Note that this isn't the most efficient way to to this - but guess it's beautyfull!

const array = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
const newArray = [...new Set(array)].sort((a, b) => a-b).map(e => new Array(array.filter( i => i === e).length).fill(e)).map(e => e.length === 1 ? e[0] : e);
console.log(newArray);

How this works:

  1. We're creating a set from the array with unique values [...new Set(array)]
  2. We're sorting the keys of the set .sort((a, b) => a-b)
  3. We're looping trought every value and calculating the occurrences of this value in the array .map(e => new Array(array.filter( i => i === e).length);
  4. In the last step we're filling the new Array with the amount of values and the value from the set from above .fill(e)
  5. We're iterating trought the array and make the entry flat if theres just a single value present .map(e => e.length === 1 ? e[0] : e)
stacj
  • 1,103
  • 1
  • 7
  • 20
  • Thank you, that's what I was actually looking for, it might not be the fastest, but my aim here is to learn array methods in javascript. – Marelons Sep 11 '20 at 07:25
2

Your approach is good enough (fast). I made some modifications to use parseInt() on keys to convert the keys back to number from string. And also made sure that a single element is not nested (as your expected output in your OP.

const array = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];

const duplicates = arr =>
    arr.reduce((a, b) => ({ ...a,
        [b]: (a[b] || 0) + 1
    }), {})

array.sort((a, b) => a - b);
let array1 = duplicates(array);
var values = Object.values(array1);
var keys = Object.keys(array1);
var newArray = [];

for (let i = 0; i < keys.length; i++) {
    let tempArray = [];
    if (values[i] === 1) {
        newArray.push(parseInt(keys[i]));
    } else {
        newArray.push(Array(values[i]).fill(parseInt(keys[i])));
    }
}

console.log(newArray);
Rahul Bhobe
  • 4,165
  • 4
  • 17
  • 32
2

Given the sorted array (ascending), you could have 2 variables, one storing the sorted unique array of element, one storing the occurrence of element in the array

After that, you map through the unique, if the occurence is greater than one, return an array filled by the element with the length of that element's occurrence, else just return that element

const sortedArray = [1, 1, 1, 1, 2, 2, 2, 4, 5, 10, 20, 20, 391, 392, 591]

const sortedUnique = Array.from(new Set(sortedArray))
const occurences = sortedArray.reduce((acc, el) => {
  acc.set(el, (acc.get(el) || 0) + 1)
  return acc
}, new Map())

const res = sortedUnique.map((el) =>
  occurences.get(el) > 1 ? Array(occurences.get(el)).fill(el) : el
)

console.log(res)
hgb123
  • 13,869
  • 3
  • 20
  • 38
2

You are almost done. but here is some improvement.

  const array = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];
array.sort((a, b) => a - b);
let groupedArr = array.reduce((acc, a) => {
  acc[a] = acc[a] || [];
  acc[a].push(a);
  return acc;
}, {});

let newArray = [];

for (a in groupedArr) {
  if (groupedArr[a].length > 1) {
    newArray.push(groupedArr[a]);
  } else {
    newArray.push(groupedArr[a][0]);
  }
}
console.log(newArray);
Sheelpriy
  • 1,675
  • 17
  • 28
  • Thanks, that's looking so much better, could you briefly explain how groupedArr function exactly works? – Marelons Sep 11 '20 at 07:32
  • 1
    @Marelons in reduce function we are grouping the numbers, being number itself the key. e.g. `{ 1:[1, 1, 1, 1], 2:[2, 2]}` – Sheelpriy Sep 12 '20 at 14:41
1

Solution #1

const array = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];

// [...array] prevents mutation of original array when invoking sort method
const sortedNested = [...array].sort().reduce((acc, cur, idx, org) => {
  if (!acc[cur]) {
    acc[cur] = cur;
  } else if (Array.isArray(acc[cur])) {
    acc[cur].push(cur);
  } else {
    acc[cur] = [cur, cur];
  }

  return idx === org.length - 1 ? Object.values(acc) : acc;
}, {});

console.log(sortedNested);

Solution #2

const array = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];

// get distinct array elements
const distinct = Array.from(new Set(array));

// map to desired output
const nested = distinct.map(value => {
  const length = array.filter(_value => _value === value).length;

  return length === 1 ? value : Array.from({length}).fill(value);
  
})
// sort ascending
.sort(
    // return a - b; if either a or b is array get their first element
    (a, b) => (Array.isArray(a) ? a[0] : a) - (Array.isArray(b) ? b[0] : b)
);

console.log(nested);
Kharel
  • 819
  • 8
  • 16