Bạn có chắc chắn muốn xóa bài viết này không ?
Bạn có chắc chắn muốn xóa bình luận này không ?
Xây dựng hệ thống “Gợi ý…” (Phần 3)
Chào các mẹ , tiếp nối bài hôm trước. Hôm nay chúng ta sẽ viết server api nhé.
Phần 1: http://cuthanh.com/nodejs/xay-dung-he-thong-goi-y-phan-1
Phần 2: http://cuthanh.com/nodejs/xay-dung-thong-goi-y-phan-2
Tổng thể
Các bước để em phát triển bất kì một ứng dụng gì
- Liệt kê những yêu cầu của bài toán
- Vẽ ra sơ đồ theo hành vi của người dùng, dòng chảy của dữ liệu (data-flow),… em có vẽ mà để ở nhà rồi, không chụp lên cho các bác coi được
- Vẽ ra cấu trúc modal
- Clone repo boilerplate của em sang
- Code
- Debug (cái khỉ này lâu nhất Mother fucker )
- Commit
- Xong!
Có thể đối với các bác thấy 3 bước đầu của em hơi xàm thì theo kinh nghiệm của em thì nó là quan trọng nhất đấy. Rất nhiều coder cứ thấy yêu cầu là cắm đầu vào code mà không biết trời đất là gì, rồi sau một hồi mân mê chả biết mình đang làm cái vẹo gì . Như mình đã nói trước đây
Cách code nhanh nhất là méo code cái gì cả
Khi bạn liệt kê yêu cầu, vẽ sơ đồ ra thì bài toán sẽ trở nên cực kì dễ hiểu. Đối với mình, một coder cũng rất rất cần bút và giấy không khác gì họa sĩ vậy. Chỉ có bút, giấy mới dễ dàng thể hiện ý tưởng của mình một cách nhanh nhất, tự do nhất thôi (Em không có tiền mua M$ Surface đâu nha, mấy bác đừng góp ý)
Yêu cầu bài toán
- Chức năng thêm, lấy danh sách, xóa Xmen.
- Mỗi Xmen có quyền “thả tim” Xmen khác
- Lấy gợi ý “suggest” cho một Xmen bất kì,… (yêu cầu quan trọng nhất)
Vẽ sơ đồ
Phần này mình xin nợ nhé
Cấu trúc model
Modal thì chỉ cần một cái để quản lý Xmen, lưu thông tin, “người yêu” các kiểu là được rồi.
File ./models/Xmen.coffee
mongoose = require 'mongoose'
AutoIncrement = require 'mongoose-sequence'
Schema = mongoose.Schema
xMen = new mongoose.Schema {
UID: Number
name: String
avatar: String
like: Array #Luu UID nhung nguoi "tha tim"
date:
type: Date
default: Date.now
}
xMen.plugin AutoIncrement, {inc_field: 'UID'}
module.exports = mongoose.model('Xmen', xMen)
Clone repo boilerplate
Như bác Bill đã dạy rồi, chọn cách ngắn nhất mà làm thôi các bác ạ . Em có sẵn một bolderplate cho việc xây dựng API server của mình, các bác có thể tìm trên mạng hoặc tự làm riêng cho mình một cái. Khi nào cần thì Git clone ...
là xong
Tôi chọn người lười biếng để làm những việc khó khăn. Bởi một người lười biếng sẽ tìm ra cách dễ dàng để làm việc đó
Bill gate - Sama
Bolderplate của em https://github.com/nlug/mt-bolderplate nếu các bác muốn sử dụng. Nhớ Star cho em nhé
Code
Xử lý các Xmen
POST /xmen
sẽ thêm một Xmen vào đội hình
app.get '/xmen', (req, res) ->
Xmen.find().exec (error, data) ->
res.json data
GET /xmen
sẽ lấy danh sách tất cả các Xmen
app.post '/xmen', (req, res) ->
if !req.body.name || !req.body.avatar
res.end "Need more information"
else
newXmen = new Xmen
name: req.body.name
avatar: req.body.avatar
newXmen.save (error) ->
throw error if error
res.status(201).json id: newXmen.UID
và GET /:UID
là lấy thông tin Xmen có UID là 123 gì đấy
app.get '/:id', (req, res) ->
Xmen.findOne({UID: req.params.id}).exec (error, data) ->
if data
res.json data
else
res.status(404).end();
Xử lý hành động “thả tim”
Route sẽ là POST /:UID-cua-Xmen/Like
và body.uid
của request sẽ lưu UID của người được thích
app.post '/:id/like', (req, res) ->
Xmen.findOne({UID: req.params.id}).exec (error, data) ->
if !data || error
res.status(404)
else
checker = data.like.indexOf req.body.uid
if (checker < 0)
# Chua tha tim
data.like.push req.body.uid
else
# Doi lai tim
data.like.splice checker, 1
data.markModified 'like'
data.save()
res.status(201).end('OK');
Xây dựng Logic cho route Suggest
Vâng, cái này là mục đích chính của chúng ta, nên cũng là bước khó nhất. Như đã nói ở phần 2, mình có đề xuất ra hai mô hình để lấy danh sách và xếp hạng các Xmen được gợi ý.
Cả hai thuật toán đều cần một hàm tính sự tương đồng của hai nhân vật. Do đó mình quăng nó vào một hàm
getSameScore = (a, b) ->
sum = _.union a, b
return 0 if sum.length == 0
intersection = _.intersection a, b
return intersection.length/sum.length
Route cho việc lấy danh sách Gợi ý… của Xmen có UID sẽ là GET /:UID/suggest
Thuật toán thứ 1 mà mình đưa ra
app.get '/:id/suggest', (req, res) ->
Xmen.findOne({UID: req.params.id}).exec (error, curXmen) ->
if !curXmen || error
res.status(404)
else
Xmen.find({UID: $ne: req.params.id}).lean().exec (error, others) ->
# tinh diem giong nhau
returnArray = []
for xmen in others
xmen.score = getSameScore(curXmen.like, xmen.like)
console.log "#{xmen.name} got score #{xmen.score}"
xmen.suggestScore = 0 if !xmen.suggestScore
if xmen.score > 0.2 # Diem toi thieu giong nhau de co the dua ra goi y
listSuggest = _.difference xmen.like, curXmen.like
console.log "#{xmen.name} like differ list", listSuggest
for people in listSuggest
if people != curXmen.UID && curXmen.like.indexOf(people) # Loai bo nhung thang da thich ra
index = _.findIndex(others, {UID: people})
console.log "Calculate score for #{people.name} index at #{index}"
if (others[index].suggestScore)
others[index].suggestScore += xmen.score;
else
others[index].suggestScore = xmen.score;
console.log "#{xmen.name} suggest #{others[index].name} for #{others[index].suggestScore}"
# Sap xep theo diem
sorted = _.sortBy others, ['suggestScore','score'] # sap xep theo suggestScore roi toi score
sorted = _.reverse sorted # mac dinh no sap tu be den lon, minh doi nguoc lai
# Loc ket qua
sorted = _.reject sorted, (xmen) ->
return (xmen.suggestScore == xmen.score == 0) || curXmen.like.indexOf(xmen.UID) >= 0
res.json sorted
- Mình sẽ giải thích đống code trên cho các bác
- Đầu tiên là phải lấy thông tin nhân vật Xmen đang xét tới rồi –
curXmen
. - Tiếp theo lấy danh sách các bác dị nhân còn lại
- Tính điểm giống nhau của từng người trong danh sách với nhân vật
curXmen
- Nếu nhân vật
Xmen
này có điểm giống nhau vớicurXmen > 0.2
(Tùy các bác thiết lập) thì sẽ lấy danh sách những thằngXmen
like mà thằngcurXmen
không - Lưu điểm “Gợi ý –
xmen.suggestScore
“ - Sắp xếp theo điểm “Gợi ý –
xmen.suggestScore
“, nếu trung nhau thì sắp xếp theo điểm “Giống nhau –xmen.score
Lưu ý ở trên là điểm Gợi ý – suggestScore
là cộng dồn của điểm Giống nhau – score
mà mỗi người gợi ý. Nói vầy cho dể hiểu nhé: A gợi ý B với 0.69 điểm. C cũng gợi ý B với số điểm 0.96 điểm. Vậy số điểm gợi ý mà B nhận được là 1.65.
Thuật toán thứ 2 mà mình tham khảo
app.get '/:id/suggest2', (req, res) ->
Xmen.findOne({UID: req.params.id}).exec (error, curXmen) ->
if !curXmen || error
res.status(404)
else
Xmen.find({UID: $ne: req.params.id}).lean().exec (error, others) ->
# tinh diem giong nhau
returnArray = []
for xmen in others
xmen.totalScore = 0;
xmen.totalLike = 0;
if _.indexOf(curXmen.like, xmen.UID) < 0 # Da thich roi thi khong tinh diem cho thang nay nua
console.log "Okey let check #{xmen.name}"
for people in others
if people.UID != xmen.UID && _.indexOf(people.like, xmen.UID) >= 0
console.log "check #{xmen.name} with #{people.name}"
xmen.totalScore += getSameScore(curXmen.like, people.like)
console.log "#{xmen.name} got totalscore #{xmen.totalScore}"
xmen.totalLike += 1;
#if xmen.totalLike == 0 #ignore if no one like this men
if (xmen.totalLike > 0)
xmen.score = xmen.totalScore/xmen.totalLike
console.log "#{xmen.name} got score #{xmen.score}"
returnArray.push (xmen);
# Sap xep theo diem
sorted = _.sortBy(returnArray, ['score','totalLike']) # sap xep theo score
sorted = _.reverse(sorted) # mac dinh no sap tu be den lon, minh doi nguoc lai
res.json sorted
Giải thích
- Đầu tiên là phải lấy thông tin nhân vật Xmen đang xét tới rồi.
- Tiếp theo lấy danh sách các bác dị nhân còn lại
- Vì công thức tính điểm gợi ý cho thanh niễn A sẽ là
Tổng điểm giống nhau của các nhân vật thích A / Tổng nhân vật thích A
. Nên ta cần tính được các dữ kiện yêu cầu - Đọc code nghiên cứu đi, em lười giải thích rồi
Bộ source Api server của mình https://github.com/nlug/Professor-x
Okey vậy là xong 2 thuật toán khó nhất. Đố các bác phát hiện ra điều gì kì lạ trong 2 thuật toán đấy đấy. Bài tiếp theo mình sẽ xây dựng Web App để ứng dụng các API mình đã viết hôm nay. Đón đọc phần tiếp theo nhé
Bài gốc: http://cuthanh.com/nodejs/xay-dung-thong-goi-y-phan-3




