如何从一个对象数组中提取所有可能匹配的对象数组?

时间:2016-10-03 07:20:02

标签: javascript arrays algorithm search sub-array

我有一组对象,例如

var arr = [
    {"a": "x"},
    {"b": "0"},
    {"c": "k"},
    {"a": "nm"},
    {"b": "765"},
    {"ab": "i"},
    {"bc": "x"},
    {"ab": "4"},
    {"abc": "L"}
];

让我们说我只对密钥对应var input = ["ab", "bc"]的对象感兴趣。这意味着我想以下列方式使用result[i].length == 2提取所有可能的子数组:

var result = [
    [{"ab": "i"}, {"bc": "x"}],
    [{"ab": "4"}, {"bc": "x"}] // or [{"bc": "x"}, {"ab": "4"}]
];

- 也就是说,子数组中对象的顺序绝对不重要:我只对每个子数组包含两个对象 - {"ab": ...}{"bc": ...}这一事实感兴趣。

如果我对var input = ["a","a","ab"]感兴趣,结果应该是这样的:

var result = [
    [{"a": "x"}, {"a": "nm"}, {"ab": "i"}],
    [{"a": "x"}, {"a": "nm"}, {"ab": "4"}]
];

我找不到达到预期结果的方法(假设input.length可能远大于2或3 - 甚至15-20可能还不够)没有因子级别的计算量,这不是身体上可能。有没有办法在解决这样的问题上有一些合理的表现? 重要提示:是的,很明显,对于相对较大的input.length值,理论上可能会有非常多的可能组合,但在实践中,result.length将永远是相当小(可能是100-200,我甚至怀疑它可以达到1000 ......)。但为了安全起见,我想设置一些限制(例如1000),这样一旦result.length达到此限制,该函数就会返回当前result并停止。

5 个答案:

答案 0 :(得分:1)

按字母顺序排序 arrinput,即O(nlogn),如果您能够在构建数组时进行排序,则可能会受益。

让我用一个例子解释我的想法:

var arr = [
    {"a": "x"},
    {"ab": "i"},
    {"ab": "4"},
    {"abc": "L"}
    {"bc": "x"},
];
var input = ["ab", "bc"];

input[0]中搜索arr(线性或甚至使用二分搜索来加快速度)。标记索引。

input[1]中搜索arr,但只考虑arr的子数组,从上一步标记的索引到结尾。

如果找到input的所有元素,则将其推送到results(您可以保留一个临时对象)。

现在,我们必须再次搜索input[0],因为可能有两个或更多条目共享该密钥。您将存储我之前提到的索引,以便您将从此索引再次开始搜索,并且由于arr已排序,因此您只需要检查下一个元素,依此类推。

时间复杂:

对数组进行排序(假设在构建数组时无法对它们进行排序):O(nlogn),其中n是元素arr的数量。

arrinput[0]的二进制搜索:O(logn)

现在搜索的下一步(input[1])远小于arr的长度,因此非常悲观的绑定将是O(n)。实际上它当然不是O(n),如果你想要,你也可以对input[1]进行二进制搜索,这将花费O(logm),其中m是大小arr[index_stored: -1]

此时,我们继续寻找input[0]的下一个出现,如果有的话,但是因为我们已经存储了索引,我们确切地知道从哪里开始搜索,我们必须检查下一个仅元素,这是一个不变的成本,因此O(1)。

然后我们对上面的input[1]做同样的事情,再次便宜。

现在,这一切都取决于input的长度,k,似乎k < n,以及您拥有的密钥的数量,对吧?

但假设一个正常的avergae情况,整个过程有一个复杂的时间:

  

O(nlogn)

但是,请注意,您必须支付一些额外的内存来存储索引,这取决于密钥的出现次数。使用蛮力aglrotihm,这将是更慢,你不需要支付任何额外的内存。

答案 1 :(得分:1)

也许不是最优化的方式。我可能会使用一些库来获得最终解决方案,但是这里有许多步骤可以解决一个快乐的道路。我很快就会添加一些评论。

为源数组中的单个键生成一个映射(即看到它的索引,因为我们可能有多个条目)

   function getKeyMap( src, key ){
        var idx_arr = [];
        src.forEach(function(pair,idx){ if(Object.keys(pair)[0] === key){ idx_arr.push(idx)} });
        return idx_arr;
    }

此映射必须针对您希望成为过滤一部分的所有键进行

function getKeysMap( src, keys ){
    var keys_map = [];
    keys.forEach(function(aKey){
        var aMap = getKeyMap(src,aKey);
        if( aMap.length ){
            keys_map.push(aMap);
        }

    });
    // if keys map lenght is less then keys length then you should throw an exception or something
    return keys_map;
}

然后你想建立所有可能的组合。我在这里使用递归,可能不是最优化的方式

function buildCombos( keys_map, carry, result ){
    if( keys_map.length === 0){
        result.push(carry);
        return;
    }
    var iter = keys_map.pop();
    iter.forEach(function(key){
        var cloneMap = keys_map.slice(0);
        var clone = carry.slice(0);
        clone.push(key);
        buildCombos(cloneMap, clone, result);
    });
}

然后我需要过滤结果以排除双重条目和具有重复索引的条目

function uniqueFilter(value, index, self) {
    return self.indexOf(value) === index;
}

function filterResult( map ){
    var filter = {};
    map.forEach(function(item){
        var unique = item.filter( uniqueFilter );
        if(unique.length === item.length){
            filter[unique.sort().join('')]=true;}
        });
    return filter;
}

然后我只是将生成的过滤后的地图解码为原始数据

function decodeMap( map,src ){
    var result = [];
    Object.keys(map).forEach(function(item){
        var keys = item.split('');
        var obj = [];
        keys.forEach(function( j ){
            obj.push( src[j])
        });
        result.push(obj);
    });
    return result;
}

包装器

function doItAll(arr, keys){
    // Get map of they keys in terms of numbers
    var maps = getKeysMap( arr, keys);
    // build combinations out of key map
    var combos = [];
    buildCombos(maps,[],combos);
    // filter results to get rid of same sequences and same indexes ina sequence
    var map = filterResult(combos);
    // decode map into the source array
    return decodeMap( map, arr )
}

用法:

var res = doItAll(arr, ["a","a","ab"])

答案 2 :(得分:1)

看到问题,它看起来像卡西斯产品。实际上,如果在操作之前,数据模型稍微修改一下,那么在几乎所有情况下,预期结果都是卡氏产品。但是,有一个案例(你提供的第二个例子)需要特殊处理。这就是我的所作所为:

  1. 稍微调整一下模型数据(这只会做一次),以便有适合应用cartessian产品的东西。
  2. 对待&#34;特殊情况&#34;有多个参数请求相同的字符串。
  3. 所有重要的逻辑都在cartessianProdModified之内。代码中的重要位被注释。希望它可以帮助您解决问题或至少给您一些想法。

    这里有一个fiddle,其中包含以下代码:

    var arr = [
        {"a": "x"},
        {"b": "0"},
        {"c": "k"},
        {"a": "nm"},
        {"b": "765"},
        {"ab": "i"},
        {"bc": "x"},
        {"ab": "4"},
        {"abc": "L"},
        {"dummy": "asdf"}
    ];
    
    // Utility function to be used in the cartessian product
    function flatten(arr) {
        return arr.reduce(function (memo, el) {
            return memo.concat(el);
        }, []);
    }
    
    // Utility function to be used in the cartessian product
    function unique(arr) {
        return Object.keys(arr.reduce(function (memo, el) {
            return (memo[el] = 1) && memo;
        }, {}));
    }
    
    // It'll prepare the output in the expected way
    function getObjArr(key, val, processedObj) {
        var set = function (key, val, obj) {
            return (obj[key] = val) && obj;
        };
        // The cartessian product is over so we can put the 'special case' in an object form so that we can get the expected output.
        return val !== 'repeated' ? [set(key, val, {})] : processedObj[key].reduce(function (memo, val) {
            return memo.concat(set(key, val, {}));
        }, []);
    }
    
    // This is the main function. It'll make the cartessian product.
    var cartessianProdModified = (function (arr) {
        // Tweak the data model in order to have a set (key: array of values)
        var processedObj = arr.reduce(function (memo, obj) {
            var firstKey = Object.keys(obj)[0];
            return (memo[firstKey] = (memo[firstKey] || []).concat(obj[firstKey])) && memo;
        }, {});
    
        // Return a function that will perform the cartessian product of the args.
        return function (args) {
            // Spot repeated args.
            var countArgs = args.reduce(function (memo, el) {
                    return (memo[el] = (memo[el] || 0) + 1) && memo;
                }, {}),
                // Remove repeated args so that the cartessian product works properly and more efficiently.
                uniqArgs = unique(args);
    
            return uniqArgs
                    .reduce(function (memo, el) {
                        return flatten(memo.map(function (x) {
                            // Special case: the arg is repeated: we have to treat as a unique value in order to do the cartessian product properly
                            return (countArgs[el] > 1 ? ['repeated'] : processedObj[el]).map(function (y) {
                                return x.concat(getObjArr(el, y, processedObj));
                            });
                        }));
                    }, [[]]);
        };
    })(arr);
    
    console.log(cartessianProdModified(['a', 'a', 'ab']));
    

答案 3 :(得分:1)

如果您能够使用ES6功能,则可以使用生成器来避免创建大型中间阵列。看起来你想要一组排序,行只包含唯一值。正如其他人也提到的那样,您可以通过与input个键匹配的cartesian product对象开始来解决此问题:

'use strict';

function* product(...seqs) {
    const indices = seqs.map(() => 0),
          lengths = seqs.map(seq => seq.length);

    // A product of 0 is empty
    if (lengths.indexOf(0) != -1) {
        return;
    }

    while (true) {
        yield indices.map((i, iseq) => seqs[iseq][i]);
        // Update indices right-to-left
        let i;
        for (i = indices.length - 1; i >= 0; i--) {
            indices[i]++;
            if (indices[i] == lengths[i]) {
                // roll-over
                indices[i] = 0;
            } else {
                break;
            }
        }
        // If i is negative, then all indices have rolled-over
        if (i < 0) {
            break;
        }
    }
}

生成器仅在迭代之间保存索引并根据需要生成新行。要实际加入与input键匹配的对象,首先必须创建一个查找:

function join(keys, values) {
    const lookup = [...new Set(keys)].reduce((o, k) => {
        o[k] = [];
        return o;
    }, {});

    // Iterate over array indices instead of objects them selves.
    // This makes producing unique rows later on a *lot* easier.
    for (let i of values.keys()) {
       const k = Object.keys(values[i])[0];
       if (lookup.hasOwnProperty(k)) {
           lookup[k].push(i);
       } 
    }

    return product(...keys.map(k => lookup[k]));
}

然后,您需要过滤掉包含重复值的行:

function isUniq(it, seen) {
    const notHadIt = !seen.has(it);
    if (notHadIt) {
        seen.add(it);
    }
    return notHadIt;
}

function* removeDups(iterable) {
    const seen = new Set();
    skip: for (let it of iterable) {
        seen.clear();
        for (let x of it) {
            if (!isUniq(x, seen)) {
                continue skip;
            }
        }
        yield it;
    }
}

还有全局唯一的行(set-of-sets方面):

function* distinct(iterable) {
    const seen = new Set();
    for (let it of iterable) {
        // Bit of a hack here, produce a known order for each row so
        // that we can produce a "set of sets" as output. Rows are
        // arrays of integers.
        const k = it.sort().join();
        if (isUniq(k, seen)) {
            yield it;
        }
    }
}

全力以赴:

function* query(input, arr) {
    for (let it of distinct(removeDups(join(input, arr)))) {
        // Objects from rows of indices
        yield it.map(i => arr[i]);
    }
}

function getResults(input, arr) {
    return Array.from(query(input, arr));
}

行动中:

const arr = [
    {"a": "x"},
    {"b": "0"},
    {"c": "k"},
    {"a": "nm"},
    {"b": "765"},
    {"ab": "i"},
    {"bc": "x"},
    {"ab": "4"},
    {"abc": "L"}
];

console.log(getResults(["a", "a", "ab"], arr));
/*
[ [ { a: 'x' }, { a: 'nm' }, { ab: 'i' } ],
  [ { a: 'x' }, { a: 'nm' }, { ab: '4' } ] ]
*/

强制性的jsFiddle

答案 4 :(得分:0)

您可以使用循环手动执行此操作,但您也可以使用内置函数Array.prototype.filter()来过滤数组,并Array.prototype.indexOf检查元素是否在另一个数组中:

var filtered = arr.filter(function(pair){
    return input.indexOf(Object.keys(pair)[0]) != -1;
});

这为您提供了符合条件的对象。

现在,数学语言中带有result数组的东西称为“组合”。这正是你想要的,所以我不会在这里描述。这里给出了生成数组(集)的所有组合的方法 - https://stackoverflow.com/a/18250883/3132718

以下是如何使用此功能:

// function assumes each element is array, so we need to wrap each one in an array 
for(var i in filtered) {
    filtered[i] = [filtered[i]];
}
var result = getCombinations(filtered, input.length /* how many elements in each sub-array (subset) */);

Object.keys(pair)[0]是一种无需迭代即可获取对象的第一个键的方法(https://stackoverflow.com/a/28670472