LINQ- comparison & updating values - c#

Probably this question was already asked before, but my google-fu and SO-Search did not get me what what I was looking for.
I have a custom class, and a custom class comparer (for checking the equality of the class) implemented with IEqualityComparer.
public class Person
{
public string Name { get; set; }
public bool Flag { get; set; }
}
public class PersonComparer : IEqualityComparer<Person>
{
#region IEqualityComparer<Person> Members
public bool Equals(Person x, Person y)
{
//case insensitive compare
return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(Person obj)
{
return base.GetHashCode();
}
#endregion
}
and in the main portion of the code I have 2 lists "source" and "target"
Person bob = new Person() { Name = "Bob" };
Person sam = new Person() { Name = "Sam" };
Person andy = new Person() { Name = "Andy" };
Person thomas = new Person() { Name = "Thomas" };
Person jimmy = new Person() { Name = "Jimmy" };
Person sam2 = new Person() { Name = "sam" }; // note the lower case
Person jane = new Person() { Name = "Jane" };
List<Person> source = new List<Person>() { bob, sam, andy, thomas };
List<Person> target = new List<Person>() { sam2, andy,jane };
what I want to do
update source list to only contain sam and andy, as bob and thomas are not in the target list. I did this
source = (from p in source where (from t in target select t)
.Contains(p, new PersonComparer())
select p).ToList();
In the target I should "Flag" sam2 and andy to true and jane is flagged as "false" by default, I should not change it.
I tried using this, but this removes "jane" from target
//sets sam2 & andy to true, removes Jane
target = (from p in target.Select(t => { t.Flag = true; return t; })
where (from s in source
select s).Intersect(select p).ToList();
Can any LINQ guru tell me what I am doing wrong ?
3.Is there a better way to write Query 1 ?
4.And finally a trivial question: how exactly do you say "=>" when you are talking to a fellow coder over the phone

As Sander has pointed out, LINQ is for querying, not updating.
However, to answer the questions... Your original query of
source = (from p in source where (from t in target select t)
.Contains(p, new PersonComparer()) select p).ToList();
would be much more simply written as:
source = source.Intersect(target, new PersonComparer()).ToList();
Having said that, you need to update PersonComparer as recursive mentioned. It should be something like this:
public int GetHashCode(Person obj)
{
return obj == null ? 0
: StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name);
}
I'm afraid I don't really understand your second query particularly well... but if you want to change the existing objects, I'd suggest a foreach loop instead of trying to use LINQ. Queries with side-effects are generally a bad idea.
You may mean something like:
// You may want to make some singleton instance available, as this has no state
PersonComparer comparer = new PersonComparer();
foreach (Person person in target)
{
if (source.Contains(person, comparer))
{
person.Flag = true;
}
}

Linq isn't meant to update list, because it operates on IEnumerable<T>. You can create a new enumerable, based on source and target that represents the collection you need.
Something like this should work:
var combined = source.Where(x => target.Any(y => y == x))

For part 4. => can be read as goes to.

The GetHashCode() method should use the the obj passed instance, not its own parent.

Related

Produce the set difference of two List Items in C#

I have 2 Lists available to me. I need to gather the data that is no longer used.
For Example
List 1:
1
2
3
4
5
List 2:
1
2
4
5
6
The result data set needs to be.
Items not Included in List 2:
3
I was hoping to use something along the lines of:
var itemsNotInList2 = List2.Except(List1).ToList();
You're dealing with List<int> in this example then you have the right idea, just the args reversed. It should be;
var itemsNotInList2 = List1.Except(List2).ToList();
Think about how to state this in plain English. To get itemsNotInList2 I want to take everything in List1 except what's in List2. Your code in the question is giving you items that are in List2 but not in List1 which there are none of since List2 is a subset of List1
Note that this approach is often not suitable for reference types because the default comaparer will compare the references themselves. In order to do a similar operate with objects you'd have to implement IEqualityComparer and invoke the overload which accepts that as it's third argument. For example if you were dealing with a List<Person> and Person had a public string Ssid you could define Equal with return p1.Ssid == p2.Ssid and use that as your basis of comparison. You can find examples of this on msdn should you need it.
public class Person
{
public string Ssid;
// other properties and methods
}
public class PersonSsidEqualityComparer : IEqualityComparer<Person>
{
public bool Equal(Person lhs, Person rhs)
{
return lhs.Ssid == rhs.Ssid
}
public int GetHashCode(Person p)
{
return p.Value.GetHashCode();
}
}
Now as an example;
List<Person> people = new List<Person>();
List<Person> otherPeople = new List<Person>();
Person p1 = new Person("123"); // pretend this constructor takes an ssid
Person p2 = new Person("123");
Person p3 = new Person("124");
Person p4 = p1;
Now some examples using the data I set up above;
people.Add(p1);
people.Add(p3);
otherPeople.Add(p2);
var ThemPeople = people.Except(otherPeople);
// gives you p1 and p3
var ThemOtherPeople = people.Except(otherPeople, new PersonSsidEqualityComparar());
// only gives you p3
otherPeople.Add(p4);
var DoingReferenceComparesNow = people.Except(otherPeople);
// gives you only p3 cause p1 == p4 (they're the same address)
Try this
var itemsNotInList2 = List1.Except(List2).ToList();
If you're comparing objects, you should probably provide your own Equality Comparer.
For example:
public class YourClass
{
public int Value;
}
public class YourClassEqualityComparer : IEqualityComparer<YourClass>
{
public bool Equals(YourClass x, YourClass y)
{
return x.Value == y.Value;
}
public int GetHashCode(YourClass obj)
{
return obj.Value.GetHashCode();
}
}
So you can use an overload of Except that takes an instance of your equality comparer:
var list = l1.Except(l2, new YourClassEqualityComparer());

C# Finding element in List of String Arrays

I cannot solve a problem for several hours now.
Here is a simplified scenario.
Let's say there is a list of people with their bids. I'm trying to find a person with the highest bid and return the name. I am able to find the highest bid, but how to I output the name?
List<String[]> list = new List<String[]>();
String[] Bob = { "Alice", "19.15" };
String[] Alice = {"Bob", "28.20"};
String[] Michael = { "Michael", "25.12" };
list.Add(Bob);
list.Add(Alice);
list.Add(Michael);
String result = list.Max(s => Double.Parse(s.ElementAt(1))).ToString();
System.Console.WriteLine(result);
As a result I get 28.20, which is correct, but I need to display "Bob" instead. There were so many combinations with list.Select(), but no success. Anyone please?
The best solution from an architectural point of view is to create a separate class (e.g. Person) that contains two properties Name and Bid of each person and a class Persons that contains the list of persons.
Then you can easily use a LINQ command.
Also instead of storing bids as string, think if bids as floating point or decimal values would be better (or store it in cents and use an int).
I don't have a compiler by hand so it's a bit out of my head:
public class Person
{
public string Name { get; set; }
public float Bid { get; set; }
public Person(string name, float bid)
{
Debug.AssertTrue(bid > 0.0);
Name = name;
Bid = bid;
}
}
public class Persons : List<Person>
{
public void Fill()
{
Add(new Person("Bob", 19.15));
Add(new Person("Alice" , 28.20));
Add(new Person("Michael", 25.12));
}
}
In your class:
var persons = new Persons();
persons.Fill();
var nameOfHighestBidder = persons.MaxBy(item => item.Bid).Name;
Console.WriteLine(nameOfHighestBidder);
This works in the simple example. Not sure about the real one
var result = list.OrderByDescending(s => Double.Parse(s.ElementAt(1))).First();
You can use Jon Skeet's MaxBy.
For usage you can see this question
e.g. in this case
list.MaxBy(s => Double.Parse(s.ElementAt(1)))[0]
More here
Should work:
var max = list.Max(t => double.Parse(t[1]));
list.First(s => double.Parse(s[1]) == max)[0]; // If list is not empty
After finding result just do as below:
list.First(x=>x[1] == result)[0]

LINQ to select items in collections with differing types

How would one implement LINQ to extract the Guid's from one collection of objects of type A such that they can exclude these Guids from another collection of objects of type B. Object A and Object B both have a Guid field called 'ID."
I have the following:
ObservableCollection<Component> component Component has a
field called ID of type Guid
ObservableCollection<ComponentInformation> ComponentInformationCollection ComponentInformation
has a field called ID of type Guid
My implementation:
component =>
{
if (component != null)
{
var cancelledComponents = new List<ComponentInformation>();
foreach (Component comp in component)
{
cancelledComponents.Add(new ComponentInformation() { ID = comp.ID });
}
this.ComponentInformationCollection.Remove(cancelledComponents);
}
});
I believe there is a more elegant solution which I've been working at to solve but the issue I keep running into is creating a 'new ComponentInformation' such that the types do not give me an error.
====== FINAL SOLUTION =======
var cancelledComponentIDs = new HashSet<Guid>(component.Select(x => x.ID));
this.ComponentInformationCollection.Remove(
this.ComponentInformationCollection.Where(x => cancelledComponentIDs.Contains(x.ID)).ToList());
Thank you to:
Jason - I used this as a template for my final solution (listed below).
Servy - While I could have used a comparer, I think for this particular scenario a comparer was not neccessary because of its one-time-use type of situation.
ComponentInformationCollection is a Silverlight DependencyProperty that will trigger a INotifyChangedEvent (MVVM pattern) when altered, so the solution above worked best for my situation.
I would do this:
var ids = new HashSet<Guid>(
component.Select(x => x.ID)
);
var keepers = ComponentInformationCollection.Where(x => !ids.Contains(x.ID));
If Component doesn't already define an Equals and GetHashCode that uses the ID to do the compare you can define a comparer such as this:
class ComponentComparer : IEqualityComparer<Component>
{
public int Compare(Component a, Component b)
{
return a.ID.CompareTo(b.ID);
}
public int GetHashCode(Component a)
{
return a.ID.GetHashCode();
}
}
Then you can just use:
var result = componentCollectionA.Except(componentCollectionB, new ComponentComparer());
(written off of the top of my head; may require minor modifications to get it to compile.)
LINQ will allow you to find the GUIDs you need, but LINQ sequences are generally immutable; you'll still need to use some kind of loop to actually change the collection. The trick is getting the correct instances of your original collection that you want to remove.
Implementing one of the equality/comparison interfaces is one way to go, and if you need to compare your objects for equality in multiple places, is definitely the way to go. If you don't want to do that, this should get you what you want:
var removeme = (from x in this.ComponentInformationCollection
join y in component on x.ID equals y.ID
select x).ToList();
removeme.ForEach(x => this.ComponentInformationCollection.Remove(x));
Thinking out loud (meaning I didn't create a project and types and compile this), but how about:
var cancelledComponents = component.Select(c=> new ComponentInformation() {ID = c.ID}).ToList();
cancelledComponents.ForEach(c => ComponentInformationCollection.Remove(c));
There are a number of ways to solve this... this is a pretty simple Linq statement to query the ones you are looking for from the collection.
var keep = typeAList.Where(a => typeBList.FirstOrDefault(b => a.ID == b.ID) == null);
Here is the little test app I put together to demo it.
class Program
{
static void Main(string[] args)
{
List<TypeA> typeAList = new List<TypeA>();
typeAList.Add(new TypeA() { ID = Guid.NewGuid() });
typeAList.Add(new TypeA() { ID = Guid.NewGuid() });
typeAList.Add(new TypeA() { ID = Guid.NewGuid() });
List<TypeB> typeBList = new List<TypeB>();
typeBList.Add(new TypeB() { ID = typeAList[0].ID });
typeBList.Add(new TypeB() { ID = typeAList[1].ID });
//this is the statement
var keep = typeAList.Where(a => typeBList.FirstOrDefault(b => a.ID == b.ID) == null);
}
}
class TypeA
{
public Guid ID { get; set; }
}
class TypeB
{
public Guid ID { get; set; }
}

Comparing two lists and ignoring a specific property

I have two employee lists that I want to get only unique records from but this has a twist to it. Each list has an Employee class in it:
public class Employee
{
// I want to completely ignore ID in the comparison
public int ID{ get; set; }
// I want to use FirstName and LastName in comparison
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
The only properties I want to compare on for a match are FirstName and LastName. I want to completely ignore ID in the comparison. The allFulltimeEmployees list has 3 employees in it and the allParttimeEmployees list has 3 employees in it. The first name and last name match on two items in the lists - Sally Jones and Fred Jackson. There is one item in the list that does not match because FirstName is the same, but LastName differs:
emp.id = null; // not populated or used in comparison
emp.FirstName = "Joe"; // same
emp.LastName = "Smith"; // different
allFulltimeEmployees.Add(emp);
emp.id = 3; // not used in comparison
emp.FirstName = "Joe"; // a match
emp.LastName = "Williams"; // not a match - different last name
allParttimeEmployees.Add(emp);
So I want to ignore the ID property in the class during the comparison of the two lists. I want to flag Joe Williams as a non-match since the last names of Smith and Williams in the two lists do not match.
// finalResult should only have Joe Williams in it
var finalResult = allFulltimeEmployees.Except(allParttimeEmployees);
I've tried using an IEqualityComparer but it doesn't work since it is using a single Employee class in the parameters rather than an IEnumerable list:
public class EmployeeEqualityComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
if (x.FirstName == y.FirstName && x.LastName == y.LastName)
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(Employee obj)
{
return obj.GetHashCode();
}
}
How can I successfully do what I want and perform this operation? Thanks for any help!
Your idea of using the IEqualityComparer is fine, it's your execution that is wrong. Notably, your GetHashCode method.
public int GetHashCode(Employee obj)
{
return obj.GetHashCode();
}
IEqualityComparer defines both Equals and GetHashCode because both are important. Do not ignore GetHashCode when you implement this interface! It plays a pivotal role on equality comparisons. No, it is not an indication that two items are equal, but it is an indicator that two elements are not. Two equal elements must return the same hash code. If they do not, they cannot be considered equal. If they do, then they might be equal, and equality functions only then go on to explore Equals.
With your implementation delegating to the GetHashCode method of the actual employee object, you are relying upon the implementation that Employee class uses. Only if that implementation is overriden will it be useful for you, and only if it is using your key fields. And if it is, then it is very likely that you did not need to define your own external comparer in the first place!
Build a GetHashCode method that factors in your key fields and you will be set.
public int GetHashCode(Employee obj)
{
// null handling omitted for brevity, but you will want to
// handle null values appropriately
return obj.FirstName.GetHashCode() * 117
+ obj.LastName.GetHashCode();
}
Once you have this method in place, then use the comparer in your call to Except.
var comparer = new EmployeeEqualityComparer();
var results = allFulltimeEmployees.Except(allParttimeEmployees, comparer);
You can override Equals and GetHashCode in your Employees class.
For example,
public class Employee
{
// I want to completely ignore ID in the comparison
public int ID { get; set; }
// I want to use FirstName and LastName in comparison
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
var other = obj as Employee;
return this.FirstName == other.FirstName && this.LastName == other.LastName;
}
public override int GetHashCode()
{
return this.FirstName.GetHashCode() ^ this.LastName.GetHashCode();
}
}
I tested with the following data set:
var empList1 = new List<Employee>
{
new Employee{ID = 1, FirstName = "D", LastName = "M"},
new Employee{ID = 2, FirstName = "Foo", LastName = "Bar"}
};
var empList2 = new List<Employee>
{
new Employee { ID = 2, FirstName = "D", LastName = "M" },
new Employee { ID = 1, FirstName = "Foo", LastName = "Baz" }
};
var result = empList1.Except(empList2); // Contained "Foo Bar", ID #2.
your IEqualityComparer should work:
var finalResult = allFulltimeEmployees.Except(allParttimeEmployees, new EmployeeEqualityComparer());
Try implementing the IEquatable(T) interface for your Employee class. You simply need to provide an implementation for an Equals() method, which you can define however you want (i.e. ignoring employee IDs).
The IEquatable interface is used by generic collection objects such
as Dictionary, List, and LinkedList when testing
for equality in such methods as Contains, IndexOf, LastIndexOf, and
Remove. It should be implemented for any object that might be stored
in a generic collection.
Example implementation of the Equals() method:
public bool Equals(Employee other)
{
return (other != null) && (FirstName == other.FirstName) && (LastName == other.LastName);
}
It's not the most elegant solution, but you could make a function like so
public string GetKey(Employee emp)
{
return string.Format("{0}#{1}", emp.FirstName, emp.LastName)
}
and then populate everything in allFullTimeEmployees into a Dictionary<string, Employee> where the key of the dictionary is the result of calling GetKey on each employee object. Then you could loop over allParttimeEmployees and call GetKey on each of those, probing into the dictionary (e.g. using TryGetValue or ContainsKey), and taking whatever action was necessary on a duplicate, such as removing the duplicate from the dictionary.

How do I Iterate Linq group result set?

I'm getting some data from my database and using linq to calculate sums and counts and group the data.
This is what I have:
var si = _repository.GetAllByDate(date);
var cs = from s in si
group s by s.Name into g
select new { Comm = g.Key, SIList = g.ToList(), Count = g.Count() };
i now need to pass cs to a method in another class so that I can extract Comm, SIList and Count for each item in the group, what type do I pass it as? IEnumerable doesn't work. The actual linq group result type seems to be:
{System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<Model.Domain.MasterData
.MyItem,Model.Domain.SI<>f__AnonymousTyped<Model.Domain.MasterData.MyItem,System.Collections.Generic.List<Model.Domain.SI>,int>>}
Any ideas? I effectively want to pass cs as a variable and iterate through it there.
You'll need to create a type that matches the definition of your anonymous type, if it's going to be used in different scopes.
public class SomeClass {
public Comm Comm { get; set; }
public IList<String> SIList { get; set; }
public Int32 Count { get; set; }
}
var si = _repository.GetAllByDate(date);
var cs = from s in si
group s by s.Name into g
select new SomeClass { Comm = g.Key, SIList = g.ToList(), Count = g.Count() };
EDIT: I supposed we can assume that the list will be of String so I'm editing for that. If that's the wrong type you'll need to change the IList<T> definition accordingly.
The reason that you get such a complicated type is because the query uses lazy execution. You are looking at the type of the expression that returns the result, not the type of the result.
The type of the result is IEnumerable<_hidden_internal_class_name_>, i.e. as you are creating anonymous objects in the query, the result is a stream of objects of a class that the compiler creates internally.
It's pretty useless to pass on that result to another method, as it would need to use reflection to read the properties in the objects. You should create a named class for the objects in the result, so that it's easy to access its properties.
Creating a type is an excellent idea, but why do that when a returned Tuple can be done without creating a new class or struct? If the need is local and or internal and the class won't be reused, try using a Tuple instead.
Select new Tuple<Comm, IEnumerable<string>, Int32>( new Comm(), myStringList.AsEnumerable(), myCount )
class Pet
{
public string Name { get; set; }
public int Age { get; set; }
}
// Uses method-based query syntax.
public static void GroupByEx1()
{
// Create a list of pets.
List<Pet> pets =
new List<Pet>{ new Pet { Name="Barley", Age=8 },
new Pet { Name="Boots", Age=4 },
new Pet { Name="Whiskers", Age=1 },
new Pet { Name="Daisy", Age=4 } };
// Group the pets using Age as the key value
// and selecting only the pet's Name for each value.
IEnumerable<IGrouping<int, string>> query =
pets.GroupBy(pet => pet.Age, pet => pet.Name);
// Iterate over each IGrouping in the collection.
foreach (IGrouping<int, string> petGroup in query)
{
// Print the key value of the IGrouping.
Console.WriteLine(petGroup.Key);
// Iterate over each value in the
// IGrouping and print the value.
foreach (string name in petGroup)
Console.WriteLine(" {0}", name);
}
}
/*
This code produces the following output:
8
Barley
4
Boots
Daisy
1
Whiskers
*/
Pass it as object and in your foreach loop, use var as the iterator.

Categories