Merge two lists by key of first list - c#

My main problem description goes like this:
I have two lists of BankAccount objects. A BankAccount has properties such as BankCode and AccountNumber which uniquely identifies an account. So both the lists may contain the same bank account but they may have their Source, Amount, or AccountTypes differing.
The aim here is to merge those two lists:
add accounts to the first list if it is available in the second (but not in the first list).
If the bank accounts are the same in both lists, update the details of the bank account in the first list with the details of the (matching) bank account in the 2nd list.
I've tried implementing the solution mentioned in one SO post. I've went and tried writing my code down at a .NET code pad site. But I am not able to get the output after trying to execute line no. 93 which I've commented.
class BankAccount
{
public string BankCode{get;set;}
public string AccountNumber{get;set;}
public string AccountType{get;set;}
public string Amount{get;set;}
public string Source{get;set;}
public override bool Equals(object obj)
{
var acc = obj as BankAccount;
return Equals(acc);
}
public override int GetHashCode()
{
return this.GetHashCode();
}
public bool Equals(BankAccount acc2)
{
if(acc2 == null) return false;
if(string.IsNullOrEmpty(acc2.BankCode)) return false;
if(string.IsNullOrEmpty(acc2.AccountNumber)) return false;
return this.BankCode.Equals(acc2.BankCode) && this.AccountNumber.Equals(acc2.AccountNumber);
}
}
//List<BankAccount> lst3 = lst.Union(lst1).ToList(); // line 93
Full code can be viewed here.
PS: I'm not sure if this could be a problem with the codepad site or not.
Update - Monday, 14 February 2011 - 4:50:24 (am) / 04:50:24 GMT
Thanx for the update. But something is still amiss. In the output, list 3's first item should have AccountType=P and Source=lst2. The 2nd requirement isn't met. I figure Union() does only a part of what I need. What do I need to do satisfy the 2nd requirement.
EDIT by drachenstern: I'm not sure this title is any better, but it's definitely more informative than the previous title as to the actual question :\

Solution 1:
This solution doesn't achieve your (newly) stated 2 requirements, but aims to fix the issue in your attempt at solving the problem using a LINQ Union().
You've got a recursive call which is causing a stack overflow exception on this line (23):
public override int GetHashCode()
{
return this.GetHashCode();
}
I suggest changing it to something like this:
public override int GetHashCode()
{
return (BankCode + AccountNumber).GetHashCode();
}
EDIT:
Ensure that members BankCount and AccountNumber will never be null or an exception will be thrown. I suggest you look up standard practices for overriding the GetHashCode() method.
Resharper's autogenerated GetHashCode override:
(The 397 value ensures that the numbers won't clash if the BankCode and AccountNumber are swapped. The unchecked means that there won't be overflow issues with the number *397)
public override int GetHashCode()
{
unchecked
{
return ((BankCode != null ? BankCode.GetHashCode() : 0)*397) ^ (AccountNumber != null ? AccountNumber.GetHashCode() : 0);
}
}
Solution 2:
This solution aims to achieve your 2 requirements, without using a LINQ Union().
If you are wanting to merge the lists, using the 2nd list as the preference then perhaps try this:
var mergedList = new List<BankAccount>();
// add items from lst or any duplicates from lst1
foreach (var bankAccount in lst)
{
var account = bankAccount;
var dupe = lst1.FirstOrDefault(item => item.Equals(account));
mergedList.Add(dupe ?? bankAccount);
}
// add any items in lst1 that are not duplicates
foreach (var bankAccount in lst1.Where(item=>!mergedList.Contains(item)))
{
mergedList.Add(bankAccount);
}
If you're looking for code minimization:
// add items from lst or any duplicates from lst1
var temp = lst.Select(item => lst1.FirstOrDefault(item1 => item1.Equals(item)) ?? item);
// add any items in lst1 that are not duplicates
var result = temp.Union(lst1.Where(item => !temp.Contains(item)));

The problem is this method in class BankAccount. It's causing a stack overflow because it keeps calling itself.
public override int GetHashCode()
{
return this.GetHashCode();
}
Try using this.ToString().GetHashCode() and overriding ToString with something that makes sense for the class.

Related

Update items in List<T> c#

I have a class to handle some data :
public class User
{
public string Name;
public string Date;
}
In another class,i create a List of User class and add data as follows :
public class Display
{
List<User> userData = new List<User>;
private void add()
{
User udata = new User;
udate.Name = "Abc";
udata.Date = "1/1/18";
userData.Add(udata);
}
}
My question is, after adding some data,how do i update it ? Say i have added a data(udata is what i mean) with a Name of ABC,how do i update it?
Since your list contains a mutable type, all you need to do is get a reference to the specific item you want to update.
That can be done in a number of ways - using it's index, using the Find method, or using linq are the first three that comes to mind.
Using index:
userData[0]?.Name = "CBA";
Using Find:
userData.Find(u => u.Name = "Abc")?.Name = "CBA";
Using linq (FirstOrDefault is not the only option):
userData.FirstOrDefault(u => u.Name = "Abc")?.Name = "CBA";
Note the use of null conditional operator (]? and .?) it prevents a null reference exception in case the item is not found.
Update
As Ak77th7 commented (thanks for that!), the code in this answer wasn't tested and will cause a compilation error -
error CS0131: The left-hand side of an assignment must be a variable,
property or indexer
The reason for this is the null-conditional operator (?.).
You can use it to get values from properties, but not for setting them.
The fix is either to accept the fact that your code might throw a NullReferenceException (which I personally believe has no room in production-grade code) or to make your code a bit more cumbersome:
// Note: Possible null here!
userData.Find(u => u.Name.EndsWith("1")).Name = "Updated by Find";
// Safe, but cumbersome
var x = userData.FirstOrDefault(u => u.Name.EndsWith("2"));
if(x is not null)
{
x.Name = "Updated by FirstOrDefault";
}
See a live demo on SharpLab.IO
Nothing tricky, really (but does use System.Linq)
**EDIT: Changed Single to First to avoid error if there are two users with the same name. **
void Update(string name, string newName)
{
var user = userData.First(u => u.Name == name);
user.Name = newName;
}
Notice this changes the object, and the List maintains reference to the changed object.

GroupBy returns different results

IEnumerable<IGrouping<StatusType, Request>> group = requests.GroupBy(r=> r.StatusType );
The grouping function above works with when requests (List<Requests>) is from EntityFramework/db.
When changing the assignment of requests from db direct, to a web service,
the grouping isn't working as intended.
Digging a bit, I found that the hash or equality of the StatusType's is different when coming from db vs web (found out thru this post).
From the accepted answer of the post, I can bypass/(resolve?) the problem by overriding..
public class StatusType : IEquatable<int>
{ // omitted other crucial equality comparison components.
// but for brevity..
public override int GetHashCode()
{
return Id;
}
}
Although overriding StatusType somewhat resolves the issue,
I feel its quite risky as
I am not the author of the code base.
There are multiple references to StatusType increasing the potential
of impending failure.
My question,
Is there a way to group by the StatusTypeId (int)
requests.groupBy(r=> r.StatusTypeId) // returns IEnumerable<IGrouping<int,Rquest>>
but get the StatusType?
IEnumerable<IGrouping<StatusType,Rquest>>
Define comparer for StatusType:
public class StatusTypeComparer : IEqualityComparer<StatusType>
{
public bool Equals(StatusType x, StatusType y)
{
return x.Id == y.Id;
}
public int GetHashCode(StatusType obj)
{
return obj.Id.GetHashCode();
}
}
Pass it to GroupBy method:
IEnumerable<IGrouping<StatusType, Request>> group =
requests.GroupBy(r => r.StatusType,
new StatusTypeComparer());
Disclaimer: Backs has a much better answer than mine but I thought I'd post it anyway in the interests of diversity.
You might be able to get something like the functionality you're looking for by using multiple Linq queries. I don't know if there is an accessible implementation of IGrouping I can use, so I've gone with Tuple<StatusType, List<Request>> instead. It should have a similar effect. So, from your original query:
IEnumerable<IGrouping<int, Request>> group = requests.GroupBy(r=> r.StatusTypeId );
You can add the following line:
IEnumerable<Tuple<StatusType, List<Request>>> groupByStatusType =
group.Select(x => new Tuple<StatusType, List<Request>>(x.First().StatusType,
x.ToList()));
Or, you can do it all on one line:
IEnumerable<Tuple<StatusType, List<Request>>> group =
requests.GroupBy(r => r.StatusTypeId)
.Select(x => new Tuple<StatusType, List<Request>>(x.First().StatusType,
x.ToList()));
You can of course tweak the queries depending on what kind of output you're expecting, but this should at least get you started. Alternately, you could get a similar result by implementing a function that iterates through everything and "manually" creates an output.

Assinging a sequential "row number" in linq output [duplicate]

This question already has answers here:
LINQ: Add RowNumber Column
(9 answers)
Closed 6 years ago.
I would like to access the row number in linq query.
There are many articles on the web stating how to do this but there is a catch:
I want to resolve the enumeration "later" and I want it to assign the same ID every time.
For this reason methods such as this do not work:
public IEnumerable<MyClass> GetThings(List<object> lst)
{
int ID=0;
return from i in lst
select new MyClass(ID++, i);
}
public class MyClass
{
public MyClass(int ID, object Stuff)
{ ... }
}
...
var x = GetThings(SomeList);
(fails because each time you resolve x by iterating each item gets a different id)
Actually you can use the Select overload that pass the index too, something like this:
lst.Select((o, i) => new MyClass(i, o));
Turns out the solution to this is quite simple - grab the row number of the source collection, not the output collection.
Obviously this only works if you are not filtering etc. and the number of items in your output collection is the same as the input collection. Unless you're ok with gaps and/or duplicates
i.e.
public IEnumerable<MyClass> GetThings(List<object> lst)
{
int ID=0;
return from i in lst.Select((item,id)=>new {Item=item, ID=id})
select new MyClass(i.ID, i.Item);
}
public class MyClass
{
public MyClass(int ID, object Stuff)
{ ... }
}
...
var x = GetThings(SomeList);
now the ID's of each item in x is the same every time you iterate it

Remove specific entry from list (beginner in c#)

I have a simple static inventory class which is a list of custom class Item. I am working on a crafting system and when I craft something I need to remove the required Items from my inventory list.
I tried to create a method that I can call which takes an array of the items to remove as a parameter, but its not working.
I think its because the foreach loop doesn't know which items to remove? I am not getting an error messages, it just doesn't work. How can I accomplish this?
public class PlayerInventory: MonoBehaviour
{
public Texture2D tempIcon;
private static List<Item> _inventory=new List<Item>();
public static List<Item> Inventory
{
get { return _inventory; }
}
public static void RemoveCraftedMaterialsFromInventory(Item[] items)
{
foreach(Item item in items)
{
PlayerInventory._inventory.Remove(item);
}
}
}
Here is the function that shows what items will be removed:
public static Item[] BowAndArrowReqs()
{
Item requiredItem1 = ObjectGenerator.CreateItem(CraftingMatType.BasicWood);
Item requiredItem2 = ObjectGenerator.CreateItem(CraftingMatType.BasicWood);
Item requiredItem3 = ObjectGenerator.CreateItem(CraftingMatType.String);
Item[] arrowRequiredItems = new Item[]{requiredItem1, requiredItem2, requiredItem3};
return arrowRequiredItems;
}
And here is where that is called:
THis is within the RecipeCheck static class:
PlayerInventory.RemoveCraftedMaterialsFromInventory(RecipeCheck.BowAndArrowReqs());
While I like Jame's answer (and it sufficiently covers the contracts), I will talk on how one might implement this equality and make several observations.
For starts, in the list returned there may be multiple objects of the same type - e.g. BasicWood, String. Then there needs to be a discriminator used for each new object.
It would be bad if RemoveCraftedMaterialsFromInventory(new [] { aWoodBlock }) to remove a Wood piece in the same way that two wood pieces were checked ("equals") to each other. This is because being "compatible for crafting" isn't necessarily the same as "being equals".
One simple approach is to assign a unique ID (see Guid.NewGuid) for each specific object. This field would be used (and it could be used exclusively) in the Equals method - however, now we're back at the initial problem, where each new object is different from any other!
So, what's the solution? Make sure to use equivalent (or identical objects) when removing them!
List<Item> items = new List<Item> {
new Wood { Condition = Wood.Rotten },
new Wood { Condition = Wood.Epic },
};
// We find the EXISTING objects that we already have ..
var woodToBurn = items.OfType<Wood>
.Where(w => w.Condition == Wood.Rotten);
// .. so we can remove them
foreach (var wood in woodToBurn) {
items.Remove(wood);
}
Well, okay, that's out of the way, but then we say: "How can we do this with a Recipe such that Equals isn't butchered and yet it will remove any items of the given type?"
Well, we can either do this by using LINQ or a List method that supports predicates (i.e. List.FindIndex) or we can implement a special Equatable to only be used in this case.
An implementation that uses a predicate might look like:
foreach (var recipeItem in recipeItems) {
// List sort of sucks; this implementation also has bad bounds
var index = items.FindIndex((item) => {
return recipeItem.MaterialType == item.MaterialType;
});
if (index >= 0) {
items.RemoveAt(index);
} else {
// Missing material :(
}
}
If class Item doesn't implement IEquatable<Item> and the bool Equals(Item other) method, then by default it will use Object.Equals which checks if they are the same object. (not two objects with the same value --- the same object).
Since you don't say how Item is implemented, I can't suggest how to write it's Equals(), however, you should also override GetHashCode() so that two Items that are Equal return the same hash code.
UPDATE (based on comments):
Essentially, List.Remove works like this:
foreach(var t in theList)
{
if (t.Equals(itemToBeRemove))
PerformSomeMagicToRemove(t);
}
So, you don't have to do anything to the code you've given in your question. Just add the Equals() method to Item.

LINQ query returns duplicate despite Distinct()

Ive got the following query that binds to a DropDownList;
if (!Page.IsPostBack)
{
var branchTags =
(
from t in context.ContactSet
orderby t.py3_BranchArea
where t.py3_BranchArea != null
select new
{
BranchTagCode = t.py3_BranchArea,
BranchTag = (t.FormattedValues != null && t.FormattedValues.Contains("py3_brancharea") ? t.FormattedValues["py3_brancharea"] : null)
}
).Distinct();
ddlBranchTags.DataSource = branchTags;
ddlBranchTags.DataBind();
}
For some reason it still ourputs 2 rows that are visually the same. It might be the case that there are two enitites in the CRM with the same name. But, if Im using distinct on the query and only returning the 'py3_brancharea' then surely the Distinct should be run on the actual records returned?
So, this suggests to me -and my limited LINQ knowledge- that its because of the line:
BranchTagCode = t.py3_BranchArea
But, this needs to be called to make it possible to call the FormattedValues.
How then do I get a distinct set of results based purely on 'BranchTag' ?
If Distinct() is not working it is possibly a problem with the particular classes gethashcode() or equals() override methods, which are either not set up correctly or omitted entirely. In a custom class you will most likely need to specify these overrides to get Distinct() and other like methods to function correctly.
You could try to use a where or any clause to differentiate between duplicates as well. Which could be a work around for the Distinct() issues.
To further explain how to set up the Distinct() Method with custom classes. You will need to within the class that you are searching through set the override methods GetHashCode() and Equals(). These or Object level methods that should be in every single class no matter what. To start head to the class in question and type this:
public override bool Equals(object obj)
then
public override int GetHashCode()
Lets say you have this simple class before the overrides:
class foo{
int H {get;set;}
public foo(int _h){
H = _h;
}
}
It would now look like this:
class foo{
int H {get;set;}
int K {get;set;}
public override bool Equals(object obj){
if(obj == null) return false;
foo test = (foo)obj);
if(test == null) return false;
if(this.H == obj.H && this.K == obj.K) return true;
}
public override int GetHashCode(){
int hashH = H.GetHashCode();
int hashK = K.GetHashCode();
return hashH ^ hashK;
}
public foo(int _h){
H = _h;
}
}
Now you could use Distinct() on Ienumerable types containing the foo class like so:
List<foo> FooList = new List<foo>(Collection of 9 Foos);
var res = FooList.Distinct();
Another, much more simple way that worked for me, but may not work in all situations, is using this guys method ( GroupBy() and First()):
Finding Distinct Elements in a List
He creates a List<Customer> customers with FirstName and LastName. Then groups them all by FirstName and grabs the first element from each group!
`
List< Customer > customers = new List< Customer >;
{
new Customer {FirstName = "John", LastName = "Doe"},
new Customer {FirstName = "Jane", LastName = "Doe"},
new Customer {FirstName = "John", LastName = "Doe"},
new Customer {FirstName = "Jay", LastName = null},
new Customer {FirstName = "Jay", LastName = "Doe"}
};
`
Then:
`
var distinctCustomers = customers.GroupBy(s => s.FirstName)
.Select(s => s.First());
`
In my situation, I had to use FirstOrDefault() though.
Is it possible that the two results are different, do they have the same branch tag code and branch tag?
You could implement a custom equality comparer and pass it to distinct() so that it only compares the field that you want? it's a bit more difficult because of the anonymous type in your select statement, but this answer has a way around that.
The default equality comparison for anonymous types is case sensitive. Do the returned values you expect have different casing? As Matt suggested you may want to look at a custom IEqualityComparer implementation on a custom class otherwise.
I changed my code from
.Distinct().ToList();
to
.ToList().Distinct().ToList();
and now it's able to avoid the duplicate. Not sure what's the reason behind.
Another possibility it that the Entity Object has a define key that is not unique.

Categories