How to join two Lists based on common property - c#

Suppose I have two Lists<myObject> where myObject consists of the two properties
Id (of type Int) and
Value (of type Double)
I need to get a list out of these two lists that is made of (anonymous) objects like this:
Id, [Double value from List 1], [Double value from List 2]
So if for a given Id both lists contain a value, it should look like this example:
12, 21.75, 19.87
If one list does not contain an object with an Id that is present in the other list, the value should be null:
15, null, 22.52
How can I achieve that?
Update: I know how I could get such a list, of course, but I'm looking for the most performant way to do it, preferrably by using some witty Linq magic.

Not sure how optimized this is, but should suit your needs - Assuming I understood what you wanted:
var enumerable1 = new[]
{
new {Id = "A", Value = 1.0},
new {Id = "B", Value = 2.0},
new {Id = "C", Value = 3.0},
new {Id = "D", Value = 4.0},
new {Id = "E", Value = 5.0},
};
var enumerable2 = new[]
{
new {Id = "A", Value = 6.0},
new {Id = "NOT PRESENT", Value = 542.23},
new {Id = "C", Value = 7.0},
new {Id = "D", Value = 8.0},
new {Id = "E", Value = 9.0},
};
var result = enumerable1.Join(enumerable2, arg => arg.Id, arg => arg.Id,
(first, second) => new {Id = first.Id, Value1 = first.Value, Value2 = second.Value});
foreach (var item in result)
Console.WriteLine("{0}: {1} - {2}", item.Id, item.Value1, item.Value2);
Console.ReadLine();
The resulting output would be something akin to:
A: 1 - 6
C: 3 - 7
D: 4 - 8
E: 5 - 9
Don't really see why you would want null values returned, unless you absolutely need to (Besides, double is not-nullable, so it would have to be the resulting combined entry that would be null instead).

The requirement is slightly unclear. Do you want a Cartesian product or a join on Id? If the latter, then this should work:
var result = from l1 in list1
join l2 in list2
on l1.Id equals l2.Id
select new {l1.Id, Value1 = l1.Value, Value2 = l2.Value};
If you actually want a full outer join, see this.

**Let say tempAllocationR is list 1 and tempAllocationV is List2 **
var tempAllocation = new List<Object>();
if (tempAllocationR.Count > 0 && tempAllocationV.Count > 0)
{
foreach (TempAllocation tv in tempAllocationV)
{
var rec = tempAllocationR.FirstOrDefault(tr => tr.TERR_ID == tv.TERR_ID && tr.TERR == tv.TERR && tr.Team == tv.Team);
if (rec != null)
{
rec.Vyzulta = tv.Vyzulta;
}
else
{
tempAllocationR.Add(tv);
}
}
tempAllocation = tempAllocationR;
}}

Related

merging 2 lists into 1 with logic

I have 2 lists of the same type.
List 1:
ID
Name
Value
1,"Prod1", 0
2,"Prod2", 50
3,"Prod3", 0
List 2:
ID
Name
Value
1,"Prod1", 25
2,"Prod2", 100
3,"Prod3", 75
I need to combine these 2 lists into 1, but I only want the values from list2 if the corresponding value from list1 == 0
So my new list should look like this:
1,"Prod1", 25
2,"Prod2", 50
3,"Prod3", 75
I've tried many variations of something like this:
var joined = from l1 in List1.Where(x=>x.Value == "0")
join l2 in List2 on l1.ID equals l2.ID into gj
select new { gj };
I've also tried a variation of the concat
What is the best way of doing this?
You just need to select the individual properties and conditionally select either the Value from the first or second list item.
var List1 = new[]
{
new { Name = "Prod1", Id = 1, Value = 0 },
new { Name = "Prod2", Id = 2, Value = 50 },
new { Name = "Prod3", Id = 3, Value = 0 },
new { Name = "NotInList2", Id = 4, Value = 0}
};
var List2 = new[]
{
new { Name = "Prod1", Id = 1, Value = 25 },
new { Name = "Prod2", Id = 2, Value = 100 },
new { Name = "Prod3", Id = 3, Value = 75 }
};
var results = from l1 in List1
join l2temp in List2 on l1.Id equals l2temp.Id into grpj
from l2 in grpj.DefaultIfEmpty()
select new
{
l1.Id,
l1.Name,
Value = l1.Value == 0 && l2 != null ? l2.Value : l1.Value
};
foreach(var item in results)
Console.WriteLine(item);
Will output
{ Id = 1, Name = Prod1, Value = 25 }
{ Id = 2, Name = Prod2, Value = 50 }
{ Id = 3, Name = Prod3, Value = 75 }
{ Id = 4, Name = NotInList2, Value = 0 }
NOTE: This assumes that you only want all the ids that are in List1 (not any that are only in List2) and that the ids are unique and that the Name from List1 is what you want even if it is different in List2.
clone l1 and
foreach (var item in l1Clone)
if (item.value == 0)
item.value == l2.FirstOrDefault(l2item => l2item.ID == item.ID)
Refer to the code below:
IEnumerable<item> join_lists(IEnumerable<item> list1, IEnumerable<item> list2)
{
var map = list2.ToDictionary(i => i.id);
return list1.Select(i => new item()
{
id = i.id,
name = i.name,
value = i.value == 0 ? map[i.id].value : i.value
});
}
You could use Zip:
var combined = list1
.Zip(list2, (product1, product2) => product1.Value == 0 ? product2 : product1);

How to filter List<object> using Linq/Lambda

Here is my scenario
List<object> obj = new List<object>();
obj.Add(new {id = 1, name = "Jakob"});
obj.Add(new {id = 2, name = "Sam"});
obj.Add(new {id = 3, name = "Albert"});
obj.Add(new {id = 1, name = "Jakob"});
How do you filter List<object> like these so it returns a List of users with name "Jakob"?
obj.Where(t => t.name == "Jakob") doesn't work
The best option you have is to declare a class that represents a user.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
Then create a list of User objects and query this list.
var users = new List<User>
{
new User { Id = 1, Name = "Jakob" },
new User { Id = 2, Name = "Sam" },
new User { Id = 3, Name = "Albert" }
}
var filteredUsers = users.Where(user => user.Name == "Jakob");
Otherwise, you have to rely on the solution that Tengiz suggested.
If you convert your object to dynamic, it should work:
obj.Where(t => ((dynamic)t).name == "Jakob")
EDIT:
For completeness, I should mention couple of things:
Usage of dynamic type comes down to usage of object with reflection on top of it, so eventually you don't get something better than reflection if you use this approach.
Usage of dynamic at all involves loading necessary assemblies (a.k.a. DLR) into CLR, which would not load if you don't use dynamic at all. In other words, it's an overhead.
So, use with your own discretion.
Yet another alternative is create array of anonymous types and then convert it to list via ToList IEnumerable extension method:
var obj = (new[] {
new { id = 1, name = "Jakob" },
new { id = 2, name = "Sam" },
new { id = 3, name = "Albert" },
new { id = 1, name = "Jakob" }}).ToList();
obj.Where(c => c.name == "Jakob");
If you don't really need a list and array is fine too - just don't convert to list. Benefit is you got strongly typed list and not list of arbitrary objects.
You could use reflection
var l = new List<object>();
l.Add(new {key = "key1", v = "value1"});
l.Add(new {key = "key2", v = "value2", v2="another value"});
l.Add(new {key = "key3", v = "value3", v3= 4});
l.Add(new {key = "key4", v = "value4", v4 = 5.3});
var r = l.Where(x=> (string)x.GetType().GetProperty("key")?.GetValue(x) == "key1");
Get the type of your elements and find the property you are looking for. Then get the value for the current instance and compare it to the value you want to filter for.
But on the other hand, this approach has the advantage of working even if the List contains items of several different anonymous types (if they have different properties), as long as they all have the property you are filtering for.
EDIT
With c# 6 you can use the ? operator, which is sort of an inline check for null. Ie, if GetProperty() returns null because the property is not found, the expression returns null without executing GetValue() (which would otherwise throw a NullReferenceException)

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

At least one object must implement IComparable error in linq query

var pairs = new [] { new { id = 1, name = "ram", dept = "IT", sal = "3000" }, new { id = 2, name = "ramesh", dept = "IT", sal = "5000" }, new { id = 3, name = "rahesh", dept = "NONIT", sal = "2000" },
new { id = 5, name = "rash", dept = "NONIT", sal = "7000" } };
var query = from stud in pairs
where (stud.name.StartsWith("r") && stud.id % 2 != 0)
//orderby stud.sal descending
group stud by stud.dept into grps
select new { Values = grps, Key = grps.Key, maxsal=grps.Max() };
////select new { id = stud.id };
foreach (dynamic result in query)
{
Console.WriteLine(result.Key);
Console.WriteLine(result.maxsal);
foreach (dynamic result2 in result.Values)
{
Console.WriteLine(result2.id + "," + result2.sal);
}
}
Console.Read();
I am getting the error "At least one object must implement IComparable.", can someone explain me why iam I getting this error ?
You are calling grps.Max() to get maximnum item in group. Your anonymous objects are not comparable. How Linq will know which one is maximum from them? Should it use id property for comparison, or name?
I believe you want to select max salary:
maxsal = grps.Max(s => Int32.Parse(s.sal))

Is there a good way to create a one-way table by a multiple choice result with linq?

I have an online research project , and also there is lots of multiple choice question.
for example...↓
Q:Which Programming language u like?
1,C
2,C++
3,CSharp
4,VB.NET
5,JAVA
I will save the selected item as 1 and unselected as 0
so the result is like↓
People Selected
people1 00100
people2 11100
people3 00110
people4 00100
people5 NULL
... ...
Now my question is how can I create a one-way table like below by the struct like above use Linq?
TOTAL (N=5)
C 1
C++ 1
CSharp 4
VB.NET 1
JAVA 0
NULL 1
This is my source , and I think it is not good
static void Main(string[] args)
{
// the mapping...
Dictionary<int, string> dic = new Dictionary<int, string>()
{
{0 , "C"} ,
{1 , "C++"} ,
{2 , "CSharp"} ,
{3 , "VB.NET"} ,
{4 , "JAVA"}
};
// the answer data by the people
List<string> lstM = new List<string>()
{
"00100" , // people1
"11100" , // people2
"00110" , // people3
"00100" , // people4
"NULL" // people5
};
var result = from p in lstM
where "NULL".Equals(p)
group p by p into g
select new {Key = g.Key , Cnt = g.Count()};
foreach (var d in dic)
{
var tmp1 = from p in lstM
where !"NULL".Equals(p) && "1".Equals(p.Substring(d.Key, 1))
select 1;
Console.WriteLine(string.Format("Key = {0} , Cnt={1}", dic[d.Key], tmp1.Count()));
}
foreach (var x in result)
{
Console.WriteLine(string.Format("Key = {0} , Cnt={1}", x.Key, x.Cnt));
}
Console.ReadKey();
}
Please anybody can give me some good idea about it?
thanks for your advice...
dic.Select(a=>
{
Option = a.Value,
Count = lstM.Where(o => o.Length > a.Key && o[a.Key] == '1').Count()
});
this will give you:
C 1
C++ 1
CSharp 4
VB.NET 1
JAVA 0
Lose all the binary encoding and "string typing". Quick example:
var options = new [] { "C", "C++", "C#", "VB.NET", "Java" };
var people1 = new string[] { "C#" };
var people2 = new string[] { "C", "C++", "C#" };
var people3 = new string[] { "C#", "VB.NET" };
var people4 = new string[] { "C#" };
var people5 = new string[] { };
var pollAnswers = new [] { options, people1, people2, people3, people4, people5 };
pollAnswers.SelectMany(answ => answ).
GroupBy(opt => opt).
ToDictionary(gr => gr.Key, gr => gr.Count() -1);
This creates a dictionary of selected answers for each person asked. Later you can use LINQ grouping and aggregation operators to produce the result you want.
C: 1
C++: 1
C#: 4
VB.NET: 1
Java: 0
I used string type for answer options as an example, it can be some other type. The point is to use reference equality and not mess with binary representation. Leave this stuff to lower-lever languages and make sure that your code is not only smart, but also easy to read and maintain.

Categories