Sort list by eventually existing value - c#

I have to sort a given list by a value which may or may not be given inside another list of each element. If no value is given, this element should appear at the bottom of the sorted list.
Here's a short example: I want to sort the list Items based on the Value of the Property which has the name foo
public class Item {
public string Name { get; set; }
public List<Property> Properties { get; set; }
}
public class Property {
public string Name { get; set; }
public int Value { get; set; }
}
List<Item> items = new List<Item>() {
new Item() {
Name = "Item 1",
Properties = new List<Property>() {
new Property {
Name = "foo",
Value = 5
},
new Property {
Name = "bar",
Value = 7
}
}
},
new Item() {
Name = "Item 2",
Properties = new List<Property>() {
new Property {
Name = "bar",
Value = 2
}
}
},
new Item() {
Name = "Item 3",
Properties = new List<Property>() {
new Property {
Name = "foo",
Value = 1
}
}
}
The sorted list should contain the Items in the order Item 1, Item 3, Item 2
I tried this:
items.FetchOrderBy
(
x => x.Properties
.FirstOrDefault
(
y => y.Name = "foo"
)
.Value
)
.ToList();
...But got the following exception: Antlr.Runtime.NoViableAltException

The problem is that when there's no property match, FirstOrDefault returns null. You could get around this by using a null-coalescing operator:
items.FetchOrderBy
(
x => (x.Properties
.FirstOrDefault
(
y => y.Name == "foo"
)
// Send non-matches to the bottom
?? new Property { Value = Int32.MaxValue })
.Value
)
.ToList();

Related

How to get same results from select as foreach when using GroupBy()

I would like to be able to attain the same results that I can get by using foreach on a grouping when using the select method and an anonymous method.
public class ExportData
{
public int Id { get; set; }
public string Colour { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Money { get; set; }
}
public class ExportDataDictionary
{
public IDictionary<string, object> ColumnData { get; set; } = new Dictionary<string, object>();
}
Given the two classes above as an example.
I create some data..
var dataCollection = new List<ExportData>
{
new ExportData { Name = "Name1", Age = 1, Colour = "Blue", Id = 1, Money = 10 },
new ExportData { Name = "Name1", Age = 2, Colour = "Red", Id = 2, Money = 20 },
new ExportData { Name = "Name1", Age = 2, Colour = "Green", Id = 3, Money = 30 },
new ExportData { Name = "Name2", Age = 1, Colour = "Yellow", Id = 4, Money = 40 },
new ExportData { Name = "Name3", Age = 2, Colour = "Blue", Id = 5, Money = 50 },
new ExportData { Name = "Name4", Age = 3, Colour = "Blue", Id = 6, Money = 10 }
};
Next I group this data by, for example, two properties as follows..
var dataGrouping = dataCollection.GroupBy(g => new { g.Name, g.Age });
I then create a list of ExportDataDictionaries and foreach through each group in the grouping, creating a new ExportDataDictionary each time and adding both of the keys to the dictionary.
var data = new List<ExportDataDictionary>();
foreach (var grouping in dataGrouping)
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", grouping.Key.Name);
datadictionary.ColumnData.Add("AGE", grouping.Key.Age);
data.Add(datadictionary);
}
The result is a collection of 5 ExportDataDictionaries with 2 Columns in each one that contain the pair of keys that correspond to each of the groupings.
My attempt to achieve the same with the Select method is shown below.
var data2 = new List<ExportDataDictionary>();
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
data2.Add(datadictionary);
return data2;
});
The result is of the type:
mydata = {System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<<>f__AnonymousType0<string, int>, ConsoleApp2.Program.ExportData>, System.Collections.Generic.List<ConsoleApp2.Program.ExportDataDictionary>>}
and it contains 5 items and each item contains 10 dictionaries. The 5 dictionaries that I expect are there with the same values as when using foreach but then there are 2 copies of each. I believe that this must be because it is creating the dictionaries for both of the keys used in the grouping. So, I am wondering how to only do this for one of the keys or just each group in the collection?
The requirement is that mydata should contain the same result as obtained by foreach in data variable
Any help much appreciated :)
Just Add .ToList() at the end of your last statement remove the data2.Add(datadictionary); statement and only return the datadictionary return datadictionary; like this
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
return datadictionary;
}).ToList();
I have run your code and checked and saw that mydata contains 5 items, and each item contains 2 ColumnData members.
Actually, your Linq query is only executed when you call the .ToList() function

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.

SelectList with SelectListGroup

I'm trying to return a SelectList with <optgroup>'s using SelectListGroup, but the code below only returns the values in the SelectList without the groups. How can I have my select list separated by groups?
public SelectList MyList()
{
var group1 = new SelectListGroup() { Name = "Group 1" };
var group2 = new SelectListGroup() { Name = "Group 2" };
var items = new List<SelectListItem>();
items.Add(new SelectListItem() { Value = "1", Text = "Item 1", Group = group1 });
items.Add(new SelectListItem() { Value = "2", Text = "Item 2", Group = group2 });
return new SelectList(items, "Value", "Text");
}
I find this method works, which uses a strongly-typed approach:
View model:
public class PageViewModel
{
public int SelectedDropdownItem { get; set; }
public IEnumerable<SelectListItem> DropdownList { get; set; }
}
Example entity model (for this example):
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public bool IsDefault { get; set; }
}
Controller:
// Get list for the dropdown (this uses db values)
var entityList = db.Entity.ToList();
// Define the Groups
var group1 = new SelectListGroup { Name = "Active" };
var group2 = new SelectListGroup { Name = "Allowed" };
// Note - the -1 is needed at the end of this - pre-selected value is determined further down
// Note .OrderBy() determines the order in which the groups are displayed in the dropdown
var dropdownList = new SelectList(entityList.Select(item => new SelectListItem
{
Text = item.Name,
Value = item.Id,
// Assign the Group to the item by some appropriate selection method
Group = item.IsActive ? group1 : group2
}).OrderBy(a => a.Group.Name).ToList(), "Value", "Text", "Group.Name", -1);
// Assign values to ViewModel
var viewModel = new PageViewModel
{
// Assign the pre-selected dropdown value by appropriate selction method (if needed)
SelectedDropdownItem = entityList.FirstOrDefault(a => a.IsDefault).Id,
DropdownList = dropdownList
};
View:
<!-- If needed, change 'null' to "Please select item" -->
#Html.DropDownListFor(a => a.SelectedDropdownItem, Model.DropdownList, null, new { #class = "some-class" })
Hope it helps someone.

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);
}

Intersect two generic lists by dynamic properties

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();

Categories