www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to return range constructs?

reply "Robin" <robbepop web.de> writes:
Hiho,

for experimental and learning reasons I am working on a simple 
matrix library for D. And since ranges seem to be a cool feature 
of the D language I am currently trying to understand them and 
use them more in my codes.

So I thought that it would be a nice feature if I could create 
methods to return specific ranges for certain matrix instances, 
e.g. something like getRowRange(size_t row) or 
getColumnRange(size_t col) which returns a range to easily 
iterate through a single row- or column vector of a matrix 
instance with a simple foreach loop.

I store the matrix data within a single dimensional array for 
performance purposes instead of a jagged array:

private
{
	T[] data;
	Dimension dim;
}

The Dimension 'dim' is an object holding the number of rows and 
columns as well as some handy methods and properties such as 
isSquare etc.

So my question is: How to define such a method which returns a 
range for a single row or column vector? The range should be 
modifiable depending on whether the matrix instance is 
const/immutable or not. Or isn't that possible at all? I thought 
that it could be tricky for row vectors since their data wouldn't 
be dense in memory.

I've looked into the std.range documentation but doesn't seem to 
get a clue how to get this working. And if it works - what range 
type would fit best for my purposes - or do I need to create a 
sub-class of a certain range type specially fit for my matrix?

My try so far:

module neoLab.core.ColumnVectorRange;

import std.range;
import neoLab.core.Matrix;

final class ColumnVectorRange(T) : RandomAccessFinite!T {
	private
	{
		T[] data;
		size_t cur = 0;
		size_t length = 0;
	}

	this(const ref Matrix m, size_t row)
	in
	{
		assert(row < m.getDimension().rows, "Row index is invalid.");
	}
	body
	{
		this.data = m.data;
		this.length = m.getDimension().cols;
	}

	ColumnVectorRange!T save() {
		// What does this do?
	}

	override T opIndex(size_t index) {
		return this.data[index]; // this correct?
	}

	override T moveAt(size_t index) {
		return this.data[this.cur]; // this good?
	}

	override size_t length() {
		return this.length;
	}

	override alias opDollar = this.length; // Is this nessecary?

	override ColumnVectorRange!E opSlice(size_t, size_t) {
		// What does this do in particular?
	}
}

I highly appreciate tips and answers! =)

Thanks in advance.

Robin
Feb 28 2014
parent reply "Dicebot" <public dicebot.lv> writes:
Looks like you are over-complicating things:

http://dpaste.dzfl.pl/eaca1408dc88

import std.range;

struct ColumnVectorForwardRange(T)
{
	private
	{
		T[] data;
		size_t cur = 0;
		size_t length = 0;
	}

	ColumnVectorForwardRange!T save()  property {
		return this;
	}

	bool empty()  property {
		return !length;
	}
	
	T front()  property {
		return data[cur];
	}
	
	void popFront() {
		++cur;
		--length;
	}
}

static assert(isForwardRange!(ColumnVectorForwardRange!int));

void main() {}
Feb 28 2014
parent reply "Robin" <robbepop web.de> writes:
Hiho,

ok thanks - I haven't known that interfaces are not a "must 
extend" like in java to serve as a real interface to something. 
This is strange to me in first place but has its pros.

And when I want to build up a RandomAccessFinite I just have to 
implement the other methods as well, okay. :D
Is this performance intense to iterate over these custom ranges 
instead of iterating plainly without them or is the compiler 
optimizing the overhead away?

I have got another problem with my current implementation. My 
data field is private (as you can see from my first post) and I 
don't really want to create a public getter for it (because of 
the encapsulation) but I either don't want to copy it everytime I 
create a range for my matrix. On top of all I want to have 
seperate source files instead of one huge file (one module) where 
I can access everything (which is what I would need atm). So is 
there another way to solve this problem? Or do I really have to 
merge all my files into one big giantic file so that they are all 
in the same module?

Robin
Feb 28 2014
next sibling parent reply "Robin" <robbepop web.de> writes:
Hiho,

sorry for the double post but I can't find an edit button.

I have managed to write a custom ForwardRange based on your code 
as I wished it to behave. However, the assertion fails on 
compilation, now. The only thing I have changed is that I have 
added a constructor as well as changing the type of T[] to 
Matrix!T pointer type.

The general layout stayed the same and I don't think that a 
constructor may break the ForwardRange interface.

Do you know what's wrong here?

Here is the code:
http://dpaste.dzfl.pl/8718b09cb825

Besides that ... are there nicer workarounds to prevent using 
matrix by value instead of storing it as a pointer which is kind 
of unsafe?

Thanks in advance!

Robin
Feb 28 2014
parent reply "anonymous" <anonymous example.com> writes:
On Friday, 28 February 2014 at 23:06:24 UTC, Robin wrote:
 sorry for the double post but I can't find an edit button.
Don't worry, there is none. Replying to yourself is proper etiquette around here.
 I have managed to write a custom ForwardRange based on your 
 code as I wished it to behave. However, the assertion fails on 
 compilation, now. The only thing I have changed is that I have 
 added a constructor as well as changing the type of T[] to 
 Matrix!T pointer type.

 The general layout stayed the same and I don't think that a 
 constructor may break the ForwardRange interface.

 Do you know what's wrong here?

 Here is the code:
 http://dpaste.dzfl.pl/8718b09cb825
Lines 43, 44: --- static assert(isForwardRange!(ColumnVectorForwardRange!double(new Matrix!double(5, 5), 2))); //static assert(isForwardRange!(ColumnVectorForwardRange!double)); --- isForwardRange works on types, not values. The commented version is correct.
 Besides that ... are there nicer workarounds to prevent using 
 matrix by value instead of storing it as a pointer which is 
 kind of unsafe?
Make it a class.
Feb 28 2014
parent reply "Robin" <robbepop web.de> writes:
Hiho,

I have made matrix a struct for a better performance and since 
everybody here on the forums was complaining about that it should 
be a struct type.

When I uncomment the currently commented assertion I get linking 
errors. (these strange and nearly unreadable error messages.)

Here is a part of them:
ColumnVectorForwardRange.o: In function 
`_D6neoLab4core6Matrix13__T6MatrixTdZ6Matrix6__ctorMFNaNbNcxmxmxbZS6neoLab4core6Matrix13__T6MatrixTdZ6Matrix':
neoLab/core/Matrix.d:(.text._D6neoLab4core6Matrix13__T6MatrixTdZ6Matrix6__ctorMFNaNbNcxmxmxbZS6neoLab4core6Matrix13__T6Mat
ixTdZ6Matrix+0x31): 
undefined reference to 
`_D6neoLab4core9Dimension9Dimension6__ctorMFNaNbNcxmxmZS6neoLab4core9Dimension9Dimension'
ColumnVectorForwardRange.o: In function

... many many many lines of error messages ...

ColumnVectorForwardRange.o: In function 
`_D6neoLab4core6Matrix13__T6MatrixTdZ6Matrix6randomFxmxmdddZS6neoLab4core6Matrix13__T6MatrixTdZ6Matrix':
neoLab/core/Matrix.d:(.text._D6neoLab4core6Matrix13__T6MatrixTdZ6Matrix6randomFxmxmdddZS6neoLab4core6Matrix13__T6Matr
xTdZ6Matrix+0x145): 
undefined reference to 
`_D6neoLab4core9Dimension9Dimension4sizeMxFNaNbNdZm'
collect2: error: ld returned 1 exit status
--- errorlevel 1

Robin
Feb 28 2014
parent "anonymous" <anonymous example.com> writes:
On Friday, 28 February 2014 at 23:53:48 UTC, Robin wrote:
 I have made matrix a struct for a better performance and since 
 everybody here on the forums was complaining about that it 
 should be a struct type.
Making it a class is just a simple way to avoid the (explicit) pointer. Weigh that against your other goals and decide. Also, a pointer in itself isn't scarily unsafe.
 When I uncomment the currently commented assertion I get 
 linking errors. (these strange and nearly unreadable error 
 messages.)

 Here is a part of them:
[...]
 undefined reference to 
 `_D6neoLab4core9Dimension9Dimension6__ctorMFNaNbNcxmxmZS6neoLab4core9Dimension9Dimension'
"undefined reference" means the linker can't find that symbol. That means, you forgot to pass some file in. When compiling and linking in one step, dmd needs all source files: something like `dmd neoLab/core/ColumnVectorForwardRange.d neoLab/core/Matrix.d neoLab/core/Dimension.d` (plus any other imported files outside of phobos). rdmd is a tool that reads the imports, calls dmd on all source files, and then runs the executable. It's shipped with dmd. With rdmd it would be just `rdmd neoLab/core/ColumnVectorForwardRange.d`.
Feb 28 2014
prev sibling parent reply "Mike Parker" <aldacron gmail.com> writes:
On Friday, 28 February 2014 at 19:02:24 UTC, Robin wrote:
 Hiho,

 ok thanks - I haven't known that interfaces are not a "must 
 extend" like in java to serve as a real interface to something. 
 This is strange to me in first place but has its pros.
Actually, when using code that works with interface types, they are just like Java. // API interface Foo { ... } void doSomethingWithAFoo( Foo foo ); // User code class FooImpl : Foo { ... } But if this is what the API looks like, it's restricting all Foos to be classes, since in D structs can't extend an interface. Sometimes, that's exactly what you want to do, but in other cases it could make sense to allow structs as well. This allows more flexibility for the user to choose between heap vs. stack, for example. In D, this can be accomplished by using compile-time constructs to determine if a type implements a particular interface. So you could do away with the Foo interface completely and have something like this. // API // A convenient way to test if a type is a Foo template isFoo( T ) { enum isFoo = // use compile-time features to test for expected methods } // One way to use isFoo... void doSomethingWithAFoo( T )( T foo ) if( isFoo!T ) { ... } // Or, alternatively... void doSomethingWithAFoo( T )( T foo ) { static if( isFoo!T ) { ... } else { static assert( false, T.stringof ~ " doesn't implement all Foo methods!"); } } // User code class FooImplA { ... } struct FooImplB { ... } As long as both A & B implement all of the methods expected of a Foo, the template will match and the code will compile. To cover as many use-cases as possible, which Phobos probably should, it's useful to have both an object-based interface (interface Foo) and the template-based structural interface (isFoo). When an API need not be that all-encompassing, one or the other approach is enough.
Feb 28 2014
parent "Robin" <robbepop web.de> writes:
Hiho,

 anonimous:
Thank you for that hint with rdmd, I was already wondering what 
that was.

 Mike Parker:
Thank you very much for clearing that up for me. I really needed 
such a clear answer about this topic. So D language is highly 
advanced in meta programming and I should really begin to use it. 
=)

Robin
Mar 01 2014