Home > database >  Javascript - Optimize algorithm (complex data strcuture)
Javascript - Optimize algorithm (complex data strcuture)

Time:10-29

Introduction

I am implementing a method which inserts posts to the respective users posts lists in my map, sorted by date (recent posts first).

This is how I am structuring my data:

state = { 
  userId: {
    posts: [
      { // object returned from my feeds algorithm in the server side
        id,
        userData: {
          id,
        },
        date,
      },

      ... more posts ...

    ],
  },

  ... more users ...
}

In my algorithm, I just need to insert all the posts that are inside a given list

 [
    { id: "post1", { userData: { id: "alex" }, date },
    { id: "post2", { userData: { id: "sara" }, date }
 ]

in the posts list of each respective user.

Problem

I also need to avoid inserting posts that already exists in my state, and I can't find a simple way to do it optimally.

Current code

This is my current implementation. I feel that this can be done easier and faster. Any help?

/*
  Algorithm
*/

function addContents(state, contents, contentType, cached) {
  const newState = state;

  contents.forEach((content) => {
    const { userData: { id: userId } } = content;

    const prevUserState = state.get(userId);
    const prevContents = prevUserState?.[contentType] ?? [];
    const newContents = prevContents;

    // TODO - Avoid inserting if already exists in prevContents! (check by **id**)

    let inserted = false;

    for (const [index, prevContent] of prevContents.entries()) {
      // Replace
      if (content.id === prevContent.id) {
         newContents[index] = content;
         inserted = true;
         break;
      }

      // Insert in the correct order
      if(content.date >= prevContent.date) {
        newContents.splice(index, 0, content);
        inserted = true;
        break;
      }
    }

    if (!inserted) {
      newContents.push(content);
    }

    newState.set([
      userId,
      {
        ...prevUserState,
        [contentType]: newContents
      }
    ]);
  });

  // if(isEqual(state, newState)) return state; (deep compare to avoid re-renderizations because of state update)

  return new Map([...newState]);
}




/*
  Test
*/


(() => {
  // State
  const state = new Map([]);
  
  // User ALEX
  const userId1 = "alex";
  const userPosts1 = [ // already sorted by date
    {
       id: "78q78w0w0",
       userData: {
        id: userId1,
       },
       date: new Date("10/26/1999 00:00:01")
     },
     {
       id: "92uwdq092",
       userData: {
        id: userId1,
       },
       date: new Date("10/26/1999 00:00:00")
     }
  ];
  state.set(userId1, { posts: userPosts1 });
  
  // User SARA
  const userId2 = "sara";
  const userPosts2 = [ // already sorted by date
    {
       id: "iipzxx115",
       userData: {
        id: userId2,
       },
       date: new Date("12/25/2003 03:30:10")
    },
    {
       id: "Wxrr22232",
       userData: {
        id: userId2,
       },
       date: new Date("01/01/2000 17:44:41")
     }
  ];
  state.set(userId2, { posts: userPosts2 });
  

 
  const newPosts = [
    {
      id: "OLDEST FOR ALEX!",
      userData: {
        id: userId1
      },
      date: new Date("10/25/1999 23:59:59")
    },
    {
      id: "NEWEST FOR SARA!",
      userData: {
        id: userId2
      },
      date: new Date("01/05/2010 22:22:22")
    },
    {
      id: "OLDEST FOR SARA!",
      userData: {
        id: userId2
      },
      date: new Date("10/25/1999 23:59:59")
    }
  ]

  addContents(state, newPosts, "posts");

  console.log(state.get(userId1))
  console.log(state.get(userId2))
})();
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Note: As this method is implemented in a React's reducer, to manage complex states, I am returning a new Map, after deep comparing the previous and the new state, to produce UI re-renderizations.

UPDATE

I have implemented another version where I do what I need, but maybe, it can be more optimized.

function addContents(state, contents, contentType, cached) {
  const newState = state;

  const exists = {}; // optimization

  for (const content of contents) {
    const {
      userData: { id: userId },
    } = content;

    const prevUserState = state.get(userId);
    const prevContents = prevUserState?.[contentType] ?? [];
    const newContents = prevContents;

    if (cached) {
      if (!exists[userId]) {
        exists[userId] = prevContents.reduce((map, content) => {
          map[content.id] = true;
          return map;
        }, {});
      }

      // Avoid inserting if necessary
      if (exists[userId][content.id]) {
        break;
      }
    }

    // Insert the new content in the user's content list

    console.log(`Inserting ${content.id}`);

    let inserted = false;

    for (const [index, prevContent] of prevContents.entries()) {
      // Replace
      if (content.id === prevContent.id) {
         newContents[index] = content;
         inserted = true;
         break;
      }

      // Insert in the correct order
      if(content.date >= prevContent.date) {
        newContents.splice(index, 0, content);
        inserted = true;
        break;
      }
    }

    if (!inserted) {
      newContents.push(content);
    }

    newState.set([
      userId,
      {
        ...prevUserState,
        [contentType]: newContents
      }
    ]);
  }

  // if (isEqual(state, newState)) return state;

  return new Map([...newState]);
}

/*
  Test
*/


(() => {
  // State
  let state = new Map([]);
  
  // User ALEX
  const userId1 = "alex";
  const userPosts1 = [ // already sorted by date
    {
       id: "78q78w0w0",
       userData: {
        id: userId1,
       },
       date: new Date("10/26/1999 00:00:01")
     },
     {
       id: "92uwdq092",
       userData: {
        id: userId1,
       },
       date: new Date("10/26/1999 00:00:00")
     }
  ];
  state.set(userId1, { posts: userPosts1 });
  
  // User SARA
  const userId2 = "sara";
  const userPosts2 = [ // already sorted by date
    {
       id: "iipzxx115",
       userData: {
        id: userId2,
       },
       date: new Date("12/25/2003 03:30:10")
    },
    {
       id: "Wxrr22232",
       userData: {
        id: userId2,
       },
       date: new Date("01/01/2000 17:44:41")
     }
  ];
  state.set(userId2, { posts: userPosts2 });
  

 
  const newPosts = [
    {
      id: "OLDEST FOR ALEX!",
      userData: {
        id: userId1
      },
      date: new Date("10/25/1999 23:59:59")
    },
    {
      id: "NEWEST FOR SARA!",
      userData: {
        id: userId2
      },
      date: new Date("01/05/2010 22:22:22")
    },
    {
      id: "OLDEST FOR SARA!",
      userData: {
        id: userId2
      },
      date: new Date("10/25/1999 23:59:59")
    }
  ]

  state = addContents(state, newPosts, "posts");

  console.log(state.get(userId1))
  console.log(state.get(userId2))
  
  /*
     Insert again!
  */
  state = addContents(state, newPosts, "posts", true);
})();
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

use an object instead of an array: This is the same concept of the normalizr library for redux: https://github.com/paularmstrong/normalizr

state = { 
  [user1Id]: {
    posts: {
      [post1Id]: { 
        id,
        userData: {
          id,
        },
        date,
      },
      [post2Id]: { 
        id,
        userData: {
          id,
        },
        date,
      },

      ... more posts ...

    },
  },

  ... more users ...
}

This way you can easily access the object you want by its Id and check if it exists: e.g. if(state[23].posts[12]) if you need to iterate the users or a user posts use object.keys(state).map(user => ...) or object.keys(state[23].posts).map(user => ...)

INSERT/UPDATE: state[23].posts[newId]: { ...newPost}

CodePudding user response:

I'm not able to follow what you are doing but I think this is what you are after. You can do it to a oneline very easy.

newdata = [{ id: "post1", { userData: { id: "alex" }, date }]
if(!oldstates.find(d => 
  d.id === newdata.id && 
  d.userData.id === newdata.userData.id && 
  d.date === newdata.date 
)) {
 oldstates.push(newdata)
}
// oneliner
if(!oldstates.find(d => d.id === newdata.id && d.userData.id === newdata.userData.id && d.date === newdata.date )) oldstates.push(newdata)
  • Related