Decorate-Sort-Undecorate, как отсортировать алфавитное поле в порядке убывания - PullRequest
3 голосов
/ 25 января 2010

У меня есть большой набор данных, для которых вычисление ключа сортировки довольно дорого. Я хотел бы использовать шаблон 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. Числа выровнены по правому краю, а текст выровнен по левому краю, все дополняется по мере необходимости.

Если мне нужно отсортировать по количеству в порядке по убыванию , я могу просто вычесть значение из константы (скажем, 10000) для построения ключей сортировки: 9950 IBM, 9952 Dell, 9901 IBM.

Как быстро / дешево построить нисходящий ключ для буквенных полей в C #?

[Все мои данные - это 8-битные символы ASCII с расширением ISO 8859.]

Примечание: в Perl это можно сделать с помощью битового дополнения строк :

 $subkey = $string ^ ( "\xFF" x length $string );

Портирование этого решения прямо в C # не работает:

 subkey = encoding.GetString(encoding.GetBytes(stringval).
                      Select(x => (byte)(x ^ 0xff)).ToArray());

Подозреваю, из-за различий в способах обработки строк в C # / Perl. Может быть, Perl сортирует в порядке ASCII, а C # пытается быть умным?

Вот пример кода, который пытается это сделать:

        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;

Ответы [ 4 ]

2 голосов
/ 26 января 2010

Вы можете добраться туда, куда хотите, хотя, признаюсь, я не знаю, есть ли лучший общий способ.

Проблема, с которой вы сталкиваетесь при прямом переводе метода 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 к карте обратно в диапазон печати

Это не очень хорошо, но работает.

0 голосов
/ 27 января 2010

Если вычисление ключа стоит дорого, зачем вообще вычислять ключ? Сравнение строк само по себе не является бесплатным, это на самом деле дорогой цикл по символам и не будет работать лучше, чем пользовательский цикл сравнения.

В этом тесте пользовательская сортировка сравнения работает примерно в 3 раза лучше, чем DSU.

Обратите внимание, что вычисление ключа 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;
        }
    }

Он работает немного медленнее, возможно, из-за вызовов делегирования binging для свойства.

0 голосов
/ 26 января 2010

Отвечая на мой вопрос (но неудовлетворительно).Чтобы создать нисходящий алфавитный ключ, я использовал этот код, а затем добавил этот подраздел к ключу поиска объекта:

   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 # с кодированием / декодированием строки.

0 голосов
/ 26 января 2010

Просто напишите 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 <>, который можно сортировать по дополнительным полям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...