-3

Given the following array of objects how can I go about creating a new one in which if there is an object with same email it just add the favoriteSport to an array. What I want to achieve is to remove the duplicate data.

Any tips on how to tackle this in Javascript?

data = [
  { 
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Soccer' 
  },
  { 
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Tennis' 
  },
  { 
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Swimming' 
  },
  { 
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Voleyball' 
  }
]

//Expected Output

[
  {
      name: 'Lisa Simpson',
      email: 'lisa@email.com',
      favoriteSport: ['Soccer', 'Tennis', 'Swimming', 'Voleyball']
  }
]
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Walleska
  • 7
  • 1
  • 1
    SO isn't a free coding service. Welcome to StackOverflow. Please visit the [help center](https://stackoverflow.com/help), take the [tour](https://stackoverflow.com/tour) to see what and [How to Ask](https://stackoverflow.com/help/how-to-ask). Do some research, search for related topics on SO; if you get stuck, post a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) of your attempt, noting input and expected output. – Alexandre Elshobokshy Dec 11 '18 at 10:15
  • Possible duplicate of [Get all unique values in a JavaScript array (remove duplicates)](https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates) – Udhay Titus Dec 11 '18 at 10:21
  • @UdhayTitus ... it is not. But of course this kind of problem (merging an object based on a unique key value pair) has been solved already several times at SO. – Peter Seliger Dec 11 '18 at 10:31
  • @Walleska ... use an approach based on [`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property) ... come back with a description of what you've tried and where you got stuck. – Peter Seliger Dec 11 '18 at 10:39

5 Answers5

1

You can try with reduce like this. You search first to see if you have already found the same email, so your array is unique by email and then you add your data.

const data = [{
        name: 'Lisa Simpson',
        email: 'lisa@email.com',
        favoriteSport: 'Soccer'
    },{
        name: 'Lisa Simpson',
        email: 'lisa@email.com',
        favoriteSport: 'Tennis'
    },{
        name: 'Lisa Simpson',
        email: 'lisa@email.com',
        favoriteSport: 'Swimming'
    },{
        name: 'Lisa Simpson',
        email: 'lisa@email.com',
        favoriteSport: 'Voleyball'}];

const group = (data) =>

    data.reduce((acc, val) => {

        const found = acc.find((item) => item.email === val.email);

        if (found && !found.favoriteSport.includes(val.favoriteSport)) found.favoriteSport.push(val.favoriteSport);

        else acc.push({ ...val, favoriteSport: [val.favoriteSport] });

        return acc;
    }, []);

console.log(group(data));

Or without find, using an object with the email as key. So you don't search the acc to see if you have found the same email already, you simply check if the property exists and add your data there.

const data = [{
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Soccer'
},{
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Tennis'
},{
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Swimming'
},{
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Voleyball'}];

const group = (data) => {

    const result = data.reduce((acc, val) => {

        if (acc[val.email]) acc[val.email].favoriteSport[val.favoriteSport] = 1;

        else acc[val.email] = { ...val, favoriteSport: { [val.favoriteSport]: 1 } };

        return acc;
    }, {});

    Object.keys(result).forEach((key) => result[key].favoriteSport = Object.keys(result[key].favoriteSport));

    return Object.values(result);
};

console.log(group(data));
Alex G
  • 1,897
  • 2
  • 10
  • 15
  • This approach too, is based on too much iteration. There are other ways to tackle this problem without making use of an additional `find`. – Peter Seliger Dec 11 '18 at 10:48
  • @PeterSeliger updated answer. Out of curiosity is there another way besides these? – Alex G Dec 11 '18 at 10:54
  • @PeterSeliger You are being a bit chatty here. Why don't you write up an answer yourself? Saying "There are other ways" doesn't help anyone. – nicholaswmin Dec 11 '18 at 11:02
  • The OP might try helping her/himself first. I'm just patient and meanwhile try to prevent (also by questioning all the quick shot answers) that she/he goes with the first solution that gets thrown on her/him without being able of estimating the quality of all this code. – Peter Seliger Dec 11 '18 at 11:17
  • @AlexG thank you Alex, it works great, I'm trying now to understand every part of this function. – Walleska Dec 11 '18 at 11:55
  • @Walleska updated answer with a bit more explanation about the steps. Let me know if I can help more. – Alex G Dec 11 '18 at 12:15
  • What is the result of this solution if there was another matching item that plays 'Tennis' too? Or yet another matching item that also likes 'Voleyball' ... ? – Peter Seliger Dec 11 '18 at 13:36
  • @PeterSeliger Thank you for your observation. I would like to know if there is another way of doing this or any improvement. If you have something feel free to comment. – Alex G Dec 11 '18 at 13:55
  • @AlexG ... there are three possible approaches that could prevent duplicate values within an unified/normalized/merged item. (A) before `push`ing a value into an item's array, make sure the latter does not already contain/`includes` the former. (B) taking the effort of building an item-specific value-storage for looking a value up, before pushing it into the array. (C) At any not that "expensive" process step, make use of an array-*unique* via `Array.from(new Set([.., ..., ...]))` ... my posted solution implements the storage/lookup approach. – Peter Seliger Dec 11 '18 at 15:33
0

You can do this by reducing the array to an object keyed by email and corresponding values as your desired final objects. You can then get our final array with Object.values:

var data = [{
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Soccer'
  },
  {
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Tennis'
  },
  {
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Swimming'
  },
  {
    name: 'Lisa Simpson',
    email: 'lisa@email.com',
    favoriteSport: 'Voleyball'
  },
  {
    name: 'Al Pacino',
    email: 'al@email.com',
    favoriteSport: 'Voleyball'
  }
];

var result = Object.values(data.reduce((acc, {favoriteSport, ...curr}) => {
  if (curr.email in acc) acc[curr.email].favoriteSport.push(favoriteSport);
  else acc[curr.email] = {...curr, favoriteSport: [favoriteSport]};
  return acc;
}, {}));

console.log(result);
slider
  • 12,810
  • 1
  • 26
  • 42
0

Here is my approach. Please note that you could take this one step further and abstract the defensive programming into helper functions.

var app = (function() {
  var init = function() {
    combineDuplicateEntries(data);
  };

  data = [
      {
          name: 'Lisa Simpson',
          email: 'lisa@email.com',
          favoriteSport: 'Soccer'
        },
      {
          name: 'Lisa Simpson',
          email: 'lisa@email.com',
          favoriteSport: 'Tennis'
        },
      {
          name: 'Lisa Simpson',
          email: 'lisa@email.com',
          favoriteSport: 'Swimming'
        },
      {
          name: 'Lisa Simpson',
          email: 'lisa@email.com',
          favoriteSport: 'Voleyball'
        }

  ];

  var finalData = {};

  function combineDuplicateEntries(data) {
    if (Array.isArray(data)) {
      console.log('Data is an array.');
      findDulicates(data)
    }
  }

  function findDulicates(data) {
    var duplicate = false;

    data.map(function(item) {
      var emailProperty = item.email;
      var favoriteSport = item.favoriteSport;

      if (emailProperty in finalData) {

        if (!finalData[emailProperty].duplicate) {
          finalData[emailProperty].favoriteSports = [];
        }

        finalData[emailProperty].duplicate = true;
        finalData[emailProperty].favoriteSports.push(favoriteSport)

        if (finalData[emailProperty].favoriteSport) {
          finalData[emailProperty].favoriteSports.push(finalData[emailProperty].favoriteSport);
          delete finalData[emailProperty].favoriteSport
        }

        duplicate = true;
      } else {
        finalData[emailProperty] = item;
        delete item.duplicate;
      }

      return duplicate;
    });

    console.log("Final data: ", finalData);
  }

  return init();
});

app(); 
-1

You can iterate with forEach then use find, filter, and map to achieve expected result.

We can use new Set(array) to get unique values and Array.from(new Set(array)) will convert it again to array.

let a = [{
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Soccer'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Tennis'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Swimming'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Voleyball'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Soccer'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Tennis'
}, {
  name: 'Homer Simpson',
  email: 'homer@email.com',
  favoriteSport: 'Barbecuing'
}, {
  name: 'Homer Simpson',
  email: 'homer@email.com',
  favoriteSport: 'TV marathoning'
}];

let result = [];
a.forEach((i) => {
  if (!result.find(x => x.name == i.name && x.email == i.email)) {
    result.push({
      name: i.name,
      email: i.email,
      favoriteSport: Array.from(new Set(a.filter(x => x.name == i.name && x.email == i.email).map(x => x.favoriteSport)))
    });
  }
});

console.log(result);
Karan
  • 12,059
  • 3
  • 24
  • 40
  • What happens if there is another matching Lisa that plays 'Tennis' too? Or yet another Lisa that also likes 'Voleyball' ... ? – Peter Seliger Dec 11 '18 at 10:56
  • @PeterSeliger Check out the answer. It will work for that also. – Karan Dec 12 '18 at 04:04
  • There are duplicates in Lisa's list of favorite sports. – Peter Seliger Dec 12 '18 at 09:55
  • @PeterSeliger Updated my answer. – Karan Dec 12 '18 at 10:25
  • It does its job now ... but on what costs ... with every `forEach` iteration step there is a `find` (that iterates too) in `result` which gets more expensive as `result` grows in length. Then again for every match that is going to be pushed into `result` there is a `filter` followed by a `map` (two full iteration cycles). Then casting this result into a `Set` (full iteration and making the internal list unique) followed by casting it back (full iteration) into an array. – Peter Seliger Dec 12 '18 at 10:51
-2

The solution thrown in now is supposed to be the proof of a generically written just reduce based approach.

The main idea is to write a reusable reducer function that uses its first accumulator argument as an storage object that holds configuration and result to this specific task. It also enriches this temporarily passed around object as registry in order to provide fast lookups for a better detection of item and string-value duplicates.

const data = [{
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Soccer'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Tennis'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Swimming'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Voleyball'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Soccer'
}, {
  name: 'Lisa Simpson',
  email: 'lisa@email.com',
  favoriteSport: 'Tennis'
}, {
  name: 'Homer Simpson',
  email: 'homer@email.com',
  favoriteSport: 'Barbecuing'
}, {
  name: 'Homer Simpson',
  email: 'homer@email.com',
  favoriteSport: 'TV marathoning'
}];


function concatSameItemStringValueByKeys(collector, item) {
  const { assign } = Object;
  const { samenessKey, concatKey, list } = collector;

  const itemRegistry  = (collector.itemRegistry || (collector.itemRegistry = {}));

  const accessKey     = item[samenessKey];
  const stringValue   = item[concatKey];

  let registeredItem  = itemRegistry[accessKey];
  if (registeredItem) {

    if (!(stringValue in registeredItem.valueRegistry)) { // - prevent item specific value duplicates.
      registeredItem.valueRegistry[stringValue] = true;

      registeredItem.type[concatKey].push(stringValue);   // - concat item specific values.
    }
  } else {
    registeredItem = itemRegistry[accessKey] = {          // - create item specific registry.
      type: assign({}, item),                             // - assign shallow item copy.
      valueRegistry: {}                                   // - create item specific value registry.
    };
    registeredItem.valueRegistry[stringValue] = true;     // - prevent future item specific value duplicates.

    registeredItem.type[concatKey] = [stringValue];       // - map item specific value initially to {Array}.

    list.push(registeredItem.type); // - store the initially mapped+registered item additionally into a list.
  }
  return collector;
}

const reducedData = data.reduce(concatSameItemStringValueByKeys, {

  samenessKey: 'email',
  concatKey: 'favoriteSport',
  list: []

}).list;

console.log('reducedData : ', reducedData);
.as-console-wrapper { max-height: 100%!important; top: 0; }

A similar problem/task now can be solved with the same reducer and another start configuration ...

const data = [{
  id: 'xyz-abc',
  value: 'foo'
}, {
  id: 'xyz-abc',
  value: 'bar'
}, {
  id: 'ikl-mno',
  value: 'foo'
}, {
  id: 'ikl-mno',
  value: 'bar'
}, {
  id: 'xyz-abc',
  value: 'baz'
}, {
  id: 'xyz-abc',
  value: 'biz'
}, {
  id: 'ikl-mno',
  value: 'foo'
}, {
  id: 'ikl-mno',
  value: 'bar'
}, {
  id: 'xyz-abc',
  value: 'foo'
}, {
  id: 'xyz-abc',
  value: 'bar'
}];


function concatSameItemStringValueByKeys(collector, item) {
  const { assign } = Object;
  const { samenessKey, concatKey, list } = collector;

  const itemRegistry  = (collector.itemRegistry || (collector.itemRegistry = {}));

  const accessKey     = item[samenessKey];
  const stringValue   = item[concatKey];

  let registeredItem  = itemRegistry[accessKey];
  if (registeredItem) {

    if (!(stringValue in registeredItem.valueRegistry)) { // - prevent item specific value duplicates.
      registeredItem.valueRegistry[stringValue] = true;

      registeredItem.type[concatKey].push(stringValue);   // - concat item specific values.
    }
  } else {
    registeredItem = itemRegistry[accessKey] = {          // - create item specific registry.
      type: assign({}, item),                             // - assign shallow item copy.
      valueRegistry: {}                                   // - create item specific value registry.
    };
    registeredItem.valueRegistry[stringValue] = true;     // - prevent future item specific value duplicates.

    registeredItem.type[concatKey] = [stringValue];       // - map item specific value initially to {Array}.

    list.push(registeredItem.type); // - store the initially mapped+registered item additionally into a list.
  }
  return collector;
}

const reducedData = data.reduce(concatSameItemStringValueByKeys, {

  samenessKey: 'id',
  concatKey: 'value',
  list: []

}).list;

console.log('reducedData : ', reducedData);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • I'm aware of being a critique of the other approaches does, in the eye of others, might somehow weaken my position. Nevertheless I always welcome a technical discussion about possible shortcomings in the code of others and mine. So just let me know what makes one think that "This answer is not useful" even twice. – Peter Seliger Dec 12 '18 at 10:05