lamechang-dev

Webフロントエンドエンジニア lamechangのブログ。

【ChatGPT】【GraphQL】ChatGPT-4に教えてもらいながらポートフォリオにGraphQLを爆速導入してページ追加してみた【20分で完了】

はじめに

この記事では、ChatGPT-4を利用した開発がどれだけの生産性向上に寄与するのか、というのをざっくり試したものになります。 今回は、あまり自分が詳しくないGraphQLで試してみました。

やってみたこと

試したことは以下の通りです。前提として私は業務上でのGraphQLの利用経験は一度もありませんし、そもそもGraphQLを個人開発などでも利用したことがありません。 また、一応事前にPlaygroundを通してGraphQLのクエリの書き方などは簡単に学びました(その際も疑問点はほぼ全てChatGPT-4に聞いた・・)


最終的に出来上がったページが以下です。ちなみにコンテンツ以外の部分は元々存在していたコンポーネントを再利用しています。 なお実装に要した時間は20分ほどです。


追加したページ
追加したページ

https://lamechang-dev.vercel.app/playground/pokemon_graphql

ざっくり振り返り

仮に1から自分が取り組むのであれば様々ググりながら進めていくとして、見積もりに1時間半-2時間ほどを設定すると思います。

が、今回 20分ほど で完了してしまいました。はっきり言って強力すぎますね。。。 しかもChatGPT-4って回答に周辺ライブラリの利用背景とかも完結に説明してくれていて非常に分かりやすいんですよね。開発体験がとても良かったです。とんでもないものが誕生してしまった気がします。

【React】【Recoil】ポートフォリオ作成でRecoilを安全かつ綺麗に使おうとしたのでその構成を紹介する

はじめに

昨日、ポートフォリオサイトをvercelでデプロイ・公開しました。

lamechang-dev.vercel.app

レポジトリも公開しています。

github.com

公開の背景としては、以下のようなところが理由になります。

  • 技術的な検証ができるなんでもできるレポジトリが欲しかった
  • せっかくだから公開できるようなものにしたかった

といったところになります🤢

また今回、実装に入る前にアーキテクチャを図に起こしつつで着手しながら進めております。 その時の図を元にどう言った意図があって今の構成にしているかについて、今回はglobalStateにフォーカスして整理してみたいと思います。

レポジトリでのRecoilの運用ルール

  • 1つの単位(ドメイン・UIの状態など)に対して index.tsactions.tsselector.tsをそれぞれ用意する
  • recoilで定義したstatesetStateそのものを直接露出させず、write hookactions.tsread hookselector.tsの中に定義してそれらのカスタムフックのみを利用できるようにする(できる限りライブラリの知識・ルールを隠蔽する)
  • state の定義自体は index.tsの中に集約する
  • stateのkeyは必ず一元管理
  • ドメイン関連のglobalStateは domain/ の下に、 ui関連のglobalStateは ui/ の下に置いていく

実際のコードを以下のサンプルコードで明示していきます。今回実装したものの中で movie と言うドメインがあるので、そちらの構成を見ていきます。

サンプルコード

src/context/model/movies/index.ts

import { atom, selector } from "recoil";
import { GLOBAL_STATE_KEYS } from "src/context/constants";
import { MovieList, Movie } from "../../../domain/movies/model";
import { stateSelectedGenreIds } from "../genres";

export const stateMyFavoriteMovieList = atom<MovieList>({
  key: GLOBAL_STATE_KEYS.DOMAIN.MOVIE.MY_FAVORITE_MOVIE_LIST,
  default: [],
});

export const stateSelectedMovie = atom<Movie | undefined>({
  key: GLOBAL_STATE_KEYS.DOMAIN.MOVIE.SELECTED_MOVIE,
  default: undefined,
});

export const stateSelectedMovieList = selector<MovieList>({
  key: GLOBAL_STATE_KEYS.DOMAIN.MOVIE.SEELCTED_MOVIE_LIST,
  get: ({ get }) => {
    return get(stateMyFavoriteMovieList).filter((movie) => {
      return get(stateSelectedGenreIds).some((id) => {
        return movie.genres?.map((genre) => genre.id).includes(id);
      });
    });
  },
});

ここには純粋な atomselectorの箱だけを置いておくイメージです。特筆する点は特にないとは思います。

src/context/model/movies/selector.ts

import {
  stateMyFavoriteMovieList,
  stateSelectedMovie,
  stateSelectedMovieList,
} from ".";
import { useGlobalValue } from "../../hooks";

export const useStateMyFavoriteMovieList = () => {
  return useGlobalValue(stateMyFavoriteMovieList);
};

export const useStateSelectedMovie = () => {
  return useGlobalValue(stateSelectedMovie);
};

export const useStateSelectedMovieList = () => {
  return useGlobalValue(stateSelectedMovieList);
};

こちらが、globalStateの read hookになっており、単純にatom・selectorを参照し返すものになります。各々のhookは先ほどの index.ts で定義したstateと1対1対応となっており、これらのhooksを view層usecase層 で呼ぶイメージです。

実際の参照する側の記述では

const myFavoriteMovieList = useStateMyFavoriteMovieList();

と言った具合に、recoilの仕様などを意識せずに直感的にhookを通してglobalStateを参照できるようになっています。

ちなみに、useGlobalValue と言うものが登場してますが、こちらは useRecoilValue を単純に代入した関数になっています。recoilに対する直接的な依存を避けるために、間に挟んでいるイメージです。以下のようなhookを用意して利用しています。

■ src/context/hooks/index.ts

import {
  useRecoilCallback,
  useRecoilState,
  useRecoilValue,
  useRecoilValueLoadable,
  useSetRecoilState,
} from "recoil";

export const useGlobalValue = useRecoilValue;

export const useSetGlobalState = useSetRecoilState;

export const useGlobalState = useRecoilState;

export const useGlobalValueLoadable = useRecoilValueLoadable;

export const useGlobalCallback = useRecoilCallback;

src/context/model/movies/actions.ts

import { useRecoilCallback } from "recoil";
import { Movie, MovieList } from "src/domain/movies/model";
import { stateMyFavoriteMovieList, stateSelectedMovie } from ".";

export const useStateMyFavoriteMovieListActions = () => {
  const setStateMyFavoriteMovieList = useRecoilCallback(
    ({ set }) =>
      (movieList: MovieList) => {
        set(stateMyFavoriteMovieList, () => movieList);
      }
  );

  return { setStateMyFavoriteMovieList };
};

export const useStateSelectedMovieActions = () => {
  const setStateSelectedMovie = useRecoilCallback(
    ({ set }) =>
      (movie: Movie) => {
        set(stateSelectedMovie, () => movie);
      }
  );

  const resetStateSelectedMovie = useRecoilCallback(({ set }) => () => {
    set(stateSelectedMovie, () => undefined);
  });

  return { setStateSelectedMovie, resetStateSelectedMovie };
};

こちらが actions.ts の定義になります。いわゆる write hook の置き場所になっており、RecoilのAPIを直接露出させずに更新処理を行えることを狙っています。 また、globalStateに対するwrite処理をこのようにカスタムフックに集約することで、発生しうるwrite処理が網羅的にここで確認できるようになることも大きなメリットであり、テストを書く組織ではテストカバレッジの向上に貢献すると思います。

こういったAPIの隠蔽は、今後Recoil以外のよりナイスな状態管理ライブラリが誕生した際に、そのリプレースの影響範囲を限定できますし、アプリケーションに対するリスクをグッと抑えられるのではないかなと思います。

実際の呼び出し側のコードサンプルも置いておきます。

const { resetStateSelectedMovie } =
    useStateSelectedMovieActions();

const handleClickCloseIconButton = useCallback(() => {
    resetStateSelectedMovie();
  }, [resetStateSelectedMovie]);

その他補足

ドメイン関連のglobalStateは domain/ の下に、 ui関連のglobalStateは ui/ の下に置いていくと言う規約についてですが、これは単純にグルーピングできるものはしていこう、と言う発想からこのようにしました👼🏻が、今後ポートフォリオの機能を増やしていくうちにこの構成が扱いづらいとなったら他の構成に変えていくかもしれませんし、そもそも現段階で domainui の2つのグルーピングが最適解だとは全然思えてないので、作っていきながら調節したいです。

あと、このレポジトリは前述した通り色々なことを実験していくために用意したもので、例えば上述した Recoil以外のよりナイスな状態管理ライブラリが誕生したというシチュエーションも実際に試運転できたらなぁと思ったりしてます。 つまり、この後 Jotai に状態管理を載せ替えてみる、とかも試してみたいなぁなんて思ってたりしますね。Jotaiの方がRecoilより優れている、と言いたい訳ではないです。

jotai.org

【ESlint】フロントエンドアーキテクチャ上で決めた依存ルールをESlintで制約として表現する

この記事は Wano Group Advent Calendar 2022の23日目の記事となります。

はじめに

改めてですが、私は音楽ディストリビューションサービス「TuneCore Japan」のフロントエンドエンジニアを担当しております。みなさん、フロントエンド開発楽しんでますか。私は相変わらず楽しくやっております🥹

さて、プロダクトTuneCore Japanは クリーンアーキテクチャ を参考にしたアーキテクチャを採用しております(ここについての詳細は割愛)。新規機能の実装などはその構成によせ、また適宜既存箇所の修正をする場合はボーイスカウトルールに乗っ取ってなるべく新しい構成に寄せられるように頑張っております。

各レイヤーの責務・依存関係をチームで擦り合わせた上で実装を進めることができており、バグ発生時の影響範囲の特定がしやすくなったことや、レイヤーごとの責務が明確になったことからコード品質が改善し、スムーズに実装に取り組めるようになったと思います。 

そして、当たり前のことですが実際の実装フェーズにおいては、レビュー担当・実装者は「実装がアーキテクチャに沿っているか」がレビュー観点に含まれることになります。もちろん、各メンバーがアーキテクチャを理解した上で日々の実装に取り組んでいるので大きく構成から外れるようなことはないですが、もちろん人間ですのでミスはします。例えば、APIの型定義をそのままアプリケーション固有のロジック・定義が存在する層で参照してしまうことなど、例を挙げればキリがありません。

ということで、どうにかしてそういった問題を自動検知できないか、と調べていた時にimportプラグインに定義されている no-restricted-paths を見つけました。今回は、その紹介をしようと思います。

import/no-restricted-pathsの設定

no-restricted-paths の実際の記述例を以下に紹介します。

"import/no-restricted-paths": [
      "warn", // or error
      {
        zones: [
          {
            from: "./src/domain/**/*",
            target: "./src/components/!(organisms)/**/*",
            message:
              "The organisms components can only depend on the domain layer",
          },
          {
            from: "./node_modules/openapi",
            target: "./src/components/**/*",
            message:
              "Components layer can not depend on OpenAPI type definition ",
          },
        ],
      },
    ],

まず1つ目の例について。例えば、Atomic Designを採用しているプロダクトにおいては、しばしばcomponents層においてドメインに依存する箇所を制限させたい、というようなルールを取り入れているかもしれません。そのような場合、上記のように記述することで、 organisms以外のコンポーネントにおいて src/domain/配下からのimportを行った場合にアラートを出すことができます。

そして2つ目の例は、node_modules下にレポジトリの依存パッケージとして存在するOpenAPI上の型定義の参照を、components層で許さないことを表現したものになります。 このように、複数個のルールを定義することが可能であり、チームで取り決めた構成に沿った依存ルールを柔軟に表現できることがわかるのではないでしょうか?

(補足)no-restricted-importsについて

no-restricted-imports は上で紹介したルールと似たような名前ですが、こちらは文字通り指定したモジュールのimportを禁止することが可能です。実際のFEプロジェクトでのユースケースとしては、サイズの大きいライブラリ(例えばlodash)の静的なimportを禁止することや、プロダクトとして非推奨となったライブラリの利用を制限させることなどができそうです。こちらもチーム内で取り決めたルールを表現する手段としては非常に有効だと思います。

"@typescript-eslint/no-restricted-imports": [
      "warn",
      {
        paths: [
          {
            name: "lodash",
           "message": "lodash is deprecated, please use 'just'",
          },
        ],
      },
    ],

最後に

チームで取り決めたルール・運用をESlint上に表現し自動検知できるようにすることで、開発をする上での実装者・レビュワーの工数・負担を軽減させることは間違いありません。弊社プロダクト TuneCore Japan もお陰様でフロントエンド開発に携わってくれるメンバーが少しずつ増えてきており、こういった改善策の恩恵をより受けることができているのではないかな、と思います。

また、TuneCore Japan では 一緒に働くメンバーを募集しています。

エンジニアだけでなく、Director等の各種ポジションをオープンしているので興味がわいた方はぜひご覧ください!

www.tunecore.co.jp

【React】ちゃんと知って使うReact.memo

この記事は Wano Group Advent Calendar 2022の10日目の記事となります。

はじめに

改めてですが、私は音楽ディストリビューションサービス「TuneCore Japan」のフロントエンドエンジニアを担当しております。そしてこの2022年も1年通してReactをゴリゴリ書き、コードレビューも何度もしてきました。TailWind CSSだけをひたすら書いてた時期もちょっとありましたが

そんな中、自分の中のもやもやとして上がっていたのがReact.memoによるパフォーマンス改善をやるべきコンポーネントとやるべきでないコンポーネントの明確な定義ができていないというところでした。この点にフォーカスして今回は React.memoをちゃんと知り、正しく使えるようにするために自分が調べたこと を整理したいと思います。

React.memoに関する公式での説明

以下、Facebookが提供している公式Docから引用します。

React.memo は高階コンポーネントです。

もしあるコンポーネントが同じ props を与えられたときに同じ結果をレンダーするなら、結果を記憶してパフォーマンスを向上させるためにそれを React.memo でラップすることができます。つまり、React はコンポーネントのレンダーをスキップし、最後のレンダー結果を再利用します。

React.memo は props の変更のみをチェックします。React.memo でラップしているあなたのコンポーネントがその実装内で useState、useReducer や useContext フックを使っている場合、state やコンテクストの変化に応じた再レンダーは発生します。

デフォルトでは props オブジェクト内の複雑なオブジェクトは浅い比較のみが行われます。比較を制御したい場合は 2 番目の引数でカスタム比較関数を指定できます。

これはパフォーマンス最適化のためだけの方法です。バグを引き起こす可能性があるため、レンダーを「抑止する」ために使用しないでください。

自分なりの言葉で上記の説明を補足すると

  • React.memoは、propsオブジェクト内の値に対して、 Object.isを通した値の比較を適用して、仮にtrutyだった場合はレンダーフェーズにおける仮想DOMの差異チェックをスキップする

  • React.memo でラップしているコンポーネント内部のstateの変更に対しては、従来通り再レンダーが走る

といった感じでしょうか。ここで注意したいことは、React.memoコンポーネントをラップした場合、追加で Object.is によるprops同士の浅い比較がレンダリングごとの計算処理として追加されていることになります。

なので、仮に各レンダーフェーズにおいて毎回Propsの変更が行われるコンポーネントがあった場合は、React.memoでラップしても毎回再レンダリングされた上、追加でObject.is によるprops同士の浅い比較が行われるため結局総合的な計算量は増え、パフォーマンスに悪影響を及ぼすことになります。このことからも、慎重に利用するべきライブラリであることは間違いありません。

ではどういった時にReact.memoを使えば良いのか

上記のような仮説を自分なりに持って調査をしている時に出会った記事が

Use React.memo() wisely

になります。記事の第二章「2. When to use React.memo」が今回の疑問の解決に役立ちました。

詳細は記事を参照していただきたいですが、基本的な方針は 同じPropsで再レンダリングされる頻度が高い場合に React.memo を適用する、というところです。

筆者が記事の中で紹介している コンポーネント MovieMovieViewsRealtime のケースが理解しやすいと思います。

MovieViewsRealtimeコンポーネントはリアルタイムに情報が変わる頻度が高い箇所とstaticな情報をもつ箇所(titlereleaseDate)にDOM要素が分かれており

staticな情報をもつ箇所をMovieコンポーネントとして切り出して React.memoでラップすることで無駄な再レンダリングを防ぐ、と言うアプローチです。

このように、親コンポーネントの再レンダリングにつられて不必要な再レンダリングが子コンポーネントで発生するようなケースは多くの現場でのReactを通したUI開発でも肌感でよく起きている印象なので、このような場面においてはReact.memoが有効だと思いました。

また、筆者が「3. When to avoid React.memo」において指摘しているように、React.memoが悪影響を及ぼすパターンも多くあります。個人的にありがちだと思ったのが、コールバックのメモ化を怠った結果、常にReact.memoによるObject.is の値がfalseを返し意図しない再レンダリングが毎回行われてしまっていたと言うケースです。

さいごに

Reactの文脈においては、レンダリングの回数が減ったことが、そのままパフォーマンス向上につながったと考えられる傾向にあると予想しますが、コンポーネントによっては、React.memoによるpropsの比較が悪影響を及ぼし結果としてパフォーマンスに問題を起こすことは、今回の調査によって注意しなければならない、と改めて思いました。

Reactの公式サイトにも書かれていますが、React.memoに上記のようなリスクがあることは明らかなので、やはりレンダリング頻度や計算時間などを確認していくことは間違いなく必要になってくると思います。自分もProfilingを運用上で適切に活用して、より高品質なUI開発ができるように気をつけて行きたいところです。

【アルゴリズム】【動的計画法】ナップザック問題のアルゴリズム実装

動的計画法とは

アルゴリズム分類の1つ。与えられた問題全体を一連の部分問題として分解した上で、それぞれの部分問題に対する解をメモ化しながら、小さな部分問題から大きな部分問題へと順に計算 => 解を求めていく手法のことを指します。動的計画法を適用できるようなケースは非常に多く、解法パタンの中には特殊なテクニックを要するものも存在します。

動的計画法を用いることで解を用いることができる問題の代表例は以下の通りです。
- ナップザック問題
- スケジューリング問題
- 発電計画問題

今回は、動的計画法の理解をする上で典型パターンといてよく用いられるナップザック問題のアルゴリズム実装をします。

ナップザック問題とは

ナップサックの中にいくつかの品物を詰め込み入れた品物の総価値を最大にするという問題を指します。より一般化して、以下のような命題を考えます。


 N個の品物があり, i(=0,1,...,N-1)番目の品物の重さは weight_i,価値は value_iで与えられます。 この N個の品物から,重さの総和が Wを超えないように,いくつか選びます。 選んだ品物の価値の総和として考えられる最大値を求めてください(ただし, W weight_iは0以上の整数とします).


動的計画法における部分問題の考え方は、以下のことを意識しながら部分問題を構成することで、部分問題同士の遷移を考察し、数式に落とし込むことで解法を導くことができます。

部分問題の作り方の基本パターンは
N個の対象物{0,1,...,N-1}に関する問題に対して,最初のi個の対象物{0,1,...,i-1}に関する問題を部分問題として扱い、一般化する
ということになります。

なので、この基本パターンを上記の命題にも適用してみるとしましょう。

部分問題の適用

以下のような部分問題を考えることができます。

ans[i] = 最初のi個の品物{0,1,...,i-1}までの中から重さがWを超えないように選んだときの,価値の総和の最大値

こうした場合の部分問題同士の遷移を考えてみます。

ans[i] から ans[i+1] に遷移する場合、 i番目の品物を選択するか否かの2通りが考えられます。しかし命題をちゃんと確認してみると重さの総和が Wを超えないようにという条件がありますが、こちらの条件を満たすか否かの判断が上記の部分問題だけでは分かりません。よって、部分問題の ans[i] を決定づける要素に、重さwを付け加えてやることにします。

ans[i][w] = 最初のi個の品物{0,1,...,i-1}までの中から重さがwを超えないように選んだときの,価値の総和の最大値

こうすることで、選択肢のグループを決定づける変数にwが加わりました。この状態で、グループごとの遷移を表現できるか考えてみます。以下において、dp[i][w](w=0,1,...,W)の値が求まっている状態を仮定した上で、dp[i+1][w](w=0,1,...,W)を求めていくことを考えてみます。

i番目の品物を選ぶとき

選んだ後の価値の総和ans[i+1][w]に対し、選ぶ前の価値の総和は ans[i][w - weight[i]]となります。 また、その選ぶ前の状態の価値の総和から value[i+1]が加わることを考えると、

ans[i+1][w]ans[i][w - weight[i]] + value[i] と表現することができることがわかります。よって、ans[i+1][w]ans[i][w - weight[i]] + value[i] の間の最大値を求めればいいことがわかります。

ただし、w - weight[i]>=0 の場合のみ適用できることに注意します。

i番目の品物を選ばないとき

選ばなかった後の価値の総和をans[i+1][w]に対し、選ばなかった前は重さが変わるはずがないので価値の総和はans[i][w]です。よって、ans[i+1][w]ans[i][w] の間の最大値を求めてあげればいいことがわかります。

具体例で遷移を確認

ここで、この遷移をより具体的に考えてみます。このような状況を考えてみましょう。品物が4個で、(weight,value)={(2,3),(1,2),(3,6),(2,1)} 実際のans[i][w]を以下のマス目状の図に埋めていきます。

先ほど紹介した遷移を利用していきこの表を左上から埋めていくことを考えた時に、赤マスで表現されるans[3][3] の値の算出は以下のように行われます。

  1. 2番目の品物を選んだ場合は、ans[2][3 - weight[2]] + value[2] = ans[2][2] + 2 = 3 + 2 = 5

  2. 2番目の品物を選ばない場合は、ans[2][3] = 3

と計算されます。

仮に、それぞれのマスがまず0で初期化されていると前提を置くとなると、この2パターンによって算出される値の最大値がans[3][3]の最終解になっていることがイメージできるのではないでしょうか。

また、アルゴリズムの計算量自体は、それぞれのグループにおける計算量が O(1)であり、グループの個数は  NWだけ存在しますので、 全体で O(NW)で表現されます。

C++ でのアルゴリズム実装

以下のようになります。

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

template<class T> void chooseMax(T& a, T b) {
  if (a < b) {
    a = b;
  }
}

int main() {

  int N; long long W;
  cin >> N >> W;
  vector<long long> weight(N), value(N);
  for (int i = 0; i < N; i++)
  {
    cin >> weight[i] >> value[i];
  }

  // アンサーテーブル定義
  vector<vector<long long> > ans(N + 1, vector<long long>(W + 1, 0));

  for (int i = 0; i < N; i ++) {
    for (int w = 0; w <= W; w ++) {
      if (w - weight[i] >= 0) {
        chooseMax(ans[i + 1][w], ans[i][w - weight[i]] + value[i]);
      }

      chooseMax(ans[i + 1][w], ans[i][w]);
    }
  }

  cout << ans[N][W] << endl;
}

【アルゴリズム】トリボナッチ数列のアルゴリズム

トリボナッチ数列とは

- [tex: T{0} = 0] - [tex: T{1} = 0] - [tex: T{2} = 1] - T{N} = T{N-1} + T{N-2} + T_{N-3} (N = 3, 4...)

によって定義される数列のことであり、[0,0,1,1,2,4,7,13,24,44...] と値が続いていく数列です。フィボナッチ数列が前2つの数字を足した数列であるのに対して、トリボナッチ数列は前3つの数字を足した数列になります。以下で、フィボナッチ数列の第 N T_{N} を計算するアルゴリズムを考えます。

上記に記載した通り、既にシンプルな漸化式が定義されているのでそれを用いて再帰関数を用いてまずアルゴリズムを表現してみます。

アルゴリズム実装(1) メモ化再帰  O(N)

メモ化を利用しない単純な再帰関数の呼び出しでは、計算量は指数的に増大してしまいます(計算量の導出は省略)。なので、同じ引数によって実行されたtribonacci()の計算結果をmemo という配列に格納するように工夫し、仮に呼び出された再帰関数の引数に対応したmemoの値が存在したら単純にそれを返すようにします。

vector<long long> memo;

long long tribonacci(int i) {
  if (i == 0) return 0;
  else if (i == 1) return 0;
  else if (i == 2) return 1;

  if (memo[i] != -1) return memo[i];

  memo[i] = tribonacci(i-1) + tribonacci(i-2) + tribonacci(i-3);

  return memo[i];
}

int main(void) {
    int N;
    cin >> N;

    memo.assign(N + 1, -1);

    cout << tribonacci(N) << endl;
}

これはいわゆる一般的なキャッシュと同じ考え方であり、メモ化をしていないケースに比べると圧倒的に計算量を抑えることができますね。

アルゴリズム実装(2) 単純なfor反復  O(N)

再帰関数の利用ではメモ化でのキャッシュ効率化はほぼ必須ですが、そもそも「前の3項を順々に足し合わせていく」という挙動は単純なfor反復でも実現可能です。この場合、計算量はそのループ数に依存するので計算量は O(N)とかなり抑えられます。

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

int main(void) {
    int N;
    cin >> N;
    vector<long long> tribonacci;

    tribonacci.assign(N + 1, 0);

    for (int i = 0; i < N; i ++) {
      if (i == 0) tribonacci[i] = 0;
      else if (i == 1) tribonacci[i] = 0;
      else if (i == 2) tribonacci[i] = 1;
      else tribonacci[i] = tribonacci[i - 1] + tribonacci[i - 2] + tribonacci[i - 3];
    }

    cout << tribonacci[N - 1] << endl;
}

大学のパッとしないGPAを米国基準にしてみたら、思いの外まともだった

はじめに

CS大学院進学

以前にキャリアの振り返りをしたこの記事でも軽く触れていたんですが、そもそも自分には大学生の時にCS大学院に行くかどうかを迷った結果院進学をやめてSIerに入社したという経緯があります。大学4年時に行った留学先にも出願準備のためにTOEFLの参考書を持っていきました。結局ほとんど使いませんでしたが。

この時は「社会人になってから、必要性を再度感じた時に進学を目指そう」と判断しました。

lamechang-dev.hatenablog.com

で、最近米国のオンラインCS修士の記事などを拝見させていただき、以下の点が自分にとって非常に魅力的に思えました。

  • 働きながら継続できそう
  • 英語での受講ができる & 入学要件にTOEFLのスコアがあって英語力向上にも利用できそう
  • コストパフォーマンスが優れている

zenn.dev

結婚しているので家庭の事情などももちろん考えながらではあるのですが、出願に向けて、英語のリハビリにもなるしTOEFL対策だけでも今年から少しずつ始めようかな?と思っている所であります。

ただ1つ大きな懸念点が。

GPAに対する不安

出願することを検討した際に、ずっと不安要素だったものがあります。そう、学部時代のGPAですね。

米国をはじめとする海外のCS修士を目指すってなった時に誰もが気にすることだと思いますし、自分も正直大した成績で卒業してないので「そもそもミニマムの出願要件満たせてないんじゃね・・?」と思ったりしていました。

が、どうやら米国基準のGPA計算は日本のそれと違うらしく、人によっては所属大学のGPAより大きく上昇することがあるということをたまたま知り、「これは自分も確認する必要があるな・・」と思い立った次第です。

家の書類置き場の奥の方に眠っていた成績証明書を引っ張り出して計算してみました。

WES

WESとは

アメリカやカナダでの留学生や移民の資格認定評価を提供する非営利団体のことらしいです。一部のアメリカの大学では、アメリカ国外の成績表認証・GPA計算等の業務をこのWESに委託している場合もあるみたいです。

WES iGPA Calculator

そしてこのWESのHPに WES iGPA Calculator というツールが置いてあります。名前から分かる通り、GPAの計算機ですね。100%実際の基準となるとは言い切れませんが、これを利用することで米国基準で判断される場合のGPAの値を計算できそうですね。参考に利用してみる価値はありそうです。

私の出身大学の場合、A+は4点、Aは3点、Bは2点、Cは1点、不合格は0点 という計算式に基づいてGPAが算出されていましたが、どうやらWESの計算式は Aは4点、Bは3点、Cは2点、Fが0点 で算出されるっぽいです。なるほど、単純に数値が一段階ずつ上がることになりますね・・

計算されたGPA

3.4 になりました。大学院によってはGPAの推奨値として3.5以上を課す所(いわゆるトップ校など)もあるので全く問題ない数値とは言い難いですが、私が進学を検討しているオンラインのCS大学院の要件は大体クリアできているみたいです。計算してみてよかったです😿

最後に

CS修士の出願要件として最もリカバリがきかないのがGPAだと思っていて、今回それがある程度問題ないことが分かって良かったです。

大学時代ってろくに勉強もせずにバンド練習やバイト・飲み会をやっていたような気がしていたのですが、昔の成績表などを見ると意外と頑張って勉強したような科目もあって、当時の学生生活を思い出したりしました。

ただ、時が戻せるならもうちょっとちゃんと本腰を入れて勉強した上で、CSの大学院に行ってたとは思います。人生。