metonym / format-fuse.js

Utility to format matching fuse.js results for easier text highlighting
MIT License
3 stars 1 forks source link

When using Fuse's useExtendedSearch, format-fuse can return overlapping matches #42

Open fpintos opened 7 months ago

fpintos commented 7 months ago

Performing a Fuse extended search for 'sao paulo' on an item that contains the text 'SAO PAULO' yields matches for [ [ 0, 2 ], [ 1, 2 ], [ 4, 8 ] ], and format-fuse returns them as SAO, AO an PAULO. Rendering those as shown in the readme causes the UI to show "SAOAO PAULO". Ideally, the resulting value would eliminate overlapping matches.

Example:

const Fuse = require('fuse.js');
const formatFuseJs = require('format-fuse.js').default;
const util = require('util');

const r = new Fuse([{ name: 'SAO PAULO' }], {
    keys: ['name'],
    includeMatches: true,
    includeScore: true,
    useExtendedSearch: true,
})
    .search('sao paulo')
    .map(s => ({ ...s, formatted: formatFuseJs([s])[0] }));

console.log(util.inspect(r, { showHidden: false, depth: null, colors: true }));

Actual:

[
  {
    item: { name: 'SAO PAULO' },
    refIndex: 0,
    matches: [
      {
        indices: [ [ 0, 2 ], [ 1, 2 ], [ 4, 8 ] ],
        value: 'SAO PAULO',
        key: 'name'
      }
    ],
    score: 0.06403390388180329,
    formatted: {
      name: [
        { text: 'SAO', matches: true },
        { text: 'AO', matches: true },
        { text: ' ', matches: false },
        { text: 'PAULO', matches: true }
      ]
    }
  }
]

Expected:

[
  {
    item: { name: 'SAO PAULO' },
    refIndex: 0,
    matches: [
      {
        indices: [ [ 0, 2 ], [ 1, 2 ], [ 4, 8 ] ],
        value: 'SAO PAULO',
        key: 'name'
      }
    ],
    score: 0.06403390388180329,
    formatted: {
      name: [
        { text: 'SAO', matches: true },
        { text: ' ', matches: false },
        { text: 'PAULO', matches: true }
      ]
    }
  }
]
fpintos commented 7 months ago

Suggestion will be to process match indices with this function:

function getNonOverlappingSets(originalSet) {
    const sets = [...originalSet];
    // Sort the sets by the first element of each set
    sets.sort((a, b) => a[0] - b[0]);

    let result = [sets[0]];

    for (let i = 1; i < sets.length; i++) {
        // Get the last set in the result array
        let lastSet = result[result.length - 1];

        // If the current set does not overlap with the last set, add it to the result
        if (sets[i][0] > lastSet[1]) {
            result.push(sets[i]);
        }
    }

    return result;
}