8

I have multiple arrays in a main/parent array like this:

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];

here are the array's for simpler reading:

[1, 17]
[1, 17]
[1, 17]
[2, 12]
[5, 9]
[2, 12]
[6, 2]
[2, 12]
[2, 12]

I want to select the arrays that are repeated 3 or more times (> 3) and assign it to a variable. So in this example, var repeatedArrays would be [1, 17] and [2, 12].

So this should be the final result:

console.log(repeatedArrays);
>>> [[1, 17], [2, 12]]

I found something similar here but it uses underscore.js and lodash.

How could I it with javascript or even jquery (if need be)?

Timmy Balk
  • 238
  • 3
  • 14
  • You could compare them against their JSON values, however, `[1, 17]` wouldn't match `[17, 1]` – Get Off My Lawn Nov 23 '18 at 21:03
  • you can accept one answer (if it helps you) by click on big gray check button on its left side. If you wish you can add +10 points to any author of any good answer by click upper gray triangle – Kamil Kiełczewski May 14 '19 at 16:21

6 Answers6

15

Try this

array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];

var r= array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))

console.log(JSON.stringify(r));

Time complexity O(n) (one array pass by filter function). Inspired by Nitish answer.

Explanation

The (r={}, a=>...) will return last expression after comma (which is a=>...) (e.g. (5,6)==6). In r={} we set once temporary object where we will store unique keys. In filter function a=>... in a we have current array element . In r[a] JS implicity cast a to string (e.g 1,17). Then in !(2-(r[a]=++r[a]|0)) we increase counter of occurrence element a and return true (as filter function value) if element a occurred 3 times. If r[a] is undefined the ++r[a] returns NaN, and further NaN|0=0 (also number|0=number). The r[a]= initialise first counter value, if we omit it the ++ will only set NaN to r[a] which is non-incrementable (so we need to put zero at init). If we remove 2- as result we get input array without duplicates - or alternatively we can also get this by a=>!(r[a]=a in r). If we change 2- to 1- we get array with duplicates only.

UPDATE

Even shorter version based on @ken comment can be written (it should always work with arrays of numbers). The original longer version of @ken code is in snippet and shows how @ken uses in clever way second argument of .filter to avoid usage global variable r.

array.filter(a=>!(2-(this[a]=++this[a]|0)))

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];

var r= array.filter(a=>!(2-(this[a]=++this[a]|0)), {})

console.log(JSON.stringify(r));
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • 2
    Some people just really like to push the limits. Respect. B) – Nelson Teixeira Apr 20 '21 at 03:15
  • 1
    I just spent around 1/2 hour trying to understand what you have done. Now that I understood it, I aprecciated it even more. If I could, I'd give you a bounty for this one. If anyone else got the mind blown, I have breaked it into parts, and made a helping example to try to study it here: https://jsfiddle.net/drpfe3hm/3/ – Nelson Teixeira Apr 24 '21 at 04:28
  • @NelsonTeixeira thank for your good words - I don't get this result immediately - but I get new ideas during daily 1 hour meditation session (which is quite paradox because I stop thinking about anything but rather focus only on breath - but ideas appears) - and I improve the answer gradually during few days. History of it is [here](https://stackoverflow.com/posts/53453045/revisions) - I start with quite clumsy solution... – Kamil Kiełczewski Apr 24 '21 at 11:19
  • 1
    Without the comma-return and abusing the 2nd arg to `Array#filter` (the `thisArg`): `array.filter(a=>!(2-(this[a]=++this[a]|0)), {})` – ken Apr 24 '21 at 15:10
  • 1
    @ken - thanks for your improvoement - nice - i think this can be writen also even shorter `array.filter(a=>!(2-(this[a]=++this[a]|0)))` – Kamil Kiełczewski Apr 24 '21 at 15:27
  • @KamilKiełczewski you might like https://codegolf.stackexchange.com :) – ken Apr 25 '21 at 16:12
  • @KamilKiełczewski if you do this using only meditation, I really would like to see what would come out from you in an Ayahuasca session. ;) – Nelson Teixeira Apr 26 '21 at 06:05
  • 1
    @ken know I'm sad. I would prefer not to know that things like Vyxal and Jelly existed. :D – Nelson Teixeira Apr 26 '21 at 06:48
9

You could take a Map with stringified arrays and count, then filter by count and restore the arrays.

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
    result = Array
        .from(array.reduce(
            (map, array) =>
                (json => map.set(json, (map.get(json) || 0) + 1))
                (JSON.stringify(array)),
            new Map
         ))
        .filter(([, count]) => count > 2)
        .map(([json]) => JSON.parse(json));
        
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter with a map at wanted count.

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
    result = array.filter(
        (map => a => 
            (json =>
                (count => map.set(json, count) && !(2 - count))
                (1 + map.get(json) || 1)
            )
            (JSON.stringify(a))
        )
        (new Map)
    );
        
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Unique!

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
    result = array.filter(
        (s => a => (j => !s.has(j) && s.add(j))(JSON.stringify(a)))
        (new Set)
    );
        
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Awesome! This works! Is there any way to remove the duplicated arrays from the original array? So the final output of `var array` would be `[[1, 17], [2, 12], [5, 9], [6, 2]]`. I tried `filter()` and `indexOf()` but it did not work – Timmy Balk Nov 23 '18 at 21:46
  • for that task, you may use a [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instead of a `Map`. – Nina Scholz Nov 23 '18 at 21:55
  • Could you please edit your answer and show me? Do I use `has()`? – Timmy Balk Nov 23 '18 at 21:59
  • @TimmyBalk just remove `.filter(([, count]) => count > 2)` line - and don't change anything else – Kamil Kiełczewski Nov 23 '18 at 22:00
  • @KamilKiełczewski Excellent! Thank you so much. I'm going to research into the `filter()` function now, thanks again Nina and Kamil. But i'm assuming removing the `.filter` line is not the same as what Nina was saying? – Timmy Balk Nov 23 '18 at 22:02
  • @KamilKiełczewski But what about if the same array occurs 3 or more times? Isn't that what the `count` does? – Timmy Balk Nov 23 '18 at 22:06
  • @TimmyBalk - just test it – Kamil Kiełczewski Nov 23 '18 at 22:08
  • @TimmyBalk the `reduce` function takes a data type as it's initial starting value. In the code it's a `Map` object. The suggestion to turn it into a `Set` object would work because a `Set` object natively doesn't allow duplicates. http://jsfiddle.net/73zpjLnk/ – zfrisch Nov 23 '18 at 22:12
  • @NinaScholz for your updated code, how do I set if there are only 3 or more duplicates, then remove all but one? Or for example, 5 or more duplicates? How do i set that? – Timmy Balk Nov 23 '18 at 23:43
  • for that case, which faster? filter or map or with set?? – Zr Classic Jan 26 '19 at 06:37
  • @ZrClassic, it depends. a for loop is actually faster than an array method. – Nina Scholz Jan 26 '19 at 09:59
5

You can use Object.reduce, Object.entries for this like below

var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];


let res = Object.entries(
            array.reduce((o, d) => {
              let key = d.join('-')
              o[key] = (o[key] || 0) + 1

              return o
          }, {}))
          .flatMap(([k, v]) => v > 2 ? [k.split('-').map(Number)] : [])
  
  
console.log(res)

OR may be just with Array.filters

var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];

let temp = {}
let res = array.filter(d => {
  let key = d.join('-')
  temp[key] = (temp[key] || 0) + 1
  
  return temp[key] == 3
})

console.log(res)
Nitish Narang
  • 4,124
  • 2
  • 15
  • 22
4

For a different take, you can first sort your list, then loop through once and pull out the elements that meet your requirement. This will probably be faster than stringifying keys from the array even with the sort:

var arr = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]]
arr.sort((a, b) => a[0] - b[0] || a[1] - b[1])

// define equal for array
const equal = (arr1, arr2) => arr1.every((n, j) => n === arr2[j])

let GROUP_SIZE = 3
first = 0, last = 1, res = []

while(last < arr.length){
    if (equal(arr[first], arr[last])) last++
    else {
        if (last - first >= GROUP_SIZE)  res.push(arr[first])
        first = last
    }
}
if (last - first >= GROUP_SIZE)  res.push(arr[first])
console.log(res)
Mark
  • 90,562
  • 7
  • 108
  • 148
  • Interesting approach, but is it really faster? – Timmy Balk Nov 23 '18 at 21:52
  • 1
    @TimmyBalk this isn't a criticism of the JSON approach -- I think it's good, but if my test is correct, it is slower (at least on my browser). Here's a jsperf test: https://jsperf.com/2json-v-sort-loop – Mark Nov 23 '18 at 21:55
2

You could also do this with a single Array.reduce where you would only push to a result property if the length is equal to 3:

var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];

console.log(array.reduce((r,c) => {
  let key = c.join('-')
  r[key] = (r[key] || 0) + 1
  r[key] == 3 ? r.result.push(c) : 0  // if we have a hit push to result
  return r
}, { result: []}).result)             // print the result property
Akrion
  • 18,117
  • 1
  • 34
  • 54
1

ES6:

const repeatMap = {}

array.forEach(arr => {
  const key = JSON.stringify(arr)
  if (repeatMap[key]) {
    repeatMap[key]++
  } else {
    repeatMap[key] = 1
  }
})

const repeatedArrays = Object.keys(repeatMap)
  .filter(key => repeatMap[key] >= 3)
  .map(key => JSON.parse(key))
Ben Steward
  • 2,338
  • 1
  • 13
  • 23