如何在 JavaScript 中用函数式编程从一对多列表构建倒置映射?

How to build an inverted map from a one-to-many list with functional programming in JavaScript?

澄清一下我所说的 "inverted map":

const foo =
  { "a": 10
  , "b": 20
  };

const foo_inverted =
  { "10": "a"
  , "20": "b"
  };

我有这个代表文件的对象:

const file =
  { id: 100
  , tags: [20, 30]
  };

给定一个文件列表,我需要构建一个地图,使我能够找到具有给定标签的所有文件。

来自这里:

const files =
  [ { id: 100
    , tags: [20, 30]
    }
  , { id: 200
    , tags: [20, 40]
    }
  ];

对此:

{ "20": { "100": 1, "200": 1 }
, "30": { "100": 1 }
, "40": { "200": 1 }
}

我最终得到了完成这项工作的代码:

const tag_file = (tag_id, file_id) => ({[tag_id]: {[file_id]: 1}});
const mergeDeepAll = reduce(mergeDeepRight, {});
const tag_map = compose(mergeDeepAll, lift(tag_file));
const tags_map = compose(mergeDeepAll, map(({id, tags}) => tag_map(tags, [id])));

tags_map(files);
//=> { "20": { "100": 1, "200": 1 }
//=> , "30": { "100": 1 }
//=> , "40": { "200": 1 }
//=> }

问题:我是否遗漏了任何可以让我更好地表达这一点的函数式编程概念?

不确定为什么需要 ramda,可以使用 reduce 和 forEach

const files = [{
  id: 100,
  tags: [20, 30]
}, {
  id: 200,
  tags: [20, 40]
}];

// loop over the array to make an object
const result = files.reduce((obj, file) => {
  // loop over the tags
  file.tags.forEach(
    tag =>
    obj[tag] ?  // have we seen the tag?
    obj[tag].push(file.id) :  // yes
    obj[tag] = [file.id] // no
  )
  return obj // return the object for reduce
}, {})

console.log(result)

编辑后

const files = [{
  id: 100,
  tags: [20, 30]
}, {
  id: 200,
  tags: [20, 40]
}];

// loop over the array to make an object
const result = files.reduce((obj, file) => {
  // loop over the tags
  file.tags.forEach(
    tag => {
    obj[tag] = obj[tag] || {}  // have we seen the tag?
    obj[tag][file.id] = 1 //
  })
  return obj // return the object for reduce
}, {})

console.log(result)

创建一个函数,使用 Array.map() (idByTags) 为每个对象生成对 [tag, id]。使用 R.chain 将所有对象转换为此类对并将它们展平。按标签分组(R.head),然后映射对象(R.mapObjIndexed)并按 id 计数(R.last):

const { pipe, chain, groupBy, head, mapObjIndexed, countBy, last } = R

const idByTags = ({ id, tags }) => tags.map(tag => [tag, id])

const fn = pipe(
  chain(idByTags),
  groupBy(head),
  mapObjIndexed(countBy(last))
)

const files = [{"id":100,"tags":[20,30]},{"id":200,"tags":[20,40]}]

const result = fn(files)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>