17 July 2019
Angular (32) -- NgRx
by Jerry Zhang
LeetCode Day 12: P118. Pascal’s Triangle (Easy)
题目
给一个正整数n,生成一个n层的杨辉三角数组。
Example:
Input: 5
Output:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
我的思路:
一开始想一层一层算,后来想如果直接用二项式系数的公式算会不会更快。所以就用组合数公式计算了每个位置的值,然后填到每层数组中。
然而事实证明,因为每一层都要算,用公式还不如一层一层直接求和呢。。。
如果是只求第n层,那无疑我的算法会快很多,因为一层一层算,时间复杂度是n^2。直接用公式是n
我的代码:
public class E_118_PascalsTriangle {
public static List<List<Integer>> generate(int numRows) {
ArrayList<List<Integer>> outerList = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
ArrayList<Integer> innerList = new ArrayList<>();
for (int j = 0; j < i + 1; j++) {
long element = combinationNumber(i, j);
innerList.add((int) element);
}
outerList.add(innerList);
}
return outerList;
}
private static long combinationNumber(int n, int m) {
if (m < n / 2 + 1) {
long temp = 1;
for (long i = n; i > n - m; i--) {
temp = temp * i;
}
for (long i = 1; i < m + 1; i++) {
temp = temp / i;
}
return temp;
} else {
long temp = 1;
for (long i = n; i > m; i--) {
temp = temp * i;
}
for (long i = 1; i < n - m + 1; i++) {
temp = temp / i;
}
return temp;
}
}
public static void main(String[] args) {
generate(25);
}
}
坑:
一开始因为用了阶乘的公式来计算C(n, m)组合数,导致当n = 25时就超过整数的范围了。
于是费了很大的劲自己写了一个计算组合数的方法。Debug很多次才正确。
最优解:
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> A = new LinkedList<List<Integer>>();
if (numRows <= 0)
return A;
LinkedList<Integer> temp = new LinkedList<Integer>();
temp.add(1);
A.add(temp);
pro(A, 1, numRows);
return A;
}
private void pro(List<List<Integer>> A, int i, int L){
if (i >= L)
return;
ArrayList<Integer> toAdd = new ArrayList<Integer>();
toAdd.add(1);
List<Integer> prev = A.get(A.size()-1);
for (int j = 1; j < prev.size(); j++){
toAdd.add(prev.get(j-1)+prev.get(j));
}
toAdd.add(1);
A.add(toAdd);
pro(A, i+1, L);
}
}
Angular
Install NgRx
npm install --save @ngrx/store
Then, the most important things are Store
, Reducers
, and Actions
.
Store
and Reducer
are tightly coupled together.
Create a reducer
Create a new file called shopping-list.reducer.ts
in the shopping-list component.
import {Ingredient} from '../../shared/ingredient.model';
import * as ShoppingListActions from './shopping-list.actions';
const initialState = {
ingredients: [
new Ingredient('Apples', 5),
new Ingredient('Tomatoes', 10)
]
};
export function shoppingListReducer(state = initialState,
action: ShoppingListActions.AddIngredient) {
switch (action.type) {
case ShoppingListActions.ADD_INGREDIENT:
return {
...state,
ingredients: [...state.ingredients, action.payload]
};
default:
return state;
}
}
A Reducer takes two arguments, the state of that app and an action to be executed on this state. We can use a switch
statement to check which action was passed in an run different code based on that. In the switch condition, we have to
check the type
of this action. Action
is imported from @ngrx/store
. As a developer, we can define which action
we have in this application. It is simply a string. Here, we outsourced the string in anther action
file and refer
it as ShoppingListAction.ADD_INGREDIENT
.
In each case, we have to return a new state.
Warning: Changing the state directly is totally wrong!
If we push a new ingredient like this in the method:
case ShoppingListActions.ADD_INGREDIENT:
return {
state.ingredients.push();
return;
};
This is totally wrong, because state changes with NgRx always have to be immutable, which means you must not edit the existing or the previous state. Instead, return a new object which will replace the old state. To not lose all the old data, copy the old state with the spread operator. Like this:
return {
...state,
ingredients: [...state.ingredients, action.payload]
};
...state
pulls out all the properties of the old state, and adds these properties to this new object.
Here, because all we have in the state is just an array of ingredient, called ingredients
, we actually are copying
the ingredients object first, then overwrite it.
In other apps, we may have many states. So always copy the old state then overwrite what you want to change.
Create an Action
Create a new file called shopping-list.actions.ts
in the shopping-list folder.
import { Action } from '@ngrx/store';
import {Ingredient} from '../../shared/ingredient.model';
export const ADD_INGREDIENT = 'ADD_INGREDIENT';
export class AddIngredient implements Action {
readonly type: string = ADD_INGREDIENT;
constructor(public payload: Ingredient) {}
}
In this file, we export a constant firstly to avoid typo. Secondly, we created a class AddIngredient
and implements
Action
from @ngrx/store
.
Use the actions
Because we export more than one thing in the actions file, we can use an alias to group all the things from this actions file into one object when we use them in the reducer.
import * as ShoppingListActions from './shopping-list.actions';
And now the case becomes ShoppingListActions.ADD_INGREDIENT
. And also, we can use action.payload to refer to the
ingredient we want to add.
Use the reducer and actions
To use everything we set up, we firstly need to import the StoreModule
in the app.module.ts
. We also need to tell
NgRx what makes up our store, which reducers are involved. So we call forRoot
and we pass in an action-reducer
map.
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
StoreModule.forRoot({shoppingList: shoppingListReducer}),
SharedModule,
CoreModule
],
Here, the name shoppingList
is defined by us.
Use the store
To use the StoreModule we just imported, inject the store in the ShoppingListComponent.
constructor(private slService: ShoppingListService,
private loggingService: LoggingService,
private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { }
Store is a generic type. The type is the structure we set up in the app module.
The key must be the key we set up in the app module, shoppingList. The type of the data stored in the shoppingList is what the reducer function returns.
Before we get ingredients from the shopping list service. Now, we want to get the ingredients from the store.
ingredients: Observable<{ingredients: Ingredient[]}> ;
ngOnInit() {
this.ingredients = this.store.select('shoppingList');
// this.ingredients = this.slService.getIngredients();
// this.igChangeSub = this.slService.ingredientsChanged
// .subscribe(
// (ingredients: Ingredient[]) => {
// this.ingredients = ingredients;
// }
// );
this.loggingService.printLog('Hello from shoppingList component.');
}
We changed the ingredients
to an observable, so we also need to modify our template.
*ngFor="let ingredient of (ingredients | async).ingredients; let i = index"
If you don’t want to use the pipe, you can also subscribe this observable in the component.
this.store.select('shoppingList').subscribe();
Dispatching the actions.
The shopping edit component is the place where we change the ingredients that are displayed.
We need to inject the store in the constructor.
constructor(private slService: ShoppingListService,
private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { }
When we submit the new ingredient that we added in the onSubmit
method, instead of calling addIngredient
method
from the service, we use the dispatch()
method from the store and pass in a new object based on that action class.
this.store.dispatch(new ShoppingListActions.AddIngredient(newIngredient));
总结 In Conclusion:
-
Action里面包着一个type和一个payload。用户调用这个action方法。
-
Reducer是一个函数,需要传进去一个state和一个action。state是从总仓库里面拿到的。Action是用户提出的。Reducer 复制老的state, 修改之后,返回一个新的state。