You don't specify how the ones with a match should be sorted. One possibility is to sort by the number of matches. It turns out that this is fairly simple. My first pass looks like this:
const countMatches = (target) => ({tags}) =>
tags .reduce ( (n, tag) => target .includes (tag) ? n + 1 : n, 0)
const descendBy = (fn) => (xs) =>
xs .slice (0)
.sort( (a, b) => fn (b) - fn (a) )
const sortByTagMatches = (target) => descendBy ( countMatches (target) )
sortByTagMatches (tags) (posts) //=> sorted results
countMatches
The function countMatches simply counts the number of matches between the target (tags) an a post's tags property. There's always a tension between using the most specific code that gets the job done and a more generic, reusable version. But here the difference between the more generic function:
const countMatches = (target, name) => (o) =>
o[name] .reduce ( (n, x) => target .includes (x) ? n + 1 : n, 0)
and the specific one:
const countMatches = (target) => ({tags}) =>
tags .reduce ( (n, tag) => target .includes (tag) ? n + 1 : n, 0)
is so slight -- and the usage difference between them is nearly as minor -- that if there's any chance I might want to reuse this functionality elsewhere in my application, I would choose this generic one.
There is another simplification we could incorporate:
const countMatches = (target, name) => (o) =>
o[name] .filter ( (x) => target .includes (x) ) .length
This has slightly simpler code. The function passed to filter is undoubtedly cleaner than the one passed to reduce. But there is a trade-off. filter creates a new array of posts, uses it only to get its length, and then throws it away. In hot code, this could conceivably be a performance problem, but mostly it just feels wrong to do so when the reduce call is not that much more complex than the filter one.
descendBy
descendBy is simple. You pass it an integer-returning function and it returns a function that itself accepts an array of values and returns a sorted version of that array. It does not modify the array in place. If you really want it to, you can just remove the slice call. This function is based on one I often use called sortBy, only with the subtraction reversed in order to descend. I might well include both in a project, although if I did, I might rename sortBy to ascendBy to make the parallel clear.
It would not be hard to make a more generic version of this function if we like. Instead of accepting a function that returns a number, we could have one that accepts a function that returns any ordered value, Dates, Strings, Numbers, and anything that implements valueOf, essentially anything that can usefully be compared using "<". (This is sometimes called the ordered -- or Ord -- type). That version might look like:
const descendBy = (fn) => (xs) =>
xs .slice (0)
.sort( (a, b, x = fn (a), y = fn (b) ) => x < y ? 1 : x > y ? -1 : 0 )
Here I'm on the fence about whether to prefer the generic version during my first pass. The specific one above really is simpler. I would probably use the specific one, knowing that it's compatible with the generic one if I ever need to replace it.
Further Simplification
descendBy seems to do too much work. It converts an Ord-returning function into a comparator, and then sorts a list using that comparator. It would be nice to break those two steps apart, making the result of descendBy a little more reusable. Here the name descend feels more right:
const descend = (fn) =>
(a, b) => fn(b) - fn(a)
const sortByTagMatches = (target) => (xs) =>
xs .slice(0) .sort (descend (countMatches (target, 'tags') ) )
We've shifted the slicing and sorting into the main function, leaving descend quite simple. And this is where I think I'd leave it. The code now looks like this:
const countMatches = (target, name) => (o) =>
o[name] .filter ( (x) => target .includes (x) ) .length
const descend = (fn) =>
(a, b) => fn(b) - fn(a)
const sortByTagMatches = (target) => (xs) =>
xs .slice(0) .sort (descend (countMatches (target, 'tags') ) )
const tags = ['one', 'two', 'three']
const posts = [{tags: ['four', 'five']}, {tags: ['one', 'six']}, {tags: ['seven']}, {tags: ['one', 'three', 'five']}, {tags: ['nine', 'two']}]
console .log (
sortByTagMatches (tags) (posts)
)
(Note that I added an additional post with two matching tags to demonstrate the additional sorting functionality.)
Why?
user633183 gave a good answer as to why we should want to break our code into small reusable functions. This just demonstrates the same process with a somewhat different idea of how the problem might break down.