2023振り返りと2024の目標

2023は世況の割には落ち着いた年だった。勤務先の業績がV字回復してレイオフ等の心配がなかったのが大きいと思う。

  • 健康維持

スポーツ(アイススケート)を続ける。毎日8時間以上の睡眠をとる。加えて「ちゃんと栄養を摂る」。忙しくても連続でお粥だけとか止めよう。

アイススケートは週2ベースで続けられた。睡眠はあまり良くない。特に秋から冬は仕事・プライベートともに忙しく、就寝が1時を過ぎることも多かった。また「一日の睡眠時間」をメトリクスにしたのに、自動計測や振り返りの仕組みを作らなかったのも反省点。食事はトータルでは悪くないはずだが、こちらも正確な記録がないためうまく判断できない。

  • ReactとJestにめっちゃ詳しくなる

周辺技術含む。いずれも公式Docや標準的な本くらいは理解してベストプラクティスをサラサラと応用できるレベル。metaのWebエンジニアとして、これが最低限の期待値だと思う。いまは残念ながら、フレームワークと同僚の優秀さに完全に頼ってしまっている。

React/Jestではなく、H2のメインプロジェクトとなったOpenTelemetryを学ぶことにした。チーム内で最も詳しいやつ、くらいにはなれたと思う。しかし同僚がOpenTelemetry世界代表みたいな人なので、どうしても比べてしまう。

  • すべてに準備して臨む

@poyothonさんの「たくさん準備して、繰り返し練習する」を一部パクらせていただいた。もともと準備好きではあるが、これを「すべてに」に拡張する。具体的には「すべてのMeetingに10分以上の予習を行う」など

95%のケースではよく準備して臨めた。『すべてに』はあまり現実的な目標ではなかったかもしれない。

  • プロトタイプで意思決定をリード

とにかく手を軽く早く動かして、プロトタイプを見せまくる。コミュニケーションや議論でプロジェクトをリードするタイプではないし、EM等にもこれが期待されている。

これは達成できたと思う。素早くコードや文章を書いてステークホルダーに見せて合意を得る、は自分の型になっている。

  • Coding Machine

「とにかくコード書くやつ」のポジションをキープし続ける。目安は
Commit数: 3位(47人中) → 5位以内
コード行数: 8位 → 5位以内
Review数: 15位 → 10位以内

Commit数: 3位(約40人中)
コード行数: 11位 (あまりいい指標ではなかった)
Review数: 1位 ☆
Review数がダントツでトップだったのは自信を持てそう。

2024の目標

  • 健康維持

スポーツ(アイススケート)を続ける。毎日8時間以上の睡眠をとる。ちゃんと栄養を摂る。

今年は目標を一つに絞った。仕事をうまくやるとか、学習を頑張るとかは健康でさえあれば自然に達成できそうな気がする。

2022振り返り(2)

評価サイクルの関係上、仕事の振り返りは2月末となる。

  • 仕事

現職で使える人材になる。数値目標は、①インフレ率以上の昇給、②ボーナス満額以上、③昇格(Stretch Goal)。この目標は評価サイクルの関係上2023年2月末日までとする。

年次Performanceが好調だったので「現職で使える人材」と評価されてるはず。数値目標については

  • #1: 達成。CashのみだとCPIコアとほぼ同等(+6%)。RSUを含めれば大幅に上回った(+23%)
  • #2: 達成。ボーナス+15%(個人評価のみ)。ただし会社の業務不調により-15%が加味されるため実質変わりなし
  • #3: 未達。これは難しい

2023の目標(recap)
今のところ以下の目標に向かって頑張っている。

  • 健康維持
  • ReactとJestにめっちゃ詳しくなる
  • すべてに準備して臨む
  • プロトタイプで意思決定をリード
  • Coding Machine

2022振り返りと2023の目標

2022は世界的不景気+レイオフと大変な年だった。自社の13%レイオフに生き残っただけで100点満点だと思う。

  • 健康 [P0]

P0に昇格。特に運動不足を持続的な形で解消したい。1~2月にフィットネスのクラスを回り、Commitできるスポーツを見つけたいと考えてる。具体的な数値目標や計画は3月までに策定する。なお睡眠は8hに増やす。

アイススケートのクラスに入ったおかげで週2回の運動が担保されるようになった。数値目標や計画は立てなかったが、モチベーションを保ちながら続けられるスポーツを見つけることができた。睡眠8h以上は9割以上の達成率。

  • 仕事

現職で使える人材になる。数値目標は、①インフレ率以上の昇給、②ボーナス満額以上、③昇格(Stretch Goal)。この目標は評価サイクルの関係上2023年2月末日までとする。

レイオフされなかったので、使える人材と評価されてると信じたい。数値目標は2月末に再評価。もっとも記録的インフレ+自社株不調なので①②はほぼ絶望的。そして③はかなり難しい。

2023の目標

  • 健康維持

スポーツ(アイススケート)を続ける。毎日8時間以上の睡眠をとる。加えて「ちゃんと栄養を摂る」。忙しくても連続でお粥だけとか止めよう。

  • ReactとJestにめっちゃ詳しくなる

周辺技術含む。いずれも公式Docや標準的な本くらいは理解してベストプラクティスをサラサラと応用できるレベル。metaのWebエンジニアとして、これが最低限の期待値だと思う。いまは残念ながら、フレームワークと同僚の優秀さに完全に頼ってしまっている。

  • すべてに準備して臨む

@poyothonさんの「たくさん準備して、繰り返し練習する」を一部パクらせていただいた。もともと準備好きではあるが、これを「すべてに」に拡張する。具体的には「すべてのMeetingに10分以上の予習を行う」など。

  • プロトタイプで意思決定をリード

とにかく手を軽く早く動かして、プロトタイプを見せまくる。コミュニケーションや議論でプロジェクトをリードするタイプではないし、EM等にもこれが期待されている。

  • Coding Machine

「とにかくコード書くやつ」のポジションをキープし続ける。目安は
Commit数: 3位(47人中) → 5位以内
コード行数: 8位 → 5位以内
Review数: 15位 → 10位以内

2021振り返りと2022の目標

2021は転職+Web転向という大目標が達成できたので完全に勝利の年だった。

  • 転職

Webエンジニアになる、と決めると現職のままではツラい。副業で経験とスキルを稼ぎつつチャンスを狙おう。

BigTechへ転職し、Webエンジニアとしてキャリアを積み始めることに成功した。

  • Web転向

UIデザインもフロントもバックエンドも「ざっと出来る」くらいになる。

フロントエンド・バックエンドが「ざっと出来る」くらいにはなれたかな。UIデザインまでは無理ゲー…。

  • インプットの量・幅を増やす

2020はアウトプット(というか練習)偏重で、しかも範囲が狭すぎた。ほぼアルゴリズムしかやってない。Web技術に関する典型本をどんどん押さえていこう。

これは転職後、社内のトレーニング教材を利用していろいろやれた。特にソフトスキル面に手が回り始めたのは大きい。典型本は数冊しか読めなかった。

  • 健康第一

睡眠7.5h、毎日運動、バランスの取れた食事。普通だけどこれが一番大事です。体調くずすとすべてが水の泡。

睡眠はほぼ達成できた。運動はぜんぜんダメ。食事も今一歩。ただし体調は崩さなかったので及第点とは言える。


2022の目標

  • 健康 [P0]

P0に昇格。特に運動不足を持続的な形で解消したい。1~2月にフィットネスのクラスを回り、Commitできるスポーツを見つけたいと考えてる。具体的な数値目標や計画は3月までに策定する。なお睡眠は8hに増やす。

  • 仕事

現職で使える人材になる。数値目標は、①インフレ率以上の昇給、②ボーナス満額以上、③昇格(Stretch Goal)。この目標は評価サイクルの関係上2023年2月末日までとする。

2020振り返りと2021の目標

2020はGAFA転職+スキルアップ!が夢だったのだけどコロナで完全に吹っ飛んでしまった。数社受けて落ちたのはいい思い出。まあLeetCodeやシステムデザイン・アルゴリズムなどの勉強をコツコツ続けられたのは悪くなかったかな。

9月からWeb開発者へ舵を切った。副業でRoRコミュニティに所属してデイリーで手を動かせてる。RoRが死にそうというウワサもあるけど、一通り使えるようになるまでは気にせずやるつもり。質の高いコミュニティはモチベが上がっていいね。

本業は昇給できたし、ボーナスも少額ながら貰えたので世界情勢を鑑みれば100点満点ということにする。

というわけで2021の目標

  • Web転向

UIデザインもフロントもバックエンドも「ざっと出来る」くらいになる。

  • インプットの量・幅を増やす

2020はアウトプット(というか練習)偏重で、しかも範囲が狭すぎた。ほぼアルゴリズムしかやってない。Web技術に関する典型本をどんどん押さえていこう。

  • 転職

Webエンジニアになる、と決めると現職のままではツラい。副業で経験とスキルを稼ぎつつチャンスを狙おう。

  • 健康第一

睡眠7.5h、毎日運動、バランスの取れた食事。普通だけどこれが一番大事です。体調くずすとすべてが水の泡。

LeetCode 312. Burst Balloons - 解法メモ

苦戦したのでメモしておく。

n個の風船が1列に並んでいて、それぞれにスコアが設定されている。風船を割ると「左のスコア x この風船のスコア x 右のスコア」だけ得られる。割る順番を工夫して、最終的なトータルスコアを最大化せよ。
・「左のスコア」「右のスコア」はなかったら1
・割った風船は詰める
0 <= nums[i] (風船iのスコア) <= 100
1 <= n <= 500

https://leetcode.com/problems/burst-balloons/
トップダウンでやると計算量がO(N!)となり間に合わない。ボトムアップで考える。
dp[l][r]: nums[l...r]での最大スコア
とする。
この範囲で最後にx番目を割るとき、そこで得られるスコアは
nums[l-1] * nums[x] * nums[r+1]
となる。
さらに、左右のdp[l][x-1]とdp[x+1][r]は独立に考えてよいことがわかる。
なぜなら、xは最後に壊されるので、たとえば左ブロック[l, x-1]はx+1番目から右は関係ない。
よって、
dp[l][r] = Max(dp[l][r], dp[l][x-1] + dp[x+1][r] + nums[l-1] * nums[m] * nums[r+1])
となる。
あらかじめnums[]の先頭と末尾に1を挿入しておくと実装が楽。

public class Solution
{
    public int MaxCoins(int[] nums)
    {
        nums = new int[] { 1 }.Concat(nums).Concat(new int[] { 1 }).ToArray();
        var dp = new int[nums.Length, nums.Length];

        for (int gap = 0; gap < nums.Length; gap++)
        {
            for (int l = 1; l < nums.Length - 1; l++)
            {
                var r = l + gap;
                if (r >= nums.Length - 1) continue;

                var val = 0;
                for (int x = l; x <= r; x++)
                    val = Math.Max(val, nums[l - 1] * nums[x] * nums[r + 1] + dp[l, x - 1] + dp[x + 1, r]);
                dp[l, r] = val;
            }
        }

        return dp[1, nums.Length - 2];
    }
}

Facebook Hacker Cup 2020 Qualification Round 参加日記

4完/5問で通過。まあ1問通せばいいんだけど。

Problem A: Travel Restrictions

N個の国が横一列に並んでいて、隣どうしの国のみ直接行き来できる。ここで、それぞれの国について、出国禁止か否か、また入国禁止か否かの条件が足される。このとき、各国間で行き来できるか、NxNのマトリックスで表示せよ(別の国をまたいでも良い)。
2 <= N <= 50

https://www.facebook.com/codingcompetitions/hacker-cup/2020/qualification-round/problems/A
Nが小さいので、すべての2国s, tについて解ける。隣の国にしかいけないので、愚直にsからtまで行こうとすればよい。
※コードは省略


Problem B: Alchemy

N個の石が横一列に並んでいる。Nは奇数で、石はそれぞれ赤か黒である。次の条件で、となりあう3つの石を合成して1つに変えることができる。
・3つの石に赤と黒の両方がある
・合成すると、赤・黒の数の多いほうの色の石が1つできあがる
・石の場所は変わらない
合成を繰り返して、最終的に1個の石にすることは可能か。
3 <= N <= 99,999

https://www.facebook.com/codingcompetitions/hacker-cup/2020/qualification-round/problems/B
合成すると必ず赤が1個、黒が1個減る。
よって、赤の数と黒の数の差が1のときのみ、最終的に1個にすることが可能である。
さらに、赤と黒が混在していれば、かならず合成をすることが可能である(どこかに赤と黒の境目があるので)。なので「赤の数と黒の数の差が1ならば可能」が解答になる。
※コードは省略


Problem C: Timber
https://www.facebook.com/codingcompetitions/hacker-cup/2020/qualification-round/problems/C

横一列にN本の木がある。木iの高さはHi、場所はPiである。それぞれの木について、①右に倒す②左に倒す③倒さない、のいずれかを行う。このとき、倒れた木どうしがぴったり接していたら、それらは結合しているとする。結合した倒木集合の最大長を求めよ。
なお、木の太さは無視してよい。倒れていない木も無視してよい。重なった倒木どうしは、結合していることになならない。
1 <= N <= 800,000
'-500,000,000 <= Pi <= 500,000,000
1 <= Hi <= 50,000,000

dp[x]を、位置xから右に連なっている倒木結合の最大長、とする。
右の木から処理していくとして、
・左に倒した場合
dp[x-Hi] = Max(Hi + dp[x], dp[x-Hi])
・右に倒した場合
dp[x] = Max(Hi + dp[x+Hi], dp[x])
のように更新していけばよい。
dp成分の最大が解答になる。

class Program
{
    static IO.StreamScanner _scanner = new IO.StreamScanner(Console.OpenStandardInput());

    static void Main(string[] args)
    {
        var t = _scanner.Integer();
        for (var c = 0; c < t; c++)
        {
            var n = _scanner.Integer();
            var ph = new List<(long, long)>();
            for (int i = 0; i < n; i++)
            {
                ph.Add((_scanner.Long(), _scanner.Long()));
            }
            ph = ph.OrderBy(x => x.Item1).ToList();
            var p = ph.Select(x => x.Item1).ToList();
            var h = ph.Select(x => x.Item2).ToList();

            var ret = 0L;
            var memo = new Dictionary<long, long>();
            for (int i = n - 1; i >= 0; i--)
            {
                var a = p[i] - h[i];
                var x = p[i];
                var b = p[i] + h[i];

                // left
                var l1 = h[i] + (memo.ContainsKey(x) ? memo[x] : 0L);
                memo[a] = Math.Max(l1, memo.ContainsKey(a) ? memo[a] : 0L);

                // right
                var l2 = h[i] + (memo.ContainsKey(b) ? memo[b] : 0L);
                memo[x] = Math.Max(l2, memo.ContainsKey(x) ? memo[x] : 0L);

                ret = Math.Max(ret, l1);
                ret = Math.Max(ret, l2);
            }
            
            IO.Printer.Out.WriteLine("Case #{0}: {1}", (c + 1), ret);
        }

        IO.Printer.Out.Flush();
    }
}


Problem D1: Running on Fumes - Chapter 1

N個の都市1...Nが一列に並んでいて、あなたは最初都市1にいる。隣の都市に移動するのに1のガソリンが消費される。ガソリンタンクはMの容量がある。さらに、各都市でガソリンをCiドルで補給することができる(Ci=0なら不可能)。都市Nへ到着するのに必要な最小コストを答えよ。不可能な場合は-1を出力すること。
なお、ガソリンタンクの空き容量にかかわらず、補給するとかならずCiドルのコストがかかる。
2 <= N <= 1,000,000
1 <= M <= N
0 <= Ci <= 1,000,000,000

https://www.facebook.com/codingcompetitions/hacker-cup/2020/qualification-round/problems/D1
dp[i]: 都市iから、タンクにMガソリンがある状態で都市Nまで行く最小コスト
とすると、
・N-i <= Mのとき
dp[i] = 0
・それ以外
dp[i] = Min(dp[x] + C[x]) (xはi+1~i+m)
になる。
Min(dp[x] + C[x])の部分はx+1からx+mの最小値なので、セグメント木(RMQ)を使えばよい。

class Program
{
    static IO.StreamScanner _scanner = new IO.StreamScanner(Console.OpenStandardInput());

    static void Main(string[] args)
    {
        var t = _scanner.Integer();

        for (int cnt = 0; cnt < t; cnt++)
        {
            var n = _scanner.Integer();
            var m = _scanner.Integer();
            var c = new List<long>();
            for (int i = 0; i < n; i++)
                c.Add(_scanner.Long());

            var dp = Enumerable.Range(0, n).Select(i => long.MaxValue).ToList();
            var seg = new Rmq();
            seg.Init(n);

            for (int i = n - 1; i >= 0; i--)
            {
                var val = long.MaxValue;
                if (n - 1 - i <= m) val = 0;
                else val = seg.Query(i + 1, i + m + 1);
                if (i != 0)
                    seg.Update(i, val == long.MaxValue ? long.MaxValue : c[i] == 0 ? long.MaxValue : val + c[i]);
                dp[i] = val;
            }

            IO.Printer.Out.WriteLine("Case #" + (cnt + 1) + ": " + (dp[0] == long.MaxValue ? -1 : dp[0]));
        }

        IO.Printer.Out.Flush();
    }
}

public class Rmq
{
    const int MAX_N = 1 << 21;
    int _n;
    long[] _dat = new long[2 * MAX_N - 1];

    public void Init(int n)
    {
        _n = 1;
        while (_n < n) _n *= 2;
        for (int i = 0; i < _dat.Length; i++) _dat[i] = long.MaxValue;
    }

    public void Update(int k, long a)
    {
        k += _n - 1;

        _dat[k] = a;
        while (k > 0)
        {
            k = (k - 1) / 2;
            _dat[k] = Math.Min(_dat[k * 2 + 1], _dat[k * 2 + 2]);
        }
    }

    public long Query(int a, int b)
    {
        return Query(a, b, 0, 0, _n);
    }

    long Query(int a, int b, int k, int l, int r)
    {
        if (r <= a || b <= l) return long.MaxValue;
        if (a <= l && r <= b) return _dat[k];
        else
        {
            var vl = Query(a, b, k * 2 + 1, l, (l + r) / 2);
            var vr = Query(a, b, k * 2 + 2, (l + r) / 2, r);
            return Math.Min(vl, vr);
        }
    }
}


Problem D2: Running on Fumes - Chapter 2

N個の都市1...Nが木構造で双方向につながっている。隣の都市に移動するのに1のガソリンが消費される。ガソリンタンクはMの容量がある。さらに、各都市でガソリンをCiドルで補給することができる(Ci=0なら不可能)。都市Aから都市Bへ移動するのに必要な最小コストを答えよ。不可能な場合は-1を出力すること。
なお、ガソリンタンクの空き容量にかかわらず、補給するとかならずCiドルのコストがかかる。ガソリンタンクの初期値はM。
2 <= N <= 1,000,000
1 <= M <= N
1 <= A, B <= N
0 <= Pi <= N  (都市iの親)
0 <= Ci <= 1,000,000,000

https://www.facebook.com/codingcompetitions/hacker-cup/2020/qualification-round/problems/D2
本番では解けなかった。
現在地・累積コスト・ガソリン量をノードにしてDijkstraで間に合うようだ。コストの小さいノードを優先。
無限ループを避けるため、ガソリンを補給するとき、過去にすでに「同都市でもっと小さい累積コストで補給済み」なら無視するようにした。現在の位置からBまでの最短距離がガソリン量以下なら、そこまでのコストが最終解となる(Bから各都市への最短距離は予め算出)。
制限時間ぎりぎりなので、たぶんもっといい方法がある。

class Program
{
    static IO.StreamScanner _scanner = new IO.StreamScanner(Console.OpenStandardInput());

    static void Main(string[] args)
    {
        var t = _scanner.Integer();
        for (var c = 0; c < t; c++)
        {
            var n = _scanner.Integer();
            var m = _scanner.Integer();
            var a = _scanner.Integer() - 1;
            var b = _scanner.Integer() - 1;
            var pc = new List<(int, int)>();
            for (int i = 0; i < n; i++)
            {
                pc.Add((_scanner.Integer() - 1, _scanner.Integer()));
            }
            var parents = pc.Select(p => p.Item1).ToList();
            var costs = pc.Select(p => p.Item2).ToList();

            var graph = Enumerable.Range(0, n).Select(i => new List<int>()).ToList();
            for (int i = 0; i < n; i++)
            {
                if (parents[i] == -1) continue;
                graph[i].Add(parents[i]);
                graph[parents[i]].Add(i);
            }

            var distToB = GetDistToB(n, b, graph);
            var ret = GetAns(n, m, a, b, graph, distToB, costs);
            IO.Printer.Out.WriteLine("Case #{0}: {1}", (c + 1), ret);
        }

        IO.Printer.Out.Flush();
    }

    static int[] GetDistToB(int n, int b, List<List<int>> graph)
    {
        var ret = Enumerable.Range(0, n).Select(i => int.MaxValue).ToArray();
        var que = new Queue<(int, int)>();  // pos, dist
        que.Enqueue((b, 0));
        while (que.Any())
        {
            var tpl = que.Dequeue();
            var pos = tpl.Item1;
            var dist = tpl.Item2;

            if (dist >= ret[pos]) continue;
            ret[pos] = dist;

            foreach (var next in graph[pos])
            {
                if (ret[next] <= dist + 1) continue;
                que.Enqueue((next, dist + 1));
            }
        }
        return ret;
    }

    static long GetAns(int n, int m, int a, int b, List<List<int>> graph, int[] distToB, List<int> costs)
    {
        var que = new PriorityQueue<Node>();
        que.Push(new Node(a, 0, m, distToB[a], null));

        var moneyAtFuel = new Dictionary<int, long>();

        while (que.Count() > 0)
        {
            var node = que.Pop();
            if (node.Pos == b || distToB[node.Pos] <= node.Fuel)
            {
                return node.Money;
            }

            // goto next node
            if (node.Fuel > 0)
            {
                foreach (var next in graph[node.Pos])
                {
                    if (node.Prev != null && next == node.Prev.Pos) continue; // hate to go back
                    que.Push(new Node(next, node.Money, node.Fuel - 1, distToB[next], node));
                }
            }

            // Fuel
            if (node.Fuel != m && costs[node.Pos] != 0)
            {
                if (moneyAtFuel.ContainsKey(node.Pos) && moneyAtFuel[node.Pos] <= node.Money) continue; // had better answer
                moneyAtFuel[node.Pos] = node.Money;
                que.Push(new Node(node.Pos, node.Money + costs[node.Pos], m, distToB[node.Pos], null));
            }
        }

        return -1;
    }

    class Node : IComparable
    {
        public int Pos;
        public long Money;
        public int Fuel;
        public int DistToB;
        public Node Prev;

        public Node(int pos, long money, int fuel, int distToB, Node prev)
        {
            Pos = pos;
            Money = money;
            Fuel = fuel;
            DistToB = distToB;
            Prev = prev;
        }

        public int CompareTo(object obj)
        {
            var other = obj as Node;

            // smaller money is better
            if (Money != other.Money)
                return Money.CompareTo(other.Money);

            // smaller distance to B is better
            if (DistToB != other.DistToB)
                return DistToB.CompareTo(other.DistToB);

            // lager fuel is better
            return other.Fuel.CompareTo(Fuel);
        }
    }
}