Merge pull request #128 from youyun/english
issue 126 translate design twitter
278
data_structure/design_Twitter.md
Normal file
@ -0,0 +1,278 @@
|
||||
# Design Twitter
|
||||
|
||||
**Translator: [youyun](https://github.com/youyun)**
|
||||
|
||||
**Author: [labuladong](https://github.com/labuladong)**
|
||||
|
||||
[Design Twitter](https://leetcode.com/problems/design-twitter/) is question 355 on LeetCode. This question is both interesting and practical. It combines both algorithms about ordered linked lists and Object Oriented (OO) design principles. We'll be able to link Twitter functions with algorithms when we look at the requirements.
|
||||
|
||||
### 1. The Question and Use Cases
|
||||
|
||||
Twitter is similar to Weibo. We'll focus on the APIs below:
|
||||
|
||||
```java
|
||||
class Twitter {
|
||||
|
||||
/** user post a tweet */
|
||||
public void postTweet(int userId, int tweetId) {}
|
||||
|
||||
/** return the list of IDs of recent tweets,
|
||||
from the users that the current user follows (including him/herself),
|
||||
maximum 10 tweets with updated time sorted in descending order */
|
||||
public List<Integer> getNewsFeed(int userId) {}
|
||||
|
||||
/** follower will follow the followee,
|
||||
create the ID if it doesn't exist */
|
||||
public void follow(int followerId, int followeeId) {}
|
||||
|
||||
/** follower unfollows the followee,
|
||||
do nothing if the ID does not exist */
|
||||
public void unfollow(int followerId, int followeeId) {}
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at an user story to understand how to use these APIs:
|
||||
|
||||
```java
|
||||
Twitter twitter = new Twitter();
|
||||
|
||||
twitter.postTweet(1, 5);
|
||||
// user 1 posts a tweet with ID 5
|
||||
|
||||
twitter.getNewsFeed(1);
|
||||
// return [5]
|
||||
// Remarks: because each user follows him/herself
|
||||
|
||||
twitter.follow(1, 2);
|
||||
// user 1 starts to follow user 2
|
||||
|
||||
twitter.postTweet(2, 6);
|
||||
// user 2 posted a tweet with ID 6
|
||||
|
||||
twitter.getNewsFeed(1);
|
||||
// return [6, 5]
|
||||
// Remarks: user 1 follows both user 1 and user 2,
|
||||
// return the recent tweets from both users,
|
||||
// with tweet 6 in front of tweet 5 as tweet 6 is more recent
|
||||
|
||||
twitter.unfollow(1, 2);
|
||||
// user 1 unfollows user 2
|
||||
|
||||
twitter.getNewsFeed(1);
|
||||
// return [5]
|
||||
```
|
||||
|
||||
This is a common case in our daily life. Take Facebook as an example, when I just added my dream girl as friend on Facebook, I'll see her recent posts in my refreshed feeds, sorted in descending order. The difference is Twitter is uni-directional, while Facebook friends are bi-directional.
|
||||
|
||||
Most of these APIs are easy to implement. The most functionally difficult part could be `getNewsFeed`, as we have to sort by time in descending. However, the list of followees are dynamic, which makes these hard to keep track of.
|
||||
|
||||
__Algorithm helps here__: Imagine we store each user's own tweets in a linked list sorted by timestamp, with each node representing the tweet's ID and timestamp (datetime of creation). If a user follows k followees, we can combine these k ordered linked lists, and apply an algorithm to get the correct `getNewsFeed`.
|
||||
|
||||
Let's put the algorithm aside first and discuss in details later. There is another question: how should we use code to represent users and tweets to apply the algorithm? __This involves OO design__. Let's break into parts and tackle them one step at a time.
|
||||
|
||||
### 2. OO Design
|
||||
|
||||
Based on the analysis just now, we need a `User` class to store information about users, and a `Tweet` class to store information of tweets. The Tweet class will also be nodes in linked lists. Let's put up the frameworks:
|
||||
|
||||
```java
|
||||
class Twitter {
|
||||
private static int timestamp = 0;
|
||||
private static class Tweet {}
|
||||
private static class User {}
|
||||
|
||||
/* the APIs skeleton */
|
||||
public void postTweet(int userId, int tweetId) {}
|
||||
public List<Integer> getNewsFeed(int userId) {}
|
||||
public void follow(int followerId, int followeeId) {}
|
||||
public void unfollow(int followerId, int followeeId) {}
|
||||
}
|
||||
```
|
||||
|
||||
Because `Tweet` class needs to store timestamp, and `User` class needs to use `Tweet` class to store the tweets posted by a user, we put `Tweet` class and `User` class in `Twitter` class as inner class. For clarity and simplicity, we'll define them one by one.
|
||||
|
||||
**1、Implementation of Tweet Class**
|
||||
|
||||
Based on the previous analysis, it is easy to implement `Tweet` class. Each `Tweet` instance just needs to store its own `tweetId` and posted timestamp `time`. As node in linked list, it also needs to have a point `next` pointing to the next node.
|
||||
|
||||
```java
|
||||
class Tweet {
|
||||
private int id;
|
||||
private int time;
|
||||
private Tweet next;
|
||||
|
||||
// initialize with tweet ID and post timestamp
|
||||
public Tweet(int id, int time) {
|
||||
this.id = id;
|
||||
this.time = time;
|
||||
this.next = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
**2、Implementation of User Class**
|
||||
|
||||
Let's think about the real use cases. A user needs to store his/her `userId`, list of followees, and list of posted tweets. The list of followees can use Hash Set to store data, to avoid duplication and search fast. The list of posted tweets should be stored in a linked list to merge with order. Refer to the diagram below:
|
||||
|
||||

|
||||
|
||||
Besides, based on OO design principles, since the list of followees and the list of tweets are stored in `User`, actions such as "follow", "unfollow", and "post" should be `User`'s actions. Let's define these as `User`'s APIs:
|
||||
|
||||
```java
|
||||
// static int timestamp = 0
|
||||
class User {
|
||||
private int id;
|
||||
public Set<Integer> followed;
|
||||
// The head of the linked list of posted tweets by the user
|
||||
public Tweet head;
|
||||
|
||||
public User(int userId) {
|
||||
followed = new HashSet<>();
|
||||
this.id = userId;
|
||||
this.head = null;
|
||||
// follow the user him/herself
|
||||
follow(id);
|
||||
}
|
||||
|
||||
public void follow(int userId) {
|
||||
followed.add(userId);
|
||||
}
|
||||
|
||||
public void unfollow(int userId) {
|
||||
// a user is not allowed to unfollow him/herself
|
||||
if (userId != this.id)
|
||||
followed.remove(userId);
|
||||
}
|
||||
|
||||
public void post(int tweetId) {
|
||||
Tweet twt = new Tweet(tweetId, timestamp);
|
||||
timestamp++;
|
||||
// insert the new tweet to the head of the linked list
|
||||
// the closer a tweet is to the head, the larger the value of time
|
||||
twt.next = head;
|
||||
head = twt;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3、 Implementation of Several APIs**
|
||||
|
||||
```java
|
||||
class Twitter {
|
||||
private static int timestamp = 0;
|
||||
private static class Tweet {...}
|
||||
private static class User {...}
|
||||
|
||||
// we need a mapping to associate userId and User
|
||||
private HashMap<Integer, User> userMap = new HashMap<>();
|
||||
|
||||
/** user posts a tweet */
|
||||
public void postTweet(int userId, int tweetId) {
|
||||
// instantiate an instance if userId does not exist
|
||||
if (!userMap.containsKey(userId))
|
||||
userMap.put(userId, new User(userId));
|
||||
User u = userMap.get(userId);
|
||||
u.post(tweetId);
|
||||
}
|
||||
|
||||
/** follower follows the followee */
|
||||
public void follow(int followerId, int followeeId) {
|
||||
// instantiate if the follower does not exist
|
||||
if(!userMap.containsKey(followerId)){
|
||||
User u = new User(followerId);
|
||||
userMap.put(followerId, u);
|
||||
}
|
||||
// instantiate if the followee does not exist
|
||||
if(!userMap.containsKey(followeeId)){
|
||||
User u = new User(followeeId);
|
||||
userMap.put(followeeId, u);
|
||||
}
|
||||
userMap.get(followerId).follow(followeeId);
|
||||
}
|
||||
|
||||
/** follower unfollows the followee, do nothing if follower does not exists */
|
||||
public void unfollow(int followerId, int followeeId) {
|
||||
if (userMap.containsKey(followerId)) {
|
||||
User flwer = userMap.get(followerId);
|
||||
flwer.unfollow(followeeId);
|
||||
}
|
||||
}
|
||||
|
||||
/** return the list of IDs of recent tweets,
|
||||
from the users that the current user follows (including him/herself),
|
||||
maximum 10 tweets with updated time sorted in descending order */
|
||||
public List<Integer> getNewsFeed(int userId) {
|
||||
// see below as we need to understand the algorithm
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Design of The Algorithm
|
||||
|
||||
The algorithm which combines k ordered linked list is implemented using Priority Queue. This data structure is an important application of Binary Heap. All inserted elements are auto sorted. When some random elements are inserted, we can easily take them out in ascending or descending order.
|
||||
|
||||
```python
|
||||
PriorityQueue pq
|
||||
# insert with random elements
|
||||
for i in {2,4,1,9,6}:
|
||||
pq.add(i)
|
||||
while pq not empty:
|
||||
# pop out the first (smallest) element each time
|
||||
print(pq.pop())
|
||||
|
||||
# Sorted Output:1,2,4,6,9
|
||||
```
|
||||
|
||||
Based on this cool data structure, we can easily implement the core function. Note that we use Priority Queue to sort `time` in __descending order__, because the larger the value of `time`, the more recent it is, and hence, the close to the head it should be placed:
|
||||
|
||||
```java
|
||||
public List<Integer> getNewsFeed(int userId) {
|
||||
List<Integer> res = new ArrayList<>();
|
||||
if (!userMap.containsKey(userId)) return res;
|
||||
// IDs of followees
|
||||
Set<Integer> users = userMap.get(userId).followed;
|
||||
// auto sorted by time property in descending order
|
||||
// the size will be equivalent to users
|
||||
PriorityQueue<Tweet> pq =
|
||||
new PriorityQueue<>(users.size(), (a, b)->(b.time - a.time));
|
||||
|
||||
// first, insert all heads of linked list into the priority queue
|
||||
for (int id : users) {
|
||||
Tweet twt = userMap.get(id).head;
|
||||
if (twt == null) continue;
|
||||
pq.add(twt);
|
||||
}
|
||||
|
||||
while (!pq.isEmpty()) {
|
||||
// return only 10 records
|
||||
if (res.size() == 10) break;
|
||||
// pop the tweet with the largest time (the most recent)
|
||||
Tweet twt = pq.poll();
|
||||
res.add(twt.id);
|
||||
// insert the next tweet, which will be sorted automatically
|
||||
if (twt.next != null)
|
||||
pq.add(twt.next);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
Here is a GIF I created to describe the process of combining linked lists. Assume there are 3 linked lists of tweets, sorted by `time` property in descending order, we'll combine them in `res` in descending order. Note that the numbers in the nodes are `time` property, not `id`:
|
||||
|
||||

|
||||
|
||||
As of now, the design of a simple Twitter timeline function is completed.
|
||||
|
||||
|
||||
### 4. Summary
|
||||
|
||||
In this article, we designed a simple timeline function using OO design principles and an algorithm which combines k sorted linked lists. This functionality is widely used in many social applications.
|
||||
|
||||
Firstly, we design the two classes, `User` and `Tweet`. On top of these, we used an algorithm to resolve the most important function. From this example, we can see that algorithms are not used alone in real applications. Algorithms need to be integrated with other knowledge to show their value.
|
||||
|
||||
However, our simple design may not cope with large throughput. In fact, the amount of data in real social applications is tremendous. There are a lot more aspects to take into consideration, including read and write performance to Database, the limit of memory cache, etc. Real applications are big and complicated engineering projects. For instance, the diagram below is a high-level system architecture diagram of a social network such as Twitter:
|
||||
|
||||

|
||||
|
||||
The problem we resolved is only a small part of the Timeline Service component. As the number of functions increases, the degree of complexity grows exponentially. Having one algorithm is not enough. It is more important to have a proper high-level design.
|
@ -1,277 +0,0 @@
|
||||
# 设计Twitter
|
||||
|
||||
「design Twitter」是 LeetCode 上第 335 道题目,不仅题目本身很有意思,而且把合并多个有序链表的算法和面向对象设计(OO design)结合起来了,很有实际意义,本文就带大家来看看这道题。
|
||||
|
||||
至于 Twitter 的什么功能跟算法有关系,等我们描述一下题目要求就知道了。
|
||||
|
||||
### 一、题目及应用场景简介
|
||||
|
||||
Twitter 和微博功能差不多,我们主要要实现这样几个 API:
|
||||
|
||||
```java
|
||||
class Twitter {
|
||||
|
||||
/** user 发表一条 tweet 动态 */
|
||||
public void postTweet(int userId, int tweetId) {}
|
||||
|
||||
/** 返回该 user 关注的人(包括他自己)最近的动态 id,
|
||||
最多 10 条,而且这些动态必须按从新到旧的时间线顺序排列。*/
|
||||
public List<Integer> getNewsFeed(int userId) {}
|
||||
|
||||
/** follower 关注 followee,如果 Id 不存在则新建 */
|
||||
public void follow(int followerId, int followeeId) {}
|
||||
|
||||
/** follower 取关 followee,如果 Id 不存在则什么都不做 */
|
||||
public void unfollow(int followerId, int followeeId) {}
|
||||
}
|
||||
```
|
||||
|
||||
举个具体的例子,方便大家理解 API 的具体用法:
|
||||
|
||||
```java
|
||||
Twitter twitter = new Twitter();
|
||||
|
||||
twitter.postTweet(1, 5);
|
||||
// 用户 1 发送了一条新推文 5
|
||||
|
||||
twitter.getNewsFeed(1);
|
||||
// return [5],因为自己是关注自己的
|
||||
|
||||
twitter.follow(1, 2);
|
||||
// 用户 1 关注了用户 2
|
||||
|
||||
twitter.postTweet(2, 6);
|
||||
// 用户2发送了一个新推文 (id = 6)
|
||||
|
||||
twitter.getNewsFeed(1);
|
||||
// return [6, 5]
|
||||
// 解释:用户 1 关注了自己和用户 2,所以返回他们的最近推文
|
||||
// 而且 6 必须在 5 之前,因为 6 是最近发送的
|
||||
|
||||
twitter.unfollow(1, 2);
|
||||
// 用户 1 取消关注了用户 2
|
||||
|
||||
twitter.getNewsFeed(1);
|
||||
// return [5]
|
||||
```
|
||||
|
||||
这个场景在我们的现实生活中非常常见。拿朋友圈举例,比如我刚加到女神的微信,然后我去刷新一下我的朋友圈动态,那么女神的动态就会出现在我的动态列表,而且会和其他动态按时间排好序。只不过 Twitter 是单向关注,微信好友相当于双向关注。除非,被屏蔽...
|
||||
|
||||
这几个 API 中大部分都很好实现,最核心的功能难点应该是 `getNewsFeed`,因为返回的结果必须在时间上有序,但问题是用户的关注是动态变化的,怎么办?
|
||||
|
||||
**这里就涉及到算法了**:如果我们把每个用户各自的推文存储在链表里,每个链表节点存储文章 id 和一个时间戳 time(记录发帖时间以便比较),而且这个链表是按 time 有序的,那么如果某个用户关注了 k 个用户,我们就可以用合并 k 个有序链表的算法合并出有序的推文列表,正确地 `getNewsFeed` 了!
|
||||
|
||||
具体的算法等会讲解。不过,就算我们掌握了算法,应该如何编程表示用户 user 和推文动态 tweet 才能把算法流畅地用出来呢?**这就涉及简单的面向对象设计了**,下面我们来由浅入深,一步一步进行设计。
|
||||
|
||||
### 二、面向对象设计
|
||||
|
||||
根据刚才的分析,我们需要一个 User 类,储存 user 信息,还需要一个 Tweet 类,储存推文信息,并且要作为链表的节点。所以我们先搭建一下整体的框架:
|
||||
|
||||
```java
|
||||
class Twitter {
|
||||
private static int timestamp = 0;
|
||||
private static class Tweet {}
|
||||
private static class User {}
|
||||
|
||||
/* 还有那几个 API 方法 */
|
||||
public void postTweet(int userId, int tweetId) {}
|
||||
public List<Integer> getNewsFeed(int userId) {}
|
||||
public void follow(int followerId, int followeeId) {}
|
||||
public void unfollow(int followerId, int followeeId) {}
|
||||
}
|
||||
```
|
||||
|
||||
之所以要把 Tweet 和 User 类放到 Twitter 类里面,是因为 Tweet 类必须要用到一个全局时间戳 timestamp,而 User 类又需要用到 Tweet 类记录用户发送的推文,所以它们都作为内部类。不过为了清晰和简洁,下文会把每个内部类和 API 方法单独拿出来实现。
|
||||
|
||||
**1、Tweet 类的实现**
|
||||
|
||||
根据前面的分析,Tweet 类很容易实现:每个 Tweet 实例需要记录自己的 tweetId 和发表时间 time,而且作为链表节点,要有一个指向下一个节点的 next 指针。
|
||||
|
||||
```java
|
||||
class Tweet {
|
||||
private int id;
|
||||
private int time;
|
||||
private Tweet next;
|
||||
|
||||
// 需要传入推文内容(id)和发文时间
|
||||
public Tweet(int id, int time) {
|
||||
this.id = id;
|
||||
this.time = time;
|
||||
this.next = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
**2、User 类的实现**
|
||||
|
||||
我们根据实际场景想一想,一个用户需要存储的信息有 userId,关注列表,以及该用户发过的推文列表。其中关注列表应该用集合(Hash Set)这种数据结构来存,因为不能重复,而且需要快速查找;推文列表应该由链表这种数据结构储存,以便于进行有序合并的操作。画个图理解一下:
|
||||
|
||||

|
||||
|
||||
除此之外,根据面向对象的设计原则,「关注」「取关」和「发文」应该是 User 的行为,况且关注列表和推文列表也存储在 User 类中,所以我们也应该给 User 添加 follow,unfollow 和 post 这几个方法:
|
||||
|
||||
```java
|
||||
// static int timestamp = 0
|
||||
class User {
|
||||
private int id;
|
||||
public Set<Integer> followed;
|
||||
// 用户发表的推文链表头结点
|
||||
public Tweet head;
|
||||
|
||||
public User(int userId) {
|
||||
followed = new HashSet<>();
|
||||
this.id = userId;
|
||||
this.head = null;
|
||||
// 关注一下自己
|
||||
follow(id);
|
||||
}
|
||||
|
||||
public void follow(int userId) {
|
||||
followed.add(userId);
|
||||
}
|
||||
|
||||
public void unfollow(int userId) {
|
||||
// 不可以取关自己
|
||||
if (userId != this.id)
|
||||
followed.remove(userId);
|
||||
}
|
||||
|
||||
public void post(int tweetId) {
|
||||
Tweet twt = new Tweet(tweetId, timestamp);
|
||||
timestamp++;
|
||||
// 将新建的推文插入链表头
|
||||
// 越靠前的推文 time 值越大
|
||||
twt.next = head;
|
||||
head = twt;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3、几个 API 方法的实现**
|
||||
|
||||
```java
|
||||
class Twitter {
|
||||
private static int timestamp = 0;
|
||||
private static class Tweet {...}
|
||||
private static class User {...}
|
||||
|
||||
// 我们需要一个映射将 userId 和 User 对象对应起来
|
||||
private HashMap<Integer, User> userMap = new HashMap<>();
|
||||
|
||||
/** user 发表一条 tweet 动态 */
|
||||
public void postTweet(int userId, int tweetId) {
|
||||
// 若 userId 不存在,则新建
|
||||
if (!userMap.containsKey(userId))
|
||||
userMap.put(userId, new User(userId));
|
||||
User u = userMap.get(userId);
|
||||
u.post(tweetId);
|
||||
}
|
||||
|
||||
/** follower 关注 followee */
|
||||
public void follow(int followerId, int followeeId) {
|
||||
// 若 follower 不存在,则新建
|
||||
if(!userMap.containsKey(followerId)){
|
||||
User u = new User(followerId);
|
||||
userMap.put(followerId, u);
|
||||
}
|
||||
// 若 followee 不存在,则新建
|
||||
if(!userMap.containsKey(followeeId)){
|
||||
User u = new User(followeeId);
|
||||
userMap.put(followeeId, u);
|
||||
}
|
||||
userMap.get(followerId).follow(followeeId);
|
||||
}
|
||||
|
||||
/** follower 取关 followee,如果 Id 不存在则什么都不做 */
|
||||
public void unfollow(int followerId, int followeeId) {
|
||||
if (userMap.containsKey(followerId)) {
|
||||
User flwer = userMap.get(followerId);
|
||||
flwer.unfollow(followeeId);
|
||||
}
|
||||
}
|
||||
|
||||
/** 返回该 user 关注的人(包括他自己)最近的动态 id,
|
||||
最多 10 条,而且这些动态必须按从新到旧的时间线顺序排列。*/
|
||||
public List<Integer> getNewsFeed(int userId) {
|
||||
// 需要理解算法,见下文
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 三、算法设计
|
||||
|
||||
实现合并 k 个有序链表的算法需要用到优先级队列(Priority Queue),这种数据结构是「二叉堆」最重要的应用,你可以理解为它可以对插入的元素自动排序。乱序的元素插入其中就被放到了正确的位置,可以按照从小到大(或从大到小)有序地取出元素。
|
||||
|
||||
```python
|
||||
PriorityQueue pq
|
||||
# 乱序插入
|
||||
for i in {2,4,1,9,6}:
|
||||
pq.add(i)
|
||||
while pq not empty:
|
||||
# 每次取出第一个(最小)元素
|
||||
print(pq.pop())
|
||||
|
||||
# 输出有序:1,2,4,6,9
|
||||
```
|
||||
|
||||
借助这种牛逼的数据结构支持,我们就很容易实现这个核心功能了。注意我们把优先级队列设为按 time 属性**从大到小降序排列**,因为 time 越大意味着时间越近,应该排在前面:
|
||||
|
||||
```java
|
||||
public List<Integer> getNewsFeed(int userId) {
|
||||
List<Integer> res = new ArrayList<>();
|
||||
if (!userMap.containsKey(userId)) return res;
|
||||
// 关注列表的用户 Id
|
||||
Set<Integer> users = userMap.get(userId).followed;
|
||||
// 自动通过 time 属性从大到小排序,容量为 users 的大小
|
||||
PriorityQueue<Tweet> pq =
|
||||
new PriorityQueue<>(users.size(), (a, b)->(b.time - a.time));
|
||||
|
||||
// 先将所有链表头节点插入优先级队列
|
||||
for (int id : users) {
|
||||
Tweet twt = userMap.get(id).head;
|
||||
if (twt == null) continue;
|
||||
pq.add(twt);
|
||||
}
|
||||
|
||||
while (!pq.isEmpty()) {
|
||||
// 最多返回 10 条就够了
|
||||
if (res.size() == 10) break;
|
||||
// 弹出 time 值最大的(最近发表的)
|
||||
Tweet twt = pq.poll();
|
||||
res.add(twt.id);
|
||||
// 将下一篇 Tweet 插入进行排序
|
||||
if (twt.next != null)
|
||||
pq.add(twt.next);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
这个过程是这样的,下面是我制作的一个 GIF 图描述合并链表的过程。假设有三个 Tweet 链表按 time 属性降序排列,我们把他们降序合并添加到 res 中。注意图中链表节点中的数字是 time 属性,不是 id 属性:
|
||||
|
||||

|
||||
|
||||
至此,这道一个极其简化的 Twitter 时间线功能就设计完毕了。
|
||||
|
||||
|
||||
### 四、最后总结
|
||||
|
||||
本文运用简单的面向对象技巧和合并 k 个有序链表的算法设计了一套简化的时间线功能,这个功能其实广泛地运用在许多社交应用中。
|
||||
|
||||
我们先合理地设计出 User 和 Tweet 两个类,然后基于这个设计之上运用算法解决了最重要的一个功能。可见实际应用中的算法并不是孤立存在的,需要和其他知识混合运用,才能发挥实际价值。
|
||||
|
||||
当然,实际应用中的社交 App 数据量是巨大的,考虑到数据库的读写性能,我们的设计可能承受不住流量压力,还是有些太简化了。而且实际的应用都是一个极其庞大的工程,比如下图,是 Twitter 这样的社交网站大致的系统结构:
|
||||
|
||||

|
||||
|
||||
我们解决的问题应该只能算 Timeline Service 模块的一小部分,功能越多,系统的复杂性可能是指数级增长的。所以说合理的顶层设计十分重要,其作用是远超某一个算法的。
|
||||
|
||||
最后,Github 上有一个优秀的开源项目,专门收集了很多大型系统设计的案例和解析,而且有中文版本,上面这个图也出自该项目。对系统设计感兴趣的读者可以点击「阅读原文」查看。
|
||||
|
||||
PS:本文前两张图片和 GIF 是我第一次尝试用平板的绘图软件制作的,花了很多时间,尤其是 GIF 图,需要一帧一帧制作。如果本文内容对你有帮助,点个赞分个享,鼓励一下我呗!
|
||||
|
||||
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
|
||||
|
||||

|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 334 KiB |
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 218 KiB |
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 343 KiB |