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
Related
I have 2 list
IEnumerable<int> Ids, IEnumerable<Guid> GuidIds
for sample data
IEnumerable<int> ids1 = new List<int>() { 1, 2, 4 };
IEnumerable<Guid> guidIds = new List<Guid>() { new Guid("F44D7A64-8BDE-41E1-810F-B24377AD7F94"), new Guid("F44D7A64-8BDE-41E1-810F-B24377AD7F96"), new Guid("F44D7A64-8BDE-41E1-810F-B24377AD7F97") };
and a class
public class ClassEnd
{
public int Id { get; set; }
public Guid GuidId { get; set; }
}
I want to merge into this by Linq query
IEnumerable<ClassEnd> mergeList = new List<ClassEnd>()
{
new ClassEnd() { Id = 1, CompanyId = new Guid("F44D7A64-8BDE-41E1-810F-B24377AD7F94") },
new ClassEnd() { Id = 2, CompanyId = new Guid("F44D7A64-8BDE-41E1-810F-B24377AD7F96") },
new ClassEnd() { Id = 3, CompanyId = new Guid("F44D7A64-8BDE-41E1-810F-B24377AD7F97") }
};
Could you please help me with how to merge like the above "mergeList" by using Linq?
If lists are of the same size - it can be easily achieved with Enumerable.Zip:
var classEnds = ids1
.Zip(guidIds, (i, guid) => new ClassEnd
{
Id = i,
GuidId = guid
})
.ToList();
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)));
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
Consider an entity named Employee which contains id,age and name as properties
I have two lists containing the Employee details
I have to compare the two lists excluding the id column
Please help with your suggestions
This will yield all the entries that are the same in both lists, ignoring the Id Property of your Employee:
var employees1 = new List<Employee>
{
new Employee(1, "Thomas", 12),
new Employee(2, "Alex", 24),
new Employee(3, "Tobias", 13),
new Employee(4, "Joshua", 12),
new Employee(5, "Thomas", 24)
};
var employees2 = new List<Employee>
{
new Employee(1, "Thomas", 12),
new Employee(2, "Yu", 24),
new Employee(3, "Max", 13),
new Employee(4, "Joshua", 30),
new Employee(5, "Maico", 13)
};
var duplicates = employees1.Intersect(employees2, new EmployeeComparer());
class EmployeeComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee employee1, Employee employee2)
{
if (Object.ReferenceEquals(employee1, null) || Object.ReferenceEquals(employee2, null) ||
Object.ReferenceEquals(employee1, employee2)) return false;
return employee1.Name == employee2.Name && employee1.Age == employee2.Age;
}
public int GetHashCode(Employee employee)
{
return 0;
}
}
class Employee
{
public Employee(int id, string name, int age)
{
Id = id;
Name = name;
Age = age;
}
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
As the post is tagged with LINQ I have used that in my answer.
static void Main(string[] args)
{
var list1 = new List<Person>();
var list2 = new List<Person>();
list1.Add(new Person(1, "james", "moon"));
list1.Add(new Person(1, "bob", "bar"));
list1.Add(new Person(1, "tim", "lane"));
list1.Add(new Person(1, "fizz", "sea"));
list2.Add(new Person(1, "buzz", "space"));
list2.Add(new Person(1, "james", "moon"));
var result = findDuplicates(list1, list2);
}
public static List<Person> findDuplicates(List<Person> l1, List<Person> l2)
{
return l1.Where(p => l2.Any(z => z.FName == p.FName && z.Addre == p.Addre)).ToList();
}
Person Class
public class Person
{
private int id;
private string fName;
private string addre;
public string Addre
{
get { return addre; }
set { addre = value; }
}
public string FName
{
get { return fName; }
set { fName = value; }
}
public int ID
{
get { return id; }
set { id = value; }
}
public Person(int i, string f, string a)
{
ID = i;
FName = f;
Addre = a;
}
}
Assuming Employee class:
class Employee
{
public int id { get; set; }
public int age { get; set; }
public string name { get; set; }
}
You can simply use Intersect :
var list1 = new List<Employee> {
new Employee{ id=2 , age=23, name="Hari"},
new Employee{ id=3 , age=10, name="Joe"},
new Employee{ id=4 , age=29, name="Daniel"},
};
var list2 = new List<Employee> {
new Employee{ id=1 , age=23, name="Hari"},
new Employee{ id=5 , age=10, name="Joe"},
new Employee{ id=6 , age=29, name="Daniel"},
};
var intersect = list1.Select(e => new { e.age, e.name }).Intersect(list2.Select(e => new { e.age, e.name })).ToList();
What is the easiest and somewhat efficient way to convert a flat structure:
object[][] rawData = new object[][]
{
{ "A1", "B1", "C1" },
{ "A1", "B1", "C2" },
{ "A2", "B2", "C3" },
{ "A2", "B2", "C4" }
// .. more
};
into a hierarchical structure:
class X
{
public X ()
{
Cs = new List<string>();
}
public string A { get; set; }
public string B { get; set; }
public List<string> Cs { get; private set; }
}
the result should look like this
// pseudo code which describes structure:
result =
{
new X() { A = "A1", B = "B1", Cs = { "C1", "C2" } },
new X() { A = "A2", B = "B2", Cs = { "C3", "C4" } }
}
Preferably using Linq extension methods. Target class X could be changed (eg. a public setter for the List), only if not possible / useful as it is now.
for this particular case:
.GroupBy( x => new { a = x[0], b = x[1] } )
.Select( x => new { A = x.Key.a, B = x.Key.b, C = x.Select( c => c[2] ) })
Something like this should work if the depth of your hierarchy is limited (as in your example where you have only three levels A, B and C). I simplified your X a little bit:
class X {
public string A { get; set; }
public string B { get; set; }
public List<string> Cs { get; set; }
}
Then you can use nested GroupBy as many times as you need (depending on the depth of the hierarchy). It would be also relatively easy to rewrite this into a recursive method (that would work for arbitrarily deep hierarchies):
// Group by 'A'
rawData.GroupBy(aels => aels[0]).Select(a =>
// Group by 'B'
a.GroupBy(bels => bels[1]).Select(b =>
// Generate result of type 'X' for the current grouping
new X { A = a.Key, B = b.Key,
// Take the third element
Cs = b.Select(c => c[2]).ToList() }));
This is more explicit than the other solutions here, but maybe it will be more readable as it is more straightforward encoding of the idea...
With X members being strings and Cs being a private set, and rawData being an array of arrays of objects, I would add a constructor to X public X(string a, string b, List<string> cs) and then perform this code
var query = from row in rawData
group row by new { A = row[0], B = row[1] } into rowgroup
select new X((string)rowgroup.Key.A, (string)rowgroup.Key.B, rowgroup.Select(r => (string)r[2]).ToList());
This is on the following raw data
object[][] rawData = new object[][]
{
new object[] { "A1", "B1", "C1" },
new object[] { "A1", "B1", "C2" },
new object[] { "A2", "B2", "C3" },
new object[] { "A2", "B2", "C4" }
// .. more
};
I wanted to see if I could write this without anonymous instances. It's not too bad:
IEnumerable<X> myList =
from raw0 in rawData
group raw0 by raw0[0] into g0
let g1s =
(
from raw1 in g0
group raw1 by raw1[1]
)
from g1 in g1s
select new X()
{
A = g0.Key,
B = g1.Key,
C = g1.Select(raw2 => raw2[2]).ToList()
}