Emotion Wave Tech Blog

福岡にあるエモーションウェーブ株式会社のエンジニアが書いています。

【javascript】2つの連想配列を比較し、重複するものを返す

目次

javascript連想配列を比較し、重複するものがないか、調べたい

どうも、高島です。

javascriptで2つの配列があります。 両方とも存在するデータを取り出すには、どうすればいいでしょうか?

普通の配列を比較するサンプルは、ネット上に結構あるのですが、連想配列の例があまり無かったので、記事を書きました。

早速ですが、filterの中でfilterを呼べば、可能です。

const ary1 = [
  {"keyno":"A1","room":"101"},
  {"keyno":"A1","room":"102"},
  {"keyno":"B1","room":"201"},
  {"keyno":"C1","room":"301"},
  {"keyno":"D1","room":"401"},
];


const ary2 = [
  {"keyno":"A1","room":"101"}, 
  {"keyno":"C3","room":"301"},
  {"keyno":"D1","room":"401"},
  {"keyno":"E1","room":"501"},
];

// 重複有り
var reList = ary1.filter(ary1row=>ary2.filter(
    ary2row=>
        ary2row.keyno === ary1row.keyno &&
        ary2row.room === ary1row.room).length > 0);
        
console.log("両方とも存在するデータ");
console.log(reList);

結果はこうです。

両方とも存在するデータ
[
  { keyno: 'A1', room: '101' },
  { keyno: 'D1', room: '401' }
]

filter内はどんな動きなのか?

実際はどんな処理がされているのでしょうか? console.logを埋め込んで確認してみます。

// 内部の動作を確認してみる
ary1.filter(ary1row=>{
    console.log("ary1row.room=" + ary1row.room);
    ary2.filter(
        ary2row=>{
            console.log("    ary2row.room=" + ary2row.room);
        })
});

結果はこうなりました。

ary1row.room=101
    ary2row.room=101
    ary2row.room=301
    ary2row.room=401
    ary2row.room=501
ary1row.room=102
    ary2row.room=101
    ary2row.room=301
    ary2row.room=401
    ary2row.room=501
ary1row.room=201
    ary2row.room=101
    ary2row.room=301
    ary2row.room=401
    ary2row.room=501
ary1row.room=301
    ary2row.room=101
    ary2row.room=301
    ary2row.room=401
    ary2row.room=501
ary1row.room=401
    ary2row.room=101
    ary2row.room=301
    ary2row.room=401
    ary2row.room=501

ary1をループして、さらにその中でary2をループしていることがわかります。 filterを使えば、ループを書かなくて済む、というわけです。

冒頭の重複を取り出す処理に話を戻します。

ary2.filter関数が、ary1rowとary2rowを比較し、一致する行を返してくれます。 この部分ですね。

ary2.filter(
    ary2row=>
        ary2row.keyno === ary1row.keyno &&
        ary2row.room === ary1row.room)

ary2.filterは、比較条件がtrueならば、データを返します。 もしfalseなら何も返しません。

データを返ってきたか否かは、lengthで判断します。

もうちょっとわかりやすく、returnを省略せずに、lengthでのtrue/false判定を分離して書いてみます。

var reList = ary1.filter(ary1row=>ary2.filter(
    ary2row=>
        ary2row.keyno === ary1row.keyno &&
        ary2row.room === ary1row.room).length > 0);

↓↓↓↓↓↓

var reList = ary1.filter(ary1row=>{
    var reAry2row = ary2.filter(
        ary2row=>{
            return (ary2row.keyno === ary1row.keyno &&
                    ary2row.room === ary1row.room)
        });

    return reAry2row.length > 0 ;
});

ary1rowとマッチしたreAry2row が存在すれば、length > 0 、ですのでtrueで返します。 ary1.filter()はtrueとなったary1rowをreListに返します。

こうして重複したデータのみが reListにセットされます。

2つの配列を比較し、重複しないものを返す

では、ary1に存在するがary2に存在しないデータは?

これは簡単です。結果が0件のものですね。 length === 0 に変えます。

// 重複しない
reList = ary1.filter(ary1row=>ary2.filter(
    ary2row=>
        ary2row.keyno === ary1row.keyno &&
        ary2row.room === ary1row.room).length === 0);

console.log("ary1に存在するがary2に存在しないデータ");
console.log(reList);
ary1に存在するがary2に存在しないデータ
[
  { keyno: 'A1', room: '102' },
  { keyno: 'B1', room: '201' },
  { keyno: 'C1', room: '301' }
]

(おまけ)自分自身の配列内で重複を取り出す

これまでは、異なる配列同士を比較しましたが、 今度は自分自身と比較すればいいです。

const ary1 = [
  {"keyno":"A1","room":"101"},
  {"keyno":"A1","room":"102"},
  {"keyno":"B1","room":"201"},
  {"keyno":"C1","room":"301"},
  {"keyno":"A1","room":"101"},
  {"keyno":"C1","room":"301"},
  {"keyno":"D1","room":"401"},
];


// 重複有り
var reList = ary1.filter((ary1row,ary1idx,selfary)=>selfary.filter(
    ary2row=>
        ary2row.keyno === ary1row.keyno &&
        ary2row.room === ary1row.room).length > 1);
        
console.log("重複するデータ");
console.log(reList);
重複するデータ
[
  { keyno: 'A1', room: '101' },
  { keyno: 'C1', room: '301' },
  { keyno: 'A1', room: '101' },
  { keyno: 'C1', room: '301' }
]

filter() はcallback関数を引数にとります。 このcallback関数の第3引数は自分自身の配列です。 それと比較すればいいです。

まとめ

filter内でfilterを呼び、そこで比較すれば、二重ループを書く必要はなくなります。

最初にfilter見たときは訳わかりませんでしたが、理解するとスッキリ書けます。

欠点といえば、読むには慣れが必要なことでしょうか。

人が書いたものは、正直ちょっとわかりにくいことが多いです。

var reList = ary1.filter((x,y,z)=>z.filter(
    w=>
        w.keyno === x.keyno &&
        w.room === x.room).length > 1);

アロー関数は、こんな感じで略した変数名で書くことが多いので、変数名からは内容が推測できませんし、returnを省略して書かれると、初心者はコードが読めずに苦しんだりします。

もう慣れるしかないんでしょうけどね。

ファイルのmime type(content type)を確認するためのjavascript

目次

前書き

どうも、高島です。

ファイルの形式やファイルサイズを、ローカルでも簡単に確認できるWebサイトを作ってみました。

  • どうやって?

HTML5のFile APIを利用します。

File APIを使ってみる

1.ソースコードです。 これをコピーして、デスクトップにmimetype_check.htmlという名前で保存してください。

<!doctype html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>ファイルアップロード</title>
        <script
          src="https://code.jquery.com/jquery-1.12.4.min.js"
          integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
          crossorigin="anonymous"></script>
        <script>
        $(document).ready(function(){
            $("#btn_file_up").click(function () {

                var self = $(this).parents("form");

                //ファイル
                var file = self.find('#file_up').val();

                if (file !== "") {

                    //ファイルタイプ
                    filetype = self.find('#file_up').prop('files')[0].type;
                    //ファイルネーム
                    filename = self.find('#file_up').prop('files')[0].name;
                    //ファイルサイズ
                    filesize = self.find('#file_up').prop('files')[0].size;
                    filesize = filesize / 1000; // 単位をkbにする

                    $("#presult").html("mimetypeは、" + filetype + "です。<br>ファイルサイズは、" + filesize + "KBです。");
                }
            });
        });
        </script>
    </head>
    <body>
        <form action="" method="post">
            <p><input type="file" id="file_up" name="file_up" value=""></p>
            <p><input type="button" id="btn_file_up" name="btn_file_up" value="アップロード"></p>
            <p>ファイルを選んでアップロードボタンを押してください。<br>
               そのファイルのmimetypeとファイルサイズが表示されます。<br>
            </p>
            <p id="presult">
            </p>

        </form>
    </body>
</html>

2. 早速、保存したファイルをブラウザで開きます。

f:id:devew:20200220103106j:plain

3. 「ファイルの選択」から、ファイルを選んでアップロードしましょう。

f:id:devew:20200221162956j:plain

4.mime typeとファイルサイズが表示されました。

f:id:devew:20200220103503j:plain

mime typeはどこに設定されているの?

1.ちなみに、xlsではなく、xlsx形式だと、「application/vnd.openxmlformats-officedocument.spreadsheetml.sheet」と表示されます。これは、どこに設定されているのでしょうか?

2.windowsレジストリエディターを起動してみましょう。レジストリをいじるとOSが正常に起動しなくなります。ここから先は自信がある人だけ。windows検索窓で「regedit」と打って、レジストリエディターを起動します。

3. HKEY_CLASSES_ROOTの配下にたくさん拡張子が並んでいます。  f:id:devew:20200220104505j:plain

4. xlsやxlsxを見てみましょう。そこの「Content type」がそうです。

f:id:devew:20200220104555j:plainf:id:devew:20200220104603j:plain

5.つまり、ここの「Content type」に登録されていないと、HTML5のFile APIではファイル形式の判断が出来ません。 Officeなど、それを使用するソフトが正常にインストールされていないと、登録されていないことがあるようです。

あとがき

File APIは便利です。わざわざサーバに飛ばさなくても、クライアント側の処理で、チェックすることが出来ます。 例えばアップロード前のファイルサイズチェックとか。

ただ、ファイル形式チェックは、拡張子が無難かも知れません。 例えばgoogleスプレッドシートを利用していて、OfficeをインストールしていないPCで、アップロードすることもあるかも知れません。そのようなPCに、「Content type」が登録されているかは正直わかりません。

まさか、「Content type」がレジストリに登録されていたとはね。

C#でAngleSharpを使ってHTMLをパースする

こんにちは。中原です。

今回はC#でAngleSharpというのを利用してHTMLをパースしてみました。

HTMLパースすることなんてあまりないかもしれませんが、もしパースする機会があればAngleSharpを使ってみてください。
AngleSharpはこちら。
github.com

AngleSharpをインストールする

まずは、NuGetからAngleSharpを検索・選択して「インストール」。
f:id:devew:20200110162339p:plain f:id:devew:20200110162359p:plain

参照設定に追加されました。
f:id:devew:20200110162541p:plain

パースするHTMLを用意する

パースするHTMLファイルを用意します。
今回はこんなHTMLを作成しました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>HTML Parser Sample</title>
  </head>
  <body>
    <p id="id-p">ID=id-pの文字</p>
    <p class="class-p">class=class-pの文字1</p>
    <p class="class-p">class=class-pの文字2</p>
    <p class="class-p">class=class-pの文字3</p>
  </body>
</html>

Program.csに以下のusigを追加して、AngleSharpを使えるようにします。

using AngleSharp.Html.Parser;

それでは簡単なパースをしてみます。

idを指定しタグの値を取得する

// htmlファイルを読み込む
var html = File.ReadAllText("html_parser_sample.html", System.Text.Encoding.UTF8);

// HTMLParserのインスタンス生成
var parser = new HtmlParser();

// htmlをパースする
var doc = parser.ParseDocument(html);

// idを指定してElement取得
var idP = doc.GetElementById("id-p");
Console.WriteLine("GetElementByIdを使って[id-p]の値取得 : {0}", idP.TextContent);

実行結果

タグの値が取得できました。 f:id:devew:20200130163605p:plain

classを指定してタグの値を取得する

// htmlファイルを読み込む
var html = File.ReadAllText("html_parser_sample.html", System.Text.Encoding.UTF8);

// HTMLParserのインスタンス生成
var parser = new HtmlParser();

// htmlをパースする
var doc = parser.ParseDocument(html);

// classを指定してElementを取得
var classpList = doc.GetElementsByClassName("class-p");
foreach (var c in classpList)
{
    Console.WriteLine("GetElementsByClassNameを使って[class-p]の値取得 : {0}", c.TextContent);
}

実行結果

指定したクラスの値が全て取得できました。 f:id:devew:20200130163836p:plain

QuerySelectAllを使ってclassを指定してタグの値を取得する

// htmlファイルを読み込む
var html = File.ReadAllText("html_parser_sample.html", System.Text.Encoding.UTF8);

// HTMLParserのインスタンス生成
var parser = new HtmlParser();

// htmlをパースする
var doc = parser.ParseDocument(html);

// QuerySelectorAllでclassを指定してElementを取得
var elements = doc.Body.QuerySelectorAll("p.class-p");
foreach (var e in elements)
{
    Console.WriteLine("QuerySelectorAllを使って[class-p]の値取得 : {0}", e.TextContent);
}

実行結果

一つ前の実行結果と同じ結果が得られました。 f:id:devew:20200130164134p:plain

上記以外にも沢山機能はありますが、今回は上記3パターンを実装してみました。
また機会あればご紹介したいと思います。

全体のソースコード一式は以下に置きました。
github.com

Gmail自動削除の方法

目次

前書き

どうも、高島です。

gmailって、何もしないとどんどん貯まっていきます。

容量を食うので定期的に削除しようと思います。

googleカレンダーのメールやソーシャルサイトからのメールは、単なる通知メールですので、数日で消してしまっていいです。

これらを自動で消していこうと思います。

  • どうやって?

Google Apps Scriptを使います。 これでgmailのサービスにアクセスして、条件に合ったメールを 定期的にゴミ箱に移動させます。

  • 方法は?Gmail編。

gmailのフィルタ機能を使って、メール受信時に自動でラベルを付けます。

ラベルが付いたメールを毎日0時にゴミ箱に移動します。

Gmailにラベルを付けよう

1.まずは、消したいメールの法則性を掴みます。 googleカレンダーからの通知メールを消したいので、送信元のメアドで判断しましょう。

f:id:devew:20200115163616p:plain

2. gmailの「設定」を開きます。

f:id:devew:20200115161230p:plain

3. 「フィルタとブロック中のアドレス」を選択します。

4.フィルタ条件を作成します。

5. 「ラベルを付ける」から、新しいラベルを作ります。

f:id:devew:20200115163229p:plain

6.ラベルが作成されました。今後受信したメールには、このラベルが自動的に付くようになります。

7. ただし、今まで受信したメールにはラベルが付いていません。ラベルを付けてあげましょう。 受信トレイの検索窓に、フィルタ条件と同じものを打ち込みます。そして、「この検索条件に一致するすべてのスレッドを選択する」をクリックします。

8. 条件に一致したメールにラベルを付けてあげましょう。

スクリプト作成と、タイマートリガー設定

1. Google Apps Scriptを作成します。 今回はgoogleスプレッドシート上に乗っけます。 (こうすることで、削除したメールの一覧などをスプレッドシート上に出力することも可能です)

2.Googleスプレッドシートを開いたら、「ツール」→「スクリプトエディタ」を開きます。

3.スクリプトファイル名は〇〇.gsとなります。今回は「コード.gs」とします。

4. コードは以下のとおりです。

function myFunction() {
 
  //3日より前のGoogleカレンダー通知メールを削除する
  deleteMail(GmailApp.search('label:calendargoogle older_than:3d'));
}

function deleteMail(deleteThreads) {
 
  for (var i = 0; i < deleteThreads.length; i++) {
    deleteThreads[i].moveToTrash();
  }
}

解説

myFunctionは最初に呼び出される関数です(詳しくは後で)。 GmailApp.search('label:calendargoogle older_than:3d') は、ラベルが「calendargoogle」と付いた、3日前のメールを抽出しています。

deleteMail関数で、それらを受け取って、 moveToTrash()でゴミ箱に移動します。簡単ですね。

参考:https://developers.google.com/apps-script/reference/gmail/gmail-app

5. 虫さんのマークからデバッグも出来ますよ。 初回実行時は、プロジェクト名とか決める必要があるので、入力しましょう。

6. gmailにアクセスする許可が必要です。聞かれたら、画面の指示に従います。

7. 実行中。しばらく待ちましょう。

8. gmailのゴミ箱見てみましょう。 おお!見事にゴミ箱に移動しています! ここに入ったメールは30日経てば、完全に削除されます。

9. 動作がうまくいくことを確認したら、バッチ化しましょう。 時計マークをクリックします。

10. トリガーを追加します。

11. トリガーを追加します。 実行する関数を選択→これはmyfunctionで良いです。 イベントのソースを選択→時間主導型を選択します。

時間ベースのトリガーのタイプを選択→日付ベースを選びます。 時刻は午前0時~1時を選びます。

12. トリガーが作成されました。

13. 翌日に実行されていることを確認しましょう。

あとがき

これでメールの自動削除が出来ました。

ほっておいても、ゴミ箱に勝手に移動してくれます。

なお、GmailApp.searchは、いろいろな条件を指定できます。 gmailの検索窓で入力できる方法をそのまま流用できます。

例えば、gmailはカテゴリというものがあり、自動的にソーシャルとかプロモーションとか分類されますが、

GmailApp.search('category:promotions older_than:15d')

とすると、15日前のプロモーションのメールが対象になります。

Google Apps Scriptを初めて使いましたが、応用次第では色々できそうです。

C#でClosedXMLを使ってExcel出力を実装する

久しぶりのブログです。
EW中原です。

本日はC#で簡単なExcel出力の実装についてです。

昔はCOMを使って実装していましたが、
動かすPCにExcelが入ってないと動かない、気をつけて実装しないとEXCELのプロセスが残る等色々とあるので、
今回はCOMを使わなくてもExcel操作ができるClosedXMLというものを使って実装してみました。

github.com

早速実装します。
今回は適当にConsoleApplicationを作成しました。

まずは、NuGetからClosedXMLを検索・選択して「インストール」。
f:id:devew:20200110135053p:plain f:id:devew:20200110135124p:plain

インストール終わると、以下の参照設定となります。
色々追加されています。
f:id:devew:20200110135253p:plain

Program.csusing ClosedXML.Excel; を追加します。
ClosedXMLを使うためです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClosedXML.Excel;

namespace ClosedXMLSample
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

実際に新規WorkBookを作成します。
今回は野菜一覧(野菜の価格表)を作成してみました。
完成したコードは以下です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClosedXML.Excel;

namespace ClosedXMLSample
{
    class Program
    {
        /// <summary>
        /// 野菜の一覧を作成します
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // Workbookを作成する
            using (XLWorkbook workbook = new XLWorkbook())
            {
                // Worksheetを追加
                IXLWorksheet worksheet = workbook.AddWorksheet("Sheet1");

                int row = 1;

                // 1行目にヘッダー部を出力
                worksheet.Cell(row, 1).SetValue("No");
                worksheet.Cell(row, 2).SetValue("Yasai Name");
                worksheet.Cell(row, 3).SetValue("Price");
                row++;

                // 2行目以降に野菜一覧作成
                worksheet.Cell(row, 1).SetValue(row-1);
                worksheet.Cell(row, 2).SetValue("Carrot");
                worksheet.Cell(row, 3).SetValue(120);
                row++;

                worksheet.Cell(row, 1).SetValue(row-1);
                worksheet.Cell(row, 2).SetValue("Tomato");
                worksheet.Cell(row, 3).SetValue(220);
                row++;

                worksheet.Cell(row, 1).SetValue(row-1);
                worksheet.Cell(row, 2).SetValue("Cabbage");
                worksheet.Cell(row, 3).SetValue(100);

                //出力した表の内側に罫線を引く
                worksheet.Range(1, 1, row, 3).Style.Border.InsideBorder = XLBorderStyleValues.Thin;

                //出力した表の外側に罫線を引く
                worksheet.Range(1, 1, row, 3).Style.Border.OutsideBorder = XLBorderStyleValues.Thin;

                // ヘッダー(1行目)に背景色を付ける
                worksheet.Range(1, 1, 1, 3).Style.Fill.BackgroundColor = XLColor.SkyBlue;

                // 名前をつけてブックを保存
                workbook.SaveAs("ClosedXMLSample.xlsx");
            }
        }
    }
}

出力結果はこのようになりました。
f:id:devew:20200110153217p:plain

ソースコード一式は以下に置きました。
github.com

今回は基本的なことしか紹介できませんでしたが、もちろん他にも色々な機能があります。
またご紹介できたらと思います。