0

I have an array of contact cards that hold names & addresses etc for users. What I want to do is create another array that removes any duplicate addresses (ie people in the same household) and creates a name that is a combination of the two. For example:

{flatNum: 1, Name: "ken"},{flatNum: 1, Name: "bob"}, {flatNum: 2, Name: "emma"}

would become:

{flatNum: 1, Name: "ken & bob"}, {flatNum: 2, Name: "emma"}

I know how I can achieve this with a long for loop type thing but was hoping to find a more concise method. I am assuming that reduce would be the key and have been playing around. Currently got this:

let contactCardsComb = contactCards.reduce(function(a,b){
  if (a.flatNum == b.flatNum){
    return a.Name = a.Name+b.Name;
  }
});

Which is obviously horribly wrong but any pointers would be great

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Chris Barrett
  • 571
  • 4
  • 23

3 Answers3

3

You could group by flatNum and get the values from the object.

const
    data = [{ flatNum: 1, Name: "ken" }, { flatNum: 1, Name: "bob" }, { flatNum: 2, Name: "emma" }],
    result = Object.values(data.reduce((r, { flatNum, Name }) => {
        if (r[flatNum]) r[flatNum].Name += ' & ' + Name;
        else r[flatNum] = { flatNum, Name };
        return r;
    }, {}));

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

this can be easily accomplished by using lodash library,

let contactCards = [
  {flatNum: 1, Name: "ken"},
  {flatNum: 1, Name: "bob"},
  {flatNum: 2, Name: "emma"},
  {flatNum: 2, Name: "april"},
  {flatNum: 2, Name: "june"}
];

let tempGroup = _.groupBy(contactCards, contact => {
  return contact.flatNum;
});

console.log('tempGroup :', tempGroup);


let contactCardsComb  = Object
  .values(tempGroup)
  .map( e => e.reduce( (a,b) =>
    ({...a, Name: `${ a.Name } & ${ b.Name }` })
  ));

console.log('result : ', contactCardsComb);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-compat/3.10.2/lodash.min.js"></script>

// a vanilla approach would be as follows

let flatNums = Array.from(new Set(contactCards.map( e => e.flatNum)))


let groupedContactCards = flatNums
                      .map( e => contactCards
                                  .filter(contact => contact.flatNum === e))

let reducedContactCards = groupedContactCards
                      .map( e => e
                                  .reduce((a,b) => ({...a,Name:`${a.Name} & ${b.Name}`})))
console.log(reducedContactCards)

or we can wrap this as below

let reducedContactCards = Array.from(new Set(contactCards.map( e => e.flatNum)))
                .map( e => contactCards   
                .filter(contact => contact.flatNum === e))
                .map( e => e.reduce((a,b) => ({...a,Name:`${a.Name} & ${b.Name}`})))
console.log(reducedContactCards)
HubballiHuli
  • 777
  • 7
  • 18
  • How often, does the above vanilla code iterate data structures? @NinaScholz 's approach, which nowadays for this kind of problem is a very common one, does iterate exactly 2 times. And I'm not talking about performance; it is more about unnecessary abstraction/complexity. If an approach in the end anyhow relies on `reduce`, why not just providing the processing logic exactly there. No need for... `map`/`Set`/`Array.from` (3x),.. `filter`/`map` (2x),.. `reduce`/`map` (2x). Overall 7 cycles where, in my opinion, a maximum of two is needed, and even one iteration is not that difficult to achieve. – Peter Seliger Sep 18 '20 at 14:18
  • @PeterSeliger I was emphasising on performance, doesn't this approach perform better for a larger data set. I think yes, please correct me if I am wrong.. – HubballiHuli Sep 18 '20 at 16:31
  • @HubballiHuli ... *"... I was emphasising on performance, doesn't this approach perform better for a larger data set. ..."* I do not know since my point was abstraction/complexity/readability. But you are free of writing a performance test. As of now I'm likewise interested in that. – Peter Seliger Sep 18 '20 at 16:40
1

I take the OP's Q. as a chance for proofing that an abstract but (highly) configurable reduce task is in no time (less than a minute, here for the OP's problem) suitable for a variety of problems that at first sight do have nothing in common, due to the different environments and the naming of variables etc.

If one takes e.g. the following approach of this answer to another Q. ... "Segregate an array based on same name") ... some days ago, one ...

  1. just needs to change the implementation (line 24) of how to merge the properties of two to be grouped items, and
  2. just has to provide the correct key name (line 42) of the target value one wants to group around all the other list items.

... code ...

//  reduce function that groups
//  any data item generically by key.
function groupByKeyAndMergeProperties(collector, item) {
  const { merge, key, index, list } = collector;
  const groupKey = item[key];

  let groupItem = index[groupKey];
  if (!groupItem) {

    //  use `Object.assign` initially in order
    //  to not mutate the original (list's) reference.
    groupItem = index[groupKey] = Object.assign({}, item);
    list.push(groupItem);

    merge(groupItem, null);
  } else {
    merge(groupItem, item);
  }
  return collector;
}

//  task specific merge function,
//  here, according to the OP's goal.
function mergeItemNames(targetItem, sourceItem) {
  if (sourceItem !== null) {
    targetItem.Name = `${ targetItem.Name } & ${ sourceItem.Name }`;
  }
}


const sampleList = [
  { flatNum: 1, Name: "ken" },
  { flatNum: 1, Name: "bob" },
  
  { flatNum: 2, Name: "emma" },
  { flatNum: 2, Name: "april" },
  { flatNum: 2, Name: "june" },

  { flatNum: 3, Name: "john" }
];

console.log(
  sampleList.reduce(groupByKeyAndMergeProperties, {

    // task specific reduce configuration.
    merge: mergeItemNames,
    key: 'flatNum',
    index: {},
    list: []

  }).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37