Mac上のAWS SAM Local でInvalidSignatureException

Mac上の AWS SAM Local で Lambda 関数から DynamoDB Local にクエリを投げようとしたら、何故か以下のようなエラー。

An error occurred (InvalidSignatureException) when calling the Query operation: Signature not yet current: 20180520T125056Z is still later than 20180520T124759Z (20180520T123259Z + 15 min.): ClientError
Traceback (most recent call last):
  File "/var/task/app.py", line 25, in lambda_handler
    (略)
  File "/var/runtime/botocore/paginate.py", line 255, in __iter__
    response = self._make_request(current_kwargs)
  File "/var/runtime/botocore/paginate.py", line 332, in _make_request
    return self._method(**current_kwargs)
  File "/var/runtime/botocore/client.py", line 314, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/var/runtime/botocore/client.py", line 612, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (InvalidSignatureException) when calling the Query operation: Signature not yet current: 20180520T125056Z is still later than 20180520T124759Z (20180520T123259Z + 15 min.)

実際の時刻は日本時間 21:33 頃なので、コンテナ内の時間が15分以上進んでしまっている。DynamoDB Local 側は正しい時刻で動いているので、不整合が生じているっぽい。

どうも、Docker for Mac のバグか何かで時刻がずれるらしいので、単純に Docker for Mac を一度 Restart したら解決した。

この辺では一年前くらいに直っているようだけど、スリープ以外にも原因があるのかも。
https://github.com/docker/for-mac/issues/17

13個の玉から重さの違う1つの玉を見つける

http://anond.hatelabo.jp/20160629132908

天秤を3回だけ使って、9つの玉から1つだけ重さの違うものを探すという頭の体操。13個まで行けるというコメントがあったので、試してみた。

やり方を日本語で書くと大変なので、Java でダラダラと記述してみる。

class FindAnomaly {
	/**
	 * 13個の玉(13要素の配列)から、{@code weigh()} を3回だけ使用して
	 * 重さ(数値)の異なる玉(配列要素)を探します。
	 * @return 重さの異なる玉の番号(配列インデックス)
	 */
	public static int findAnomaly13(int[] n) {
		int[] n0123 = { n[0], n[1], n[2], n[3] };
		int[] n4567 = { n[4], n[5], n[6], n[7] };

		int[] n01245 = { n[0], n[1], n[2], n[4], n[5] };
		int[] n89ABC = { n[8], n[9], n[10], n[11], n[12] };
		
		// 0〜3の4個、4〜7の4個、8〜12の5個に分け、4個と4個を比較
		switch(weigh(n0123, n4567)) {
		case LEFT:
			// 0〜3の方が重い
			// 0,1,2,4,5 と、残りの5個(標準の重さ)を比較
			switch(weigh(n01245, n89ABC)) {
			case LEFT:
				// 0,1,2,4,5 が標準より重い。つまり 0,1,2 のいずれかが重い
				// 0 と 1 を比較し、差があれば重い方が正解。同じなら 2 が正解
				switch(weigh(n[0], n[1])) {
				case LEFT:
					return 0;
				case RIGHT:
					return 1;
				default:
					return 2;
				}
			case RIGHT:
				// 0,1,2,4,5 が標準より軽い。つまり 4,5 のどちらかが軽い
				// 4 と 5 を比較し、軽い方が正解
				switch(weigh(n[4], n[5])) {
				case LEFT:
					return 5;
				case RIGHT:
					return 4;
				default:
					// ありえないケース
					throw new IllegalStateException();
				}
			default:
				// 0,1,2,4,5 が標準と同じ、つまり残りの 3 が標準より重いか、6,7 が標準より軽い
				int[] n36 = { n[3], n[6] };
				int[] n89 = { n[8], n[9] };
				// 3,6 と標準の重さの2個を比較
				switch(weigh(n36, n89)) {
				case LEFT:
					// 3,6 が標準より重い。つまり 3 が正解
					return 3;
				case RIGHT:
					// 3,6 が標準より軽い。つまり 6 が正解
					return 6;
				default:
					// 3,6 は標準と同じ。つまり残りの 7 が正解
					return 7;	
				}
			}
		case RIGHT:
			// 0〜3 の方が軽い
			// この中は、0〜3 の方が重いケースの逆で、やり方は同じ
			switch(weigh(n01245, n89ABC)) {
			case LEFT:
				switch(weigh(n[4], n[5])) {
				case LEFT:
					return 4;
				case RIGHT:
					return 5;
				default:
					throw new IllegalStateException();
				}
			case RIGHT:
				switch(weigh(n[0], n[1])) {
				case LEFT:
					return 1;
				case RIGHT:
					return 0;
				default:
					return 2;
				}
			default:
				int[] n36 = { n[3], n[6] };
				int[] n89 = { n[8], n[9] };
				switch(weigh(n36, n89)) {
				case LEFT:
					return 6;
				case RIGHT:
					return 3;
				default:
					return 7;	
				}
			}
		default:
			// 8〜12 のどれかが正解。重いか軽いかはまだわからない
			int[] n012 = { n[0], n[1], n[2] };
			int[] n89A = { n[8], n[9], n[10] };
			// 8,9,10 を標準と比較
			switch(weigh(n012, n89A)) {
			case LEFT:
				// 8,9,10 の方が軽い
				// 8 と 9 を比較し、差があれば軽い方が正解。同じなら 10 が正解。
				switch(weigh(n[8], n[9])) {
				case LEFT:
					return 9;
				case RIGHT:
					return 8;
				default:
					return 10;
				}
			case RIGHT:
				// 8,9,10 の方が重い
				// 8 と 9 を比較し、差があれば重い方が正解。同じなら 10 が正解。
				switch(weigh(n[8], n[9])) {
				case LEFT:
					return 8;
				case RIGHT:
					return 9;
				default:
					return 10;
				}
			default:
				// 8,9,10 は標準と同じ重さ
				// 11 を標準と比較し、差があれば 11 が正解。同じなら残りの 12 が正解。
				switch(weigh(n[0], n[11])) {
				case LEFT:
				case RIGHT:
					return 11;
				default:
					// 重いか軽いかわからない唯一のケース
					return 12;
				}
			}
		}
	}
	
	/**
	 * 重さを比較した結果
	 */
	private enum Balance {
		LEFT,    // 左が重い
		RIGHT,   // 右が重い
		EQUAL;   // 同じ
	}
	
	/**
	 * 重さを比較した結果を返します。
	 * @param left 左の玉(の重さ)
	 * @param right 右の玉(の重さ)
	 * @return 結果
	 */
	private static Balance weigh(int left, int right) {
		if (left > right) {
			return Balance.LEFT;
		} else if (left < right) {
			return Balance.RIGHT;
		} else {
			return Balance.EQUAL;
		}
	}
	
	/**
	 * 重さを比較した結果を返します。
	 * @param left 左の玉(の重さ)の配列
	 * @param right 右の玉(の重さ)の配列
	 * @return 結果
	 */
	private static Balance weigh(int[] left, int[] right) {
		return weigh(sum(left), sum(right));
	}
	
	private static int sum(int[] n) {
		int sum = 0;
		for (int i = 0; i < n.length; i++) {
			sum += n[i];
		}
		return sum;
	}
}

13個全ての玉について、軽い場合と重い場合のどちらでも正しく判定できるか確認。

import java.util.Arrays;
import java.util.function.ToIntFunction;

public class TestMain {
	public static void main(String[] args) {
		int size = 13;
		for (int i = 0; i < size; i++) {
			test(FindAnomaly::findAnomaly13, size, i, true);
			test(FindAnomaly::findAnomaly13, size, i, false);
		}
	}
	
	private static void test(ToIntFunction<int[]> findAnomaly, int size, int anomaly, boolean heavy) {
		int[] balls = new int[size];
		Arrays.fill(balls, 1);
		balls[anomaly] = heavy ? 2 : 0;
		
		System.out.printf("%s : ", Arrays.toString(balls));
		int result = findAnomaly.applyAsInt(balls);
		if (result == anomaly) {
			System.out.printf("result=%d, Success%n", result);
		} else {
			System.out.printf("result=%d, Failure%n", result);
			throw new RuntimeException();
		}
	}
}

OKですね。

[2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=0, Success
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=0, Success
[1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=1, Success
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=1, Success
[1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=2, Success
[1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=2, Success
[1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=3, Success
[1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1] : result=3, Success
[1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1] : result=4, Success
[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1] : result=4, Success
[1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1] : result=5, Success
[1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1] : result=5, Success
[1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1] : result=6, Success
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1] : result=6, Success
[1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1] : result=7, Success
[1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1] : result=7, Success
[1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1] : result=8, Success
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1] : result=8, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1] : result=9, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1] : result=9, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1] : result=10, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1] : result=10, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1] : result=11, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1] : result=11, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] : result=12, Success
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0] : result=12, Success

タッチタイピングの標準配列は明らかにおかしい

タッチタイピングでキーボード下段左の "Z" "X" "C" キーを打つ指は、Wikipediaにも書かれているように、それぞれ小指、薬指、中指であるのが標準であるらしい。どのタッチタイピング教材でも大抵こうなっている。

ただ、これは人体の構造上不自然なので、一つずつずらしてそれぞれ薬指、中指、人差し指で打った方が良い。*1

キーボードは肩幅よりも狭いので、キーを打つときは肘を外に広げ、手は内側に向けたほうが楽だ。右手の担当範囲はきちんとこういう手の置き方に適した配置になっており、キーボードの上段では内側に、下段になるにつれ外側に担当するキーが配置されている。そのため、標準の担当範囲でも人差し指でホームポジションの J から M キーに移る時のように、指を自然に曲げるだけで下段のキーを打つことができる。

手は左右対称なのだから、左手側も同様にキーボード上段では内側に、下段では外側に担当範囲が広がっているべきなのに、なぜか標準とされる担当範囲では逆の向きになっている。楽な姿勢でキーを打つためには、右手と同様に、例えば人差し指ならホームポジションの F からそのまま指を曲げて C キーを打つべきで、C を中指が担当するのは不自然だ。X と Z も同様で、ホームポジションからそのまま指を曲げて打てる中指、薬指がそれぞれ担当したほうが良い。標準とされる担当範囲で打とうとすると、左腕は不自然に脇を締め、肩を前にせり出させ、手が外側に向くようにしてキーを打たなければならない*2。そうした姿勢は不自然で、疲れのもとだ。このような不自然な姿勢を暗に強要するタイピング方式は明らかにおかしい。

瞬間的な速度や効率だけを考えれば、担当するキーの数を均している標準の配列の方が良いのかもしれない。しかし、一日中キーボードに指を置いているような人種は、標準配列でタイピングするのは健康のために止めたほうが良いと思う。

*1:ここの2つ目に記載されているような配列

*2:そうでなければ手首の曲げ方が不自然になり、そっちに負担がかかる

終了条件をつけた Stream を作成する

たとえば正規表現で文字列をスキャンし、マッチした部分文字列からなる Stream を作成したいとする。この Stream は、マッチしなくなったら終了する必要があるが、どうするか?

Stream を簡単に作成できる Stream#generate() だと無限のストリームしか作れず、「条件にマッチしたら終了させる」ことができない。以下のようにすると、当然のようにプログラムが終了しない。

// 文字列に含まれる数値の合計を表示
public static void main(String[] args) throws Exception {
    String s = "1 2 3 4 5 6 7 8 9 10";

    int sum = scannedStream(Pattern.compile("\\d+"), s)
        .mapToInt(Integer::valueOf)
        .reduce(0, Integer::sum);
    
    // "sum: 55" と表示させたい
    System.out.println("sum: " + sum);
}

/** 
 * pattern に一致した部分文字列のストリームを返す
 */
private static Stream<String> scannedStream(Pattern pattern, String target) {
    Matcher m = pattern.matcher(target);    
    return Stream.generate(() -> m.find() ? m.group() : null).filter(s -> s != null);
}

Ruby の take_while のような機能が欲しいところだけど、Java の Stream には無い。

ここで、いったん全部をマッチさせて List に入れておくなどの妥協案をとらず、あくまでもストリーム処理したい場合は、以下のように行う必要があるっぽい。

  1. 終了条件にマッチしたら hasNext() が false になる Iterator を作成する
  2. Splitrators.spliteratorUnknownSize()Iterator から Splitrator を作成する
  3. StreamSupport.stream() で Spliterator から Stream を作成する

上の scannedStream の修正版は、以下のようになる。

private static Stream<String> scannedStream(Pattern pattern, String target) {
    Matcher m = pattern.matcher(target);
    
    Iterator<String> it = new Iterator<String>() {
        @Override
        public boolean hasNext() {
            return m.find();
        }

        @Override
        public String next() {
            return m.group();
        }
    };
    
    return StreamSupport.stream(
            Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED),
            false);
}

この文字列マッチの場合はそもそも並列実行できないので、あまり Stream の有り難みはないけど。

参考情報

JDK8の java.util.stream のパッケージドキュメントに大体書いてある。

https://docs.oracle.com/javase/jp/8/api/java/util/stream/package-summary.html

あと、この辺が詳しい。

http://enterprisegeeks.hatenablog.com/archive/category/Java8

Windows でも capybara-webkit でテストしたい

会社でも Capybaracapybara-webkit を使った integration test をしたいので、Windows 7 で環境を作ってみる。

基本的にここの手順に従っているが、左の通りにしなくても良いようなので、以下の手順で行った。

RubyとDevKitのインストール

RubyInstaller.org から、Ruby 2.0 の最新版と対応するDevelopment Kit*1をダウンロード。

Ruby は C:\Ruby200 などにインストール。途中で環境変数PATHにRubyのbinフォルダを追加するオプションがあるので、チェックしておく。

Development Kit は C:\DevKit などに展開。インストール手順はGitHubInstallation Instructions のページにあるが、コマンドプロンプトで以下のようにする。

pushd C:\DevKit
ruby dk.rb init
ruby dk.rb install

Qtのインストール

Qt project のページから Qt 4.8 系で "MinGW" と書いてあるインストーラ*2をダウンロードし、インストールする。

インストール中、"This package requires MinGW with GCC 4.8.2 .... Please specify a directory ...." のようなメッセージが出るので、入力欄に Development Kit のフォルダの中にある mingw フォルダを指定する。例えば、 C:\DevKit\mingw など。

インストールしたら、Windows環境変数PATHにQtのインストール先のbinフォルダを追加する。例えば C:\Qt\4.8.6\bin など。

更に、Qtのインストール先の mkspecs\win32-g++\qmake.conf の63行目あたりにある

QMAKE_LFLAGS		=

を、以下のように変更する。

QMAKE_LFLAGS		= -static-libgcc -static-libstdc++

公式の手順ではQMAKE_RCCも変更するようにあるが、最初から変更されているようだった。

capybara-webkit のインストール

ここまで準備したら、コマンドプロンプトを新しく開き、

gem install capybara-webkit

で、普通にインストールされる。

Temporarily enhancing PATH to include DevKit...
Building native extensions.  This could take a while...
Successfully installed capybara-webkit-1.1.1
Parsing documentation for capybara-webkit-1.1.1
Installing ri documentation for capybara-webkit-1.1.1
1 gem installed

確認

以下のようなテストクラスを作って試してみた。

# coding: utf-8

require 'capybara-webkit'
require 'test/unit'

class GoogleSearchTestCase < Test::Unit::TestCase
  include Capybara::DSL

  def setup
    # Capybara-WebKit を使用
    Capybara.current_driver = :webkit
    Capybara.javascript_driver = :webkit
    # 外部のWebアプリが対象のため、サーバは起動しない
    Capybara.run_server = false
    # User-Agent を変更したい場合は、以下のようにする
    # page.driver.header('user-agent', 'User-Agent文字列')
  end

  def teardown
    Capybara.use_default_driver
  end

  def test_search
    # Google日本のトップページを表示
    visit 'http://www.google.co.jp/'

    # スクリーンショットを取得
    save_screenshot 'before.png'

    # q という名前の項目に「テスト」と入力
    fill_in 'q', :with => 'テスト'

    # 「Google 検索」というラベルのボタン/リンクをクリック
    click_on 'Google 検索'

    # 検索結果件数部分を取得
    stats = find('#resultStats').text.scan(/\d+(?:,\d+)*/)

    # 検索結果画面のスクリーンショットを取得
    save_screenshot 'after.png'

    # 結果件数の取得と、規定件数を超えたことの検証
    assert_operator stats.length, :>=, 1, '結果件数が取得できません'
    assert_operator stats[0].gsub(/\D/, '').to_i, :>, 10_000_000, '結果件数が不足しています'
  end
end

実行*3

> ruby capybara-test.rb

Run options:

# Running tests:

Finished tests in 2.405000s, 0.4158 tests/s, 0.8316 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.0.0p451 (2014-02-24) [i386-mingw32]

スクリーンショットもちゃんと取れている。

これは簡単で良いですね。

*1:以前 Nokogiri のインストールが64bit版のDevelopment Kitではうまくいかなかったため、RubyもDevelopment Kitも32bit版を使用。capybara-webkit も32bit版でないとダメかどうかは不明

*2:Qt libraries 4.8.x for Windows (MinGW 〜) みたいの

*3:サジェスト機能で出てくるオーバーレイ要素のせいでたまにテストに失敗するが、フレームワークとしては動作に問題ない

Excelでタイムラインチャートを作成

積み上げ横棒グラフで最初のデータ系列を透明にすると、それっぽいのが出来るようで。

Excel でデータをガント チャートで表示する - Office サポート

Webサーバのログからリクエストごとの生存期間をグラフにするとか、そういう場合にお手軽ですね。

楽天の宣伝メールに対するGmailフィルタの設定方法

楽天のショップなどから来る宣伝メールは、fromアドレスが注文確認や発送連絡と同じなので、普通にフィルタを作成すると必要なメールまでフィルタされてしまう。

これに対しては、Gmailなら以下のように「含む」に「配信停止」を設定したフィルタを作成すれば一網打尽にできる。「特定電子メール法」により広告宣伝メールには配信停止手続き(オプトアウト)が可能である旨を記載する必要があるため、この手の文言はかならず含まれているからだ。

注文確認などの必要なメールはオプトアウト方法表示義務の対象外で、普通はこうした文言は含まれていないため、区別が可能になる。

いちいちオプトアウトしたり、注文の度に宣伝OKチェックを外したりする作業は面倒なので、フィルタして勝手に見えなくなるようにした方が健康に良いですね。