Lets suppose i have a list and i want to remove accoding to the condition that if priority item is there then remove all smaller priority items . for eg
Sortedarr[] =["world champion","Best performer","Best gold", "Best silver", "Best bronze", "regional"]
Conditions.
if gold is present i want every medal except silver and bronze.
if gold is not presnt but max silver is present then show only silver not bronze
if both silver and gold are not present then show bronze.
the array would always be sorted so always gold>siilver>bronze would be the order.
My implementation is working but i dont think its the optimised way of doing it.
if(arr.Contains("Best gold")){
sortedArr.RemoveAll(x => ((string)x) == "Best silver"
|| ((string)x) == "Best Bronze" );
}
if (sortedArr.Contains("Best silver")) {
sortedArr.RemoveAll(x =>((string)x) == "Best bronze");
}
thank you in advance
If it comes to sorting, I would recommend implementing own comparer for string, so implementing interface IComparer<string>.
Below simply checks wether compared string contains either gold, silver or bronze and gives desired output based on score of a medal.
In case of a tie, we fallback to chosen comparer, here StringComparer.OrdinalIgnoreCase.
In case if none of the compared strings contain any of a medals, then we also fallback to chosen comparison type.
public class MedalComparer : IComparer<string>
{
private const string Gold = "gold";
private const string Silver = "silver";
private const string Bronze = "bronze";
// Here you should put all medals with respctive score
private static readonly Dictionary<int, string> Medals = new()
{
{1, Gold}, {2, Silver}, {3, Bronze},
};
private static readonly StringComparison StringComparison = StringComparison.OrdinalIgnoreCase;
private static readonly IComparer<string> _fallbackComparer = StringComparer.OrdinalIgnoreCase;
public int Compare(string x, string y)
{
return IsTie(x, y)
? _fallbackComparer.Compare(x, y)
: Evaluate(x, y) ?? _fallbackComparer.Compare(x, y);
}
private static bool IsTie(string x, string y)
{
return (x.Contains(Gold, StringComparison) && y.Contains(Gold, StringComparison)) ||
(x.Contains(Silver, StringComparison) && y.Contains(Silver, StringComparison)) ||
(x.Contains(Bronze, StringComparison) && y.Contains(Bronze, StringComparison));
}
private static int? Evaluate(string x, string y)
{
foreach (var medalKey in Medals.Keys.OrderBy(x => x))
{
if(x.Contains(Medals[medalKey], StringComparison))
{
return 1;
}
if (y.Contains(Medals[medalKey], StringComparison))
{
return -1;
}
}
return null;
}
}
Then you can pass it conveniently to OrderBy method as shown below:
public static class Program
{
public static async Task Main(string[] args)
{
var testData = new[]{ "world champion","Best performer","Best gold", "Best silver", "Best bronze", "regional"};
var sorted = testData.OrderBy(x => x, new MedalComparer());
}
}
Your question is not clear. but you can use Array.IndexOf Method to check item is present:
List<string> array =(new string[] {"world champion", "Best performer", "Best gold", "Best silver", "Best bronze", "regional"}).ToList();
if (array.IndexOf("Best gold") > 0)
array.RemoveAll(m=>m.Contains("Best silver") || m.Contains("Best bronze"));
else if (array.IndexOf("Best silver") > 0)
array.RemoveAll(m => m.Contains("Best bronze"));
List<string> array =(new string[] {"world champion", "Best performer", "Best gold", "Best silver", "Best bronze", "regional"});
array.Contains("Best gold") ? array.RemoveAll(x => ((string)x) == "Best silver" || ((string)x) == "Best Bronze" )
: (array.Contains("Best silver") ?
array.RemoveAll(x =>((string)x) == "Best bronze")
: return array)
Related
I need to do an intersect between strings but comparing substrings:
public class MiNumeroEqualityComparer : IEqualityComparer<string> {
public bool Equals(string x, string y) => x.Contains(y);
public int GetHashCode(string obj) => obj.GetHashCode();
}
List<string> lst = new List<string> { "abcXdef", "abcXdef", "abcede", "aYcde" };
List<string> num = new List<string> { "X", "Y", "Z" };
var fin = lst.Intersect(num, new MiNumeroEqualityComparer());
I expect in fin: "abcXdef", "abcXdef", "aYcde"
But it's empty, why?
First I've tried substring with case insensitive with: (without success too)
public bool Equals(string x, string y) => x.IndexOf(y, StringComparison.InvariantCultureIgnoreCase) >= 0;
But empty too.
You're doing an intersection between two lists, which will give you the common items between them. Since neither list contains an identical item, you are getting no results.
If you want to get all the items from lst that contain an item from num, then you can do something like the code below, which uses the string.Contains method to filter the items from lst:
var fin = lst.Where(item => num.Any(item.Contains));
Result:
{ "abcXdef", "abcXdef", "aYcde" }
Alternatively, if you do want to do a case-insensitive query, you can use the IndexOf method instead:
var fin = lst.Where(item => num.Any(n =>
item.IndexOf(n, StringComparison.OrdinalIgnoreCase) >= 0));
If that's hard to understand (sometimes Linq is), the first code snippet above is a shorthand way of writing the following:
var fin = new List<string>();
foreach (var item in lst)
{
foreach (var n in num)
{
if (item.Contains(n))
{
fin.Add(item);
break;
}
}
}
Sure Rufus has solved your issue in the answer provided. But let me explain why your approach was not working.
The reason it is producing an empty result is because Equals(string x, string y) will never be called. It can infer the inequality from the GetHashCode method. If the hashes are the same, then it will call Equals. In other words, your logic in Equals will never be executed.
Here is some code so you can see what is going on.
class Program
{
static void Main(string[] args)
{
// See I added an item at the end here to show when Equals is called
List<string> lst = new List<string> { "abcXdef", "abcXdef", "abcede", "aYcde", "X" };
List<string> num = new List<string> { "X", "Y", "Z" };
var fin = lst.Intersect(num, new MiNumeroEqualityComparer()).ToList();
Console.ReadLine();
}
}
public class MiNumeroEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
Console.WriteLine("Equals called for {0} and {1}.", x, y);
return x.Contains(y);
}
public int GetHashCode(string obj)
{
Console.WriteLine("GetHashCode alled for {0}.", obj);
return obj.GetHashCode();
}
}
If you run the above code, it will only call Equals for items which produce the same hash; so for "X" only.
See the output in this fiddle.
Intersect gets common elements from 2 collections. The Intersect method here is elegant. It can be used on many types of elements.
your result is empty because it is not a common value in the lists.
List<string> lst = new List<string> { "abcXdef", "abcXdef", "abcede", "aYcde" };
List<string> num = new List<string> { "X", "Y", "abcXdef", "Z", "aYcde" };
var fin = lst.Intersect(num);
fin >> abcXdef,aYcde
There is a function in .NET
public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index);
Is there a simply way to use similar function to find out if substrings occurs at some position (index)?
I mean:
public static bool ElementAtPosContains(this string, int index, string[] valuesToCheck)
{ ... }
string test1 = "abcd5f";
string[] substrings = {"1" , "2", "3", "4", "5"};
if (test.ElementAtPosContains(4, substrings))
{
DoSomething();
}
If there would be 1, 2, 3, 4, 5 at 4 position in string - return true. I can do this thing:
public static bool ElementAtPosContains(this string inputStr, int index, string[] valuesToCheck)
{
if (valuesToCheck == null)
return false;
foreach (string value in valuesToCheck)
{
if (inputStr.Substring(index, value.Length) == value)
return true;
}
return false;
}
But this seems to be not very effective
Try with this:
public static bool ElementAtPosContains(this string inputStr, int index, string[] valuesToCheck)
{
if (valuesToCheck == null || valuesToCheck.Length == 0 || inputStr.Length < index)
return false;
return valuesToCheck.Any(sub => string.CompareOrdinal(inputStr, index, sub, 0, sub.Length) == 0);
}
CompareOrdinal comparisons are always case-sensitive and don't take culture into consideration. This makes them faster then usual Compare or = operator. If you don't need culture-aware comparison or case-insensitive comparison, this method should do the job faster since it compares numeric values of characters from the string.
Demo
This seems to be smarter:
public static bool ElementAtPosContains(this string inputStr, int index, IList<String> valuesToCheck)
{
if (valuesToCheck == null || inputStr.Length < index)
return false;
return valuesToCheck.Any(s => s.Length + index <= inputStr.Length
&& inputStr.IndexOf(s, index) == index);
}
Demo
Although i think that you're doing premature optimization.
I have a dictionary containg ID which are alphanumeric (e.g. a10a10 & d10a9) from which I want the biggest ID, meaning 9 < 10 < a ...
When I use the following code, d10a9 is MAX since 9 is sorted before 10
var lsd = new Dictionary<string, string>();
lsd.Add("a", "d10a10");
lsd.Add("b", "d10a9");
string max = lsd.Max(kvp => kvp.Value);
How can I get the Max value of the IDs with the Longest string combined?
I think you may try to roll your own IComparer<string>
class HumanSortComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// your human sorting logic here
}
}
Usage:
var last = collection.OrderBy(x => x.Value, new HumanSortComparer()).LastOrDefault();
if (last != null)
string max = last.Value;
this works like a charm assuming IDs always start with "d10a":
int max = lsd.Max(kvp => Convert.ToInt32(kvp.Value.Substring(4)));
Console.Write(string.Format("d10a{0}", max));
One way would be to do this
string max =lsd.Where(kvp=>kvp.Value.Length==lsd.Max(k=>k.Value.Length)).Max(kvp => kvp.Value);
however I think that this method would evalute the max length for each item so you may be better to extract it to a variable first
int maxLength=lsd.Max(kvp=>kvp.Value.Length);
string max = lsd.Where(kvp=>kvp.Value.Length == maxLength).Max(kvp => kvp.Value);
If you are going to have null strings in there you may need to perform null checks too
int maxLength=lsd.Max(kvp=>(kvp.Value??String.Empty).Length);
string max = lsd.Where(kvp=>(kvp.Value??String.Empty).Length == maxLength).Max(kvp => kvp.Value);
Alternatively treat your string as Base36 number and convert to long for the max function and then convert back again to get the max string.
string max =lsd.Max(tvp=>tvp.Value.FromBase36()).ToBase36();
public static class Base36 {
public static long FromBase36(this string src) {
return src.ToLower().Select(x=>(int)x<58 ? x-48 : x-87).Aggregate(0L,(s,x)=>s*36+x);
}
public static string ToBase36(this long src) {
StringBuilder result=new StringBuilder();
while(src>0) {
var digit=(int)(src % 36);
digit=(digit<10) ? digit+48 :digit+87;
result.Insert(0,(char)digit);
src=src / 36;
}
return result.ToString();
}
}
Finally just just the Agregate extension method instead of Max as this lets you do all the comparison logic....
lsd.Agregate(string.Empty,(a,b)=> a.Length == b.Length ? (a>b ? a:b) : (a.Length>b.Length ? a:b));
This could doesn't have null checks but you easily add them in.
I think if you did this:
var max = lsd.OrderByDescending(x => x.Value)
.GroupBy(x => x.Value.Length)
.OrderByDescending(x => x.Key)
.SelectMany(x => x)
.FirstOrDefault();
It may give you what you want.
You need StringComparer.OrdinalIgnoreCase.
Without the need to use linq, the function that do that is quite simple.
Complexity is, of course, O(n).
public static KeyValuePair<string, string> FindMax(IEnumerable<KeyValuePair<string, string>> lsd)
{
var comparer = StringComparer.OrdinalIgnoreCase;
var best = default(KeyValuePair<string, string>);
bool isFirst = true;
foreach (KeyValuePair<string, string> kvp in lsd)
{
if (isFirst || comparer.Compare(kvp.Value, best.Value) > 0)
{
isFirst = false;
best = kvp;
}
}
return best;
}
Okay - I think you need to first turn each key into a series of strings and numbers - since you need the whole number to be able to determine the comparison. Then you implement an IComparer - I've tested this with your two input strings as well as with a few others and it appears to do what you want. The performance could possibly be improved - but I was brainstorming it!
Create this class:
public class ValueChain
{
public readonly IEnumerable<object> Values;
public int ValueCount = 0;
private static readonly Regex _rx =
new Regex("((?<alpha>[a-z]+)|(?<numeric>([0-9]+)))",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public ValueChain(string valueString)
{
Values = Parse(valueString);
}
private IEnumerable<object> Parse(string valueString)
{
var matches = _rx.Matches(valueString);
ValueCount = matches.Count;
foreach (var match in matches.Cast<Match>())
{
if (match.Groups["alpha"].Success)
yield return match.Groups["alpha"].Value;
else if (match.Groups["numeric"].Success)
yield return int.Parse(match.Groups["numeric"].Value);
}
}
}
Now this comparer:
public class ValueChainComparer : IComparer<ValueChain>
{
private IComparer<string> StringComparer;
public ValueChainComparer()
: this(global::System.StringComparer.OrdinalIgnoreCase)
{
}
public ValueChainComparer(IComparer<string> stringComparer)
{
StringComparer = stringComparer;
}
#region IComparer<ValueChain> Members
public int Compare(ValueChain x, ValueChain y)
{
//todo: null checks
int comparison = 0;
foreach (var pair in x.Values.Zip
(y.Values, (xVal, yVal) => new { XVal = xVal, YVal = yVal }))
{
//types match?
if (pair.XVal.GetType().Equals(pair.YVal.GetType()))
{
if (pair.XVal is string)
comparison = StringComparer.Compare(
(string)pair.XVal, (string)pair.YVal);
else if (pair.XVal is int) //unboxing here - could be changed
comparison = Comparer<int>.Default.Compare(
(int)pair.XVal, (int)pair.YVal);
if (comparison != 0)
return comparison;
}
else //according to your rules strings are always greater than numbers.
{
if (pair.XVal is string)
return 1;
else
return -1;
}
}
if (comparison == 0) //ah yes, but were they the same length?
{
//whichever one has the most values is greater
return x.ValueCount == y.ValueCount ?
0 : x.ValueCount < y.ValueCount ? -1 : 1;
}
return comparison;
}
#endregion
}
Now you can get the max using OrderByDescending on an IEnumerable<ValueChain> and FirstOrDefault:
[TestMethod]
public void TestMethod1()
{
List<ValueChain> values = new List<ValueChain>(new []
{
new ValueChain("d10a9"),
new ValueChain("d10a10")
});
ValueChain max =
values.OrderByDescending(v => v, new ValueChainComparer()).FirstOrDefault();
}
So you can use this to sort the string values in your dictionary:
var maxKvp = lsd.OrderByDescending(kvp => new ValueChain(kvp.Value),
new ValueChainComparer()).FirstOrDefault();
I look for an equality between two instances of this struct.
public struct Serie<T>
{
T[] X;
double[] Y;
public Serie(T[] x, double[] y)
{
X = x;
Y = y;
}
public override bool Equals(object obj)
{
return obj is Serie<T> && this == (Serie<T>)obj;
}
public static bool operator ==(Serie<T> s1, Serie<T> s2)
{
return s1.X == s2.X && s1.Y == s2.Y;
}
public static bool operator !=(Serie<T> s1, Serie<T> s2)
{
return !(s1 == s2);
}
This doesn't work. What am I missing?
double[] xa = { 2, 3 };
double[] ya = { 1, 2 };
double[] xb = { 2, 3 };
double[] yb = { 1, 2 };
Serie<double> A = new Serie<double>(xa, ya);
Serie<double> B = new Serie<double>(xb, yb);
Assert.AreEqual(A, B);
You're comparing the array references rather than their contents. ya and yb refer to different arrays. If you want to check the contents of the arrays, you'll have to do so explicitly.
I don't think there's anything built into the framework to do that for you, I'm afraid. Something like this should work though:
public static bool ArraysEqual<T>(T[] first, T[] second)
{
if (object.ReferenceEquals(first, second))
{
return true;
}
if (first == null || second == null)
{
return false;
}
if (first.Length != second.Length)
{
return false;
}
IEqualityComparer comparer = EqualityComparer<T>.Default;
for (int i = 0; i < first.Length; i++)
{
if (!comparer.Equals(first[i], second[i]))
{
return false;
}
}
return true;
}
As an aside, your structs are sort of mutable in that the array contents can be changed after the struct is created. Do you really need this to be a struct?
EDIT: As Nick mentioned in the comments, you should really override GetHashCode as well. Again, you'll need to get the contents from the arrays (and again, this will cause problems if the arrays get changed afterwards). Similar utility method:
public static int GetHashCode<T>(T[] array)
{
if (array == null)
{
return 0;
}
IEqualityComparer comparer = EqualityComparer<T>.Default;
int hash = 17;
foreach (T item in array)
{
hash = hash * 31 + comparer.GetHashCode(item);
}
return hash;
}
I don't think there's anything built into the framework to do that for you, I'm afraid
In 4.0, there is:
StructuralComparisons.StructuralEqualityComparer.Equals(firstArray, secondArray);
You should compare the contents of the Array in your Equality logic ...
Also, it is recommended that you implement IEquatable<T> interface on your struct, as this prevents boxing/unboxing issues in some cases.
http://blogs.msdn.com/jaredpar/archive/2009/01/15/if-you-implement-iequatable-t-you-still-must-override-object-s-equals-and-gethashcode.aspx
The part s1.Y == s2.Y tests if they are 2 references to the same array instance, not if the contents are equal. So despite the title, this question is actually about equality between array(-reference)s.
Some additional advice: Since you are overloading you should design Serie<> as immutable and because of the embedded array I would make it a class instead of a struct.
Calling == performs reference equality on arrays - they don't compare the contents of their elements. This basically means that a1 == a2 will only return true if the exact same instance - which isn't what you want, I think..
You need to modify your operator == to compere the contents of the x array, not it's reference value.
If you're using .NET 3.5 (with link) you can do:
public static bool operator ==(Serie<T> s1, Serie<T> s2)
{
return ((s1.X == null && s2.X == null) || s1.X.SequenceEquals( s2.X ))
&& s1.Y == s2.Y;
}
If you need to do deep comparison (beyond references), you can supply SequenceEquals with a custom IEqualityComparer for the type of T.
You probably should also consider implementing the IEquatable<T> interface for your struct. It will help your code work better with LINQ and other parts of the .NET framework that perform object comparisons.
You can create a private accessor for your struct and use CollectionAssert:
[TestMethod()]
public void SerieConstructorTest()
{
double[] xa = { 2, 3 };
double[] ya = { 1, 2 };
double[] xb = { 2, 3 };
double[] yb = { 1, 2 };
var A = new Serie_Accessor<double>(xa, ya);
var B = new Serie_Accessor<double>(xb, yb);
CollectionAssert.AreEqual(A.X, B.X);
CollectionAssert.AreEqual(A.Y, B.Y);
}
This code works fine.
References:
CollectionAssert.AreEqual Method
How to create private accessor
I've got a large set of data for which computing the sort key is fairly expensive. What I'd like to do is use the DSU pattern where I take the rows and compute a sort key. An example:
Qty Name Supplier
Row 1: 50 Widgets IBM
Row 2: 48 Thingies Dell
Row 3: 99 Googaws IBM
To sort by Quantity and Supplier I could have the sort keys: 0050 IBM, 0048 Dell, 0099 IBM. The numbers are right-aligned and the text is left-aligned, everything is padded as needed.
If I need to sort by the Quanty in descending order I can just subtract the value from a constant (say, 10000) to build the sort keys: 9950 IBM, 9952 Dell, 9901 IBM.
How do I quickly/cheaply build a descending key for the alphabetic fields in C#?
[My data is all 8-bit ASCII w/ISO 8859 extension characters.]
Note: In Perl, this could be done by bit-complementing the strings:
$subkey = $string ^ ( "\xFF" x length $string );
Porting this solution straight into C# doesn't work:
subkey = encoding.GetString(encoding.GetBytes(stringval).
Select(x => (byte)(x ^ 0xff)).ToArray());
I suspect because of the differences in the way that strings are handled in C#/Perl. Maybe Perl is sorting in ASCII order and C# is trying to be smart?
Here's a sample piece of code that tries to accomplish this:
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
List<List<string>> sample = new List<List<string>>() {
new List<string>() { "", "apple", "table" },
new List<string>() { "", "apple", "chair" },
new List<string>() { "", "apple", "davenport" },
new List<string>() { "", "orange", "sofa" },
new List<string>() { "", "peach", "bed" },
};
foreach(List<string> line in sample)
{
StringBuilder sb = new StringBuilder();
string key1 = line[1].PadRight(10, ' ');
string key2 = line[2].PadRight(10, ' ');
// Comment the next line to sort desc, desc
key2 = encoding.GetString(encoding.GetBytes(key2).
Select(x => (byte)(x ^ 0xff)).ToArray());
sb.Append(key2);
sb.Append(key1);
line[0] = sb.ToString();
}
List<List<string>> output = sample.OrderBy(p => p[0]).ToList();
return;
You can get to where you want, although I'll admit I don't know whether there's a better overall way.
The problem you have with the straight translation of the Perl method is that .NET simply will not allow you to be so laissez-faire with encoding. However, if as you say your data is all printable ASCII (ie consists of characters with Unicode codepoints in the range 32..127) - note that there is no such thing as '8-bit ASCII' - then you can do this:
key2 = encoding.GetString(encoding.GetBytes(key2).
Select(x => (byte)(32+95-(x-32))).ToArray());
In this expression I have been explicit about what I'm doing:
Take x (which I assume to be in 32..127)
Map the range to 0..95 to make it zero-based
Reverse by subtracting from 95
Add 32 to map back to the printable range
It's not very nice but it does work.
Just write an IComparer that would work as a chain of comparators.
In case of equality on each stage, it should pass eveluation to the next key part. If it's less then, or greater then, just return.
You need something like this:
int comparision = 0;
foreach(i = 0; i < n; i++)
{
comparision = a[i].CompareTo(b[i]) * comparisionSign[i];
if( comparision != 0 )
return comparision;
}
return comparision;
Or even simpler, you can go with:
list.OrderBy(i=>i.ID).ThenBy(i=>i.Name).ThenByDescending(i=>i.Supplier);
The first call return IOrderedEnumerable<>, the which can sort by additional fields.
Answering my own question (but not satisfactorily). To construct a descending alphabetic key I used this code and then appended this subkey to the search key for the object:
if ( reverse )
subkey = encoding.GetString(encoding.GetBytes(subkey)
.Select(x => (byte)(0x80 - x)).ToArray());
rowobj.sortKey.Append(subkey);
Once I had the keys built, I couldn't just do this:
rowobjList.Sort();
Because the default comparator isn't in ASCII order (which my 0x80 - x trick relies on). So then I had to write an IComparable<RowObject> that used the Ordinal sorting:
public int CompareTo(RowObject other)
{
return String.Compare(this.sortKey, other.sortKey,
StringComparison.Ordinal);
}
This seems to work. I'm a little dissatisfied because it feels clunky in C# with the encoding/decoding of the string.
If a key computation is expensive, why compute a key at all? String comparision by itself is not free, it's actually expensive loop through the characters and is not going to perform any better then a custom comparision loop.
In this test custom comparision sort performs about 3 times better then DSU.
Note that DSU key computation is not measured in this test, it's precomputed.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DSUPatternTest
{
[TestClass]
public class DSUPatternPerformanceTest
{
public class Row
{
public int Qty;
public string Name;
public string Supplier;
public string PrecomputedKey;
public void ComputeKey()
{
// Do not need StringBuilder here, String.Concat does better job internally.
PrecomputedKey =
Qty.ToString().PadLeft(4, '0') + " "
+ Name.PadRight(12, ' ') + " "
+ Supplier.PadRight(12, ' ');
}
public bool Equals(Row other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Qty == Qty && Equals(other.Name, Name) && Equals(other.Supplier, Supplier);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Row)) return false;
return Equals((Row) obj);
}
public override int GetHashCode()
{
unchecked
{
int result = Qty;
result = (result*397) ^ (Name != null ? Name.GetHashCode() : 0);
result = (result*397) ^ (Supplier != null ? Supplier.GetHashCode() : 0);
return result;
}
}
}
public class RowComparer : IComparer<Row>
{
public int Compare(Row x, Row y)
{
int comparision;
comparision = x.Qty.CompareTo(y.Qty);
if (comparision != 0) return comparision;
comparision = x.Name.CompareTo(y.Name);
if (comparision != 0) return comparision;
comparision = x.Supplier.CompareTo(y.Supplier);
return comparision;
}
}
[TestMethod]
public void CustomLoopIsFaster()
{
var random = new Random();
var rows = Enumerable.Range(0, 5000).Select(i =>
new Row
{
Qty = (int) (random.NextDouble()*9999),
Name = random.Next().ToString(),
Supplier = random.Next().ToString()
}).ToList();
foreach (var row in rows)
{
row.ComputeKey();
}
var dsuSw = Stopwatch.StartNew();
var sortedByDSU = rows.OrderBy(i => i.PrecomputedKey).ToList();
var dsuTime = dsuSw.ElapsedMilliseconds;
var customSw = Stopwatch.StartNew();
var sortedByCustom = rows.OrderBy(i => i, new RowComparer()).ToList();
var customTime = customSw.ElapsedMilliseconds;
Trace.WriteLine(dsuTime);
Trace.WriteLine(customTime);
CollectionAssert.AreEqual(sortedByDSU, sortedByCustom);
Assert.IsTrue(dsuTime > customTime * 2.5);
}
}
}
If you need to build a sorter dynamically you can use something like this:
var comparerChain = new ComparerChain<Row>()
.By(r => r.Qty, false)
.By(r => r.Name, false)
.By(r => r.Supplier, false);
var sortedByCustom = rows.OrderBy(i => i, comparerChain).ToList();
Here is a sample implementation of comparer chain builder:
public class ComparerChain<T> : IComparer<T>
{
private List<PropComparer<T>> Comparers = new List<PropComparer<T>>();
public int Compare(T x, T y)
{
foreach (var comparer in Comparers)
{
var result = comparer._f(x, y);
if (result != 0)
return result;
}
return 0;
}
public ComparerChain<T> By<Tp>(Func<T,Tp> property, bool descending) where Tp:IComparable<Tp>
{
Comparers.Add(PropComparer<T>.By(property, descending));
return this;
}
}
public class PropComparer<T>
{
public Func<T, T, int> _f;
public static PropComparer<T> By<Tp>(Func<T,Tp> property, bool descending) where Tp:IComparable<Tp>
{
Func<T, T, int> ascendingCompare = (a, b) => property(a).CompareTo(property(b));
Func<T, T, int> descendingCompare = (a, b) => property(b).CompareTo(property(a));
return new PropComparer<T>(descending ? descendingCompare : ascendingCompare);
}
public PropComparer(Func<T, T, int> f)
{
_f = f;
}
}
It works a little bit slower, maybe because of property binging delegate calls.