next_permutation and cartesian product for ranges?

H. S. Teoh hsteoh at quickfur.ath.cx
Tue Oct 9 16:13:56 PDT 2012


On Tue, Oct 09, 2012 at 11:24:27PM +0200, Timon Gehr wrote:
[...]
> That's cute. =)
> flatten is in std.algorithm and is called joiner.

Ahahahaha... I knew joiner existed; why didn't I think of it. :P


> Also, you need to be careful with index types.
> I'd suggest:
> 
> import std.algorithm, std.range;
> 
> auto cartesianProd(R1,R2)(R1 range1, R2 range2)
> if (isForwardRange!R1 && isInfinite!R1 &&
>     isBidirectionalRange!(typeof(take(range2,1))) && isInfinite!R2)
> {
> 	return sequence!"n"(cast(size_t)1).map!(
> 		(size_t n)=>range1.save.take(n) // need to specify param type. DMD bug.
> 		.zip(range2.save.take(n).retro),
> 	).joiner;
> }
[...]

Ack! UFCS overload! ;-) (just kidding... it's actually a lot easier to
read with UFCS.)

And I think that trailing comma should be a ')'.

Alright, now to take care of the case where one of the ranges is not
infinite:

-------------------------------------------------------------------------------
import std.algorithm;
import std.range;

auto cartesianProd(R1,R2)(R1 range1, R2 range2)
{
    // I decided to hoist the conditions in here 'cos the cases overlap.
    // Probably still need to factor them and put them in the sig
    // constraint. But hey, this works for now.
    static if (isForwardRange!R1 && isInfinite!R1 &&
        isBidirectionalRange!(typeof(take(range2,1))) && isInfinite!R2)
    {
        // Courtesy of Timon Gehr
        return sequence!"n"(cast(size_t)1)
            .map!((size_t n) => range1.save.take(n)
                                .zip(range2.save.take(n).retro))
            .joiner;
    }
    else static if (isInputRange!R1 && !isInfinite!R1 && isForwardRange!R2)
    {
	// A one-liner! Gotta love D's functional programming features!
        return range2.map!((ElementType!R2 a) => zip(range1.save, repeat(a)))
            .joiner;
    }
    else static if (isInputRange!R2 && !isInfinite!R2 && isForwardRange!R1)
    {
	// I could've aliased this to the previous case with tuples
	// swapped, but why bother when you can do it the direct way?
        return range1.map!((ElementType!R1 a) => zip(repeat(a), range2.save))
            .joiner;
    }
    else static assert(0);
}

unittest {
    // A simple test case: Cartesian product of the set of natural numbers with
    // itself.
    auto N = sequence!"n"(0);
    auto N2 = cartesianProd(N, N);

    assert(map!"[a[0],a[1]]"(N2.take(10)).equal([[0, 0], [0, 1], [1, 0],
        [0, 2], [1, 1], [2, 0], [0, 3], [1, 2], [2, 1], [3, 0]]));

    // Let's prove that it works for more than just the natural numbers
    auto odds = sequence!"2*n+1"(0);
    auto evens = sequence!"2*n"(0);

    assert(map!"[a[0],a[1]]"(cartesianProd(odds, evens).take(10))
        .equal([[1, 0], [1, 2], [3, 0], [1, 4], [3, 2], [5, 0], [1, 6], [3, 4],
                [5, 2], [7, 0]]));

    // Test finite cases
    auto arr = [1,2,3];
    assert(cartesianProd(arr,N)
        .take(10)
        .map!"[a[0],a[1]]"
        .equal([[1, 0], [2, 0], [3, 0], [1, 1], [2, 1], [3, 1], [1, 2], [2, 2],
                [3, 2], [1, 3]]));

    assert(cartesianProd(N,arr)
        .take(10)
        .map!"[a[0],a[1]]"
        .equal([[0, 1], [0, 2], [0, 3], [1, 1], [1, 2], [1, 3], [2, 1], [2, 2],
                [2, 3], [3, 1]]));

    auto barr = [4,5,6];
    assert(cartesianProd(arr,barr)
        .map!"[a[0],a[1]]"
        .equal([[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], [2, 6],
                [3, 6]]));
}

// To keep Andrei happy:
// vim:set ts=4 sw=4 expandtab:
-------------------------------------------------------------------------------

Destroy!!


T

-- 
Nothing in the world is more distasteful to a man than to take the path that leads to himself. -- Herman Hesse


More information about the Digitalmars-d mailing list