【问题标题】:How can I reorganize this data using point free methods如何使用无点方法重新组织这些数据
【发布时间】:2019-12-23 22:43:44
【问题描述】:

我正在尝试学习一些有关函数式编程和 Ramda 的知识,并且正在使用我最喜欢的旧数据结构操作来尝试一些无意义的东西。很少或没有循环。我的输入数据:

const data = [{
    timeline_map: {
        "2017-05-06": 770,
        "2017-05-07": 760,
        "2017-05-08": 1250,
    }
}, {
    timeline_map: {
        "2017-05-06": 590,
        "2017-05-07": 210,
        "2017-05-08": 300,
    }
}, {
    timeline_map: {
        "2017-05-06": 890,
        "2017-05-07": 2200,
        "2017-05-08": 1032,
    }
}]

期望的输出:

[
  ["2017-05-06", 770, 590, 890, ...],
  ["2017-05-07", 760, 210, 2200, ...],
  ["2017-05-08", 1250, 300, 1032, ...]
]

这是我的代码:

const condense = x => x.timeline_map
var rest = []
const pluckDate = (obj, i) => {
  return [Object.keys(obj)[i]]
}
const getValues = value => {
  data.map(function () {
    return data.timeline_map
  })
}
const result = R.compose(
  R.values,
  R.mapObjIndexed(pluckDate),
  R.map(condense)
)
console.log(result(data))

到目前为止,我得到的只是提取的日期:

[["2017-05-06"], ["2017-05-07"], ["2017-05-08"]]

这离解决方案还很远并且我什至不知道我是否以“好”的方式完成了它。解决这个问题的“功能”/“ramda”/“无点”方法是什么?

JSBIN

【问题讨论】:

  • Object.entries([].concat(...data.map(i => Object.entries(i.timeline_map))).reduce((acc, [k, v]) => Object.assign({}, acc, { [k]: k in acc ? acc[k].concat(v) : [v] }), {})).map(([k, v]) => [k, ...v]) --- 只是为了好玩——纯 ES 解决方案
  • 我确实发现了最后一点的能力 -- .map(k, v) => [k, ...v] -- ES6 最好的特性之一

标签: javascript data-structures functional-programming ramda.js


【解决方案1】:

这是一种方法。可能还有更简单的:

const convert = pipe(
  pluck('timeline_map'), //=> [{"2017-05-06": 770, "2017-05-07": 760, ...}, ...]
  map(toPairs),          //=> [[["2017-05-06", 770], ["2017-05-07", 760], ...], ...]
  unnest,                //=> [["2017-05-06", 770], ["2017-05-07", 760], ...]
  groupBy(head),         //=> {"2017-05-06": [["2017-05-06", 770], ["2017-05-06", 590], ...], "2017-05-07": [...], ...}
  map(map(last)),        //=> {"2017-05-06": [770, 590, 890], "2017-05-07": [760, 210, 2200], ...]
  toPairs,               //=> [["2017-05-06", [770, 590, 890]], ["2017-05-07", [760, 210, 2200]], ...]
  map(apply(prepend))    //=> [["2017-05-06", 770, 590, 890], ["2017-05-07", 760, 210, 2200], ...]
)
convert(data)

这涉及许多不同的步骤,我已经展示了每个步骤生成的数据类型。

您可以在 Ramda REPL 上看到这一点。

【讨论】:

    【解决方案2】:

    只是为了好玩,我想尝试在一个循环中使用两个嵌套的 reducer。我认为我会分享我的想法的三个原因:

    • 我认为最好的起点始终是写它而不用担心风格,直到你得到你想要的结果,然后再尝试“无点”:)
    • 很高兴看到转换基本上是两个reduce 操作的组合:从{ timeline_map }[entries],以及从[entries][[key, ...values]]
    • 这表明想要在一个循环中完成所有事情需要你做一些丑陋的事情:D

    看看两个约简、变异Map 方法是否也可以组合成一个无点 Ramda 函数可能会很有趣……但我不是解决这个问题的合适人选。

    // from [[key, value]] to Map(key, [key, values])
    const timeLineReducer = (map, [key, value]) => 
      map.set(
        key, 
        (map.get(key) || [key]).concat(value)
      );
    
    // From [{ key: value }] to Map(key, [key, values])
    const dataReducer = (map, { timeline_map }) =>
       Object
          .entries(timeline_map)
          .reduce(timeLineReducer, map);
    
    const transformData = data => Array.from(
        data
          .reduce(dataReducer, new Map())
          .values()
      );
      
    console.log(transformData(getData()))
      
    
    
    // data
    function getData() { return [{timeline_map:{"2017-05-06":770,"2017-05-07":760,"2017-05-08":1250}},{timeline_map:{"2017-05-06":590,"2017-05-07":210,"2017-05-08":300}},{timeline_map:{"2017-05-06":890,"2017-05-07":2200,"2017-05-08":1032}}]; }

    编辑:出于好奇,我想尝试一下point free 编程。一个有趣的谜题,但我不能说我真的很欣赏这个结果。

    以前从未尝试过,所以我什至不确定它是否免费。但它有效!

    // e.g.: [2] -> [1] -> [1, 2]
    const fConcat = flip(concat);
    
    // e.g. : [1, 3] -> [1, 2] -> [1, 2, 3]
    const fConcatTail = useWith(fConcat, [tail, identity]);
    
    // e.g.: secNil(1, null) -> true
    const secNil = compose(isNil, nthArg(1));
    
    // e.g.: fConcatTailIf([0, 1], null)   -> [0, 1]
    // e.g.: fConcatTailIf([0, 2], [0, 1]) -> [0, 1, 2]
    const fConcatTailIf = ifElse(
      secNil,
      clone,
      fConcatTail
    );
    
    // e.g.: ["a", 1] -> lensProp("a")
    const kvpKeyLensProp = compose(lensProp, head);
    
    
    // e.g.: ["a", 1] -> {}              -> { a: ["a", 1] }
    // e.g.: ["a", 2] -> { a: ["a", 1] } -> { a: ["a", 1, 2]}
    const handleKVP = compose(
      apply(over),                          // create over that waits for {}
      ap([kvpKeyLensProp, fConcatTailIf]),  // ap with arg.
      of                                    // wrap argument in array
    );
    
    // (kvp, map) => handleKVP(kvp)(map);
    const mergeKVPWithMap = uncurryN(2, handleKVP);
    
    // Flip because reduce is inverted ((map, kvp) => ...)
    // e.g.: {} -> ["a", 1] -> { a: ["a", 1]}
    const entryReducer = flip(mergeKVPWithMap);
     
    
    // e.g.: {}              -> [ ["a", 1] ] -> { a: ["a", 1] }
    // e.g.: { a: ["a", 1] } -> [ ["a", 2] ] -> { a: ["a", 1, 2] }
    const timelineReducer = reduce(entryReducer);
    
    // e.g. { timeline_map: { a: 1 } } -> [ [ "a", 1 ] ]
    const entriesFromData = compose(toPairs, prop("timeline_map"));
    
    // Decorate 2nd argument of timelineReducer with entriesFromData
    const dataReducer = useWith(timelineReducer, [identity, entriesFromData]);
    
    // Return only the values from our composed object
    const transformData = compose(values, reduce(dataReducer, { }));
      
    // Call our transformation with our data
    transformData(getData())
      
    
    
    // data
    function getData() { return [{timeline_map:{"2017-05-06":770,"2017-05-07":760,"2017-05-08":1250}},{timeline_map:{"2017-05-06":590,"2017-05-07":210,"2017-05-08":300}},{timeline_map:{"2017-05-06":890,"2017-05-07":2200,"2017-05-08":1032}}]; }

    Ramda REPL!试试吧

    【讨论】:

      【解决方案3】:

      出于演示目的,这里是另一种解决方案。

       const concatAll = reduce(mergeWith(concat), {});
       const wrapInArray = value => [value];
       const zipKeysWithValues = (arr) => zip(keys(arr), values(arr));
      
       const res1 = pipe(
        pluck('timeline_map'),
        map(map(wrapInArray)),
        concatAll,
        zipKeysWithValues,
        map(flatten),
       )(data);
      
       const res2 = compose(
          map(flatten), 
          zipKeysWithValues, 
          concatAll, 
          map(map(wrapInArray)), 
          pluck('timeline_map'))(data);
      

      您也可以同时使用pipecompose。对于日志记录,在任何两个步骤之间使用tap(console.log)

      【讨论】:

      • 我在文档中没有看到 wrapInArray 或 zipKeysWithVues。你用的是什么版本?
      • 定义在前三行
      • 哦,对不起,我在手机上,完全错过了,对不起。
      • 注意zipKeysWithValues也可以写成lift(zip)(keys, values)。而wrapInArray 也是 Ramda 的of。但我比我的回答更喜欢这个。 concatAll 是一个很好的抽象,map(flatten)map(apply(prepend)) 干净得多
      • 谢谢@ScottSauyet,非常感谢您花时间添加到我的解决方案中。 :)
      【解决方案4】:

      纯js最快的方法

      var data = [{
          timeline_map: {
              "2017-05-06": 770,
              "2017-05-07": 760,
              "2017-05-08": 1250,
          }
      }, {
          timeline_map: {
              "2017-05-06": 590,
              "2017-05-07": 210,
              "2017-05-08": 300,
          }
      }, {
          timeline_map: {
              "2017-05-06": 890,
              "2017-05-07": 2200,
              "2017-05-08": 1032,
          }
      }];
      
      var mapper = {};
      var output = [];
      data.forEach((_node) => {
          for (let key in _node.timeline_map) {
              if (!mapper[key]) {
                  mapper[key] = [];
              }
              mapper[key].push(_node.timeline_map[key]);
          }
      });
      for (let key in mapper) {
          let row = [key];
          row = row.concat(mapper[key]);
          output.push(row);
      }
      console.log(mapper); //"{"2017-05-06":[770,590,890],"2017-05-07":[760,210,2200],"2017-05-08":[1250,300,1032]}"
      
      console.log(output); //[["2017-05-06",770,590,890],["2017-05-07",760,210,2200],["2017-05-08",1250,300,1032]]
      

      【讨论】:

        猜你喜欢
        • 2020-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-23
        • 2011-12-26
        • 2022-11-12
        相关资源
        最近更新 更多