Linq Union does not work - c#

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.

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 latest record and group with highest date - LINQ

I have table Billing as below
AccoundID | Group | DateOfBill
1234 | A | 2017-07-12
1234 | B | 2017-07-16
1234 | C | 2017-07-31
1235 | A | 2017-07-31
1236 | B | 2017-07-31
As you see, AccountID 1234 have made 3 transaction on July 2017. So I need a list where the AccountID 1234 must be in Group C because that's is latest date on that transaction.
Here is my code snippet
var LatestAccount = from n in Billing
where (n.Group == "A" || n.Group == "B" || n.Group == "C")
group n by new { n.AccountID, n.Group } into g
select new {
AccountId = g.Key.AccountID,
Group = g.Key.Group ,
DateOfBill = g.Max(t => t.DateOfBill)
};
But the result is wrong. How to do in LINQ?
class Program
{
static void Main(string[] args)
{
List<Billing> Billings = new List<Billing>()
{
new Billing()
{
AccountID = 1234, DateOfBill = new DateTime(2017,07,12), Group = "A"
},
new Billing()
{
AccountID = 1234, DateOfBill = new DateTime(2017,07,16), Group = "B"
},
new Billing()
{
AccountID = 1234, DateOfBill = new DateTime(2017,07,31), Group = "C"
},
new Billing()
{
AccountID = 1235, DateOfBill = new DateTime(2017,07,31), Group = "A"
},
new Billing()
{
AccountID = 1236, DateOfBill = new DateTime(2017,07,31), Group = "B"
}
};
var LatestAccount = from n in Billings
where (n.Group == "A" || n.Group == "B" || n.Group == "C")
group n by new { n.AccountID } into g
select g.Where(d => d.DateOfBill == g.Max(m => m.DateOfBill)).Select(x => new { AccountId = g.Key.AccountID, Group = x.Group, DateOfBill = x.DateOfBill }).FirstOrDefault();
foreach (var item in LatestAccount)
{
Console.WriteLine("AccountID: " + item.AccountId + " Date of Bill: " + item.DateOfBill + " Group: "+ item.Group);
}
Console.ReadLine();
}
}
class Billing
{
public int AccountID { get; set; }
public string Group { get; set; }
public DateTime DateOfBill { get; set; }
}
Is below what you want?
If you show me the output you want, I can modify my answer.

Reading in list using SqlDataReader C#

I have to fill in some lists in while loop as:
while (_myReader_1.Read())
{
_Row_Counter++;
int _authorID = _myReader_1.GetInt32(0);
Author _author = _eAthors.FirstOrDefault(_a => _a._AuthorID == _authorID);
if (_author == null)
{
_author = new Author
{
_AuthorID = _authorID,
_AuthorName = _myReader_1.GetString(1),
_Attributes = new List<AuthorAttributes>()
};
}
var _attribute = new AuthorAttributes()
{
_PaperID = new List<int>(),
_CoAuthorID = new List<int>(),
_VenueID = new List<int>()
};
_attribute._PaperID.Add(_myReader_1.GetInt32(2));
_attribute._CoAuthorID.Add(_myReader_1.GetInt32(3));
_attribute._VenueID.Add(_myReader_1.GetInt32(4));
_attribute._Year = _myReader_1.GetInt32(5);
_author._Attributes.Add(_attribute);
_eAthors.Add(_author);
}
_myReader_1.Close();
The data in SQL table looks like:
Author_ID | Author_Name | Paper_ID | CoAuthor_ID | Venue_ID | Year
------------------------------------------------------------------
677 | Nuno Vas | 812229 | 901706 | 64309 | 2005
677 | Nuno Vas | 812486 | 901706 | 65182 | 2005
677 | Nuno Vas | 818273 | 901706 | 185787 | 2005
677 | Nuno Vas | 975105 | 901706 | 113930 | 2007
677 | Nuno Vas | 975105 | 1695352 | 113930 | 2007
... | ... | ... | ... | ... | ...
The issue is each time loop iterates, new lists _PaperID, _CoAuthorID and _VenueID are created, which is not desired. As we have a check if(author == null), then to create a new author, similarly I want to check if a list for _PaperID exists for an author e.g. for Author_ID = 677, then to Add in same list until Author_ID get changed.
Also until the Author_ID = 677, the list _eAuthors should have Count = 1
I'm attaching some images to refine the problem.
Image 1: Showing eAuthors Count = 3, Attributes Count = 3 for AuthorID = 677, while 3 of iterations passed whereas eAuthors Count should = 1.
Image 2: Showing Individual Attribute lists for each row, as in 3rd iteration the Attribute e.g. CoAuthorID, the Count = 1, whereas it should be = 3 while in 3rd iteration and same for rest of the Attributes
Following the data structure shown and seeing what depicted in images, it seems that all attributes (Paper, CoAuthor, Venue) are of type lists, so there is no need to declare attributes as List<AuthorAttributes>. Follow this to what you want to achieve:
while (_myReader_1.Read())
{
_Row_Counter++;
int _authorID = _myReader_1.GetInt32(0);
Author _author = _eAthors.FirstOrDefault(_a => _a._AuthorID == _authorID);
if (_author == null)
{
_author = new Author
{
_AuthorID = _authorID,
_AuthorName = _myReader_1.GetString(1),
_Attributes = new AuthorAttributes()
};
}
// Check if list _PaperID doesn't exist
if (_author._Attributes._PaperID == null)
{
// Create new _PaperID
_author._Attributes._PaperID = new List<int>();
// Add Paper_ID to _PaperID
_author._Attributes._PaperID.Add(_myReader_1.GetInt32(2));
}
else // Add Paper_ID to existing _PaperID list
_author._Attributes._PaperID.Add(_myReader_1.GetInt32(2));
// Check if list _CoAuthorID doesn't exist
if (_author._Attributes._CoAuthorID == null)
{
// Create new _CoAuthorID
_author._Attributes._CoAuthorID = new List<int>();
// Add CoAuthor_ID to _CoAuthorID
_author._Attributes._CoAuthorID.Add(_myReader_1.GetInt32(3));
}
else // Add CoAuthor_ID to existing _CoAuthorID list
_author._Attributes._CoAuthorID.Add(_myReader_1.GetInt32(3));
// Check if list _CoAuthorID doesn't exist
if (_author._Attributes._VenueID == null)
{
// Create new _VenueID
_author._Attributes._VenueID = new List<int>();
// Add Venue_ID to _VenueID
_author._Attributes._VenueID.Add(_myReader_1.GetInt32(4));
}
else // Add Venue_ID to existing _VenueID list
_author._Attributes._VenueID.Add(_myReader_1.GetInt32(4));
// Add Year to _Year
_author._Attributes._Year =_myReader_1.GetInt32(5);
if (!_eAthors.Contains(_author))
_eAthors.Add(_author);
}
_myReader_1.Close();
Assuming your data structure looks like this:
Author
AuthorAttributes
Papers (list)
PaperID
CoAuthors (list)
CoAuthorID
Venues (list)
VenueID
Year
You could try this:
while (_myReader_1.Read())
{
_Row_Counter++;
int _authorID = _myReader_1.GetInt32(0);
string _authorName = _myReader_1.GetString(1);
int _paperID = _myReader_1.GetInt32(2);
int _coAuthorID = _myReader_1.GetInt32(3);
int _venueID = _myReader_1.GetInt32(4);
int _year = _myReader_1.GetInt32(5);
Author _author = _eAthors.FirstOrDefault(_a => _a._AuthorID == _authorID);
if (_author == null)
{
_author = new Author
{
_AuthorID = _authorID,
_AuthorName = _authorName,
_AuthorAttributes = new AuthorAttributes
{
_Papers = new List<int>(),
_Venues = new List<int>(),
_Year = _year,
_CoAuthors = new List<int>()
}
};
_eAthors.Add(_author); // only add if author not found
}
if ( !_author._AuthorAttributes._Papers.Contains( _paperID ) )
_author._AuthorAttributes._Papers.Add( _paperID );
if ( !_author._AuthorAttributes._CoAuthors.Contains( _coAuthorID ) )
_author._AuthorAttributes._CoAuthors.Add( _coAuthorID );
if ( !_author._AuthorAttributes._Venues.Contains( _venueID ) )
_author._AuthorAttributes._Venues.Add( _venueID );
}
_myReader_1.Close();
Add the newly created author right after initialization in the author == null check. Then check if author.PaperID == null and if it is, add the AuthorAttributes. Like this:
while (_myReader_1.Read())
{
_Row_Counter++;
int _authorID = _myReader_1.GetInt32(0);
Author _author = _eAthors.FirstOrDefault(_a => _a._AuthorID == _authorID);
if (_author == null)
{
_author = new Author
{
_AuthorID = _authorID,
_AuthorName = _myReader_1.GetString(1),
_Attributes = new List<AuthorAttributes>()
};
_eAthors.Add(_author); // ********** Add the new author
}
// Watch out!!! author.Attributes may be null for existing authors!!!
if (author.Attributes.PaperID == null || author.PaperID.Count == 0) // Check for PaperID existence
{
var _attribute = new AuthorAttributes()
{
_PaperID = new List<int>(),
_CoAuthorID = new List<int>(),
_VenueID = new List<int>()
};
_attribute._PaperID.Add(_myReader_1.GetInt32(2));
_attribute._CoAuthorID.Add(_myReader_1.GetInt32(3));
_attribute._VenueID.Add(_myReader_1.GetInt32(4));
_attribute._Year = _myReader_1.GetInt32(5);
_author._Attributes.Add(_attribute);
}
}
_myReader_1.Close();
Of course, if necessary you can process each attribute separately, by adding an if block for each one.

Merging collections using LINQ while overriding rows with same ID

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);

Categories

Resources