How to sort array based on an object array

Asked

Viewed 50 times

0

Problem:

I’m trying to sort an array of boxes according to the tags so that the more compatible tags the more you’ll be in the first place.

tags = ['new', 'tech'];

boxes = [{
    id: 1,
    name: 'tech ipsum',
    tags: ['watch', 'tech'],
  },
  {
    id: 2,
    name: 'apple ipsum',
    tags: ['new', 'tech', 'apple'],
  },
  {
    id: 3,
    name: 'windows ipsum',
    tags: ['tech', 'apple'],
  },
];

boxes.map(box => {
  console.log(box);
  box.tags.includes(tags);
  console.log(box.tags.includes(tags));
});

I’m trying to solve this problem by creating two loops and going through the tags and counting how many combinations are and then I save the index and sort at the end, but this is not an acceptable algorithm, it has some formula or algorithm name to help solve this problem?

This would be the expected result:

[{
  id: 2,
  name: 'apple ipsum',
  tags: ['new', 'tech', 'apple'],
}, {
  id: 3,
  name: 'windows ipsum',
  tags: ['tech', 'apple'],
}, {
  id: 1,
  name: 'tech ipsum',
  tags: ['watch', 'tech'],
}]

2 answers

3

An interesting solution would be a kind of numerical ranking.

Follow an example

const tags = ['new', 'tech'];

const boxes = [{
    id: 1,
    name: 'tech ipsum',
    tags: ['watch', 'tech'],
  },
  {
    id: 2,
    name: 'apple ipsum',
    tags: ['new', 'tech', 'apple'],
  },
  {
    id: 3,
    name: 'windows ipsum',
    tags: ['tech', 'apple'],
  },
  {
    id: 4,
    name: 'linux ipsum',
    tags: ['new', 'linux', 'tech'],
  },
];

const boxRank = (objTags) => {
  /* Transforma o resultado em uma string */
  const n = objTags.map((tag) => {
    /* Identifica em qual posição do array de `tags` o item está. Se o resultado for maior que -1, retorna o index e acrescenta +1 para evitar o número 0 no ínicio. */
    const i = tags.indexOf(tag);
    
    return i > -1 ? i + 1 : 0;
  }).join('');
  
  /* Transforma em numérico */
  return parseInt(n);
};

const sortBoxesByRank = (a, b) => {
  if (boxRank(a.tags) > boxRank(b.tags)) {
    return -1;
  }
  
  if (boxRank(a.tags) < boxRank(b.tags)) {
    return 1;
  }
  
   return 0;
};

boxes.forEach((item) => {
   console.log('rank', item.name, boxRank(item.tags))
});

/* Ordena o array baseado no rank das tags de cada objeto */
boxes.sort(sortBoxesByRank);

In this example, I added one more test item but the result is what you said to expect:

[{
  id: 2,
  name: 'apple ipsum',
  tags: ['new', 'tech', 'apple'],
}, {
  id: 3,
  name: 'windows ipsum',
  tags: ['tech', 'apple'],
}, {
  id: 1,
  name: 'tech ipsum',
  tags: ['watch', 'tech'],
}]

Just do not consider the exact order of the tags, only the amount of occurrence.

For more information on how the sort, has some good answers here.

I hope I’ve helped

  • Very good your answer! Only thing I would adjust is "newBoxes = boxes.Sort(...)" because, according to the documentation, the Sort method does not return a new array.

  • 1

    My fault, it is not necessary to assign to a new variable. Removed! Thank you for the warning

3

The best thing is "divide and conquer". Let’s go in parts.

First, create a function that counts the amount of "compatible tags" in a box:

function matches(box, tags) {
  return box.tags.filter(tag => tags.includes(tag)).length;
}

With it ready, we can use the method Array.Sort, passing a comparison function. According to the documentation, this function will compare all boxes, in double... If the value returned by the function is negative, it will put the box "A" before the box "B". If you’re positive, you’ll put box "A" after box "B".

An easy way to do this is to subtract our amount of compatible boxes, so a box "A" with 2 compatible tags will always come before a box "B" with 1 compatible tag (since 1 - 2 = -1, negative):

boxes.sort((box1, box2) => matches(box2, tags) - matches(box1, tags));
  • 1

    Much leaner than my solution. + 1

  • 1

    I found your sortBoxesByRank more didactic than mine

Browser other questions tagged

You are not signed in. Login or sign up in order to post.