Xây dựng hệ thống “Gợi ý…” (Phần 3)
White

Minh Thành viết ngày 16/08/2016

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

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/Likebody.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 ý.
alt text
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ới curXmen > 0.2 (Tùy các bác thiết lập) thì sẽ lấy danh sách những thằng Xmen like mà thằng curXmen 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

alt text
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

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Minh Thành

5 bài viết.
30 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
21 0
Chào các bác. Hôm nay mình sẽ việt một bài mang tính chuyên môn một tí nha, các bác ủng hộ Giới thiệu Hệ thống, hay là chức năng gợi ý – mình cũn...
Minh Thành viết gần 2 năm trước
21 0
White
17 6
Chào các CEO tương lai Tối qua mò mẫm kiếm film siêu anh hùng coi mà chả hiểu tự sao lại click vào cái film này coi, chắc có hình gái xinh . Sau k...
Minh Thành viết gần 2 năm trước
17 6
White
15 4
Tiếp tục loạt bài nào… (Link) Ý tưởng Đâu tiên phải xem ý tưởng mình gợi ý phòng cho giao sư như thế nào đã. Với hệ thống gợi ý như thế này, thì...
Minh Thành viết gần 2 năm trước
15 4
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
5 bài viết.
30 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!