I have read that the LINQ is very lazy...
I have a class with a model:
class Movie
{
public string Title { get; set; }
public float Rating { get; set; }
private int year;
public int Year
{
get
{
Console.WriteLine("Returning {0} for movie {1}", year, Title);
return year;
}
set
{
year = value;
}
}
}
And I have the following code:
var movies = new List<Movie>
{
new Movie {Title = "The Dark Knight", Rating = 8.9f, Year = 2008 },
new Movie {Title = "The King's Speech", Rating = 8.0f, Year = 2010 },
new Movie {Title = "Casablanca", Rating = 8.5f, Year = 1942 },
new Movie {Title = "Star Wars 5", Rating = 8.7f, Year = 1980 }
};
var query = movies.Where(m => m.Year > 2000);
Console.WriteLine( query.Count( ) );
foreach(var movie in query)
{
Console.WriteLine(movie.Title);
}
And here is the output:
Returning 2008 for movie The Dark Knight
Returning 2010 for movie The King's Speech
Returning 1942 for movie Casablanca
Returning 1980 for movie Star Wars 5
2
Returning 2008 for movie The Dark Knight
The Dark Knight
Returning 2010 for movie The King's Speech
The King's Speech
Returning 1942 for movie Casablanca
Returning 1980 for movie Star Wars 5
So, the Count method does not make the query to be executed? I see this as an opposite to an optimization... So is there a logical reason behind this? (The only reasonable reason that I see for this behaviour is CQS principle)
Thanks
Count did executed the query. those are the 4 lines before the "2".
the 6 lines after is the foreach. which rerun the query.
If what puzzle you is why the expression was evaluted twice. and by
So, the Count method does not make the query to be executed?
you mean, "Evaluate the conditions and saving the result". this is not how LINQ works.
Generaly speaking, when enumerating LINQ, for each element requested (MoveNext) LINQ is calculating only the new element. This way it doesn't process data that wasn't asked for.
Rerunning the same LINQ expression will go over the elements again and do the processing all over again.
Saving & reusing the result is on you. you can easily do it using ToArray() or ToList()
Related
I have done some search here and also on the web but either I'm using wrong keywords or maybe most of the examples on MVVM deal with one model only.
I'm having two models in my project (self learning project on MVVM), song model and artist model. So far been able to bind a listview with a collection of information (from song), such that when user clicks on a row on listview information about a song are populated in few textbox controls.
The question I'm facing is that how can I communicate between two models? if we consider a model as a table with its column/fields then I should be able to create a reference to artist model (a foreign key) but what I'm not getting is that how I can retrieve information about an artist when I cilck on his song in the listview?
Long story short, I like to click on a row in listview which showing list of songs and then get its singer/artist pictures, his real name and etc. I'm not following the concept behind how to find related piece of data about an song in artist model.
Any advice will be apprecaited.
this is what I have now:
public class Song
{
string _singerId;
string _singerName;
string _songName;
string _songWriter;
string _genre;
int _songYear;
Artist artistReference;
Then I have:
public class Artist
{
string _artistBirthName;
string _artistNationality;
string _artistImageFile;
DateTime _artistDateOfBirth;
DateTime _artistDateOfDeath;
bool _isArtistAlive;
Thanks.
EDIT:
Here is how I provide the information:
Question is how can I insert Artist reference in Song collection?
Artists = new ObservableCollection<Artist>()
{
new Artist() { ArtistBirthName = "Francis Albert Sinatra", ArtistNickName = "Ol' Blue Eyes", ArtistNationality = "American", ... },
new Artist() { ArtistBirthName = "Elvis Aaron Presley", ArtistNickName = "", ArtistNationality = "American", ... },
new Artist() { ArtistBirthName = "James Paul McCartney", ArtistNickName = "", ArtistNationality = "British", ... },
new Artist() { ArtistBirthName = "Thomas John Woodward", ArtistNickName = "", ArtistNationality = "British", ... }
};
//later read it from xml file or a table.
Songs = new ObservableCollection<Song>()
{
new Song() {ARTIST INFO GOES HERE? HOW?, SingerName = "Fank Sinatra", SongName="Fly me to the Moon", SongWriterName="Bart Howard", Genre="Jazz" ,YearOfRelease= 1980 },
new Song() {SingerName = "Elvis Presley", SongName="Can't Help Falling in Love", SongWriterName="Paul Anka", Genre="Pop", YearOfRelease= 1969},
new Song() {SingerName = "The Beatles", SongName="Let It Be", SongWriterName="John Lennon", Genre="Rock", YearOfRelease= 1970},
new Song() {SingerName = "Tom Jones", SongName="Its Not Unusual", SongWriterName="Les Reed & Gordon Mills", Genre="Pop" , YearOfRelease= 1965}
};
I'm either missing something here or you're just looking for difficulties where there really are none. :) When creating a song object, just pass an artist to it. For example Artist artist1 = new Artist(...); Song song1 = new Song(..., artist1);
You'll of course want to define the constructors first.
EDIT: After your edit :)
You can do something like this:
using System.Linq; // For lambda operations
(...)
Songs = new ObservableCollection<Song>()
{
new Song() {Artist = Artists.FirstOrDefault(x => x.Name == "Francis Albert Sinatra"), SingerName = ...}
(...)
}
The Artists.FirstOrDefault(...) part is a LINQ query. It iterates over the Artists collection and selects the first item in the collection that matches the condition. If it doesn't find a matching item, it then uses a default value, which should be NULL. It would be better to give each artist a unique ID and search by it instead of name, though, as there can be more artists with the same name. Don't hesitate to ask if you have more questions!
Maybe it's late in the night, but I'm stumped here. I'm trying to combine multiple lists with the same properties into one. I thought that LINQ's .UNION would do the trick but I was mistaken. Here's an example of a few of my lists:
LIST1 (report names):
Date Name Title Product
02/01/13 Steve Hello World Report
02/05/13 Greg Howdy Report
LIST2 (song names):
Date Name Title Product
01/01/13 John Time Song
01/05/13 Bob Sorry Song
LIST3 (games names):
Date Name Title Product
12/01/12 Google Bike Race Game
12/05/12 Apple Temple Run Game
My class is very simple. Here's what it looks like:
public class MyClass {
public DateTime Date { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Product { get; set; }
}
In case you're wondering, I used this LINQ query to get one of the above lists:
var finalList = Games
.Select (s => new MyClass {
Date = (System.DateTime) s.Games.Creation_date,
Name = s.Games.Last_name,
Title = string.Format("{0} (Report)", s.Game.Headline),
Product="Report"
})
;
So far, it's pretty easy, but I want to combine all my lists into 1. So, my final list should look like:
Date Name Title Product
02/01/13 Steve Hello World Report
02/05/13 Greg Howdy Report
01/01/13 John Time Song
01/05/13 Bob Sorry Song
12/01/12 Google Bike Race Game
12/05/12 Apple Temple Run Game
I thought that a UNION command would do it:
var newList = List1.Union(List2).Union(List3);
But I'm not getting the desired output.
Date Name Title Product
02/01/13 Steve Hello World Report
02/05/13 Greg Howdy Report
01/01/13 Bob Time Game
01/05/13 John Sorry Song
12/01/12 Google Bike Race Song
12/05/12 Apple Temple Run Game
Any idea on what I'm doing wrong here?
Try:
list1.Concat(list2).Concat(list3);
You don't want to be using Union ( working or not) anyway as it does set union.
You could try using the AddRange command should look something like this
var FullList = list1.AddRange(list2).AddRange(list3);
or the fail safe way whould be
var FullList = list1.Concat(list2).Concat(list3).ToList(); //Personally i would use this
or you also have
var FullList = new[] { list1, list2, list3 }.SelectMany(a => GetAllProducts(a)).ToList();
I need to create a nested listview and found a great article on how to do it, but my situation is a bit different. I am a linq newbie and need a little help please :)
I need to get my data into a format similar in that article to work (on that link above, search for "Configuring the ListView" and see table right above it).
Here is my data:
Format Movie Name Price
DVD Star Wars 12.99
DVD Star Wars II 13.99
Blue-Ray Star Wars 15.99
Blue-Ray Star Wars II 17.99
Here is what I have, which isn't really that close, but it is as far as I could get:
var MoviesToBuy = from Movie in dtMovieListDetails.AsEnumerable()
//join MovieDetails in dtMovieListDetails.AsEnumerable() on (string)Movie["ID"] equals (string)MovieDetails["ID"]
group Movie by new
{
Format = Movie["Format"],
Movies = Movie
} into grp
select new
{
Format = (string)grp.Key.Format,
Movies = grp.Key.Movies
};
MoviesToBuy = MoviesToBuy.OrderBy(p => p.Format);
lvwAmazonMovieGroup.DataSource = MoviesToBuy.ToList();
lvwAmazonMovieGroup.DataBind();
I have 3 specific issues/questions:
1.) What I have doesn't work. Since my second column in the group equates to all rows, no actual group is created.
2.) Despite prior issue, I am also getting "Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource" error. In this case, the Movies column is being created as a DataRow datatype. Not sure if that is what is creating the problem. Can I cast on that field somehow?
3.) how can I sort on the fields in the movies. I.e. in the end I want the data to be sorted by Format then Movie Name so the nested list view looks like this:
Blue-Ray
Star Wars 12.99
Star Wars II 13.99
DVD
Star Wars 15.99
Star Wars II 17.99
Any points are greatly appreciated!
Thanks in advance,
Chad
I was thinking you could start with the following, adjusting for the proper variable names and AsEnumerable(), etc.
It orders your movies as you want and puts them in a nested structure as you requested:
var moviesToBuy = from movie in dtMovieListDetails
orderby movie.Format, movie.Price
group movie by movie.Format into grp
select new
{
Format = grp.Key,
Movies = grp.Select (g => new { MovieName = g.MovieName, Price = g.Price })
};
Here's an example program that implements the above query.
Try something like this:
var res = from m in movies
group m by m.Format into grouped
orderby grouped.Key
select new
{
Format = grouped.Key,
Movies = grouped.AsEnumerable().OrderBy(x => x.MovieName)
};
Alternatively
var res = from m in movies
orderby m.MovieName
group m by m.Format into grouped
orderby grouped.Key
select new
{
Format = grouped.Key,
Movies = grouped.AsEnumerable()
};
Using this seed data:
var movies = new[] {
new Movie { Format = "DVD", MovieName = "SW1"},
new Movie { Format = "Blue-ray", MovieName = "SW1"},
new Movie { Format = "DVD", MovieName = "SW2"},
new Movie { Format = "Blue-ray", MovieName = "SW2"},
new Movie { Format = "DVD", MovieName = "RF"}
};
Produced:
Format: Blue-ray
Movie: SW1
Movie: SW2
Format: DVD
Movie: RF
Movie: SW1
Movie: SW2
Just for completness, I used this code to generate the previous list
foreach (var item in res)
{
Console.WriteLine("Format: " + item.Format);
foreach (var item2 in item.Movies)
{
Console.WriteLine("\tMovie: " + item2.MovieName);
}
}
Looking for LINQ needed to sort on a date field but also have similar titles grouped and sorted. Consider something like the following desired ordering:
Title Date
"Some Title 1/3" 2009/1/3 "note1: even this is old title 3/3 causes this group to be 1st"
"Some Title 2/3" 2011/1/31 "note2: dates may not be in sequence with titles"
"Some Title 3/3" 2011/1/1 "note3: this date is most recent between "groups" of titles
"Title XYZ 1of2" 2010/2/1
"Title XYz 2of2" 2010/2/21
I've shown titles varying by some suffix. What if a poster used something like the following for titles?
"1 LINQ Tutorial"
"2 LINQ Tutorial"
"3 LINQ Tutorial"
How would the query recognize these are similar titles?
You don't have to solve everything, a solution for the 1st example is much appreciated.
Thank you.
Addendum #1 20110605
#svick also Title authors typically are not thoughtful to use say 2 digits when their numbering scheme goes beyond 9. for example 01,02...10,11 etc..
Typical patterns I've seen tend to be either prefix or suffix or even buried in such as
1/10 1-10 ...
(1/10) (2/10) ...
1 of 10 2 of 10
Part 1 Part 2 ...
You pointed out a valid pattern as well:
xxxx Tutorial : first session, xxxx Tutorial : second session, ....
If I have a Levenshtein function StringDistance( s1, s2 ) how would I fit into the LINQ query :)
Normal grouping in LINQ (and in SQL, but that's not relevant here) works by selecting some key for every element in the collection. You don't have such key, so I wouldn't use LINQ, but two nested foreaches:
var groups = new List<List<Book>>();
foreach (var book in books)
{
bool found = false;
foreach (var g in groups)
{
if (sameGroup(book.Title, g[0].Title))
{
found = true;
g.Add(book);
break;
}
}
if (!found)
groups.Add(new List<Book> { book });
}
var result = groups.Select(g => g.OrderBy(b => b.Date).ToArray()).ToArray();
This gradually creates a list of groups. Each book is compared with the first one in each group. If it matches, it is added to the group. If no group matched, the book creates a new group. In the end, we sort the results using LINQ with dot notation.
It would be more correct if books were compared with each book in a group, not just the first. But you're may not get completely correct results anyway, so I think this optimization is worth it.
This has time complexity O(N²), so it's probably not the best solution if you had millions of books.
EDIT: To sort the groups, use something like
groups.OrderBy(g => g.Max(b => b.Date))
For ordering by date you should use the OrderBy operator.
Example:
//Assuming your table is called Table in datacontext ctx
var data = from t in ctx.Table
order by t.Date
select t;
For grouping strings after similarity you should consider something like the Hamming distance or the Metaphone algorithm. (Although I do not know any direct implementations of these in .Net).
EDIT: As suggested in the comment by svick, the Levenstein distance may also be considered, as a better alternative to the Hamming distance.
Assuming that your Title and Date fields are contained in class called model consider the following class definition
public class Model
{
public DateTime Date{get;set;}
public string Title{get;set;}
public string Prefix
{get
{
return Title.Substring(0,Title.LastIndexOf(' '));
}
}
}
Alongside Date and Title properties i have created a prefix property with no setter and it is returning us the common prefix using substring. you can use any method of your choice in getter of this property. Rest of job is simple. Consider this Linqpad program
void Main()
{
var model = new List<Model>{new Model{Date = new DateTime(2011,1,3), Title = "Some Title 1/3"},
new Model{Date = new DateTime(2011,1,1), Title = "Some Title 2/3"},
new Model{Date = new DateTime(2011,1,1), Title = "Some Title 3/3"},
new Model{Date = new DateTime(2011,1,31), Title = "Title XYZ 1of2"},
new Model{Date = new DateTime(2011,1,31), Title = "Title XYZ 2of2"}};
var result = model.OrderBy(x => x.Date).GroupBy(x => x.Prefix);
Console.WriteLine(result);
}
Edits >>>
If we put the prefix aside the query itself is not returning what I was after which is: 1) Sort the groups by their most recent date 2) sort by title within clusters. Try the following
var model = new List<Model>{
new Model{Date = new DateTime(2009,1,3), Title = "BTitle 1/3"},
new Model{Date = new DateTime(2011,1,31), Title = "BTitle 2/3"},
new Model{Date = new DateTime(2011,1,1), Title = "BTitle 3/3"},
new Model{Date = new DateTime(2011,1,31), Title = "ATitle XYZ 2of2"},
new Model{Date = new DateTime(2011,1,31), Title = "ATitle XYZ 1of2"}
};
var result = model.OrderBy(x => x.Date).GroupBy(x => x.Prefix);
Console.WriteLine(result);
Lets say I have a list of movies with a set of start times
public class Show
{
public string Name { get; set; }
public DateTime Start { get; set; }
}
Movie 1 - 8:00
Movie 1 - 10:00
Movie 1 - 12:00
Movie 2 - 9:00
Movie 2 - 11:30
Movie 2 - 15:00
Movie 3 - 12:00
Movie 3 - 13:00
I end up with a grouped list, with the name of the movie being the key. I want to take one time from each group and create a List<DateTime>.
Now since there are 3 items with 8 times, I should have 18 different lists.
8:00 - 9:00 - 12:00
8:00 - 9:00 - 13:00
10:00 - 9:00 - 12:00
etc.
I tried just looping through the groups
foreach (var movie in movies)
{
foreach (var time in movie)
{
// Start looping through other groups and movies
}
}
Anyways, this is either a bad solution or I wasn't doing something right because I ended up with Lists of List<DateTime> and I had to start looping through those lists to build more lists by only taking one item from each... It was just horrible. A regular nested foreach won't work because then I end up with my 8:00 Movie 1 having all instances of Movie 2 & 3, so I had to keep the lists separate and that just became too messy.
Any suggestions of a better way to approach this?
Summary:
An easier way to understand it would be like this, you want to see 3 movies in a single day. You know which 3 you want to see, you don't know what times you want to see them at.
I need a list that would present all the options. You can see this movie at this time, this one at this time...
e.g.
List 1 - Movie 1 - 8:00, Movie 2 - 9:00, Movie 3 - 12:00
List 2 - Movie 1 - 8:00, Movie 2 - 9:00, Movie 3 - 13:00
List 3 - Movie 1 - 8:00, Movie 2 - 11:30, Movie 3 - 12:00
etc.
Order of the list does not matter.
You're making this more complex than you need to I think. If you start with a list of showtimes, then it's a simple matter of ordering to enumerate.
List<Show> startTimes = GetTimesFromDB();
var moviesGrouped = startTimes.GroupBy(x => x.Name);
foreach(var group in moviesGrouped)
{
// print times for each movie
}
To use a concrete example, lets use this sample:
List<Sample> sample = new List<Sample>
{
new Sample { Id = 1, Time = DateTime.Now },
new Sample { Id = 1, Time = DateTime.Now.AddDays(1) },
new Sample { Id = 2, Time = DateTime.Now.AddDays(2) },
new Sample { Id = 2, Time = DateTime.Now.AddDays(3) },
new Sample { Id = 3, Time = DateTime.Now.AddDays(4) },
};
foreach (var group in sample.GroupBy(x => x.Id))
{
foreach (var element in group)
{
Console.WriteLine(element.Id);
Console.WriteLine(element.Time);
}
}
You simply group by your token (in this case movie name), and then enumerate the grouping (which will contain your movie times).
However, you could greatly simplify this by changing your object model.
public class Movie
{
public int MovieId { get; set; }
public string Name { get; set; }
public List<Showing> Showings { get; set; }
}
public class Showing
{
public DateTime StartTime { get; set; }
public List<Seat> UnsoldSeats { get; set; }
// Etc
}
Then when you have a List<Movie>, it's already set up to do that display, and it naturally makes more sense than a simple showing class.
foreach(var movie in movies)
{
foreach(var showing in movie.Showings)
// DoStuff
}