使用map和reduce进行规范化

时间:2017-08-20 01:53:12

标签: javascript ecmascript-6

我想做一个雷达图,但我无法控制api输出,所以我必须将一个复杂的数据转换成某种形式。我被困了至少半个小时。

如何转换此原始数据

const raw = [{
  "device_info": {
    "device_id": 123,
    "name": "iphone",
  },
  "age_data": [{
    "age_range": "0-10",
    "total_count": 15,
    "man": 6,
    "women": 9
  }, {
    "age_range": "11-20",
    "total_count": 11,
    "man": 7,
    "women": 4
  }]
}, {
  "device_info": {
    "device_id": 456,
    "name": "android",
  },
  "age_data": [{
    "age_range": "0-10",
    "total_count": 1,
    "man": 1,
    "women": 0
  }, {
    "age_range": "11-20",
    "total_count": 2,
    "man": 0,
    "women": 2
  }]
}]

进入这个

const data = [{
  age_group: '0-20',
  iphone: 26,
  android: 3
}, {
  age_group: '21-30',
  iphone: 0,
  android: 0
}, ];

https://jsfiddle.net/cqmyganr/2

这就是我的尝试:

const age_group = raw[0].age_data.map(obj => ({
  age_group: obj.age_range
}))

const cams = raw.map(obj => ({
  device_id: obj.device_info.device_id,
  device_name: obj.device_info.name
}))

console.log(cams)

const age_group_with_cams = age_group.map(obj =>
  Object.assign({}, obj, ...cams)
)

console.log(age_group_with_cams)

2 个答案:

答案 0 :(得分:2)

好的,我会使用reduce,因为您正在将数据转换为另一种形式list-of-devices-with-age-info => list-of-ages-with-devices列表可以有不同的长度。

这将是我的第一步,将所有设备映射到所有现有年龄

const usageByAge = raw.reduce((by_age, dev) => { 
  dev.age_data.forEach(age => {
    if (!by_age[age.age_range]) by_age[age.age_range] = {};
    by_age[age.age_range][dev.device_info.name] = age.total_count;
  })

  return by_age;
}, {})

// output, something like this:
usageByAge = {
  "0-10": { android: 123, iphone: 321 },
  "10-20": { android: 123, iphone: 321 },
}

再次从这里开始减少功能,这次创建一个规则:

isGroupInBounds = (g, lo, hi) => {
  const [gl, gh] = g.split('-');
  return gl >= lo && gh <= hi;
}

["0-20", "21-30"].map(age_group => {
  const group = { age_group, iphone: 0, android: 0 };
  const [l, h] = age_group.split('-')

  Object.keys(usageByAge).forEach(inputGroup => {
    if (isGroupInBounds(inputGroup, l, h)) {
      Object.keys(usageByAge[inputGroup]).forEach(deviceName => {
         group[deviceName] += usageByAge[inputGroup][deviceName];
      })
    }
  })

  return group;
})

Yeeah,这个非常棘手,在你的小提琴中试过它并且它有效;)谢谢你的练习!

https://jsfiddle.net/cqmyganr/1/

答案 1 :(得分:1)

在阅读@ webdeb的回答后,我认为有更好的方法可以做他做的事情。这就是我想出的:

const raw = [ { device_info: { device_id: 123
                             , name: "iphone" }
              , age_data: [ { age_range: "0-10"
                            , total_count: 15
                            , man: 6
                            , women: 9 }
                          , { age_range: "11-20"
                            , total_count: 11
                            , man: 7
                            , women: 4 } ] }
            , { device_info: { device_id: 456
                             , name: "android" }
              , age_data: [ { age_range: "0-10"
                            , total_count: 1
                            , man: 1
                            , women: 0 }
                          , { age_range: "11-20"
                            , total_count: 2
                            , man: 0
                            , women: 2 } ] } ];

// First, we convert the raw data into a relation:

const relation = [], devices = {};

const toInt = x => parseInt(x, 10);

for (const { device_info: { device_id, name }, age_data } of raw) {
    devices[name] = 0; // The default total_count of every device.

    for (const { age_range, total_count, man, women } of age_data) {
        const [age_begin, age_end] = age_range.split("-").map(toInt);

        relation.push({ device_id, name
                      , age_begin, age_end
                      , total_count, man, women });
    }
}

// Next, we write a function that given an age range returns its stats:

const getAgeRange = (begin, end) =>
    relation.reduce((table, tuple) => {
        if (tuple.age_begin >= begin && tuple.age_end <= end) {
            const row = table.find(row => row.name === tuple.name);

            if (row) {
                row.total_count += tuple.total_count;
                row.man         += tuple.man;
                row.women       += tuple.women;
            } else table.push(Object.assign({}, tuple));
        } return table;
    }, []);

// Finally, we create an array of the age groups we're looking for:

const getAgeGroup = (begin, end) =>
    getAgeRange(begin, end).reduce((summary, { name, total_count }) => {
        summary[name] = total_count;
        return summary;
    }, Object.assign({ age_group: begin + "-" + end }, devices));

const data = [ getAgeGroup(0,  20)
             , getAgeGroup(21, 30) ];

console.log(data);

希望这是有道理的。我试图尽可能地坚持relational model