我有一大堆数据,计算排序键相当昂贵。我想要做的是使用DSU模式,我获取行并计算排序键。一个例子:
Qty Name Supplier
Row 1: 50 Widgets IBM
Row 2: 48 Thingies Dell
Row 3: 99 Googaws IBM
按数量和供应商排序我可以使用排序键:0050 IBM
,0048 Dell
,0099 IBM
。数字是右对齐的,文本是左对齐的,所有内容都根据需要填充。
如果我需要按降序顺序按Quanty排序,我可以从常量(例如10000)中减去该值来构建排序键:9950 IBM
,{{1 },9952 Dell
。
如何快速/廉价地为C#中的字母字段构建降序键?
[我的数据是所有8位ASCII w / ISO 8859扩展字符。]
注意:在Perl中,可以通过bit-complementing the strings:
完成9901 IBM
将此解决方案直接移植到C#中不起作用:
$subkey = $string ^ ( "\xFF" x length $string );
我怀疑是因为在C#/ Perl中处理字符串的方式不同。也许Perl按ASCII顺序排序,而C#试图变得聪明?
以下是尝试实现此目的的示例代码:
subkey = encoding.GetString(encoding.GetBytes(stringval).
Select(x => (byte)(x ^ 0xff)).ToArray());
答案 0 :(得分:2)
你可以到达你想要的地方,虽然我承认我不知道是否有更好的整体方式。
你对Perl方法的直接翻译所遇到的问题是,.NET根本不允许你对编码这么自由放任。但是,如果你说你的数据都是可打印的ASCII(即由Unicode代码点在32..127范围内的字符组成) - 请注意,没有“8位ASCII”这样的东西 - 那么你可以这样做:
key2 = encoding.GetString(encoding.GetBytes(key2).
Select(x => (byte)(32+95-(x-32))).ToArray());
在这个表达式中,我明确表达了我正在做的事情:
x
(我假设在32..127)它不是很好,但确实有效。
答案 1 :(得分:0)
只需编写一个可作为比较器链的IComparer。 如果每个阶段都是平等的,那么应该将其发展到下一个关键部分。如果它小于或等于,则返回。
你需要这样的东西:
int comparision = 0;
foreach(i = 0; i < n; i++)
{
comparision = a[i].CompareTo(b[i]) * comparisionSign[i];
if( comparision != 0 )
return comparision;
}
return comparision;
甚至更简单,你可以选择:
list.OrderBy(i=>i.ID).ThenBy(i=>i.Name).ThenByDescending(i=>i.Supplier);
第一个调用返回IOrderedEnumerable&lt;&gt;,可以按其他字段排序。
答案 2 :(得分:0)
回答我自己的问题(但不令人满意)。要构造一个降序字母键,我使用了这段代码,然后将这个子键附加到该对象的搜索键:
if ( reverse )
subkey = encoding.GetString(encoding.GetBytes(subkey)
.Select(x => (byte)(0x80 - x)).ToArray());
rowobj.sortKey.Append(subkey);
一旦我建了钥匙,我就不能这样做了:
rowobjList.Sort();
因为默认比较器不是ASCII顺序(我的0x80 - x
技巧所依赖的)。所以我不得不写一个使用序数排序的IComparable<RowObject>
:
public int CompareTo(RowObject other)
{
return String.Compare(this.sortKey, other.sortKey,
StringComparison.Ordinal);
}
此似乎可行。我有点不满意,因为在C#中使用字符串的编码/解码感觉很笨拙。
答案 3 :(得分:0)
如果密钥计算很昂贵,为什么要计算密钥呢?字符串比较本身并不是免费的,它实际上是昂贵的字符循环,并且不会比自定义比较循环更好。
在此测试中,自定义比较排序比DSU好大约3倍。
请注意,在此测试中未测量DSU密钥计算,它是预先计算的。
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);
}
}
}
如果您需要动态构建分拣机,可以使用以下内容:
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();
以下是比较器链构建器的示例实现:
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;
}
}
它的工作速度有点慢,可能是因为属性绑定委托调用。