Find objects that meet a range of two number properties from an array of objects

Viewed 39

could I get some suggestions on how I can efficiently find items to delete based off the start and end properties.

Cases:

  • added - if an existing item start/period was changed, the new updated item is added.
  • deleted - if an existing item start/period was changed and the existing item needs deleting.
  • updated - if an existing item cost was changed and the existing item needs updating.
  • unchanged - if there was no changes.

Thank you. Please anyone help

const existingItems = [
    {
        id: '111',
        start: 0,
        end: 10,
        cost: 100
    },
    {
        id: '222',
        start: 20,
        end: 30,
        cost: 200
    },
    { // ignored
        id: '333',
        start: 20,
        end: 30,
        cost: 400
    },
];

const updatedItems = [
    {
        id: '111',
        start: 0,
        end: 9,
        cost: 42
    },
    {
        id: '222',
        start: 20,
        end: 30,
        cost: 400 // cost change, so only added to updated
    }
];

const addedItem = [];
const deletedItems = [];

existingItems.forEach((existingItem) => {
  const existingStartEnd = `${existingItem.id}-${existingItem.start}-${existingItem.end}`

  updatedItems.forEach((updatedItem) => {
    const updatedStartEnd = `${updatedItem.id}-${updatedItem.start}-${updatedItem.end}`

    if (updatedItem.id === existingItem.id && (existingStartEnd !== updatedStartEnd & existingItem.cost === updatedItem.cost)) {
      deletedItems.push(existingItem);
    }
  })
})

// expected result
const items = {
  deleted: [
    {
        id: '111',
        start: 0,
        end: 10,
        cost: 100
    }
  ],
  added: [
    {
        id: '111',
        start: 0,
        end: 9,
        cost: 42
    },
  ],
  updated: [
    {
        id: '222',
        start: 20,
        end: 30,
        cost: 400
    },
  ],
};

Attempt, however, the issue is deleted array shouldn't include existing Item with ID 333 because there was no changes. I cannot find a way :(

function changes( existing, updated ) {

  function key( o ) {
    return o.id + '|' + o.start + '|' + o.end;
  }

  existingMap = new Map( existingItems.map( o => [ key( o ), o ] ) );
  updatedMap  = new Map( updatedItems.map(  o => [ key( o ), o ] ) );

  const changedItems = {};
  
  changedItems.added =   [ ...updatedMap ]
    .filter( m => ! existingMap.has( m[ 0 ] ) )
    .map( m => m[ 1 ] );
    
  changedItems.deleted = [ ...existingMap ]
    .filter( m => ! updatedMap.has( m[ 0 ] ) )
    .map( m => m[ 1 ] );
    
  changedItems.updated = [ ...updatedMap ]
    .filter( m => existingMap.has( m[ 0 ] )  &&  updatedMap.get( m[ 0 ] ).cost !== existingMap.get( m[ 0 ] ).cost )
    .map( m => m[ 1 ] );

  changedItems.unchanged = [ ...updatedMap ]
    .filter( m => existingMap.has( m[ 0 ] )  &&  updatedMap.get( m[ 0 ] ).cost === existingMap.get( m[ 0 ] ).cost )
    .map( m => m[ 1 ] );
    
  return changedItems;
  
}


const existingItems = [
    {
        id: '111',
        start: 0,
        end: 10,
        cost: 100
    },
    {
        id: '222',
        start: 20,
        end: 30,
        cost: 200
    },
    {
        id: '333',
        start: 20,
        end: 30,
        cost: 200
    },
];

const updatedItems = [
    {
        id: '111',
        start: 0,
        end: 9,
        cost: 42
    },
    {
        id: '222',
        start: 20,
        end: 30,
        cost: 400 // cost change, so only added to updated
    },
];

let result = changes( existingItems, updatedItems )
console.log( result );

1 Answers

I'm not 100% sure I fully understand what you're trying to do, but you could produce the expected result in your example via reduce:

const existingItems = [{
    id: '111',
    start: 0,
    end: 10,
    cost: 100
  },
  {
    id: '222',
    start: 20,
    end: 30,
    cost: 200
  },
  { // ignored
    id: '333',
    start: 20,
    end: 30,
    cost: 400
  },
];

const updatedItems = [{
    id: '111',
    start: 0,
    end: 9,
    cost: 42
  },
  {
    id: '222',
    start: 20,
    end: 30,
    cost: 400 // cost change, so only added to updated
  }
];

// convenience function for comparing the start and end between two items.
// returns true if they match, false if they don't.
const samePeriod = (
{start: sa, end: ea} = {},
{start: sb, end: eb} = {}) => (sa === sb && ea === eb);

const result = updatedItems.reduce((acc, item) => {
  // find an existing item with the same id
  const existing = existingItems.find(({id}) => id === item.id);
  
  if (!existing) {
    // no item found. nothing to do.
    return acc;
  }
  
  // does the existing item have a different start/end?
  if (!samePeriod(item, existing)) {
    // add the old item to acc.deleted
    acc.deleted.push(existing);
    
    // add the updated item to acc.added
    acc.added.push(item);
    
    // done with this iteration
    return acc;
  }
  
  // did the cost change?
  if (item.cost != existing.cost) {
    // add the item to acc.updated
    acc.updated.push(item);
  }

  return acc;
}, {
  deleted: [],
  added: [],
  updated: []
});

console.log(result);

Related