简体   繁体   中英

“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 .

In SQL terms, I'd refer to this as a "Full Outer Join on the source column." I'm finding it difficult to write code in JavaScript that works this way. 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,
  • Initialize final with arr1 values, if you don't need immutability you can directly push on arr1
  • Loop over arr2 and check if value is not available in mapper, push in final with default value
  • 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
  • Iterate array 2 and check whether the corresponding source exists. If yes skip it and if not add one.

Optionally, you may sort the result by source if you need to.

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.

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')); 

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