Home About
JavaScript , set-theory

再び JavaScript編) 微妙に揺れのある2つの文字列リストに対する差(difference)の計算

この問題を解くHaskell による実装を書いたのだが、 結局のところ、対象となる文字列(画像ファイル名)の等価性をカスタマイズするために、 文字列を丸めてそれを使って等価性の比較を行っているだけ。 Haskell では、 Eq 型クラスを自前で用意した Item 型に実装する形で、 言語にもともと備えられている仕組みを 使っているのでコードとしてはわかりやすいのは確かだけれど。

そこでこの問題を Haskell 発想により近い形で JavaScript で実装しなおしてみる。

微妙に揺れのある次のような 二つの文字列リスト(画像ファイル名)があるとする。

const listA = ['grape.pdf', 'apple_v1.pdf', 'apple_v2.pdf', 'lemon_v1.pdf', 'peach_v2.pdf'];
const listB = ['grape.png', 'lemon.png', 'strawberry.png'];

listA の画像から listB の画像をPDFからPNG変換して作成した、という状況。 変換し忘れている画像ファイル(PDF)を知りたい。 そんな場合の計算方法について考える。

まずこの各リストの要素の文字列をオブジェクトに変換することを考える。

const roundFilename = (filename)=>{
    if( filename.match(/([a-z]+)(_v\d)?\.(pdf|png)/) ){
        return `${RegExp.$1}.png`;
    } else {
        return filename;
    }
};

const toItem = (filename)=>{
	return {
	  name: filename,
	  roundedName: roundFilename(filename)};
};

Item オブジェクトは実際のファイル名(name) と丸められたファイ名(roundedName) を持つ。 Item 同士が等しいかどうかを評価するときには roundedName を使うことにする。

では差の計算をする myDifference 関数:

const contains = (itemList,item)=>{
    const isEquals = (item0, item1)=>{
        return ( item0.roundedName == item1.roundedName );
    };

    const filteredList = _.filter(itemList, (it)=> isEquals(it, item));

    return ( filteredList.length>0 );
};

const myDifference = (itemListA, itemListB)=>{
    return _.filter( itemListA, (itemA)=> !contains(itemListB, itemA) )
};

実際に差を計算する。

const listA = ['grape.pdf', 'apple_v1.pdf', 'apple_v2.pdf', 'lemon_v1.pdf', 'peach_v2.pdf'];
const listB = ['grape.png', 'lemon.png', 'strawberry.png'];


const toItemList = (list)=> _.map(list, (it)=> toItem(it));

const result = myDifference(
	toItemList(listA), 
	toItemList(listB));

console.log(result);

実行する。

$ node index.js
[
  { name: 'apple_v1.pdf', roundedName: 'apple.png' },
  { name: 'apple_v2.pdf', roundedName: 'apple.png' },
  { name: 'peach_v2.pdf', roundedName: 'peach.png' }
]

まとめ

コード全体:

const _ = require('underscore');

const listA = ['grape.pdf', 'apple_v1.pdf', 'apple_v2.pdf', 'lemon_v1.pdf', 'peach_v2.pdf'];
const listB = ['grape.png', 'lemon.png', 'strawberry.png'];

const roundFilename = (filename)=>{
    if( filename.match(/([a-z]+)(_v\d)?\.(pdf|png)/) ){
        return `${RegExp.$1}.png`;
    } else {
        return filename;
    }
};

const toItem = (filename)=>{
	return {
	  name: filename,
	  roundedName: roundFilename(filename)};
};


const contains = (itemList,item)=>{
    const isEquals = (item0, item1)=>{
        return ( item0.roundedName == item1.roundedName );
    };

    const filteredList = _.filter(itemList, (it)=> isEquals(it, item));

    return ( filteredList.length>0 );
};

const myDifference = (itemListA, itemListB)=>{
    return _.filter( itemListA, (itemA)=> !contains(itemListB, itemA) )
};

const toItemList = (list)=> _.map(list, (it)=> toItem(it));

const result = myDifference(
	toItemList(listA), 
	toItemList(listB));

console.log(result);