4

Hey I'm breaking my head over something that should be very simple. I have a few arrays

//input

var array1 = ["white", "white", "yellow"];
var array2 = ["white", "white", "yellow", "red"];
var array3 = ["white", "white", "yellow", "orange"];

//desired output

var result = ["white", "white", "yellow", "red", "orange"];

This should be a simple problem but I just haven't been able to wind my head around it. I tried by using a snapshot of the first array, then see if the color was already in the snapshot array remove it from the snapshot, put it in another snapshot etc... but I ended up with lines and lines of code. Couldn't even get it to work since I was deleting all "white" colors from the snapshot instead of just one and other things where going wrong.

Cans someone give me a second perspective, cause I'm running against a wall atm

My last attempt as asked to provide the code

    let attacks = entry.attacks;
    if(attacks !== undefined){
        let lastSnapshot = [];

        attacks.forEach(attack => {
            if(lastSnapshot.length === 0){
                attack.forEach(attackColor => {
                    lastSnapshot.push(attackColor)
                })                        
            }else{
                let newSnapshot = [];
                attack.forEach(attackColor => {
                    var start_index = lastSnapshot.findIndex(attackColor)
                    if(start_index !== -1){
                        var number_of_elements_to_remove = 1;
                        lastSnapshot.splice(start_index, number_of_elements_to_remove);                                
                    }

                    newSnapshot.push(attackColor)
                })
                lastSnapshot = newSnapshot;                              
            }
        })
    }
kenny
  • 1,157
  • 1
  • 16
  • 41

2 Answers2

4

You could use reduce for the arrays and a forEach for single items of the array for adding items to r.

Then a hash table is used to store the visited items and their last index of the temporary result array r. If no item is found, the actual value is pushed.

var array1 = ["white", "white", "yellow"],
    array2 = ["white", "white", "yellow", "red"],
    array3 = ["white", "white", "yellow", "orange"],
    result = [array1, array2, array3].reduce((r, a) => {
        var indices = Object.create(null);
        a.forEach(b => {
            var p = r.indexOf(b, indices[b] || 0);
            indices[b] = p === -1 ? r.push(b) : p + 1;
        });
        return r;
    });
    
console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • `var p = r.includes(b); if (!p) { r.push(b); } else { indices[b] = p; }` Is a bit easier to read to me and does the same I believe – StudioTime Aug 05 '18 at 15:54
  • i need the index of the found item for the next search. – Nina Scholz Aug 05 '18 at 15:55
  • Thanks Nina your code works perfectly and is vanilla javascript – kenny Aug 05 '18 at 15:56
  • @NinaScholz Yeah I see what you're doing now, interesting that I can't get it to fail without the index, but Array.includes() can take an index - anyhow, good answer – StudioTime Aug 05 '18 at 16:00
  • 1
    @NinaScholz My concept is not clear about this thing, can you give me some hint about this when we should use `Object.create(null)` than `{}`? – Nishant Dixit Aug 05 '18 at 16:33
  • 1
    I had to stare at that for a while to figure it out, but it's *really* nice. +1 – Mark Aug 05 '18 at 16:38
  • 1
    `Object.create(null)` chrates a really empty object without prototypes like `toString`. by using this empty object, you could use it if the values are `toString` for caching the index. the plain object `{}` does not work for all kind of keys, because of the prototypes. the decision when to use an empty object instead of a plain object is maily driven by the expected values. if unclear use the empty one to be on the safer side. – Nina Scholz Aug 05 '18 at 16:41
  • Yesss, Nina's razor sharp mind strikes again. Nice one! – Carsten Massmann Aug 05 '18 at 17:54
1

You can try this way also with Set, Spread and forEach.

var array1 = ["white", "white", "yellow"];
var array2 = ["white", "white", "yellow", "red"];
var array3 = ["white", "white", "yellow", "orange"];
var merged = Array.from(new Set(array2.concat(array3)));
var desired = [...array1];
merged.forEach(function(element, index) {
  if (array1.indexOf(element) == -1) {
    desired.push(element);
  }
})
console.log(desired)

Spread Syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

forEach: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

A l w a y s S u n n y
  • 36,497
  • 8
  • 60
  • 103
  • Thanks your code works and luckily I can use the spread operator in my little project. However Nina Scholz code works as well and she is using vanilla javascript. I feel inclined giving the right answer to her ... hope you don't mind – kenny Aug 05 '18 at 15:55
  • @kenny nope didn't mind because I've learn a lot on SO from Nina Scholz, she is my idol in JS. Glad it helps you somehow. Best of luck. `FYI:` my version is also Vanilla JS :) – A l w a y s S u n n y Aug 05 '18 at 15:58
  • it is ? cause when using the ... operator I need to add the following to my projects "plugins": [ "transform-async-to-generator", "transform-class-properties", "transform-object-rest-spread" ], btw: I'm a java developer learning react javascript so there is a lot not evident to me yet – kenny Aug 05 '18 at 16:06
  • 1
    please add as `var array2 = ["white", "white", "yellow", "red", "red"];` and try your algo. as it looks like, it never adds the second `"red"` because you just check with `includes`. – Nina Scholz Aug 05 '18 at 16:08