Home > Software design >  Is there a way to run two for loops at the same time but after an iteration is complete?
Is there a way to run two for loops at the same time but after an iteration is complete?

Time:09-08

Suppose I have 3 for loops, A, B and C Now, after an iteration of A is complete, I want to go to for loop B and after an iteration of B is complete, I want to move on loop C. However, I want to make it so that when loop B is called, loop A will still run (It wont wait for loop B to finish) and the same thing for loop B and C.

Basically if you were to compare the index values for each loop, B would always be 1 less than A and C would always be 2 less than A

CodePudding user response:

If I assume that for each value produced in the loop A that I should start a new loop B that in turn starts a new loop C AND I want the executions to run interleaved, then this does that:

public interface ICoroutine
{
    void Run<T>(IEnumerable<T> source, Action<ICoroutine, T> action);
}

public static class CoroutineEx
{
    private class Coroutine : ICoroutine
    {
        public List<IEnumerator> _enumerators = new List<IEnumerator>();

        public void Run<T>(IEnumerable<T> source, Action<ICoroutine, T> action)
        {
            _enumerators.Insert(0, source.Do(t => action(this, t)).GetEnumerator());
        }
    }

    public static void Run<T>(this IEnumerable<T> source, Action<ICoroutine, T> action)
    {
        var e = new Coroutine();
        e.Run(source, action);
        while (e._enumerators.Any())
        {
            foreach (var x in e._enumerators.ToArray())
            {
                if (!x.MoveNext())
                {
                    e._enumerators.Remove(x);
                }
            }
        }
    }

    public static void Run<T>(this IEnumerable<T> source, ICoroutine coroutine, Action<T> action)
    {
        coroutine.Run(source, (c, t) => action(t));
    }
}

Now I can write this kind of code:

Enumerable
    .Range(1, 3)
    .Run((coroutine, loop_a) =>
    {
        Console.WriteLine($"a={loop_a}");
        Enumerable.Range(1, loop_a).Run(coroutine, loop_b =>
        {
            Console.WriteLine($"a={loop_a}, b={loop_b}");
            Enumerable.Range(1, loop_b).Run(coroutine, loop_c =>
            {
                Console.WriteLine($"a={loop_a}, b={loop_b}, c={loop_c}");
            });
        });
    });

Here's the output of that run:

a=1
a=1, b=1
a=2
a=2, b=1
a=1, b=1, c=1
a=3
a=3, b=1
a=2, b=1, c=1
a=2, b=2
a=2, b=2, c=1
a=3, b=1, c=1
a=3, b=2
a=3, b=2, c=1
a=2, b=2, c=2
a=3, b=3
a=3, b=3, c=1
a=3, b=2, c=2
a=3, b=3, c=2
a=3, b=3, c=3

CodePudding user response:

For these loops to run simultaneously, you must utilize some type of asnychronous code. However, if you use asynchronous code, it may be hard to sync the timing together as you want.

I would use Andy's for loop suggestion and try to change your separate for execution blocks into Tasks that reference methods that take indices or a local foreach variable as a parameter.

The following code ensures that A, B, and C all start in order, but if you put it into a .NET Interactive notebook and run it, you will see that they do not always finish in order. You can always await the Tasks, but then again, now you're waiting for them in sequence.

int n = 5;

for (int a = 0, b = -1, c = -2; c < n; a  , b  , c  )
{
    Task aTask, bTask = Task.CompletedTask, cTask = Task.CompletedTask;

    aTask = Task.Run(() => Console.WriteLine("Do work A"));

    if (b >= 0)
        bTask = Task.Run(() => Console.WriteLine("Do work B"));

    if (c >= 0)
        cTask = Task.Run(() => Console.WriteLine("Do work C"));

    Task.WaitAll(aTask, bTask, cTask);
    Console.WriteLine($"iteration {a}");
}

CodePudding user response:

If I assume that for each value produced in the loop A that I should start a new loop B that in turn starts a new loop C AND I want the executions to run interleaved, then another option is to use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

IObservable<Unit> query =
    from a in Observable.Range(1, 3).Do(x => Console.WriteLine($"a={x}"))
    from b in Observable.Range(1, a).Do(x => Console.WriteLine($"a={a}, b={x}"))
    from c in Observable.Range(1, b).Do(x => Console.WriteLine($"a={a}, b={b}, c={x}"))
    select Unit.Default;
    
IDisposable subscription = query.Subscribe();

When I run this code, I get this:

a=1
a=1, b=1
a=2
a=1, b=1, c=1
a=2, b=1
a=3
a=2, b=1, c=1
a=2, b=2
a=3, b=1
a=2, b=2, c=1
a=3, b=1, c=1
a=3, b=2
a=2, b=2, c=2
a=3, b=2, c=1
a=3, b=3
a=3, b=2, c=2
a=3, b=3, c=1
a=3, b=3, c=2
a=3, b=3, c=3
  •  Tags:  
  • c#
  • Related