Merging collections using LINQ while overriding rows with same ID - c#

I got two collections of objects.
For example:
List<Foo> firstFoos = new List<Foo>();
List<Foo> secondFoos = new List<Foo>();
firstFoos.Add(new Foo() { Id = 1, ValueA = 10, ValueB = 15 });
firstFoos.Add(new Foo() { Id = 2, ValueA = 20, ValueB = 25 });
firstFoos.Add(new Foo() { Id = 3, ValueA = 30, ValueB = 35 });
firstFoos.Add(new Foo() { Id = 4, ValueA = 40, ValueB = 45 });
secondFoos.Add(new Foo() { Id = 1, ValueA = 100, ValueB = 150 });
secondFoos.Add(new Foo() { Id = 2, ValueA = 200, ValueB = 250 });
Using LINQ, how can I merge the two collection overriding firstFoos by secondFoos which have the same ID?
Expected result is:
| Id | ValueA | ValueB |
|---------|--------|--------|
| 1 | 100 | 150 |
| 2 | 200 | 250 |
| 3 | 30 | 35 |
| 4 | 40 | 45 |
Please note that this example case has only two value columns (ValueA and ValueB), but an actual case could have many more.

I'd convert it to an Id -> Foo dictionary, and then just update with a regular foreach:
var fooDict = firstFoos.ToDictionary(foo => foo.Id, foo => foo);
foreach (var foo in secondFoos)
fooDict[foo.Id] = foo;
var newFoos = fooDict.Values.OrderBy(foo => foo.Id).ToList();

You can define a custom equality comparer and use Union():
public class FooComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
return x.Id == y.Id;
}
public int GetHashCode(Foo obj)
{
return obj.Id.GetHashCode();
}
}
And then:
var mergedList = secondFoos.Union(firstFoos, new FooComparer())
.ToList();
This uses the fact that items in secondFoos are added to the resulting enumeration before any item in firstFoo, any item in firstFoo with an already existing Id will hence be filtered out. This assumes of course that Id should be distinct across all items.

This should work for you
var concat = firstFoos.Select(x => new { Foo = x, list=1 })
.Concat(secondFoos.Select(x => new { Foo = x, list= 2 });
var merge = from x in concat
group x by x.Foo.Id into x
select x.Count() == 1 ? x.First().Foo : x.First(y => y.list == 2).Foo;

var result = secondFoos.Concat(
firstFoos.Except(secondFoos,
new LambdaComparer<Foo>((a, b) => a.Id == b.Id)))
.ToList();
Another option, because you can never have too many solutions to the same problem ;)

Another option
var f1NotInF2 = from f1 in firstFoos
where !secondFoos.Exists(f2 => f1.Id == f2.Id)
select f1;
var mixed = f1NotInF2.Concat(secondFoos);

I would use something like this:
List<Foo> newFoos = new List<Foo>();
Foo selected = null;
foreach (Foo foo in firstFoos)
{
selected = secondFoos.FirstOrDefault(x => x.Id == foo.Id);
if (selected != null)
{
newFoos.Add(selected);
}
else
{
newFoos.Add(foo);
}
}

This will work:
var merged = firstFoos.Where(f => !secondFoos.Any(s => s.Id == f.Id))
.Union(secondFoos).OrderBy(c=>c.Id);

Related

Linq to sql query check for equality of tuples

How write linq to sql query to retrieve records from table below that (B ,C) be in list c.
Table is in database.
var c = new List<(int,int)>{(1,4), (3,6)};
+---+---+---+
| A | B | C |
+---+---+---+
| a | 1 | 4 |
| b | 2 | 5 |
| c | 3 | 6 |
+---+---+---+
Query should return a and c.
If you are talking about tuples being "fully"(all elements are sequentially equal) equal then you can just use contains:
var c = new List<(int,int)>{(1,3), (3,6)};
var table = new List<(int,int)>{(1,3), (2,5), (3,6)};
var res = table
.Where(i => c.Contains(i))
.ToList();
Equality and tuples
If #Caius Jard's assumption is right then just change .Where(i => c.Contains(i)) to .Where(i => !c.Contains(i))
I am not sure how your logic is but i think you are trying to do something like this,
Let's assume your table model looks like below,
public class MyTable {
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public MyTable(string a, string b, string c) {
A = a;
B = b;
C = c;
}
}
And let's fill the data you have shared and query,
var c = new List<(int, int)> { (1, 4), (3, 6) };
List<MyTable> myTables = new List<MyTable>();
myTables.Add(new MyTable("a", "1", "4"));
myTables.Add(new MyTable("b", "2", "5"));
myTables.Add(new MyTable("c", "3", "6"));
var res = myTables.Where(x => c.Any(y => y.Item1.ToString() == x.B && y.Item2.ToString() == x.C)).Select(x => x.A);
Console.WriteLine(string.Join(" ", res));
Console.ReadKey();
This will print:
a c

Replace values of List<Class>

I have two List<class>, List1 and List2 which contains multiple columns: RowNo, Value1, Value2, etc. as follows
List1
| RowNo | Value |
|-------|-------|
| 1 | 11 |
| 2 | 22 |
| 3 | 33 |
| 4 | 88 |
List2
| RowNo | Value |
|-------|-------|
| 1 | 44 |
| 2 | 55 |
| 3 | 66 |
I want to replace the value of element of List1 with the value of element of List2 if the RowNo matches.The output I want to generate is as follows
Desired result
| RowNo | Value |
|-------|-------|
| 1 | 44 |
| 2 | 55 |
| 3 | 66 |
| 4 | 88 |
Any Ideas or suggestions? How can I achieve this? What can be the best and efficient way to do this?
You can just use a loop to compare the values in List1 with List2, and if a match is found, update the Value
foreach (var item in List1)
{
var match = List2.FirstOrDefault(x => x.RowNo == item.RowNo);
if (match != null)
{
item.Value = match.Value;
}
}
Using Linq
List1.ForEach(l1 => l1.Value = (List2.FirstOrDefault(l2 => l2.RowNo == l1.RowNo) ?? l1).Value);
The Value property of l1 list element will be set to itself if no element will be found on the List2 list.
Full code
class MyClass
{
public int RowNo { get; set; }
public int Value { get; set; }
}
var List1 = new List<MyClass>()
{
new MyClass(){RowNo = 1, Value = 11},
new MyClass(){RowNo = 2, Value = 22},
new MyClass(){RowNo = 3, Value = 33},
new MyClass(){RowNo = 4, Value = 88},
};
var List2 = new List<MyClass>()
{
new MyClass(){RowNo = 1, Value = 44},
new MyClass(){RowNo = 2, Value = 55},
new MyClass(){RowNo = 3, Value = 66}
};
List1.ForEach(l1 => l1.Value = (List2.FirstOrDefault(l2 => l2.RowNo == l1.RowNo) ?? l1).Value);
List1.ForEach(x =>
{
var item = List2.FirstOrDefault(y => y.RowNo == x.RowNo);
if (item != null)
{
x.Value = item.Value;
}
});
Put all data of list1 into a Dictionary (key is the RowNo).
Loop over list2 to update the Dictionary.
Convert the data of the Dictionary to a List.
It approaches an O(n) operation.
Use this extension method to achieve what you want:
public static class LinqExtentions
{
public static void Project<T>(this IEnumerable<T> lst1, IEnumerable<T> lst2,
Func<T, object> key, Action<T, T> action)
{
foreach (var item1 in lst1)
{
var item2 = lst2.FirstOrDefault(x => key(x).Equals(key(item1)));
if (item2 != null)
{
action(item1, item2);
}
}
}
}
then you can use it like this:
List1.Project(List2, x => x.RowNo, (y, z) => { y.Value = z.Value; });
What it does
is projecting one list over the other, then matching the key values in both (RowNo in your example), when two items have the same key then the action supplied in the third parameter is applied, in this example, you want elements in the first list to have the same Value as elements in the second list, that's exactly what this delegate does:
(y, z) => { y.Value = z.Value; }
you can use this extension method to achieve the same requirement for any pair of lists:
Call Project on the list you want to change.
Pass the the list of the values you want to assign to the first list, as the first parameter.
Pass the key property as the second parameter.
The third
parameter is the action you want to apply on your list.
You can loop over List1 and check if List2 contains a match then fill the result in a new list
List<YourClass> result = new List<YourClass>();
for (int i = 0; i < List1.Count; i++)
{
YourClass resRowValue = List1[i];
if (List2.Count > i && List2[i].RowValue.equals(resStrRowValue.RowValue)
resStr.RowValue = List2[i].RowValue;
result.Add(resRowValue);
}
//set the result to List1
List1 = result;
You can do this also using linq
List1 = List1.Select(x => {
int i = List1.IndexOf(x);
YourClass newValue = List2.FirstOrDefault(y => y.RowValue.Equals(x.RowValue));
if (newValue != null)
x.RowValue = newValue.RowValue;
return x;
}).ToList();

Get the last record of group using LINQ

I have a list:
var list = new List<Foo>() {
new Foo() { FooId = 1, GroupId = 1, ValueA = 3},
new Foo() { FooId = 2,GroupId = 1, ValueA = 40},
new Foo() { FooId = 3,GroupId = 2, ValueA = 80},
new Foo() { FooId = 4, GroupId = 2, ValueA = 20},
};
I want to just get the latest record so the result would be like:
| GroupId | ValueA |
|---------|--------|
| 1 | 40 |
| 2 | 20 |
Depending on what you want you can change OrderBy or even can use First or Last in this.
list
.OrderByDescending(a => a.ValueA)
.GroupBy(a => a.GroupId)
.Select(g => g.Last());
var result =
( from p in List
group p by p.Value into g
select new {
GroupId = g.OrderByDescending(x=>x.Id).FirstOrDefault().GroupId,
ValueA = g.OrderByDescending(x => x.Id).FirstOrDefault().ValueA
}
).ToList();
It works for me...
So you can try.
Is this what you want to do?
var result1 = from p in list
group p by p.GroupId into g
select new { GroupId = g.Key, MaxFooId = g.FooId.Max() };
var result2 = from p in result1
join list q on p.MaxFooId equals q.FooId
select new { GroupId = p.GroupId, ValueA = q.ValueA }

Linq Union does not work

I have a two lists rca and purchase as follow.
List<GroupDate> rca = (from sold in GetSoldOut
group sold by new { sold.CreatedDate, sold.SubCategoryID }
into g
select new GroupDate
{
Date = g.Key.CreatedDate,
SubCategoryID = g.Key.SubCategoryID,
Count = g.Count()
}).ToList();
and
List<GroupDate> purchase = (from sold in stock
group sold by new { sold.CreatedDate, sold.SubCategoryID }
into g
select new GroupDate
{
Date = g.Key.CreatedDate,
SubCategoryID = g.Key.SubCategoryID,
Count = g.Sum(a => a.Quantity)
}).ToList();
And Join this two lists as follow.
var leftOuterJoinRP = from first in replace
join last in prepaid
on new { first.Date, first.SubCategoryID } equals new { last.Date, last.SubCategoryID }
into temp
from last in temp.DefaultIfEmpty(new GroupDate { })
select new CardBalance
{
Date = first.Date,
SubCategoryID = first.SubCategoryID,
ReDemage = first.Count,
Prepaid = last.Count
};
var rightOuterJoinRP = from last in prepaid
join first in replace
on new { last.Date, last.SubCategoryID } equals new { first.Date, first.SubCategoryID }
into temp
from first in temp.DefaultIfEmpty(new GroupDate { })
select new CardBalance
{
Date = last.Date,
SubCategoryID = last.SubCategoryID,
ReDemage = first.Count,
Prepaid = last.Count
};
leftOuterJoinRP contains
Date---| Balance | OpeningStock | Prepaid | Purchase | RCA | Demage | SubCategoryId
1/1/17 | 0-------| 0----------- | 1------ | 600 -----| 2-- | 0 ---- | 84
and
rightOuterJoinRP contains
Date---| Balance | OpeningStock | Prepaid | Purchase | RCA | Demage | SubCategoryId
1/1/17 | 0-------| 0----------- | 1------ | 600-----| 2-- | 0 ---- | 84
1/2/17 | 0-------| 0----------- | 1------ | 110-----| 1-- | 0 ---- | 84
Union leftOuterJoinRP and rightOuterJoinRP as follow.
var fullOuterJoinRP = leftOuterJoinRP.Union(rightOuterJoinRP);
But it does not union. fullOuterJoinRP get all rows.
You need to use the Union method which takes an IEqualityComparer<T> parameter.
Let's say you have a TestClass
public class TestClass
{
public int TestInteger { get; set; }
public string TestString { get; set; }
}
And create two lists
List<TestClass> list1 = new List<TestClass>();
list1.Add(new TestClass() { TestInteger = 1, TestString = "t1" });
list1.Add(new TestClass() { TestInteger = 2, TestString = "t2" });
List<TestClass> list2 = new List<TestClass>();
list2.Add(new TestClass() { TestInteger = 1, TestString = "t1" });
list2.Add(new TestClass() { TestInteger = 3, TestString = "t3" });
IEnumerable<TestClass> list3 = list1.Union(list2);
Here, the Union method will return all four objects, like in your question.
The Union method needs an IEqualityComparer<TestClass> parameter to compare the objects.
public class TestClassComparer : IEqualityComparer<TestClass>
{
public bool Equals(TestClass x, TestClass y)
{
//Check whether the objects are the same object.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether the class properties are equal.
return x != null && y != null && x.TestInteger.Equals(y.TestInteger) && x.TestString.Equals(y.TestString);
}
public int GetHashCode(TestClass obj)
{
//Get hash code for the TestString field if it is not null.
int hashTestString = obj.TestString == null ? 0 : obj.TestString.GetHashCode();
//Get hash code for the TestInteger field.
int hashTestInteger = obj.TestInteger.GetHashCode();
//Calculate the hash code for the TestClass object.
return hashTestString ^ hashTestInteger;
}
}
Now, if you call
IEnumerable<TestClass> list3 = list1.Union(list2, new TestClassComparer());
The resulting list3 will have three unique objects.

Pivot data using LINQ

I have a collection of items that contain an Enum (TypeCode) and a User object, and I need to flatten it out to show in a grid. It's hard to explain, so let me show a quick example.
Collection has items like so:
TypeCode | User
---------------
1 | Don Smith
1 | Mike Jones
1 | James Ray
2 | Tom Rizzo
2 | Alex Homes
3 | Andy Bates
I need the output to be:
1 | 2 | 3
Don Smith | Tom Rizzo | Andy Bates
Mike Jones | Alex Homes |
James Ray | |
I've tried doing this using foreach, but I can't do it that way because I'd be inserting new items to the collection in the foreach, causing an error.
Can this be done in Linq in a cleaner fashion?
I'm not saying it is a great way to pivot - but it is a pivot...
// sample data
var data = new[] {
new { Foo = 1, Bar = "Don Smith"},
new { Foo = 1, Bar = "Mike Jones"},
new { Foo = 1, Bar = "James Ray"},
new { Foo = 2, Bar = "Tom Rizzo"},
new { Foo = 2, Bar = "Alex Homes"},
new { Foo = 3, Bar = "Andy Bates"},
};
// group into columns, and select the rows per column
var grps = from d in data
group d by d.Foo
into grp
select new {
Foo = grp.Key,
Bars = grp.Select(d2 => d2.Bar).ToArray()
};
// find the total number of (data) rows
int rows = grps.Max(grp => grp.Bars.Length);
// output columns
foreach (var grp in grps) {
Console.Write(grp.Foo + "\t");
}
Console.WriteLine();
// output data
for (int i = 0; i < rows; i++) {
foreach (var grp in grps) {
Console.Write((i < grp.Bars.Length ? grp.Bars[i] : null) + "\t");
}
Console.WriteLine();
}
Marc's answer gives sparse matrix that can't be pumped into Grid directly.
I tried to expand the code from the link provided by Vasu as below:
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
return source.GroupBy(key1Selector).Select(
x => new
{
X = x.Key,
Y = source.GroupBy(key2Selector).Select(
z => new
{
Z = z.Key,
V = aggregate(from item in source
where key1Selector(item).Equals(x.Key)
&& key2Selector(item).Equals(z.Key)
select item
)
}
).ToDictionary(e => e.Z, o => o.V)
}
).ToDictionary(e => e.X, o => o.Y);
}
internal class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public string Function { get; set; }
public decimal Salary { get; set; }
}
public void TestLinqExtenions()
{
var l = new List<Employee>() {
new Employee() { Name = "Fons", Department = "R&D", Function = "Trainer", Salary = 2000 },
new Employee() { Name = "Jim", Department = "R&D", Function = "Trainer", Salary = 3000 },
new Employee() { Name = "Ellen", Department = "Dev", Function = "Developer", Salary = 4000 },
new Employee() { Name = "Mike", Department = "Dev", Function = "Consultant", Salary = 5000 },
new Employee() { Name = "Jack", Department = "R&D", Function = "Developer", Salary = 6000 },
new Employee() { Name = "Demy", Department = "Dev", Function = "Consultant", Salary = 2000 }};
var result5 = l.Pivot3(emp => emp.Department, emp2 => emp2.Function, lst => lst.Sum(emp => emp.Salary));
var result6 = l.Pivot3(emp => emp.Function, emp2 => emp2.Department, lst => lst.Count());
}
* can't say anything about the performance though.
You can use Linq's .ToLookup to group in the manner you are looking for.
var lookup = data.ToLookup(d => d.TypeCode, d => d.User);
Then it's a matter of putting it into a form that your consumer can make sense of. For instance:
//Warning: untested code
var enumerators = lookup.Select(g => g.GetEnumerator()).ToList();
int columns = enumerators.Count;
while(columns > 0)
{
for(int i = 0; i < enumerators.Count; ++i)
{
var enumerator = enumerators[i];
if(enumator == null) continue;
if(!enumerator.MoveNext())
{
--columns;
enumerators[i] = null;
}
}
yield return enumerators.Select(e => (e != null) ? e.Current : null);
}
Put that in an IEnumerable<> method and it will (probably) return a collection (rows) of collections (column) of User where a null is put in a column that has no data.
I guess this is similar to Marc's answer, but I'll post it since I spent some time working on it. The results are separated by " | " as in your example. It also uses the IGrouping<int, string> type returned from the LINQ query when using a group by instead of constructing a new anonymous type. This is tested, working code.
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 1, UserName = "Mike Jones"},
new { TypeCode = 1, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
var Columns = from i in Items
group i.UserName by i.TypeCode;
Dictionary<int, List<string>> Rows = new Dictionary<int, List<string>>();
int RowCount = Columns.Max(g => g.Count());
for (int i = 0; i <= RowCount; i++) // Row 0 is the header row.
{
Rows.Add(i, new List<string>());
}
int RowIndex;
foreach (IGrouping<int, string> c in Columns)
{
Rows[0].Add(c.Key.ToString());
RowIndex = 1;
foreach (string user in c)
{
Rows[RowIndex].Add(user);
RowIndex++;
}
for (int r = RowIndex; r <= Columns.Count(); r++)
{
Rows[r].Add(string.Empty);
}
}
foreach (List<string> row in Rows.Values)
{
Console.WriteLine(row.Aggregate((current, next) => current + " | " + next));
}
Console.ReadLine();
I also tested it with this input:
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 3, UserName = "Mike Jones"},
new { TypeCode = 3, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
Which produced the following results showing that the first column doesn't need to contain the longest list. You could use OrderBy to get the columns ordered by TypeCode if needed.
1 | 3 | 2
Don Smith | Mike Jones | Tom Rizzo
| James Ray | Alex Homes
| Andy Bates |
#Sanjaya.Tio I was intrigued by your answer and created this adaptation which minimizes keySelector execution. (untested)
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
var lookup = source.ToLookup(x => new {Key1 = key1Selector(x), Key2 = key2Selector(x)});
List<TKey1> key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList();
List<TKey2> key2s = lookup.Select(g => g.Key.Key2).Distinct().ToList();
var resultQuery =
from key1 in key1s
from key2 in key2s
let lookupKey = new {Key1 = key1, Key2 = key2}
let g = lookup[lookupKey]
let resultValue = g.Any() ? aggregate(g) : default(TValue)
select new {Key1 = key1, Key2 = key2, ResultValue = resultValue};
Dictionary<TKey1, Dictionary<TKey2, TValue>> result = new Dictionary<TKey1, Dictionary<TKey2, TValue>>();
foreach(var resultItem in resultQuery)
{
TKey1 key1 = resultItem.Key1;
TKey2 key2 = resultItem.Key2;
TValue resultValue = resultItem.ResultValue;
if (!result.ContainsKey(key1))
{
result[key1] = new Dictionary<TKey2, TValue>();
}
var subDictionary = result[key1];
subDictionary[key2] = resultValue;
}
return result;
}

Categories

Resources