yumetodoの旅とプログラミングとかの記録

旅や登山の記録やプログラミング関連の話とかフリーソフト紹介とか

Google Apps Scriptで配列を返す関数を作る:居住市区町村の文字列正規化

みらい研究室実行委員会の6月のイベントのアンケート解析で、住んでいる区市町村名を問う項目の集計を備え付けのifやらleftやらmid関数でやりくりするのは限界があったので、
Google Apps Script(通称GAS、ExcelVBAみたいな位置で言語的にはJavaScript)で関数を作りました。

ただ、データ数が2000件近くあるので全セルにこの作った関数を呼ぶ式を打つと

エラー: Script invoked too many times per second for this Google user accout.

といった具合に怒られるので対策が必要でした。

Google スプレッドシートでカスタム関数を多くのセルで呼び出しエラーが表示される場合、配列を返す関数(配列数式)に変更する。または、新しいスプレッドシートを利用。 | すぐに忘れる脳みそのためのメモ
を参考に配列を返す関数を作ります。

最も注意するべきなのは、自作関数の引数にある適当な範囲を渡した時の引数は、「stringのArrayのArray」ということです。断じて「stringのarray」ではありません。

function remove_prefectures_name(str){
	if(typeof str !== "string") return "error: unexpected input.";
	var buf = "";
	var tmp = str.charCodeAt(2);
	if("道".charCodeAt(0) === tmp || "都".charCodeAt(0) === tmp || "府".charCodeAt(0) === tmp || "県".charCodeAt(0) === tmp){
		buf = str.slice(3);
	}
	else if("県".charCodeAt(0) === str.charCodeAt(3)){
		buf = str.slice(4);
	}
	return (buf.length === 0) ? str : buf;
}
function match_in_array(arr, str){
	var is_matched = false;
	for(var i = 0; !is_matched && i < arr.length; ++i){
		is_matched = (arr[i] === str);
	}
	return is_matched;
}
function add_City(str){
	var case_do_not_add_City = ["市", "区", "町", "村", "県", "都", "道", "府", "県"];
	return (match_in_array(case_do_not_add_City, str.slice(-1))) ? str : str + "市";
}
function normalize_adress_helper(main_input, case1_input, case4_input){
	if(typeof main_input !== "string" || typeof case1_input !== "string" || typeof case4_input !== "string"){
		throw new TypeError("main_input(type:" + typeof main_input + ")unexpected input.");
	}
	var re = "";
	switch(main_input){
	case "1.東京23区内":
		re = (case1_input === "無記入" || case1_input === "") ? "無記入" : "1-" + case1_input + "区";
	break;
	case "4.その他":
		var do_not_edit = ["アメリカ", "韓国", "korea"];
		re = (case4_input === "無記入" || case4_input === "") ? "無記入" :  (match_in_array(do_not_edit, case4_input)) ? case4_input : add_City(remove_prefectures_name(case4_input));
	break;
	default:
		re = main_input;
	}
	return re;
}
function normalize_adress(main_input, case1_input, case4_input){
	if(typeof main_input === "string" && typeof case1_input === "string" && typeof case4_input === "string"){
		return normalize_adress_helper(main_input, case1_input, case4_input);
	}
	else if(Array.isArray(main_input) && Array.isArray(case1_input) && Array.isArray(case4_input) && main_input.length == case1_input.length && case1_input.length == case4_input.length){
		var re = [];
		for(var i = 0; i < main_input.length; ++i){
			re.push(normalize_adress_helper(main_input[i][0], case1_input[i][0], case4_input[i][0]));
		}
		return re;
	}
	else{
		throw new TypeError("main_input(type:" + typeof main_input + ")unexpected input.");
	}
}

盛大にぶっ壊れたパスワード付きzipを解析してみる

はじめに

まあ何回か書いている通り、みらい研究室実行委員会のWebページ担当なわけですが、何故にや知らん、理大祭会議なるものに出席することになりまして、それが理大祭、つまい学園祭に出展するために出なければならんとのことだからやむを得ない。

で、出て、資料もらって、資料がWebでも上がってるというから、「見てみるか」となったわけですね。これが元凶です

解凍できない・・・だと!?

私はWindows使いなんですが、標準の解凍機能はお粗末なんで、CubeICEを愛用しています。ところが

ERROR: エラーを特定できません

いやいや、どういうことだってばよ?よろしい、ならば7-zip先生に・・・

7-zip error

なんやって・・・。ねんのためWindows標準機能で・・・

ms error1 ms error 2

パスワード違うだぁ!?こういう時はコマンドラインで・・・

わぁ・・・。なんかすごいエラーだけどとりあえず解凍できたぞ

ちょっとzipを解析するか

まずzipとはどういうデータ構造かというと、いろんな情報とデータに分かれていて、これがファイルごとに存在します。ではその情報のうち、ヘッダーを見ていきましょう。

オフセットサイズ内容
04ローカルファイルヘッダのシグネチャ = 0x504B0304(PK\003\004)
42展開に必要なバージョン (最小バージョン)
62汎用目的のビットフラグ
82圧縮メソッド
102ファイルの最終変更時間
122ファイルの最終変更日付
144CRC-32
184圧縮サイズ
224非圧縮サイズ
262ファイル名の長さ (n)
282拡張フィールドの長さ (m)
30nファイル名
30+nm拡張フィールド

で、実際のバイナリエディタで開いたものがこちら。

binary zip
  1. CRC32が格納される32bit分が空にもかかわらず、汎用目的のflagの3bit目が1になっていない。
    →ほとんどの解凍ソフトで解凍に失敗
  2. ファイル名がUTF-8エンコードされているが、汎用目的のflagの11bit目が1になっていない。
    Macや最近のLinuxではこの場合UTF-8として扱うので文字化けが発生しないが、WindwosではShift-JISとして扱うので文字化けを引き起こす。
  3. 暗号化zipにはアーカイブデータの前に12byteの暗号ヘッダーが付加される。これの12バイト目に圧縮元のデータのCRC32値の上位8ビットが設定されるはずだが、上述の通りヘッダーのCRC32がぶっ壊れているため、一致しない。
    →ほとんどの解凍ソフトで解凍に失敗

で、こんなぶっ壊れたファイルを吐くソフトは一体何者だ

ちょっとわからない。__MACOSXとあるから、Macで作られたんだろうけど。

教訓

Google Apps Scriptで範囲から欠けた数を列挙する

Google Apps Scriptで範囲から欠けた数を列挙する

今回もまたみらい研究室実行委員会の3/19に行ったイベントのアンケート解析をしています。

で、委員みんなでアンケートの内容を私が作ったGoogleFormに打ち込んでデータ化して、それを私がいい感じにまとめて「解析は任せた」の投げつけるお仕事をしているわけです
GoogleFormで打ち込むためにアンケートに通し番号を付けてそれを入力してもらうんですが、順番ばらばらでみんな打ち込むので、どれが欠けてるかわかりにくくて仕方ないです。
というわけで、欠けた数をカンマ区切りの文字列で返すlist_missing_num関数を作りました。

function list_missing_num(numbers) {
    if(
        "undefined" !== typeof(numbers) && Array.isArray(numbers)
        && "object" === typeof(numbers[0]) && Array.isArray(numbers[0]) && "number" === typeof(numbers[0][0])
    ){
        var arr = [];
        var i;
        for(i = 0; i < numbers.length; ++i) arr.push(numbers[i][0]);//convert
        arr.sort(function(a, b){ return a - b; });//sort
        arr = arr.filter(function (x, i, self) { return self.indexOf(x) === i; });//unique
        var re = [];
        var push_back = function(min, max){
            for(var i = min; i <= max; ++i) re.push(i);
        }
        var j;
        for(i = 1, j = arr[0]; i < arr.length; j = arr[i], ++i){
            if(1 !== arr[i] - j){
                push_back(j + 1, arr[i] - 1);//push missing nums
            }
        }
        return (re.length === 0) ? "nothing" : re.join();
    }
    else{
        throw new TypeError("unexpected input. numbers:" + typeof(numbers) + " min:" + typeof(min) + " max:" + typeof(max));
    }
}

まあ特に説明することは無いですね。重複はfilterで無視してます。