Jerry's Blog

Recording what I learned everyday

View on GitHub


27 July 2019

Angular (42) -- Subscribing the NgRx State

by Jerry Zhang

LeetCode Day 22: P167. Two Sum II - Input array is sorted (Easy)

题目:

给一个升序排列的数组,并且给一个目标数。找到数组中的两个数,使它们的和等于目标数。

我的思路:

遍历数组里的每一个数,用目标数减去这个数,去找是否存在这个差值。如果存在,退出循环,如果不存在,记录一下找到哪个位置了。 因为数组是升序排列的,下次找的时候,肯定从上一次失败的那个点往前找。找到的就不用再找了。

我的代码:

public class E_167_TwoSumII_InputArrayIsSorted {
    public static int[] twoSum(int[] numbers, int target) {
        int position = numbers.length - 1;
        int[] result = new int[2];
        for (int i = 0; i < numbers.length; i++) {
            while (target - numbers[i] < numbers[position] && position > i) {
                position--;
            }
            if (target - numbers[i] == numbers[position]) {
                result[0] = i + 1;
                result[1] = position + 1;
                return result;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] test = {2, 7, 11, 15};
        int[] ints = twoSum(test, 22);
        System.out.println(Arrays.toString(ints));
    }
}

最优解:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0, right = numbers.length - 1;
        while(left < right) {
            int sum = numbers[left] + numbers[right];
            if(sum == target) {
                return new int[] { left + 1, right + 1 };
            }
            
            if(sum > target) {
                right--;
            } else {
                left++;
            }
        }
        
        return new int[0];
    }
}

设了两个指针指向数组的头尾。分别叫left, right。只要左小于右,就检查一下它们的和是否是目标数,如果是就直接返回结果。 如果不是,那看它们的和是大于了目标值还是小于目标值。如果大于目标值,右指针向左移一格。如果小于目标值, 左指针向右移动一格。

这个很厉害,从两侧推进,一步一步调整到结果的值。因为结果是唯一的,所以从两边推进是必然能找到结果的。至于推进哪一边,完全 由现在的两个数的和,跟目标值之间的大小关系确定。

不过其实我的思路也差不多,只是没有答案这么优雅。

Angular:

Fetching Recipe Detail Data

Again, inject the store first. In recipe-detail file:

constructor(
    private route: ActivatedRoute,
    private router: Router,
    private store: Store<fromApp.AppState>
  ) { }

This time, we have a problem. Getting route params already done with an observable, so we use a pipe chain to convert the observable.

ngOnInit() {
    this.route.params.pipe(
      map(params => {
        return +params['id'];
      }),
      switchMap(id => {
        this.id = id;
        return this.store.select('recipes');
      }),
      map(recipesState => {
        return recipesState.recipes.find((recipe, index) => {
          return index === this.id;
        });
      }))
      .subscribe(recipe => {
        this.recipe = recipe;
      });
  }

In Recipe Edit Component

constructor(
    private route: ActivatedRoute,
    private router: Router,
    private store: Store<fromApp.AppState>
  ) { }

if (this.editMode) {
      // const recipe = this.recipeService.getRecipe(this.id);
      this.storeSub = this.store
        .select('recipes')
        .pipe(
          map(recipeState => {
            return recipeState.recipes.find((recipe, index) => {
              return index === this.id;
            });
          })
        )
        .subscribe(recipe => {
          recipeName = recipe.name;
          recipeImagePath = recipe.imagePath;
          recipeDescription = recipe.description;
          if (recipe.ingredients) {
            for (const ingredient of recipe.ingredients) {
              recipeIngredients.push(
                new FormGroup({
                  'name': new FormControl(ingredient.name, Validators.required),
                  'amount': new FormControl(ingredient.amount, [
                    Validators.required,
                    Validators.pattern(/^[1-9]+[0-9]*$/)
                  ])
                })
              );
            }
          }
        });
    }

Using NgRx in the Resolver:

If we refresh our app, the data will be lost. So we need to fetch data in the resolver. To fetch recipes, we need to write a new effect:

Recipe Effect:

@Effect()
  fetchRecipes = this.actions$.pipe(
    ofType(RecipesActions.FETCH_RECIPES),
    switchMap(() => {
      return this.http.get<Recipe[]>(
        'https://recipebook-cc2b1.firebaseio.com/recipes.json'
      );
    }),
    map(recipes => {
      return recipes.map(recipe => {
        return {
          ...recipe,
          ingredients: recipe.ingredients ? recipe.ingredients : []
        };
      });
    }),
    map(recipes => {
      return new RecipesActions.SetRecipes(recipes);
    })
  );

Register the effect in the app.module

EffectsModule.forRoot([AuthEffects, RecipeEffects]),

Dispatch the Fetch data Action

In the header component:

onFetchData() {
    // this.dataStorageService.fetchRecipes().subscribe();
    this.store.dispatch(new RecipeActions.FetchRecipes());
  }

In the resolver, we also call fetchRecipes, but the resolver expect an observable and it waits for the observable to complete before it loads the route. Dispatching the fetchRecipes action does not give us an observable. We could use a tricky way. We can use the Actions and ofType from @ngrx/effects to listen to the action SET_RECIPES. Once it is done, the resolver will work.

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
    Observable<Recipe[]> | Promise<Recipe[]> | Recipe[] {
    return this.store.select('recipes').pipe(
      take(1),
      map(recipesState => {
        return recipesState.recipes;
      }),
      switchMap(recipes => {
        if (recipes.length === 0) {
          this.store.dispatch(new RecipesActions.FetchRecipes());
          return this.actions$.pipe(
            ofType(RecipesActions.SET_RECIPES),
            take(1)
          );
        } else {
          return of(recipes);
        }
      })
    );
  }

In auth.actions.ts file, add a new attribute as a payload, redirect. Then in the auth effect, we only redirect when this attribute is true.

@Effect({dispatch: false})
  authRedirect = this.actions$.pipe(
    ofType(AuthActions.AUTHENTICATE_SUCCESS),
    tap((authSuccessAction: AuthActions.AuthenticateSuccess) => {
      if (authSuccessAction.payload.redirect) {
        this.router.navigate(['/']);
      }
    })
  );
tags: Angular