2

Is there anyway to achieve the following in C# (or any other ,Net language)?

public double nestedParamArrayLoop(function delegatedFunction, LoopControllers loopControllers)
{
    double total = 0;

    NestedLoopControllers loopControllers = new NestedLoopControllers(loopController, loopMaxes);

    foreach(LoopController loopController in loopControllers);
    {
        nestedfor (loopController)
        {
            // this line results in one or more loopControllers being passed in
            total += delegatedFunction(loopController);
        }
    }

    return total;
}

public double delegatedFunction(params int[] arguments)
{
    // dummy function to compute product of values
    long product = 1;
    for (int i = 0; i < arguments.Count ; i++)
        product *= arguments[i];
    return product;
}

Where delegatedFunction is called with a variable number of parameters, according to the number of controllers in the array loopControllers? Each loopController would contain a start value, a max value and an increment value (i.e. template a for loop).

The syntax above doesn't quite work as I'm not sure any exists to capture this paradigm. But the idea is that you can specify an arbitrary number of nested loops and then the nesting is done for you by the compiler (or the runtime). So it's a kind of templated nesting where you define the loop conditions for an arbitrary number of loops and the environment constructs the loops for you.

For example

  • NestedParamsArrayLoop(delegatedFunction, loopContoller1); results in iterated calls to delegatedFunction(values for loopValue1);
  • NestedParamsArrayLoop(delegatedFunction, loopContoller1, loopController2); results in iterated calls to delegatedFunction(values for loopValue1, values for loopValue2);
  • NestedParamsArrayLoop(delegatedFunction, values for loopContoller1, values for values for loopController2, loopController3); results in iterated calls to delegatedFunction(loopValue1, values for loopValue2, values for loopValue3);

The goal of this is to avoid writing separate functions with different numbers of arguments but where the actual guts of the logic is common across them.

I hope I've done a decent job of explaining this but if not please ask!

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Bit Racketeer
  • 493
  • 1
  • 3
  • 13
  • I suppose you may achieve this by a recursive method. – MakePeaceGreatAgain Nov 22 '17 at 07:56
  • 1
    The "syntax" in your question looks more like pseudo code then like c#... – Zohar Peled Nov 22 '17 at 07:56
  • Well yes it has to be pseudo-code, because I don't know how to write the real thing (if it's even possible). – Bit Racketeer Nov 22 '17 at 07:57
  • So if I pass 3 "controllers" that will result in 3 nested for loops (say with loop variables i,j,k), and inside all those loops is call to `delegateFunction(i,j,k)`? – Evk Nov 22 '17 at 07:58
  • 1
    Seems like an XY-problem. You think the solution of your problem is Y, thus you want to solve that, instead of asking what the actual solution for your original problem X was. So what actual problem are you trying to solve? – MakePeaceGreatAgain Nov 22 '17 at 07:58
  • Evk, yes, exactly. So if I pass in one LoopController then you get delegateFunction(i). If I pass in two LoopControllers then you get delegateFunction(i,j). And with three as you say, delegateFunction(i,j,k). With four (i,j,k,l). etc. Sorry if I haven't explained this perfectly but I find it hard to express. And yes the code is pseudocode (and even has an error in it). – Bit Racketeer Nov 22 '17 at 08:02
  • HimBromBeere The exact algorithm I trying to solve is a generic approach to 2D, 3D, 4D interpolation. The interpolation works exactly the same way per dimension and results in wasteful/risky duplication of code. I'm not sure I understand how a recursive function would work when the number of parameters is variable. – Bit Racketeer Nov 22 '17 at 08:04
  • All - thanks for very quick responses. It will be great to solve this. – Bit Racketeer Nov 22 '17 at 08:05
  • @HimBromBeere I can see here why you say it seems like an XY-problem. There is indeed a specific issue I'm working on (interpolation in any number of dimensions) but the question of variable-depth nesting of loops has come up in other areas too, hence I posed it as a theoretical/syntactic question. – Bit Racketeer Nov 23 '17 at 09:46

3 Answers3

1

I think this is pretty much what you want to do.

Start with a LoopController definition:

public class LoopController : IEnumerable<int>
{
    public int Start;
    public int End;
    public int Increment;

    private IEnumerable<int> Enumerate()
    {
        var i = this.Start;
        while (i <= this.End)
        {
            yield return i;
            i += this.Increment;
        }
    }

    public IEnumerator<int> GetEnumerator()
    {
        return this.Enumerate().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Now you can define NestedParamArrayLoop like so:

public double NestedParamArrayLoop(Func<int[], double> delegatedFunction, List<LoopController> loopControllers)
{
    double total = 0;

    foreach (LoopController loopController in loopControllers)
    {
        total += delegatedFunction(loopController.ToArray());
    }

    return total;
}

Now the rest is easy:

void Main()
{
    var loopControllers = new List<LoopController>()
    {
        new LoopController() { Start = 4, End = 10, Increment = 2 },
        new LoopController() { Start = 17, End = 19, Increment = 1 },
    };
    Console.WriteLine(NestedParamArrayLoop(DelegatedFunction, loopControllers));
}

public double DelegatedFunction(params int[] arguments)
{
    long product = 1;
    for (int i = 0; i < arguments.Count(); i++)
        product *= arguments[i];
    return product;
}

You could even define NestedParamArrayLoop as this:

public double NestedParamArrayLoop(Func<int[], double> delegatedFunction, List<LoopController> loopControllers)
{
    return
        loopControllers
            .Select(lc => delegatedFunction(lc.ToArray()))
            .Sum();
}

Is this more like what you're after?

public double NestedParamArrayLoop(Func<int[], double> delegatedFunction, List<LoopController> loopControllers)
{
    Func<IEnumerable<int>, IEnumerable<IEnumerable<int>>> getAllSubsets = null;
    getAllSubsets = xs =>
        (xs == null || !xs.Any())
            ? Enumerable.Empty<IEnumerable<int>>()
            : xs.Skip(1).Any()
                ? getAllSubsets(xs.Skip(1))
                    .SelectMany(ys => new[] { ys, xs.Take(1).Concat(ys) })
                : new[] { Enumerable.Empty<int>(), xs.Take(1) };

    double total = 0;


    foreach (LoopController loopController in loopControllers)
    {
        foreach (var subset in getAllSubsets(loopController))
        {
            total += delegatedFunction(subset.ToArray());
        }
    }

    return total;
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Wow let me try that.... oh man that's really clever. I had an idea that a yield might be involved.... thanks!! Will get back to you later today... – Bit Racketeer Nov 22 '17 at 08:08
  • Nice. I was starting to write something similar but you where faster. One thing to note is that your implementation requires Start to be less than Stop - otherwise you get an infinite loop in your Enumerate() function. – Zohar Peled Nov 22 '17 at 08:11
  • @BitRacketeer I thought you need another thing. There are no nested loops here – Evk Nov 22 '17 at 08:18
  • @Evk - The OP needed to expand the parameters to pass to the delegated function - I think that's what he meant by a nested loop. – Enigmativity Nov 22 '17 at 08:22
  • @Evk & Enigmativity - I want to iterate each parameter in the arbitrary-length parameter array. So with one parameter 1, 2, 3, 4. With two parameters (1,1), (1,2), (1,3), (1,4), then (2,1), (2,2), (2,3), (2,4). Then (3,1)...(3,4). And with three params (1,1,1), (1,1,2),(1,1,3),(...); then (2,1,1).(2,1,2),(2,1,3)(...) etc. I find it hard to explain in words - "arbitrary-depth nesting or a parameter array" is the closest I can get so far. – Bit Racketeer Nov 22 '17 at 08:36
  • @Enigmativity maybe a better way of describing what I am after is "dynamic nesting". That's succinct and a bit closer. – Bit Racketeer Nov 22 '17 at 08:37
  • Just found this which looks interesting https://stackoverflow.com/questions/3536833/arbitrary-number-of-nested-loops – Bit Racketeer Nov 22 '17 at 08:38
  • @BitRacketeer - I see what you're after now. Would it be right to say that you want every possible selection of the input values? From the `null` set to the full set `{1, 2, 3, 4}`, including every permutation? – Enigmativity Nov 22 '17 at 09:00
  • @BitRacketeer - I've added a further option to my answer. – Enigmativity Nov 22 '17 at 09:05
0

Since the OP asked for a solution in any .NET language, I wrote one in F# (translating @Enigmativity's answer). No NestedLoopController class required:

[[ 4 .. 2 .. 10 ]; [ 17 .. 1 .. 19 ]]
|> Seq.map (fun args ->
    (1L, args)
    ||> Seq.fold (fun a x -> a * int64 x))
|> Seq.sum
|> printfn "%d"

You can probably translate this to C# LINQ in a relatively straightforward way…

dumetrulo
  • 1,993
  • 9
  • 11
0

I think what you really need is what is called cartesian product of multiple sets. Here is good article from Eric Lippert about doing that with arbitrary number of sets in C#. So create function like this (I won't explain it because I cannot do this better than Eric did in his article):

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(params IEnumerable<T>[] sources) {
    IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };
    foreach (var source in sources) {
        var tmp = source;
        result = result.SelectMany(
            seq => tmp,
            (seq, item) => seq.Concat(new[] { item }));
    }               
    return result;
}

Then use like this:

foreach (var n in CartesianProduct(Enumerable.Range(1, 4), Enumerable.Range(1, 4))) {
    Console.WriteLine(String.Join(", ", n));
    // in your case: delegatedFunction(n);
}

outputs

1, 1
1, 2
1, 3
1, 4
2, 1
2, 2
2, 3
2, 4
3, 1
3, 2
3, 3
3, 4
4, 1
4, 2
4, 3
4, 4

It's easy to replace Enumerable.Range with your LoopController, Enumerable.Range is used just as an example.

Evk
  • 98,527
  • 8
  • 141
  • 191