How to iterate all k-subsets of a range in a specific order?

Ali Çehreli acehreli at yahoo.com
Fri Oct 5 02:26:02 PDT 2012


On 10/05/2012 01:08 AM, Tommi wrote:
 > I can write the following in C++ to iterate through all 2-subsets of a
 > forward-range in that specific order:
 >
 > #include <iterator>
 > #include <boost/range/concepts.hpp>
 >
 > template <typename R>
 > void fun(const R& r)
 > {
 > BOOST_CONCEPT_ASSERT(( ForwardRangeConcept<R> ));
 >
 > for (auto i = std::next(std::begin(r)); i != std::end(r); ++i)
 > {
 > for (auto j = std::begin(r); j != i; ++j)
 > {
 > _cprintf("%d %d", *j, *i);
 > }
 > }
 > }
 >
 > But I can't see a generic way of accomplishing the same with D's
 > forward-ranges. The following works only if the 'front' property of the
 > range returns a reference:
 >
 > import std.range;
 > import std.stdio;
 >
 > void fun(R)(R r)
 > if (isForwardRange!R)
 > {
 > writeln("idx x y");
 >
 > auto idx = 0;
 > auto rsaved = r.save;
 >
 > for (; !r.empty; r.popFront())
 > {
 > for (auto r2 = rsaved;
 > &r2.front != &r.front; // front could return rvalue
 > r2.popFront())
 > {
 > writefln("%s : %s %s", idx, r2.front, r.front);
 > ++idx;
 > }
 > }
 > }
 >
 > void main()
 > {
 > auto r = [0, 1, 2, 3, 4];
 >
 > fun(r);
 > readln();
 > }
 >
 > Prints:
 > -------
 > idx x y
 > 0 : 0 1
 > 1 : 0 2
 > 2 : 1 2
 > 3 : 0 3
 > 4 : 1 3
 > 5 : 2 3
 > 6 : 0 4
 > 7 : 1 4
 > 8 : 2 4
 > 9 : 3 4
 >
 >
 > If you're wondering what's so special about that ordering of k-subsets,
 > it's because then:
 >
 > idx == x.nCr(1) + y.nCr(2)
 >
 > ...where nCr (n choose r) does what the homonym button in calculators 
does.
 >
 > So, is there a generic way to write a function in D that corresponds to
 > that C++ version, or is this a defect of the D's range concept?

The != operator on the two ranges works for this case:

     for (; !r.empty; r.popFront()) {
         for (auto r2 = rsaved.save; r2 != r; r2.popFront()) {
             writefln("%s : %s %s", idx, r2.front, r.front);
             ++idx;
         }
     }

I tried it with an infinite range that is implemented as a struct, which 
in turn is passed through take():

struct FibonacciSeries
{
     int first = 0;
     int second = 1;

     enum empty = false;

     @property int front() const
     {
         return first;
     }

     void popFront()
     {
         int third = first + second;
         first = second;
         second = third;
     }

     FibonacciSeries save() const
     {
         return this;
     }
}

     fun(FibonacciSeries().take(6));

That worked.

But when I tried it with an infinite range that is implemented as a 
class, the != operator failed because std.range.Take does not implement 
opEquals:

class SquaresRange
{
     int first;

     this(int first = 0)
     {
         this.first = first;
     }

     enum empty = false;

     @property int front() const
     {
         return opIndex(0);
     }

     void popFront()
     {
         ++first;
     }

     SquaresRange save() const
     {
         return new SquaresRange(first);
     }

     int opIndex(size_t index) const
     {
         immutable integerValue = first + cast(int)index;
         return integerValue * integerValue;
     }

// Does have a special opEquals:

     override equals_t opEquals(Object o)
     {
         const rhs = cast(const SquaresRange)o;
         return rhs && (first == rhs.first);
     }
}

// Unfortunately this never stops:
     fun((new SquaresRange()).take(4));

I think if Take had an opEquals() defined, it could apply == explicitly 
on the range member that it uses for its iteration.

This brings up a question: Should all range types implement opEquals() 
for "range equality" as opposed to "identity equality" of the underlying 
range (i.e. Take.source in this case).

Ali



More information about the Digitalmars-d-learn mailing list