はてだBlog(仮称)

私的なブログど真ん中のつもりでしたが、気づけばWebサイト系のアプリケーション開発周りで感じたこと寄りの自分メモなどをつれづれ述べています。2020年6月現在、Elasticsearch、pandas、CMSなどに関する話題が多めです。...ですが、だんだんとより私的なプログラムのスニペット置き場になりつつあります。ブログで述べている内容は所属組織で販売している製品などに関するものではなく、また所属する組織の見解を代表するものではありません。

JavaScriptでもmapやreduce、filter(手習いメモ) その2

次の記事のつづきです。 (このブログは全体的にそうですが、誰得でいうと私自身用って感じのスニペットですネ...)

itdepends.hateblo.jp

find

/* findIndex同様に、find って関数を引数に取れるので便利 */

//find初歩的な例
// (filterと異なり、最初にヒットしたものが得られる)
//
const data = [
    { id: '111', name: 'A', score: 100 },
    { id: '111', name: 'B', score: 50 },
    { id: '222', name: 'C', score: 100 },
    { id: '222', name: 'C', score: 50 },
    { id: '222', name: 'D', score: 70 },
];

const ffn = i => ['A', 'C'].includes(i.name);
const found1st = data.find(ffn)

console.log(found1st)


// > console.log(found)
// { id: '111', name: 'A', score: 100 }

配列を前方一致でグループ化できるか

/* sort */
// 配列の要素の全てに前方一致するようなある要素が存在するか
//  (思ったほどsortの例にはならなかったケド....。どちらかといえば、JavaScriptのmapは「src」が使えることの便利さの例かも。)
const true1 = ['1234', '12', '123', '12345']; // 12が該当
const false2 = ['234', '12', '123', '12345']; // 特に該当するもの無し
const hougan = ary => [...ary].sort().reduce((a, c) => c.startsWith(a) ? c : undefined, '');
const hougan2 = ary => [...ary].sort().map((c, i, src) => c.startsWith(src[0])).every(Boolean);

hougan2(true1);
hougan2(false2);

/*
> 
> hougan2(true1);
true
> hougan2(false2);
false
> 
>
*/

配列のintersect(順番維持)


// 順番維持版の2つのリストのintersect とその逆
const intersect = (a, b) => a.filter(el => b.includes(el));
const without = (a, b) => a.filter(el => !b.includes(el));

/*
>intersect([1, 2, 3, 4], [4, 5, 2])
[2, 4]
> without([1, 2, 3, 4], [4, 5, 2])
[1, 3]
*/

オブジェクト配列(順序はそれほど意味がない)を同等の形式のオブジェクトに変換


//オブジェクトの配列(各オブジェクトは実際は特定のプロパティで一意になっているようなもの)をそのプロパティの値をキーとする1つのオブジェクトに変換する
// 例えば、Elasticsearchの検索結果の一覧をドキュメントのIDをキーにしたオブジェクトに変換する... といった例をイメージした演算
let x = [{ id: 'a1', title: 'abc' }, { id: 'b2', title: 'def' }];

function keyBy(objArr, key) {
    const obj = {};
    objArr.forEach(i => {
        obj[i[key]] = i
    });
    return obj;
}

keyBy(x, 'id')

/*    
> keyBy(x, 'id')
{ a1: { id: 'a1', title: 'abc' }, b2: { id: 'b2', title: 'def' } }
*/

ポスティングリスト風のデータに対して外部から与えた関数で「スコアリング」

/* ポスティングデータ/ポスティングリスト([キー,TF/IDFなどの持ち点1,持ち点2,...]形式)のキーごとの合計スコアを計算 */
//   ↓

/* インプットデータ(仕込み) */
const parr = [
    // 検索条件Aに関するヒットの文書一覧
    [
        ['ID123', 900, 800], ['ID124', 9000, 8000], ['ID125', 90000, 80000], ['ID222', 5, 5]
    ],
    // 検索条件Bに関するヒットの文書一覧
    [
        ['ID111', 1, 1], ['ID123', 100, 200], ['ID124', 1000, 2000], ['ID125', 10000, 20000]
    ]
    // ... 実際は続いていく
];
const fnarr = [
    // 検索条件Aに関する個別の評価関数
    p => ({ key: p[0], score: p[1] + p[2] }),
    // 検索条件Bに関する個別の評価関数
    p => ({ key: p[0], score: p[1] * 2 + p[2] }),
]

/* スコア計算ロジック */
const DEFAULT_FUNC = x => ({ key: x[0], score: x[1] });
function score(parr, fnarr, strategyfn = (a, b) => a + b) {
    //
    if (!parr || parr.length === 0) {
        return Object.create(null);
    }
    //スコア評価関数の設定(引数で評価関数が引き渡されているならそれを使うが未指定の場合はデフォルト(持ち点1の値をそのまま使う))
    const fns = fnarr && fnarr.length > 0
        ? fnarr
        : Array(parr.length).fill(DEFAULT_FUNC);

    //スコアリング
    const scoreObj = Object.create(null);
    parr.forEach((p, i) => {
        p.forEach(el => {
            const { key, score } = fns[i](el);
            scoreObj[key] = strategyfn((scoreObj[key] || 0), score);
        });
    });
    return scoreObj;
}


/* スコア計算例 */
score(parr, fnarr)
/*
> score(parr, fnarr)
{
  ID123: 2100,
  ID124: 21000,
  ID125: 210000,
  ID222: 10,
  ID111: 3
}
*/