How to convert foreach loop to a Linq query? - c#

With the following structure
[[1,10],[2,20],[5,45],[10,34]]
this foreach loop finds the first element that matches "planYear". If planYear=5 then the third element value of "45" would be selected.
List<object> gifts = gifts;
foreach (List<object> item in gifts)
{
if (item[0] == planYear)
{
gift = Convert.ToDouble(item[1]);
break;
}
}
What would be an analogous Linq statement to achieve this same result?

var gift = gifts.Cast<List<object>>()
.Where(x => x[0] == planYear)
.Select(x => Convert.ToDouble(x[1]))
.FirstOrDefault();
If no matching entry has been found gift will be 0. If that's not what you want, use First() instead. This will throw an exception if no matching item exists.
This answer assumes - just like your foreach loop - that every item inside gifts is actually a List<object>. If even one item is of a different type, this code will throw an InvalidCastException. If this is a problem, use OfType instead of Cast.

var gift = Convert.ToDouble(
gifts.Cast<List<object>>().First(x => x[0] == planYear)[1]);

Related

Why Lambda expression returns System.Linq.Enumerable+WhereSelectEnumerableIterator`2 as a result

I'm getting following string as result while returning a list of string using lambda expression:
System.Linq.Enumerable+WhereSelectEnumerableIterator`2[HOrg.ServiceCatalog.Contracts.Models.IOfferProperty,System.String]
My code is:
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.Add(offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)
.ToString());
}
Within foreach loop, offer variable contains expected values. But when I make condition using lambda expression, it returns System.Linq.Enumerable+WhereSelectEnumerableIterator`2 as a result.
When I search for this, I got a few suggestions like,
Copying results of lambda expressions in to a seperate list
Use ToList() for lambda expression then assign it to a result variable
and more suggestion. But no answer is helpful for me.
Is anybody know what's wrong in this code?
Instead of converting sequence to String:
// How can .Net convert sequence into string? The only way is to return type name
// which is unreadable System.Linq.Enumerable+WhereSelectEn...
offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)
.ToString()
Join the items into a string
// Join items into string with "; " delimiter, e.g. "1; 2; 3; 4"
offerIds.Add(string.Join("; ", offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)));
If you expect a single result for every offer, try:
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.Add(offer.Where(x => x.PropertyDefinitionId == propertyDefinitionId).Select(x => x.OfferId).FirstOrDefault()?.ToString());
}
It seems to me that you want a collection of offerIds as a string, where multiple are attached to the offerproperties.
If so, then you are looking for the addrange function. Also move your ToString() call inside the select statement, not after it.
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.AddRange(offer.Where(x => x.PropertyDefinitionId == propertyDefinitionId).Select(x => x.OfferId.ToString()));
}
Now for each offer, a selection of OfferId-strings is added to your offerIds IList

Get all results in a IQueryable instead of .FirstOrDefault

I have the following code in my controller which gets the first result of a search with multiple words. Similar to the answer to this question: ASP:NET MVC multiple words in search
var items = from s in db.Items select s;
if (!string.IsNullOrEmpty(searchString) && !searchString.Any(x => Char.IsWhiteSpace(x)))
{
searchString.Trim();
items = items.Where(s => s.keywords.Contains(searchString) || s.notes.Contains(searchString)
}
else if (!string.IsNullOrEmpty(searchString) && searchString.Any(x => Char.IsWhiteSpace(x)))
{
searchString.Trim();
string[] strings = searchString.Split(' ');
var finalItems = new List<Items>();
foreach (var splitString in strings)
{
finalItems.Add(items.FirstOrDefault(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString));
}
items = finalItems.ToList().AsQueryable();
return View(items.ToPagedList(pageNumber, pageSize));
}
I want to get all the matching results instead of the first match. Now when I try to change the .FirstOrDefault line to:
finalItems.Add(items.Where(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString)
I get a 'cannot convert from 'System.Linq.IQueryable(MyProject.Models.Items)' to 'MyProject.Models.Items.
I've tried changing my items assignment to:
var items = from s in db.Items
select s as IQueryable(Items);
That seems to fix the issue but then it breaks all my s.keywords.Contains or s.notes.Contains with the error 'IQueryable(Items) does not contain a definition for keywords or notes and no extension method keywords or notes accepting a first argument of type IQueryable(Items) could be found'.
FirstOrDefault() returns only one element of some type(MyProject.Models.Items in your case) or null.
Where() returns collection of items.
Add() method expects MyProject.Models.Items - one element, not collection.
You should use AddRange() to add collection:
finalItems.AddRange(items.Where(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString);

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;
}

Remove a specific item from a list using LINQ

I have a list containing some objects and want to use LINQ to remove a specific item but am not sure how to do that.
foreach (var someobject in objectList)
{
if (someobject.Number == 1) // There will only one item where Number == 1.
{
list.remove(someobject)
}
}
You cannot use a foreach to remove items during enumeration, you're getting an exception at runtime.
You could use List.RemoveAll:
list.RemoveAll(x => x.Number == 1);
or, if it's actually not a List<T> but any sequence, LINQ:
list = list.Where(x => x.Number != 1).ToList();
If you are sure that there is only one item with that number or you want to remove one item at the maximum you can either use the for loop approach suggested in the other answer or this:
var item = list.FirstOrDefault(x => x.Number == 1);
if(item ! = null) list.Remove(item);
The link to the other question i have posted suggests that you can modify a collection during enumeration in C# 4 and later. No, you can't. That applies only to the new concurrent collections.
You can't use foreach to remove item from collection. This will throw an exception that the collection is modified.
You can perform it with for
for (int i=objectList.Count-1; i>=0 ; i--)
{
if (objectList[i].Number == 1) // there will only one item with Number = 1
{
objectList.Remove(objectList[i]);
}
}
Other case is to use Remove/RemoveAll like Tim Schmelter show.
You CAN use a foreach loop to remove an item from a list. The important thing is to use break to stop the loop once you have removed the item. If the loop continues after an item is removed from the list, an exception will be thrown.
foreach (var someobject in objectList)
{
if (someobject.Number == 1) // There will only one item where Number == 1
{
objectList.remove(someobject);
break;
}
}
However, this will ONLY work if there's only one object you want to remove, as in your case.
you could remove at the index if the value meets the criteria
for (int i=0; i < objectList.Count; i ++)
{
if (objectList[i].Number == 1) // there will only one item with Number = 1
{
objectList.RemoveAt[i];
}
}

Update all objects except one in a collection using Linq

Is there a way to do the following using Linq:
foreach (var c in collection)
{
if (c.Condition == condition)
{
c.PropertyToSet = value;
// I must also check I only set this value to one minimum and only one element.
}
else
{
c.PropertyToSet = otherValue;
}
}
To clarify, I want to iterate through each object in a collection and then update a property on each object except for one element of my collection that should updated to another value.
At this moment I use a counter to check I set my value to one and only one element of my collection. I removed it from this example to let people suggest other solutions.
The original question without exception in collection is here
EDIT
I ask this question because I'm not sure it's possible to do it with LinQ. so your answers comfort my opinion about LinQ. Thank you.
You can use .ForEach to make the change, and .Single to verify only one element matches the condition:
// make sure only one item matches the condition
var singleOne = collection.Single(c => c.Condition == condition);
singleOne.PropertyToSet = value;
// update the rest of the items
var theRest = collection.Where(c => c.Condition != condition);
theRest.ToList().ForEach(c => c.PropertyToSet = otherValue);
I don't suggest you to implement this with Linq. Why? Because Linq is for querying, not for modification. It can return you objects which match some condition, or objects which don't match. But for updating those objects you still need to use foreach or convert query results to list and use ForEach extension. Both will require enumerating sequence twice.
So, simple loop will do the job:
foreach (var c in collection)
{
c.PropertyToSet = (c.Condition == condition) ? value : otherValue;
}
collection.Where(x => <condition>).ToList().ForEach(x => <action>);
Hacky way to use LINQ if you persist to use:
var result = collection.Select(c =>
{
c.PropertyToSet = c.Condition == condition ? value : otherValue;
return c;
});
But my recommendation, don't do this, you code actually get the best approach, for more readability, you can change:
foreach (var c in collection)
c.PropertyToSet = c.Condition == condition ? value : otherValue;
You can use a ternary operator in conjunction with a linq statement:
collection.ToList().ForEach(c => c.PropertyToSet = c.Condition == condition ? value : otherValue);
However I woud just use a regular foreach here to avoid converting the collection to a list.
Well, you could do:
var itemToSetValue = collection.FirstOrDefault(c => c.Condition == condition);
if(itemToSetValue != null)
itemToSetValue.PropertyToSet = value;
// Depending on what you mean, this predicate
// might be c => c != itemToSetValue instead.
foreach (var otherItem in collection.Where(c => c.Condition != condition))
{
otherItem.PropertyToSet = otherValue;
}
Now of course that's not a pure LINQ solution, but pure LINQ solutions are not appropriate for modifying existing collections.

Categories

Resources