How to intersect multiple IEnumerable? - c#

I don't understand why my variable selected doesn't contain the content of all the TempX variable. For example, in my case, the variable TempX containt one SuperObject but as soon as I reach the first intersect, it's lost and my View always show an empty list...
By the way, the blablabla.ToList() are real and complicated linq query. I put this to make it clearer.
Thanks and here is the code:
public ActionResult Search(string q)
{
ViewBag.q = q;
String[] strQueries = q.Split(' ');
IEnumerable<SuperObject> selected = new List<SuperObject>();
foreach (string str in strQueries)
{
//Query 1
IEnumerable<SuperObject> Temp1 = blablabla.ToList();
//Query 2
IEnumerable<SuperObject> Temp2 = blablabla2.ToList();
//Query 3
IEnumerable<SuperObject> Temp3 = blablabla3.ToList();
//Query 4
IEnumerable<SuperObject> Temp4 = blablabla4.ToList();
selected = selected.Intersect(Temp1);
selected = selected.Intersect(Temp2);
selected = selected.Intersect(Temp3);
selected = selected.Intersect(Temp4);
}
return View("Search", selected);
}

You probably want to use Union instead of Intersect. Here's the difference, I think it's self explanatory:

You are intersecting an empty list with Temp 1-4. This isn't going to yield any results.
Edit: To elaborate, an intersect gets all elements common to two collections. Since the first collection you are intersecting is empty the result is always going tobe empty. So the answer really depends on what you are trying to do. Are you trying to find only elements common to all 4 lists? if so do what BurundukXP said:
selected = Temp1.Intersect(Temp2);
selected = selected.Intersect(Temp3);
selected = selected.Intersect(Temp4);
Are you trying to get a unique list of all the elements in each list? Do something like this:
selected.AddRange(Temp1);
selected.AddRange(Temp2);
selected.AddRange(Temp3);
selected.AddRange(Temp4);
selected.Distinct();

Related

Determine if an element in a list contains the same value

I have a List containing a number of Guid's.
List<Guid> recordIds = new List<Guid>;
I need to verify if the Guid's in this list are all identical.
So instead of iterating the whole list I was thinking about using some sort of:
var IdsAreIdentical = recordIds.TrueForAll(x => x == DontKnowWhatToPutHere);
My issue is that I am not really shure about the usage. Maybe somebody can put me in the right direction.
If you want to verify if all the id are the same, you could verify that all the values are the same as the first one :
bool allIdentical = recordIds.TrueForAll(i => i.Equals(recordIds.FirstOrDefault());
Another variant would be to verify the number of distinct values that you have. If the result is 1, the ids are all identicals.
var allIdentical = list.Distinct().Count() == 1;

Adding a List to SQL database (Entity Database)

I got a database with members, each member has a list of sports they do.
now I want to loop through a listbox and add every selected item to my database.
This is my database :
And this is my code :
foreach (var item in sportCheckedListBox.CheckedIndices)
{
int sportindex = Convert.ToInt32(item.ToString()) + 1;
var queryResult = from sp in context.Sports
where sp.sportnr == sportindex
select sp;
foreach (var sport in queryResult)
{
myMember.Sports.Add(sport);
}
}
This looks kinda 'shady', how could I do this better ?
One thing I'd do for sure is move the query out of the loop. Queries should never exist in loops for performance and maintainability reasons. LINQ knows how to translate a (new int[] { 0, 1, 2, ... }).Contains(column) construct into a WHERE column IN (0, 1, 2, ...) statement, so let's use that:
// Get all checked items together
var lookupIndices = sportCheckedListBox.CheckedIndices.Select(i => Convert.ToInt32(item.ToString()) + 1);
// Find all matching sport numbers
var queryResult = from sp in context.Sports
where lookupIndices.Contains(sp.sportnr)
select sp;
// Now loop over the results
foreach (var sport in queryResult)
{
myMember.Sports.Add(sport);
}
// save changes
I think you can just do AddRange:
myMember.Sports.AddRange(queryResult);
myMember.Sports.SaveChanges()
You may need to covert queryResult to an IEnumerable type if it's not already though.
There is nothing fundamentally wrong with your approach, but you can achieve it more concisely with Linq.
Instead of your foreach loop, if you always want to assign a new list you could use
myMember.Sports = queryResult.ToList();
If you want to instead concatenate results to an existing list, you could use
myMember.Sports = myMember.Sports.Concat(queryResult.ToList());
If you wanted to do the same as above, but not have any duplicates (as defined by the object you are adding), instead
myMember.Sports = myMember.Sports.Union(queryResult.ToList());

Returning list of list values (consolidated)

I want to be able to return a list of all "list values" coming from the query.. 'query' below returns multiple rows of results back from db, each as an item in a list. A sample result back from db would look like...
sample query results when I put break point: (this is what first line of code below 'query' returns from db)
Name = John ; Address = 1230, Ewded ; listOfCities = "NY, CH, LA"
Name = Eric; Address = 12 , Ewded ; listOfCities = "BO, SE, OR"
Code:
List<Index.Result> query = getresultsbackfromdb();
// query content at this point looks like above 1,2
List<string> result = new List<string>();
foreach (var item in query)
{
results.Add(item.listCities);
//'results' list takes in string and not a list
//How do I return a consolidated list of items
}
return result; // this should have ""NY, CH, LA, BO, SE, OR"
//I am trying to get a list of all cities from 1,2 included in
//one single list.
There is method in a List that allows you to add multiple items
foreach (var item in query)
{
results.AddRange(item.listCities);
}
Docs for List.AddRange Method.
Also, just in case if you need to filter out some repeated items, you can use a Distinct LINQ method.
You can try this code based on Split Method
var result = yourString.Split(',');
var input = "NY, CH, LA";
var result = input.Split(',');
And you can save this value in List<object>
var list = new List<object>();
list.Add(result );
You want the AddRange and string.Split methods
results.AddRange(string.Split(',', item.ListCities));
string.Split will split the string into an array wherever it finds the given character, and add range will add all items in an array to the list.
Try this:
var result = query.SelectMany(x => x.listOfCities.Split(','));
Or use
var result = query.SelectMany(x => x.listOfCities.Split(',')).Distinct();
to get the list without duplicates.
If you like Linq then you could do this one line:
using System.Linq;
List<string> result = query.SelectMany(s => s.listCities).ToList();
(This does essentially the same thing as oleksii's AddRange.)

LINQ Intersect but add the result to a New List

I have a list called DiscountableObject. Each item on the list in turn has a Discounts collection. What I need is a list of Discounts which are common across all DiscoubtableObjects.
Code:
List<Discount> IntersectionOfDiscounts = new List<Discount>();
foreach(var discountableItem in DiscoutableObject)
{
IntersectionOfDiscounts = IntersectionOfDiscounts.Intersect(discountableItem.Discounts);
}
This will undoubtedly return an empty list because by IntersectionOfDiscounts was empty in the first instance.
What I want is to take item 1 of the DiscountableObject, compare it with the next item of DiscountableObject and so on.
I know what I am trying to do is wrong because, I am doing the intersection and the addition to the list at the same time...but how else baffles me?
How do I get around this?
Initialize IntersectionOfDiscounts to the first Discount List (if there is more than one) rather than to an empty list. You can also then skip the first item in the 'foreach' loop.
// add check to ensure at least 1 item.
List<Discount> IntersectionOfDiscounts = DiscoutableObject.First().Discounts;
foreach(var discountableItem in DiscoutableObject.Skip(1))
{
IntersectionOfDiscounts = IntersectionOfDiscounts.Intersect(discountableItem.Discounts);
}
Possibly more elegant way:
var intersection = discountableObject
.Select(discountableItem => discountableItem.Discounts)
.Aggregate( (current, next) => current.Intersect(next).ToList());
Missed your 6 minute deadline, but I like it anyway...

iterating over a collection and removing ones I dont want

I have a problem with the following. I have a collection:
Collection<Vehicle> list = new Collection<Vehicle>();
code = 1,2,3, Description = "aaa"
code = 10,438,13, Description = "bbb"
code = 81,8623,362, Description = "ccc"
code = 163,4312,53, Description = "ddd"
...
But I only care about some of them.. The list I care about is here, i.e. codesCareAbout = "1|2|3|163|4312|53"
I need to iterate through the collection and either deleting the Vehicle I don't care about, or cherry picking into another list containing the Vehicles I care about?
Any suggestions?
Many thanks,
James
You can iterate your list backwards, and use RemoveAt using the for index to remove from the list:
for (int i = list.Count - 1; i >= 0; i--)
{
Foo item = list[i];
if (IsFoobar(item))
list.RemoveAt(i);
}
Counting backwards is required so that you don't mutate your index counting as you go, using a for loop is required because you cannot mutate a list being enumerated with an enumerator.
Alternatively, do as you suggested, populate into an empty list the stuff you want - however, usage depends on whether you need to modify the list you are given or can make a new list.
Assuming that Vehicule has Code (string) property and Description property (question is not clear !).
1/ Clone the list : var clone = list.ToList();
2/ Iterate and decide if current item is interesting :
foreach(var item in clone)
{
if (! IsInteresting(item))
list.Remove(item);
}
3/ IsInteresting could be, for example :
foreach(var code in item.Code.Split(','))
{
if (codesCareAbout.Split('|').Contains(code))
return true;
}
Filtering the list with linq produces the cleanest code:
var interestinglist = list.Where(v=> v.IsInteresting(v));
IsInteresting can be implemented as
codesIcareAbout.Intersect(v.Codes).Any();
this assumes both fields are collections rather than strings, but that is easily fixed with a call to string.Split().

Categories