Decorate-Sort-Undecorate,如何按降序对字母字段进行排序

时间:2010-01-25 20:47:02

标签: c# sorting

我有一大堆数据,计算排序键相当昂贵。我想要做的是使用DSU模式,我获取行并计算排序键。一个例子:

        Qty   Name      Supplier   
Row 1:   50   Widgets   IBM
Row 2:   48   Thingies  Dell
Row 3:   99   Googaws   IBM

按数量和供应商排序我可以使用排序键:0050 IBM0048 Dell0099 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());

4 个答案:

答案 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)
  • 将范围映射到0..95以使其从零开始
  • 从95
  • 减去
  • 添加32以映射回可打印范围

它不是很好,但确实有效。

答案 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;
        }
    }

它的工作速度有点慢,可能是因为属性绑定委托调用。