zoukankan      html  css  js  c++  java
  • [Transducer] Make Transducer works for Iteratable collection and Object

    We've seen how we can transduce from arrays or other iterables, but plain objects aren't iterable in Javascript. In this lesson we'll modify our transduce() function so that it supports iterating from plain objects as well, treating each key value pair as an entry in the collection.

    To do this we'll be using a lodash function called entries.

    The whole point to make collection works for Object type is because when we use for.. of loop, Object is not itertable type, so Object still cannot be used. The fix that problem, we can use 'entries' from lodash, to only get value as an array from the Object, so that we can loop though the array.

    import {isPlainObject, entries} from 'lodash';
    import {map, into} from '../utils';
    
    let transduce = (xf /** could be composed **/, reducer, seed, _collection) => {
    
        const transformedReducer = xf(reducer);
        let accumulation = seed;
    
        const collection = isPlainObject(_collection) ? entries(_collection) : _collection;
    
        for (let value of collection) {
            accumulation = transformedReducer(accumulation, value);
        }
    
        return accumulation;
    };
    
    const objectValues = obj => {
        return into([], map(kv => kv[1]), obj);
    };
    
    objectValues({one: 1, two: 2});

    --- 

    const { isObject, isArray } = require('crocks');
    const { toPairs } = require('ramda');
    
    const data = [1,2,3];
    const inc = x => x + 1;
    const double = x => 2 * x;
    const lessThanThree = x => x < 3;
    const toUpper = s => s.toUpperCase();
    const isVowel = char => ['a', 'e', 'i', 'o', 'u'].includes(char.toLowerCase());
    const compose = (...fns) => (...args) => fns.reduce((acc, fn) => [fn.call(null, ...acc)], args)[0]
    ////////////////////
    /**
     * Problem: We loop over array 3 times! We want to loop over only once
     * in order to improve the profermance.
     */
    const res1 = data
        .filter(lessThanThree)
        .map(double)
        .map(inc)
    
    console.log(res1)    // [3,5]
    
    ////////////////////////////////
    /**
     * Problem: it is not pure function and we do mutation. But it is faster
     * than we do .filter.map.map style, because it only loop the array once.
     */
    let res2 = [];
    data.forEach((x) => {
        let item;
        if (lessThanThree(x)) {
            item = inc(double(x))
            res2.push(item);
        }
    })
    console.log(res2)    // [3,5]
    
    ////////////////////////////////
    /**
     * Good: We avoid the mutation and can be write as pure function and it only loop once!
     * Problem: But we lose our function composion style! We still want .filter.map.map styling.
     * Meanwhile it should be profermance wise.
     */
    const res3 = data.reduce((acc, curr) => {
        if (lessThanThree(curr)) {
            acc.push(inc(double(curr)));
        }
        return acc;
    }, []);
    console.log(res3);    // [3,5]
    
    
    ////////////////////////////////////
    //data.reduce(reducer, seed), reducer is something we can compose!
    //Because reducer :: (acc, curr) => acc
    //For every reducer functions' signature are the same.
    //If the function sinature are the same, then we can compose function together!
    const _mapReducer = (xf, array) =>
        array.reduce((acc, curr) => {
            acc.push(xf(curr))
            return acc;
        }, []);
    const _filterReducer = (xf, array) =>
        array.reduce((acc, curr) => {
            if (xf(curr)) acc.push(curr);
            return acc;
        }, []);
    // To make fns easy to compose, we extract 'array' data & init value
    const mapReducer = (xf) => ((acc, curr) => {
        acc.push(xf(curr))
        return acc;
    });
    const filterReducer = pred => ((acc, curr) => {
        if (pred(curr)) acc.push(curr);
        return acc;
    });
    // now mapReducer and filterReducer both have the same function signature.
    console.log(data.reduce(mapReducer(double), [])); // [2,4,6]
    console.log(data.reduce(mapReducer(inc), [])); // [2,3,4]
    console.log(data.reduce(filterReducer(lessThanThree), []));  // [1,2]
    
    // In order to compose reudcers together we need to make mapReducer and filterReducer as high order functions to take reducer as arguement
    // Take a reducer as input and return a reducer signature as output is the key to do composion!
    const map = xf => reducer => ((acc, curr) => {
        acc = reducer(acc, xf(curr))
        return acc;
    });
    const filter = pred => reducer => ((acc, curr)=> {
        if (pred(curr)) acc = reducer(acc, curr)
        return acc;
    })
    // For mapReducer and filterReducer, we both do acc.push()
    // therefore we can extrat this as base reducer
    const pushReducer = (acc, value) => {
        acc.push(value);
        return acc;
    };
    
    /**
     * Now we are able to use functional style and loop the array only once!s
     */
    const doulbeLessThanThree = compose(
        map(inc),
        map(double),
        filter(lessThanThree)
    )
    const res5 = data.reduce(doulbeLessThanThree(pushReducer),  []);
    console.log(res5); // [3,5]
    
    ////////////////////////////////////
    // Define our transducer!
    /**
     * transducer :: ((a -> b -> a), (a -> b -> a), [a], [a]) -> [a]
     * @param {*} xf: base reducer
     * @param {*} reducer: the composion redcuer signature
     * @param {*} seed : init value
     * @param {*} collection : data
     */
    const _transducer = (xf, reducer, seed, collection) => {
        return collection.reduce(xf(reducer), seed);
    }
    const res6 = _transducer(doulbeLessThanThree, pushReducer, [], data);
    console.log(res6); // [3,5]
    
    const transducer = (xf, reducer, seed, collection) => {
        let acc = seed;
    
        collection = isObject(collection) ? toPairs(collection): collection
    
        const transformReducer = xf(reducer);
        for (let curr of collection) {
            acc = transformReducer(acc, curr)
        }
    
        return acc;
    }
    
    const res7 = transducer(
        compose(filter(isVowel), map(toUpper)),
        (acc, curr) => acc + curr,
        '',
        'transducer'
    );
    console.log("7", res7); // AUE
    
    
    const numMap = new Map()
    numMap.set('a', 1);
    numMap.set('b', 2);
    numMap.set('c', 3);
    numMap.set('d', 4);
    const res8 = transducer(
        doulbeLessThanThree,
        pushReducer,
        [],
        numMap.values()
    );
    console.log("8", res8); // [3,5]
    
    /**
     * into helper
     * transducer = (xf, reducer, seed, colllection)
     * Until so far, we have to know what kind of base reducer we need to use
     * for example, push reduer for array, concat reducer for string...
     *
     * The idea of into helper is to let transducer to figure out what reducer
     * we want to use automaticlly instead of we telling transducer which one to use
     *
     *  into:: (to, xf, collection)
     */
    
    const objectReducer = (obj, value) => Object.assign(obj, value);
    const into = (to, xf, collection) => {
         if (Array.isArray(to)) {
            return transducer(xf, pushReducer, to, collection);
         } else if (isObject(to)) {
            return transducer(xf, objectReducer, to, collection)
         }
         throw new Error('into only supports arrays and objects as `to`');
     }
    
     /**
      * seq helper
      * Different from into help, seq helper will infer the collection type
      */
    const seq = (xf, collection) => {
        if (isArray(collection)) {
           return transducer(xf, pushReducer, [], collection);
        } else if (isObject(collection)) {
           return transducer(xf, objectReducer, {}, collection)
        }
        throw new Error('seq : unsupport collection type');
    }
    console.log(seq(compose(
        filter(x => x < 5),
        map(x => x * 2)
    ), [1,2,3]));
    
    const filp = map(([k, v]) => ({[v]: k}));
    console.log(seq(filp, {one: 1, two: 2})); /**{1: 'one, 2: 'two'} */
  • 相关阅读:
    IE7 下父元素及子元素的隐藏顺序带来的 display:none bug
    PHP 类的魔术方法及类的自动加载
    元素及文本的居中
    软件工程个人作业 词频统计
    读书笔记——《最后期限》
    读书笔记——《构建之法》
    [Reading] Asking while Reading
    【Paper Reading】Learning while Reading
    Android学习一:文件操作
    Android学习九:屏幕自适应
  • 原文地址:https://www.cnblogs.com/Answer1215/p/8305883.html
Copyright © 2011-2022 走看看