LINQ select from TList? - c#

I have a list of RowData items (a custom class).
A RowData item contains a list of CellData items (another custom class).
Currently, the only way I can access each individual CellData item is through the RowData's this[int index], which means I'd have to create a loop for each search.
What I want is to create a method that will return all of the CellData items for a given index value:
private List<RowData> rowList;
public IEnumerable<CellData> Cells(int index) {
foreach (var row in rowList) {
if (row.ID == index) {
return row.AsEnumerable<CellData>();
}
}
return null;
}
I don't like returning NULL, and I'd like to know how to accomplish this task using some LINQ techniques (either Query or Method syntax).

To be able to use LINQ syntax for getting cells collection from row, your RowData need to be enumerable, i.e. implement IEnumerable interface.
By .AsEnumerable<CellData>() in your example, I assume your RowData already implements it and enumerating RowData returns collection of CellData objects.
If not, you need to implement IEnumerable in RowData on your own. If your this[int index] accessor reads the data from a collection, you can just return that collection's GetEnumerator.
And then, you'll be able to fetch CellData collection from RowData collection using LINQ queries, i.e. like this:
var cells = rowList.Where(x => x.ID == index).SelectMany(x => x);

Assuming only one row is returned given an index:
public IEnumerable<CellData> Cells(int index)
{
var foundRow = rowList.FirstOrDefault(row => row.ID == index);
//if no row was found, foundRow will be null
if(foundRow != null)
return foundRow.AsEnumerable<CellData>();
else
return new CellData[]; //return empty cells IEnumerable. Could also be new List<CellData>()
}
}
This code assumes foundRow.AsEnumerable<CellData>() returns a IEnumerable<CellData>.
You have to check for nullity, because row at a given index might nor be found.

This will achieve the same result.
var resultsList = from x in rowList
where x.ID == index
select x;
return resultsList;

public IEnumerable<CellData> Cells(int index){
var cells = (from r on rowList
where r.ID == index
select r.Cells).FirstOrDefault();
return cells;
}

What about:
List<CellData> cells = rowList.Where(x => x.Index == index).SelectMany(x => x.Cells).ToList();

Related

Iterate over an IQueryable without calling ToList()

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

Query IEnumerable as IEnumerable<Type>

I have a problem I need to solve efficiently.
I require the index of an element in an IEnumerable source, one way I could do this is with the following
var items = source.Cast<ObjectType>().Where(obj => obj.Start == forDate);
This would give me an IEnumerable of all the items that match the predicate.
if(items != null && items.Any()){
// I now need the ordinal from the original list
return source.IndexOf(items[0]);
}
However, the list could be vast and the operation will be carried out many times. I believe this is inefficient and there must be a better way to do this.
I would be grateful if anyone can point me in the correct direction.
Sometimes, it's good to forget about Linq and go back to basics:
int index = 0;
foeach (ObjectType element in source)
{
if (element.Start == forDate)
{
return index;
}
index++;
}
// No element found
Using Linq, you can take the index of each object before filtering them:
source
.Cast<ObjectType>()
.Select((obj, i) => new { Obj = obj, I = i })
.Where(x => x.Obj.Start == forDate)
.Select(x => x.I)
.FirstOrDefault();
However, this is not really efficient, the following will do the same without allocations:
int i = 0;
foreach (ObjectType obj in source)
{
if (obj.Start == forDate)
{
return i;
}
i++;
}
Your second code sample was invalid: since items is an IEnumerable, you cannot call items[0]. You can use First(). Anyway:
var items = source.Cast<ObjectType>()
.Select((item, index) => new KeyValuePair<int, ObjectType>(index, item))
.Where(obj => obj.Value.Start == forDate);
and then:
if (items != null && items.Any()) {
return items.First().Key;
}
If you need to do this multiple times I would create a lookup for the indices.
ILookup<DateTime, int> lookup =
source
.Cast<ObjectType>()
.Select((e, i) => new { e, i })
.ToLookup(x => x.e.Start, x => x.i);
Now given a forDate you can do this:
IEnumerable<int> indices = lookup[forDate];
Since the lookup is basically like a dictionary that returns multiple values you get the results instantly. So repeating this for multiple values is super fast.
And since this returns IEnumerable<int> you know when there are duplicate values within the source list. If you only need the first one then just do a .First().

Linq Distinct only in next rows match

I have a datatable with the following information:
365.00
370.00
369.59
365.00
365.00 -> match with previous item
365.00 -> match with previous item
I only need to remove the next matched items, like this:
365.00
370.00
369.59
365.00
I tried:
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.DistinctBy(i => i.articlepricehistory_cost)
.ToList();
Result:
365.00
370.00
369.59
Any ideas?
Another approach:
public static IEnumerable<T> MyDistinct<T>(this IEnumerable<T> items)
{
T previous = default(T);
bool first = true;
foreach(T item in items)
{
if (first || !Equals(previous, item))
{
first = false;
previous = item;
yield return item;
}
}
}
Or, as requested, with a selector
public static IEnumerable<T> MyDistinct<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
U previous = default(U);
bool first = true;
foreach(T item in items)
{
U current = selector(item);
if (first || !Equals(previous, current))
{
first = false;
previous = current;
yield return item;
}
}
}
Here's a neat LINQ solution for u
var list = (dt as Enumerable);
var numbers = list.TakeWhile((currentItem, index) => currentItem != list.ElementAtOrDefault(index - 1));
Keep in mind if u have 0 as the first element it will be ommitted from the new list since ElementAtOrDefault will return 0 in the first iteration of the while loop (index of -1), thus evaluating the expression to false. A simple if statement can help you avoid this.
Here's an idea I have not actually tried
Do a Skip(1) on the query to produce a second query.
Now append to the second query any element not equal to the last element in the first query, to produce a third query.
Now zip join the first and third queries together to form a set of pairs; this is the fourth query.
Now construct a fifth query that filters out pairs that have identical elements from the fourth query.
Finally, construct a sixth query that selects the first element of each pair from the fifth query.
The sixth query is the data set you want.
The problem in your query is that you are using .DistinctBy() which will return distinct results only. So if 365.00 appeared anywhere, it won't show up in the returned list again.
var differentPreviousList = new List<double>();
var itemPriceList = dt.ToList();
differentPreviousList.Add(itemPriceList[0]);
for (var index = 1; index < itemPriceList.Count; index++)
{
if (itemPriceList[index - 1] == itemPriceList[index]) continue;
differentPriceList.Add(itemPriceList[index]);
}
It may not be an elegant solution but I would just parse a bare query..here is how.
Run a lambda query to get all the original results without trying to filter out DistinctBy.
Create an object of a single query result of the type you initially queried for.
Initialize a new list for foreach parse results.
Do a for each loop for each result.
The first if section should be if(object above loop is null).
IF is true add item to list
ELSE if check to see if value of item is same as the last iteration.
Store foreach iteration object to the object declared before the loop.
Rinse and repeat, and the result is no duplicate objects found in a row in the loop will be stored in the list resulting in what you wanted.
I think you have to use a temporary value to check if the next value matches the current value or not.
double temporary = -1; // temp value for checking
List<double> results = new List<double>(); // list to store results
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.Select(a => a.articlepricehistory_cost)
.ToList()
.ForEach(cost =>
{
if (temporary != cost) { results.Add(cost); }
temporary = cost;
});
foreach (var result in results)
{
Console.WriteLine(result);
}
After ForEach method is equivalent to the following.
foreach (var cost in costs)
{
if (temporary != cost)
{
results.Add(cost);
Console.WriteLine(cost);
}
temporary = cost;
}

Using C# lambda to find not matching elements in collection

I have two collections that I get from functions:
IEnumerable<InventoryItem> inventoryItems = get();
IEnumerable<InventoryItem> relatedItems = get();
I want to assign related items to each inventory item. But, related item can't match the inventory item itself. Meaning inventory item cant have itself for related item.
I am trying to skip the overlapping elements in the collection this way:
foreach (var item in inventoryItems)
{
InventoryItem item1 = item;
relatedItems.SkipWhile(x => x.RelatedItems.Contains(item1)).ForEach(i => item1.RelatedItems.Add(i));
Save(item);
}
This does not seem to work. Do any of you Linq user have any better suggestions.
The problem that I have is with SkipWhile(x => x.RelatedItems.Contains(item1)) part. The other part works when matching items regardless if they overlap
Where with negative condition should filter out the only item you don't need (note that comapison with != may need to be replaced with some other condition that check item identity)
item1.RelatedItems = relatedItems
.Where(x => !x.RelatedItems.Any(r => r!= item1)).ToList();
Try this:
public IEnumerable<T> GetNotMatchingElements<T>(IEnumerable<T> collection1, IEnumerable<T> collection2)
{
var combinedCollection = collection1.Union(collection2);
var filteredCollection = combinedCollection.Except(collection1.Intersect(collection2));
return filteredCollection;
}
Not sure I completely understand, but if I do, this should work:
foreach (var invItem in inventoryItems)
{
invItem.RelatedItems = relatedItems
.Where(relItem => !relItem.RelatedItems.Contains(invItem)));
Save(invItem);
}

c# - LINQ select from Collection

I'm attempting to write a simple Select method on a class that inherits from IList.
public class RowDataCollection : IList<RowData> {
private List<RowData> rowList;
internal RowDataCollection(List<RowData> data) {
rowList = data;
}
// ...
}
public RowDataCollection Rows;
public RowDataCollection Select(string colName, object value) {
List<RowData> rowList = from item in Rows
where item[colName].Value == value
select item;
return new RowDataCollection(rowList);
}
Some problems I'm having:
First:
VS2010 reports Cannot implicitly convert type 'IEnumerable<RowData>' to 'List<RowData>'. An explicit conversion exists (are you missing a cast?)
OK, where does the CAST go?
Second:
Someone could pass in an invalid colName value (i.e. String.IsNullOrEmpty(colName)) or a null parameter (object value == null).
How would I handle the way my function returns if the input parameters are invalid?
[Solved]
I edited my Select statement (even renamed it per the suggestions here). I had to use a switch to cast to the data type that the data was in, but it does work.
public RowDataCollection SelectRow(string colName, object value) {
if (!String.IsNullOrEmpty(colName) && (value != null) && (0 < Rows.Count)) {
switch (Rows[0][colName].GetValueType()) {
case TableDataType.Boolean:
return new RowDataCollection(Rows.Where(r => (bool)r[colName].Value == (bool)value).ToList());
case TableDataType.Character:
return new RowDataCollection(Rows.Where(r => (char)r[colName].Value == (char)value).ToList());
case TableDataType.DateTime:
return new RowDataCollection(Rows.Where(r => (DateTime)r[colName].Value == (DateTime)value).ToList());
case TableDataType.Decimal:
return new RowDataCollection(Rows.Where(r => (Decimal)r[colName].Value == (Decimal)value).ToList());
case TableDataType.Integer:
return new RowDataCollection(Rows.Where(r => (int)r[colName].Value == (int)value).ToList());
case TableDataType.String:
return new RowDataCollection(Rows.Where(r => r[colName].Value.ToString() == value.ToString()).ToList());
}
}
return null;
}
[Solved (short version)]
Jon Skeet posted this about the same time I posted my solution, and (as always) his code is much nicer.
public RowDataCollection SelectRow(string colName, object value) {
List<RowData> rowList = Rows.Where(r => r[colName].Value.Equals(value)).ToList();
return new RowDataCollection(rowList);
}
#Jon Skeet: If I ever see your face in the same line at some software developer position I'm applying for, I'm just going to turn around and go home.
#Everyone: Thanks for all the help!
The result of a query like that isn't a List<T>, it's an IEnumerable<T>. If you want to convert that into a List<T>, just call ToList:
List<RowData> rowList = (from item in Rows
where item[colName].Value == value
select item).ToList();
As it happens, you're only calling Where in your query. I would rewrite this as:
List<RowData> rowList = Rows.Where(item => item[colName].Value.Equals(value))
.ToList();
I'd also rename the method to something which is obviously filtering rather than projecting, given that the latter is the more common use of the term "select" in LINQ.
As for input parameters - I suggest you validate the arguments and throw an exception if they're not valid:
if (string.IsNullOrEmpty(colName))
{
throw new ArgumentException("colName");
}
You're getting the error message because LINQ Queries return IEnumerable, not List.
If you need a List, it's easy enough:
List<RowData> rowList = (from item in Rows
where item[colName].Value == value
select item).ToList();
You can't directly cast an IEnumerable<RowData> to a List<RowData>, however, there does exist a convenience function Enumerable.ToList<T>(), used like so:
List<RowData> rowList = (from item in Rows
where item[colName].Value == value
select item).ToList();
As for your second question, an exception would occur during the ToList() call as the LINQ expression is evaluated immediately. You have a few options, including throwing ArgumentExceptions or returning an empty list. It depends on your use cases. I'd suggest simply throwing an exception (assuming you have some HasColumn() method on your RowData class):
if (colName == null)
{
throw new ArgumentNullException("colName");
}
else if (!Rows.All(row => row.HasColumn(colName)))
{
throw new ArgumentException("No such column " + colName, "colName");
}
Per your edit, another approach, if a column missing is not necessarily a "problem":
...
// note the change to Any()
else if (!Rows.Any(row => row.HasColumn(colName))
{
throw new ArgumentException("No such column " + colName, "colName");
}
List<RowData> rowList = (from item in Rows
where item.HasColumn(colName)
&& item[colName].Value == value
select item).ToList();
You have to convert IQueriable<> to List<>, by calling ToList();
public RowDataCollection Select(string colName, object value) {
List<RowData> rowList = from item in Rows
where item[colName].Value == value
select item;
return new RowDataCollection(rowList.ToList());
}

Categories

Resources