简体   繁体   中英

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

Just to clarify this is what I mean by "inverted map":

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

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

I have this object representing a file:

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

Given a list of files I need to build a map that allows me to find all files with a given tag.

From this:

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

To that:

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

I ended up with this code which does the job:

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 }
//=> }

Question: am I missing any functional programming concepts that would have allowed me to express this better?

Create an a function that generates pairs [tag, id] for each object, using a Array.map() ( idByTags ). Using R.chain convert all objects to such pairs and flatten them. Group by the tag (R.head), and then map the object (R.mapObjIndexed) and count by the 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>

not sure why you would need ramda, can do it with reduce and 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)

AFTER YOUR EDIT

 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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM