I have 2 customer lists and I'm trying to get a list of customers that DO NOT have a matching Name property between the 2 lists. I also need to include the customers from the second list that DO match, but that do not have the source property set to "migrated". Basically I would have a list of customer to add and update. I've tried a bunch of ways, but when I add the conditional for Source I end up with the wrong results. I'm doing it this way to have the ability to migrate in batches.
var legacyCustomers = new List<Customer>{
new Customer() { Name = "Customer 1" },
new Customer() { Name = "Customer 2" },
new Customer() { Name = "Customer 3" },
new Customer() { Name = "Customer 4" }
};
var currentCustomers = new List<Customer>{
new Customer() { Name = "Customer 1", Source = "migrated" },
new Customer() { Name = "Customer 2", Source = "migrated" },
new Customer() { Name = "Customer 3", Source = "" }
};
In this scenario I need "Customer 3" and "Customer 4" added to a new Customer list.
Here's a fiddle I've been using https://dotnetfiddle.net/Z0RoFe
Any help is very appreciated.
The code becomes a little bit simpler if we implement IEqualityComparer<Customer>. That means we're creating a class that uses custom logic to determine if two customers are equal.
public class CustomerNameEqualityComparer : IEqualityComparer<Customer>
{
public bool Equals(Customer x, Customer y)
{
return string.Equals(x?.Name, y?.Name, StringComparison.CurrentCultureIgnoreCase);
}
public int GetHashCode(Customer obj)
{
return obj.Name?.GetHashCode() ?? 0;
}
}
According to this class, two customers are equal if they have the same name. The convenience is that we can use this comparison without actually modifying the Customer class, since this might not always be the way we want to compare customers.
We can do it without this, but it results in a lot of complicated Where functions that compare the names. If you're going to compare items using specific comparison logic then it's easier to create the comparison once and re-use it.
If we did this (given that firstList and secondList are both List):
var customersFromFirstListNotInSecondList = firstList.Except(secondList);
It wouldn't work because it would use reference equality to compare the two lists instead of looking for matching names. But if we do this:
var customersFromFirstListNotInSecondList =
firstList.Except(secondList, new CustomerNameEqualityComparer());
It will compare the customers in the two lists just by matching their names.
That comparer class also makes the second step easier to implement:
var matchingButNotMigrated =
firstList.Intersect(secondList, new CustomerNameEqualityComparer())
.Where(customer => customer.Source != "migrated");
This returns the items that are on both lists (intersection), again comparing using the names. Once it has the items that are on both lists it excludes those that are migrated.
Simply add the extra condition to your Where clause (i.e. where the names don't match or the source does not equal "migrated"):
var migrateList = legacyCustomers
.Where(c => currentCustomers.All(c2 =>
!string.Equals(c2.Name, c.Name, StringComparison.CurrentCultureIgnoreCase) ||
!string.Equals(c2.Source, "migrated", StringComparison.CurrentCultureIgnoreCase)
))
.ToList();
You literally just need to add a logical OR and check for the "migrated" string.
var migrateList = legacyCustomers.Where(c => currentCustomers.All(c2 =>
!string.Equals(c2.Name, c.Name, StringComparison.CurrentCultureIgnoreCase)
|| c2.Source != "migrated")).ToList();
There's no need to do any additional check on the name since any non-matching names are already included, so the extra condition will only be adding names that do match but have a Source of "migrated".
Using the Except method produces a O(n) solution.
var comparer = new CustomerNameEqualityComparer();
var results = legacyCustomers
.Except(currentCustomers.Where(customer => customer.Source == "migrated"), comparer);
Console.WriteLine($"Result: {String.Join(", ", results.Select(c => c.Name))}");
Output:
Customer 3, Customer 4
I am using the elegant CustomerNameEqualityComparer class created by #Scott Hannen. 😃
public class CustomerNameEqualityComparer : IEqualityComparer<Customer>
{
public bool Equals(Customer x, Customer y)
{
return string.Equals(x?.Name, y?.Name, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(Customer obj)
{
return obj.Name?.GetHashCode() ?? 0;
}
}
Related
I'm trying to develop a LINQ query that will identify objects that have duplicate values. I only need the objects where a string in a multivalued attribute matches a string in the same attribute on another object AND the "name" values don't match.
I am trying to use the following code, but it does not work because it doesn't seem possible to use the "o" variable in a subquery.
myList.Where(o => myList.Any(a => a.name != o.name && a.multival.Any(p => o.multival.Contains(p))))
Why even use linq for this? it will be convoluted and difficult to read. I would solve this problem with a nested for loop:
var listOfDuplicates = new IEnumerable<YourObjectType>();
foreach (var a in myList)
{
foreach (var b in myListb)
{
if (a.multival == b.multival && a.name != b.name)
listOfDuplicates.Add(a);
}
}
In response to comments, this is how one would implement a method to exit similar to LINQs FirstOrDefault() and other methods that exit after X amount of matches:
Public IEnumerable<YourObjectType> FindDuplicates(IEnumerable<YourObjectType> myList, int maxDupes)
{
var listOfDuplicates = new IEnumerable<YourObjectType>();
foreach (var a in myList)
{
foreach (var b in myListb)
{
if (a.multival == b.multival && a.name != b.name)
listOfDuplicates.Add(a);
if (listOfDuplicates.length == maxDupes)
return listOfDuplicates;
}
}
return listOfDuplicates;
}
Your query should actually "work," but it's not going to be very efficient if your list size is particularly large. If you're having troubles compiling, check to be sure you do not have any typos. If you're having problems at runtime, add some null checks on your variables and properties. The rest of this answer is to guide how you might utilize Linq to make your query better.
Given the query you have attempted to write, I am going to infer the following closely approximates the relevant parts of your class structure, though I'm using different name for what you have as "multival."
class Foo
{
public string Name { get; set; }
public string[] Attributes { get; set; }
}
And then given an object list looking roughly like this
var mylist = new List<Foo>
{
new Foo { Name = "Alpha", Attributes = new[] { "A", "B", "C" } },
new Foo { Name = "Bravo", Attributes = new[] { "D", "E", "F" } },
new Foo { Name = "Charlie", Attributes = new[] { "G", "H", "A" } }
};
For finding objects that match any other object based on any match of an attribute, this is how I would approach it using Linq:
var part1 = from item in mylist
from value in item.Attributes
select new { item, value };
var query = (from pairA in part1
join pairB in part1 on pairA.value equals pairB.value
where pairA.item.Name != pairB.item.Name
select pairA.item)
.Distinct(); // ToList() to materialize, as necessary
If you were to run that through your editor of choice and explore the contents of part2, you would expect to see objects "Alpha" and "Charlie" based on the shared attribute of "A".
This approach should scale much better than a nested foreach should the size of your initial list be significant (for example, your list containing 10,000 elements instead of 3), which is precisely what your initial approach is.
I got 5 lists. One is containing the date of release and the others are the attributes of that list but seperated in multiple lists.
List<string> sortedDateList = x1.OrderBy(x => x).ToList();
This code is sorting the list with the oldest date first, like it should. But I also want to sort (sync) the other attributes list, because they need the same index as the date.
How can I realize that? I'm new to Linq-methods.
You could use the .Zip() method to combine the lists as described here. You could combine them into a class or an anonymous type and then sort them.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => new { Num = first, Word = second });
var sorted = numbersAndWords.OrderBy(x => x.Num).ToList();
Alternately, if you can guarantee that all the lists are of the same length (or just grab the shortest list) you could use the following instead of the .Zip() extension.
var numbersAndWords = numbers.Select((number, i) => new { Num = number, Word = words[i], Foo = myFoos[i] }); // Where myFoos is another collection.
And in the lambda combine all the items from the separate lists into an object at the same time by accessing the collection by index. (Avoids multiple use of .Zip()) Of course, if you try to access an index that is larger than the list size you will get an IndexOutOfRangeException.
As far as I understand your question, you have different lists containing properties of certain objects. You should definitely look into storing all data into one list of a class of your making, where you consolidate all separate information into one object:
var list = new List<YourClass>
{
new YourClass
{
Date = ...,
OtherProperty = ...,
},
new YourClass
{
Date = ...,
OtherProperty = ...,
},
};
var ordered = list.OrderBy(o => o.Date);
But if you insist in storing different properties each in their own list, then you could to select the dates with their index, then sort that, as explained in C# Sorting list by another list:
var orderedDates = list.Select((n, index) => new { Date = n, Index = index })
.OrderBy(x => x.Date)
.ToList();
Then you can use the indexes of the sorted objects to look up the properties in the other lists, by index, or sort them on index as explained in C# Sort list while also returning the original index positions?, Sorting a list and figuring out the index, and so on.
It almost sounds like you want 1 list of a class.
public class MyClass{
public string Date{get; set;} //DateTime is a better type to use for dates by the way
public string Value2{get; set;}
public string Value3{get; set;}
public string Value4{get; set;}
public string Value5{get; set;}
}
...
var sortedDateList = x1.OrderBy(x => x.Date).ToList()
Create an Object containing the date and attributes:
public class DateWithAttributes
{
public string Date {get;set;}
public Attribute Attribute1 {get;set;}
public Attribute Attribute2 {get;set;}
...
}
List<DateWithAttributes> DateWithAttributesList = new List<DateWithAttributes>()
{
DateWithAttribute1,
DateWithAttribute2
}
List<DateWithAttributes> sortedDateList = DateWithAttributesList.OrderBy(x => x.date).ToList();
If you want to keep the lists separate, and/or create the ordered versions as separate lists, then you can concatenate the index to the dates and sort by dates, then use the sorted indexes:
var orderedIndexedDateOfReleases = dateOfReleases.Select((d, i) => new { d, i }).OrderBy(di => di.d);
var orderedDateOfReleases = orderedIndexedDateOfReleases.Select(di => di.d).ToList();
var orderedMovieNames = orderedIndexedDateOfReleases.Select(di => movieNames[di.i]).ToList();
If you don't mind the result being combined, you can create a class or use an anonymous class, and again sort by the dates:
var orderedTogether = dateOfReleases.Select((d, i) => new { dateOfRelease = d, movieName = movieNames[i] }).OrderBy(g => g.dateOfRelease).ToList();
This question already has answers here:
List<string> complex sorting
(5 answers)
Closed 6 years ago.
I have a list of items and I know I can order them alphabetically by the property name, but how can I order them by the actual value and not the property.
For clarification let's say I have this code:
List<string> lstNames = db.TestDB.Where(x => x.TestCode == true).Select(x => x.FavoriteName).ToList();
Now I know I can order alphabetically by the FavoriteName or vice versa, but for arguments sake let's say the Names that are in the database are "Nick", "Adam", "Chris" and if I used the OrderBy method:
List<string> lstNames = db.TestDB.Where(x => x.TestCode == true).Select(x => x.FavoriteName).OrderBy(x => x.FavoriteName).ToList();
this would list the names alphabetically "Adam", "Chris","Nick", and vice versa with:
List<string> lstNames = db.TestDB.Where(x => x.TestCode == true).Select(x => x.FavoriteName).OrderBy(x => x.FavoriteName).OrderByDescending(x => x.FavoriteName).ToList();
But what if I wanted to order them as "Chris", "Adam", "Nick"?
Basically I am looking for a custom way to order a list by the actual values and not the property name?
Also, these values that are in my database will not be changed and I doubt any will be added/deleted.. so I have also tried putting all of the values into their own list and tried the OrderBy method with that list but it did not work.
Any help is appreciated. Thanks.
You can create a custom comparer for sorting:
public class MyComparer : IComparer<string>
{
public int Compare(string stringA, string stringB)
{
// your comparison logic
// return 1 if stringA "is greater than" stringB
// return -1 if stringA "is less than" stringB
// return 0 if they are equal
}
}
And use that when ordering:
lstNames.OrderBy(x => x, instanceOfMyComparer)
The comparison logic which defines your ordering in the example given in the question doesn't really make sense to me. But that's not particularly important. Whatever logic you want to use would simply go in that Compare() method.
If the ordering is arbitrary but you know what it should be, I'd suggest adding another table to your DB that has the FavoriteName and the order #, join to that and then order the result.
If you don't want to do that in the DB, you can accomplish the same thing by hardcoding it, something like this:
var nameOrder = new[] {
new {Name = "Chris", Order = 1}
, new {Name = "Adam", Order = 2}
, new {Name = "Nick", Order = 3}
};
lstNames.Join(nameOrder, lstName => lstName, nameOrd => nameOrd.Name, (Name, nameOrd) => new { Name, nameOrd.Order }).OrderBy(o => o.Order);
I have 2 lists
List 1
var hashTags = new List<HashTag>();
hashTags.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags.Add(new HashTag
{
Name = "#HashTag2",
Index = 2
});
hashTags.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
List 2
var hashTags2 = new List<HashTag>();
hashTags2.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags2.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags2.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
How do I check if all the elements in hashTags2 exist in hashTags? The index can be ignored and only the name matching is crucial. I can write a for loop to check element but I am looking for a LINQ solution.
Simple linq approach.
hashTags2.All(h=> hashTags.Any(h1 => h1.Name == h.Name))
Working Demo
As only equality of the names is to be taken into account, the problem can be solved by first mapping to the names and then checking containment as follows.
var hashTags2Names = hashTags2.Select( iItem => iItem.Name );
var hashTagsNames = hashTags.Select( iItem => iItem.Name );
var Result = hashTags2Names.Except( hashTagsNames ).Any();
So you want a boolean linq expression that returns true if the name of every element in hashTags2 exists in hashTags?
For this you want the function Enumerable.All, you want that every Hashtag in hashTags2 ...
bool result = hashTags2.All(hashTag => ...)
what do you want to check for every hashTag in hashTags2? That the name is a name in hashTags. So we need the names of hashTags:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
and to check if an item is in a sequence: Enumerable.Contains.
Put it all together:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
bool result = hashTags2.All(hashTag => names.Contains(hashTag.Name));
Of if you want one fairly unreadable expression:
bool result = hashTags2.All(hashTagX =>
hashTags.Select(hashTagY => hashTagY.Name)
.Contains(hashtagX)))
Because of delayed execution there is no difference between the first and the second method. The first one will be more readable.
With Linq to objects you will need at least one IEqualityComparar, to
tell linq how to compare objects and to determine when they are equal.
A simple comparer would be the following that uses the Name property to determine equality of your HashTag.
public class NameEquality : IEqualityComparer<HashTag>
{
public bool Equals(HashTag tag, HashTag tag2)
{
return tag.Name == tag2.Name;
}
public int GetHashCode(HashTag tag)
{
return tag.Name.GetHashCode();
}
}
With this Equality Comparer you can use the linq method Except(), to get all Elements from your list hashTag that are not part of hashTag2.
hashTags.Except(hashTags2, new NameEquality())
I prefer the join operator, however it is just a matter of taste, I guess:
var hashMatched = hashTags.Join(hashTags2,_o => _o.Name, _i => _i.Name, (_o,_i) => _o);
I know there are a lot of examples of this on the web, but I can't seem to get this to work.
Let me try to set this up, I have a list of custom objects that I need to have limited on a range of values.
I have a sort variable that changes based on some action on the UI, and I need to process the object differently based on that.
Here is my object:
MyObject.ID - Just an identifier
MyObject.Cost - The cost of the object.
MyObject.Name - The name of the object.
Now I need to filter this based on a range in the cost, so I will have something similar to this, considering that I could be limiting by Either of my bottom two properties.
var product = from mo in myobject
where mo.Cost <= 10000
or
var product = from mo in myobject
where mo.Name equals strName
Now I have the dynamic linq in my project, but I'm not figuring out how to get it to actually work, as when I do some of the examples I am only getting:
Func<Tsourse>bool> predicate
as an option.
Update:
I am trying to find a solution that helps me Objectify my code, as right now it is a lot of copy and paste for my linq queries.
Update 2:
Is there an obvious performance difference between:
var product = from mo in myobject
... a few joins ...
where mo.Cost <= 10000
and
var product = (from mo in myobject
... a few joins ...)
.AsQueryable()
.Where("Cost > 1000")
Maybe not directly answering your question, but DynamicQuery is unnecessary here. You can write this query as:
public IEnumerable<MyObject> GetMyObjects(int? maxCost, string name)
{
var query = context.MyObjects;
if (maxCost != null)
{
query = query.Where(mo => mo.Cost <= (int)maxCost);
}
if (!string.IsNullOrEmpty(name))
{
query = query.Where(mo => mo.Name == name);
}
return query;
}
If the conditions are mutually exclusive then just change the second if into an else if.
I use this pattern all the time. What "Dynamic Query" really means is combining pure SQL with Linq; it doesn't really help you that much with generating conditions on the fly.
using System.Linq;
var products = mo.Where(x => x.Name == "xyz");
var products = mo.Where(x => x.Cost <= 1000);
var products = mo.Where(x => x.Name == "xyz" || x.Cost <= 1000);
Read this great post on DLINQ by ScottGu
Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
You would need something like
var product = myobject.Where("Cost <= 10000");
var product = myobject.Where("Name = #0", strName);
If you downloaded the samples you need to find the Dynamic.cs file in the sample. You need to copy this file into your project and then add
using System.Linq.Dynamic; to the class you are trying to use Dynamic Linq in.
EDIT: To answer your edit. Yes, there is of course a performance difference. If you know the variations of filters beforehand then I would suggest writing them out without using DLINQ.
You can create your own Extension Method like so.
public static class FilterExtensions
{
public static IEnumerable<T> AddFilter<T,T1>(this IEnumerable<T> list, Func<T,T1, bool> filter, T1 argument )
{
return list.Where(foo => filter(foo, argument) );
}
}
Then create your filter methods.
public bool FilterById(Foo obj, int id)
{
return obj.id == id;
}
public bool FilterByName(Foo obj, string name)
{
return obj.name == name;
}
Now you can use this on an IEnumerable<Foo> very easily.
List<Foo> foos = new List<Foo>();
foos.Add(new Foo() { id = 1, name = "test" });
foos.Add(new Foo() { id = 1, name = "test1" });
foos.Add(new Foo() { id = 2, name = "test2" });
//Example 1
//get all Foos's by Id == 1
var list1 = foos.AddFilter(FilterById, 1);
//Example 2
//get all Foo's by name == "test1"
var list2 = foos.AddFilter(FilterByName, "test1");
//Example 3
//get all Foo's by Id and Name
var list1 = foos.AddFilter(FilterById, 1).AddFilter(FilterByName, "test1");