Reputation: 83
The goal : you have a list of objects, you want the end user to be able to choose some objects properties to define a sort and choose sort direction for each property.
Let's take this Node object as an example:
public class Node
{
public int CustomProperty { get; set; }
public string CustomStringProperty { get; set; }
public string id;
public string parentId;
public string Speed;
public int WheelNumber;
public int SeatNumber;
public List<Node> Child { get; set; }
public Node()
{
Child = new List<Node>();
}
}
Let's say you have a list of Node you want to sort based on criterias such as speed, wheelnumber or seat number.
You will use whatever .OrderBy, .ThenByDescending and so on based on goal but the problem is it's hard coded so it's the developer who is currently managing code; the end user won't dive into code, even less each time he needs to change the criterias collection for sorting and define direction for each criteria (object property).
You could use some code helping the user to define preferences, something like this :
public static List<Node> Sort(List<Node> nodes, int level, string[] sortingPreferences)
{
// Recursively sort nodes children
foreach (var node in nodes)
node.Child = Sort(node.Child, level + 1, sortingPreferences);
// Now sort the nodes based on preference
if (nodes.Count < 2) return nodes;
if (level < sortingPreferences.Length)
{
switch (sortingPreferences[level])
{
case "SPEED": return nodes.OrderBy(node => node.Speed).ToList();
case "WHEEL_NUMBER": return nodes.OrderBy(node => node.WheelNumber).ToList();
case "SEAT_NUMBER": return nodes.OrderBy(node => node.SeatNumber).ToList();
case "SPEED - WHEEL_NUMBER": return nodes.OrderBy(node => node.Speed).ThenBy(node => node.WheelNumber).ToList();
case "SPEED - WHEEL_NUMBER - SEAT_NUMBER": return nodes.OrderBy(node => node.Speed).ThenBy(node => node.WheelNumber).ThenByDescending(node => node.SeatNumber).ToList();
// And so on...
// And so on...
}
}
// Unchanged (or nodes.OrderBy(some default order)
return nodes;
}
This code example is just here to illustrate the idiot approach in there, ie you won't cover all criterias combinations to define your multisort to apply on a list of objects and then extend this combination process to cover also directions for each of the criterias used.
The question is : How to define some sort settings/preferences in which criterias (speed, wheel number, seat number...) would be applied, along with their related sort direction, on a list of object to multisort it ?
I mean, for example, preferences could be given this way :
new List<string>[]
{
new List<string>{ "SPEED", "ASCENDING" },
new List<string>{ "WHEEL_NUMBER", "DESCENDING" },
new List<string>{ "SEAT_NUMBER", "DESCENDING" },
},
Here you have 3 criterias but the end user, through a gui, could add some more and choose direction for each of them.
Then, how to take these preferences into account to apply them on the list of objects?
Upvotes: 0
Views: 285
Reputation: 52240
Create a dictionary of delegates to supply your query with OrderBy clauses. Each delegate must accept an object and return the value to sort on.
var map = new Dictionary<string,Func<Node,IComparable>>
{
{ "PropertyA", node => node.PropertyA },
{ "PropertyB", node => node.PropertyB }
};
Then put your sorting keys into an array as well:
var sortBy = new string[] { "PropertyA", "PropertyB" };
Once you have that in order, you can sort with a simple loop by looking up the delegate for each key. You have to reverse the order of keys, since the each key processed will take precedence over the previous keys.
foreach (var sortKey in sortBy.Reverse())
{
list = list.OrderBy( map[sortKey] );
}
Here's how it looks in context with test data:
var map = new Dictionary<string,Func<Node,IComparable>>
{
{ "PropertyA", node => node.PropertyA },
{ "PropertyB", node => node.PropertyB }
};
IEnumerable<Node> list = new Node[]
{
new Node { PropertyA = 1, PropertyB = "Z" },
new Node { PropertyA = 2, PropertyB = "A" },
new Node { PropertyA = 2, PropertyB = "B" }
};
var sortBy = new string[] { "PropertyA", "PropertyB" };
foreach (var sortKey in sortBy.Reverse())
{
list = list.OrderBy( map[sortKey] );
}
foreach (var node in list)
{
Console.WriteLine("{0} {1}", node.PropertyA, node.PropertyB);
}
Output:
1 Z
2 A
2 B
Working example on DotNetFiddle
Upvotes: 2
Reputation: 101
Wrap your OrderBy
calls into ICommands
(command pattern). First you build a commands queue where each command returns a collection, then you loop over the commands queue. If your input changes the commandslist
has to be rebuilt.
You can use a fifo queue to store the commands and when an command should be removed you just dequeue until you find the command to be removed while keeping all dequeued commands to then enqueue them minus the removed command back to the queue.
Edit: you can use c# Func for the commands and a queue of keyvaluepair sorting enum and Func.
Func<List<T>,List<T>>
for example and Queue<KeyValuePair<SortEnum,Func<List<T>,List<T>>>
Upvotes: 0