LINQ and data projection

G

Gary Nastrasio

I have an array of data and I would like to project every 3 elements
into a new data type.

For example, I have this array:

string[] myString = {"1.0", "2.0", "3.0", "7.0", "8.0", "9.0"};


I would like to project this data into a vertex struct:

struct Vertex
{
public Vertex(float a, float b, float c){x=a;y=b;z=c;}
float x, y, z;
};


Here's a for loop that will do it, but I'd like to figure it out with LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i + 2]);



Thanks for any help.
 
F

Frans Bouma [C# MVP]

Gary said:
I have an array of data and I would like to project every 3 elements
into a new data type.

For example, I have this array:

string[] myString = {"1.0", "2.0", "3.0", "7.0", "8.0", "9.0"};


I would like to project this data into a vertex struct:

struct Vertex
{
public Vertex(float a, float b, float c){x=a;y=b;z=c;}
float x, y, z;
};


Here's a for loop that will do it, but I'd like to figure it out with LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i + 2]);


if a 2-line loop statement which is perfectly clear and readable does
the trick, why trying to do it with linq, which likely will also be at
least as complex (if possible at all)?

FB

--
------------------------------------------------------------------------
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
G

Gary Nastrasio

Here's a for loop that will do it, but I'd like to figure it out with
LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i + 2]);


if a 2-line loop statement which is perfectly clear and readable
does the trick, why trying to do it with linq, which likely will also be
at least as complex (if possible at all)?

FB


You're absolutely right about the loop being clear and readable. I
wanted to try using LINQ for the simple reason of increasing my LINQ
skill level.
 
A

Arne Vajhøj

Gary said:
I have an array of data and I would like to project every 3 elements
into a new data type.

For example, I have this array:

string[] myString = {"1.0", "2.0", "3.0", "7.0", "8.0", "9.0"};


I would like to project this data into a vertex struct:

struct Vertex
{
public Vertex(float a, float b, float c){x=a;y=b;z=c;}
float x, y, z;
};


Here's a for loop that will do it, but I'd like to figure it out with LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i + 2]);


LINQ does not seem to be the best tool to transform:

1.0
2.0
3.0
7.0
8.0
9.0

to:

1.0 2.0 3.0
7.0 8.0 9.0

which is the core of the problem.

Arne
 
F

Frans Bouma [C# MVP]

Gary said:
Here's a for loop that will do it, but I'd like to figure it out with
LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i + 2]);


if a 2-line loop statement which is perfectly clear and readable
does the trick, why trying to do it with linq, which likely will also
be at least as complex (if possible at all)?

FB


You're absolutely right about the loop being clear and readable. I
wanted to try using LINQ for the simple reason of increasing my LINQ
skill level.


Heh, then this particular task will definitely increase your skill
level, because IMHO it's pretty hard to do, at first glance. I haven't
looked at it more closely, but I think it's pretty hard to do this in
linq, because of the skipping part you have to do after you've read 3
elements from the sequence. I can think of a way with ElementAt(n), but
that is extremely inefficient (it seeks with every element), or with a
3-query process where you project the top element of the sequence into a
new anonymous type together with the rest of the sequence (although that
also sounds like impossible).

I've done a lot of linq-to-objects queries in the past months and my
experience is that for some things it's really handy, and one should
definitely use it for these situations, but in other situations, like
where it takes a while before you see how it should be done, it's often
easier to simply go for a loop, as that gets the thing done as well.

FB

--
------------------------------------------------------------------------
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
A

Anthony Jones

Frans Bouma said:
Gary said:
Here's a for loop that will do it, but I'd like to figure it out with
LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i +
2]);

if a 2-line loop statement which is perfectly clear and readable
does the trick, why trying to do it with linq, which likely will also be
at least as complex (if possible at all)?

FB


You're absolutely right about the loop being clear and readable. I
wanted to try using LINQ for the simple reason of increasing my LINQ
skill level.


Heh, then this particular task will definitely increase your skill level,
because IMHO it's pretty hard to do, at first glance. I haven't looked at
it more closely, but I think it's pretty hard to do this in linq, because
of the skipping part you have to do after you've read 3 elements from the
sequence. I can think of a way with ElementAt(n), but that is extremely
inefficient (it seeks with every element), or with a 3-query process where
you project the top element of the sequence into a new anonymous type
together with the rest of the sequence (although that also sounds like
impossible).

I've done a lot of linq-to-objects queries in the past months and my
experience is that for some things it's really handy, and one should
definitely use it for these situations, but in other situations, like
where it takes a while before you see how it should be done, it's often
easier to simply go for a loop, as that gets the thing done as well.


This is an interesting problem though and its definitely a good exercise
that can help broaden ones knowledge of LINQ.

I've been trying to solve this for the general case where all that is known
of the data source is that its an IEnumerable<T>. In this case indexers are
not available. Of course direct usuage of the Enumerator returned by
GetEnumerator could be used but I'm wondering if a solution can be described
in LINQ or with the extension methods and Lambda's.

So far I've failed miserably.
 
A

Anthony Jones

Anthony Jones said:
Frans Bouma said:
Gary said:
Here's a for loop that will do it, but I'd like to figure it out with
LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i >

This is an interesting problem though and its definitely a good exercise
that can help broaden ones knowledge of LINQ.

I've been trying to solve this for the general case where all that is
known > of the data source is that its an IEnumerable<T>. In this case
indexers are not available. Of course direct usuage of the Enumerator
returned by GetEnumerator could be used but I'm wondering if a solution
can be described in LINQ or with the extension methods and Lambda's.

So far I've failed miserably.


This seems pretty sucessful and readable:-

struct Vertex
{
public int x { get; private set; }
public int y { get; private set; }
public int z { get; private set; }
public Vertex(int a, int b, int c) : this() { x = a; y = b; z = c; }
}

var x = from i in Enumerable.Range(1, 9)
group i by (i - 1) / 3 into g
let v = g.ToArray()
select new Vertex(v[0], v[1], v[2]);

foreach (var v in x)
Console.WriteLine("x: {0}, y: {1}, z: {2}", v.x, v.y, v.z);

It would be nice to avoid the ToArray() though.
 
J

Jeroen Mostert

Anthony said:
Anthony Jones said:
Frans Bouma said:
Gary Nastrasio wrote:

Here's a for loop that will do it, but I'd like to figure it out
with LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i >

This is an interesting problem though and its definitely a good
exercise that can help broaden ones knowledge of LINQ.

I've been trying to solve this for the general case where all that is
known > of the data source is that its an IEnumerable<T>. In this
case indexers are not available. Of course direct usuage of the
Enumerator returned by GetEnumerator could be used but I'm wondering
if a solution can be described in LINQ or with the extension methods
and Lambda's.

So far I've failed miserably.


This seems pretty sucessful and readable:-

struct Vertex
{
public int x { get; private set; }
public int y { get; private set; }
public int z { get; private set; }
public Vertex(int a, int b, int c) : this() { x = a; y = b; z = c; }
}

var x = from i in Enumerable.Range(1, 9)
group i by (i - 1) / 3 into g
let v = g.ToArray()
select new Vertex(v[0], v[1], v[2]);

foreach (var v in x)
Console.WriteLine("x: {0}, y: {1}, z: {2}", v.x, v.y, v.z);

It would be nice to avoid the ToArray() though.

Easy enough, though it's hardly elegant:

var flatVertices = new double[] { 1.0, 2.0, 3.0, 7.0, 8.0, 9.0 };
var vertices =
from v in flatVertices.Select((f, i) => new { f, i = i / 3 })
group v by v.i into g
let x = g.First().f
let yg = g.Skip(1)
let y = yg.First().f
let zg = yg.Skip(1)
let z = zg.First().f
select new Vertex(x, y, z);

It's not really possible to do this elegantly in LINQ (OK, so you might get
something if you throw in recursion, but I honestly wouldn't bother). Use a
real functional language like F#, so you get the benefits of arbitrary
tuples and pattern matching.
 
A

Anthony Jones

Jeroen Mostert said:
Anthony said:
Anthony Jones said:
Gary Nastrasio wrote:

Here's a for loop that will do it, but I'd like to figure it out
with LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i >
This is an interesting problem though and its definitely a good exercise
that can help broaden ones knowledge of LINQ.

I've been trying to solve this for the general case where all that is
known > of the data source is that its an IEnumerable<T>. In this case
indexers are not available. Of course direct usuage of the Enumerator
returned by GetEnumerator could be used but I'm wondering if a solution
can be described in LINQ or with the extension methods and Lambda's.

So far I've failed miserably.


This seems pretty sucessful and readable:-

struct Vertex
{
public int x { get; private set; }
public int y { get; private set; }
public int z { get; private set; }
public Vertex(int a, int b, int c) : this() { x = a; y = b; z = c; }
}

var x = from i in Enumerable.Range(1, 9)
group i by (i - 1) / 3 into g
let v = g.ToArray()
select new Vertex(v[0], v[1], v[2]);

foreach (var v in x)
Console.WriteLine("x: {0}, y: {1}, z: {2}", v.x, v.y, v.z);

It would be nice to avoid the ToArray() though.

Easy enough, though it's hardly elegant:

var flatVertices = new double[] { 1.0, 2.0, 3.0, 7.0, 8.0, 9.0 };
var vertices =
from v in flatVertices.Select((f, i) => new { f, i = i / 3 })
group v by v.i into g
let x = g.First().f
let yg = g.Skip(1)
let y = yg.First().f
let zg = yg.Skip(1)
let z = zg.First().f
select new Vertex(x, y, z);

It's not really possible to do this elegantly in LINQ


If elegance was the only criteria then the my existing attempt is pretty
good, its just that it would be nice not be creating and throwing away an
array per vertex.
(OK, so you might get something if you throw in recursion, but I honestly
wouldn't bother). >Use a real functional language like F#, so you get the
benefits of arbitrary tuples and pattern matching.

F# may be a solution if someone could find a way to explain it properly to
non-functional thinkers. What would an F# solution look like?
 
A

Anthony Jones

Anthony Jones said:
Jeroen Mostert said:
Anthony said:
message Gary Nastrasio wrote:

Here's a for loop that will do it, but I'd like to figure it out
with LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i >
This is an interesting problem though and its definitely a good
exercise that can help broaden ones knowledge of LINQ.

I've been trying to solve this for the general case where all that is
known > of the data source is that its an IEnumerable<T>. In this case
indexers are not available. Of course direct usuage of the Enumerator
returned by GetEnumerator could be used but I'm wondering if a solution
can be described in LINQ or with the extension methods and Lambda's.

So far I've failed miserably.


This seems pretty sucessful and readable:-

struct Vertex
{
public int x { get; private set; }
public int y { get; private set; }
public int z { get; private set; }
public Vertex(int a, int b, int c) : this() { x = a; y = b; z = c; }
}

var x = from i in Enumerable.Range(1, 9)
group i by (i - 1) / 3 into g
let v = g.ToArray()
select new Vertex(v[0], v[1], v[2]);

foreach (var v in x)
Console.WriteLine("x: {0}, y: {1}, z: {2}", v.x, v.y, v.z);

It would be nice to avoid the ToArray() though.

Easy enough, though it's hardly elegant:

var flatVertices = new double[] { 1.0, 2.0, 3.0, 7.0, 8.0, 9.0 };
var vertices =
from v in flatVertices.Select((f, i) => new { f, i = i / 3 })
group v by v.i into g
let x = g.First().f
let yg = g.Skip(1)
let y = yg.First().f
let zg = yg.Skip(1)
let z = zg.First().f
select new Vertex(x, y, z);

It's not really possible to do this elegantly in LINQ


If elegance was the only criteria then the my existing attempt is pretty
good, its just that it would be nice not be creating and throwing away an
array per vertex.


Actually taking a look at your code again it does fix a bug in my solution
where the grouping value is all wrong.
 
M

Michael C

Gary Nastrasio said:
Here's a for loop that will do it, but I'd like to figure it out with
LINQ:

for(int i = 0 ; i < myString.Length ; i += 3)
Vertex v = new Vertex(myString, myString[i + 1], myString[i + 2]);


Linq has a lot of holes that are not that difficult to come accross. If
you're going to use it you need to get used to the idea of adding your own
linq functions. In this case you could write a function to grab a certain
numer of elements from the source. The end result would look something like
this. Note the conversion to integer which your original code doesn't have.

myString.Select(i => Convert.ToInt32(i)).GrabChunk((i, j, k) => new
Vertex(i, j, k));



The function would look something like this. Note I don't have vs2008 here
to test this so some fixes will undoubtedly be needed. We can write a
function for 2, 3 and 4 arguements, for obvious reasons we don't need one
for 1 arguement :)


//for 2 arguements:
public static IEnumerable<TResult> GrabChunk<TIn, TResult>(this
IEnumerable<TIn> source, Func<TIn, TIn, TResult> selector)
{
IEnumerator<TIn> en = source.GetEnumerator();
TIn a, b;
if(en.MoveNext())
{
a = en.Current;
if(en.MoveNext())
{
b = en.Current;
yield return selector(a, b);
}
}
}

//for 3 arguements:
public static IEnumerable<TResult> GrabChunk<TIn, TResult>(IEnumerable<TIn>
source, Func<TIn, TIn, TIn, TResult> selector)
{
IEnumerator<TIn> en = source.GetEnumerator();
TIn a, b, c;
if(en.MoveNext())
{
a = en.Current;
if(en.MoveNext())
{
b = en.Current;
if(en.MoveNext())
{
c = en.Current;
yield return selector(a, b, c);
}
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top