MATz Tech

webの技術的なことを中心にいろいろと

ハッシュの配列をユニークにするPerlモジュール作りました

はい、前回言ってたモジュール公開しました。
metacpan.org

CPAN Authorの登録や、minilコマンド使ってのモジュール公開など、
初めてでしたが比較的やりやすかったです^^

ただ何箇所か詰まった部分もあるので、そこはPerl入学式の方に聞いて助けてもらいました。ご協力ありがとうございました!
なお、「Hash::Uniqueという名前は空間名を占有しすぎ」というご指摘もいただいたので、次回からは気をつけたいと思います。


モジュール自体は、もともとPHPで作ってた関数をPerlに移植した形です。
連想配列(Perlではハッシュの配列)を、指定したキーで一意にするという関数です。
まあ、同じようなモジュールは既にCPANにありそうですね^^;

公開したモジュールのソースはCPANから見れるので、ここでは元となったphpのソースを載せておきます。

<?php
function getUniqueAssociativeArray($arr, $key){
    $tmp = array();
    $resultArr = array();

    foreach( $arr as $value ){
        if(!in_array( $value[$key], $tmp)) {
            array_push($tmp, $value[$key]);
            array_push($resultArr, $value);
        }
    } 

    return $resultArr;
}   

$people = array(
   array('id' => 1, 'name' => '佐藤'),
   array('id' => 2, 'name' => '鈴木'),
   array('id' => 3, 'name' => '田中'),
   array('id' => 4, 'name' => '佐藤')
);

$peopleUnique = getUniqueAssociativeArray($people, "name");
var_dump($peopleUnique);
?>


結果

array(3) {
  [0]=>
  array(2) {
    ["id"]=>
    int(1)
    ["name"]=>
    string(6) "佐藤"
  }
  [1]=>
  array(2) {
    ["id"]=>
    int(2)
    ["name"]=>
    string(6) "鈴木"
  }
  [2]=>
  array(2) {
    ["id"]=>
    int(3)
    ["name"]=>
    string(6) "田中"
  }
}

「Perl入学式」行ってきました

今回行ってきたのはこちら!

www.perl-entrance.org

Perlの勉強会です。
勉強会自体は「入学式」というだけあって、PG初心者や未経験者に優しい内容でした。

個人的にはPerlは大学の授業以来ということで、多分6年ぶりくらい。
当時は、DNAの塩基配列Perlで解析するという、なかなかマニアックなことをやっておりました。

ほとんど忘れてると思ったものの、シンタックスPHPに似てると感じたので割とサクサク書けてます。

今はCPANに公開するためのモジュールを作ってるので、できたらまた報告します。

Netbeansの▶︎ボタンでphpファイルをコマンド実行する

どうも、php書くときはいつもNetBeans使ってます。

バッチ処理書くときとか、いちいちコマンドライン立ち上げて実行するのも面倒なので、いつもこうやって動作確認してますよ、という話。

① プロジェクトの構成を設定

まずNetBeansを立ち上げて、「実行」から「プロジェクトの構成を設定」を選びます。
最初は「デフォルト」にチェックが入っているはずです。ここでは「カスタマイズ」を選びましょう。

f:id:MATz:20170506225639p:plain


② 新規構成を作成

構成は「デフォルト」になっているので、「新規」ボタンをクリックしましょう。
構成名は任意でわかりやすい名前を入れればOKです。

f:id:MATz:20170506230359p:plain


③ 実行方法を選択

次に、実行方法を選びます。
ここでは、「スクリプト」を選択してください。

f:id:MATz:20170506230612p:plain


PHPファイルを選択

そして、開始ファイルを選びます。
ここでは、実行したいPHPファイルを選択してください。

f:id:MATz:20170506230920p:plain


⑤ 設定一覧

最終的には、こんな感じになります。
今回PHPインタプリタはデフォルトのままにしていますが、デフォルトのチェックを外せば任意のものを選ぶこともできます。

f:id:MATz:20170506231112p:plain


⑥ ▶︎ボタンで実行

地球マークの左側のプルダウンから、作成した構成を選択できます。
選択後、緑の▶︎ボタンをクリックすれば実行されます。
実行結果は、下部の出力ウィンドウに出力されます。

f:id:MATz:20170506231448p:plain


引数とPHPオプションの設定

ちなみに、引数とPHPオプションも簡単に設定できます。

f:id:MATz:20170506234156p:plain

f:id:MATz:20170506234256p:plain

jsとcanvasで画像の容量を落とす〜その2〜

前回のだと上手くcanvasに描画されない場合が多かったので、やり直してみました。


htmlはこちら。

<form action="" method="post" id="imageForm">
    <img src="" id="preview" />
    <canvas id="canvas"></canvas>
    <input type="file" id="imageSelect" onChange="imgDisp();" />
    <input type="button" onClick="imgUpload();" value="アップロード" />
</form>



まず、ファイル選択時の処理。

選択した画像を、img要素に原寸サイズで表示します。
原寸サイズで表示して全体のレイアウト崩れるの嫌なら、hiddenにすればOKです。

function imgDisp() {
    var file = $("#imageSelect").prop("files")[0];
                
    //画像ファイルかチェック
    if (file["type"] != "image/jpeg" && file["type"] != "image/png" && file["type"] != "image/gif") {
        alert("jpgかpngかgifファイルを選択してください");
        $("#imageSelect").val('');
        return false;
    }
                
    var fr = new FileReader();
    fr.onload = function() {
        //選択した画像をimg要素に表示
        $('#preview').attr("src", fr.result);                        
    };
    fr.readAsDataURL(file);
} 



次に、アップロードボタンクリック時の処理。

img要素から画像をimageオブジェクトとして取得して、canvas要素に描画します。
描画時にサイズ縮小すると、これだけで結構容量下がります。
今回は描画時の横幅を800pxにして、縦横比保ったまま縮小してます。

あ、ちなみにcanvas要素もhidden入れたら非表示にできます。


そして、canvasデータの画質を落としてajaxでPOST送信します。

ここではtoDataURLでバイナリ化しますが、この時に第二引数で画質が指定できるんです。
0.0〜1.0の数値を指定して、好きな画質に落としましょう。

今回は、100KB以下に落とします。
まず最初は、第二引数を指定せずオリジナル容量(画質を落としていない場合の容量)を取得します。
次に、取得したオリジナル容量から100KB以下に落とすための数値を算出して、指定しています。


最後に、ajaxでPOST送信したら完了!

function imgUpload() {
    //加工後の横幅を800pxに設定
    var processingWidth = 800;            
            
    //加工後の容量を100KB以下に設定
    var processingCapacity = 100000;                               

    //ファイル選択済みかチェック
    var fileCheck = $("#imageSelect").val().length;
    if (fileCheck === 0) {
        alert("画像ファイルを選択してください");
        return false;
    }
                
    //imgタグに表示した画像をimageオブジェクトとして取得
    var image = new Image();
    image.src = $("#preview").attr("src");

    var h;
    var w;

    //原寸横幅が加工後横幅より大きければ、縦横比を維持した縮小サイズを取得
    if(processingWidth < image.width) {
        w = processingWidth;
        h = image.height * (processingWidth / image.width);

    //原寸横幅が加工後横幅以下なら、原寸サイズのまま
    } else {
        w = image.width;
        h = image.height;
    }

    //取得したサイズでcanvasに描画
    var canvas = $("#canvas");
    var ctx = canvas[0].getContext("2d");
    $("#canvas").attr("width", w);
    $("#canvas").attr("height", h);
    ctx.drawImage(image, 0, 0, w, h);                          

    //canvasに描画したデータを取得
    var canvasImage = $("#canvas").get(0);

    //オリジナル容量(画質落としてない場合の容量)を取得
    var originalBinary = canvasImage.toDataURL("image/jpeg"); //画質落とさずバイナリ化
    var originalBlob = base64ToBlob(originalBinary); //画質落としてないblobデータをアップロード用blobに設定
    console.log(originalBlob["size"]);

    //オリジナル容量blobデータをアップロード用blobに設定
    var uploadBlob = originalBlob;                    

    //オリジナル容量が加工後容量以上かチェック
    if(processingCapacity <= originalBlob["size"]) {
        //加工後容量以下に落とす
        var capacityRatio = processingCapacity / originalBlob["size"];
        var processingBinary = canvasImage.toDataURL("image/jpeg", capacityRatio); //画質落としてバイナリ化
        uploadBlob = base64ToBlob(processingBinary); //画質落としたblobデータをアップロード用blobに設定
        console.log(capacityRatio);                        
        console.log(uploadBlob["size"]);
    }

    //アップロード用blobをformDataに設定
    var form = $("#imageForm").get(0);
    var formData = new FormData(form);                    
    formData.append("selectImage", uploadBlob);

    //formDataをPOSTで送信
    $.ajax({
        async: false,
        type: "POST",
        url: "upload.php",
        data: formData,
        dataType: "text",
        cache: false,
        contentType: false,
        processData: false,
        error: function (XMLHttpRequest) {
            console.log(XMLHttpRequest);
            alert("アップロードに失敗しました");
        },
        success: function (res) {
            if(res !== "OK") {
                console.log(res);
                alert("アップロードに失敗しました");
            } else {
                alert("アップロードに成功しました");
            }
        }
    });
}

// 引数のBase64の文字列をBlob形式にする
function base64ToBlob(base64) {
    var base64Data = base64.split(',')[1], // Data URLからBase64のデータ部分のみを取得
          data = window.atob(base64Data), // base64形式の文字列をデコード
          buff = new ArrayBuffer(data.length),
          arr = new Uint8Array(buff),
          blob,
          i,
          dataLen;
    // blobの生成
    for (i = 0, dataLen = data.length; i < dataLen; i++) {
        arr[i] = data.charCodeAt(i);
    }
    blob = new Blob([arr], {type: 'image/jpeg'});
    return blob;
}            



upload.phpは、シンプルにPOSTで受けた画像をアップロードしてるだけです。
バイナリ化するときにjpgで指定してるので、保存するファイルの拡張子はjpg固定です。

<?php
try{
    if(!move_uploaded_file($_FILES["selectImage"]["tmp_name"], 'test.jpg')){
        throw new Exception('画像ファイルアップロードエラー!');
    }
    
    echo 'OK';
} catch (Exception $ex) {
    echo $ex->getMessage();
}
?>



という訳で前回からの大きな変更は、canvas描画のタイミングをファイル選択時からアップロードボタンクリック時に移したことです。
どうやらinput type="file" からimg要素への表示と、img要素からcanvasへの描画は、分けて処理しないと上手くいかない模様。
FileReaderオブジェクトの、onload プロパティとreadAsDataURL メソッドの使い方なのかなあ…
でも、onload プロパティとreadAsDataURL メソッドの後に、canvas描画やっても一緒だったしなあ。。。


ちなみに、バリデーション的にpngもOKにはしてますが、jpgに変換してるので透過部分は黒くなってしまいます^^;

次回はこの辺解決してみるのもありかな。


<参照>
‘input type=file’から’canvas’への転写(画像の引き伸ばし対処) – GUNMA GIS GEEK
toDataURL() メソッド - Canvasリファレンス - HTML5.JP
canvasで描いた絵をバイナリ形式でサーバーにPOST送信する方法 | while(isプログラマ)

「そうだ Go、京都。」行ってきました

はい、また勉強会に行ってきました。

developer.hatenastaff.com

今回はGo言語です。

Go言語の経験は、IntelliJ IDEA入れてHello world出したくらいなんですが、
もうかれこれ5年ほどphperやってる身としては、Goやっといた方がいいかなあと思い参加。


自分的なまとめとしては以下の通り

・標準ライブラリが充実してるのはgood

・また、自分でライブラリ作ってる方もいたので面白そう

シンタックスはシンプルなので可読性高そう

・Goの入門としてはA Tour of Goが良いらしい

A Tour of Goが一通りできたらこちらの本やってみると良さげ
www.amazon.co.jp

だいぶ良書のようです^^


そして、Goそのものについてではないのですが、
はてな社員の方がプライベートでも毎日コードを書くコツを仰ってました。

・休日のうちに平日やるissueを作っておく

・一日で複数のissueが解決できたら、git commit のdate変更してしまうのもあり

・インプット(本による学習や問題解決)とアウトプット(ブログやLT)を呼吸するように行う

モチベーション保って毎日少しずつ進めるためには、楽しむことが大切。
インプットとアウトプットを、呼吸するように自然にできるようになれば楽しくなってくる。

なるほど
私もそんな風にスキルアップしていきたいなー


最後に、はてなの皆さん今回はありがとうございましたm(_ _)m
懇親会のピザ美味しかったですo(≧▽≦)o

MEANスタックとMERNスタック

先日、Node.jsの勉強会に行ってきたので覚え書き。

今までJavaScriptはクライアントサイドのjQueryくらいしか経験がなく、サーバ側は触ったことなかったんですが、たまたま近所で勉強会やってたんで行ってきました。

いろんな話聞けたのですが、その中で印象深かったMEANスタックとMERNスタックについてです。

「MEAN」「MERN」というのは、所謂「XAMPP」みたいな言語やDBを組み合わせた開発環境のアーキテクチャです。

MEAN

M:MongoDB

ドキュメント指向のDBで、RDMSではなくNoSQLに分類されます。

E:Express

Node.jsのMVCフレームワークです。

A:AngularJS

JavaScriptのクライアントサイドのMVWフレームワークです。

N:Node.js

サーバーサイドのJavaScript環境です。

MERN

M:MongoDB

E:Express

R:React.js

Facebook製のUI構築ライブラリです。フレームワークではありません。

N:Node.js



結局、AngularJSとReact.jsどっちを使うねんということです。

ちなみに、Google Trendsはこんな感じでした。

f:id:MATz:20170410003853p:plain

f:id:MATz:20170410004351p:plain

う〜ん、圧倒的!

色々調べてみたら、チャット機能みたいにリアルタイム性が強いならReact.jsが向いてるようです。
そうでなければAngularJSが良さげです。

また、AngularJSはhtml主体で、React.jsはJavaScriptが主体。
私のようにjQueryに慣れてる場合は、AngularJSの方が取っ付きやすそうですね。

勉強会でも、AngularJSオススメの人がたくさんいました。

というわけで、Node.js始めるならMEANからやってみようかなと思っている今日この頃。