How to merge two different List - c#

I have a List<string> list1, example values:
var list1 = new List<string>()
{
"123", "1234", "12345",
};
I have a class:
public class TestClass {
public string name{ get; set; }
public int count{ get; set; }
}
and I have a List<TestClass> list2, example values:
var list2 = new List<TestClass>()
{
new TestClass() { name = "12", count = 0 },
new TestClass() { name = "123", count = 5 },
new TestClass() { name = "1234", count = 20 },
};
I want to merge list1 and list2 and the result should be:
name count
12 0
123 5
1234 20
12345 0

This works nicely:
var list1 = new List<string>()
{
"123", "1234", "12345",
};
var list2 = new List<TestClass>()
{
new TestClass() { name = "12", count = 0 },
new TestClass() { name = "123", count = 5 },
new TestClass() { name = "1234", count = 20 },
};
var merged =
list2
.Concat(list1.Select(x => new TestClass() { name = x, count = 0 }))
.GroupBy(x => x.name)
.SelectMany(x => x.Take(1))
.ToList();
It gives me:

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<TestClass> lst1 = new List<TestClass>();
lst1.Add(new TestClass(){name="One", count = 1});
lst1.Add(new TestClass(){name="Two", count = 2});
lst1.Add(new TestClass(){name="Three", count = 3});
List<TestClass> lst2 = new List<TestClass>();
lst2.Add(new TestClass(){name="Four", count = 4});
lst2.Add(new TestClass(){name="Two", count = 2});
lst2.Add(new TestClass(){name="Three", count = 3});
var unionlst = lst1.Union(lst2, new TestClassComparer ());
foreach(var x in unionlst){
Console.WriteLine(x.name + ","+x.count);
}
}
class TestClassComparer : IEqualityComparer<TestClass>
{
public bool Equals(TestClass p1, TestClass p2)
{
return p1.name == p2.name && p1.count == p2.count;
}
public int GetHashCode(TestClass p)
{
return p.count;
}
}
public class TestClass {
public string name{ get; set; }
public int count{ get; set; }
}
}
Sample output:
One,1
Two,2
Three,3
Four,4

Related

How to refactor linq predicate into Func variable

I have this code:
if (model.Id > 0)
{
return _buyerRepository.Exists(x => (model.Id != x.Id) &&
(x.IdentificationNumber == model.IdentificationNumber));
}
return _buyerRepository.Exists(x =>
x.IdentificationNumber == model.IdentificationNumber);
How to extract (model.Id != x.Id) and to put in the linq query based on if model.Id>0 or it's a 0 ?
I am thinking on ExpressionTrees and Func<T,bool>, but Im not sure how to put a condition.
I would move from Exists to Any and do the following:
IEnumerable<T> query = (IEnumerable<T>)_buyerRepository;
if (model.Id > 0)
query = query.Where(x => model.Id != x.Id);
return query.Any(x => x.IdentificationNumber == model.IdentificationNumber);
See an example here:
https://dotnetfiddle.net/EkoDym
public class Program
{
public static void Main()
{
var foos = new List<Foo>()
{
new Foo() { Id = 1, IdentificationNumber = "1" },
new Foo() { Id = 2, IdentificationNumber = "2" },
};
Console.WriteLine(HasAny(foos, 1, "1"));
Console.WriteLine(HasAny(foos, 0, "1"));
var foosv2 = new List<Foo>()
{
new Foo() { Id = 2, IdentificationNumber = "1" },
};
Console.WriteLine(HasAny(foosv2, 1, "1"));
Console.WriteLine(HasAny(foosv2, 2, "1"));
}
static bool HasAny(List<Foo> foos, int id, string identificationNumber)
{
IEnumerable<Foo> query = (IEnumerable<Foo>)foos;
if (id > 0)
query = query.Where(x => id != x.Id);
return query.Any(x => x.IdentificationNumber == identificationNumber);
}
}
public class Foo
{
public int Id { get; set; }
public string IdentificationNumber { get; set; }
}
In case you may believe that the Where condition will be evaluated for all items before Any you don't have to worry about it that is the laziness of LINQ and Where will only be evaluated till Any finds a result.
You can see here a demo:
https://dotnetfiddle.net/Wen09a
public class Program
{
public static void Main()
{
var foos = new List<Foo>()
{
new Foo() { Id = 1, IdentificationNumber = "1" },
new Foo() { Id = 2, IdentificationNumber = "2" },
new Foo() { Id = 3, IdentificationNumber = "3" },
new Foo() { Id = 4, IdentificationNumber = "4" },
new Foo() { Id = 5, IdentificationNumber = "5" },
new Foo() { Id = 6, IdentificationNumber = "6" },
};
Console.WriteLine(HasAny(foos, 1, "2"));
}
static bool HasAny(List<Foo> foos, int id, string identificationNumber)
{
IEnumerable<Foo> query = (IEnumerable<Foo>)foos;
if (id > 0)
query = query.Where(x =>
{
Console.WriteLine($"Compare Ids - {id} and {x.Id}");
return id != x.Id;
});
return query.Any(x => x.IdentificationNumber == identificationNumber);
}
}
public class Foo
{
public int Id { get; set; }
public string IdentificationNumber { get; set; }
}
Even though I have declared the Ids 3 to 6 which would all be unequal to the id 1 it will stop searching on the first positive value for Any.

c# cross join two same type of lists

So here I have some code, which works ok. But I want to change the select part to something else, I am not sure what other methods I can use any help would be appreciated.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var all = new List<People>{new People{Id = 1, Name = "andy1", Age = null}, new People{Id = 2, Name = "andy2", Age = null}, new People{Id = 3, Name = "andy3", Age = null}, new People{Id = 4, Name = "andy4", Age = null}, };
var someOfThem = new List<People>{new People{Id = 1, Name = null, Age = 1}, new People{Id = 2, Name = null, Age = 1},new People{Id = 3, Name = null, Age = 1}};
var test = someOfThem.Select(c =>
{
c.Name = all.Find(a => a.Id == c.Id).Name;
return c;
});
foreach (var item in test)
Console.WriteLine("{0}={1}={2}", item.Id, item.Name, item.Age);
}
}
public class People
{
public int Id
{
get;
set;
}
public int? Age
{
get;
set;
}
public string Name
{
get;
set;
}
}
And here is the result.
1=andy1=1
2=andy2=1
3=andy3=1
I am just wondering is there another way to achieve the same result but a more elegant way? or an easier way?
var test = someOfThem.Select(c =>
{
c.Name = all.Find(a => a.Id == c.Id).Name;
return c;
});
Update
Sorry I did not show my problem properly at first, I have updated my quesiton. Please have a look again.
You can use C#'s LINQ keywords and more specifically, the join keyword assosciated with it:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var all = new List<People> { new People { Id = 1, Name = "andy1", }, new People { Id = 2, Name = "andy2", }, new People { Id = 3, Name = "andy3", }, new People { Id = 4, Name = "andy4", }, };
var someOfThem = new List<People> { new People { Id = 1, Name = null, }, new People { Id = 2, Name = null, } };
var test = from item in someOfThem
join element in all on item.Id equals element.Id
select element;
foreach (var item in test)
Console.WriteLine("{0}={1}", item.Id, item.Name);
}
}
public class People
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}
The code version would be
var test = someOfThem.Join(all, item => item.Id, element => element.Id, (item, element) => element);
as shown in Robert's comment
You can use the Join (you can also use a dictionary, but I'm not going to show it):
Here's the syntax for join:
var test = someOfThem.Join(all, item => item.Id, element => element.Id,
(item, element) => new Person {
Id = item.Id ?? element.Id,
Name = item.Name ?? element.Name,
Age = item.Age ?? element.Age
});
You can implement Equals and GetHashCode in your People class and use Intersect.
Or, create an EqualityComparer, that way your comparison logic is decoupled:
class Program
{
public static void Main()
{
var all = new List<People> { new People { Id = 1, Name = "andy1", }, new People { Id = 2, Name = "andy2", }, new People { Id = 3, Name = "andy3", }, new People { Id = 4, Name = "andy4", }, };
var someOfThem = new List<People> { new People { Id = 1, Name = null, }, new People { Id = 2, Name = null, } };
var test = all.Intersect(someOfThem, new PeopleIdComparer()).ToList();
foreach (var item in test)
Console.WriteLine("{0}={1}", item.Id, item.Name);
}
}
public class PeopleIdComparer : IEqualityComparer<People>
{
public bool Equals(People x, People y)
{
return x.Id == y.Id;
}
public int GetHashCode(People obj)
{
return HashCode.Combine(obj.Id);
}
}
public class People
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}

How Can I Achieve this Using LINQ?

The best way I can describe what I'm trying to do is "Nested DistinctBy".
Let's say I have a collection of objects. Each object contains a collection of nicknames.
class Person
{
public string Name { get; set; }
public int Priority { get; set; }
public string[] Nicknames { get; set; }
}
public class Program
{
public static void Main()
{
var People = new List<Person>
{
new Person { Name = "Steve", Priority = 4, Nicknames = new string[] { "Stevo", "Lefty", "Slim" }},
new Person { Name = "Karen", Priority = 6, Nicknames = new string[] { "Kary", "Birdie", "Snookie" }},
new Person { Name = "Molly", Priority = 3, Nicknames = new string[] { "Mol", "Lefty", "Dixie" }},
new Person { Name = "Greg", Priority = 5, Nicknames = new string[] { "G-man", "Chubs", "Skippy" }}
};
}
}
I want to select all Persons but make sure nobody selected shares a nickname with another. Molly and Steve both share the nickname 'Lefty' so I want to filter one of them out. Only the one with highest priority should be included. If there is a highest priority tie between 2 or more then just pick the first one of them. So in this example I would want an IEnumerable of all people except Steve.
EDIT: Here's another example using music album instead of person, might make more sense.
class Album
{
string Name {get; set;}
int Priority {get;set;}
string[] Aliases {get; set;}
{
class Program
{
var NeilYoungAlbums = new List<Album>
{
new Person{ Name = "Harvest (Remastered)", Priority = 4, Aliases = new string[] { "Harvest (1972)", "Harvest (2012)"}},
new Person{ Name = "On The Beach", Priority = 6, Aliases = new string[] { "The Beach Album", "On The Beach (1974)"}},
new Person{ Name = "Harvest", Priority = 3, Aliases = new string[] { "Harvest (1972)"}},
new Person{ Name = "Freedom", Priority = 5, Aliases = new string[] { "Freedom (1989)"}}
};
}
The idea here is we want to show his discography but we want to skip quasi-duplicates.
I would solve this using a custom IEqualityComparer<T>:
class Person
{
public string Name { get; set; }
public int Priority { get; set; }
public string[] Nicknames { get; set; }
}
class PersonEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null || y == null) return false;
return x.Nicknames.Any(i => y.Nicknames.Any(j => i == j));
}
// This is bad for performance, but if performance is not a
// concern, it allows for more readability of the LINQ below
// However you should check the Edit, if you want a truely
// LINQ only solution, without a wonky implementation of GetHashCode
public int GetHashCode(Person obj) => 0;
}
// ...
var people = new List<Person>
{
new Person { Name = "Steve", Priority = 4, Nicknames = new[] { "Stevo", "Lefty", "Slim" } },
new Person { Name = "Karen", Priority = 6, Nicknames = new[] { "Kary", "Birdie", "Snookie" } },
new Person { Name = "Molly", Priority = 3, Nicknames = new[] { "Mol", "Lefty", "Dixie" } },
new Person { Name = "Greg", Priority = 5, Nicknames = new[] { "G-man", "Chubs", "Skippy" } }
};
var distinctPeople = people.OrderBy(i => i.Priority).Distinct(new PersonEqualityComparer());
EDIT:
Just for completeness, this could be a possible LINQ only approach:
var personNicknames = people.SelectMany(person => person.Nicknames
.Select(nickname => new { person, nickname }));
var groupedPersonNicknames = personNicknames.GroupBy(i => i.nickname);
var duplicatePeople = groupedPersonNicknames.SelectMany(i =>
i.OrderBy(j => j.person.Priority)
.Skip(1).Select(j => j.person)
);
var distinctPeople = people.Except(duplicatePeople);
A LINQ-only solution
var dupeQuery = people
.SelectMany( p => p.Nicknames.Select( n => new { Nickname = n, Person = p } ) )
.ToLookup( e => e.Nickname, e => e.Person )
.SelectMany( e => e.OrderBy( p => p.Priority ).Skip( 1 ) );
var result = people.Except( dupeQuery ).ToList();
See .net fiddle sample
This works once, then you have to clear the set. Or store the results in a collection.
var uniqueNicknames = new HashSet<string>();
IEnumerable<Person> uniquePeople = people
.OrderBy(T => T.Priority) // ByDescending?
.Where(T => T.Nicknames.All(N => !uniqueNicknames.Contains(N)))
.Where(T => T.Nicknames.All(N => uniqueNicknames.Add(N)));

Merge 2 instances of same class based on ID using Linq/c#

I have an application which gets 2 instances of the same class from different method.I want to merge the 2 instance into 1 instance of the class. Below is the format of the instances
Instance 1
[{
"locationId": "ABCD",
"Fruits":
{
Fruit:
{
TypeId: "Mango",
}
}
},
{
"locationId": "EFGH",
"Fruits":
{
Fruit:
{
TypeId: "Pear",
}
}
}]
Instance 2
[{
"locationId": "ABCD",
"Fruits":
{
Fruit:
{
TypeId: "Apple",
}
}
},
{
"locationId": "EFGH",
"Fruits":
{
Fruit:
{
TypeId: "Kiwi",
}
}
}]
I want to merge them so that they appear as below,
[{
"locationId": "ABCD",
"Fruits":
{
Fruit:
{
TypeId: "Mango",
},
Fruit:
{
TypeId: "Apple",
}
}
},
{
"locationId": "EFGH",
"Fruits":
{
Fruit:
{
TypeId: "Pear",
},
Fruit:
{
TypeId: "Kiwi",
}
}
}]
Is it possible using linq? Could anyone please direct me to the correct path?
So you have two sequence of objects, where every object has a LocationId and a Fruit (or an array of Fruits).
You want a LINQ statement, that results in a sequence of objects where every object has a LocationId, and a sequence of all Fruits on that location.
For this we use Enumerable.GroupBy
You forgot to tell us what type of input is. Let's name it:
class Fruit {...}
class FruitOnLocation
{
public string LocationId {get; set;}
public Fruit Fruit {get; set;}
}
It might be that you have a sequence of Fruits on this location. The code will be very similar.
IEnumerable<FruitOnLocation> instance1 = ...
IEnumerable<FruitOnLocation> instance2 = ...
var result = instance1.Concat(instance2) // concatenate the two sequences
.GroupBy(item => item.LocationId, // make groups of objects with same LocationId
(locationId, fruitsOnLocation) => new // from every locationId and all fruitsOnLocation
{ // with this locationId make one new
LocationId = locationId, // object containing the common locationId
Fruits = fruitsOnLocation // and the list of all Fruits with this
.ToList(), // locationId
})
I would join the two lists on locationId and than concat the fruits.
var list1 = new[]
{
new LocationFruit { locationId = "ABCD", Fruits = new List<Fruit> { new Fruit { TypeId = "Mango"} }},
new LocationFruit { locationId = "EFGH", Fruits = new List<Fruit> { new Fruit { TypeId = "Pear"} }}
};
var list2 = new[]
{
new LocationFruit { locationId = "ABCD", Fruits = new List<Fruit> { new Fruit { TypeId = "Apple"} }},
new LocationFruit { locationId = "EFGH", Fruits = new List<Fruit> { new Fruit { TypeId = "Kiwi"} }}
};
var result = from a in list1
join b in list2 on a.locationId equals b.locationId
select new LocationFruit
{
locationId = a.locationId,
Fruits = a.Fruits.Concat(b.Fruits).ToList()
};
Assumed your classes looked like this:
public class LocationFruit
{
public string locationId { get; set; }
public List<Fruit> Fruits { get; set; }
}
public class Fruit
{
public string TypeId { get; set; }
}
Or if you want non matched items from both lists:
var result = list1
.Concat(list2)
.GroupBy(
x => x.locationId,
(key, items) => new LocationFruit
{
locationId = key,
Fruits = items.SelectMany(y => y.Fruits).ToList()
});
I think that you might want to have/use classes as follows:
class StockEntry
{
public IEnumerable<PerLocationStock> Stocks { get; set; }
}
class PerLocationStock
{
public string LocationId { get; set; }
public IEnumerable<Fruit> Fruits { get; set; }
}
public class Fruit
{
public string TypeId { get; }
public Fruit(string typeId)
{
TypeId = typeId;
}
protected bool Equals(Fruit other) => string.Equals(TypeId, other.TypeId);
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Fruit) obj);
}
public override int GetHashCode() => (TypeId != null ? TypeId.GetHashCode() : 0);
}
Where your object initialistion would look as follows:
var instance1 = new StockEntry
{
Stocks = new[] {
new PerLocationStock
{
LocationId = "ABCD",
Fruits = new [] { new Fruit("Mango") }
},
new PerLocationStock
{
LocationId = "EFGH",
Fruits = new [] { new Fruit("Pear") }
}
}
};
var instance2 = new StockEntry
{
Stocks = new[] {
new PerLocationStock
{
LocationId = "ABCD",
Fruits = new [] { new Fruit("Apple") }
},
new PerLocationStock
{
LocationId = "EFGH",
Fruits = new [] { new Fruit("Kiwi"), new Fruit("Pear") }
}
}
};
And finally your merging objects algorithms would look as follows:
var mergedInstance = new[] { instance1, instance2 }
.SelectMany(se => se.Stocks)
.GroupBy(pls => pls.LocationId)
.Aggregate(
new StockEntry { Stocks = Enumerable.Empty<PerLocationStock>() },
(stockEntry, groupedByLocationFruits) =>
{
var perLocationStock = new PerLocationStock
{
LocationId = groupedByLocationFruits.Key,
Fruits = groupedByLocationFruits.SelectMany(x => x.Fruits).Distinct()
};
stockEntry.Stocks = new List<PerLocationStock>(stockEntry.Stocks) { perLocationStock };
return stockEntry;
});
Next loop over the result in a following way:
foreach (var locationStock in mergedInstance.Stocks)
{
Console.WriteLine($"{nameof(PerLocationStock.LocationId)}={locationStock.LocationId}");
foreach (var fruitsInLocation in locationStock.Fruits)
Console.WriteLine($"{nameof(Fruit.TypeId)}={fruitsInLocation.TypeId}");
}
And finally get your expected result:
LocationId=ABCD
TypeId=Mango
TypeId=Apple
LocationId=EFGH
TypeId=Pear
TypeId=Kiwi
Please note that duplicates were removed from the fruits thanks to the Fruit.Equals.

Merge a list of object array c#

I have list of object array (List<object[]> a) that come from different sources (files, sql, webservices) and I need a way to join them.
For example, I have this two list:
List<object[]> listA = new List<object[]>();
object[] a = new object[] { 1, "A", 1200, "2016-12-31" };
listA.Add(a);
a = new object[] { 2, "B", 5200, "2016-12-31" };
listA.Add(a);
a = new object[] { 3, "C", 3500, "2016-12-31" };
listA.Add(a);
a = new object[] { 4, "D", 100, "2016-12-31" };
listA.Add(a);
List<object[]> listB = new List<object[]>();
object[] b = new object[] { 44, 859, "2016-12-08" };
listB.Add(b);
b = new object[] { 23, 851, "2016-12-07" };
listB.Add(b);
b = new object[] { 31, 785, "2016-12-09" };
listB.Add(b);
And the result will be this one:
List<object[]> listC = new List<object[]>();
object[] c = new object[] { 1, "A", 1200+859, 44, "2016-12-08" };
listC.Add(c);
c = new object[] { 2, "B", 5200+851, 23, "2016-12-07" };
listC.Add(c);
c = new object[] { 3, "C", 3500+785, 31, "2016-12-09" };
listC.Add(c);
c = new object[] { 4, "D", 100, null, null };
listC.Add(c);
The lists are bigger than in the example and I have to configure how to merge then but if I found a way to do this in linq is the half of the way.
Any ideas?
You can zip both sequences and concat with left items from listA:
listA.Zip(listB, (a, b) =>
new object[] { a[0], a[1], (int)a[2] + (int)b[1], b[0], b[2] })
.Concat(listA.Skip(listB.Count).Select(a =>
new object[] { a[0], a[1], a[2], null, null }))
You can also use group join or select items from second list by index of item in first list.
I also suggest you to use custom classes instead of arrays of objects to make your code more readable, get nice type checking, descriptive properties, and intellisense. E.g.
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public int Value { get; set; }
public DateTime Date { get; set; }
}
And
public class Bar
{
public int Id { get; set; }
public int Value { get; set; }
public DateTime Date { get; set; }
}
Of course you should use more appropriate names here. Now your code will look like
List<Foo> foos = new List<Foo> {
new Foo { Id=1, Name="A", Value=1200, Date=new DateTime(2016,12,31) },
new Foo { Id=2, Name="B", Value=5200, Date=new DateTime(2016,12,31) },
new Foo { Id=3, Name="C", Value=3500, Date=new DateTime(2016,12,31) },
new Foo { Id=4, Name="D", Value=100, Date=new DateTime(2016,12,31) },
};
List<Bar> bars = new List<Bar> {
new Bar { Id=44, Value=859, Date=new DateTime(2016,12,8) },
new Bar { Id=23, Value=851, Date=new DateTime(2016,12,7) },
new Bar { Id=31, Value=785, Date=new DateTime(2016,12,9) }
};
You can also create custom type to hold combined data.
public class Result
{
public int FooId {get; set; }
public string Name {get; set; }
public int Value {get;set;}
public int? BarId {get;set;}
public DateTime? Date {get; set;}
}
And getting results will look like
var results = foos.Zip(bars, (f, b) => new Result{
FooId = f.Id, Name = f.Name, Value = f.Value + b.Value, BarId = b.Id, Date = b.Date
}).Concat(foos.Skip(bars.Count).Select(f => new Result {
FooId = f.Id, Name = f.Name, Value = f.Value
}));
Try Working Code

Categories

Resources