CMK

Let's create something!


  • Home

  • Categories

  • Tags

  • Archives

  • About

Trie

Posted on 2018-08-31 | In Algorithm | Comments:

what

Trie, is actually a more general tree, also prefix tree.

Used for massive words process, dictionary representation, and lexicographic sorting, word frequency calc.

How

I. TrieNode

TrieNode has mainly two parts:

  1. children, to link next possible characters appear in a words. If only considering ‘A-Z’, there is 26 children at most
  2. info used to record current TrieNode
    • isWord, denotes previous character is a word or not.
    • cnt, denotes number of words consist of current prefix

Note: this representation is a little different from binary tree, in which info data are stored for current Node. However, in Trie, info data are stored for previous Node, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
For dictionary: { "a", "bd", "c" }, the Trie would be represented as following:

root TrieNode(0xefe4)
- cnt: 0
- isWord: false
- children:{'a': TrieNode , 'b': TrieNode, 'c': TrieNode, ... }
/ | \
TrieNode(0xffe3) TrieNode(0xffe4) TrieNode(0xffe5)
- cnt: 1 - cnt: 0 - cnt: 1
- isWord: true - isWord: false - isWord: false
- children: null - children: {'d':TrieNode} - children: null
|
...

Implementation

  • interfaces

    • build (constructor)
    • isWord
    • countWord
    • insert
    • delete
    • hasPrefix
    • getPrefixNode
  • Time Complexity: [ n words, K is number of characters of the longest word ]

    • build: O(nK)
    • isWord: O(K)
    • countWord: O(K)
    • insert: O(K)
    • delete: O(K)
  • Space Complexity: [N is the number of all characters appears in the dictionary ]

    • too much space: O(N) +O(N^2) +O(N^3) +O(N^4) + O(N^K)
    • good news is: when it is a really dictionary, all the space will be filled up, so not so much waste
  • delete might be a little complex. The to-be-deleted word possibly :

    1
    2
    3
    4
    5
    6
    when need delete a node, what should really to be done????
    - delete the key from HashMap<Character, Node>
    - reset to null when using Node[]
    So, always ask two questions:
    - the node to be deleted, is part of other long word [by checking 'children']?
    - the node itself is the end of a word? [by checking 'isWord' & 'cnt']?
    • null, empty string, not exists in the Trie
    • contains other short words: “abc”, “ab”
      • recursively from bottom to top delete node, till the longest prefix
    • has same words as itself: “abc”, “abc”
      • don’t delete node, only update cnt
    • being a prefix of other long words: “abc”, “abcde”
      • don’t delete node, only update isWord
    • share prefix with other words: “abc”, “abd”
      • recursively from bottom to top delete node, till the longest prefix
    • only itself in the dictionary, not affect others “abc”
      • delte all nodes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210

import java.util.*;

class Node {
public int cnt;
public boolean isWord;
public Map<Character, Node> children;

public Node(){
this.cnt = 0;
this.isWord = false;
this.children = new HashMap<>();
}
}


class Trie{
Node root;
public Trie( String[] strArr ){
this.root = new Node();
for( String str : strArr ){
insert( str );
}
}

public boolean hasPrefix(String str ){
if( str == null || str.length() == 0 ) return true;
int len = str.length();
Node cur = this.root;
for( int i = 0; i < len; i++ ){
char ch = str.charAt(i);
if( !cur.children.containsKey(ch) ) return false;
cur = cur.children.get(ch);
}
return true;
}

public Node getPrefixNode (String str){
if( !hasPrefix(str) ) return null;
int len = str.length();
Node cur = this.root;
for( int i = 0; i < len; i++ ){
char ch = str.charAt(i);
cur = cur.children.get(ch);
}
return cur;
}

public void insert( String str ){
if( str == null || str.length() == 0 ) return;
int len = str.length();
Node cur = this.root;
for( int i = 0; i < len; i++ ){
char ch = str.charAt(i);
if( !cur.children.containsKey(ch) ){
cur.children.put( ch, new Node() );
}
cur = cur.children.get(ch);
}
cur.cnt++;
cur.isWord = true;
}

public void delete (String str ){
if( str == null || str.length() == 0 ) return;
if( !hasPrefix(str) ) return;
Node cur = getPrefixNode(str);
cur.cnt--;
if(cur.cnt < 0) cur.cnt = 0;
if(cur.cnt > 0) return; // at this position, there still word exist
else{ // at this position, there no word exist
if( cur.children.size() > 0 ) { // but can be prefix of other words
cur.isWord = false;
return;
}else{ // not a word, nor a prefix of others
deleteNode(str);
}
}

}
public void deleteNode( String str ){
int len = str.length();
if( len == 0 ) return;

String prevStr = str.substring(0, len-1);
Node prevNode = getPrefixNode(prevStr);
prevNode.children.remove(str.charAt(len-1));
if( prevNode.children.size()==0 && prevNode.cnt==0 ) delete(prevStr);
return;
}


public boolean isWord( String str ){
if( !hasPrefix(str) ) return false;
return getPrefixNode(str).isWord;
}

public int countWord( String str ){
if( !hasPrefix(str) ) return 0;
return getPrefixNode(str).cnt;
}


}

class TrieTree{
public static void main( String[] args ){
String[] dict = { "a", "ab", "ab", "abc", "abc", "abc", "abcd", "ac", "acd", "ad", "2,24", "2,2,4" };
Trie trieTree = new Trie(dict);

// String isWordTest1 = "ab";
// String isWordTest2 = "";
// String isWordTest3 = "a";
// String isWordTest4 = "abcd";
// String isWordTest5 = "abcde";

// String countTest = "abc";
// String insertTest = "abc";


// System.out.println( trieTree.isWord(isWordTest1));
// System.out.println( trieTree.isWord(isWordTest2));
// System.out.println( trieTree.isWord(isWordTest3));
// System.out.println( trieTree.isWord(isWordTest4));
// System.out.println( trieTree.isWord(isWordTest5));


// System.out.println( trieTree.countWord(countTest));
// trieTree.insert(insertTest);
// System.out.println( trieTree.countWord(countTest));
// trieTree.insert(insertTest);
// System.out.println( trieTree.countWord(countTest));


// String hasPrefixTest1 = "abcd";
// String hasPrefixTest2 = "2,2";
// String hasPrefixTest3 = "abcdd";
// System.out.println( trieTree.hasPrefix(hasPrefixTest1));
// System.out.println( trieTree.hasPrefix(hasPrefixTest2));
// System.out.println( trieTree.hasPrefix(hasPrefixTest3));


String deleteTest1 = "abcd";
String deleteTest2 = "abc";
String deleteTest3 = "ab";
trieTree.delete(deleteTest1);
System.out.println( trieTree.countWord(deleteTest3)); // 2
System.out.println( trieTree.countWord(deleteTest2)); // 3
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();

trieTree.delete(deleteTest1);
System.out.println( trieTree.countWord(deleteTest3)); // 2
System.out.println( trieTree.countWord(deleteTest2)); // 3
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();

trieTree.delete(deleteTest2);
System.out.println( trieTree.countWord(deleteTest3)); // 2
System.out.println( trieTree.countWord(deleteTest2)); // 2
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();

trieTree.delete(deleteTest2);
System.out.println( trieTree.countWord(deleteTest3)); // 2
System.out.println( trieTree.countWord(deleteTest2)); // 1
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();


trieTree.delete(deleteTest2);
System.out.println( trieTree.countWord(deleteTest3)); // 2
System.out.println( trieTree.countWord(deleteTest2)); // 0
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();



trieTree.delete(deleteTest2);
System.out.println( trieTree.countWord(deleteTest3)); // 2
System.out.println( trieTree.countWord(deleteTest2)); // 0
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();



trieTree.delete(deleteTest3);
System.out.println( trieTree.countWord(deleteTest3)); // 1
System.out.println( trieTree.countWord(deleteTest2)); // 0
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();



trieTree.delete(deleteTest3);
System.out.println( trieTree.countWord(deleteTest3)); // 0
System.out.println( trieTree.countWord(deleteTest2)); // 0
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();



trieTree.delete(deleteTest1);
System.out.println( trieTree.countWord(deleteTest3)); // 0
System.out.println( trieTree.countWord(deleteTest2)); // 0
System.out.println( trieTree.countWord(deleteTest1)); // 0
System.out.println();

}
}

Web Worker - not interfer with UI thread

Posted on 2018-08-12 | In Full stack | Comments:

I. What

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

II. To be continued

How to Make Animation using JS

Posted on 2018-08-11 | In Full stack | Comments:

I. problem

We want a function to move an element to its right by distance in specified time

  • API: function moveToRight(ele, dis, time) {}

II. Implementation

  1. must not be position: static: using ele.style.left + ele.offsetLeft
  2. no restrict on position: using `
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html>
<head>
<style>
#container{
width: 100px;
height: 100px;
border: 1px solid red;
position: relative;
}
</style>
</head>
<body>
<div id="container">
</div>

<script>


// Version 1
function moveToRight_2(ele, dis, time) {
var interval = 10
var oneStep = interval * dis / time
var totalSteps = dis / oneStep
var step = 0
let curLeft = ele.offsetLeft

var interval_handler= setInterval(function(){
curLeft += oneStep
ele.style.left = curLeft + 'px'
step += 1
if( step === totalSteps ) clearInterval(interval_handler)
}, interval)
}



// Version 2
function moveToRight(ele, dis, time) {
let d = dis/time * 10;
let cur = 0;
let myInterval;

myInterval = setInterval(function(){
cur += d;
if (cur <= dis) {
ele.style.transform = "translate(" + cur +"px, 0)";
} else {
clearInterval(myInterval);
console.log("stop");
}
}, 1);
}


// Test
var ele = document.getElementById("container");
moveToRight_2(ele, 100, 500);

</script>

</body>
</html>

Node.js

Posted on 2018-06-01 | In Full stack | Comments:

Event-driven & asynchrous IO & Event Emitter

  1. NOTE :

    • Node.js is an application [a process] in OS; but has multiple threads
    • Only one JS thread, all others are worker threads in thread pool used by OS
    • they share data via message queue
  2. Asynchrous I/O

    • Each I/O operation is assigned a worker thread in Node.js run time env;
    • But only one master thread (js engine) deal with computation
  3. Even Loop

    • Node.js — V8 — libuv — OS

    • libuv [cross platform Asynchronous I/O]: handles utilize other threads to complete asynchronous I/O

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
         ┌───────────────────────────┐
      ┌─>│ timers │
      │ └─────────────┬─────────────┘
      │ ┌─────────────┴─────────────┐
      │ │ pending callbacks │
      │ └─────────────┬─────────────┘
      │ ┌─────────────┴─────────────┐
      │ │ idle, prepare │
      │ └─────────────┬─────────────┘ ┌───────────────┐
      │ ┌─────────────┴─────────────┐ │ incoming: │
      │ │ poll │<─────┤ connections, │
      │ └─────────────┬─────────────┘ │ data, etc. │
      │ ┌─────────────┴─────────────┐ └───────────────┘
      │ │ check │
      │ └─────────────┬─────────────┘
      │ ┌─────────────┴─────────────┐
      └──┤ close callbacks │
      └───────────────────────────┘

Node.js use cases

  1. I/O-bound application
    • Except the only one JS processing thread, all other threads deal with I/O operation, so that I/O processing can happend simultaneously => save time
    • JS processing thread has no CPU-bound computation, only stay in event loop, and keep staring at the top of message queue; When some event done, handle its callbacks

process.nextTick() vs. setImmediate()

  1. tick: each iteration of an Event Loop is called a tick.
  2. process.nextTick() : will execute all callbacks set by it previousely in next tick
  3. setImmediate(): will execute only one callback in next tick, even set multiple callbacks

CommonJS: require & exports

  1. Sad History: Before CommonJS, JavaScript doesn’t have module importing feature, so it is limited to small script, cannot be used to develop large project

  2. Current state : Many modules like buffer File Http Socket can be imported using CommonJS

  3. Implementation:

    • module is is glolal object in Node.js, have property exports

      1
      module = { exports: {} }
    • Before we step into the details, let’s see before CommonJS, how to use import function. The public functions are exposed while the private properties and methods are encapsulated

      1
      2
      3
      4
      5
      6
      7
      8
      var revealingModule = (function () {
      var privateVar = "Ben Thomas";
      function setNameFn( strName ) {
      privateVar = strName;
      }
      return { setName: setNameFn, };
      })();
      revealingModule.setName( "Paul Adams" );
    • When a module is exported, inside Node.js it will be wrapped as IIFE (Immediately Invoked Function Expression)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 1. original code
      var greet = function () { console.log('Hello World'); };
      module.exports = greet;

      // 2. Parsed code by Node.js
      (function (exports, require, module, __filename, __dirname) { //add by node.js
      var greet = function () { console.log('Hello World'); };
      module.exports = greet;
      }).apply(); //add by node.js

      return module.exports;
  4. How to use:

    • define module: module.exports or exports [module.exports === exports]

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /* --- circle.js --- */

      // 1st way
      exports.area = (radius) => Math.pow(radius, 2) * 3.14;
      exports.circunference = (radius) => 2 * radius * 3.14;

      // 2nd way
      const area = (radius) => Math.pow(radius, 2) * 3.14;
      const circunference = (radius) => 2 * radius * 3.14;
      module.exports = {area: area, circumference: circunference};

      // 3rd way
      module.exports = {
      area: (radius) => Math.pow(radius, 2) * 3.14;
      circunference: (radius) => 2 * radius * 3.14;
      }
    • use module everywhere:

      1
      2
      3
      const circle = require('./circle');
      circle.area(3)
      circle.circunference(3)

AMD: (Asynchronous Module Definition)

  1. Why need AMD?**

    • Since Node.js are always running locally inside server, the require are instantly completed, i.e. synchronously
    • Browser need to download js files from server asynchorously. While one file want require another, the other one might still not downloaded => ERROR!
    • Thus, we need another mechnism able to import modules asynchronously : AMD comes in!
  2. How to use it in browser?

    • [Good Article]: https://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

    • use define() to declare the module dependency for each file

    • download require.js & main.js in same project folder as other js files

    • only add one <script> tag to HTML is OK, as follows

      • 1
        <script data-main="scripts/main" src="scripts/require.js"></script>

Web

  1. Request Method

    • request.method: GET, POST, PUT, DELETE

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        function (req, res) { 
        switch (req.method)
        case 'POST':
        update(req, res);
        break;
        case 'DELETE':
        remove(req, res);
        break;
        case 'PUT':
        create(req, res);
        break;
        case 'GET':
        default:
        get(req, res);
        }
        }
  2. Request URL

    • complete URL like the following: (Hash part will be discarded)

      1
      http://user:pass@host.com:8080/p/a/t/h?query=string#hash
    • pathname = url.parse(req.url).pathname

    • routers in Node.js:

      • url: /[controller]/[action]/a/b/c

      • handlers for different controllers & actions

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        // routes different urls to their related handlers
        var url = require('url')
        function (req, res) {
        var pathname = url.parse(req.url).pathname;
        var paths = pathname.split('/');
        var controller = paths[1] || 'index';
        var action = paths[2] || 'index';
        var args = paths.slice(3);
        if (handles[controller] && handles[controller][action]) {
        handles[controller][action].apply(null, [req, res].concat(args));
        } else {
        res.writeHead(500);
        res.end('找不到响应控制器'); }
        }

        // handlers object
        handles.index = {};
        handles.index.index = function (req, res, foo, bar) {
        res.writeHead(200);
        res.end(foo);
        };
  3. Request Query String

    • query = url.parse(req.url).query

    • url: /domainname/path?foo=bar&baz=val

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        //eg1.  '/domainname/path?foo=bar&baz=val' 
        {
        foo: 'bar',
        baz: 'val'
        }

        //eg2. '/domainname/path?foo=bar&foo=val'
        {
        foo: ['bar', 'val'],
        }
  4. Cookie

    • workflow: [ key point: browser sends cookie for each request]

      • server sends cookie to browser
      • browser stores it in localstorage
      • browser sends cookie every time making request
    • [For browser:] string: Cookie: foo=bar; baz=val => req.header.cookie

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        // browser parse cookie
        var parseCookie = function (cookie) {
        var cookies = {};
        if (!cookie) { return cookies; }
        var list = cookie.split(';');
        for (var i = 0; i < list.length; i++) {
        var pair = list[i].split('=');
        cookies[pair[0].trim()] = pair[1];
        }
        return cookies;
        };

        // browser adds cookie to `req` object
        function (req, res) {
        req.cookies = parseCookie(req.headers.cookie);
        hande(req, res);
        }
    • [For server:]

      • Format:

        • 1
          Set-Cookie: name=value; Path=/ ; Expires = Sun, 01/21/1987 09:01:01 GMT; Domain = .domain.com;
      • arguments:

        • name&value: the useful stuff
        • domain: cookie only sent to the domain
        • path: cookie only sent when this domain/path
        • expire / maxAge: when or how long expires
        • HttpOnly: will make this cookie cannot be access by document.cookie
        • secure: only sent when HTTPS; not sent when HTTP
      • Serialize Cookie [Server] : convert cookie to string

        • 1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          var serialize = function (name, val, opt) { 
          var pairs = [name + '=' + encode(val)]; opt = opt || {};
          if (opt.maxAge) pairs.push('Max-Age=' + opt.maxAge);
          if (opt.domain) pairs.push('Domain=' + opt.domain);
          if (opt.path) pairs.push('Path=' + opt.path);
          if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());
          if (opt.httpOnly) pairs.push('HttpOnly');
          if (opt.secure) pairs.push('Secure');
          return pairs.join('; ');
          };
  5. Session

    • why need it: cookie with too much data will make delay in network transmission

    • Solution:

      • store data on server side, give a key (sessionID) to cookie to refer its data
      • set expire to its corresponding cookie
  6. Cache response file [ has nothing to do with cookie ]

    • Client: If-Modified-Since, Server: Last-Modified
    • Client: If-None-Match, Server: ETag
      • solve problem: even if file modified, but content didn’t modified
      • compare the digest of target file
    • Server: Expires / Cache-Control
      • the first 2 ways still need to send request, and wait for response => waste time!
      • This approach doesn’t need to send request if doesn’t expires
      • But there is a problem: the time are not the same on two sides, max-age of cache-controlcomes in
    • how to update cache when server file updated within expired period?
      • Problem: browser will use stale file, since it not expire
      • We also update the URL, since Cache pairs with URL
  7. Authentication / Authorization

    • basic authentication
      • request with : useranme & password
    • OAuth2.0
      • request with access_token
    • Bearer token: JWT
      • request with Json Web Token
  8. Form Data

    • content-type: application/x-www-form-urlencoded (“foo=bar&baz=val”)
    • content-type: application/json;
    • content-type: application/xml;
  9. Simulate a Client(browser) & a Server

    1. server implementation [send response as a server]

      1. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        let http = require('http')
        let fs = require('fs')
        let url = require('url')

        http.creatServer( function(req, res){
        let pathname = url.parse(req.url).pathname
        console.log("request for " + pathname + "received.")

        fs.readFile(pathname.substr(1), function(err, data){
        if(err){
        console.log(err)
        res.writeHead(404, {'Content-Type': 'text/html'})
        }else{
        res.writeHead(200, {'Content-Type': 'text/html'})
        res.write(data.toString())
        }
        res.end()
        })
        }).listen(8080)

        console.log("Server running at http://127.0.0.1:8080/")
    2. client implementation [send request as a browser]

      1. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        let http = require('http')

        let options ={
        host: 'localhost',
        port: '8080',
        path: '/index.html'
        }

        let callback = function(res){
        let body = ''
        res.on( 'data', function(data){
        body += data
        })

        res.on( 'end', function(){
        console.log(body)
        })
        }

        let req = http.request(options, callback)
        req.end()

XSS / CSRF

  1. XSS: cross site script
    • inject script from unsafe input, and runs it to get cookie, ie sessionID
    • [Solution]: convert to HTML entities: & will be &amp;
  2. CSRF:
    • don’t bother to get sessionID
    • lure customer to another website, while he is in middle of session of some weak-protection Bank website
    • the other website will submit a form to Bank website indicating transfer money
    • Bank website server cannot tell from whom that request is. It still believe that is from its customer since he is in the middle of the session
    • [Solution]:
      • the Bank form add another random hidden input field
      • each time receive request, check if the random value equal to sent one
      • This way the form cannot be duplicate

Global variables

  1. console
    • console.log('11')
  2. process
    • process.exit(0)
    • process.nextTick(callback)
  3. __filename: absolute path + name for the current script being executed
  4. __dirname: absolute path for the current script being executed

Util Objects

  1. util: make one object inherit from the other one
    1. util.inherits(constructor, superConstructor)

Versions

  1. semantic versioning
    • consists of three numbers, separated by periods, such as 2.3.0
    • the middle number has to be incremented, when new functionality is added
    • the first number has to be incremented, when compatibility is broken, so that existing code that uses the package might not work with the new version,
  2. caret character (^)
    • any version compatible with the given number may be installed
    • ^2.3.0 would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed

Express Framework

Posted on 2018-06-01 | In Full stack | Comments:

express()

  1. let express = require('express'): Creates an Express application

  2. express.static(root, [options]): serves static files and is based on serve-static.

    • here we can set etag lastModified maxAge etc.

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var options = {
      dotfiles: 'ignore',
      etag: false,
      extensions: ['htm', 'html'],
      index: false,
      maxAge: '1d',
      redirect: false,
      setHeaders: function (res, path, stat) {
      res.set('x-timestamp', Date.now())
      }
      }

      app.use(express.static('public', options))
  3. express.Router([options]): Creates a new router object. Refer to Routing section below

App

  1. The app object denotes the Express application

    1. 1
      2
      3
      4
      5
      6
      7
      8
      var express = require('express');
      var app = express();

      app.get('/', function(req, res){
      res.send('hello world');
      });

      app.listen(3000);
  2. sub-app :

    1. app.mountpath

      1. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        var express = require('express');

        var app = express(); // the main app
        var admin = express(); // the sub app

        admin.get('/', function (req, res) {
        console.log(admin.mountpath); // /admin
        res.send('Admin Homepage');
        });

        app.use('/admin', admin); // mount the sub app
    2. app.on('mount', callback(parent))

      1. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        var admin = express();

        admin.on('mount', function (parent) {
        console.log('Admin Mounted');
        console.log(parent); // refers to the parent app
        });

        admin.get('/', function (req, res) {
        res.send('Admin Homepage');
        });

        app.use('/admin', admin);
  3. app.METHOD(path, callback [, callback ...])

    • METHOD is the HTTP method of the request, such as GET, PUT, POST, and so on, in lowercase.
    • callback can be:
      • A middleware function.
      • A series of middleware functions (separated by commas).
      • An array of middleware functions.
      • A combination of all of the above.
  4. app.listen([port[, host[, backlog]]][, callback]) : Starts a UNIX socket , binds and listens for connections on the specified host and port.

  5. app.param([name], callback): Add callback triggers to route parameters, where name is the name of the parameter or an array of them, and callback is the callback function.

  6. app.path(): returns the canonical path of the app, a string.

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      var app = express()
      , blog = express()
      , blogAdmin = express();

      app.use('/blog', blog);
      blog.use('/admin', blogAdmin);

      console.log(app.path()); // ''
      console.log(blog.path()); // '/blog'
      console.log(blogAdmin.path()); // '/blog/admin'
  7. app.route(path): Returns an instance of a single route

    1. Use app.route() to avoid duplicate route names

    2. e.g.

    3. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var app = express();

      app.route('/events')
      .all(function(req, res, next) {
      // runs for all HTTP verbs first
      // think of it as route specific middleware!
      })
      .get(function(req, res, next) {
      res.json(...);
      })
      .post(function(req, res, next) {
      // maybe add a new event...
      });
  8. app.use([path,] callback [, callback...]): mount specified middleware at the specified path

Routing

  1. app.get( url, handler )

  2. app.post( url, handler )

  3. app.put( url, handler )

  4. app.delete( url, handler )

    • 1
      2
      3
      4
      5
      6
      let express = require('express')
      let app = express()

      app.get('/', function(req, res){
      res.send("Hello world")
      })
  5. app.all( url, handler ): special route method to load all headlers for GET, POST, PUT, DELETE, etc,

  6. route parameters

    1. 1
      2
      3
      4
      5
      6

      app.get('/users/:userId/books/:bookId', function (req, res) {
      res.send(req.params)
      })
      // If Request URL: http://localhost:3000/users/34/books/8989
      // Then req.params: { "userId": "34", "bookId": "8989" }
  7. route handlers:

    • Multiple route handlers can be put inside a route as function array

    • use next() to invoke the next handler to handle route

    • 1
      2
      3
      4
      5
      6
      app.get('/example/d', [cb0, cb1], function (req, res, next) {
      console.log('the response will be sent by the next function ...')
      next()
      }, function (req, res) {
      res.send('Hello from D!')
      })
  8. app.route()

    • create chainable route handlers for a route path by using app.route()

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      app.route('/book')
      .get(function (req, res) {
      res.send('Get a random book')
      })
      .post(function (req, res) {
      res.send('Add a book')
      })
      .put(function (req, res) {
      res.send('Update the book')
      })
  9. express.Router(): achieve relative & modular route handling

    • using express.Router() to create a modular route, which can handle relative route as a single module/ file

    • then pass it to the abosolute route handling module

    • [e.g. ]: The app will now be able to handle requests to /birds and /birds/about, as well as call the timeLog middleware function that is specific to the route.

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      /*--- bird.js : modular route handler ---*/

      var express = require('express')
      var router = express.Router()

      // middleware that is specific to this router
      router.use(function timeLog (req, res, next) {
      console.log('Time: ', Date.now())
      next()
      })
      // define the home page route
      router.get('/', function (req, res) {
      res.send('Birds home page')
      })
      // define the about route
      router.get('/about', function (req, res) {
      res.send('About birds')
      })

      module.exports = router


      /*--- app.js: include bird route handler ---*/
      var birds = require('./birds')
      // ...
      app.use('/birds', birds)

Middleware

  1. Credit to: https://segmentfault.com/a/1190000010877994

  2. different levels of middleware

    • Application-level middleware: app.use()

    • Router-level middleware router.use()

    • Built-in middleware express.static()

    • Third-party middleware app.use(cookieParser())

      • 1
        2
        3
        4
        5
        6
        var express = require('express')
        var app = express()
        var cookieParser = require('cookie-parser')

        // load the cookie-parsing middleware
        app.use(cookieParser())
  3. write our own middleware!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    // 1. create our own middleware
    function App() {
    if (!(this instanceof App))
    return new App();
    this.init();
    }
    App.prototype = {
    constructor: App,
    init: function() {
    this.request = { //模拟的request
    requestLine: 'POST /iven_ HTTP/1.1',
    headers: 'Host:www.baidu.com\r\nCookie:BAIDUID=E063E9B2690116090FE24E01ACDDF4AD:FG=1;BD_HOME=0',
    requestBody: 'key1=value1&key2=value2&key3=value3',
    };
    this.response = {}; //模拟的response
    this.chain = []; //存放中间件的一个数组
    this.index = 0; //当前执行的中间件在chain中的位置
    },
    use: function(handle) { //这里默认 handle 是函数,并且这里不做判断
    this.chain.push(handle);
    },
    next: function() { //当调用next时执行index所指向的中间件
    if (this.index >= this.chain.length)
    return;
    let middleware = this.chain[this.index];
    this.index++;
    middleware(this.request, this.response, this.next.bind(this));
    },
    }

    // 2. use it
    function lineParser(req, res, next) {
    let items = req.requestLine.split(' ');
    req.methond = items[0];
    req.url = items[1];
    req.version = items[2];
    next(); //执行下一个中间件
    }

    function headersParser(req, res, next) {
    let items = req.headers.split('\r\n');
    let header = {}
    for(let i in items) {
    let item = items[i].split(':');
    let key = item[0];
    let value = item[1];
    header[key] = value;
    }
    req.header = header;
    next(); //执行下一个中间件
    }


    let app = App();
    app.use(lineParser);
    app.use(headersParser);
    app.next();

request

The req object represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on

  1. req.baseUrl:

    • The URL path on which a router instance was mounted;

    • similar to the mountpath property of the app object, except app.mountpath returns the matched path pattern(s).

    • 1
      2
      3
      4
      5
      6
      7
      8
      var greet = express.Router();

      greet.get('/jp', function (req, res) {
      console.log(req.baseUrl); // /greet
      res.send('Konichiwa!');
      });

      app.use('/greet', greet); // load the router on '/greet'
  2. req.body: key-value pairs of data submitted in the request body; Need body-parser()

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var app = require('express')();
      var bodyParser = require('body-parser');

      app.use(bodyParser.json()); // for parsing application/json
      app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

      // Now all JSON & Raw text are parsed, and ready to use as `req.body`
      app.post('/profile', function (req, res, next) {
      console.log(req.body);
      res.json(req.body);
      });
  3. req.cookies: using cookie-parse to set cookies onto request

  4. req.hostname : hostname of the Host HTTP header.

  5. req.ip: remote IP address of the request.

  6. req.method: method of request: GET, POST, PUT, and so on.

  7. req.originalUrl vs req.baseUrl vs req.path:

    1. 1
      2
      3
      4
      5
      6
      app.use('/admin', function(req, res, next) {  // GET 'http://www.example.com/admin/new'
      console.log(req.originalUrl); // '/admin/new'
      console.log(req.baseUrl); // '/admin'
      console.log(req.path); // '/new'
      next();
      });
  8. req.query: object containing a property for each query string parameter in the route

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // GET /search?q=tobi+ferret
      req.query.q
      // => "tobi ferret"

      // GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
      req.query.order
      // => "desc"

      req.query.shoe.color
      // => "blue"

      req.query.shoe.type
      // => "converse"
  9. req.route: currently-matched route, a string.

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      app.get('/user/:id?', function userIdHanler(req, res) {
      console.log(req.route);
      res.send('GET');
      });

      ==>
      { path: '/user/:id?',
      stack:
      [ { handle: [Function: userIdHandler],
      name: 'userIdHandler',
      params: undefined,
      path: undefined,
      keys: [],
      regexp: /^\/?$/i,
      method: 'get' } ],
      methods: { get: true } }

response

represents the HTTP response that an Express app sends when it gets an HTTP request.

  1. res.app === req.app: a reference to the instance of the Express application that is using the middleware.

  2. res.headersSent: indicates if the app sent HTTP headers for the response.

  3. res.append(field [, value]): Appends the specified value to the HTTP response header field

  4. res.cookie(name, value [, options]): Sets cookie name to value

    1. 1
      2
      3
      4
      5
      res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
      res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
      //Default encoding
      res.cookie('some_cross_domain_cookie', 'http://mysubdomain.example.com',{domain:'example.com'});
      // Result: 'some_cross_domain_cookie=http%3A%2F%2Fmysubdomain.example.com; Domain=example.com; Path=/'
  5. res.clearCookie(name [, options]): Clears the cookie specified by name.

    1. 1
      2
      res.cookie('name', 'tobi', { path: '/admin' });
      res.clearCookie('name', { path: '/admin' });
  6. res.download(path [, filename] [, options] [, fn]): Transfers the file at path as an “attachment”. Typically, browsers will prompt the user for download.

    1. 1
      2
      3
      4
      5
      6
      7
      8
      res.download('/report-12345.pdf', 'report.pdf', function(err){
      if (err) {
      // Handle error, but keep in mind the response may be partially-sent
      // so check res.headersSent
      } else {
      // decrement a download credit, etc.
      }
      });
  7. res.end([data] [, encoding]): Ends the response process

    1. If you need to respond with data, instead use methods such as res.send()and res.json().
  8. res.format(object): when present. It uses req.accepts() to select a handler for the request

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      ``res.format({
      'text/plain': function(){
      res.send('hey');
      },

      'text/html': function(){
      res.send('<p>hey</p>');
      },

      'application/json': function(){
      res.send({ message: 'hey' });
      },

      'default': function() {
      // log the request and respond with 406
      res.status(406).send('Not Acceptable');
      }
      });
  9. res.set(field, vale): set response header

  10. res.get(field): get HTTP response header specified by field

  11. res.json([body]): Sends a JSON response.

    1. 1
      2
      res.json({ user: 'tobi' });
      res.status(500).json({ error: 'message' });
  12. res.redirect([status,] path): Redirects to the URL derived from the specified path, with specified status

    1. 1
      2
      res.redirect(301, 'http://example.com');
      res.redirect('../login');
  13. res.send([body]): Sends the HTTP response.

    1. 1
      2
      3
      4
      5
      res.send(new Buffer('whoop'));
      res.send({ some: 'json' });
      res.send('<p>some html</p>');
      res.status(404).send('Sorry, we cannot find that!');
      res.status(500).send({ error: 'something blew up' });
  14. res.sendFile(path [, options] [, fn]): Transfers the file at the given path.

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      app.get('/file/:name', function (req, res, next) {

      var options = {
      root: __dirname + '/public/',
      dotfiles: 'deny',
      headers: {
      'x-timestamp': Date.now(),
      'x-sent': true
      }
      };

      var fileName = req.params.name;
      res.sendFile(fileName, options, function (err) {
      if (err) {
      next(err);
      } else {
      console.log('Sent:', fileName);
      }
      });

      });
  15. res.sendStatus(statusCode): Sets the response HTTP status code to statusCode and send its string representation as the response body.

    1. 1
      2
      3
      4
      res.sendStatus(200); // equivalent to res.status(200).send('OK')
      res.sendStatus(403); // equivalent to res.status(403).send('Forbidden')
      res.sendStatus(404); // equivalent to res.status(404).send('Not Found')
      res.sendStatus(500); // equivalent to res.status(500).send('Internal Server Error')

bodyParser

  1. urlencoded format data submitted from <form>,can be parsed intores.body

    1
    app.use(bodyParser.urlencoded({ extended: false }));
  2. json format data transferred using API, can be parsed into res.body

    1
    app.use(bodyParser.json());

set(): set globle variable in Express

You can use the express instance to store and retrieve variables. In this case, you can set the title to be “My Site” and retrieve it later with something like var title = app.get('title');without the need to declare and keep a global variable messing around

1
2
3
4
5
6
app.set('title','My Website');  // set()
var title = app.get('title'); // get()

<=>

title = 'My Website'

Authetication: JWT vs Session

Posted on 2018-05-21 | In Full stack | Comments:

I. Why do we need Authetication?

Scenario: bank accout transaction

Authetication: an approach to know “Alice” from client side is the real “Alice” in the bank database

The bank web server needs a way to identify its customers’ true identity, knowing ‘Alice’ is the real ‘Alice’ as she claims.

II. session&cookie authetication

1. Solution A

An approach is checking her account&password. If matches, she can access Alice’s bank account; Otherwise, no way to Alice’s bank account.

2. Problem of Solution A

HTTP is stateless. After Alice log into the server, next time when Alice make another request to draw ¥100 from her account (subtract ¥100 from her accout). Now she still needs to prove she is Alice. But asking her accout&password again and again is rediculous!

3. Solution B:session & cookie

  • First time, the Alice log into her account on bank server;
  • Bank server maintains a session for her, and issues sessionID to her cookie.
  • Later, each time Alice want to make a transaction, she makes HTTP requests with that sessionID(all HTTP requests carry cookie )
  • Until a certain period of time, the session for Alice become invalid if no activities from Alice
  • After a inactive period, if Alice wants to make transaction again, she has to log into system to set up a new session in bank server
  • Serveris the safe guard**, preventing evil from accessing sensitive bank account information. Using sessionandcookie, customers finally don’t have to input their account&password again and again!

4. New problem in “distributed” serivce system

  • Distributed means: after Alice log into her account on server#1, next time she might be directed by load balancer to server#2, if at that time server#1 is already serving extremely large amount of people.

  • New problem is: session & cookie approach doesn’t work in this case. Server#1 has Alice’s necessarycontext for following transaction after she logs into it. But server#2 doesn’t even know who she is! So, Alice is asked for her account and password again! Bad user experience!!!

5. Soutions for “distributed” serivce system

【好文】https://www.cnblogs.com/study-everyday/p/7853145.html

  • version 1: session dupication (多机同步)
    • 思路:多个web-server之间相互同步session,这样每个web-server之间都包含全部的session
    • 优点:web-server支持的功能,应用程序不需要修改代码
    • 不足:
      • session的同步需要数据传输,占内网带宽,有时延
      • 所有web-server都包含所有session数据,数据量受内存限制,无法水平扩展
      • 有更多web-server时要歇菜
  • version 2: Session Storage at brower (客户端存储法)
    • 思路:服务端存储所有用户的session,内存占用较大,可以将session存储到浏览器cookie中,每个端只要存储一个用户的数据了
    • 优点:服务端不需要存储
    • 缺点:
      • 每次http请求都携带session,占外网带宽
      • 数据存储在端上,并在网络传输,存在泄漏、篡改、窃取等安全隐患
      • session存储的数据大小受cookie限制
  • version 3: Nginx reverse proxy hash: (反向代理hash一致性)

    • 思路:反向代理层使用用户ip来做hash,以保证同一个ip的请求落在同一个web-server上
    • 优点:

      • 只需要改nginx配置,不需要修改应用代码
      • 负载均衡,只要hash属性是均匀的,多台web-server的负载是均衡的
      • 可以支持web-server水平扩展(session同步法是不行的,受内存限制)
    • 不足:

      • 如果web-server重启,一部分session会丢失,产生业务影响,例如部分用户重新登录
      • 如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确的session
  • version 4: Database layer for whole session(后端统一集中存储)

    • 思路:将session存储在web-server后端的存储层,数据库或者缓存
    • 优点:

      • 没有安全隐患
      • 可以水平扩展,数据库/缓存水平切分即可
      • web-server重启或者扩容都不会有session丢失
    • 不足:

      • 增加了一次网络调用,并且需要修改应用代码

      session读取的频率会很高,数据库压力会比较大。如果有session高可用需求,cache可以做高可用,但大部分情况下session可以丢失,一般也不需要考虑高可用。

  • Summary: for improved session&cookie approach

    对于方案3和方案4,推荐后者:

    • web层、service层无状态是大规模分布式系统设计原则之一,session属于状态,不宜放在web层
    • 让专业的软件做专业的事情,web-server存session?还是让cache去做这样的事情吧。

III. JWT authetication

好文: https://www.ctolib.com/flylib-fly-auth.html

  1. server不再作为敏感数据库的关卡,为client服务;
  2. brower自己每次都携带authentication token进行request

0.【JWT本质:】

  • 相当于每次browser request,都要求用户输入account & password
  • 即:第一次用户登陆成功后,server根据其“用户名密码”生成唯一“令牌token”,往后该用户只要带上该token进行访问就可以
  • 相同点:session方法类似,cookie中的sessionID也相当于“令牌token”,每次请求都携带上
  • 不同点:
    • JWT方法的token生成依赖于用户名密码与SHA256,不受server环境影响
    • session方法的sessionID生成依赖于server session context,每个server生成的sessionID不一致,无法适用于distributed system

1. Problems with session approach

  • memcache或者redis的稳定性会影响业务系统的可用性
  • 微服务的并发能力受限于memcache或者redis的并发能力
  • 由此可见,集中的session管理也不是最优的选择。
  • 我们不再关注session本 ,把session的问题抛开,让我们的服务都是无状态的。
  • 于是我们把目光转向WEB TOKEN: JWT(Json web token )

2. Benefits using JWT

【好文】https://www.ctolib.com/topics-113075.html

  • 解决跨域问题:这种基于Token的访问策略可以克服cookies的跨域问题。
  • 服务端无状态可以横向扩展,Token可完成认证,无需存储Session。
  • 系统解耦,Token携带所有的用户信息,无需绑定一个特定的认证方案,只需要知道加密的方法和密钥就可以进行加密解密,有利于解耦。
  • 防止跨站点脚本攻击,没有cookie技术,无需考虑跨站请求的安全问题。

3. Principle of JWT 原理简介

JSON Web Tokens的格式组成,jwt是一段被base64编码过的字符序列,用点号分隔,一共由三部分组成,头部header,消息体playload和签名sign

  • 头部Header是json格式:

    1
    2
    3
    4
    5
    {
    "typ":"JWT", // 该类型是JWT类型
    "alg":"HS256", // 加密方式声明是HS256
    "exp":1491066992916 // expire 代表过期时间
    }
  • 消息体Playloadjson格式:

    消息体的具体字段可根据业务需要自行定义和添加,只需在解密的时候注意拿字段的key值获取value。

    {
        "userid":"123456",
        "iss":"companyName" 
    }
    
  • 签名sign的生成

    • 签名的生成是把header和playload分别使用base64url编码,接着用’.‘把两个编码后的字符串连接起来
    • 再把这拼接起来的字符串配合密钥进行HMAC SHA-256算法加密
    • 然后再次使用base64url编码,这就拿到了签名sign.
    • 最后把header和playload和sign用’.‘ 连接起来就生成了整个JWT。

4. Principle of JWT Checking 校验简介

JWT不能保护数据,只能验证数据是否是原始数据,是否被篡改过

JWT是由header.playload.sign连接组成,只有sign是用密钥加密的,而所有的信息都在header和playload中可以直接获取,sign的作用只是校验header和playload的信息是否被篡改过

  1. 加密:比如要加密验证的是userid字段:

    • 首先按前面的格式组装json消息头header和消息体playload,按header.playload组成字符串;
    • 再根据密钥和HS256加密header.playload得到sign签名;
    • 最后得到jwtToken为header.playload.sign,在http请求中的url带上参数想后端服务请求认证
  2. 解密:后端服务校验jwtToken是否有权访问接口服务,进行解密认证,如校验访问者的userid:

    • 用将字符串按.切分三段字符串,分别得到header和playload和sign。
    • 然后将header.playload拼装用密钥和SHA-256算法进行加密然后得到新的字符串和sign进行比对,
    • 如果一样就代表数据没有被篡改,然后从头部取出exp对存活期进行判断
    • 如果超过了存活期就返回空字符串,如果在存活期内返回userid的值

The Joy of Egg-Dropping(2-egg Puzzle)

Posted on 2018-05-17 | In Algorithm | Comments:

I. Problem statement

1. Easy version

Given infinite number of same eggs, try with minimun trials to find out the highest floor where tossing the egg won’t result in egg crash. The building has 200 floors.

  • 变量:N=200
  • 目标:试验次数最小
  • 约束:none

2. Hard version

Only 2 eggs were given, try with minimun trials to find the highest floor where tossing the egg won’t result in egg crash. The building has 200 floors. (The egg can be reused as long as it is intact)

  • 变量:F=200, E=2
  • 目标:试验次数最小
  • 约束:

II. Solution for easy version

Since the number of eggs we can use is unlimited, we can use the fastest approach in the universe to find the unknown target in a list: binary search

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- left direction: break 
- right direction:intact

0 100 200
/ \
50 150
/ \ / \
25 75 125 175
/ \
...

Toss an egg on floor 100:
1. if egg break, toss another on floor 50;
1.1 if egg break, toss another on floor 25;
1.1.1 ...
1.1.2 ...
1.2 if doesn't break, toss another on floor 75;
1.2.1 ...
1.2.2 ...
2. if doesn't break, toss another on floor 150;
2.1 if egg break, toss another on floor 125;
2.1.1 ...
2.1.2 ...
2.2 if doesn't break, toss another on floor 175;
2.2.1 ...
2.2.2 ...

III. Solution for hard version

Since it is not so intuitive at first glance, we will take a few trials to get a deeper understanding of this problem.

General idea:

egg-1 used to narrow down possible range
egg-2 used to sequentially find the target floor

  1. [egg-1 with step=10] egg-1 tossed on floor 20,40,60,80,100,120,… with a step=20

    • worst-case: 29 trials
      • egg-1 tossed on 200 and break: 10 trials
      • egg-2 tossed on 199 (from 181), break: 19 trials
    • best-case: 2 trials
      • egg-1 tossed on 20 and break: 1 trials
      • egg-2 tossed on 1: 1 trials
    • for this method, what is the min trials can we say?
      • 29 trials(the worst case), no worse than it
      • we cannot guarantee it is the best case
  2. [edge case: BS] egg-1 tossed on floor 100, 200

    • worst-case: 201 trials
      • egg-1 tossed on 200 and break: 2 trials
      • egg-2 tossed on 199 (from 101), break: 199 trials
    • best-case: 2 trials
      • egg-1 tossed on 100 and break: 1 trials
      • egg-2 tossed on 1: 1 trials
    • for this method, what is the min trials can we say?
      • 201 trials(the worst case), no worse than it
  3. [Solution 1]

    • https://www.zhihu.com/question/19690210/answer/22937826
    • the ‘step’ affect min-trial
    • we want control ‘step’ to make the trials of worst case and best case as close as possible; This way it will divvy up the trial between worst and best cases
    • Goal: worst case trials = best case trials
    • egg-1 tossed on floor x, 2x-1, 3x-2, .. 1
    • 蛋一第一次扔在X层,第二次扔在比原来高 X-1层,… 高2, 高1,使得X+(X-1)+…+2+1=200
    • 最坏情况(=最好情况)总的次数就是:
      • 如果在第X层就碎,那么蛋二最坏情况要扔X-1,所以总数是 1+X-1=X
      • 如果在X+(X-1)碎,那么蛋二最坏情况要扔 X-2,所以2+X-2=X
    • x = 20

      1
      2
      3
      4
      5
      6
      7
      8
      |_|__
      |_| x-2
      |_|__
      |_| x-1
      |_|___
      |_| x
      |_|
      |_|___
    • 假设蛋一第一次扔在X层,第二次扔在比原来高 X-1层,… 高2, 高1,使得X+(X-1)+…+2+1=100,那么最坏情况总的次数就是:如果在第X层就碎,那么蛋二最坏情况要扔X-1,所以总数是 1+X-1=X如果在X+(X-1)碎,那么蛋二最坏情况要扔 X-2,所以2+X-2=X

  1. [Solution 2]

    • 链接:https://www.zhihu.com/question/19690210/answer/125713075

    • 子问题F[i, j]: H的可行范围为[0, i],手上还有j个鸡蛋的时候,最坏情况下的最小试验次数是多少。我们的目标函数就是F[N, K]。

    • 这个能够设立是基于这样一种想法,如果H的可能范围目前是[L, R],那么其状态与[0, R-L]是完全一致的,所以我们仅仅记录有可行范围的宽度就可以了。
    • F[i, j]=min{max{F[k-1, j-1], F[i-k, j]}}+1,
      • 其中1<=k<=i。k表示这一次实验是从k楼把鸡蛋扔下去,扔下去有两种结果。
      • 一种是鸡蛋碎了,那么剩余的鸡蛋个数是j-1,H的可行范围变为[0, k-1],那么需要的最少实验次数为F[k-1, j-1]。
      • 另一种是鸡蛋没碎,那么剩余的鸡蛋个数是j,H的可行范围是[k, i],那么需要的最少实验次数是F[k-i, j]。
    • 由于题目所求的是最坏情况下的最优策略,所以对每种k情况内取max,再对所有k取min。
    • 边界条件为F[0, j]=0,F[i, 1]=i。
    • 时间复杂度为O(NK^2)。

React Router v4

Posted on 2018-05-15 | In Full stack | Comments:

I. Three types of Components:

1. router components

create a history object, 提供“前进”、“后退”

  • <BrowserRouter>: for server that responds to requests
  • <HashRouter>: for a static file server.

2. route matching components

comparing a ‘s path prop with the current location’s pathname
只要路由匹配,Route所在位置就会render相应的Component

  • <Route> :包容性路由

    • 3个props:
      • path:匹配路径
      • component:对应的组件
      • render:没有可用组件时,直接render
        • 1个state:
      • match
      • match包含4个field:isExact,params,path,url
        • 1个context:
    • router obj
    • router 包含 history,routes
  • <Switch>:排他性路由

    1
    2
    3
    4
    5
    6
    7
    8
    <Switch>
    <Route exact path='/' component= {Home}/>
    <Route path='/about' component={About}/>
    <Route path='/contact' component={Contact}/>
    <Route component={NoMatch}/>
    </Switch>

    // when none of the above match, <NoMatch> will be rendered
    • How ?
      iterate over all of its children elements and only render the first one that matches the current location.
    • Props: component, render, and children

3. navigation components

  • <Link>
  • <NavLink> :a special type of that can style itself as “active” when its ‘to’ prop matches the current location.
  • <Redirect>: navigate using its ‘to’ prop
    • 注意:不是 “函数”,而是“组件”,需要放在render中return
    • 没有路由解析时候,可以直接使用redirect,重定向到默认页面

II. 3种方式renders Component

  • <Route component>
  • <Route render>
  • <Route children>

III. Route在render component同时,传入3个量,作为component的props(不论3中方式的哪一种)

  • match
  • history
  • location

比如render:func( )方式render conponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// eg1: 
<Route path="/home" render={() => <div>Home</div>}/>

// eg2: wrapping/composing
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)

<FadingRoute path="/cool" component={Something}/>"
- render props: path、children/render/component、
- render state:match
- component props(route所render): history、location、match
"不论是哪种方式render,都会拿到这三个props"
- 因此,eg2中render:func方式,直接将props作为function component的参数传入
"render={ props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}"

IV. Route vs Component如何关联

  • Router 将路径 match上之后,会调用相应的 Route, 由Route 将相关的参数:match、location、history等传入到 Component中
  • Route 的 state: match、location 传入到 Component的 props: match、location
  • The Route will render , where props are some router specific things that look like { match, location, history }.
  • If the user is not at /dashboard then the Route will render null. That’s pretty much all there is to it.

V. match.url vs match.path

A match object contains information about how a matched the URL.

  • match:是 route的state;是component的props
  • Eg: consider the route /users/:userId
    • match.path - (string) 用于匹配路径模式。用于构建嵌套的 ; match.path would be /users/:userId
    • match.url - (string) URL 匹配的部分。 用于构建嵌套的 ; match.url would have the :userId value filled in, e.g. “users/5”.”

VI. match.params 表示 url 匹配上的变量值

1
2
3
<Route path="/:id" component={Child} />

{match.params.id}

URL 后半部分由变量 id 兜住,如果进来的url为/abc,那么 match.params.id = abc

VII. Eg: Custom Link

Route实际上是 Component 的买办,为Component办事!
Route放在哪个位置,那里就有可能render出它携带的component(只要url match)

1
2
3
<Route path="/abc" component={Child_1} />
<Route path="/abc" component={Child_2} />
<Route path="/abc" component={Child_3} />

VIII. <Prompt>:防止中途跳转

适用情况:用户在填表过程中,点击了“后退”或“跳转链接”,需弹窗询问
<Prompt>是保留tag,跟<Link>一样

  • 创建一个wrapper,包含<form>, <Prompt>
  • wrapper中设置state.inMiddle,来决定是否render<Prompt>
  • 改变state.inMiddle的根源:form中的input输入框;
    • 只要有任何输入值,就将state.inMiddle置为true
    • 提交form后,将form reset()之后,需要将state.inMiddle置为false
  • Prompt的props:
    • when:检查state.inMiddle
    • message: 决定弹窗的显示信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Form extends React.Component {
state = {
isBlocking: false
};

render() {
const { isBlocking } = this.state;

return (
<form
onSubmit={event => {
event.preventDefault();
event.target.reset();
this.setState({
isBlocking: false
});
}}
>

<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>

<input
placeholder="type something"
onChange={event => {
this.setState({
isBlocking: event.target.value.length > 0
});
}}
/>


<button>Submit</button>

</form>
);
}
}

Reference

  • Official API: https://reacttraining.com/react-router/web/guides/philosophy
  • 中文版API:https://www.jianshu.com/p/e3adc9b5f75c

Node+MySQL

Posted on 2018-05-14 | In Full stack | Comments:

Install

This is a Node.js module, install:
npm install mysql --save

Connect

Before connecting to databases, make sure MySQL database is turned on.

【注意】:

  • 先执行 connect(),让connection object与数据库建立连接。
  • 然后,在对该connection执行.query(MySQL_Command, callback_func)
1
2
3
4
5
6
7
8
9
10
11
var mysql      = require('mysql');
var connection = mysql.createConnection({
host : 'example.org',
user : 'bob',
password : 'secret'
});

connection.query('SELECT 1', function (error, results, fields) {
if (error) throw error;
// connected!
});

Disconnect

1
2
3
connection.end(function(err) {
// Callback function
});

Escaping query values

Three ways to avoid SQL injection

  • mysql.escape()
  • connection.escape()
  • pool.escape()
  • ?placeholder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1 -connection.escape()
var userId = 'some user provided value';
var sql = 'SELECT * FROM users WHERE id = ' + connection.escape(userId);
connection.query(sql, function (error, results, fields) {
if (error) throw error;
// ...
});

// 2 - ? placeholder
var adr = 'Mountain 21';
var sql = 'SELECT * FROM customers WHERE address = ?';
con.query(sql, [adr], function (err, result) {
if (err) throw err;
console.log(result);
});

Creating a Database

1
2
3
4
connection.query("CREATE DATABASE mydb", function (err, result) {
if (err) throw err;
console.log("Database created");
});

Creating a Table

Two ways:

  • create database and table simultenously
  • first create database, and connect it; then create table with the connection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// approach 1

connection.query("CREATE DATABASE db", function (error, rows, fields) {
if (error) throw error;
});

connection.query("USE db", function (error, rows, fields) {
if (error) throw error;
});

connection.query("CREATE TABLE tb(id INT, name VARCHAR(50))", function (error, rows, fields) {
if (error) throw error;
});


//-----------------------
// approach 2

var con = mysql.createConnection({
host: "localhost",
user: "yourusername",
password: "yourpassword",
database: "mydb"
});

con.connect(function(err) {});

var sql = "CREATE TABLE customers (name VARCHAR(255), address VARCHAR(255))";

con.query(sql, function (err, result) {
if (err) throw err;
console.log("Table created");
});

Selecting From a Table

To select data from a table in MySQL, use the SELECT statement.

1
2
3
4
5
6
var sql = "SELECT * FROM customers WHERE address = 'Park Lane 38'";

con.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
});

Wildcard Characters%: represent zero, one or multiple characters:

1
var sql = "SELECT * FROM customers WHERE address LIKE 'S%'"

Insert Into Table

To fill a table in MySQL, use the INSERT INTO statement.

1
2
3
4
5
6
var sql = "INSERT INTO customers (name, address) VALUES ('Company Inc', 'Highway 37')";

con.query(sql, function (err, result) {
if (err) throw err;
console.log("1 record inserted");
});

Update Table

You can update existing records in a table by using the UPDATE statement:

1
2
3
4
5
6
var sql = "UPDATE customers SET address = 'Canyon 123' WHERE address = 'Valley 345'";

con.query(sql, function (err, result) {
if (err) throw err;
console.log(result.affectedRows + " record(s) updated");
});

Sort the Result

Use the ORDER BY statement to sort the result in ascending or descending order.

The ORDER BY keyword sorts the result ascending by default. To sort the result in descending order, use the DESC keyword.

1
2
3
4
5
6
var sql = "SELECT * FROM customers ORDER BY name DESC";

con.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
});

Delete Record

You can delete records from an existing table by using the DELETE FROM statement:

1
2
3
4
5
var sql = "DELETE FROM customers WHERE address = 'Mountain 21'";

con.query(sql, function (err, result) {
if (err) throw err;
console.log("Number of records deleted: " + result.affectedRows);

Limit the Result

You can limit the number of records returned from the query, by using the LIMIT statement:

1
2
3
4
5
6
var sql = "SELECT * FROM customers LIMIT 5";

con.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
});

Join Two or More Tables

Combine rows from two or more tables, based on a related column between them, by using a JOIN statement.

1
2
3
4
5
6
7
8
9
//users

[
{ id: 1, name: 'John', favorite_product: 154},
{ id: 2, name: 'Peter', favorite_product: 154},
{ id: 3, name: 'Amy', favorite_product: 155},
{ id: 4, name: 'Hannah', favorite_product:},
{ id: 5, name: 'Michael', favorite_product:}
]
1
2
3
4
5
6
// products
[
{ id: 154, name: 'Chocolate Heaven' },
{ id: 155, name: 'Tasty Lemons' },
{ id: 156, name: 'Vanilla Dreams' }
]

1. Inner join

These two tables can be combined by using users’ favorite_product field and products’ id field.

1
2
3
4
5
6
var sql = "SELECT users.name AS user, products.name AS favorite FROM users JOIN products ON users.favorite_product = products.id";

con.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
});

2. Left Join
If you want to return all users, no matter if they have a favorite product or not, use the LEFT JOIN statement:

1
2
3
4
SELECT users.name AS user,
products.name AS favorite
FROM users
LEFT JOIN products ON users.favorite_product = products.id

3. Right Join
If you want to return all products, and the users who have them as their favorite, even if no user have them as their favorite, use the RIGHT JOIN statement:

1
2
3
4
SELECT users.name AS user,
products.name AS favorite
FROM users
RIGHT JOIN products ON users.favorite_product = products.id

React: Expense Tracking app

Posted on 2018-04-23 | In Portfolios | Comments:

I. About

  • An expense tracking application proving CRUD using React.
  • Github:
    • https://github.com/caomingkai/Expense-Tracking.git
  • Chart1: Create

  • **Chart2: Update

  • Chart3: Delete

II. Structure

1. Components Structure

1
2
3
4
5
6
7
8
App
|___ Summary
|___ Form _ _ _ _ RecordsAPI
| |
|___ Table |
|_______ TableRow
|____ TableDisplay
|____ TableEdit
  • App Component: is the data hub
    • state: records for entries in Table component
    • state: isLoaded show loading info, before showing Table
    • state: error
    • method: componentDidMount() get data from database
    • method: addRecord() passed to Form
    • method: deleteRecord() passed to Table and TableRow
    • method: updateRecord() passed to Table and TableRow
    • method: credits() passed to Summary
    • method: debits() passed to Summary
    • method: balance() passed to Summary
  • Summary Component: displays negative/ positive summation for the records
  • Form Component: add record into records, updating Table
  • Table Component: just pass records to TableRow
  • TableRow Component: display each record in records
  • RecordsAPI: utility providing interface to make RESTful API call

2. File Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
|__node_modules
|
|__public
| |___ index.html
|
|__ src
|___ utils: RecordsAPI.js
|
|___ components:
|___ App.js
|___ Form.js
|___ Summary.js
|___ Table.js
|___ TableRow.js

III. Ajax request

Several Ways:

  1. Ajax Libraries
    • Axios
      • axios.get(url[, config])
      • axios.delete(url[, config])
      • axios.head(url[, config])
      • axios.options(url[, config])
      • axios.post(url[, data[, config]])
      • axios.put(url[, data[, config]])
    • jQuery AJAX
  2. Browser built-in window.fetch

    No need to prepend “window” before fetch(): default obj

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ## No need to prepend "window" before fetch, this is the default obj

    fetch('http://example.com/movies.json')
    .then(function(response) {
    return response.json();
    })
    .then(function(myJson) {
    console.log(myJson);
    });

When and where to initiate Ajax call?

Do it in componentDidMount lifecycle method. So you can use setState to update your component when the data is retrieved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}

IV. Concept

1. 组件数据流: Model -> View

  • 父组件M -> 子组件V

    • 父组件prop 之间传给 子组件 prop
  • 子组件M -> 父组件V

    • 回调函数
      • 父组件向子组件传递回调函数
      • JS中,function是一等公民,因此传入的值会作为自身field保存起来;与C/Java不同。
  • sibling 组件间传值 M->V
    • 必须依靠二者共同父组件来传递
      • 但等组件之间的关系越来越复杂时候,这种依靠父组件作为中间人传值方式 would be a mess!
      • Redux comes into picture

2. 双向绑定: Model <-> View

  • 通过绑定<input>的 onChange() 监视 View的变换
  • 在 onChange Handler中更新组件的值,完成 View => Model 的数据流。

3. React 生命周期

  • Mount
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()
  • Update
    • componentWillReceiveProps(): 将接收新的props
    • shouldComponentUpdate(): 是否应该被更新?
    • componentWillUpdate(): 组件即将更新
    • render(): 组件被渲染
    • componentDidUpdate(): 组件完成更新
  • Unmount
    • componentWillUnmount(): 组件被卸载前做一些数据清除
1…567…9
Mingkai Cao

Mingkai Cao

All-trade Jack, loves full-stack

85 posts
5 categories
136 tags
GitHub E-Mail
© 2020 Mingkai Cao
Powered by Hexo
|
Theme — NexT.Mist v6.0.6