10

My input is an array like so:

[7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7]

I want to group together the numbers and add them, but by neighbors, not by total in the array. So the output would be:

['7:4', '4:2', '5:3', 1, 9, 2, '7:2']

I've attempted a few different methods using reduce, and gotten close but using the built-in Javascript methods I end up counting ALL in the array, not by neighbors.

const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
const masterArray = [];

const unique = new Set (numberArray); // Set {7, 4, 5, 1, 9, 2, 7}
unique.forEach(u => {
  masterArray.push(numberArray.filter(e => e === u));
});

console.log(masterArray);

Set is obviously wrong to use here because that gets the unique values and counts them, but I want to do it by neighbor only. So then I think I should be using a reduce but I run into the same problem.

0

8 Answers 8

3

var test = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];

console.log(
  test.reduce((acc, element) => {
    if (acc.length && acc[acc.length - 1].key == element) {
      acc[acc.length - 1].count++;
    } else {
      acc.push({ key: element, count: 1 });
    }
    
    return acc;
  }, []).map(element => `${element.key}:${element.count}`)
);

So the logic first reduces the number to an array, tracking the last key and count. So long as the key is the same, the count is incremented. Once the key changes, a new object is pushed to start the next run. After the reduce is done, a map is performed to convert the key and counts into the desired strings.

Sign up to request clarification or add additional context in comments.

1 Comment

Perfect. I added a check in the .map part to leave single numbers alone: element => element.count !== 1 ? ${element.key}:${element.count} : element.key) and it works perfectly.
3

You'll need to keep track of the last number iterated over in a persistent variable, as well as the number of occurrences of the last number which gets incremented:

const arr = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
let lastNum = arr[0];
let count = 0;
const results = [];
const doPush = () => {
  results.push(
    count === 1
      ? lastNum
      : `${lastNum}:${count}`
  );
};
for (const num of arr) {
  if (num !== lastNum) {
    doPush();
    lastNum = num;
    count = 1;
  } else count++;
}
doPush();

console.log(results);

2 Comments

This looks like I would solve it in C++, but is this also faster in javascript? also when there is a million numbers? (displaying total lack of experience in javascript)
I don't know. On one hand, I think higher-level languages like JS are fundamentally somewhat slower than lower-level languages if the lower-level languages are optimized for performance. On the other hand, JS compilers nowadays are pretty good at optimization, so the difference might not be that large. As always, for performance, run some tests yourself with realistic inputs to find out.
3

You could reduce the array by having a look to the last value a the index before the actual value.

const 
    array = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7],
    result = array.reduce((r, value, i, { [i - 1]: last }) => {
        let count = 0;
        if (last === value) count = (+r.pop().toString().split(':')[1] || 0) + 1;
        return [...r, count ? `${value}:${count}`: value];
    }, []);

console.log(result);

1 Comment

{ [i - 1]: last } how that is possible?
2

Here is a solution that uses a recursive function to group the neighbors, and then Array.prototype.map() to format with a colon:

const arr = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7],
  output = (function groupNeighbors([first, ...rest], output = [], last) {
    last === first ? output[output.length - 1].push(first) : output.push([first]); 
    return rest.length ? groupNeighbors(rest, output, first) : output;
  })(arr).map(({ [0]: num, length }) => length > 1 ? [num, length].join(':') : num);

console.log(output);

As it uses recursion, it is limited in terms of input array length, you can find the stack size limits per browser here: Browser Javascript Stack size limit

Variant with Array.prototype.reduce() (slightly shortest, no recursion, unlimited input length):

const arr = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7],
  output = arr
    .reduce(
      (acc, cur, i, { [i - 1]: last }) =>
        (cur === last ? acc[acc.length - 1].push(cur) : acc.push([cur])) && acc,
      []
    )
    .map(({ [0]: num, length }) => length > 1 ? [num, length].join(':') : num);

console.log(output);

Comments

1

Yes, the proper choice here is reduce:

const countDuplicatesQuantity = (arr) => {
    const duplicatesObj = arr.reduce((duplicates, item) => {
        duplicates[item] = duplicates[item] ? duplicates[item] + 1 : 1;

        return duplicates;
    }, {});

    return Object.keys(duplicatesObj).map(
        (key) => `${key}:${duplicatesObj[key]}`,
    );
};

Comments

1

You wanted reduce, how about twice? :) (I don't know if I've done something stupid here.)

First reduce finds where the values change in the array, second uses that to build new array.

const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];

const masterArray = firstArray
  .reduce((acc, v, i, all) => all[i + 1] !== v ? acc.concat(i) : acc, [])
  .reduce(
    (acc, v, i, all) => {
      const range = v - (i === 0 ? -1 : all[i - 1]);
      return acc.concat(range > 1 ? firstArray[v] + ':' + range : firstArray[v]);
    }, []
  );
  

console.log(masterArray);

Comments

0

Using array.reduce:

const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
var newArray = [];
var count = 0;
firstArray.reduce((acc, cur, index) => {
  if (acc == cur) {
    count++;
  }
  if (acc != cur || index+1 == firstArray.length) {
    newArray.push(acc + ":" + count);
    count=1
  }
  return cur;
})
console.log(newArray);

Comments

0

my way ... I felt that there could be "simpler"

const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
 
const result = firstArray.reduceRight((r,v)=>
  {
  let [n,c] = !r.length ? [-v,0] : isNaN(r[0]) ? r[0].split(':') : [r[0],1]
  if (n==v) r[0] = `${n}:${++c}`
  else      r.unshift(v)
  return r
  },[])
  
console.log( JSON.stringify(result) )
//  [ "7:4", "4:2", "5:3", 1, 9, 2, "7:2" ]
.as-console-wrapper { max-height: 100% !important; top: 0; }

1 Comment

@stonerose036 I felt that there could be "simpler"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.