I have a multi-threaded application that has to perform 3 different categories of work items. Category A is the highest priority items, Category B items comes after A and Category C items comes after B. These work items are queued to thread pool using Tasks. Let's say, there are 10 category C items already in queue and then a category B item is added. In this case, I would like category B item to be processed before any of the category C item. Is there anyway of accomplishing this?
You can implement it by creating your own queue process. This is just a code mockup.
Create an object like this
public class PrioritizableTask
{
public PrioritizableTask(Task task, int taskPriority)
{
Task = task;
Priority = taskPriority;
}
public int Priority { get; private set; }
public Task Task { get; private set; }
}
And then create another collection class and implement a new method on it, something like this.
public class PrioritizableTasksCollection : IList<PrioritizableTask>
{
private static readonly List<PrioritizableTask> runners = new List<PrioritizableTask>();
public void Add(PrioritizableTask item)
{
runners.Add(item);
}
public Task GetNextPriority()
{
var priorityTask = runners.OrderBy(x => x.Priority).FirstOrDefault();
return priorityTask != null ? priorityTask.Task : null;
}
}
Consume like
PrioritizableTasksCollection executors = new PrioritizableTasksCollection();
executors.Add(new PrioritizableTask(new Task(() => { }), 4));
executors.Add(new PrioritizableTask(new Task(() => { }), 3));
executors.Add(new PrioritizableTask(new Task(() => { }), 7));
executors.Add(new PrioritizableTask(new Task(() => { }), 5));
executors.Add(new PrioritizableTask(new Task(() => { }), 1));
executors.Add(new PrioritizableTask(new Task(() => { }), 2));
Task executeNext = executors.GetNextPriority();
Implement your own deleting on the collection.
I've been looking at your problem and i did not find a built-in thread-safe sorted collection.
So i built a basic thread-safe SortedSet<int> wrapper class.
Sorted Set
public class MyThreadSafeSortedSet
{
private SortedSet<int> _set = new SortedSet<int>(new MyComparer());
private readonly object _locker = new object();
public void Add(int value)
{
lock (_locker)
{
_set.Add(value);
}
}
public int? Take()
{
lock (_locker)
{
if (_set.Count == 0)
return null;
var item = _set.First();
_set.Remove(item);
return item;
}
}
}
I built a custom comparer which prefers even numbers
public class MyComparer : Comparer<int>
{
public override int Compare(int x, int y)
{
if (x % 2 == 0)
{
if (y % 2 == 0)
return x - y;
else
return -1;
}
else
{
if (y % 2 == 0)
return 1;
else
return x - y;
}
}
}
And finally two threads. One to produce items; the other one to take them
static void Main(string[] args)
{
MyThreadSafeSortedSet queue = new MyThreadSafeSortedSet();
var task1 = Task.Run(() =>
{
Random r = new Random();
for (int i = 0; i < 15; i++)
{
Task.Delay(100).Wait();
var randomNumber = r.Next();
queue.Add(randomNumber);
}
Console.WriteLine("I'm done adding");
});
var task2 = Task.Run(() =>
{
Random r = new Random();
while (true)
{
var delay = r.Next(500);
Task.Delay(delay).Wait();
var item = queue.Take();
Console.WriteLine("Took: {0}", item);
if (item == null)
break;
}
});
Task.WaitAll(task2);
}
You can change the specialized SortedSet and custom comparer for your own classes.
Hope it helped
Please look my version of solution based on BinarySearch method of List class.
enum CategoryOfWorkItem: int { C = 0, B, A };
struct WorkItem : IComparer<WorkItem>
{
public CategoryOfWorkItem Category;
public int Compare(WorkItem x, WorkItem y)
{
return x.Category - y.Category;
}
public void AddTo(List<WorkItem> list)
{
int i = list.BinarySearch(this, this);
if (i < 0) i = ~i;
list.Insert(i, this);
}
}
Example of usage
List<WorkItem> list = new List<WorkItem>();
Task.Run(() =>
{
Random rand = new Random();
for (int i = 0; i < 20; i++)
{
WorkItem item = new WorkItem();
switch (rand.Next(0, 3))
{
case 0: item.Category = CategoryOfWorkItem.A; break;
case 1: item.Category = CategoryOfWorkItem.B; break;
case 2: item.Category = CategoryOfWorkItem.C; break;
}
lock (list)
{
item.AddTo(list);
}
Task.Delay(rand.Next(100, 1000)).Wait();
Console.WriteLine("Put {0}", item.Category);
}
Console.WriteLine("Putting finished.");
});
Task.WaitAll(Task.Run(() =>
{
Random rand = new Random();
while (true)
{
WorkItem item;
Task.Delay(rand.Next(500, 1000)).Wait();
lock (list)
{
if (list.Count < 1) break;
item = list[list.Count - 1];
list.RemoveAt(list.Count - 1);
}
Console.WriteLine("Get {0}", item.Category);
}
Console.WriteLine("Getting finished.");
}));
Related
i'm trying to solve project euler's third problem but it seems that the compiler skips a for loop so it makes my code totally useless
note : the idea didn't show any syntax error
here's the code :
class Program
{
static void Main(string[] args)
{
const long n = 600851475143;
List<long> factors = new List<long>();
factors = getFactors(Math.Sqrt(n));
long max = 0;
for (int i = 0; i<factors.Count ;i++)//this loop in particular , it doesn't print the "testing.."
{
Console.WriteLine("test....");
if(isPrime(getFactors(factors[i])))
{
max = factors[i];
}
}
Console.ReadKey();
}
static List<long> getFactors(double number)
{
List<long> list = new List<long>();
for(int i = 2;i<=number;i++)
{
if(number%i ==0)
{
list.Add(i);
}
}
return list;
}
static bool isPrime(List<long> list)
{
if(list.Count == 2)
{
return true;
}
else
{
return false;
}
}
}
static List<long> getFactors(double number)
{
List<long> list = new List<long>();
for (int i = 2; i <= number; i++)
{
if (Math.Floor(number % i) == 0)
{
list.Add(i);
}
}
return list;
}
number is a fraction, it will never == 0 unless its cast to an int
I have here this code to create ID Randome when calling and then add to the ArrayList, but I want to check if I already have same ID to dont added to ArrayList I have used BinarySearch to check Result
but it look there Something wrong
public delegate void DESetUp();
public static DESetUp IdSetUP = delegate ()
{
ArrayList valID = new ArrayList();
Func<int> getID = () => new Random().Next(1, 5);
int Result = getID();
if (Result == valID.BinarySearch(Result))
{
valID.Add(Result);
Console.WriteLine("AddSuccessful");
}
else
{
Console.WriteLine("AddFailed");
}
foreach (var item in valID)
{
Console.WriteLine("your id is : {0}", item);
}
};
Thank you
try this:
use List instead of ArrayList:
Always you need to sort the values with BinarySearch
List<int> valID = new List<int>();
Func<int> getID = () => new Random().Next(1, 10);
int Result = getID();
// here need to sort the list
valID.Sort();
if (valID.BinarySearch(Result) < 0) // if the position of value is lles than zero the value does not exists
{
valID.Add(Result);
Console.WriteLine("AddSuccessful");
}
else
{
Console.WriteLine("AddFailed");
}
i have reached a great result
i have assisted delegate List
public class MainClass
{
//assist delegate List<int>
public delegate void DESetUp(List<int> DLint);
//assist delegate List<int>
public static DESetUp IdSetUP = delegate (List<int> valID)
{
Func<int> getID = () => new Random().Next(1, 3);
int Result = getID();
// here need to sort the list
valID.Sort();
if (valID.BinarySearch(Result) < 0) // if the position of value is lles than zero the value does not exists
{
valID.Add(Result);
Console.WriteLine("AddSuccessful");
}
else
{
Console.WriteLine("AddFailed");
}
foreach (int item in valID)
{
Console.WriteLine(item);
}
};
static void Main(string[] args)
{
//assist List<int> ID
List<int> ID = new List<int>();
//ADD delegate + List<int> + IdSetUP
IdSetUP(ID);
IdSetUP(ID);
IdSetUP(ID);
IdSetUP(ID);
}
}
thank you all
Let's say that we have two object types:
class Type1
{
public int Value {get;set;}
}
class Type2
{
public int Val {get; set;}
}
And we have two IEnumerable's for them:
IEnumerable<Type1> type1col;
IEnumerable<Type2> type2col;
What I want to have: each of type1col elements Value property value would have adequate type2col Val property value added.
We can say that both IEnumerables will have the same length always.
For now I am using this:
for (int i = 0; i < type1col.Count(); i++)
{
type1col.ElementAt(i).Value += type2col.ElementAt(i).Val;
}
but is there any better (faster & shorter) approach to do the same?
You can use IEnumerable.Zip:
var type1Col = type1Col.Select(x => x.Value)
.Zip(type2Col.Select(x => x.Value), (x, y) => x + y)
.Select(x => new Type1 { Value = x });
But as you allready have simple lists you can also use a classic loop and use indexers instead of IEnumerable.ElementAt:
for(int i = 0; i < type1Col.Count; i++)
{
type1Col[i].Value += typeo2Col[i];
}
Enumerating both together would be faster
[Benchmark]
public static void Enumerator()
{
using (var enumerator1 = Type1S.GetEnumerator())
{
using (var enumerator2 = Type2S.GetEnumerator())
{
while (enumerator1.MoveNext() && enumerator2.MoveNext())
{
enumerator1.Current.Value += enumerator2.Current.Val;
}
}
}
}
If you want to do an in-place modification of the elements of a sequence rather than having the overhead of creating a new sequence using Zip() you could do something like this:
public static void Combine<T1, T2>(IEnumerable<T1> target, IEnumerable<T2> modifyier, Action<T1, T2> modify)
{
using (var seq1 = target.GetEnumerator())
using (var seq2 = modifyier.GetEnumerator())
{
while (seq1.MoveNext() && seq2.MoveNext())
{
modify(seq1.Current, seq2.Current);
}
}
}
Which you would use like this:
IEnumerable<Type1> typecol1 = new List<Type1>{new Type1{Value = 1 }, new Type1 { Value = 2 } };
IEnumerable<Type2> typecol2 = new List<Type2>{new Type2{Val = 3}, new Type2{ Val = 4 } };
Combine(typecol1, typecol2, (type1, type2) => type1.Value += type2.Val);
foreach (var item in typecol1)
{
Console.WriteLine(item.Value);
}
(I am currently restricted to .NET 4.0)
I have a situation where I want to process items in parallel as much as possible, order must be maintained, and items can be added at any time until "stop" is pressed.
Items can come in "bursts", so it is possible that the queue will completely drain, there will be a pause, and then a large number of items will come in at once again.
I want the results to become available as soon as they are done.
Here is a simplified example:
class Program
{
static void Main(string[] args)
{
BlockingCollection<int> itemsQueue = new BlockingCollection<int>();
Random random = new Random();
var results = itemsQueue
.GetConsumingEnumerable()
.AsParallel()
.AsOrdered()
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
.Select(i =>
{
int work = 0;
Console.WriteLine("Working on " + i);
//simulate work
for (int busy = 0; busy <= 90000000; ++busy) { ++work; };
Console.WriteLine("Finished " + i);
return i;
});
TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
foreach (int i in results)
{
Console.WriteLine("Result Available: " + i);
}
completion.SetResult(true);
});
int iterations;
iterations = random.Next(5, 50);
Console.WriteLine("------- iterations: " + iterations + "-------");
for (int i = 1; i <= iterations; ++i)
{
itemsQueue.Add(i);
}
while (true)
{
char c = Console.ReadKey().KeyChar;
if (c == 's')
{
break;
}
else
{
++iterations;
Console.WriteLine("adding: " + iterations);
itemsQueue.Add(iterations);
}
}
itemsQueue.CompleteAdding();
completion.Task.Wait();
Console.WriteLine("Done!");
Console.ReadKey();
itemsQueue.Dispose();
}
}
As the above example shows, what will typically happen, is that results will become available up until the last few results (I'm not 100% sure of this, but the number of results that it stops short may be roughly correlated with the number of cores on the box), until itemsQueue.CompleteAdding(); is called (in the example, the "s" key is pressed), at which point the rest of the results will finally become available.
Why do the results not become available immediately despite the fact that I specify .WithMergeOptions(ParallelMergeOptions.NotBuffered), and how can I make them become available immediately?
Note that the problem is not an issue if you can call BlockingQueue.CompleteAdding() instance method - that will cause all results to finish.
Short Answer
If on the other hand, you need to maintain order, and need to have the results available as soon as they can be, and you don't have an opportunity to call BlockingQueue.CompleteAdding(), then if at all possible, you are much better off having the consumption of items in the queue be non-parallel, but parallelize the processing of each individual task.
E.g.
class Program
{
//Not parallel, but suitable for monitoring queue purposes,
//can then focus on parallelizing each individual task
static void Main(string[] args)
{
BlockingCollection<int> itemsQueue = new BlockingCollection<int>();
Random random = new Random();
var results = itemsQueue.GetConsumingEnumerable()
.Select(i =>
{
Console.WriteLine("Working on " + i);
//Focus your parallelization efforts on the work of
//the individual task
//E.g, simulated:
double work = Enumerable.Range(0, 90000000 - (10 * (i % 3)))
.AsParallel()
.Select(w => w + 1)
.Average();
Console.WriteLine("Finished " + i);
return i;
});
TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
foreach (int i in results)
{
Console.WriteLine("Result Available: " + i);
}
completion.SetResult(true);
});
int iterations;
iterations = random.Next(5, 50);
Console.WriteLine("------- iterations: " + iterations + "-------");
for (int i = 1; i <= iterations; ++i)
{
itemsQueue.Add(i);
}
while (true)
{
char c = Console.ReadKey().KeyChar;
if (c == 's')
{
break;
}
else
{
++iterations;
Console.WriteLine("adding: " + iterations);
itemsQueue.Add(iterations);
}
}
itemsQueue.CompleteAdding();
completion.Task.Wait();
Console.WriteLine("Done!");
Console.ReadKey();
itemsQueue.Dispose();
}
}
Longer Answer
It appears that there is an interaction between the BlockingQueue in particular and AsOrderable()
It seems that AsOrderable will stop the processing of Tasks whenever one of the enumerators in the partition blocks.
The default partitioner will deal with chunks typically greater than one - and the blocking queue will block until the chunk can be filled (or CompleteAdding is filled).
However, even with a chunk size of 1, the problem does not completely go away.
To play around with this, you can sometimes see the behavior when implementing your own partitioner. (Note, that if you specify .WithDegreeOfParallelism(1) the problem with results waiting to appear goes away - but of course, having a degree of parallelism = 1 kind of defeats the purpose!)
e.g.
public class ImmediateOrderedPartitioner<T> : OrderablePartitioner<T>
{
private readonly IEnumerable<T> _consumingEnumerable;
private readonly Ordering _ordering = new Ordering();
public ImmediateOrderedPartitioner(BlockingCollection<T> collection) : base(true, true, true)
{
_consumingEnumerable = collection.GetConsumingEnumerable();
}
private class Ordering
{
public int Order = -1;
}
private class MyEnumerator<S> : IEnumerator<KeyValuePair<long, S>>
{
private readonly object _orderLock = new object();
private readonly IEnumerable<S> _enumerable;
private KeyValuePair<long, S> _current;
private bool _hasItem;
private Ordering _ordering;
public MyEnumerator(IEnumerable<S> consumingEnumerable, Ordering ordering)
{
_enumerable = consumingEnumerable;
_ordering = ordering;
}
public KeyValuePair<long, S> Current
{
get
{
if (_hasItem)
{
return _current;
}
else
throw new InvalidOperationException();
}
}
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
lock (_orderLock)
{
bool canMoveNext = false;
var next = _enumerable.Take(1).FirstOrDefault(s => { canMoveNext = true; return true; });
if (canMoveNext)
{
_current = new KeyValuePair<long, S>(++_ordering.Order, next);
_hasItem = true;
++_ordering.Order;
}
else
{
_hasItem = false;
}
return canMoveNext;
}
}
public void Reset()
{
throw new NotSupportedException();
}
}
public override IList<IEnumerator<KeyValuePair<long, T>>> GetOrderablePartitions(int partitionCount)
{
var result = new List<IEnumerator<KeyValuePair<long,T>>>();
//for (int i = 0; i < partitionCount; ++i)
//{
// result.Add(new MyEnumerator<T>(_consumingEnumerable, _ordering));
//}
//share the enumerator between partitions in this case to maintain
//the proper locking on ordering.
var enumerator = new MyEnumerator<T>(_consumingEnumerable, _ordering);
for (int i = 0; i < partitionCount; ++i)
{
result.Add(enumerator);
}
return result;
}
public override bool SupportsDynamicPartitions
{
get
{
return false;
}
}
public override IEnumerable<T> GetDynamicPartitions()
{
throw new NotImplementedException();
return base.GetDynamicPartitions();
}
public override IEnumerable<KeyValuePair<long, T>> GetOrderableDynamicPartitions()
{
throw new NotImplementedException();
return base.GetOrderableDynamicPartitions();
}
public override IList<IEnumerator<T>> GetPartitions(int partitionCount)
{
throw new NotImplementedException();
return base.GetPartitions(partitionCount);
}
}
class Program
{
static void Main(string[] args)
{
BlockingCollection<int> itemsQueue = new BlockingCollection<int>();
var partitioner = new ImmediateOrderedPartitioner<int>(itemsQueue);
Random random = new Random();
var results = partitioner
.AsParallel()
.AsOrdered()
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
//.WithDegreeOfParallelism(1)
.Select(i =>
{
int work = 0;
Console.WriteLine("Working on " + i);
for (int busy = 0; busy <= 90000000; ++busy) { ++work; };
Console.WriteLine("Finished " + i);
return i;
});
TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
foreach (int i in results)
{
Console.WriteLine("Result Available: " + i);
}
completion.SetResult(true);
});
int iterations;
iterations = 1; // random.Next(5, 50);
Console.WriteLine("------- iterations: " + iterations + "-------");
for (int i = 1; i <= iterations; ++i)
{
itemsQueue.Add(i);
}
while (true)
{
char c = Console.ReadKey().KeyChar;
if (c == 's')
{
break;
}
else
{
++iterations;
Console.WriteLine("adding: " + iterations);
itemsQueue.Add(iterations);
}
}
itemsQueue.CompleteAdding();
completion.Task.Wait();
Console.WriteLine("Done!");
Console.ReadKey();
itemsQueue.Dispose();
}
}
Alternate Approach
If parallelizing the individual task (as recommended in the "short answer") is not a possibility, and all the other problem constraints apply, then you can implement your own type of queue that spins up tasks for each item - thus letting the Task Parallel Library handle the scheduling of work, but synchronize the consumption of results on your own.
For example, something like the below (with the standard "no warranties" disclaimer!)
public class QueuedItem<TInput, TResult>
{
private readonly object _lockObject = new object();
private TResult _result;
private readonly TInput _input;
private readonly TResult _notfinished;
internal readonly bool IsEndQueue = false;
internal QueuedItem()
{
IsEndQueue = true;
}
public QueuedItem(TInput input, TResult notfinished)
{
_input = input;
_notfinished = notfinished;
_result = _notfinished;
}
public TResult ReadResult()
{
lock (_lockObject)
{
if (!IsResultReady)
throw new InvalidOperationException("Check IsResultReady before calling ReadResult()");
return _result;
}
}
public void WriteResult(TResult value)
{
lock (_lockObject)
{
if (IsResultReady)
throw new InvalidOperationException("Result has already been written");
_result = value;
}
}
public TInput Input { get { return _input; } }
public bool IsResultReady
{
get
{
lock (_lockObject)
{
return !object.Equals(_result, _notfinished) || IsEndQueue;
}
}
}
}
public class ParallelImmediateOrderedProcessingQueue<TInput, TResult>
{
private readonly ReaderWriterLockSlim _addLock = new ReaderWriterLockSlim();
private readonly object _readingResultsLock = new object();
private readonly ConcurrentQueue<QueuedItem<TInput, TResult>> _concurrentQueue = new ConcurrentQueue<QueuedItem<TInput, TResult>>();
bool _isFinishedAdding = false;
private readonly TResult _notFinished;
private readonly Action<QueuedItem<TInput, TResult>> _processor;
/// <param name="notFinished">A value that indicates the result is not yet finished</param>
/// <param name="processor">Must call SetResult() on argument when finished.</param>
public ParallelImmediateOrderedProcessingQueue(TResult notFinished, Action<QueuedItem<TInput, TResult>> processor)
{
_notFinished = notFinished;
_processor = processor;
}
public event Action ResultsReady = delegate { };
private void SignalResult()
{
QueuedItem<TInput, TResult> item;
if (_concurrentQueue.TryPeek(out item) && item.IsResultReady)
{
ResultsReady();
}
}
public void Add(TInput input)
{
bool shouldThrow = false;
_addLock.EnterReadLock();
{
shouldThrow = _isFinishedAdding;
if (!shouldThrow)
{
var queuedItem = new QueuedItem<TInput, TResult>(input, _notFinished);
_concurrentQueue.Enqueue(queuedItem);
Task.Factory.StartNew(() => { _processor(queuedItem); SignalResult(); });
}
}
_addLock.ExitReadLock();
if (shouldThrow)
throw new InvalidOperationException("An attempt was made to add an item, but adding items was marked as completed");
}
public IEnumerable<TResult> ConsumeReadyResults()
{
//lock necessary to preserve ordering
lock (_readingResultsLock)
{
QueuedItem<TInput, TResult> queuedItem;
while (_concurrentQueue.TryPeek(out queuedItem) && queuedItem.IsResultReady)
{
if (!_concurrentQueue.TryDequeue(out queuedItem))
throw new ApplicationException("this shouldn't happen");
if (queuedItem.IsEndQueue)
{
_completion.SetResult(true);
}
else
{
yield return queuedItem.ReadResult();
}
}
}
}
public void CompleteAddingItems()
{
_addLock.EnterWriteLock();
{
_isFinishedAdding = true;
var queueCompletion = new QueuedItem<TInput, TResult>();
_concurrentQueue.Enqueue(queueCompletion);
Task.Factory.StartNew(() => { SignalResult(); });
}
_addLock.ExitWriteLock();
}
TaskCompletionSource<bool> _completion = new TaskCompletionSource<bool>();
public void WaitForCompletion()
{
_completion.Task.Wait();
}
}
class Program
{
static void Main(string[] args)
{
const int notFinished = int.MinValue;
var processingQueue = new ParallelImmediateOrderedProcessingQueue<int, int>(notFinished, qi =>
{
int work = 0;
Console.WriteLine("Working on " + qi.Input);
//simulate work
int maxBusy = 90000000 - (10 * (qi.Input % 3));
for (int busy = 0; busy <= maxBusy; ++busy) { ++work; };
Console.WriteLine("Finished " + qi.Input);
qi.WriteResult(qi.Input);
});
processingQueue.ResultsReady += new Action(() =>
{
Task.Factory.StartNew(() =>
{
foreach (int result in processingQueue.ConsumeReadyResults())
{
Console.WriteLine("Results Available: " + result);
}
});
});
int iterations = new Random().Next(5, 50);
Console.WriteLine("------- iterations: " + iterations + "-------");
for (int i = 1; i <= iterations; ++i)
{
processingQueue.Add(i);
}
while (true)
{
char c = Console.ReadKey().KeyChar;
if (c == 's')
{
break;
}
else
{
++iterations;
Console.WriteLine("adding: " + iterations);
processingQueue.Add(iterations);
}
}
processingQueue.CompleteAddingItems();
processingQueue.WaitForCompletion();
Console.WriteLine("Done!");
Console.ReadKey();
}
}
I've got various chapters with different depths.
so there are 14.1 and 14.4.2 and 14.7.8.8.2 and so on.
Alphanumerical sorted the 14.10 will appear before 14.2. That's bad. It should come after 14.9.
Is there an easy way to sort theese, without adding leading zeros? f.e. with linq?
public class NumberedSectionComparer : IComparer<string>
{
private int Compare(string[] x, string[]y)
{
if(x.Length > y.Length)
return -Compare(y, x);//saves needing separate logic.
for(int i = 0; i != x.Length; ++i)
{
int cmp = int.Parse(x[i]).CompareTo(int.Parse(y[i]));
if(cmp != 0)
return cmp;
}
return x.Length == y.Length ? 0 : -1;
}
public int Compare(string x, string y)
{
if(ReferenceEquals(x, y))//short-cut
return 0;
if(x == null)
return -1;
if(y == null)
return 1;
try
{
return Compare(x.Split('.'), y.Split('.'));
}
catch(FormatException)
{
throw new ArgumentException();
}
}
}
I did this right now, need some tests:
using System;
using System.Collections.Generic;
using System.Linq;
namespace TestesConsole
{
class Program
{
static void Main(string[] args)
{
string[] vers = new[]
{
"14.10",
"14.9",
"14.10.1",
};
var ordered = vers.OrderBy(x => x, new VersionComparer()).ToList();
}
}
public class VersionComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string[] xs = x.Split('.');
string[] ys = y.Split('.');
int maxLoop = Math.Min(xs.Length, ys.Length);
for (int i = 0; i < maxLoop; i++)
{
if(int.Parse(xs[i]) > int.Parse(ys[i]))
{
return 1;
}
else if(int.Parse(xs[i]) < int.Parse(ys[i]))
{
return -1;
}
}
if(xs.Length > ys.Length)
{
return 1;
}
else if(xs.Length < ys.Length)
{
return -1;
}
return 0;
}
}
}
var headers = new List<string> {"14.1.2.3", "14.1", "14.9", "14.2.1", "14.4.2", "14.10.1.2.3.4", "14.7.8.8.2"};
headers.Sort(new MySorter());
class MySorter : IComparer<string>
{
public int Compare(string x, string y)
{
IList<string> a = x.Split('.');
IList<string> b = y.Split('.');
int numToCompare = (a.Count < b.Count) ? a.Count : b.Count;
for (int i = 0; i < numToCompare; i++)
{
if (a[i].Equals(b[i]))
continue;
int numa = Convert.ToInt32(a[i]);
int numb = Convert.ToInt32(b[i]);
return numa.CompareTo(numb);
}
return a.Count.CompareTo(b.Count);
}
}
Using IComparer hast the big disadvantage of repeating the rather expensive calculation very often, so I thought precalculating an order criterium would be a good idea:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ChapterSort
{
class Program
{
static void Main(string[] args)
{
String[] chapters=new String[] {"14.1","14.4.2","14.7.8.8.2","14.10","14.2","14.9","14.10.1.2.3.4","14.1.2.3" };
IEnumerable<String> newchapters=chapters.OrderBy(x => new ChapterNumerizer(x,256,8).NumericValue);
foreach (String s in newchapters) Console.WriteLine(s);
}
}
public class ChapterNumerizer
{
private long numval;
public long NumericValue {get{return numval;}}
public ChapterNumerizer (string chapter,int n, int m)
{
string[] c = chapter.Split('.');
numval=0;
int j=0;
foreach (String cc in c)
{
numval=n*numval+int.Parse(cc);
j++;
}
while (j<m)
{
numval*=n;
j++;
}
}
}
}
This solution is more general.
public class SequenceComparer<T> : IComparer<IEnumerable<T>> where T : IComparable<T>
{
public int Compare(IEnumerable<T> x, IEnumerable<T> y)
{
IEnumerator<T> enx = x.GetEnumerator();
IEnumerator<T> eny = y.GetEnumerator();
do
{
bool endx = enx.MoveNext();
bool endy = eny.MoveNext();
if (!endx && !endy)
return 0;
if (!endx)
return -1;
if (!endy)
return 1;
var comp = enx.Current.CompareTo(eny.Current);
if(comp != 0)
return comp;
} while (true);
}
}
Then use:
var sv = vers.Select(v => new { Key = v, Split = v.Split('.').Select(Int32.Parse) });
var ordered = sv.OrderBy(x => x.Split, new SequenceComparer<int>()).Select(x => x.Key);
As a small LINQ one-liner:
List<string> chapters= new List<string>()
{
"14.1",
"14.4.2",
"14.7.8.8.2",
"14.10",
"14.2"
};
chapters.OrderBy(c => Regex.Replace(c, "[0-9]+", match => match.Value.PadLeft(10, '0')));
Independent of levels but surely not the best performance...
Credits are going to https://stackoverflow.com/a/5093939/226278