简体   繁体   English

使用JavaScript的“完全外部联接”

[英]“Full outer join” using JavaScript

I don't mind using a library to solve this problem, especially if it simplifies the code. 我不介意使用库来解决此问题,尤其是如果它简化了代码。

I've got some data like this: 我有一些这样的数据:

 [{source: 'b', foo: 'bar'},
   {source:'d', foo: 'baz'}]

And I've got another array like this: 我还有另一个像这样的数组:

['b', 'c', 'e']

I'd like to process these two arrays and get this as output: 我想处理这两个数组并将其作为输出:

 [{source: 'b', foo: 'bar'},
    {source: 'c', foo: 'someDefaultValue'}, 
    {source:'d', foo: 'baz'}, 
    {source: 'e', foo: 'someDefaultValue'}]

To elaborate, if data is in the first array, it should remain in the result. 详细地说,如果数据在第一个数组中,则应保留在结果中。 If data is in the second array, it should appear in the result with default values. 如果数据在第二个数组中,则它应以默认值出现在结果中。 I want the result to be sorted by source . 我希望结果按source排序。

In SQL terms, I'd refer to this as a "Full Outer Join on the source column." 用SQL术语,我将其称为“源列上的完全外部联接”。 I'm finding it difficult to write code in JavaScript that works this way. 我发现很难以这种方式在JavaScript中编写代码。 How would I get the result given the two inputs? 给定两个输入,如何得到结果?

You could take two arrays of object and a key on which the join is taking place an collect all values in a map. 您可以采用两个对象数组和一个进行联接的键来收集映射中的所有值。

 function fullOuterJoin(left, right, on) { const getV = o => o[on], getMap = (m, o) => m.set(getV(o), Object.assign(m.get(getV(o)) || {}, o)); return Array .from(left.reduce(getMap, right.reduce(getMap, new Map)).values()) .sort((a, b) => getV(a).localeCompare(getV(b))); } var left = [{ source: 'b', foo: 'bar' }, { source:'d', foo: 'baz' }], source = ['b', 'c', 'e'], right = source.map(source => ({ source, foo: 'someDefaultValue' })), result = fullOuterJoin(left, right, 'source'); console.log(result); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

You can do something like this 你可以做这样的事情

  • Create a Map with source as key, 创建一个以源为键的Map
  • Initialize final with arr1 values, if you don't need immutability you can directly push on arr1 使用arr1值初始化final ,如果您不需要不变性,则可以直接按下arr1
  • Loop over arr2 and check if value is not available in mapper, push in final with default value 循环访问arr2并检查映射器中的值是否不可用,以默认值压入final
  • Sort based on source 根据来源排序

 let arr1 = [{source: 'b', foo: 'bar'}, {source:'d', foo: 'baz'}] let arr2 = ['b', 'c', 'e'] let final = [...arr1] let mapper = new Map(arr1.map(v=>[v.source,v.source])) arr2.forEach(val=>{ if(!mapper.has(val)){ final.push({source:val, foo:'default'}) } }) final.sort((a,b)=> a.source.localeCompare(b.source)) console.log(final) 

I am assuming you want to combine the result into a new array instead of modifying inputs. 我假设您想将结果合并到一个新数组中,而不是修改输入。

  • Index source values from array 1 索引array 1源值
  • Iterate array 2 and check whether the corresponding source exists. 迭代array 2并检查相应的源是否存在。 If yes skip it and if not add one. 如果是,请跳过它,否则请添加一个。

Optionally, you may sort the result by source if you need to. (可选)您可以根据需要按source对结果进行排序。

Here you go: 干得好:

 let data1 = [{ source: 'b', foo: 'bar' }, { source: 'd', foo: 'baz' }]; let data2 = ['b', 'c', 'e']; let indices = []; let data3 = [...data1]; data1.forEach(token => indices.push(token.source)); data2.forEach(token => { if (!indices.includes(token)) { data3.push({ source: token, foo: 'default' }) } }); console.log(data3); 

Not sure there's any super easy way of doing this without just writing it up yourself. 不知道是否有任何超级简单的方法可以不自己编写。

 const data = [ {source: 'b', foo: 'bar'}, {source: 'd', foo: 'baz'} ]; const other = ['b', 'c', 'e']; const defaultValue = 'someDefaultValue'; const joined = [...data]; other.forEach(source => { if (!data.some(item => item.source === source)) { joined.push({source, foo: defaultValue}); } }); console.log(joined); 

So say that arrays are called first and second in the order which you wrote. 可以这么说,数组按照您编写的顺序分别称为firstsecond

then I think this is the solution 那么我认为这是解决方案

let result = [...first];
for(let letter of second) {
    let contains = false;
    for(let obj of first) {
        if(obj.source == letter) {
            contains = true;
            result = [...result, obj]
        }
    }
    if(!contains) result = [...result, {source: letter, bar: "defaultValue"}] 
} 
result = result.sort((a, b) => a.source < b.source ? -1 : (a.source == b.source ? 0 : 1));
result = Array.from(new Set(result))

I didn't want to go too complex, and didn't want to use newer syntax, but here's more of a helper function, more generic for people to use and expand on. 我不想太复杂,也不想使用较新的语法,但是这里有更多的辅助功能,供人们使用和扩展。 Hopefully is straight forward enough. 希望足够直接。 This only sorts properly for string values. 这仅对字符串值正确排序。 It is immutable so doesn't change the original arrays. 它是不可变的,因此不会更改原始数组。

 var arrLeft = [ {source: 'b', foo: 'bar'}, {source:'d', foo: 'baz'} ]; var arrRight = ['b', 'c', 'e']; var fullJoin = function(arr1, arr2, key, defaultVal) { // Create deep copy array of the left side to include all... var newArr = JSON.parse(JSON.stringify(arr1)); var newEle; // Loop through right side to make sure to include all for (var i = 0; i < arr2.length; i++) { // If it doesn't exist in left side, add it with default value if (!newArr.find(function(p) { return p[key] === arr2[i]; })) { newEle = {}; newEle[key] = arr2[i]; newEle.foo = defaultVal; newArr.push(newEle); } } // Sort on key alphabetically return newArr.sort(function(a, b) { var sourceA = a[key].toLowerCase(); var sourceB = b[key].toLowerCase(); switch(true) { case sourceA > sourceB: return 1; case sourceA < sourceB: return -1; default: return 0; } }); }; // Call like so console.log(fullJoin(arrLeft,arrRight,'source','somedefault')); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM