Search code examples
foreachtuplesdvariadic

Destructured iteration over variadic arguments like a tuple sequence in D


Let's say I want to process a variadic function which alternately gets passed start and end values of 1 or more intervals and it should return a range of random values in those intervals. You can imagine the input to be a flattened sequence of tuples, all tuple elements spread over one single range.

import std.meta; //variadic template predicates
import std.traits : isFloatingPoint;
import std.range;

auto randomIntervals(T = U[0], U...)(U intervals)
if (U.length/2 > 0 && isFloatingPoint!T && NoDuplicates!U.length == 1) {
    import std.random : uniform01;
    
    T[U.length/2] randomValues;
    // split and iterate over subranges of size 2
    foreach(i, T start, T end; intervals.chunks(2)) {   //= intervals.slide(2,2)
        randomValues[i] = uniform01 * (end - start) + start,
    }
    return randomValues.dup;
}

The example is not important, I only use it for explanation. The chunk size could be any finite positive size_t, not only 2 and changing the chunk size should only require changing the number of loop-variables in the foreach loop.

In this form above it will not compile since it would only expect one argument (a range) to the foreach loop. What I would like is something which rather automatically uses or infers a sliding-window as a tuple, derived from the number of given loop-variables, and fills the additional variables with next elements of the range/array + allows for an additional index, optionally. According to the documentation a range of tuples allows destructuring of the tuple elements in place into foreach-loop-variables so the first thing, I thought about, is turning a range into a sequence of tuples but didn't find a convenience function for this.

Is there a simple way to loop over destructured subranges (with such a simplicity as shown in my example code) together with the index? Or is there a (standard library) function which does this job of splitting a range into enumerated tuples of equal size? How to easily turn the range of subranges into a range of tuples?

Is it possible with std.algorithm.iteration.map in this case (EDIT: with a simple function argument to map and without accessing tuple elements)?

EDIT: I want to ignore the last chunk which doesn't fit into the entire tuple. It just is not iterated over.

EDIT: It's not, that I couldn't program this myself, I only hope for a simple notation because this use case of looping over multiple elements is quite useful. If there is something like a "spread" or "rest" operator in D like in JavaScript, please let me know!

Thank you.


Solution

  • (Added as a separate answer because it's significantly different from my previous answer, and wouldn't fit in a comment)

    After reading your comments and the discussion on the answers thus far, it seems to me what you seek is something like the below staticChunks function:

    
    unittest {
        import std.range : enumerate;
        size_t index = 0;
        foreach (i, a, b, c; [1,2,3,1,2,3].staticChunks!3.enumerate) {
            assert(a == 1);
            assert(b == 2);
            assert(c == 3);
            assert(i == index);
            ++index;
        }
    }
    
    import std.range : isInputRange;
    
    auto staticChunks(size_t n, R)(R r) if (isInputRange!R) {
        import std.range : chunks;
        import std.algorithm : map, filter;
        return r.chunks(n).filter!(a => a.length == n).map!(a => a.tuplify!n);
    }
    
    
    auto tuplify(size_t n, R)(R r) if (isInputRange!R) {
        import std.meta : Repeat;
        import std.range : ElementType;
        import std.typecons : Tuple;
        import std.array : front, popFront, empty;
        
        Tuple!(Repeat!(n, ElementType!R)) result;
    
        static foreach (i; 0..n) {
            result[i] = r.front;
            r.popFront();
        }
        assert(r.empty);
    
        return result;
    }
    

    Note that this also deals with the last chunk being a different size, if only by silently throwing it away. If this behavior is undesirable, remove the filter, and deal with it inside tuplify (or don't, and watch the exceptions roll in).