読んだ技術書籍まとめ 2019年 + おまけの2018年

はじめに

2019年もそろそろ終わるので、棚卸も含めて2019年中に読んだ技術書籍を備忘録的に感想付きでまとめていきます。

ついでに2018年のものも覚えている限りでまとめていこうと思います。

追記

反省としては、内容を忘れてしまっている書籍も多いので、しっかり復習しないとなと思った次第です😞

システムプログラミング

フロントエンド

アプリケーション

インフラ

その他コメントは省略

セキュリティ

機械学習

その他

2018年

おわりに

来年は、1. Haskellを覚える 2. Reactを覚える 3. Go言語でマイクロサービスを作れるようになる 4. Chaos Engineeringに関する知識を高める を目標にやっていきたいと思います!

*1:ShellとTCPサーバまでは作ったけど、その後のWebサーバとFTPサーバで挫折した

Cloud FunctionsのHTTP Function呼び出しは認証が必要になるよってお話

この記事は「Google Cloud Platform Advent Calendar 2019」11日目の記事です。

3行でわかるまとめ

  1. 2020年1月15日以降に作成されるCloud FunctionsのHTTP Function呼び出し(以下、HTTP呼び出し)はデフォルトで認証が必要となります!
  2. 2020年1月15日以降に作成されるHTTP呼び出しを認証なしで行いたい場合は、「Cloud Functions Invoker」にallUsersを設定してあげる必要があります!
  3. この情報は日本語ドキュメントと英語ドキュメントで差異があるので英語ドキュメントを読んでください!

これまでのHTTP呼び出しの認証方法

僕が知る限り、半年前まではCloud Functions自体に認証方法がなかったため、 Cloud Functions内で認証するためのロジックを書いてあげる必要がありました。

当時、ドキュメントに記載のあった方法は、GCS上に認証用のbucketを作成し、そのbucketに対する権限を確認することで認証するという方法でした。 tech.mercari.com

この方法はFunctionsの内容が冗長になるのに加えて、不必要なGCSバケットが必要などの問題がありました😩

これからのHTTP呼び出しの認証方法

公式ドキュメント

Cloud Runのroles/run.invokerのようなRoleがCloud Functionsに登場しました!

この項目にallUsersを指定しない限りは、権限を持つユーザ・サービスアカウントしかHTTP呼び出しできないようになるので、 Functions内での認証のためのコードやGCSバケットのことを考えなくて済むようになりました😊

注意点

2020年の1月15日以降に作られるHTTP FunctionはデフォルトでHTTP呼び出しに認証が必要になるようなので、気をつけてください!

最後に

有益な情報の割になかなか見つけるのが難しい場所にドキュメントがあったので、共有させていただきました! ここまで読んでいただいてありがとうございました🙇‍♂️

terraform-providersの開発手順

Terraformでの作業中に、Terraform Providerに(バグとも言えないような)軽微な不具合を発見したので、 PRを送るために開発環境構築・改修の検証を行った。

普段はGo言語で開発を行わないので、また似たような状況になった時に困らないように、 備忘録として開発環境構築・改修の検証の手順を残しておく。

Go言語のインストール

 $ brew install goenv
 $ vim ~/.bashrc // 追記内容は下記参考
 $ source ~/.bashrc
 $ goenv install 1.11.4
 $ goenv global 1.11.4
 $ mkdir $HOME/go

~/.bashrc の追記内容

# For goenv
export PATH="$HOME/.goenv/bin:$PATH"
if which goenv > /dev/null; then
  eval "$(goenv init -)"
fi
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$PATH

Terraform Providerの開発環境構築

README通りに進めていく。

 $ mkdir -p $GOPATH/src/github.com/terraform-providers
 $ cd $GOPATH/src/github.com/terraform-providers
 $ git clone git@github.com:terraform-providers/terraform-provider-aws
 $ cd $GOPATH/src/github.com/terraform-providers/terraform-provider-aws
 $ make build

テスト

 $ make test
 $ make testacc TESTARGS='-run=テストfuncton名'

実際に動かして検証したい時

まずビルドしたバイナリをPluginとして使えるようにする。

 $ mkdir -p $HOME/.terraform.d/darwin_amd64
 $ cp $GOPATH/bin/terraform-provider-aws $HOME/.terraform.d/darwin_amd64/terraform-provider-aws_vX.Y.Z // X.Y.Zは現行の最新版より大きい値にする

次に、検証に使うTerraformファイル内で下のようにprovider情報を修正して $ terraform init する。

provider "aws" {
 version = "~> X.Y"
}

おわり

追記

PRがマージされました。

github.com

TypeScriptでHubotを書いてみた

TypeScript Advent Calendar 2018 11日目の記事です。

ChatOpsの導入として、何かしらのbotをNode.jsで書いてみたいなあと思いまして、 社内向けにHubot(もう古い?)を導入してみました。

Node.jsでの開発は初めてだったのですが、せっかくならTypeScriptで書いてみようということになり、 TypeScriptでHubotを書いてみました。

導入編

まずHomebrewを使ってNVMとYarnをインストールします。

 $ brew install nvm
 $ nvm install --lts
 $ nvm use --lts
 $ brew install yarn --without-node

設定ファイルを用意したり、TypeScriptをインストールします。

 $ yarn init // 適当に答える
 $ yarn add  webpack webpack-cli ts-loader typescript tslint tslint-config-airbnb @types/node

Hubotをインストールします。

 $ yarn add generator-hubot yo hubot @types/hubot

ここら辺まで来ると、HubotをTSで書けるようになります。

import hubot = require("hubot");

module.exports = (robot: hubot.Robot<any>): void => {
  robot.respond(/hello/i, (msg: hubot.Response<hubot.Robot<any>>)=>{
    msg.reply("world!");
  });
};

プログラムを書いたので、テストを書きます。(TDDっぽく先に書いてもいいですね。)

まずは必要なライブラリをインストールします。

 $ yarn add --dev chai co hubot-test-helper mocha
const Helper = require('hubot-test-helper');
const helper = new Helper('../scripts/hello.js');

const co = require('co');
const expect = require('chai').expect;

describe('hello world', function() {
  beforeEach(function() {
    this.room = helper.createRoom();
  });
  afterEach(function() {
    this.room.destroy();
  });

  context('user says hello to hubot', function() {
    beforeEach(function() {
      return co(function*() {
        yield this.room.user.say('alice', '@hubot hello');
      }.bind(this));
    });

    it ('should reply to user', function() {
      expect(this.room.messages).to.eql([
        ['alice', '@hubot hello'],
        ['hubot', '@alice world!']
      ]);
    })
  });
});

これでひとまずプログラムとテストをTSで書くことができました!

運用編

Herokuにデプロイしています。 (Herokuへのデプロイで詰まった話はココ

その他

Hubotがメンテされていなくて辛い。

github.com

LeetCode: ZigZag Conversion

Easy問題ばかり解いていたので、たまにはMedium問題を解きます。

問題

The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

P   A   H   N
A P L S I I G
Y   I   R

And then read line by line: "PAHNAPLSIIGYIR" Write the code that will take a string and make this conversion given a number of rows:

string convert(string s, int numRows);

解答

本問題では、sの中での何文字目がジグザグの何行目に来るのかを知る必要がありそうです。

とはいえ、いまいち掴みどころがわかりません。 そこで手を動かて試してみることにします。

  1. numRows = 1 の時

これはsが返ってきますね。

  1. s.length <= numRows の時

これも1.と同様にsが返ってきますね。

  1. numRows < s.length の時

主に考えないといけないのはこの場合です。与えられている例題で少し考えてみます。

3-1. s = "PAYPALISHIRING", numRows = 3の時

実際の値 sの中で何文字目 ジグザグの何行目
P 1 1
A 2 2
Y 3 3
P 4 2
A 5 1
L 6 2
I 7 3
S 8 2
H 9 1
I 10 2
R 11 3
I 12 2
N 13 1
G 14 2

なるほど、そういうことですね。 ひとまず実装の戦略が何となく立ちましたが、もう一つの例題でも確認してみます。

3-1. s = "PAYPALISHIRING", numRows = 4の時

実際の値 sの中で何文字目 ジグザグの何行目
P 1 1
A 2 2
Y 3 3
P 4 4
A 5 3
L 6 2
I 7 1
S 8 2
H 9 3
I 10 4
R 11 3
I 12 2
N 13 1
G 14 2

なるほど、やはりそうですね。

上2つの表で注目して欲しいのは右端の列です。

数字がそれぞれ 1, 2, 3, 2, 1, 2, 3, 2, 1, ...1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 3, 2, 1, ...と周期的に並んでいますね。

これがつまりどういうことかというと、3-1.の時、11 + 4 * n 番目(nは非負整数)、2偶数番目3は...というように規則的に数字が現れるということです。(3-2.の時はもう少し複雑な規則になりますが、基本的には同じことです。)

以上の考察から、sの中での何文字目がジグザグの何行目に来るかがわかったので、あとは実装するのみです。

public class Solution {
  public String convert(String s, int numRows) {
    if (numRows <= 1 || s.length() <= numRows) {
      return s;
    }
    char[] ans = new char[s.length()];
    int current = 0;
    for (int i = 0; i < numRows; i++) {
      int patterNum = 2 * numRows - 2;
      int intervalNum = 2 * (numRows - i) - 2;
      for (int j = i; j < s.length(); j += patterNum) {
        ans[current] = s.charAt(j);
        current++;
        if (i != 0 && i != numRows - 1  && j + intervalNum < s.length()) {
          ans[current] = s.charAt(j + intervalNum);
          current++;
        }
      }
    }
    return String.valueOf(ans);
  }
}

(sの長さをm、numRowsをnした時)時間計算量:O(m x n)、メモリ計算量:O(m)

この解法は結構速い方みたいですね。(どんぐりの背比べですが。) f:id:tsukakei1012:20181128142150p:plain

それでは他の人の解答も見てみます。

class Solution {
        public String convert(String s, int numRows) {
        char[] result = new char[s.length()];
        int count = 0;
        if(numRows == 1)return s;
        // 1行目の文字を埋めている
        for(int j = 0 ; j < s.length(); j += (numRows - 1) * 2)
            result[count++] = s.charAt(j);
        
        // 2 ~ numRows-1行目の文字を埋めている
        for(int i = 1 ; i < numRows - 1 ; i++) {
            int j = i;
            int add = (numRows - 1) * 2 - 2 * j;
                // i 行目の文字を埋めている処理
            while(j < s.length()) {
                result[count++] = s.charAt(j);
                j += add;
                add = (numRows - 1) * 2 - add;
            }
        }
        // numRows行目の文字を埋めている     
        for(int j = numRows - 1 ; j < s.length(); j += (numRows - 1) * 2)
            result[count++] = s.charAt(j);
        return String.copyValueOf(result);
        }
}

少しわかりづらいので、コメントを入れました。 2 ~ numRows-1行目の文字の埋め方が少し違いますが、やっていることは基本的に同じですね。

LeetCode: Valid Parentheses

問題

Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.

Note that an empty string is also considered valid.

解答

問題文を読んでExampleを見た瞬間にStackだなと思ったので、それで実装しました。 スタック - Wikipedia

import java.util.LinkedList;
class Solution {
    public boolean isValid(String s) {
    LinkedList<Character> parenthesesStack = new LinkedList<>();
    for (char ch : s.toCharArray()) {
      if (ch == '(' || ch == '{' || ch == '[') {
        parenthesesStack.push(ch);
      } else if (!parenthesesStack.isEmpty()) {
        char openParentheses = parenthesesStack.pop();
        if (!(openParentheses == '(' && ch == ')')
            && !(openParentheses == '{' && ch == '}')
            && !(openParentheses == '[' && ch == ']')) {
          return false;
        }
      } else {
        return false;
      }
    }
    return parenthesesStack.isEmpty();
  }
}

時間計算量:O(n)、メモリ計算量:O(n)

他の人の解答はこんな感じです。

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        
        for(char c : s.toCharArray()){
            if(c=='(' || c=='[' || c=='{')
                stack.push(c);
            else if(!stack.isEmpty() && isMatch(stack.peek(),c)) {
                stack.pop();
            } else{
                return false;
            }
               
        }
        
        return stack.isEmpty()? true : false;
    }
    
    public boolean isMatch(char a, char b ){
       return  (a=='('&& b==')'|| a=='{' && b =='}' || a=='[' && b==']') ? true : false;
    }
}

時間計算量:O(n)、メモリ計算量:O(n)

やっていることは全く同じなのですが、三項演算子を使って foo ? true : false はちょっと拒絶反応が出ますね。。。

LeetCode: Longest Common Prefix

問題

Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string "".

解答

大して上手いやり方が思い当たらず、力任せに解くことにしました。

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs.length == 0) return "";
        StringBuilder sb = new StringBuilder("");
        char currentChar = 0;
        for (int i = 0; ; i++) {
              for (int j = 0; j < strs.length; j++) {
                  String str = strs[j];
                  if (str.length() <= i) {
                    return sb.toString();
                  }
                  char ch = str.charAt(i);
                  if (j == 0) {
                    currentChar = ch;
                  }
                  if (ch != currentChar) {
                    return sb.toString();
                  }
             }
        sb.append(currentChar);
        }
    }
}

入力される配列のサイズをm、文字列の平均長をnとすると、

時間計算量:O(m x n)、メモリ計算量:O(n)

グラフを見た感じも他の人も同じような戦略のようですね。 f:id:tsukakei1012:20181128114407p:plain

他の人の解答も見てみましょう。

class Solution {
    public String longestCommonPrefix(String[] strs) {
        
        if(strs.length == 0 || strs == null)
        {
            return "";
        }
        
        String longCommonPre = strs[0];
        for(int i = 1;i<strs.length;i++)
        {
            int k = 0;
            String currentItem = strs[i];
            while(k < longCommonPre.length() && k < currentItem.length() && longCommonPre.charAt(k) == currentItem.charAt(k))
            {
                k++;
                
            }
            if( k == 0)
            {
                return "";
            }
            longCommonPre = longCommonPre.substring(0,k);
        }
        
        return longCommonPre;
    }
}

時間計算量:O(m x n)、メモリ計算量:O(n)

やっていることは同じですが、僕のコードの方がわかりやすくないですか?