1
\$\begingroup\$

With this data I am returning one object per keyword prioritising preferedDomain string on domain and then its higher rank value, it is already ordered by rank so returning one object per keyword will take care of that.

This does work and I have done some performance checks and found out that doing a for loop will take longer than this.

Is there a better approach to tackle this problem?

const preferedDomain = 'prefered'

const data = [
  { keyword: 'hey', domain: 'apple', rank: 1},
  { keyword: 'hey', domain: 'apple', rank: 2},
  { keyword: 'hey', domain: 'prefered', rank: 46},

  { keyword: 'foo', domain: 'amazon', rank: 1},
  { keyword: 'foo', domain: 'amazon', rank: 2},
  { keyword: 'foo', domain: 'amazon', rank: 3},

  { keyword: 'bla', domain: 'prefered', rank: 1},
  { keyword: 'bla', domain: 'prefered', rank: 2},
  { keyword: 'bla', domain: 'prefered', rank: 3}
]

// Object map with prefered objects only
const prefered = data.reduce((acc, obj) => {
  if (!acc[obj.keyword] && obj.domain === preferedDomain) acc[obj.keyword] = obj
  return acc
}, {})

// If keyword is on prefered object use that, otherwise the first entry
const res = Object.values(
  data.reduce((acc, obj) => {
    if (!acc[obj.keyword]) acc[obj.keyword] = prefered[obj.keyword] ?? obj
    return acc
  }, {})
)

console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; }

\$\endgroup\$
2
  • \$\begingroup\$ you can do just one pass, if you overwrite existing object (in the keyword map) when it is not prefered and the currently iterated item is. if the input was not sorted you could also just overwrite existing object when rank is better. \$\endgroup\$
    – slepic
    Commented Aug 27, 2021 at 8:13
  • \$\begingroup\$ Thanks for your response, could you demostrate the single pass option? I tried something like that but when it overwrites I always ended up with the last rank. \$\endgroup\$
    – Álvaro
    Commented Aug 27, 2021 at 9:13

1 Answer 1

1
\$\begingroup\$

To do the job in a single pass you can do it like this:

const preferedDomain = 'prefered'

const data = [
  { keyword: 'hey', domain: 'apple', rank: 1},
  { keyword: 'hey', domain: 'apple', rank: 2},
  { keyword: 'hey', domain: 'prefered', rank: 46},

  { keyword: 'foo', domain: 'amazon', rank: 1},
  { keyword: 'foo', domain: 'amazon', rank: 2},
  { keyword: 'foo', domain: 'amazon', rank: 3},

  { keyword: 'bla', domain: 'prefered', rank: 1},
  { keyword: 'bla', domain: 'prefered', rank: 2},
  { keyword: 'bla', domain: 'prefered', rank: 3}
]

const res = Object.values(
  data.reduce((acc, obj) => {
    const best = acc[obj.keyword]
    if (best) {
      const bestDomainIsPrefered = best.domain === preferedDomain
      const objDomainIsPrefered = obj.domain === preferedDomain
      if (
        (objDomainIsPrefered && !bestDomainIsPrefered)
        ||
        (objDomainIsPrefered === bestDomainIsPrefered && obj.rank < best.rank)
      ) {
        acc[obj.keyword] = obj
      }
    } else {
        acc[obj.keyword] = obj
    }
    return acc
  }, {})
)

console.log(res)

Also it might be worth to sacrifice a little performace using a foreach for the sake of readability... Even a Map instead of plain object could help here.

const map = new Map()
for (const obj of data) {
  const best = map.get(obj.keyword)
  if (!best || ...) {
    map.set(obj.keyword, obj)
  }
}

return [ ...map.values() ]

On other hand if you are obsessed with performance, you might want to avoid comparing the best.domain to preferedDomain more then once by storing the objDomainIsPrefered into the map along with the object. But I'm not sure at what point, if at all, the gain would overcome the loss for having to create more objects.

const map = new Map()
for (const obj of data) {
  const best = map.get(obj.keyword)
  if (!best || ...) {
    map.set(obj.keyword, { obj, isDomainPrefered: objDomainIsPrefered })
  }
}

return [ ...map.values() ].map((envelope) => envelope.obj)
\$\endgroup\$
2
  • \$\begingroup\$ Thanks, what does if (!best || ...) means? I get an error with that: SyntaxError: Unexpected token '...' \$\endgroup\$
    – Álvaro
    Commented Aug 27, 2021 at 14:02
  • \$\begingroup\$ @Álvaro hehe thats Just a placeholder for the conditions from the first snippet. I didn't want to write them again... \$\endgroup\$
    – slepic
    Commented Aug 27, 2021 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.