I have some data like
ID Sequence customIndex
1 1 0
2 2 0
3 3 2
4 4 1
5 5 0
I need to use sequence in order by when customIndex is zero other wise use customIndex.
So result should be ID in order of 1,2,4,3,5.
I need LINQ implementation using Lambda. I tried some solution but could not implement.
Posting duplicate and deleting previous one, because of wrong formatting the meaning of question got changed and I received bunch of negative votes.
Added code at dotnet fiddle:
https://stable.dotnetfiddle.net/fChl40
The answer is based on assumption, that CustomIndex is greater or equals to zero:
var result =
data.OrderBy(x => x.CustomIndex==0 ? x.Sequence :
data.Where(y => y.CustomIndex==0 && y.Sequence < x.Sequence)
.Max(y => (int?)y.Sequence))
.ThenBy(x => x.CustomIndex);
This is working for provided test data:
l.OrderBy(a => a.customIndex != 0 ?
list.Where(b => b.Sequence < a.Sequence && b.customIndex == 0)
.OrderByDescending(c => c.Sequence)
.FirstOrDefault()
.Sequence : a.Sequence)
.ThenBy(c=>c.customIndex )
.ToList();
The idea is to order non zero values by first preceding zero valued rows, and then by non zero values itself.
This is something I wanted:
public static void Main()
{
List<Data> data = new List<Data>();
data.Add(new Data{ Id=1, Sequence=1, CustomIndex=0});
data.Add(new Data{ Id=5, Sequence=5, CustomIndex=0});
data.Add(new Data{ Id=6, Sequence=6, CustomIndex=2});
data.Add(new Data{ Id=2, Sequence=2, CustomIndex=0});
data.Add(new Data{ Id=3, Sequence=3, CustomIndex=2});
data.Add(new Data{ Id=4, Sequence=4, CustomIndex=1});
data.Add(new Data{ Id=7, Sequence=7, CustomIndex=1});
int o = 0;
var result = data
.OrderBy(x=>x.Sequence).ToList()
.OrderBy((x)=> myCustomSort(x, ref o) )
;
result.ToList().ForEach(x=> Console.WriteLine(x.Id));
}
public static float myCustomSort(Data x, ref int o){
if(x.CustomIndex==0){
o = x.Sequence;
return x.Sequence ;
}
else
return float.Parse(o + "."+ x.CustomIndex);
}
Sample code: https://stable.dotnetfiddle.net/fChl40
I will refine it further
Based on your question and reply to my comment, I understand you need to clusterize the items' collection, then consider Sequence and CustomIndex on all items of each cluster.
Once clustered (split into blocks depending on a specific criterion) you can merge them back into a unique collection, but while doing that you can manipulate each cluster independently the way you need.
public static class extCluster
{
public static IEnumerable<KeyValuePair<bool, T[]>> Clusterize<T>(this IEnumerable<T> self, Func<T, bool> clusterizer)
{
// Prepare temporary data
var bLastCluster = false;
var cluster = new List<T>();
// loop all items
foreach (var item in self)
{
// Compute cluster kind
var bItemCluster = clusterizer(item);
// If last cluster kind is different from current
if (bItemCluster != bLastCluster)
{
// If previous cluster was not empty, return its items
if (cluster.Count > 0)
yield return new KeyValuePair<bool, T[]>(bLastCluster, cluster.ToArray());
// Store new cluster kind and reset items
bLastCluster = bItemCluster;
cluster.Clear();
}
// Add current item to cluster
cluster.Add(item);
}
// If previous cluster was not empty, return its items
if (cluster.Count > 0)
yield return new KeyValuePair<bool, T[]>(bLastCluster, cluster.ToArray());
}
}
// sample
static class Program
{
public class Item
{
public Item(int id, int sequence, int _customIndex)
{
ID = id; Sequence = sequence; customIndex = _customIndex;
}
public int ID, Sequence, customIndex;
}
[STAThread]
static void Main(string[] args)
{
var aItems = new[]
{
new Item(1, 1, 0),
new Item(2, 2, 0),
new Item(3, 3, 2),
new Item(4, 4, 1),
new Item(5, 5, 0)
};
// Split items into clusters
var aClusters = aItems.Clusterize(item => item.customIndex != 0);
// Explode clusters and sort their items
var result = aClusters
.SelectMany(cluster => cluster.Key
? cluster.Value.OrderBy(item => item.customIndex)
: cluster.Value.OrderBy(item => item.Sequence));
}
}
It ain't pretty, but it exemplifies what you were asking for, I think:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Data> data = new List<Data>();
data.Add(new Data { Id = 1, Sequence = 1, CustomIndex = 0 });
data.Add(new Data { Id = 2, Sequence = 2, CustomIndex = 0 });
data.Add(new Data { Id = 3, Sequence = 3, CustomIndex = 2 });
data.Add(new Data { Id = 4, Sequence = 4, CustomIndex = 1 });
data.Add(new Data { Id = 5, Sequence = 5, CustomIndex = 0 });
//List of items where the sequence is what counts
var itemsToPlaceBySequence = data.Where(x => x.CustomIndex == 0).OrderBy(x => x.Sequence).ToList();
//List of items where the custom index counts
var itemsToPlaceByCustomIndex = data.Where(x => x.CustomIndex != 0).OrderBy(x => x.CustomIndex).ToList();
//Array to hold items
var dataSlots = new Data[data.Count];
//Place items by sequence
foreach(var dataBySequence in itemsToPlaceBySequence) {
dataSlots[dataBySequence.Sequence - 1] = dataBySequence ;
}
//Find empty data slots and place remaining items in CustomIndex order
foreach (var dataByCustom in itemsToPlaceByCustomIndex) {
var index = dataSlots.ToList().IndexOf(null);
dataSlots[index] = dataByCustom ;
}
var result = dataSlots.ToList();
result.ForEach(x => Console.WriteLine(x.Id));
var discard = Console.ReadKey();
}
public class Data
{
public int Id { get; set; }
public int Sequence { get; set; }
public int CustomIndex { get; set; }
}
}
The ordering you want to do (order partly on CustomIndex and partly on Sequence) doesn't work like that. But this should be close to what you want. Order first by CustomIndex, and then by Sequence.
var result = data.OrderBy(x => x.CustomIndex).ThenBy(x => x.Sequence);
Related
So I have a list of prices from a database. I would like to sort it so that the first entry in a list is the entry with the lowest number. And then all other entry are order by input date.
How can this be done?
This is my code, which is a mess, sorry I'm trying stuff :)
var itemPriceDate = itemPrice.OrderBy(d => d.Invoice.DateInvoice).ToList();
var itemPriceDateLow= itemPriceDate.OrderBy(c => c.qtPrice).ThenBy(d => d.Invoice.DateInvoice);
ViewBag.ItemPrice = itemPriceDateLow; ```
First find out the lowest value from the List(itemPrice).
double lowest_price = itemPrice.Min(c => c.qtPrice);
Next, remove the lowest element from the list.
var itemToRemove = itemPrice.Single(c => c.qtPrice == lowest_price);
itemPrice.Remove(itemToRemove);
Next, sort the remaining list based on input Date.
var newList = itemPrice.OrderByDescending(d => d.Invoice.DateInvoice).ToList();
Finally, add lowest element at first index
newList.Insert(0, lowest_price);
LINQ is great when it works, but it sometimes does unexpected things. Depending on how large your dataset is, you may be better off doing it as a stored procedure that returns the data already ordered.
If the dataset is small or you're cornered into using C# to do it there is the option of using a custom sort function. Without knowing the exact structure of your data, this is more intended as a blanket example that will need tweaking accordingly.
Let's say your list is stored in the itemPrice variable, if you do something along the lines of:
itemPrice.Sort((a, b) => {
int retVal = a.qtPrice < b.qtPrice;
return ret != 0 ? ret : a.Invoice.DateInvoice < b.Invoice.DateInvoice;
});
Will sort by qtPrice and then fall back to the DateInvoice field; you may need to swap the less than to a greater than to get your desired order.
One sort is enough. What I think it should be is:
var itemPriceDateLow= itemPriceDate.OrderBy(c => c.qtPrice).ThenBy(d => d.Invoice.DateInvoice);
This will obviously give you whole collection. You might want to use .First() if you want to get top most element.
One thing to remember - ThenBy, OrderBy are ascending by default.
Take a look at this example:
class Program
{
static void Main(string[] args)
{
List<ItemPrice> items = new List<ItemPrice>();
items.Add(new ItemPrice() { Date = DateTime.Now, QtyPrice = 1});
items.Add(new ItemPrice() { Date = DateTime.Now.AddDays(-1), QtyPrice = 1});
items.Add(new ItemPrice() { Date = DateTime.Now, QtyPrice = 2});
var sortedItem = items.OrderBy(p => p.QtyPrice).ThenBy(p => p.Date).First();
Console.WriteLine($"Default Ascending sort {sortedItem.Date}, {sortedItem.QtyPrice}");
var sortedItemWithReverseDate = items.OrderBy(p => p.QtyPrice).ThenByDescending(p => p.Date).First();
Console.WriteLine($"Descending sort on date {sortedItemWithReverseDate.Date}, {sortedItemWithReverseDate.QtyPrice}");
}
}
class ItemPrice {
public DateTime Date { get; set; }
public decimal QtyPrice { get; set; }
}
It will give you:
Default Ascending sort 16/08/2021 12:47:34, 1
Descending sort on date 17/08/2021 12:47:34, 1
You would need to iterate the collection twice in this case, since you would first need to know the Aggregate Value (Min). Then, you could use a Custom Comparer as the following.
public class CustomComparer : IComparer<Item>
{
private int _minValue;
public CustomComparer(int minValue)
{
_minValue= minValue;
}
public int Compare(Item instanceA, Item instanceB)
{
if(instanceA.Price == _minValue) return -1;
if(instanceB.Price == _minValue) return 1;
return instanceA.InputDate.CompareTo(instanceB.InputDate);
}
}
Now you can fetch the result as
var min = list.Min(x=>x.Price);
var result = list.OrderBy(x=>x,new CustomComparer(min));
Example,
public class Item
{
public int Price{get;set;}
public DateTime InputDate{get;set;}
}
var list = new List<Item>
{
new Item{Price = 2, InputDate=new DateTime(2021,3,1)},
new Item{Price = 12, InputDate=new DateTime(2021,7,1)},
new Item{Price = 12, InputDate=new DateTime(2021,9,1)},
new Item{Price = 42, InputDate=new DateTime(2021,1,1)},
new Item{Price = 32, InputDate=new DateTime(2021,6,1)},
new Item{Price = 22, InputDate=new DateTime(2021,4,1)},
new Item{Price = 2, InputDate=new DateTime(2021,3,2)},
new Item{Price = 12, InputDate=new DateTime(2021,2,1)}
};
var min = list.Min(x=>x.Price);
var result = list.OrderBy(x=>x,new CustomComparer(min));
Output
Thx for all your inputs.
For me the right way to go was.
Order my "itemPrice" list by "OrderByDescending(by date)"
Then find out the lowest value from the List(itemPrice).
double lowest_price = itemPrice.Min(c => c.qtPrice);
Then declare a new List
List<qtInvoice> newItemPrice = new List<qtInvoice>();
First loop that adds all the "lowest_price" to "newItemPrice" list
foreach (var item in itemPriceDate)
{
if (item.qtPrice == lowest_price)
{
newItemPrice.Add(item);
}
}
Then in second loop you add all the rest of the prices to "newItemPrice" list
foreach (var item in itemPriceDate)
{
if (item.qtPrice != lowest_price)
{
newItemPrice.Add(item);
}
}
class Program
{
static void Main(string[] args)
{
int[] arr1 = { 1, 2, 3, 4 };
int[] arr2 = { 1, 2, 3 };
int[] arr3 = { 1, 2, 3 };
int[] arr4 = { 1, 2, 3, 4 };
int[] arr5 = { 1, 2, 3, 4 };
int[] arr6 = { 1, 2, 3, 4, 5 };
Order Order1 = new Order { OrderId = 1, Deatils = arr1 };
Order Order2 = new Order { OrderId = 2, Deatils = arr2 };
Order Order3 = new Order { OrderId = 3, Deatils = arr3 };
Order Order4 = new Order { OrderId = 4, Deatils = arr4 };
Order Order5 = new Order { OrderId = 5, Deatils = arr5 };
Order Order6 = new Order { OrderId = 6, Deatils = arr6 };
// I want to Output like this based on same values in details object.
string similarOrderDetailsOrderIds_1 = "1,4,5";
string similarOrderDetailsOrderIds_2 = "2,3";
string similarOrderDetailsOrderIds_3 = "5";
}
}
public class Order
{
public int OrderId { get; set; }
public int[] Deatils { get; set; }
}
I want to output like this because OrderId-1,4,5 have the same values in Details.
string similarOrderDetailsOrderIds_1 = "1,4,5";
string similarOrderDetailsOrderIds_2 = "2,3";
string similarOrderDetailsOrderIds_3 = "5";
This extension method is a generic generalization of the solution provided by Michael Liu here which orders elements prior to hash code generation and will work for any T which itself correctly implements GetHashCode() and Equals():
static class Extensions
{
public static int GetCollectionHashCode<T>(this IEnumerable<T> e)
{
return ((IStructuralEquatable)e.OrderByDescending(x => x).ToArray())
.GetHashCode(EqualityComparer<T>.Default);
}
}
You can utilize it like so:
var orders = new[] { Order1, Order2, Order3, Order4, Order5, Order6 };
var groups = orders
.Where(x => x.Deatils != null)
.GroupBy(x => x.Deatils.GetCollectionHashCode())
.Select(x => x.Select(y => y.OrderId));
Note: Deatils typo above is intentional as per OP.
You could use LINQ as the other answer provided or create a helper method to group them for you.
public static IEnumerable<IEnumerable<T>> Aggregate<T>(IEnumerable<T> ungrouped, Func<T, T, bool> Expression)
{
// create a place to put the result
List<List<T>> groupedItems = new();
// create a mutable storage to keep track which ones we shouldn't compare
List<T> mutableBag = ungrouped.ToArray().ToList();
// n^2 isn't great but it was easy to implement, consider creating a hash from all elements and use those instead of manual comparisons
foreach (var left in ungrouped)
{
// create a place to store any similar items
List<T> similarItems = new();
// compare element to every remaining element, if they are similar add the other to the similar list
foreach (var right in mutableBag)
{
if (Expression(left, right))
{
similarItems.Add(right);
}
}
// if we didn't find any similar items continue and let GC collect similar items
if (similarItems.Count == 0)
{
continue;
}
// since we found items remove the from the mutable bag so we don't have duplicate entries
foreach (var item in similarItems)
{
mutableBag.Remove(item);
}
// add the similar items to the result
groupedItems.Add(similarItems);
}
return groupedItems;
}
public static bool Compare<T>(T[] Left, T[] Right)
{
// this compare method could be anything I would advise against doing member comparison like this, this is simplified to provide how to use the Aggregate method and what the CompareMethod might look like for comlpex types
if (Left.Length != Right.Length)
{
return false;
}
foreach (var item in Left)
{
if (Right.Contains(item) is false)
{
return false;
}
}
return true;
}
For the given example
Order Order1 = new Order { OrderId = 1, Deatils = arr1 };
Order Order2 = new Order { OrderId = 2, Deatils = arr2 };
Order Order3 = new Order { OrderId = 3, Deatils = arr3 };
Order Order4 = new Order { OrderId = 4, Deatils = arr4 };
Order Order5 = new Order { OrderId = 5, Deatils = arr5 };
Order Order6 = new Order { OrderId = 6, Deatils = arr6 };
Order[] arrs = { Order1, Order2, Order3, Order4, Order5, Order6 };
We could use this new helper method like this:
var groupedItems = Aggregate(arrs, (left, right) => Compare(left.Deatils, right.Deatils));
I have a very large in memory List, I'm looking for the most efficient algorithm to take a list of Items, and finding all of parents when child is provided.
List<Data> elements = new List<Data>
{
new Data {Id = 1, ParentId = null },
new Data {Id = 2, ParentId = 1},
new Data {Id = 3, ParentId = 2},
new Data {Id = 4, ParentId = 3}
};
var parents =
elements
.Where(x => x.ParentId != null)
.ToDictionary(x => x.Id, x => x.ParentId.Value);
IEnumerable<int> GetParents(int i) =>
parents.ContainsKey(i)
? new[] { parents[i] }.Concat(GetParents(parents[i]))
: Enumerable.Empty<int>();
var result = GetParents(3); //1,2
This works fine, but Its not efficient way.
How can the code be rewritten so that no recursive calls to Execute are made?
An non recursive solution is pretty straightforward:
var currentId = i;
while (parents.TryGetValue(currentId, out var parentId))
{
yield return parentId;
currentId = parentId;
}
Am I missing something?
As you know any recursive approach is not a good choice where you are dealing with a big amount of data. Because of recursive calls using heap stack and after a while, you will get StackOverFlowException. So just as you asked I provided a simple-designed non-recursive implementation for your question.
** In this sample, I'm getting deep into the hierarchy just for 7000 levels down because the recursive approach raises StackOverFlowException for more than that.
** The non-recursive approach including the first node which has null value as ParendId.
** The non-recursive execution time is much better than the recursive one.
public class Data
{
public int Id { get; set; }
public int? ParentId { get; set; }
}
static List<Data> elements = new List<Data>();
static void Main(string[] args)
{
//To fill up the list with huge number if items
elements.Add(new Data() { Id = 1, ParentId = null });
Enumerable.Range(2, 1000000).ToList().ForEach(x => elements.Add(new Data { Id = x, ParentId = x - 1 }));
//Making dictionary as you did it
var parents =elements.ToDictionary(x => x.Id, x => x.ParentId);
/*Non-Recursive Approach*/
IEnumerable<int?> GetNonRecursiveParents(int i)
{
List<int?> parentsList = new List<int?>();
if (parents.ContainsKey(i))
{
var parentNode = parents[i];
do
{
parentsList.Add(parentNode);
parentNode = parents[parentNode.Value];
}
while (parentNode != null);
}
return parentsList;
};
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var r = GetNonRecursiveParents(7000);
stopwatch.Stop();
var elapsed1 = stopwatch.Elapsed;// Execution time: 00:00:00.0023625
//Making dictionary as you did it
parents = elements.Where(x => x.ParentId != null).ToDictionary(x => x.Id, x => x.ParentId);
/*Recursive Approach*/
IEnumerable<int?> GetParents(int i) =>
parents.ContainsKey(i)
? new[] { parents[i] }.Concat(GetParents(parents[i].Value))
: Enumerable.Empty<int?>();
stopwatch.Restart();
var result = GetParents(7000);
stopwatch.Stop();
var elapsed2= stopwatch.Elapsed;// Execution time: 00:00:00.0040636
}
We retrieve a list of objects from a database and cannot rely on the Id order to guarantee they will be in the right sequence, as objects may have been edited, deleted etc.
They look like this:
Id NextId
1 3
2 0
3 17
17 2
So the correct order is 1, 3, 17, 2.
I came up with this code to solve the problem:
long lastStep = steps.Single(x => x.NextId == 0).Id;
//Probably should be a guard clause for nulls
List<MyObject> orderedSteps = new List<MyObject>();
int retries = 0;
do
{
foreach (var entry in steps)
{
if (lastStep == entry.NextId) orderedSteps.Add(entry);
retries++;
}
} while (orderedSteps.Count() < steps.Count() && retries < 10000);
//Flip the order so it runs first to last
orderedSteps.Reverse();
return orderedSteps;
I think this works...but it feels kind of hacky, and that there's a more safe and efficient way of doing it.
Any Suggestions? Thanks!
You could do this directly in the database using a recursive CTE:
WITH SequenceQuery (Id, NextId, Ordering)
AS
(
SELECT Id,
NextId,
0 AS Ordering
FROM Steps
WHERE Id = 1
UNION ALL
SELECT Steps.Id,
Steps.NextId,
SequenceQuery.Ordering + 1 AS Ordering
FROM SequenceQuery INNER JOIN Steps
ON SequenceQuery.NextId = Steps.Id
)
SELECT *
FROM SequenceQuery
ORDER BY Ordering
In the event of a cycle, this will return an error once it hits the maximum recursion depth. The maximum depth is by default 100; if your data set could legitimately have more than 100 elements, you can increase the limit with the following clause (which goes right at the end of the query, after the SELECT statement):
OPTION (MAXRECURSION 1000) -- (for example)
This will be by far the fastest way to get the data back, provided that the Id column is properly indexed.
If you prefer to do it in code, then you'll need to load the entire table into a dictionary beforehand and then walk through it. The advantage to this is that you can explicitly detect cycles instead of depending on a numeric limit to the number of levels.
var steps = ...;
var stepById = steps.ToDictionary(step => step.Id);
var stepsInOrder = new List<int>();
var visited = new HashSet<int>();
// Make sure that when we hit 0, we'll definitely stop.
Debug.Assert(!stepsInOrder.ContainsKey(0));
int currentStepId = 1;
while (stepById.TryGetValue(currentStepId, out Step step))
{
stepsInOrder.Add(currentStepId);
int nextStepId = step.NextId;
if (!visited.Add(nextStepId))
throw new Exception($"Cycle found at step {nextStepId}");
currentStepId = nextStepId;
}
(SQL tested, C# code untested)
Here's my solution. Requires several assumptions to be true: Single chain, terminated with a 0 Id.
public class Item
{
public int Id;
public int NextId;
public override string ToString()
{
return string.Format("Item {0} (links to {1})", Id, NextId);
}
};
class Program
{
static void Main(string[] args)
{
Item[] items = new Item[] {
new Item() { Id = 1, NextId = 3 },
new Item() { Id = 2, NextId = 0 },
new Item() { Id = 3, NextId = 17 },
new Item() { Id = 17, NextId = 2 }
};
Dictionary<int, int> idToIndex = new Dictionary<int, int>();
int headId = 0;
for (int index = 0; index < items.Length; ++index)
{
idToIndex.Add(items[index].Id, index);
headId = headId ^ items[index].NextId ^ items[index].Id;
}
int currentId = headId;
while (currentId != 0)
{
var item = items[idToIndex[currentId]];
Console.WriteLine(item);
currentId = item.NextId;
}
}
}
My suggestion is as follows:
class MyObject
{
public long Id;
public long NextId;
public override string ToString() => Id.ToString();
};
public void q48710242()
{
var items = new[]
{
new MyObject{ Id = 1, NextId = 3 },
new MyObject{ Id = 2, NextId = 0 },
new MyObject{ Id = 3, NextId = 17 },
new MyObject{ Id = 17, NextId = 2 }
};
var nextIdIndex = items.ToDictionary(item => item.NextId);
var orderedSteps = new List<MyObject>();
var currentStep = new MyObject() { Id = 0 };
while (nextIdIndex.TryGetValue(currentStep.Id, out currentStep))
{
orderedSteps.Add(currentStep);
}
orderedSteps.Reverse();
var output = string.Join(", ", orderedSteps);
}
Returns:
output = "1, 3, 17, 2"
This uses a dictionary to build an index of the items as in Jonathan's answer, but by using NextId as the key. The algorithm then proceeds backwards from the 0 as in the original question to build the list in reverse. This approach has no problems with loops in the data as any such loop will never be entered assuming that Id is unique.
If the data contains multiple elements with the same NextId, then it forms a tree structure:
var items = new[]
{
new { Id = 1, NextId = 3 },
new { Id = 2, NextId = 0 },
new { Id = 3, NextId = 17 },
new { Id = 17, NextId = 2 },
new { Id = 100, NextId = 2 }
};
This will cause the .ToDictionary() call to fail with System.ArgumentException: An item with the same key has already been added.
If the data contains no entries with a NextId equal to 0, it will return an empty list.
Update Changed to return a list of objects rather than the indices.
Hope this helps
In C#,I have List of Employee object. Employee class is
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
In List objected are sorted based on Employee.ID. I have an array of int which is basically Employee.ID which I want on top of the list and in list,order must remain same as in array.
If I hava input like this
List:
[
{ID:1,Name:A},
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
and Array: {2,3,1}
Then I want Output List:
[
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:1,Name:A},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
And I have done this
foreach (int i in a)
{
list = list.OrderBy(x => x.ID != i).ToList();
}
//a is array
//list is List
Any better Solution.Thanks in advance.
After you got your list sorted based on the ID just iterate the array and move the elements. In order to do this you need to first remove and then insert the item at the correct position.
for(int i = 0; i < myArray.Length; i++)
{
var e = myList.Single(x => x.Id == myArray[i]);
myList.Remove(e);
myList.Insert(i, e);
}
You may also want to use SingleOrDefault instead of Single to verify that myList even contains the element with the current id, e.g. when your array contains [2, 3, 101]
To add another version to the mix. The complete sorting can be done in one go:
list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? int.MaxValue : i; }).ToList();
where list is the EmployeeList and a the indices array. (NB, the for loop is not needed, the above should do both sortings).
Inside the OrderBy callback, if the id is not inside a, int.MaxValue is returned to place it after the ones inside the array (a.Length would work as well). OrderBy should maintain the original order of the enumeration (list) for those elements that return the same value.
PS, if you want to sort first by index inside a and the rest on the ids (not necessarily the original order), you can use the following (as long as a.Length + largest ID < int.MaxValue) : list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? a.Length + e.ID : i; }).ToList();
Here's a way to do it in pure LINQ, without changing the original sequence.
Broken into steps to see what's going on.
public static void Main()
{
var employeeList = new List<Employee>()
{
new Employee(){ ID= 1,Name= "A"},
new Employee() { ID= 2,Name= "B"},
new Employee() { ID= 3,Name= "AA"},
new Employee() { ID= 4,Name= "C"},
new Employee() { ID= 5,Name= "CD"},
new Employee() { ID= 100,Name= "Z"}
};
var orderByArray = new int[] { 2, 3, 1, 100, 5, 4 };
var sortPos = orderByArray.Select((i, index) => new { ID = i, SortPos = index });
var joinedList = employeeList.Join(sortPos, e => e.ID, sp => sp.ID, (e, sp) => new { ID = e.ID, Name = e.Name, SortPos = sp.SortPos });
var sortedEmployees = joinedList.OrderBy(e => e.SortPos).Select(e => new Employee { ID = e.ID, Name = e.Name });
}
Try this using LINQ:
List<Employee> employees = ...
int[] ids = ...
var orderEmployees = ids.Select(id => employees.Single(employee => employee.ID == id))
.Concat(employees.Where(employee => !ids.Contains(employee.ID)).ToList();
Foreach id in ids array we will grab the matching employee and we will concat to it all the employees that their id does not exist in ids array.
I like to use a special Comparer for that, it seems clearer to me, though a bit more code. It hides the complexity of the sort in the comparer class, and then you can just call it with :
theList.OrderBy(x => x.id, new ListOrderBasedComparer(sortList));
It will sort according to any list passed to the comparer when instantiating, and will put elements not in the "known sort list" at the end.
You can of course adapt it to your special needs.
public class ListOrderBasedComparer: Comparer<int>
{
private List<int> sortList;
public ListOrderBasedComparer(List<int> sortList)
{
// if you want you can make constructor accept arrays and convert it
// (if you find that more convenient)
this.sortList = sortList;
}
public override int Compare(int x, int y)
{
var indexOfX = sortList.FindIndex(a => a == x);
var indexOfY = sortList.FindIndex(a => a == y);
// handle elements not in sortArray : if not in sort array always assume they should be "less than the others" and "equal between them".
if (indexOfX == -1 && indexOfY == -1) return 0;
if (indexOfY == -1) return -1;
if (indexOfX == -1) return 1;
// if elements are in sortArray (FindIndex returned other than -1), use usual comparison of index values
return indexOfX.CompareTo(indexOfY);
}
}
Example on how to use it, with Linq :
public class TestCompare
{
public void test ()
{
var myArray = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var myArray2 = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 0, name = "X" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 23, name = "Z"},
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var sortList = new List<int> { 2, 3, 1, 4, 5, 6 };
// good order
var mySortedArray = myArray.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
// good order with elem id 0 and 23 at the end
var mySortedArray2 = myArray2.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
}
}
public class MyClass
{
public int id;
public string name;
}