Using linq select a multiple price values - linq

var filterModel = new List<FilterModel>()
{
new FilterModel {Price = 1},
new FilterModel {Price = 1},
new FilterModel {Price = 15},
new FilterModel {Price = 20},
new FilterModel {Price = 410},
new FilterModel {Price = 9511},
new FilterModel {Price = 9511},
new FilterModel {Price = 252},
new FilterModel {Price = 555},
new FilterModel {Price = 602}
};
var priceList = new List<PriceList>
{
new PriceList{MinPrice = 0,MaxPrice = 30},
new PriceList{MinPrice = 70,MaxPrice = 130},
new PriceList{MinPrice = 200,MaxPrice = 250},
new PriceList{MinPrice = 400,MaxPrice = 600},
//etc.etc. continue...
};
I have 2 models. I'm trying to use LINQ. My code is working.
What would be the shortest (cleanest) way to do this?
var newFilterModel = new List<FilterModel>();
foreach (var t in priceList)
{
newFilterModel.AddRange(filterModel
.Where(x => x.Price > t.MinPrice && x.Price < t.MaxPrice)
.ToList());
}
var distinctNewFilterModel = newFilterModel.Select(p=>new { p.Price})
.Distinct().ToList();

I don't know if this is short & clean enough for you, but...
var newFilterModel = filterModel
// Select just the price
.Select(f => f.Price)
// Remove duplicates
.Distinct()
// Find prices in the price list
.Where(price => priceList
.FindIndex(p => p.MinPrice <= price && price <= p.MaxPrice) != -1)
// Turn the price back into a FilterModel object
.Select(price => new FilterModel { Price = price })
// Turn the entire result into a new List<FilterModel>
.ToList();
newFilterModel.ForEach(newF => Console.WriteLine(newF.Price));
Results:
1
15
20
410
555
If you were to implement IEquatable<> in your FilterModel class like this:
public class FilterModel : IEquatable<FilterModel>
{
public int Price { get; set; }
public bool Equals(FilterModel other)
{
//Check whether the compared object is null.
if (ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return other.Price == Price;
}
public override int GetHashCode()
{
//Get hash code for the Price field.
return Price.GetHashCode();
}
}
Then your Linq statement gets shorter:
var newFilterModel = filterModel
// Remove duplicates
.Distinct()
// Find prices in the price list
.Where(filterPrice => priceList
.FindIndex(price => price.MinPrice <= filterPrice.Price && filterPrice.Price <= price.MaxPrice) != -1)
// Turn the entire result into a List<FilterModel>
.ToList();
newFilterModel.ForEach(p => Console.WriteLine(p.Price));
Results:
1
15
20
410
555

You can use cross join like this and get IEnumerable<FilterModel>
var distinctNewFilterModel = from filter in filterModel
from price in priceList
where filter.Price > price.MinPrice && filter.Price < price.MaxPrice
group filter by filter.Price into groupped
select groupped.First();
but not sure that this shortest and cleanest than you have

Related

Optimize queries for Union, Except, Join with LINQ and C#

I have 2 objects (lists loaded from XML) report and database (showed bellow in code) and i should analyse them and mark items with 0, 1, 2, 3 according to some conditions
TransactionResultCode = 0; // SUCCESS (all fields are equivalents: [Id, AccountNumber, Date, Amount])
TransactionResultCode = 1; // Exists in report but Not in database
TransactionResultCode = 2; // Exists in database but Not in report
TransactionResultCode = 3; // Field [Id] are equals but other fields [AccountNumber, Date, Amount] are different.
I'll be happy if somebody could found time to suggest how to optimize some queries.
Bellow is the code:
THANK YOU!!!
//TransactionResultCode = 0 - SUCCESS
//JOIN on all fields
var result0 = from d in database
from r in report
where (d.TransactionId == r.MovementID) &&
(d.TransactionAccountNumber == long.Parse(r.AccountNumber)) &&
(d.TransactionDate == r.MovementDate) &&
(d.TransactionAmount == r.Amount)
orderby d.TransactionId
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount,
TransactionResultCode = 0
};
//*******************************************
//JOIN on [Id] field
var joinedList = from d in database
from r in report
where d.TransactionId == r.MovementID
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount
};
//Difference report - database
var onlyReportID = report.Select(r => r.MovementID).Except(joinedList.Select(d => d.TransactionId));
//TransactionResultCode = 1 - Not Found in database
var result1 = from o in onlyReportID
from r in report
where (o == r.MovementID)
orderby r.MovementID
select new TransactionList()
{
TransactionId = r.MovementID,
TransactionAccountNumber = long.Parse(r.AccountNumber),
TransactionDate = r.MovementDate,
TransactionAmount = r.Amount,
TransactionResultCode = 1
};
//*******************************************
//Difference database - report
var onlyDatabaseID = database.Select(d => d.TransactionId).Except(joinedList.Select(d => d.TransactionId));
//TransactionResultCode = 2 - Not Found in report
var result2 = from o in onlyDatabaseID
from d in database
where (o == d.TransactionId)
orderby d.TransactionId
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount,
TransactionResultCode = 2
};
//*******************************************
var qwe = joinedList.Select(j => j.TransactionId).Except(result0.Select(r => r.TransactionId));
//TransactionResultCode = 3 - Transaction Results are different (Amount, AccountNumber, Date, )
var result3 = from j in joinedList
from q in qwe
where j.TransactionId == q
select new TransactionList()
{
TransactionId = j.TransactionId,
TransactionAccountNumber = j.TransactionAccountNumber,
TransactionDate = j.TransactionDate,
TransactionAmount = j.TransactionAmount,
TransactionResultCode = 3
};
you may try something like below:
public void Test()
{
var report = new[] {new Item(1, "foo", "boo"), new Item(2, "foo2", "boo2"), new Item(3, "foo3", "boo3")};
var dataBase = new[] {new Item(1, "foo", "boo"), new Item(2, "foo22", "boo2"), new Item(4, "txt", "rt")};
Func<Item, bool> inBothLists = (i) => report.Contains(i) && dataBase.Contains(i);
Func<IEnumerable<Item>, Item, bool> containsWithID = (e, i) => e.Select(_ => _.ID).Contains(i.ID);
Func<Item, int> getCode = i =>
{
if (inBothLists(i))
{
return 0;
}
if(containsWithID(report, i) && containsWithID(dataBase, i))
{
return 3;
}
if (report.Contains(i))
{
return 2;
}
else return 1;
};
var result = (from item in dataBase.Union(report) select new {Code = getCode(item), Item = item}).Distinct();
}
public class Item
{
// You need also to override Equals() and GetHashCode().. I omitted them to save space
public Item(int id, string text1, string text2)
{
ID = id;
Text1 = text1;
Text2 = text2;
}
public int ID { get; set; }
public string Text1 { get; set; }
public string Text2 { get; set; }
}
Note that you need to either implement Equals() for you items, or implement an IEqualityComparer<> and feed it to Contains() methods.

Simple LINQ query

I have a List of X items. I want to have LINQ query that will convert it into batches (a List of Lists), where each batch has 4 items, except for the last one which can have 1-4 (whatever the remainder is). Also, the number 4 should be configurable so it could 5, 17, etc.
Can anyone tell me how to write that?
List<Item> myItems = ...;
List<List<Item>> myBatches = myItems.????
Thank you in advance!
If you're happy with the results being typed as IEnumerable<IEnumerable<T>> then you can do this:
int groupSize = 4;
var myBatches = myItems.Select((x, i) => new { Val = x, Idx = i })
.GroupBy(x => x.Idx / groupSize,
x => x.Val);
If you want an actual List<List<T>> then you'll need to add a couple of extra ToList calls:
int groupSize = 4;
var myBatches = myItems.Select((x, i) => new { Val = x, Idx = i })
.GroupBy(x => x.Idx / groupSize,
x => x.Val,
(k, g) => g.ToList())
.ToList();
Here is a good article about using Take and Skip to do paging, which is identical functionality to what you are requesting. It doesn't get you all of the way to a single line of LINQ, but hopefully helps.
This made me think of how we did this before LINQ.
var vessels = new List<Vessel>()
{ new Vessel() { id = 8, name = "Millennium Falcon" },
new Vessel() { id = 4, name = "Ebon Hawk" },
new Vessel() { id = 34, name = "Virago"},
new Vessel() { id = 12, name = "Naboo royal starship"},
new Vessel() { id = 17, name = "Radiant VII"},
new Vessel() { id = 7, name = "Lambda-class shuttle"},
new Vessel() { id = 23, name = "Rogue Shadow"}};
var chunksize=2;
// With LINQ
var vesselGroups = vessels.Select((v, i) => new { Vessel = v, Index = i })
.GroupBy(c => c.Index / chunksize, c => c.Vessel, (t,e)=>e.ToList())
.ToList();
// Before LINQ (most probably not optimal)
var groupedVessels = new List<List<Vessel>>();
var g = new List<Vessel>();
var chunk = chunksize;
foreach(var vessel in vessels)
{
g.Add(vessel);
chunk--;
if (chunk == 0)
{
groupedVessels.Add(g);
g = new List<Vessel>();
chunk = chunksize;
}
}
groupedVessels.Add(g);

How do I transfer this logic into a LINQ statement?

I can't get this bit of logic converted into a Linq statement and it is driving me nuts. I have a list of items that have a category and a createdondate field. I want to group by the category and only return items that have the max date for their category.
So for example, the list contains items with categories 1 and 2. The first day (1/1) I post two items to both categories 1 and 2. The second day (1/2) I post three items to category 1. The list should return the second day postings to category 1 and the first day postings to category 2.
Right now I have it grouping by the category then running through a foreach loop to compare each item in the group with the max date of the group, if the date is less than the max date it removes the item.
There's got to be a way to take the loop out, but I haven't figured it out!
You can do something like that :
from item in list
group item by item.Category into g
select g.OrderByDescending(it => it.CreationDate).First();
However, it's not very efficient, because it needs to sort the items of each group, which is more complex than necessary (you don't actually need to sort, you just need to scan the list once). So I created this extension method to find the item with the max value of a property (or function) :
public static T WithMax<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector)
{
var max = default(TValue);
var withMax = default(T);
var comparer = Comparer<TValue>.Default;
bool first = true;
foreach (var item in source)
{
var value = selector(item);
int compare = comparer.Compare(value, max);
if (compare > 0 || first)
{
max = value;
withMax = item;
}
first = false;
}
return withMax;
}
You can use it as follows :
from item in list
group item by item.Category into g
select g.WithMax(it => it.CreationDate);
UPDATE : As Anthony noted in his comment, this code doesn't exactly answer the question... if you want all items which date is the maximum of their category, you can do something like that :
from item in list
group item by item.Category into g
let maxDate = g.Max(it => it.CreationDate)
select new
{
Category = g.Key,
Items = g.Where(it => it.CreationDate == maxDate)
};
How about this:
private class Test
{
public string Category { get; set; }
public DateTime PostDate { get; set; }
public string Post { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<Test> test = new List<Test>();
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 5, 12, 0, 0), Post = "A1" });
test.Add(new Test() { Category = "B", PostDate = new DateTime(2010, 5, 5, 13, 0, 0), Post = "B1" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 12, 0, 0), Post = "A2" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 13, 0, 0), Post = "A3" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 14, 0, 0), Post = "A4" });
var q = test.GroupBy(t => t.Category).Select(g => new { grp = g, max = g.Max(t2 => t2.PostDate).Date }).SelectMany(x => x.grp.Where(t => t.PostDate >= x.max));
}
Reformatting luc's excellent answer to query comprehension form. I like this better for this kind of query because the scoping rules let me write more concisely.
from item in source
group item by item.Category into g
let max = g.Max(item2 => item2.PostDate).Date
from item3 in g
where item3.PostDate.Date == max
select item3;

LINQ query and lambda expressions

I'm trying to write a LINQ query and am having problems. I'm not sure if lambda expressions are the answer or not but I think they may be.
I have two combo boxes on my form: "State" and "Color".
I want to select Widgets from my database based on the values of these two dropdowns.
My widgets can be in one of the following states: Not Started, In Production, In Finishing, In Inventory, Sold. Widgets can have any color in the 'color' table in the database.
The 'state' combobox has selections "Not Sold," "In Production/Finishing", "Not Started," "In Production," "In Finishing," "In Inventory," "Sold." (I hope these are self-explanatory.)
The 'color' dropdown has "All Colors," and a separate item for each color in the database.
How can I create a LINQ query to select the widgets I want from the database based on the dropdowns?
var WidgetStateChoosen = "Sold";
//var WidgetStateChoosen = "All Widgets";
var WidgetColourChoosen = "Orange";
//var WidgetColourChoosen = "All Colours";
var widgetselected = Widgets.Where
(w =>
( (WidgetStateChoosen == "All Widgets") ? (w.WidgetState != WidgetStateChoosen) : (w.WidgetState == WidgetStateChoosen) )
&&
( (WidgetColourChoosen == "All Colours") ? (w.WidgetColour != WidgetColourChoosen) : (w.WidgetColour == WidgetColourChoosen) )
);
Way more code then I wish, but oh well! I wasnt sure I completely understood your state and selectionstate, but I hope my example is still helpful.
[TestMethod]
public void SelectionTest()
{
var userSelections = GetUserSelections("AllColor", (SelectedState[])Enum.GetValues(typeof(SelectedState)));
var inventory = this.GetInventory();
foreach (var currentSelection in userSelections)
{
var selection = currentSelection;
var result = from item in inventory
where (item.Color == selection.Color || selection.Color == "AllColor") &&
this.GetStates(selection.State).Contains(item.State)
select item;
Console.WriteLine("Item selected for selection: Color:{0} SelectedState:{1}", selection.Color, selection.State);
foreach (var item in result)
{
Console.WriteLine("Item Color:{0};Item State:{1}", item.Color, item.State);
}
Console.WriteLine("");
}
}
private IEnumerable<State> GetStates(SelectedState state)
{
var list = new List<State>();
foreach (State currentState in Enum.GetValues(typeof(State)))
{
if (((int)currentState & (int)state) == (int)currentState)
{
list.Add(currentState);
}
}
return list;
}
private IEnumerable<Item> GetInventory()
{
return new List<Item>()
{
new Item() {State = State.NotStarted, Color = "Blue"},
new Item() {State = State.InFinishing, Color = "Red"},
new Item() {State = State.Sold, Color = "Yellow"},
new Item() {State = State.Sold, Color = "Blue"},
new Item() {State = State.InProduction, Color = "Blue"},
new Item() {State = State.InInventory, Color = "Blue"},
};
}
private IEnumerable<UserSelection> GetUserSelections(String color, IEnumerable<SelectedState> states)
{
var list = new List<UserSelection>();
foreach (var state in states)
{
list.Add(new UserSelection() { Color = color, State = state });
}
return list;
}
[Flags]
private enum State
{
NotStarted = 1,
InProduction = 2,
InFinishing = 4,
InInventory = 8,
Sold = 16
}
private enum SelectedState
{
NotSold = State.InInventory, //Where does it map? I assume INInventory even if it doesnt make much sense
InProductionOrFinishing = State.InProduction | State.InFinishing,
NotStarted = State.NotStarted,
InProduction = State.InProduction,
InFinishing = State.InFinishing,
InInventory = State.InInventory,
Sold = State.Sold,
SomeBizarroTrippleState = State.InProduction | State.Sold | State.NotStarted
}
private class UserSelection
{
public String Color { get; set; }
public SelectedState State { get; set; }
}
private class Item
{
public String Color { get; set; }
public State State { get; set; }
}
var query = db.Widgets;
if (stateFilter == "Not sold")
query = query.Where(w => w.State != WidgetState.Sold);
else if (stateFilter == "In Production/Finishing")
query = query.Where(w => w.State == WidgetState.InProduction || w.State == WidgetState.Finishing);
if (colorFilter != "All colors")
query = query.Where(w => w.Color = colorFilter);
(of course you should have a better way of testing the selected value from the combobox, testing on strings is really bad...)

LINQ Union with Constant Values

Very primitive question but I am stuck (I guess being newbie). I have a function which is supposed to send me the list of companies : ALSO, I want the caller to be able to specify a top element for the drop-down list as well.. (say for "None"). I have following piece of code, how I will append the Top Element with the returning SelectList?
public static SelectList GetCompanies( bool onlyApproved, FCCIEntityDataContext entityDataContext, SelectListItem TopElement )
{
var cs = from c in entityDataContext.Corporates
where ( c.Approved == onlyApproved || onlyApproved == false )
select new
{
c.Id,
c.Company
};
return new SelectList( cs.AsEnumerable(), "Id", "Comapny" );
}
Thanks!
This should work for you:
List<Corporate> corporates =
(from c in entityDataContext.Corporates
where (c.Approved == onlyApproved || onlyApproved == false)
select c).ToList();
corporates.Add(new Corporate { Id = -1, Company = "None" });
return new SelectList(corporates.AsEnumerable(), "Id", "Comapny");
This method has always worked for me.
public static SelectList GetCompanies( bool onlyApproved, FCCIEntityDataContext entityDataContext, SelectListItem TopElement )
{
var cs = from c in entityDataContext.Corporates
where ( c.Approved == onlyApproved || onlyApproved == false )
select new SelectListItem {
Value = c.Id,
Text = c.Company
};
var list = cs.ToList();
list.Insert(0, TopElement);
var selectList = new SelectList( list, "Value", "Text" );
selectList.SelectedValue = TopElement.Value;
return selectList;
}
Update forgot the lesson I learned when I did this. You have to output the LINQ as SelectListItem.
cs.ToList().Insert(0, new { TopElement.ID, TopElement.Company });
You could convert it to a list as indicated or you could union the IQueryable result with a constant array of one element (and even sort it):
static void Main(string[] args)
{
var sampleData = new[] {
new { Id = 1, Company = "Acme", Approved = true },
new { Id = 2, Company = "Blah", Approved = true }
};
bool onlyApproved = true;
var cs = from c in sampleData
where (c.Approved == onlyApproved || onlyApproved == false)
select new
{
c.Id,
c.Company
};
cs = cs.Union(new [] {new { Id = -1, Company = "None" }}).OrderBy(c => c.Id);
foreach (var c in cs)
{
Console.WriteLine(String.Format("Id = {0}; Company = {1}", c.Id, c.Company));
}
Console.ReadKey();
}

Resources