[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

microsoft.public.dotnet.framework

LINQ Conditional Where

coconet

4/8/2008 2:32:00 PM

I am trying to use LINQ to select some things from a tab-delimited
string. I have a "where" clause but I want to put some if.. logic
inside it. Right now I have this:
MyClassList =
from line in myReader.GetLines()
let lineItems = line.Split( '\t' )
where line.StartsWith( "#" )
select new MyClass
{.....}

But what I want is a "conditional where", so if the line does not
begin with "#" the select new MyClass part runs, but if it does start
with "#" then assign the line to a new string that I will use later.

As you can tell, I am new to LINQ syntax. Is this even possible?

Thanks.
7 Answers

Marc Gravell

4/8/2008 2:52:00 PM

0

That looks about right... as it stands, the "select" should only run for
those lines that satisfy the "where". My only feedback would be that I
would reverse the "let" and the "where" so that the "where" filters the
data before you run Split on it - i.e.

from line in myReader.GetLines()
where line.StartsWith( "#" )
let lineItems = line.Split( '\t' )

Were you having a problem with this?

Marc

Marc Gravell

4/8/2008 2:59:00 PM

0

Ah - right, sorry; I misread the post.

For LINQ-to-objects, you could probably do it by having a method for the
predicate (on the "where") that strores the value:

List<string> comments = new List<string>();
var filteredLines = lines.Where(s => {
if(s.StartsWith("#")) {
comments.Add(s);
return false;
}
return true;
});
var query = from line in filteredLines
let lineItems = line.Split( '\t' )
select new string[] {lineItems[0], lineItems[1], lineItems[2]};

For interest, "Push-LINQ" would also be an option here, as it is
designed to process one stream in multiple disparate ways:

http://msmvps.com/blogs/jon.skeet/archive/2008/01/04/quot-push-quot-linq-revisited-next-attempt-at-an-explan...

Marc

Marc Gravell

4/8/2008 5:25:00 PM

0

More info on push-LINQ (which is free, btw):

http://www.pobox.com/~skeet/csharp...

IEnumerable<T> only allows one bit of code to actively process a
data-feed, which makes it hard to do two (or more) different
things without getting spaghetti code.
Push-LINQ is a different design. Rather than the code
pulling data ("foreach" etc), the data-source drives the process,
pushing data through the various filters etc. In many cases
this removed the need to buffer individual records (as discussed
on Jon's blog with the "voter" scenario).

To illustrate, the following is a fully working example (requires
the MiscUtil-r232 binary); most importantly, note that it only
processes the input feed *once* (this could be a file, a database,
or anything else really). [note I've used ~, not \t, for simplicity
re posting tab literals...]

If it looks longer than you might expect, that is just because
I did a few more things for demonstration purposes.

It also shows how to use LineReader to efficiently read a file.

Marc

using System;
using System.Collections.Generic;
using MiscUtil.IO;
using MiscUtil.Linq;
using MiscUtil.Linq.Extensions;
using System.IO;

class Program
{
const string MY_FILE_CONTENTS =
@"# rough cast of flintones
#family flintstones
Fred~Flintstone~32
Wilma~Flintstone~31
#family rubble
Barney~Rubble~31
Betty~Rubble~29
#the kids
BamBam~Flintstone~2
Pebbles~Rubble~1
# pets
Dino~Flintstone~4
#eof";
static void Main()
{
// the push-LINQ start-point; a datap-producer
var source = new DataProducer<string>();

// listen to data and log and comments
// (note that we don't have to use a list here,
// we could do something more interesting as
// we see the lines)
var comments = (from line in source
where line.StartsWith("#")
select line.TrimStart('#').Trim()).ToList();

// listen to data and create entities from
// lines that aren't comments
var people = from line in source
where !line.StartsWith("#")
let fields = line.Split('~')
select new
{
Forename = fields[0],
Surname = fields[1],
Age = int.Parse(fields[2])
};

// just for the fun of it, find the longest line-length etc
var maxLen = source.Max(line => line.Length);
var count = source.Count();

// and while we're having fun, perform some aggregates
// on the people *as we're reading them!*
// (not afterwards, like how LINQ-to-objects works)
var stats = (from person in people
group person by person.Surname into grp
//orderby grp.Key // BUG! action: MG to fix
select new {
Surname = grp.Key,
Count = grp.Count(),
MaxAge = grp.Max(p=>p.Age)
}).ToList();

// and we'll want to catch the people
var peopleList = people.ToList();

// now that we've set everything up
// read the file *once*
//source.ProduceAndEnd(new LineReader(path));
source.ProduceAndEnd(new LineReader(()=>new
StringReader(MY_FILE_CONTENTS)));

// sort the groups
stats.Sort(grp => grp.Surname);

// show what we got
Console.WriteLine(":: Max Line Length ::");
Console.WriteLine(maxLen.Value);
Console.WriteLine(":: Line Count ::");
Console.WriteLine(count.Value);
Write("Comments", comments);
Write("People", peopleList);
// (this "projection" only necessary due to
// glitch in Future<T>; action: MG to fix)
Write("Stats", stats, grp => new {
grp.Surname, Count = grp.Count.Value,
MaxAge = grp.MaxAge.Value
});
}

// these are just to make life easier when showing values
static void Write<T>(string caption, IEnumerable<T> items)
{
Write<T,T>(caption, items, item => item);
}
static void Write<TSource,TValue>(string caption,
IEnumerable<TSource> items,
Func<TSource,TValue> projection)
{
Console.WriteLine();
Console.WriteLine(":: " + caption + " ::");
foreach (var item in items)
{
Console.WriteLine(projection(item));
}
}
}

coconet

4/8/2008 8:05:00 PM

0

Wow, that is a lot to dig into. Thanks!

Marc Gravell

4/9/2008 2:19:00 PM

0

For info - found a workaround to the line marked BUG:

var stats = (from person in people
group person by person.Surname into grp
let agg = new
{
Surname = grp.Key,
Count = grp.Count(),
MaxAge = grp.Max(p => p.Age)
}
orderby agg.Surname
select agg).ToList();

then you can remove the separate stats.Sort...

Marc

coconet

4/15/2008 9:28:00 PM

0


After doing some testing and re-testing, I have not gotten through
this. push-LINQ looks like an awesome thing but is out of the question
for this current exercise.

My text is actually coming from an HTTP server, so I am parsing the
tab-delimited stream as it comes, and it'll either have a "#"
prepended on it in which case I want to pass the line to another
method for parsing, or it will not have a "#" in which case it must be
data, so split it and put the parts into an entity. Here is what I
have right now.

myReader = new StreamReader( myResponse.GetResponseStream() ,
configsettings.ascii );
var dataParts =
from dataLine in myReader.GetLines()
let lineItems = dataLine.Split( '\t' )
where !dataLine.StartsWith( "#" )
select new MyClass
{
DownloadStamp = DateTime.Now ,
DollarValue = lineItems[0].ToString() ,
StatusCode = lineItems[1].ToString()
};
MyGroup = (IEnumerable<MyClass>)dataParts;

This works but the conditional comment thing I need to deal with.

Help?








On Tue, 08 Apr 2008 15:59:06 +0100, Marc Gravell
<marc.gravell@gmail.com> wrote:

>Ah - right, sorry; I misread the post.
>
>For LINQ-to-objects, you could probably do it by having a method for the
>predicate (on the "where") that strores the value:
>
> List<string> comments = new List<string>();
> var filteredLines = lines.Where(s => {
> if(s.StartsWith("#")) {
> comments.Add(s);
> return false;
> }
> return true;
> });
> var query = from line in filteredLines
> let lineItems = line.Split( '\t' )
> select new string[] {lineItems[0], lineItems[1], lineItems[2]};
>
>For interest, "Push-LINQ" would also be an option here, as it is
>designed to process one stream in multiple disparate ways:
>
>http://msmvps.com/blogs/jon.skeet/archive/2008/01/04/quot-push-quot-linq-revisited-next-attempt-at-an-explan...
>
>Marc

Marc Gravell

4/17/2008 7:20:00 AM

0

I did something like this in my second post; does it not work?