Elevator Design

How would I design the elevators for a new 40 story office

building that had an average of 100 people per floor to most efficiently fill and empty the building given a standard 9-5 workday and traffic conditions in my city? The answer needed to be completely detailed, including expected passengers per car, time per stop, average floors stops per trip at various hours, etc.

1. Ask

  1. How many elevators are there?
  2. What is the capacity of each elevator?
  3. Is the efficiency goal focused only at the start & end of day & not in between (i.e. lunch time, breaks)?

2. General optimal critiera

  • provide even service to each floor
  • minimize how long passengers wait for an elevator to arrive
  • minimize how long passengers spend to get to their destination floor
  • serve as many passengers as possible

The first advice that I’ve read is to ask some questions before you start answering. It will show that you are strategic & don’t jump to random assumptions. So I will probably ask questions like: Is the efficiency goal focused only at the start & end of day & not in between (i.e. lunch time, breaks)? How many elevators are there? What is the capacity of each elevator?

2) Assuming that everything is average, i.e. 6 elevators, 15 people per elevator, and focus only on start and end date, then the sample data should follow a normal distribution.

730-8 – 2%

8-830 – 14%

830-9 – 34%

9-930 – 34%

930-10 – 14%

10-1030 – 2%

3) I will break this down & solve the worst case scenario first. This means, 34 people x 40 floors = 1360 people to be transported by 6 elevator x 15 = total 90 capacity during 830-9 or 9-930 am.

4) Focusing on this more manageable problem, 1360 / 90 means each elevator will make 15 full cycles (lobby to highest floor and back) 5) Since we want to minimize the cycle time for each elevator, we assign one elevator per subset of 40/6 consecutive floors. This should address the issue on minimizing time per stop. 6) That means, the final design should be a load balancing of the elevators by minimizing the travel time — Elevator A – 1st to 7th floor, B – 8th to 14th floor, and so forth. Do you guys see anything wrong with this line of thinking?

先说说我对单个电梯设计的想法(欢迎批评指正)

1 Elevator Object, 应该包含physical components: Door, Indicator Lights,
Control Panel. 一些性质(Non physical properties): Speed, Num of floors,
capacity, max weight. 所能从事的操作methods: moveto, stop, ringbell。然后电
梯应该能够handle user request, 所以还应有一个requestQueue, 电梯应该根据自己
的state 和 requestQueue做出moveto, stop的决定,所以有一component:
requestHandler(Strategy pattern),可以set不同的requestHanlder.

2 Door, properties: State, method: open, close, getState.

3 Indicator light(指示所到楼层),properties: state; method: on, off,
getState

4 Control Panel, 包含physical component: Floor Buttons, Other buttons(也可直
接把Buttons 当作 elevator的components,还没考虑哪一个方法好)

5 Button, properties: floorNum, Parent Elevator, methods: OnPress(Observer
Pattern).

6 ElevatorRequestHandler: handleRequest(Elevator ele, requestList rlist), 可
以define 一个interface, 然后又各种不同实现

7 Request: 可以define 一个abstract class, 然后有子类movingRequest,
helpRequest doorRequest etc.

A Single Elevator

Use Case:

  1. User
    1. press a button to summon the lift
    2. press a button to get to a specific floor
  2. Button
    1. floor button and level button
    2. illuminates when pressed
    3. place an ‘elevator request’ when pressed
  3. Elevator
    1. moves up/down
    2. open/close the door

ElevatorRequests Class

Each button press results in an elevator request which has to be served. Each of these requests is tracked at a global place. ElevatorRequests, the class which stores elevator requests can use different strategies to schedule the elevator requests.

ElevatorController

The elevator is controlled by a controller class which we call ElevatorController. The elevator controller instructs the elevator what to do and also can shutdown/start up the elevator of the building. The elevator controller reads the next elevator request to be processed and serves it.

Button (Abstract) Class

Button is abstract class defining common behavior like illuminate, doNotIlluminate. FloorButton, ElevatorButton extend Button type and define placeRequest() which is invoked when the button is pressed.

In conclusion, ElevatorController runs the show by reading the ElevatorRequests to process and instructing the Elevator what to do. User send request by pressing Buttons.

Extend the answer to multiple elevators

  1. Each elevator have 1 controller.
  2. Floor based requests can be served by any elevator, thus these requests are added to a common area accessible by all controllers.
  3. Each elevator controller runs as a separate thread and checks if it can process a floor request. Mind synchronization issues.

http://www.columbia.edu/~cs2035/courses/ieor4405.S13/p14.pdf

https://github.com/joeblau/sample-elevator-control-system

http://blog.jobbole.com/74672/

http://blog.gssxgss.me/elevator-simulator-java/

FacebookTwitterGoogle+Share

Count Univalue Subtrees

Count Univalue Subtrees

Given a binary tree, count the number of uni-value subtrees.

A Uni-value subtree means all nodes of the subtree have the same value.

For example:
Given binary tree,

              5
             / \
            1   5
           / \   \
          5   5   5

return 4.

Solution:

public int countUnivalSubtrees(TreeNode root) {
    int[] count = new int[1];
    isUnivalSubtrees(root, count);
    return count[0];
}

public boolean isUnivalSubtrees(TreeNode root, int[] count) {
    if (root == null) {
        return false;
    }
    boolean left = isUnivalSubtrees(root.left, count);
    boolean right = isUnivalSubtrees(root.right, count);
    if (!left && !right) {
        if (root.left == null && root.right == null) {
            count[0]++;
            return true;
        }
    } else if (left && right) {
        if (root.left.val == root.val && root.right.val == root.val) {
            count[0]++;
            return true;
        }
    } else if (left && !right) {
        if (root.right == null && root.left.val == root.val) {
            count[0]++;
            return true;
        }
    } else if (!left && right) {
        if (root.left == null && root.right.val == root.val) {
            count[0]++;
            return true;
        }
    }
    return false;
}

Ugly Number I & II

Ugly Number I

Write a program to check whether a given number is an ugly number.

Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 6, 8 are ugly while 14 is not ugly since it includes another prime factor 7.

Note that 1 is typically treated as an ugly number.

public boolean isUgly(int num) {
        if (num <= 0) {
            return false;
        }
        if (num == 1) {
            return true;
        }
        int[] factors = {2, 3, 5};
        for (int i = 0; i < factors.length; i++) {
            while (num % factors[i] == 0) {
                num = num / factors[i];
            }
        }
        return num == 1;
    }

Ugly Number II

Write a program to find the n-th ugly number.

Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.

Note that 1 is typically treated as an ugly number.

https://leetcode.com/discuss/52716/o-n-java-solution

Solution:

O(n) time, one pass solution. O(n) space.

The idea of this solution is from this page:http://www.geeksforgeeks.org/ugly-numbers/

The ugly-number sequence is 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, … because every number can only be divided by 2, 3, 5, one way to look at the sequence is to split the sequence to three groups as below:

(1) 1×2, 2×2, 3×2, 4×2, 5×2, …
(2) 1×3, 2×3, 3×3, 4×3, 5×3, …
(3) 1×5, 2×5, 3×5, 4×5, 5×5, …

We can find that every subsequence is the ugly-sequence itself (1, 2, 3, 4, 5, …) multiply 2, 3, 5.

Then we use similar merge method as merge sort, to get every ugly number from the three subsequence.

Every step we choose the smallest one, and move one step after,including nums with same value.

    public int nthUglyNumber(int n) {
        int[] ugly = new int[n];
        ugly[0] = 1;
        int index2 = 0, index3 = 0, index5 = 0;
        int factor2 = 2, factor3 = 3, factor5 = 5;
        for (int i = 1; i < n; i++) {
            int min = Math.min(Math.min(factor2, factor3), factor5);
            ugly[i] = min;
            if (factor2 == min) {
                index2++;
                factor2 = 2 * ugly[index2];
            }
            if (factor3 == min) {
                index3++;
                factor3 = 3 * ugly[index3];
            }
            if (factor5 == min) {
                index5++;
                factor5 = 5 * ugly[index5];
            }
        }
        return ugly[n - 1];
    }

Union-Find 并查集

Union-Find is an algorithm which is widely used to solve finding disjoint components.

void init(int[] nums) {
    int[] fathers = new int[nums.length];
    //initially, father is itself
    for (int i = 0; i < nums; i++) {
        fathers[i] = i;
    }
}

//find the father for both nodes
//if they are in different groups, union them by setting fathers[xFather] = yFather;
void union(int x, int y, int[] fathers) {
    int xFather = findAndSet(x, fathers);
    int yFather = findAndSet(y, fathers);
    fathers[xFather] = yFather;
}

//recursively find its parent until reach the top which its parent is itself
//to improve the performance, we compress the path by setting the top father
//so that next time we call find, it is O(1) time complexity
int findAndSet(int x, int[] fathers) {
    int parent = fathers[x];
    while (parent != fathers[parent]) {
        parent = fathers[parent];
    }
    return parent;
}

Related Questions:

1. Graph Valid Tree Medium

Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.

For example:

Given n = 5 and edges = [[0, 1], [0, 2], [0, 3], [1, 4]], return true.

Given n = 5 and edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]], return false.

    Note: you can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0, 1] is the same as [1, 0] and thus will not appear together in edges.

    //union-find algorithm
    //findSet is a faster version of find method which finds the top father of a node
    //and then set the father to
    //which basically compress the path.
    //another improvement could be: only
    public boolean validTree(int n, int[][] edges) {
        int[] fathers = new int[n];
        for (int i = 0; i < n; i++) {
            fathers[i] = i;
        }
        for (int i = 0; i < edges.length; i++) {
            //union
            int xfather = findSet(edges[i][0], fathers);
            int yfather = findSet(edges[i][1], fathers);
            //since there is no duplicate edge(important! otherwise this algorithm is wrong)
            //so if xfather == yfather means they are in
            if (xfather == yfather) {
                return false;
            }
            fathers[xfather] = yfather;
        }
        //there should only be one node(root) pointing to itself
        int count = 0;
        for (int i = 0; i < fathers.length; i++) {
            if (fathers[i] == i) {
                count++;
            }
        }
        return count == 1;
    }
    
    public int findSet(int x, int[] fathers) {
        int parent = fathers[x];
        while (parent != fathers[parent]) {
            parent = fathers[parent];
        }
        fathers[x] = parent;
        return parent;
    }

    2. Find the Connected Component in the Undirected Graph

    public List<List<Integer>> connectedSet(ArrayList<UndirectedGraphNode> nodes) {
        //init unions
        Map<Integer, Integer> fathers = new HashMap<>();
        for (UndirectedGraphNode node : nodes) {
            int label = node.label;
            if (!fathers.containsKey(label)) {
                fathers.put(label, label);
            }
            for (UndirectedGraphNode neighbor : node.neighbors) {
                union(label, neighbor.label, fathers);
            }
        }
        //put it into a new map which key is the father of the component
        //value is a list of node within the component
        Map<Integer, List<Integer>> components = new HashMap<>();
        for (Integer label : fathers.keySet()) {
            int father = findAndSet(label, fathers);
            if (components.containsKey(father)) {
                components.get(father).add(label);
            } else {
                List<Integer> component = new ArrayList<>();
                component.add(label);
                components.put(father, component);
            }
        }
        //sort the result
        List<List<Integer>> res = new ArrayList<>();
        for (List<Integer> component : components.values()) {
            Collections.sort(component);
            res.add(component);
        }
        return res;
    }
    
    public void union(int x, int y, Map<Integer, Integer> fathers) {
        int xFather = findAndSet(x, fathers);
        int yFather = findAndSet(y, fathers);
        if (xFather != yFather) {
            fathers.put(xFather, yFather);
        }
    }
    
    public int findAndSet(int label, Map<Integer, Integer> fathers) {
        if (!fathers.containsKey(label)) {
            fathers.put(label, label);
            return label;
        }
        int parent = fathers.get(label);
        while (parent != fathers.get(parent)) {
            parent = fathers.get(parent);
        }
        fathers.put(label, parent);
        return parent;
    }

    3. Find the Weak Connected Component in the Directed Graph

    public List<List<Integer>> connectedSet2(ArrayList<DirectedGraphNode> nodes) {
        //init unions
        Map<Integer, Integer> fathers = new HashMap<>();
        for (DirectedGraphNode node : nodes) {
            int label = node.label;
            if (!fathers.containsKey(label)) {
                fathers.put(label, label);
            }
            for (DirectedGraphNode neighbor : node.neighbors) {
                union(label, neighbor.label, fathers);
            }
        }
        //put it into a new map which key is the father of the component
        //value is a list of node within the component
        Map<Integer, List<Integer>> components = new HashMap<>();
        for (Integer label : fathers.keySet()) {
            int father = findAndSet(label, fathers);
            if (components.containsKey(father)) {
                components.get(father).add(label);
            } else {
                List<Integer> component = new ArrayList<>();
                component.add(label);
                components.put(father, component);
            }
        }
        //sort the result
        List<List<Integer>> res = new ArrayList<>();
        for (List<Integer> component : components.values()) {
            Collections.sort(component);
            res.add(component);
        }
    
        return res;
    }
    
    public void union(int x, int y, Map<Integer, Integer> fathers) {
        int xFather = findAndSet(x, fathers);
        int yFather = findAndSet(y, fathers);
        if (xFather != yFather) {
            fathers.put(xFather, yFather);
        }
    }
    
    public int findAndSet(int label, Map<Integer, Integer> fathers) {
        if (!fathers.containsKey(label)) {
            fathers.put(label, label);
            return label;
        }
        int parent = fathers.get(label);
        while (parent != fathers.get(parent)) {
            parent = fathers.get(parent);
        }
        fathers.put(label, parent);
        return parent;
    }

    Partition Array

    1. with target outside the array

        public int partitionArray(int[] nums, int k) {
            if (nums == null || nums.length == 0) {
                return 0;
            }
            int left = 0;
            int right = nums.length - 1;
            while (left <= right) {
                while (left <= right && nums[left] < k) {
                    left++;
                }
                while (left <= right && nums[right] >= k) {
                    right--;
                }
                if (left < right) {
                    swap(nums, left, right);
                    left++;
                    right--;
                }
            }
            return left;
        }
    
        public void swap(int[] nums, int a, int b) {
            int tmp = nums[a];
            nums[a] = nums[b];
            nums[b] = tmp;
        }

    2. with target with in the array(Quick select)

    Dynamo: Amazon’s Highly Available Key-value Store 论文笔记

    Dynamo: Amazon’s Highly Available Key-value Store

    1. System Assumptions and Requirements in this case

      1. High write availability (this is based on their use cases like shopping carts, user should be able to update the shopping carts anytime). So the design is also writable and resolve conflicts when read.
      2. Query model is simple read and write operations to a data item which is uniquely identified by unique keys. No need for relational schemas. (Which is also based on the observation of some Amazon’s services.)
      3. ACID(Atomicity, Consistency, Isolation, Durability) are not strictly followed since it targets applications that tolerant weaker consistency, which is called eventually consistency.
    2. Design Considerations

      1. When to resolve update conflicts? Read or Write?
        1. Since it focus on high write availability, so it pushes conflict resolution to reads (which unlike many traditional DBs which execute conflict resolution during writes and has simple policy for reads)
      2. Who to resolve the conflicts? The data store or application?
        1. The application is responsible to resolve conflict updates. Since data store only has simple police like “last write wins” to resolve conflicts while application has more knowledge of each different situations and could have different strategy to resolve conflicts.
      3. Incremental scalability
        1. Add/Delete one node at a time without having a huge impact on both read/writes of the system.
      4. Symmetry
        1. No outstanding nodes. Each node should have the same responsibilities as its peers.
    3. Architecture

      Problem

      Technique

      Advantage

      Partitioning

      Consistent Hashing

      Incremental Scalability

      High Availability for writes

      Vector clocks with reconciliation during reads

      Version size is decoupled from update rates.

      Handling temporary failures

      Sloppy Quorum and hinted handoff

      Provides high availability and durability guarantee when some of the replicas are not available.

      Recovering from permanent failures

      Anti-entropy using Merkle trees

      Synchronizes divergent replicas in the background.

      Membership and failure detection

      Gossip-based membership protocol and failure detection.

      Preserves symmetry and avoids having a centralized registry for storing membership and node liveness information.

      sosp-figure2-small

      1. Partitioning (Consistent Hashing)
        1. Both node and key are mapped to the same hash space (eg. 00~FF)
        2. Key K is stored in B, which means B is responsible for K
        3. Pros:
          1. Load balance (each node would get roughly similar number of keys)
          2. Scalability (add/delete one nodes, only its neighbors would be affected)
      2. Replication  
        1. Dynamo is setup, N is assigned as a parameter indicating each data item is replicated on N nodes.
        2. Each key contains a list of nodes which is responsible for its read/write operation. Which is called Preference List. Length of the preference list should be larger than N just in case nodes failures.
        3. Using the consistent hashing, each node finds its coordinator, who is responsible to replicate the data to N-1 clockwise successor nodes.
      3. Versioningsosp-figure3-small
        1. Vector Clock is used to show if there are update conflicts. Mainly used in key-value storage which doesn’t have locks for writes to pursue better performance.
        2. D5([Sx, 3],[Sy, 1],[Sz,1]) means data item 5 which was updated by Sx 3 times, Sy 1 time, Sz 1time. Using the vector, it is easily to find out if two different version are parallel.
        3. When reads the data, the vector clock is also included in the data item.
        4. Deep understanding and examples, please check here
        5. Cons: Vector Clock some times could be too long if there are many different servers involved in writes. But in real cases it should not happen since writes are generally handled by top N nodes in the preference list of that key. Even if it happens, we can have a upper bound size of the vector clock and get rid of the old vectors depending on the timestamp, which might potentially cause problems when trying to resolve conflicts.
      4. Get() & Put() operation
        1. Only first N healthy nodes in the preference list are involved. (those are down and inaccessible are skipped)
        2. W + R > N (W/R: number of nodes which should success for writes/reads)
        3. When put(), the coordinator generates the vector clock with the new version and writes the new version locally. Then replicates the new version to first N reachable node in the preference list. Consider write successful as long as there is W-1 nodes respond.
        4. Similarly, for get(), the coordinates request the data from first N reachable nodes from the preference list and as long as there are R-1 response it will then returns all version of the data.
      5. Failure handling (Hinted Handoff)
        1. Check the Dynamo ring above, if node A is down, the data item which is supposed to written to A is now written to D (suppose N=3) along with the metadata (indicating which node it is supposed to be at) which is stored separately in D
        2. Once such hint is discovered, and A is recovered, D will send the replica to A and then delete the replica from itself.
        3. Hinted Handoff ensures read/writes won’t be rejected due to single node down or network failure.
      6. Recovering from permanent failures、Membership and failure detection待进一步整理。

    Reference: http://blog.ddup.us/2011/11/07/amazon-dynamo/