高性能数据转换

77 阅读3分钟
  • 反思1

最近发现思维能力下降的厉害,写了一个很挫的数据转换。

需求是遍历一个二维数组,根据json对象(有可能需要合并),生成一个新的一维数组。

这个合并,让我陷入了思维困局,固定思维觉得需要去查找转换的数组,是否已经有这个对象了,已经有了就合并,没有就生成新的。

导致算法复杂度On了。架构师连夜帮忙写了一个高性能转换,换上之后效果飞增。不得不拍手叫绝。

  • 效果差异

先来看下优化之前的效果:

image.png 高性能数据转换后的js执行时间:

image.png

差别不就有了吗,就一个算法问题节省了几乎百分百的时间啊!

  • 需求描述

再来说下具体需求:

time{name: 'count', tags: {group: 'test1',type: 'test2'}}{name: 'sum_count', tags: {group: 'test3',type: 'test4'}}{name: 'count', tags: {group: 'test6',type: 'test5'}}{name: 'sum_count', tags: {group: 'test6',type: 'test5'}}
2023-03-3112342334
2023-03-3012342334
2023-03-2912342334
...............

把上面这个表格转成下面这种格式:

timegrouptypecountsum_count
2023-03-31test1test2123
2023-03-31test3test442334
2023-03-30test1test2123
2023-03-30test3test442334
2023-03-29test1test2123
2023-03-29test3test442334
  • 反思2

当然这个是具象之后的需求。我一开始就没有把需求具象,总觉得把需求用这种方式表达出来要花很多时间,确实要花十几分钟,才能把需求具象成这样(加上写和想的时间)。但是有了具象的东西之后就好做思考了,而不是在脑子里把概念的一个东西转成另一个东西。非常考验脑力。还是采用这种方式,对脑子更友好一些。宁愿多花点时间吧。

  • 具体转换过程

挫的版本(完全就是思维的灾难啊)

rotateRowColumn(rotateColumns) {
    if (!rotateColumns.length && !this.dimensionEntity.hasTags) {
      return
    }
    const item = {}
    rotateColumns.forEach(c => {
      item[c.key || c.field] = ''
    })

    let temResults = [],
      results = []
    this.dimensions.forEach(dim => {
      if (
        dim.type === NUMBER &&
        dim.tags &&
        Object.prototype.toString.call(dim.tags) === '[object Object]'
      ) {
        if (!temResults.find(t => this.findItemByTag(t, dim.tags))) {
          const newItem = { ...item }
          Object.keys(dim.tags).forEach(t => {
            newItem[t + HYPHEN + TAG_ORDINAL] = dim.tags[t]
          })
          temResults.push(newItem)
        }
      }
    })
    if (
      this.dimensions[0].type !== TIME &&
      this.dimensions[0].type !== ORDINAL
    ) {
      this.source.forEach(sourceElement => {
        sourceElement.forEach((val, index) => {
          const dim = this.dimensions[index]
          const obj = temResults.find(t => this.findItemByTag(t, dim.tags))
          obj[dim.name + HYPHEN + dim.type] = val
        })
      })
      return temResults
    }
    this.source.forEach(sourceElement => {
      sourceElement.forEach((val, index) => {
        const dim = this.dimensions[index]
        const dim0 = this.dimensions[0]
        if (dim.type === TIME || dim.type === ORDINAL) {
          const temp = temResults.map(i => {
            return {
              ...i,
              [dim.name + HYPHEN + dim.type]: val
            }
          })
          results.push(...temp)
          return
        }
        const obj = results.find(
          t =>
            this.findItemByTag(t, dim.tags) &&
            t[dim0.name + HYPHEN + dim0.type] === sourceElement[0]
        )
        obj[dim.name + HYPHEN + dim.type] = val
      })
    })
    return results
  }

牛叉的版本

export default class DatasetConverter {
  static convert(dataset) {
    if (
      dataset === null ||
      dataset.dimension === null ||
      dataset.source === null
    ) {
      return []
    }

    const columnInfos = this.parseDatasetDimensions(dataset.dimensions)
    const jsonObjects = []
    for (const row of dataset.source) {
      const rowToJsonObjects = this.parseRowToJsonObjects(row, columnInfos)
      jsonObjects.push(...rowToJsonObjects)
    }

    return jsonObjects
  }

  static parseDatasetDimensions(dimensions) {
    const columnInfos = []

    for (let i = 0; i < dimensions.length; i++) {
      const dimension = dimensions[i]
      //用了一个ColumnInfo的结构,没有任何冗余代码
      const columnInfo = new ColumnInfo(dimension)
      columnInfos.push(columnInfo)
    }

    return columnInfos
  }

  static parseRowToJsonObjects(row, columnInfos) {
    const objectMap = {}
    const commonProperties = {}

    for (let i = 0; i < row.length; i++) {
      const value = row[i]
      const ci = columnInfos[i]

      if (ci.common) {
        commonProperties[ci.dimension.getName()] = value
        continue
      }
      //用tags做为key,设了一个小小的缓存,并没有多大内存开销
      let jsonObject = objectMap[ci.stringTags]
      //没转换过,就是新的
      if (!jsonObject) {
        jsonObject = { ...ci.jsonTags }
        objectMap[ci.stringTags] = jsonObject
      }

      jsonObject[ci.dimension.getName()] = value
    }
    for (const json of Object.values(objectMap)) {
      Object.assign(json, commonProperties)
    }
    //最后把map的values拿出来就是想要的数组了呀
    return Object.values(objectMap)
  }
}

class ColumnInfo {
  constructor(dimension) {
    this.dimension = new Dimension(dimension)
    this.common = this.isCommonDimension()

    if (!this.common) {
      this.stringTags = this.dimension.getStringTags()
      this.jsonTags = this.dimension.getTags()
    }
  }
  isCommonDimension() {
    if (!this.dimension.getTags()) {
      return true
    }
    return false
  }
}
class Dimension {
  name
  type
  tags
  constructor({ name, type, tags } = {}) {
    this.name = name
    this.type = type
    this.tags = tags
  }
  getName() {
    return this.name + HYPHEN + this.type
  }
  getStringTags() {
    return JSON.stringify(this.tags)
  }
  getTags() {
    if (isEmptyObject(this.tags)) return null
    const tags = {}
    for (const tag in this.tags) {
      tags[tag + HYPHEN + TAG_ORDINAL] = this.tags[tag]
    }
    return tags
  }
}

巧用数组下标和用tags为key的map就搞定了查找的问题,功力深厚!佩服佩服!反观自己写的代码,真是思维灾难,一个程序员怎么能写出这么流水账的代码呢???哎!