Intersect two generic lists by dynamic properties - c#

i have two generic lists with a few properties to compare but i want that the key identifiers are dynamic by a List<string>.
So lets say we have the class:
class A
{
string Name { get; set; }
string Color1 { get; set; }
string Color2 { get; set; }
string Length { get; set; }
}
The user now can select from an user interface which properties of two lists of those objects need to overlap so that a correct pair is selected. This is stored in a List<string>. As example, if the list string contains "Name" and "Color1" there will be only objects returned where "Name" and "Color1" are overlapping.
I was trying to write a function, but unfortunately i'm not sure which collection i should cast the generic lists to and how do i apply the names of the properties on those? If the name of the "identificators" were always the same, it wouldn't be a problem with Linq/Lambda ;)
Thanks in advance

You need to use reflection for this. This works:
public class A
{
public string Name { get; set; }
public string Color1 { get; set; }
public string Color2 { get; set; }
public string Length { get; set; }
public static IEnumerable<A> Intersecting(IEnumerable<A> input, List<string> propertyNames)
{
if(input == null)
throw new ArgumentNullException("input must not be null ", "input");
if (!input.Any() || propertyNames.Count <= 1)
return input;
var properties = typeof(A).GetProperties();
var validNames = properties.Select(p => p.Name);
if (propertyNames.Except(validNames, StringComparer.InvariantCultureIgnoreCase).Any())
throw new ArgumentException("All properties must be one of these: " + string.Join(",", validNames), "propertyNames");
var props = from prop in properties
join name in validNames.Intersect(propertyNames, StringComparer.InvariantCultureIgnoreCase)
on prop.Name equals name
select prop;
var allIntersecting = input
.Select(a => new {
Object = a,
FirstVal = props.First().GetValue(a, null),
Rest = props.Skip(1).Select(p => p.GetValue(a, null)),
})
.Select(x => new {
x.Object, x.FirstVal, x.Rest,
UniqueValues = new HashSet<object>{ x.FirstVal }
})
.Where(x => x.Rest.All(v => !x.UniqueValues.Add(v)))
.Select(x => x.Object);
return allIntersecting;
}
}
Sample data:
var aList = new List<A> {
new A { Color1 = "Red", Length = "2", Name = "Red" }, new A { Color1 = "Blue", Length = "2", Name = "Blue" },
new A { Color1 = "Red", Length = "2", Name = "A3" }, new A { Color1 = "Blue", Length = "2", Name = "A3" },
new A { Color1 = "Red", Length = "3", Name = "Red" }, new A { Color1 = "Blue", Length = "2", Name = "A6" },
};
var intersecting = A.Intersecting(aList, new List<string> { "Color1", "Name" }).ToList();

Related

How to sort a list of objects by another list

I have two classes:
class Location
{
public string Address { get; set; }
}
class Person
{
public string Address { get; set; }
public string Name { get; set; }
}
And then I create two lists of objects:
var locations = new List<Location>()
{
new Location()
{
Address = "AA"
},
new Location()
{
Address = "BB"
},
new Location()
{
Address = "CC"
},
new Location()
{
Address = "BB"
}
};
var people = new List<Person>()
{
new Person()
{
Address = "BB",
Name = "Foo"
},
new Person()
{
Address = "CC",
Name = "Bar"
},
new Person()
{
Address = "AA",
Name = "xxx"
},
new Person()
{
Address = "BB",
Name = "yyy"
},
};
What I want is to sort the people list by matching Address property in the locations list. This is the result I would like to have:
xxx
Foo
Bar
yyy
I tried with this code:
var orderedPeopleList = people.OrderBy(p => locations.FindIndex(l => l.Address.Equals(p.Address)));
But it is not working correctly and the two last lines are in the wrong order. What is the best way to do this ordering with linq?
var orderedPeopleList = new List<Person>();
foreach (var location in locations)
{
var foundPeople = people.Where(p => p.Address == location.Address).FirstOrDefault();
if (foundPeople != null)
{
orderedPeopleList.Add(foundPeople);
people.Remove(foundPeople);
}
}
just do it :
locations= locations.OrderBy(x => x.Address).ToList();
var orderedPeopleList=new List<Person>();
for (var i = 0; i < locations.Count(); i++)
{
peopelOrderedList.Add(people.FirstOrDefault(x => x.Address == locations[i].Address && peopelOrderedList.All(c => c.Name != x.Name)));
}
peopelOrderedList.RemoveAll(x => x == null);

Can I use LINQ GroupBy to do this more cleanly?

In this contrived example, which closely resembles my real-world problem, I have a data set coming from an external source. Each record from the external source takes the following form:
[Classification] NVARCHAR(32),
[Rank] INT,
[Data] NVARCHAR(1024)
I am looking to build an object where the Rank and Data are patched into a single instance of a response object that contains list properties for the three hard-coded Classification values, ordered by Rank.
I have something that works, but I can't help but think that it could be done better. This is what I have:
public static void Main()
{
IEnumerable<GroupingTestRecord> records = new List<GroupingTestRecord>
{
new GroupingTestRecord { Classification = "A", Rank = 1, Data = "A1" },
new GroupingTestRecord { Classification = "A", Rank = 2, Data = "A2" },
new GroupingTestRecord { Classification = "A", Rank = 3, Data = "A3" },
new GroupingTestRecord { Classification = "B", Rank = 1, Data = "B1" },
new GroupingTestRecord { Classification = "B", Rank = 2, Data = "B2" },
new GroupingTestRecord { Classification = "B", Rank = 3, Data = "B3" },
new GroupingTestRecord { Classification = "C", Rank = 1, Data = "C1" },
new GroupingTestRecord { Classification = "C", Rank = 2, Data = "C2" },
new GroupingTestRecord { Classification = "C", Rank = 3, Data = "C3" },
};
GroupTestResult r = new GroupTestResult
{
A = records.Where(i => i.Classification == "A").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
B = records.Where(i => i.Classification == "B").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
C = records.Where(i => i.Classification == "C").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
};
The source record DTO:
public class GroupingTestRecord
{
public string Classification { get; set; }
public int? Rank { get; set; }
public string Data { get; set; }
}
The destination single class:
public class GroupTestResult
{
public IEnumerable<GroupTestResultItem> A { get; set; }
public IEnumerable<GroupTestResultItem> B { get; set; }
public IEnumerable<GroupTestResultItem> C { get; set; }
}
The distination child class:
public class GroupTestResultItem
{
public int? Rank { get; set; }
public string Data { get; set; }
}
Ouput
{
"A":[
{
"Rank":1,
"Data":"A1"
},
{
"Rank":2,
"Data":"A2"
},
{
"Rank":3,
"Data":"A3"
}
],
"B":[
{
"Rank":1,
"Data":"B1"
},
{
"Rank":2,
"Data":"B2"
},
{
"Rank":3,
"Data":"B3"
}
],
"C":[
{
"Rank":1,
"Data":"C1"
},
{
"Rank":2,
"Data":"C2"
},
{
"Rank":3,
"Data":"C3"
}
]
}
Fiddle
Is there a better way to achieve my goal here?
The same JSON output was achieved using GroupBy first on the Classification and applying ToDictionary on the resulting IGrouping<string, GroupingTestRecord>.Key
var r = records
.GroupBy(_ => _.Classification)
.ToDictionary(
k => k.Key,
v => v.Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank).ToArray()
);
var json = JsonConvert.SerializeObject(r);
Console.WriteLine(json);
which should easily deserialize to the destination single class (for example on a client)
var result = JsonConvert.DeserializeObject<GroupTestResult>(json);
is it possible to get the top level result into a GroupTestResult object?
Build the result from the dictionary
var result = new GroupTestResult {
A = r.ContainsKey("A") ? r["A"] : Enumerable.Empty<GroupTestResultItem>();,
B = r.ContainsKey("B") ? r["B"] : Enumerable.Empty<GroupTestResultItem>();,
C = r.ContainsKey("C") ? r["C"] : Enumerable.Empty<GroupTestResultItem>();,
};
Or this
var result = records.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, x => x.Select(y => new {y.Rank, y.Data})
.OrderBy(y => y.Rank));
Console.WriteLine(JsonConvert.SerializeObject(result));
Full Demo Here

The most frequently occurring item in a list

I have a list in this table
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
I want to know what are the most frequent fruit in this table what is the code that appears to me this result
I am use
var max = db.Fruits.Max();
There is an error in that?
Try
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
var Val = fruitList.GroupBy(x => x.ID,
(key, y) => y.MaxBy(x => x.ID).value)
As Drew said in the comments, you want to GroupBy on the value that you care about (I did Name, since ID tends to be unique in most data structures) and then OrderByDescending based on the count.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var fruits = new List<Fruit> { new Fruit { ID = 1, Name = "Apple" }, new Fruit { ID = 2, Name = "Apple" }, new Fruit { ID = 3, Name = "Pear" } };
var most = fruits.GroupBy(f => f.Name).OrderByDescending(group => group.Count());
Console.WriteLine(most.First().Key);
}
}
public class Fruit
{
public int ID { get; set; }
public string Name{ get; set; }
}
If you want to get the name of the item that exists most in your list, first find the id that is most occurring:
var fruitAnon = fruits
.GroupBy(item => item.ID)
.Select(item => new {
Key = item.Key,
Count = item.Count()
})
.OrderByDescending(item => item.Count)
.FirstOrDefault();
This will return an anonymous object that will have the most frequent id, and the count represents the number of times it exists in the list. You can then find that object's name:
var fruit = fruits.FirstOrDefault(x => x.ID == fruitAnon.Key);
If you had a list like this:
List<Fruits> fruits = new List<Fruits>() {
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" }
};
Then:
Console.WriteLine(fruit.Name);
Would print Orange.

Map value to item from list and add the new value to the same list C#

I have an Array of colors viz.
var colorPallete = new string[]{color1, color2, color3, color4, color5};
I also have a list of objects which contains an ID.
eg. var previousList<MyModel> = new List<MyModel>();
MyModel.cs
public class MyModel()
{
public int ID {get; set;}
public string Class{get; set;}
public string Name {get; set;}
public string Color {get; set;}
}
I want to assign the objects with same ID with a certain color. And then add the assigned color as a new value to the list.
for eg:
Previous list :-
ID :1
Name: abc
Class: Senior
ID :2
Name: xyz
Class: Medium
ID :3
Name: pqr
Class: junior
ID :1
Name: mno
Class: junior
New List :-
ID :1
Name: abc
Class: Senior
Color :color1
ID :2
Name: xyz
Class: Medium
Color :color2
ID :3
Name: pqr
Class: junior
Color :color3
ID :1
Name: mno
Class: junior
Color :color1
This works for me:
var colorPallete = new string[]
{
"color1", "color2", "color3", "color4", "color5",
};
var previousList = new []
{
new { ID = 1, Name = "abc", Class = "Senior", },
new { ID = 2, Name = "xyz", Class = "Medium", },
new { ID = 3, Name = "pqr", Class = "junior", },
new { ID = 1, Name = "mno", Class = "junior", },
};
var newList =
previousList
.Select(x => new
{
x.ID,
x.Name,
x.Class,
Color = colorPallete.ElementAtOrDefault(x.ID - 1),
})
.ToList();
I get this result:
With the question update providing the class MyModel the code can then be written like so:
var colorPallete = new string[]
{
"color1", "color2", "color3", "color4", "color5",
};
var previousList = new List<MyModel>()
{
new MyModel() { ID = 1, Name = "abc", Class = "Senior", },
new MyModel() { ID = 2, Name = "xyz", Class = "Medium", },
new MyModel() { ID = 3, Name = "pqr", Class = "junior", },
new MyModel() { ID = 1, Name = "mno", Class = "junior", },
};
var newList =
previousList
.Select(x => new MyModel()
{
ID = x.ID,
Name = x.Name,
Class = x.Class,
Color = colorPallete.ElementAtOrDefault(x.ID - 1),
})
.ToList();
Which gives:
Now, this approach produces a new list keeping the old list and the old objects intact. Generally this is what you should try to do. It's best to mutate objects only when you know that's what they're designed to do.
So it becomes possible to do an in-place update of the original list like so:
previousList.ForEach(x => x.Color = colorPallete.ElementAtOrDefault(x.ID - 1));
This results in modifying the previousList objects without creating a newList.
If you are using List<T> (not IEnumerable<T>) and you don't want to create a new list, but need to update values in the existing list instead, you can do it with the single query. There are three ways to process your scenario (A, B, C):
var colorPallete = new string[]
{
"Red", "Green", "Blue"
};
var list = new List<MyModel>()
{
new MyModel() { ID = 1, Name = "model1", Class = "A", },
new MyModel() { ID = 1, Name = "model11", Class = "AA", },
new MyModel() { ID = 2, Name = "model2", Class = "B", },
new MyModel() { ID = 3, Name = "model3", Class = "C", },
new MyModel() { ID = 4, Name = "model4", Class = "D", },
new MyModel() { ID = 5, Name = "model5", Class = "E", },
};
//A. This code assigns null for unknown IDs
//I.e. if (ID > 0 && ID < colorPallete.Length) then color will be picked from colorPallete[],
//else it will be null
list.ForEach(x => x.Color = colorPallete.ElementAtOrDefault(x.ID - 1));
//B. This code apply some default color for unknown IDs
//I.e. if (ID > 0 && ID < colorPallete.Length) then color will be picked from colorPallete,
//else it will be "DefaultColor"
list.ForEach(x => x.Color = colorPallete.ElementAtOrDefault(x.ID - 1) ?? "DefaultColor");
//C. This code can assign the same color to models with different IDs,
//but models with identical IDs always will have identical color
list.ForEach(x => x.Color = colorPallete.ElementAtOrDefault((x.ID - 1) % colorPallete.Length));
I would create a class for the objects with a color property like this:
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public string Class { get; set; }
public string Color { get; set; } // Nullable
}
And for the colors I would create another class with an ID to compare with the ID of MyClass:
public class MyColor
{
public int ID { get; set; }
public string Color { get; set; }
}
For each color in colorPalette you would assign an ID that matches the ID of the list of MyClass.
So at first the color from MyClass would be null. And then you could loop over the list of MyClass:
foreach (MyClass myClass in myClassList)
{
myClass.Color = colorPalette.FirstOrDefault(col => col.ID = myClass.ID);
}
Or without an ID in Color class (comparing the names of the variables which is not a beautiful solution):
foreach (MyClass myClass in myClassList)
{
myClass.Color = colorPalette.FirstOrDefault(col => int.Parse(nameof(col.Color).Replace("color", "")) == myClass.ID);
}

Linq to return a new object with a selection from the list

I have the following object structure:
public class A
{
public string ID { get; set; }
public IList<B> Values { get; set; }
}
public class B
{
public string Code { get; set; }
public string DisplayName { get; set; }
}
public List<A> IDs;
I would like to use Linq to query B and return a single instance of A with the single element of B in values. Is that possible? I currently do this with a foreach but I am thinking Linq would be neater.
foreach (A a in IDs)
{
foreach (B b in a.Values)
{
if (b.Code == code)
{
return (new A()
{
ID = a.ID,
Values = new List<B>()
{
new B()
{
Code = b.Code,
DisplayName = b.DisplayName
}
}
});
}
}
}
Try this:
IDs.Where(a=>a.ID = id)
.Select(a => new A()
{
ID = a.ID,
Values = new List<B>()
{
new B()
{
Code = a.Values.First().Code,
DisplayName = a.Values.First().DisplayName
}
}
});
In LINQ with the query-syntax:
return (from a in IDs
from b in a.Values
where b.Code == code
select (new A
{
ID = a.ID, Values = new List<B>
{
new B
{
Code = b.Code,
DisplayName = b.DisplayName
}
}
})).FirstOrDefault();
Run the following in LinqPad (LinqPad.com)
void Main()
{
List<A> IDs= new List<A>() {
new A() { ID = "1", Values = new List<B>() {
new B { Code = "1", DisplayName = "1"},
new B { Code = "2", DisplayName = "2"},
new B { Code = "3", DisplayName = "3"} } },
new A() { ID = "4", Values = new List<B>() {
new B { Code = "4", DisplayName = "4"},
new B { Code = "5", DisplayName = "5"},
new B { Code = "6", DisplayName = "6"} } },
new A() { ID = "7", Values = new List<B>() {
new B { Code = "7", DisplayName = "7"},
new B { Code = "8", DisplayName = "8"},
new B { Code = "9", DisplayName = "9"} } }
};
A result = IDs.Where(a => a.Values.Any(b=> b.Code == "4")).FirstOrDefault();
result.Dump();
result = IDs.FirstOrDefault(a => a.Values.Any(b=> b.Code == "8"));
result.Dump();
}
// Define other methods and classes here
public class A
{
public string ID { get; set; }
public IList<B> Values { get; set; }
}
public class B
{
public string Code { get; set; }
public string DisplayName { get; set; }
}
You get this:
Prior edits follow:
With the edit to the question:
A result = IDs.Where(a => a.Values.Any(b=> b.Code == code)).FirstOrDefault();
Original answer below
The following will return the first A element where ID = id
A result = IDs.Where(a => a.ID == id).FirstOrDefault();
This makes it a list
List<A> result = IDs.Where(a => a.ID == id).FirstOrDefault().ToList();

Categories