简体   繁体   中英

Find closest match when comparing array javascript

Let's say I have an array of objects, foo , and each of these objects has a property, bar , which is an array.

foo[0].bar = [1]
foo[1].bar = [2]
foo[2].bar = [2,4,5]
foo[3].bar = [2,4,6]
foo[4].bar = [2,4,6,7]
foo[5].bar = [2,4,6,9]
foo[6].bar = [3]

I need a way to take a test array and do a particular "best match" search like so:

mySearch([2,4,6]);     // returns [foo[3], foo[4], foo[5]]
mySearch([2,4,6,7]);   // returns [foo[4]]
mySearch([2,4,6,8]);   // returns [foo[3]]
mySearch([3,2,1]);     // returns [foo[6]]
mySearch([4]);         // returns []

Essentially, I want the objects in foo[] which match as many exact numbers in the test array as possible. Ordering is important, but the lengths of the test array or the target objects are all variable.

I can probably figure this out using an ugly amount of for-next loops and if-thens, but I feel like there should be a better way of doing it. (If it's helpful, I can provide a constant, maxLength , which is equivalent to the length of the longest array in the collection.)

Thank you!

EDIT: sorry, I had variables in the desired results mixed up a bit from a previous edit.

EDIT 2: Sorry I'm having such a difficult time describing this. The situation I'm dealing with is like reading: given a string of characters "HELLOWORL" against a standard English dictionary, I want a function that finds the longest possible word that fits: "HELLO" (skipping "HE" and "HELL" which are also in the dictionary).

Here is a try, not sure this is what you are after though.

 const foo = [{bar: [1]}, {bar: [2]}, {bar: [2,4,5]}, {bar: [2,4,6]}, {bar: [2,4,6,7]}, {bar: [2,4,6,9]}, {bar: [3]}]; function search(arr, minLengthMatch){ if (!minLengthMatch) // specify min match or use the default the arr length minLengthMatch = arr.length; return foo.filter(function(x){ var matchFound = 0; for(var i in arr){ if (x.bar.indexOf(arr[i]) == i) matchFound++; else break; // tha match seq have been broken } return matchFound>=minLengthMatch }); // change from .map to flatMap if you want a single array } console.log(search([2,4,6])); 

You could compare same length slices of your foo arrays with the input array using JSON.stringify (assuming your array values are simple like the integers in your example). Then you need to iterate over the appropriate subsets of foo based on your rules for when to match exact length, longer and shorter length arrays.

See snippet below:

 const matchBars = (objs, arr) => { let matches = objs.reduce((acc, obj) => { let len = arr.length <= obj.bar.length ? arr.length : obj.bar.length; let a = JSON.stringify(arr.slice(0, len)); let b = JSON.stringify(obj.bar.slice(0, len)); if (a === b) { if (len < arr.length) { acc.short.push(obj); } else { acc.long.push(obj); } } return acc; }, {long: [], short: []}); if (!matches.long.length) { return matches.short.sort((a, b) => b.bar.length - a.bar.length).slice(0, 1); } return matches.long; } const foo = [{bar: [1]}, {bar: [2]}, {bar: [2,4,5]}, {bar: [2,4,6]}, {bar: [2,4,6,7]}, {bar: [2,4,6,9]}, {bar: [3]}]; console.log(matchBars(foo, [2, 4, 6])); // [{bar: [2,4,6]}, {bar: [2,4,6,7]}, {bar: [2,4,6,9]}] console.log(matchBars(foo, [2, 4, 6, 8])); // [{bar: [2,4,6]}] 

I do not think there is a very simple way to do this, but for all I can see this would do the trick

 const foo = [{bar: [1]}, {bar: [2]}, {bar: [2,4,5]}, {bar: [2,4,6]}, {bar: [2,4,6,7]}, {bar: [2,4,6,9]}, {bar: [3]}, {bar: [3, 4]}]; const mySearch = searchArray => { var maxMatch = 0; const searchString = searchArray.join("") const matchingObjectsArray = foo.map(obj => { const barString = obj.bar.join("") if (barString.startsWith(searchString) || searchString.startsWith(barString)){ maxMatch = searchString.length >= barString.length ? barString.length : searchString.length return obj }else return null //if there are no matches replace obj with null }).filter(Boolean) // get rid of nulls const filteredMatches = matchingObjectsArray .filter(obj => obj.bar.length >= maxMatch) return filteredMatches } console.log(mySearch([3,4,6,9])) /* { bar: [ 3, 4 ] } ] */ 

NOTE: An earlier version of this answer had a misuse of foreach in Javascript. This answer has been edited to fix that

I think I got you now.

Here is a solution that has only 2 loops and 2 ifs:

NOTE: This solution assumes foo is a global var. It might be a good idea to pass foo, or make mySearch() a member function of foo instead, but hopefully this is enough to give you the picture.

mySearch(test) {
    var result[]; // the list of matches
    var maxLen = 0; // to be filled later
    // loop control vars.
    var j=0;
    var i=0;
    // loop 1, for searching every bar
    for(j=0;j<foo.length;j++) {
        // if 1, for knowing our max length. 
        // Could be replaced with a ?:, but I don't remember if those exist in js
        // if they do, it'd look something like
        // maxLen = foo[j].bar.length > test.length ? test.length : foo[j].bar.length;
        if(foo[j].bar.length > test.length) {
            maxLen = test.length;
        } else {
            maxLen = foo[j].bar.length;
        }

        // a flag to hold if our current bar matches
        var doesMatch = true;

        // loop 2. This one is because we have a variable amount of
        for(i =0; i<maxLen;i++) {
            doesMatch = doesMatch && (foo[j].bar[i] == test[i]); 
        }

        // if 2, see what we matched
        if(doesMatch) {
            // if we did, add it to the results
            result.push(foo[j]);
        }
    }
    // return what matched
    return result;
}

Hopefully this is efficient enough for what you're looking for.

PS with the ?: syntax you could change the line maxLen = myFoo.bar.length > test.length ? test.length : foo[j].bar.length; maxLen = myFoo.bar.length > test.length ? test.length : foo[j].bar.length; to var maxLen = foo[j].bar.length > test.length ? test.length : foo[j].bar.length; maxLen = foo[j].bar.length > test.length ? test.length : foo[j].bar.length; since maxLen is only used in the loop.

PPS You also mentioned that you already have a maxLength available, so if it serves the same purpose as my maxLen then you could use that instead.

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