2

I want to build an effective data structure for such a case:
There are lots of users, each user has an ID and a name. Each user can follow others. There are four kinds of commands that I need to handle: create-user, follow, delete-user and cancel-follow. Here is an example:

create-user id=3 name="Chandler"
create-user id=7 name="Janice"
create-user id=2 name="Joey"
follow id=3 id=7     # Chandler follows Janice
follow id=7 id=3     # Janice follows Chandler
follow id=2 id=7
follow id=7 id=2
delete-user id=7
follow id=3 id=2
follow id=2 id=3
cancel-follow id=3 id=2

In a word, I need to read a file, containing lots of commands as above and process all of the data.

Here is what I've tried (functions to handle the four kinds of commands):

struct User
{
    unsigned long id;
    string name;
    unordered_set<User *> followers;
    unordered_set<User *> fans;

    User(unsigned long pid, const string & pname) : id(pid), name(pname) {}
};

list<User> users;

User & getUserById(list<User> & users, unsigned long id)
{
    auto it = std::find_if(users.begin(), users.end(), [&](User & u) {return id == u.id;});
    return *it;
}

void createUser(list<User> & users, unsigned long id, const string & str)
{
    users.emplace_back(User(id, str));
}

void deleteUser(list<User> & users, unsigned long id)
{
    auto itUser = std::find_if(users.begin(), users.end(), [&](User & u) {return id == u.id;});
    auto itFans = itUser->fans;
    for (auto it = itFans.begin(); it != itFans.end(); ++it)
    {
        (*it)->followers.erase(&*itUser);
    }
    auto itFollowers = itUser->followers;
    for (auto it = itFollowers.begin(); it != itFollowers.end(); ++it)
    {
        (*it)->fans.erase(&*itUser);
    }
    users.erase(itUser);
}

void buildRelation(list<User> & users, unsigned long follower, unsigned long fan)
{
    User & u1 = getUserById(users, follower);  //3
    User & u2 = getUserById(users, fan);  //7
    u1.fans.insert(&u2);
    u2.followers.insert(&u1);
}

void cancelRelation(list<User> & users, unsigned long follower, unsigned long fan)
{
    User & u1 = getUserById(users, follower);       //3
    User & u2 = getUserById(users, fan);  //2
    u1.fans.erase(&u2);
    u2.followers.erase(&u1);
}

It works without any error.

However, I used my code to process a file, containing 70000-line commands, it took about 67 seconds.

I really want to get a better performance (20 seconds maybe?), I know I need a better data structure but for now I have no idea how to design a better one.

Yves
  • 8,474
  • 7
  • 59
  • 114
  • 1
    You were testing with an optimized build right? Did you profile to see what was taking the time? – Retired Ninja Nov 29 '18 at 04:15
  • 1
    A note about `std::list`: It is really good for insert and delete anywhere and it has very favorable [iterator invalidation](https://stackoverflow.com/questions/6438086/iterator-invalidation-rules) that you are taking full advantage of, but for pretty much everything else you can do with it, it downright sucks. `getUserById` has the potential to be an absolute horrorshow of pointer chasing and cache misses. – user4581301 Nov 29 '18 at 04:30

2 Answers2

3

Your input data looks well suited to a key, value data structure like std::unordered_map (https://en.cppreference.com/w/cpp/container/unordered_map)

Where the id value could act as the key to store and retrieve the User data structure.

If you could process the data into a structure like std::unordered_map instead of list Then your getUserById function would not need to search the list every time to retrieve user data.

Kieran
  • 214
  • 1
  • 6
  • As an added bonus, `unordered_map` has very forgiving reference invalidation even though iterators can be invalidated easily when inserting items.. – user4581301 Nov 29 '18 at 04:35
  • Great. I just tested `std::unordered_map` and now it takes only 13 seconds. Pretty good. Thanks. – Yves Nov 29 '18 at 07:05
0

For user have a map kind of data structure key as I'd and value as Name.

Implement a directed graph as structure as

Graph{ intV;LinkedList<Map<Long,String>>adjList[]; }
  1. Create User:
    Create a node in graph

  2. Follow user : Create a directed link from that user to other user

  3. delete user : Using graph traversal technique find user and delete the node

  4. Cancel - Follow : Remove link from graph as it is directed graph

Hope it helps

Code_Eat_Sleep
  • 303
  • 2
  • 6