In my c# app I have list of custom objects:
obj a,b,c,d,i,f,j
a.id = 1;
b.id = 2;
c.id = 3;
d.id = 4;
i.id = 5;
f.id = 6;
j.id = 7;
var firstList = new List<obj> {a,b,c,d,i,f,j};
And I have second list of int's
var secondList = new List<int> {5,6,2,1,3};
Items in secondList - this is id's from firstList in specific order. This list can include not all id's from firstList (in example not enough 4 and 7)
My question: how I can sort my first list by parameter id and by scenario, described in secondList. If in secondList some ids is missed - add corresponding items of firstList to the end of result list.
Unfortunatelly solution with multiple OrderBy(...).ThenBy(...) can't help me.
var firstListDic = firstList.ToDictionary(o => o.id);
var secondListSet = new HashSet<int>(secondList);
var result = secondList.Select(i => firstListDic[i])
.Concat(firstList.Where(o => !secondListSet.Contains(o.id)))
.ToList();
It would be:
secondList.Union(firstList.Select(o => o.id))
.Select(o => firstList.FirstOrDefault(k => k.id == o))
.ToList();
If your obj class contains something like
class obj
{
public int id;
public override string ToString()
{
return id.ToString();
}
}
Then you can test this
var firstList = new List<int> { 1, 2, 3, 4, 5, 6, 7 }
.Select(o => new obj() {id = o}).ToList();
var secondList = new List<int> { 5, 6, 2, 1, 3 };
var thirdlist =
secondList
.Union(firstList.Select(o => o.id))
.Select(o => firstList.FirstOrDefault(k => k.id == o))
.ToList();
Console.WriteLine(string.Join(",",thirdlist));
Related
Few days ago I asked same question with SQL, but now it arises in C# code
Lets say we have this kind of class for holding different id/text pairs:
public class Text {
public int id { get; set; }
public string text { get; set; }
...
}
Now lets populate some data,
ListA gets a lot of data:
List<Text> ListA = new List<Text>{
new () {id = 1, text = "aaa1"},
new () {id = 2, text = "aaa2"},
new () {id = 3, text = "aaa3"},
new () {id = 4, text = "aaa4"},
new () {id = 5, text = "aaa5"},
new () {id = 6, text = "aaa6"},
};
And ListB gets just a little bit of data:
List<Text> ListB = new List<Text>{
new () {id = 4, text = "bbb4"},
new () {id = 5, text = "bbb5"},
};
And now what we are looking:
var result = ... // Some Linq or Lambda magic goes here
// and if we do:
foreach(var item in result){
Console.WriteLine(item.Id + " " + item.Text);
}
// Result will be:
1 : aaa1
2 : aaa2
3 : aaa3
4 : bbb4
5 : bbb5
6 : aaa6
You can try looking for id within ListB:
var result = ListA
.Select(a => ListB.FirstOrDefault(b => b.id == a.id) ?? a);
Here for each a within ListA we try to find corresponding by id (b.id == a.id) item within ListB. If no such item is found we just return ListA item: ?? item
In case of .Net 6 you can use overloaded .FirstOrDefault version (we can pass a as a default value):
var result = ListA
.Select(a => ListB.FirstOrDefault(b => a.id == b.id, a));
It might be more efficient to convert ListB to a Dictionary first:
var dictB = ListB.ToDictionary(x=> x.id)
Then you can write
var result = ListA.Select(x => dictB.TryGetValue(x.id, out var b) ? b : x)
UPD Rewrote taking comment suggestions into account
One option is to do an Union operation, by specifying an EqualityComparer. If the order is important, you can do an OrderBy operation at the end.
class TextIdComparer : EqualityComparer<Text> {
public override bool Equals(Text x, Text y) => x.id == y.id;
}
var result = ListB.Union(ListA, new TextIdComparer()).OrderBy(x => x.id)
This question already has answers here:
LINQ - Full Outer Join
(16 answers)
Closed 4 years ago.
Here I have 2 lists of same object type.
object = {id: xxx, ...} // attribute "id" is used to find the identical obj
List oldSet = [old1, old2, old3];
List newSet = [new2, new3, new4];
// old2 = {id= 2, result = 5, ...}
// new2 = {id= 2, result = 1, ...}
// expected result = {oldSet: old2; newSet: new2}
I want to merge both lists, also keeping the origin of which list it came from.
The expected result as below:
List mergedSet = [{old1, null}, {old2, new2}, {old3, new3}, {null, new4}];
I'm thinking to use LINQ C# for it, but stuck somewhere.
Kindly advise.
Thanks! :)
Here's some code that does what you want using Linq. It basically walks through all the old list, and adds pairs to the merged list by looking for matches from the new list (and adding null as the second item if no match was found). Then it walks through the remaining items in the new list and adds them with null for the first item. It selects a dynamic type with two properties: OldSet and NewSet, so you know where each item came from.
The merge code is simply:
var mergedSet = oldSet.Select(o =>
new {OldSet = o, NewSet = newSet.FirstOrDefault(n => n.id == o.id)})
.Concat(newSet.Where(n => oldSet.All(o => o.id != n.id)).Select(n =>
new {OldSet = (Item) null, NewSet = n}));
This is based on the following item class:
class Item
{
public int id { get; set; }
public string result { get; set; }
public override string ToString()
{
return $"{result}{id}";
}
}
We create our lists:
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
Run the merge code (very first snippet), and then display results:
foreach (var item in mergedSet)
{
Console.WriteLine($"{item.NewSet},{item.OldSet}");
}
Output
Try something like this :
List<string> oldSet = new List<string>() {"old1", "old2", "old3"};
List<string> newSet = new List<string>() {"new2", "new3", "new4"};
var results = oldSet.Select((x,i) => new { oldSet = x, newSet = newSet[i]}).ToList();
You can left join the two lists. I edited the answer as you actually need to left join twice, union, and apply a select distinct to get the cases where oldSet = null and no duplicates...
var mergedSet = (from o in oldSet
join n in newSet on o.id equals n.id into ns
from n in ns.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Union(from n in newSet
join o in oldSet on n.id equals o.id into os
from o in os.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Distinct();
Might be an overkill, but if you really want to use LINQ
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
var resultL = oldSet.GroupJoin(
newSet,
o => o.id,
n => n.id,
(o,n) => new { Old = o, New = n })
.SelectMany(
n => n.New.DefaultIfEmpty(),
(o,n) => new Tuple<Item,Item>(o.Old,n));
var resultR= newSet.GroupJoin(
oldSet,
n => n.id,
o=> o.id,
(n,o) => new { Old = o, New = n })
.SelectMany(
o=> o.Old.DefaultIfEmpty(),
(n,o) => new Tuple<Item,Item>(o,n.New));
var result = resultL.Union(resultR).Distinct();
In this case, you have to use two GroupJoin and the Union the results.
Look at the following code:
var res1 = oldSet.GroupJoin(newSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = x, Y = yy }; });
var res2 = newSet.GroupJoin(oldSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = yy, Y = x }; });
var result = res1.Union(res2).ToList();// Your result is here
I have a table which contains Branch Ids and Department Ids. I have three branches and 1st branch has only 1 Department, the 2nd branch has two departments and 3rd branch has three Departments.
Now, I need to write a query to find branches which have department 1 but doesn't have dept. 2 and dept. 3.
This is just an example, I have a much more complex scenario which is very dynamic. I am using this example to put forward my question.
I am attaching the picture to understand the problem.
Here's query:
db.ConnectedBRDE.Where(x => x.DeptId == 1 && x.DeptId != 2)
.Select(x => x.BranchId)
.ToList();
This query is giving my all three Branches, whereas, I only need branch 1 because this is the only branch which doesn't have department 2.
This part && x.DeptId != 2 is wrong, I guess. What should I write here to make my filter working?
Stephen Muecke's comment does indeed work.
I have tested it in DotNetFiddle.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
List<TestClass> lstOfItems = new List<TestClass>();
var itemOne = new TestClass(){BranchName = "Branch One", BranchId = 1, DeptId = 1};
var itemTwo = new TestClass(){BranchName = "Branch Two", BranchId = 2, DeptId = 1};
var itemThree = new TestClass(){BranchName = "Branch Two", BranchId = 2, DeptId = 2};
var itemFour = new TestClass(){BranchName = "Branch Three", BranchId = 3, DeptId = 1};
var itemFive = new TestClass(){BranchName = "Branch Three", BranchId = 3, DeptId = 2};
var itemSix = new TestClass(){BranchName = "Branch Three", BranchId = 3, DeptId = 3};
lstOfItems.Add(itemOne);
lstOfItems.Add(itemTwo);
lstOfItems.Add(itemThree);
lstOfItems.Add(itemFour);
lstOfItems.Add(itemFive);
lstOfItems.Add(itemSix);
var myList = lstOfItems.GroupBy(x => x.BranchName).Where(y => y.Count() == 1 && y.First().DeptId == 1).ToList();
foreach(var item in myList){
Console.WriteLine(item.Key);
}
// Output
// Branch One
}
}
public class TestClass
{
public string BranchName {get;set;}
public int BranchId {get;set;}
public int DeptId {get;set;}
}
Basically, once all of the records are grouped by BranchName property, then we want to count all of the records under each branch name.. and if the count equals 1 then that means that branch only has 1 record.. and then we find the DeptId of that record and if it equals 1 then that satisfies your condition.
I think bellowing code is what are you looking for
var list = new List<Model>();
list.Add(new Model(1, 1));
list.Add(new Model(2, 1));
list.Add(new Model(2, 2));
list.Add(new Model(3, 1));
list.Add(new Model(3, 2));
list.Add(new Model(3, 3));
var notValidBranchIds = list.Where(x => x.DeptId == 2 || x.DeptId == 3).Select(x => x.BranchId);
var result = list.Where(x => x.DeptId == 1 && !notValidBranchIds.Contains(x.BranchId)).Select(x => x.BranchId);
// you can also use this. It solve the problem in a line
var betterResult = list.GroupBy(x => new { x.DeptId })
.Select(x => x.FirstOrDefault(a => a.DeptId == 1))
.Where(y => y != null)
.ToList();
return only first branchId's record.
Hope it helps to you.
if you have access to Branch and Department models, i suggest use this query: Branches.Where(b=>b.Departments.All(d=>d.Id != 2) && b.Departments.Any(d=>d.Id==1))
I have following scenario where I want to find duplicates after forming the group and realign/format the duplicate data with some common class.
Example -
var lst = new List<Test>
{
new Test{Category="A",Class="Class1",Id="101",Name="John"},
new Test{Category="B",Class="Class2",Id="102",Name="Peter"},
new Test{Category="A",Class="Class2",Id="103",Name="David"},
new Test{Category="C",Class="Class3",Id="104",Name="Julia"},
new Test{Category="D",Class="Class4",Id="105",Name="Ken"},
new Test{Category="A",Class="Class1",Id="106",Name="Robert"},
};
I have created the group as -
var group =
from c in lst
group c by new
{
c.Category,
c.Class
} into g
select new
{
Category = g.Key.Category,
Class = g.Key.Class,
Id = lst.Where(x => g.Key.Category == x.Category && g.Key.Class==x.Class)
.Select(y => y.Id).ToList()
};
Which results me 2 group items for Category A with different Classes -
GroupItem1 - Category = "A" , Class = "Class1", Id = {101,106}
GroupItem2 - Category = "A" , Class = "Class2", Id = {103}
So I have requirement to show result in such case as below with other categories as -
Category = "A", Class = "Class1 OR SomeCommonClass", Id = {101,106,103}
Is it possible to achieve this result with minimum code and optimized logic.
If you want to group by Category and get the result below is the query.
var group =
from c in lst
group c by new
{
c.Category
} into g
select new
{
Category = g.Key.Category,
Class = lst.Where(x => g.Key.Category == x.Category).Select(y => y.Class).ToList(),
Id = lst.Where(x => g.Key.Category == x.Category)
.Select(y => y.Id).ToList()
};
Replace your group query with this:
var groups =
from c in lst
group c by c.Category into g
select new { Category = g.Key, Class = g.Select(c => c.Class).Distinct().Join(" or "), IDs = g.Select(c => c.Id).ToList() };
where Join is an IEnumerable extension method:
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
var group = lst.GroupBy(l => l.Category)
.Select(x => new
{
Category = x.Key,
Class = string.Join(" OR ", x.Select(c => c.Class).Distinct()),
Ids = x.Select(c => c.Id).ToList()
}).ToList();
This question already has answers here:
LINQ OrderBy versus ThenBy
(4 answers)
Closed 5 years ago.
IQueryable<Employee> query = ((IEnumerable<Employee>)employeeList)
.Select(x => x)
.AsQueryable();
var strListEmployees = input.MustIncludeIdsInPage.Split(",").ToList();
//the list of employee is dynamic, it'd return 3, 4, 5 or more data
var entities = query
.OrderBy(item => strListEmployees.IndexOf(item.Id.ToString()))
.PageBy(input)
.ToList();
example data
What I want is something like this in order:
by employee name
D
F
A
B
C
E
G
H
Employee D, F, A on top (fix value in List) and show the rest with name sorting (order by).
As M. Wiśnicki mentioned, this is easily solveable as You got only 3 elements. But to dynamically resolve this, I would stick to some function, where You would enter the List (or IEnumerable) of the objects and also the Names, based on which You want to filter them.
The code below is recursion, which will go through the array and select the 1st element (from array) and add the rest. Rest is calling the same function without the 1st name & without the element we have already added.
Something like:
public IEnumerable<Employee> GetOrderedPrefered(IEnumerable<Employee> aList, string[] aNames)
{
if (aNames.Length == 0) return aList.OrderBy(a => a.Name).ToList();
var lRes = new List<Employee>()
{
aList.FirstOrDefault(a => a.Name == aNames[0])
};
lRes.AddRange(
GetOrderedPrefered(
aList.Where(a => a.Name != aNames[0]),
aNames.Where(a => a != aNames.First()
).ToArray()
));
return lRes;
}
Usage:
var lRes = GetOrderedPrefered(persons, names);
foreach (var item in lRes)
Console.WriteLine(item.Name);
> D
> F
> A
> B
> C
> E
> G
You can use OrderBy() and ThenBy()
List<Test> tests = new List<Test>()
{
new Test() {EmployeeID = "1", Name = "A"},
new Test() {EmployeeID = "2", Name = "B"},
new Test() {EmployeeID = "3", Name = "C"},
new Test() {EmployeeID = "4", Name = "D"},
new Test() {EmployeeID = "5", Name = "E"},
new Test() {EmployeeID = "6", Name = "F"},
new Test() {EmployeeID = "7", Name = "G"},
new Test() {EmployeeID = "8", Name = "H"},
};
var x = tests.OrderBy(name => name.Name != "D")
.ThenBy(name => name.Name != "F")
.ThenBy(name => name.Name != "A")
.ThenBy(name => name.Name)
.ToList();
Result is: First D,F,A and others names
Edit:
string[] filtr = new[] {"D", "F", "A"};
var fdata = tests.Where(d => filtr.Contains(d.Name)).OrderBy(z=>z.Name).ToList();
var odata = tests.Where(d => !filtr.Contains(d.Name)).OrderBy(z => z.Name).ToList();
fdata.AddRange(odata);
var set = Enumerable.Range(0, 8)
.Select(i => new {
Name = new string(new[] { (char)('A' + i) })
});
var before = string.Join(",", set.Select(i => i.Name)); //A,B,C,D,E,F,G,H
var priorities = "D,F".Split(',').Select((v, i) => new { Value = v, Index = i });
var query = from s in set
join p in priorities on s.Name equals p.Value into m
from x in m.DefaultIfEmpty(new { Value = s.Name, Index = int.MaxValue })
orderby x.Index, s.Name
select s.Name;
var result = string.Join(",", query); //D,F,A,B,C,E,G,H