C# Remove duplicated objects from list and increase first - c#

I have an list of objects => class Example { int quantity; string name; string comment; } and I want to remove all duplicates and increase the quantity by the number of duplicates that have the same name and comment.
Example:
[
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 2, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"},
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 1, name: "Bye", comment: "Good bye"},
]
and the result it should be:
[
{quantity: 2, name: "Hello", comment: "Hello there"},
{quantity: 3, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"}
]

I want to remove all duplicates
You haven't defined when two example objects are "duplicates". I guess, that you mean to say, that if two Examples objects have the same values for properties Name and Comment, then they are duplicates.
Normally you can use one of the overloads of Enumerable.GroupBy to find Duplicates. Use the overload with a parameter resultSelector to define exactly what you want as result.
IEnumerable<Example> examples = ...
var result = examples.GroupBy(
// key: Name-Comment
example => new
{
Name = example.Name,
Comment = example.Comment,
}
// parameter resultSelector: for every Name/Comment combination and all
// Examples with this Name/Comment combination make one new example:
(nameCommentCombination, examplesWithThisNameCommentCombination) => new Example
{
Name = nameCommentCombination.Name,
Comment = nameCommentCombination.Comment,
// Quantity is the sum of all Quantities of all Examples with this
// Name/Comment combination
Quantity = examplesWithThisNameCommentCombination
.Select(example => example.Quantity)
.Sum(),
});
This will only work if you want exact string equality. Are "Hello" and "HELLO" equal? And how about "Déjà vu" and "Deja vu"? Do you want case insensitivity for Name and Comment? And how about diacritical characters?
If you want more than just plain string equality, consider to create an ExampleComparer class.
class ExampleComparer : EqualityComparer<Example>
{
... // TODO: implement
}
Usage would be:
IEnumerable<Example> examples = ...
IEqualityComparer<Example> comparer = ...
var result = examples.GroupBy(example => example, // key
// resultSelector:
(key, examplesWithThisKey) => new Example
{
Name = key.Name,
Comment = key.Comment,
Quantity = examplesWithThiskey.Sum(example => example.Quantity),
},
comparer);
Implement the ExampleComparer
class ExampleComparer : EqualityComparer<Example>
{
public static IEqualityComparer<Example> ByNameComment {get;} = new ExampleComparer;
private static IEqualityComparer<string> NameComparer => StringComparer.CurrentCultureIgnoreCase;
private static IEqualityComparer<string> CommentComparer => StringComparer.CurrentCultureIgnoreCase;
I chose for two separate string comparers, so if later you decide different comparison, for instance Name has to match exactly, then you only have to change it here.
public override bool Equals (Example x, Example y)
{
// almost every Equals method starts with the following three lines
if (x == null) return y == null; // true if both null
if (y == null) return false; // false, because x not null
if (Object.ReferenceEquals(x, y)) return true; // same object
// return true if both examples are considered equal:
return NameComparer.Equals(x.Name, y.Name)
&& CommentComparer.Equals(x.Comment, y.Comment);
}
public override int GetHashCode(Example x)
{
if (x == null) return 5447125; // just a number
return NameComparer.GetHashCode(x.Name)
^ CommentComparer.GetHashCode(x.Comment);
}
Note: this will also work if Name or Comment are null or empty!
I used operator ^ (XOR), because that gives a fairly good hash if there are only two fields to consider. If you think that the vast majority of Examples have unique names, consider to check only property Name:
return NameComparer.GetHashCode(x.Name);
Because method Equals uses the NameComparer and CommentComparer to check for equality, make sure you use the same Comparers to calculate the HashCode.

Here's what I'd do:
Example[] before = new Example[]
{
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 2, Name = "Bye", Comment = "Good bye" },
new Example { Quantity = 1, Name = "Hi", Comment = "Hi there" },
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 1, Name = "Bye", Comment = "Good bye" },
};
Example[] after =
before
.GroupBy(x => new { x.Name, x.Comment }, x => x.Quantity)
.Select(x => new Example { Quantity = x.Sum(), Name = x.Key.Name, Comment = x.Key.Comment })
.ToArray();
That gives:
Perhaps this version is a little clearer:
Example[] after =
before
.GroupBy(
x => new { x.Name, x.Comment },
(k, xs) => new Example
{
Quantity = xs.Sum(x => x.Quantity),
Name = k.Name,
Comment = k.Comment
})
.ToArray();
Or perhaps this:
Example[] after =
(
from x in before
group x.Quantity by new { x.Name, x.Comment } into xs
select new Example
{
Quantity = xs.Sum(x => x),
Name = xs.Key.Name,
Comment = xs.Key.Comment
}
).ToArray();

Here's a simple solution, which gives you the answer in the List tata but you can do .ToArray() if you wish.
public class Example
{
public int quantity;
public string name;
public string comment;
}
Example[] toto = new Example[]
{
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 2,
name = "Bye",
comment = "Good bye"
},
new Example
{
quantity = 1,
name = "Hi",
comment = "Hi there"
},
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 1,
name = "Bye",
comment = "Good bye"
}
};
List<Example> tata = new List<Example>();
foreach (Example exa in toto)
{
bool found = false;
foreach (Example exb in tata)
{
if (exb.name == exa.name && exb.comment == exa.comment)
{
exb.quantity += exa.quantity;
found = true;
break;
}
}
if (!found)
{
tata.Add(exa);
}
}
A good exercise would be to LINQ that!

Related

Using Linq Select to create a list that contains more items than the original list

I have a list of items. For example (although the list could be any length):
var inputList = new List<Input1>()
{
new Input1() { Test = "a" },
new Input1() { Test = "b" }
};
What I want to do is create a new list of:
a1, a2, b8, b9
That is the value of Test (i.e. a) with a suffix based on the value of Test.
In that order. Obviously, this is a minimum workable example, not the actual problem. So I'd like to use something like the .Select to split the data - something like this:
var outputList = inputList.Select(x =>
{
if (x.Test == "a")
{
return new Input1() { Test = "a1" };
//return new Input1() { Test = "a2" };
}
else if (x.Test == "b")
{
return new Input1() { Test = "b8" };
//return new Input1() { Test = "b9" };
}
else
{
return x;
}
});
Input1 for completeness:
class Input1
{
public string Test { get; set; }
}
That is, to return a list that contains items that were not in the original list.
I realise I can use a foreach, but I'm interested if there's a better / more concise way.
Suppose you have a method that transforms your single input into multiple inputs:
public static Input1[] Transform(Input1 x)
{
if (x.Test == "a") return new[] {new Input1("a1"), new Input1("a2")};
if (x.Test == "b") return new[] {new Input1("b8"), new Input1("b9")};
return new[] {x};
}
(This is just from your toy example - I guess you actually need a transformation that is more meaningful.)
Then you can just use SelectMany to get your desired result in the correct order:
inputList
.SelectMany(Transform);
If you're using C# 8.0 or above, you may use switch expression as follows:
var outputList =
inputList.SelectMany(x => x.Test switch
{
"a" => new[] { new Input1() { Test = "a1" }, new Input1() { Test = "a2" } },
"b" => new[] { new Input1() { Test = "b8" }, new Input1() { Test = "b9" } },
_ => new[] { x }
})
.ToList();

How to merge a single list using C# lambda

I have a class like the following:
public class MyData
{
public int Key { get; set; }
public string MyString { get; set; }
public bool MyFlag { get; set; }
}
I have a list of these classes:
var data = new List<MyData>();
Which is populated as follows:
data.Add(new MyData() { Key = 1, MyFlag = true, MyString = "Hello" });
data.Add(new MyData() { Key = 1, MyFlag = false, MyString = "Goodbye" });
data.Add(new MyData() { Key = 2, MyFlag = true, MyString = "Test" });
data.Add(new MyData() { Key = 2, MyFlag = false, MyString = "Merge" });
data.Add(new MyData() { Key = 3, MyFlag = false, MyString = "Data" });
What I want is a list as follows:
Key true false
1 Hello Goodbye
2 Test Merge
3 Data
I need an anonymous type that reflects the three values above. I found a few posts and articles that seemed to suggest GroupJoin, but I'm unsure how I could use that in this case as it seems to allow joining two separate lists.
I suggest grouping (by Key property). If you want true and false properties we have to put it as #true and #false since true and false are keywords:
var result = data
.GroupBy(item => item.Key)
.Select(chunk => new {
Key = chunk.Key,
#true = chunk.FirstOrDefault(item => item.MyFlag)?.MyString,
#false = chunk.FirstOrDefault(item => !item.MyFlag)?.MyString,
});
Please note that I am not in front of a PC right now (so I could not test it), but this should give you an idea at the very least.
data
.GroupBy(x => x.Key)
.Select(
x => new {
Key = x.Key,
True = data.SingleOrDefault(y1 => y1.Key == x.Key && y1.MyFlag)?.MyString,
False = data.SingleOrDefault(y2 => y2.Key == x.Key && !y2.MyFlag)?.MyString
});

Flatten a collection based on an internal property

Given the follow class structure:
int Id;
string[] Codes;
And the following data:
Foo { Id = 1, Codes = new[] { "01", "02" } }
Foo { Id = 2, Codes = new[] { "02", "03" } }
Foo { Id = 3, Codes = new[] { "04", "05" } }
I would like to end up with the following structure.
Code = "01", Id = 1
Code = "02", Id = 1
Code = "02", Id = 2
Code = "03", Id = 2
Code = "04", Id = 3
Code = "05", Id = 3
I've got the following query, but it's giving me a collection as the Id rather than the flat structure I am after.
collection.GroupBy(f => f.Codes.SelectMany(c => c), f => f.Id,
(code, id) => new { Code = code, Id = id })
.ToArray()
What am I missing?
SelectMany can return multiple elements for each item as a single list
items
.SelectMany(foo => foo.Codes.Select(code => new { Id = foo.Id, Code = code }));
The answer of Diego Torres is correct; I would only add to it that this query is particularly concise and readable in the comprehension form:
var q = from foo in foos
from code in foo.Codes
select new { Code = code, foo.Id };

IEqualityComparer fails using two properties

When I use this comparer in Distinct() it always returns false. Can't see a reason why.
public class IdEqualityComparer : IEqualityComparer<Relationship>
{
public bool Equals(Relationship x, Relationship y)
{
if (x == null && y == null)
return true;
else if (x == null || y == null)
return false;
else if (x.ID == y.ID && x.RelatedID == y.RelatedID)
return true;
else
return false;
}
public int GetHashCode(Relationship obj)
{
unchecked
{
int hash = (obj.ID ?? "").GetHashCode() ^ (obj.RelatedID ?? "").GetHashCode();
return hash;
}
}
}
The hash seems correct to me, but the ID and RelatedID comparison never returns true.
It fails, as I can check the result afterward and the output is not distinct using those two properties.
Apologies to all! It is working fine.
I was comparing objects defined similarly to Marc's answer. But, I did this:
var relators = relationships.Distinct(new RelationshipEqualityComparer());
and then sent relationships, rather than realtors, to the next method where the items were reviewed. Sometimes it takes another pair of eyes!
Thanks!
Seems to work fine here;
static void Main()
{
var objs = new[]
{
new Relationship { ID = "a", RelatedID = "b" }, // <----\
new Relationship { ID = "a", RelatedID = "c" }, // |
new Relationship { ID = "a", RelatedID = "b" }, // dup--/
new Relationship { ID = "d", RelatedID = "b" }, // <------\
new Relationship { ID = "d", RelatedID = "c" }, // |
new Relationship { ID = "d", RelatedID = "b" }, // dup ---/
new Relationship { ID = "b", RelatedID = "c" }, //
};
var count = objs.Distinct(new IdEqualityComparer()).Count();
System.Console.WriteLine(count);
}
gives 5, not 7 (which we would expect if it always returned false). Tested with:
public class Relationship
{
public string ID { get; set; }
public string RelatedID { get; set; }
}
To illustrate this more clearly:
var a = new Relationship { Id = "x", RelatedID = "foo" };
var b = new Relationship { Id = "y", RelatedID = "foo" };
var c = new Relationship { Id = "x", RelatedID = "foo" };
we can now demonstrate that the comparer returns true and false appropriately:
var comparer = new IdEqualityComparer();
Console.WriteLine(comparer.Equals(a, b)); // False
Console.WriteLine(comparer.Equals(a, c)); // True
we can also show that the hash-code is working appropriately:
Console.WriteLine(comparer.GetHashCode(a));
Console.WriteLine(comparer.GetHashCode(b));
Console.WriteLine(comparer.GetHashCode(c));
note that the numbers will change, but for me on this run this gives:
-789327704
1132350395
-789327704
The numbers don't matter - what matters is that the first and last are equal, and (ideally) different from the middle one.
So: the comparer is working fine, and the premise of the question is incorrect. You need to identify in your code what is different, and fix it.

c#: Move element whose ID is in array to top of list

In C#,I have List of Employee object. Employee class is
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
In List objected are sorted based on Employee.ID. I have an array of int which is basically Employee.ID which I want on top of the list and in list,order must remain same as in array.
If I hava input like this
List:
[
{ID:1,Name:A},
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
and Array: {2,3,1}
Then I want Output List:
[
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:1,Name:A},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
And I have done this
foreach (int i in a)
{
list = list.OrderBy(x => x.ID != i).ToList();
}
//a is array
//list is List
Any better Solution.Thanks in advance.
After you got your list sorted based on the ID just iterate the array and move the elements. In order to do this you need to first remove and then insert the item at the correct position.
for(int i = 0; i < myArray.Length; i++)
{
var e = myList.Single(x => x.Id == myArray[i]);
myList.Remove(e);
myList.Insert(i, e);
}
You may also want to use SingleOrDefault instead of Single to verify that myList even contains the element with the current id, e.g. when your array contains [2, 3, 101]
To add another version to the mix. The complete sorting can be done in one go:
list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? int.MaxValue : i; }).ToList();
where list is the EmployeeList and a the indices array. (NB, the for loop is not needed, the above should do both sortings).
Inside the OrderBy callback, if the id is not inside a, int.MaxValue is returned to place it after the ones inside the array (a.Length would work as well). OrderBy should maintain the original order of the enumeration (list) for those elements that return the same value.
PS, if you want to sort first by index inside a and the rest on the ids (not necessarily the original order), you can use the following (as long as a.Length + largest ID < int.MaxValue) : list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? a.Length + e.ID : i; }).ToList();
Here's a way to do it in pure LINQ, without changing the original sequence.
Broken into steps to see what's going on.
public static void Main()
{
var employeeList = new List<Employee>()
{
new Employee(){ ID= 1,Name= "A"},
new Employee() { ID= 2,Name= "B"},
new Employee() { ID= 3,Name= "AA"},
new Employee() { ID= 4,Name= "C"},
new Employee() { ID= 5,Name= "CD"},
new Employee() { ID= 100,Name= "Z"}
};
var orderByArray = new int[] { 2, 3, 1, 100, 5, 4 };
var sortPos = orderByArray.Select((i, index) => new { ID = i, SortPos = index });
var joinedList = employeeList.Join(sortPos, e => e.ID, sp => sp.ID, (e, sp) => new { ID = e.ID, Name = e.Name, SortPos = sp.SortPos });
var sortedEmployees = joinedList.OrderBy(e => e.SortPos).Select(e => new Employee { ID = e.ID, Name = e.Name });
}
Try this using LINQ:
List<Employee> employees = ...
int[] ids = ...
var orderEmployees = ids.Select(id => employees.Single(employee => employee.ID == id))
.Concat(employees.Where(employee => !ids.Contains(employee.ID)).ToList();
Foreach id in ids array we will grab the matching employee and we will concat to it all the employees that their id does not exist in ids array.
I like to use a special Comparer for that, it seems clearer to me, though a bit more code. It hides the complexity of the sort in the comparer class, and then you can just call it with :
theList.OrderBy(x => x.id, new ListOrderBasedComparer(sortList));
It will sort according to any list passed to the comparer when instantiating, and will put elements not in the "known sort list" at the end.
You can of course adapt it to your special needs.
public class ListOrderBasedComparer: Comparer<int>
{
private List<int> sortList;
public ListOrderBasedComparer(List<int> sortList)
{
// if you want you can make constructor accept arrays and convert it
// (if you find that more convenient)
this.sortList = sortList;
}
public override int Compare(int x, int y)
{
var indexOfX = sortList.FindIndex(a => a == x);
var indexOfY = sortList.FindIndex(a => a == y);
// handle elements not in sortArray : if not in sort array always assume they should be "less than the others" and "equal between them".
if (indexOfX == -1 && indexOfY == -1) return 0;
if (indexOfY == -1) return -1;
if (indexOfX == -1) return 1;
// if elements are in sortArray (FindIndex returned other than -1), use usual comparison of index values
return indexOfX.CompareTo(indexOfY);
}
}
Example on how to use it, with Linq :
public class TestCompare
{
public void test ()
{
var myArray = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var myArray2 = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 0, name = "X" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 23, name = "Z"},
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var sortList = new List<int> { 2, 3, 1, 4, 5, 6 };
// good order
var mySortedArray = myArray.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
// good order with elem id 0 and 23 at the end
var mySortedArray2 = myArray2.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
}
}
public class MyClass
{
public int id;
public string name;
}

Categories

Resources