0

I have a situation that so far I have not been able to find a satisfactory solution for. Below is the code on a high level.

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
  o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9};

function matched(i, j) {
  return a[i]===o[j];
}

for (var i=0; i<a.length; ++i) {
  for (var j in o) {
    if (matched(i, j)) console.log(a[i]);
  }
}

I have an array and an object. I'm looping through the array, then the object, to find a match via the function matched() which returns a boolean true or false. If the condition is true, then I log the array item. If you run the code right now (https://jsfiddle.net/thdoan/0tubbokj/), you should see numbers 1-3 and 7-9 outputted to the console.

What I'm trying to do is to output the numbers with a one-second delay in between each number. I know how to introduce a delay in between each loop iteration, but I only want to add the delay for the numbers that are printed (i.e., when matched() returns true).

Clarification: My current solution, which I'm not satisfied with, is to save the matched items to a separate array and iterate over that array with a delay, but I'm looking for a solution that does not require creating a new array.

3
  • You need recursion for that. Change the loop for() with a recursive function. If you can't, you can setTimeout(function() { console.log(result); },1000); but it's not fine. Commented Sep 19, 2016 at 8:20
  • @MarcosPérezGude can you demonstrate your solution in a fiddle or codepen because I couldn't get it to work. Thanks. Commented Sep 19, 2016 at 8:23
  • My suggestion is equal to the answer from TJCrowder. Commented Sep 19, 2016 at 9:54

5 Answers 5

3

What I'm trying to do is to output the numbers with a one-second delay in between each number.

You've also said in a comment:

...in the real app the matched set can grow to be very large, so I would rather not consume more memory if there is a solution that doesn't require outputting to a third array.

To achieve both of those goals, you have to completely abandon your for loops and instead do a chained series of setTimeouts.

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
  o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9};

function matched(i, j) {
  return a[i]===o[j];
}

// Get the property names in `o`, and start at the beginning
var keys = Object.keys(o);
var i = 0;
var keyIndex = 0;
tick();

function tick() {
  // Get the "j" value for this tick
  var j = keys[keyIndex];
  
  // Is this a match?
  var flag = matched(i, j);
  if (flag) {
    console.log(a[i]);
  }
  
  // Move to the next entry in our nested loops
  if (++keyIndex >= keys.length) {
    keyIndex = 0;
    if (++i >= a.length) {
      // Done
      return;
    }
  }

  // Continue
  if (flag) {
    // We output one, wait a second before next
    setTimeout(tick, 1000);
  } else {
    // No output, continue immediately
    tick(); // SEE NOTE BELOW
  }
}

NOTE: If there may be thousands of non-matches in a row, you might consider using a loop in tick instead of chaining to it. As of ES2015, JavaScript is supposed to have tail-call optimization and our tick wouldn't have to push itself on the stack (it would just call back to the beginning), but some JavaScript engines haven't implemented TCO yet, which could mean you'd end up with a significant stack depth.

So, with a loop:

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
  o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9};

function matched(i, j) {
  return a[i]===o[j];
}

// Get the property names in `o`, and start at the beginning
var keys = Object.keys(o);
var i = 0;
var keyIndex = 0;
tick();

function tick() {
  var match = findNextMatch();
  if (match) {
    console.log(match);
    setTimeout(tick, 1000);
  }
}

function findNextMatch() {
  var j;
  var match;
  
  while (!match && i < a.length) {
    j = keys[keyIndex];
    if (matched(i, j)) {
      match = a[i];
    }

    // Move to the next entry in our nested loops
    if (++keyIndex >= keys.length) {
      keyIndex = 0;
      ++i;
    }
  }

  return match;
}

Actually, that seems cleaner to me anyway, even without the deep stack concern.

Sign up to request clarification or add additional context in comments.

4 Comments

Looks like this is what the OP wants. Functionally, this is the best answer :D +1
T.J Crowder I like your solution, it seems elegant to me. Thanks a lot!
@10basetom: The disadvantage to it is that it has to build and keep that array of the keys in o, which you said could be in the thousands. That said, "thousands" of strings in an array isn't a big deal in modern JavaScript environments, and you can release the array when done. The alternative would be to count your way up to the current "j" each time in a for-in loop, which A) Would have higher runtime cost (though not likely to be big enough to really matter, and B) Would rely on stable for-in iteration order. (cont'd)
(cont'ing) If you don't change o, I would expect stable for-in iteration order, but it's not guaranteed (not even in ES2015 and later). I bet it's reliable cross-engine, though. :-)
2

The solution is relatively simple:

Don't worry about the logging when you're gathering the results. Instead, store all results in a new array.

Then, iterate over that result array, with a delay:

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
    o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9},
    result = [];

function matched(i, j) {
  return a[i]===o[j];
}

for (var i=0; i<a.length; ++i) {
  for (var j in o) {
    if (matched(i, j))
      result.push(a[i]); // Store the found result.
  }
}

var i = 0,
    length = result.length;

(function iterator() {
    console.log(result[i]);         // Log the current result

    if(++i < length) {              // If there are more entries in the array
        setTimeout(iterator, 1000); // Log the next entry in 1 second.
    }
})();

4 Comments

Cerbrus, thanks, but that was the solution that I wasn't satisfied with :). The array in the sample code is very simple, but in the real app the matched set can grow to be very large, so I would rather not consume more memory if there is a solution that doesn't require outputting to a third array.
How "very large" are we talking, @10basetom?
Cerbrus, up to thousands of items in o and hundreds of items in a. There could potentially be hundreds of matches, and each match can be a large object, so I'm trying to minimize memory consumption as much as possible.
@10basetom: I think you might be overestimating the amount of memory a array with (for example) 1k results takes. That said, The only way to find out what works best for you, is to try both T.J's answer, and mine.
1

Take you code but change the output to a push into another (global) array. Then use a timer to print the array content one by one.

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
  o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9},
  t = [];

function matched(i, j) {
  return a[i]===o[j];
}

// function to output first element of array
function output(){
  if(t.length > 0){ // only generate output if array t isn't empty
    console.log(t.shift());
    setTimeout(output, 1000); // recall this function after 1s
  }
}

for (var i=0; i<a.length; ++i) {
  for (var j in o) {
    if (matched(i, j)) t.push(a[i]); // store all found items inside the new array
  }
}
output();

Comments

1

An ES6 solution with a generator.

function* getCommon(array, object) {
    var set = new Set(array), k;
    for (k in o) {
        if (set.has(o[k])) {
            yield o[k];
        }
    };
}

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
    o = { a: 1, b: 2, c: 3, d: 10, e: 11, f: 12, g: 7, h: 8, i: 9 },
    generator = getCommon(a, o),
    interval = setInterval(function () {
        var item = generator.next();
        if (item.done) {
            clearInterval(interval);
        } else {
            console.log(item.value);
        }
    }, 1000);

3 Comments

If there's 10000 matches, there are 10000 timeouts pending at the same time. This method could be a memory hog for larger arrays.
The use of Set should include a compatibility disclaimer. You're also still calling setInterval in a loop, even though it will (should) only ever be called once.
@Cerbrus, now with a single call.
-1

You can create a function which includes current for, for..in loop pattern, though iterating only single a element at for loop a function call while maintaining existing for..in loop; substitute returning a Promise which recursively calls function if incremented variable is less than a.length; else recursively return function with incremented variable, or if variable is a.length, return a value to .then() at completion of process.

This approach will make calls to setTimeout() sequential, without generating a pending call to setTimeout in future, next call to function is only made when current setTimeout completes, or no matches were returned for that iteration of loops.

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
, o = {a: 1, b: 2, c: 3, d: 10, e: 11, f: 12, g: 7, h: 8, i:9};

function matched(i, j) {
  return a[i] === o[j];
}

function matcher(index = 0) {
  var len = index < a.length;
  for (var i = index; i < index + 1; ++i) {
    for (var j in o) {
      if (matched(i, j)) {
        return new Promise(function(resolve) {
          setTimeout(function() {
            resolve(a[i]);
          }, 1000)
        })
        .then(function(result) {
          console.log(`result:${result}`);
          if (len) {
            return matcher(++index)
          }
        });
      }
    }
  };
  return len ? matcher(++index) : "matcher complete";
}

matcher().then(function(complete) {
  console.log(complete);
});

1 Comment

You're potentially generating thousands of promises at the same time, each with a pending timeout. This'll be a memory hog.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.