简体   繁体   中英

Key value pair intersection of an array of objects

I would like to know if there is a way to find the intersection of a key value pair in an array of objects. Let's say you have an array of three objects which all have the same keys like this :

    arrayOfObj = [
    {
        "a": 1,
        "b": "stringB"
        "c": {"c1":1,
            "c2": "stringC2"
            }
    },
    {
        "a": 1,
        "b": "stringBdiff"
        "c": {"c1":1,
            "c2": "stringC2"
            }
    },
    {
        "a": 1,
        "b": "stringB"
        "c": {"c1":1,
            "c2": "stringC2"
            }
    }
  ]

I would like to find the common key value pairs of the three objects:

output= [
 {"a":1}, 
 {"c": {"c1":1, 
        "c2":"stringC2"
       }
 }
]

This is what I have done so far, it works but not on nested objects. I would like to know if there is a more elegant way to do it and one that could work on nested object as well.

 let properties; let commonFound = false; let notCommonFound = false; const commonValues = []; let value; const initialArray = [{ "a": 2, "b": "stringB", "c": { "c1": 1, "c2": "stringC2" } }, { "a": 1, "b": "stringB", "c": { "c1": 2, "c2": "stringC2" } }, { "a": 1, "b": "stringB", "c": { "c1": 2, "c2": "stringC2" } } ]; const commonStorage = []; const reference = initialArray[0]; properties = Object.keys(reference); properties.forEach((property) => { for (let i = 0; i < initialArray.length; i++) { commonFound = false; notCommonFound = false; for (let j = 0; j <i ; j++) { if (initialArray[i][property] === initialArray[j][property]) { commonFound = true; value = initialArray[i][property]; } else { notCommonFound = true; value = []; } } } if (commonFound && !notCommonFound) { commonStorage.push({[property] : value}); } }); console.log(commonStorage);

Before we implement intersect we'll first look at how we expect it to behave –

console.log
  ( intersect
      ( { a: 1, b: 2, d: 4 }
      , { a: 1, c: 3, d: 5 }
      )
      // { a: 1 }

  , intersect
      ( [ 1, 2, 3, 4, 6, 7 ]
      , [ 1, 2, 3, 5, 6 ]
      )
      // [ 1, 2, 3, <1 empty item>, 6 ]

  , intersect
      ( [ { a: 1 }, { a: 2 }, { a: 4, b: 5 }, ]
      , [ { a: 1 }, { a: 3 }, { a: 4, b: 6 }, ]
      )
      // [ { a: 1 }, <1 empty item>, { a: 4 } ]

  , intersect
      ( { a: { b: { c: { d: [ 1, 2 ]    } } } }
      , { a: { b: { c: { d: [ 1, 2, 3 ] } } } }
      )
      // { a: { b: { c: { d: [ 1, 2 ] } } } }
  )

Challenging problems like this one are made easier by breaking them down into smaller parts. To implement intersect we will plan to merge two calls to intersect1 , each contributing one side of the computed result –

const intersect = (left = {}, right = {}) =>
  merge
    ( intersect1 (left, right)
    , intersect1 (right, left)
    )

Implementing intersect1 is remains relatively complex due to the need to support both objects and arrays – the sequence of map , filter , and reduce helps maintain a flow of the program

const intersect1 = (left = {}, right = {}) =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          // both values are objects
          isObject (v) && isObject (right[k])
            ? [ k, intersect (v, right[k]) ]
          // both values are "equal"
          : v === right[k]
            ? [ k, v ]
          // otherwise
          : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          isObject (v)
            ? Object.keys (v) .length > 0
            : true
      )
    .reduce
      ( assign
      , isArray (left) && isArray (right) ? [] : {}
      )

Lastly we implement merge the same way we did in the other Q&A

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (assign, left)

The final dependencies –

const isObject = x =>
  Object (x) === x

const isArray =
  Array.isArray

const assign = (o, [ k, v ]) =>
  (o [k] = v, o)

Verify the complete program works in your browser below –

 const isObject = x => Object (x) === x const isArray = Array.isArray const assign = (o, [ k, v ]) => (o [k] = v, o) const merge = (left = {}, right = {}) => Object.entries (right) .map ( ([ k, v ]) => isObject (v) && isObject (left [k]) ? [ k, merge (left [k], v) ] : [ k, v ] ) .reduce (assign, left) const intersect = (left = {}, right = {}) => merge ( intersect1 (left, right) , intersect1 (right, left) ) const intersect1 = (left = {}, right = {}) => Object.entries (left) .map ( ([ k, v ]) => isObject (v) && isObject (right[k]) ? [ k, intersect (v, right[k]) ] : v === right[k] ? [ k, v ] : [ k, {} ] ) .filter ( ([ k, v ]) => isObject (v) ? Object.keys (v) .length > 0 : true ) .reduce ( assign , isArray (left) && isArray (right) ? [] : {} ) console.log ( intersect ( { a: 1, b: 2, d: 4 } , { a: 1, c: 3, d: 5 } ) // { a: 1 } , intersect ( [ 1, 2, 3, 4, 6, 7 ] , [ 1, 2, 3, 5, 6 ] ) // [ 1, 2, 3, <1 empty item>, 6 ] , intersect ( [ { a: 1 }, { a: 2 }, { a: 4, b: 5 }, ] , [ { a: 1 }, { a: 3 }, { a: 4, b: 6 }, ] ) // [ { a: 1 }, <1 empty item>, { a: 4 } ] , intersect ( { a: { b: { c: { d: [ 1, 2 ] } } } } , { a: { b: { c: { d: [ 1, 2, 3 ] } } } } ) // { a: { b: { c: { d: [ 1, 2 ] } } } } )


intersectAll

Above intersect only accepts two inputs and in your question you want to compute the intersect of 2+ objects. We implement intersectAll as follows -

const None =
  Symbol ()

const intersectAll = (x = None, ...xs) =>
  x === None
    ? {}
    : xs .reduce (intersect, x)

console.log
  ( intersectAll
      ( { a: 1, b: 2, c: { d: 3, e: 4 } }
      , { a: 1, b: 9, c: { d: 3, e: 4 } }
      , { a: 1, b: 2, c: { d: 3, e: 5 } }
      )
      // { a: 1, c: { d: 3 } }

  , intersectAll
      ( { a: 1 }
      , { b: 2 }
      , { c: 3 }
      )
      // {}

  , intersectAll
      ()
      // {}
  )

Verify the results in your browser –

 const isObject = x => Object (x) === x const isArray = Array.isArray const assign = (o, [ k, v ]) => (o [k] = v, o) const merge = (left = {}, right = {}) => Object.entries (right) .map ( ([ k, v ]) => isObject (v) && isObject (left [k]) ? [ k, merge (left [k], v) ] : [ k, v ] ) .reduce (assign, left) const intersect = (left = {}, right = {}) => merge ( intersect1 (left, right) , intersect1 (right, left) ) const intersect1 = (left = {}, right = {}) => Object.entries (left) .map ( ([ k, v ]) => isObject (v) && isObject (right[k]) ? [ k, intersect (v, right[k]) ] : v === right[k] ? [ k, v ] : [ k, {} ] ) .filter ( ([ k, v ]) => isObject (v) ? Object.keys (v) .length > 0 : true ) .reduce ( assign , isArray (left) && isArray (right) ? [] : {} ) const None = Symbol () const intersectAll = (x = None, ...xs) => x === None ? {} : xs .reduce (intersect, x) console.log ( intersectAll ( { a: 1, b: 2, c: { d: 3, e: 4 } } , { a: 1, b: 9, c: { d: 3, e: 4 } } , { a: 1, b: 2, c: { d: 3, e: 5 } } ) // { a: 1, c: { d: 3 } } , intersectAll ( { a: 1 } , { b: 2 } , { c: 3 } ) // {} , intersectAll () // {} )


remarks

You'll want to consider some things like –

intersect
  ( { a: someFunc, b: x => x * 2, c: /foo/, d: 1 }
  , { a: someFunc, b: x => x * 3, c: /foo/, d: 1 }
  )
  // { d: 1 }                          (actual)
  // { a: someFunc, c: /foo/, d: 1 }   (expected)

We're testing for what's considered equal here in intersect1

const intersect1 = (left = {}, right = {}) =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, intersect (v, right[k]) ]
            : v === right[k] // <-- equality?
              ? [ k, v ]
              : [ k, {} ]
      )
    .filter
      ( ...

If we want to support things like checking for equality of Functions, RegExps, or other objects, this is where we would make the necessary modifications


recursive diff

In this related Q&A we compute the recursive diff of two objects

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM