web-dev-qa-db-ja.com

Linq OrderByで独自のIComparer <T>を使用する

ジェネリックがあります

_List<MyClass>
_

MyClassには、次のような値を含むプロパティInvoiceNumberがあります。

200906/1
200906/2
..
200906/10
200906/11
200906/12

私のリストは

_BindingList<T>
_

linqでのソートをサポートしています:

_protected override void ApplySortCore(
           PropertyDescriptor property, ListSortDirection direction)
{

    _sortProperty = property;
    _sortDirection = direction;

    var items = this.Items;

    switch (direction)
    {
        case ListSortDirection.Ascending:
            items = items.OrderByDescending(x => property.GetValue(x)).ToList();
            break;
        case ListSortDirection.Descending:
            items = items.OrderByDescending(x => property.GetValue(x)).ToList();
            break;
    }

    this.Items = items;

}
_

ただし、デフォルトの比較演算子は(想定どおり)次のようにソートします。

200906/1
200906/10
200906/11
200906/12
200906/2

この場合は厄介です。

ここで、これで独自の_IComparer<T>_を使用します。次のようになります。

_public class MyComparer : IComparer<Object>
{

    public int Compare(Object stringA, Object stringB)
    {
        String[] valueA = stringA.ToString().Split('/');
        String[] valueB = stringB.ToString().Split('/');

        if(valueA .Length != 2 || valueB .Length != 2)
             return String.Compare(stringA.ToString(), stringB.ToString());

        if (valueA[0] == valueB[0]) 
        {
          return String.Compare(valueA[1], valueB[1]);
        }
        else
        {
          return String.Compare(valueA[0], valueB[0]);
        }

    }

}
_

このApplySortCoreを使用するようにIComparerコードを変更しました:

_case ListSortDirection.Ascending:
    MyComparer comparer = new MyComparer();
    items = items.OrderByDescending(
              x => property.GetValue(x), comparer).ToList();
    break;
_

コードをデバッグすると、MyComparer.Compare(object, object)が複数回呼び出され、比較メソッドに対して正しい値(-1、0、1)が返されることがわかります。

しかし、私のリストは依然として「間違った」方法でソートされています。何か不足していますか?私は見当もつかない。

30

あなたの比較者は私には間違っているようです。あなたはまだデフォルトのテキストの順序でソートしているだけです。確かに、2つの数値を解析し、それに基づいてソートする必要があります。

public int Compare(Object stringA, Object stringB)
{
    string[] valueA = stringA.ToString().Split('/');
    string[] valueB = stringB.ToString().Split('/');

    if (valueA.Length != 2 || valueB.Length != 2)
    {
        stringA.ToString().CompareTo(stringB.ToString());
    }

    // Note: do error checking and consider i18n issues too :)
    if (valueA[0] == valueB[0]) 
    {
        return int.Parse(valueA[1]).CompareTo(int.Parse(valueB[1]));
    }
    else
    {
        return int.Parse(valueA[0]).CompareTo(int.Parse(valueB[0]));
    }
}

(これは、Compareが正しい値を返していることをデバッグして検証したというあなたの質問とは相性が悪いことに注意してください。

さらに、Svenの権利-itemsの値を変更してもバインドリストはまったく変更されません。以下を追加する必要があります。

this.Items = items;

メソッドの下部にあります。

17
Jon Skeet

私は一般的な自然な分類の問題に遭遇し、解決策をここにブログしました:

Linq OrderBy()との自然ソート比較

public class NaturalSortComparer<T> : IComparer<string>, IDisposable
{
    private bool isAscending;

    public NaturalSortComparer(bool inAscendingOrder = true)
    {
        this.isAscending = inAscendingOrder;
    }

    #region IComparer<string> Members

    public int Compare(string x, string y)
    {
        throw new NotImplementedException();
    }

    #endregion

    #region IComparer<string> Members

    int IComparer<string>.Compare(string x, string y)
    {
        if (x == y)
            return 0;

        string[] x1, y1;

        if (!table.TryGetValue(x, out x1))
        {
            x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
            table.Add(x, x1);
        }

        if (!table.TryGetValue(y, out y1))
        {
            y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
            table.Add(y, y1);
        }

        int returnVal;

        for (int i = 0; i < x1.Length && i < y1.Length; i++)
        {
            if (x1[i] != y1[i])
            {
                returnVal = PartCompare(x1[i], y1[i]);
                return isAscending ? returnVal : -returnVal;
            }
        }

        if (y1.Length > x1.Length)
        {
            returnVal = 1;
        }
        else if (x1.Length > y1.Length)
        { 
            returnVal = -1; 
        }
        else
        {
            returnVal = 0;
        }

        return isAscending ? returnVal : -returnVal;
    }

    private static int PartCompare(string left, string right)
    {
        int x, y;
        if (!int.TryParse(left, out x))
            return left.CompareTo(right);

        if (!int.TryParse(right, out y))
            return left.CompareTo(right);

        return x.CompareTo(y);
    }

    #endregion

    private Dictionary<string, string[]> table = new Dictionary<string, string[]>();

    public void Dispose()
    {
        table.Clear();
        table = null;
    }
}
9
James McCormack

Alphanumアルゴリズムを使用できます。

  (...)
    items.OrderBy(x => property.GetValue(x), new AlphanumComparator())
  (...)

AlphanumComparator

/*
 * The Alphanum Algorithm is an improved sorting algorithm for strings
 * containing numbers.  Instead of sorting numbers in ASCII order like
 * a standard sort, this algorithm sorts numbers in numeric order.
 *
 * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
 *
 * Based on the Java implementation of Dave Koelle's Alphanum algorithm.
 * Contributed by Jonathan Ruckwood <[email protected]>
 *
 * Adapted by Dominik Hurnaus <[email protected]> to
 *   - correctly sort words where one Word starts with another Word
 *   - have slightly better performance
 *
 * Released under the MIT License - https://opensource.org/licenses/MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
using System;
using System.Collections;
using System.Text;

/*
 * Please compare against the latest Java version at http://www.DaveKoelle.com
 * to see the most recent modifications
 */
namespace AlphanumComparator
{
    public class AlphanumComparator : IComparer
    {
        private enum ChunkType {Alphanumeric, Numeric};
        private bool InChunk(char ch, char otherCh)
        {
            ChunkType type = ChunkType.Alphanumeric;

            if (char.IsDigit(otherCh))
            {
                type = ChunkType.Numeric;
            }

            if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
                || (type == ChunkType.Numeric && !char.IsDigit(ch)))
            {
                return false;
            }

            return true;
        }

        public int Compare(object x, object y)
        {
            String s1 = x as string;
            String s2 = y as string;
            if (s1 == null || s2 == null)
            {
                return 0;
            }

            int thisMarker = 0, thisNumericChunk = 0;
            int thatMarker = 0, thatNumericChunk = 0;

            while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
            {
                if (thisMarker >= s1.Length)
                {
                    return -1;
                }
                else if (thatMarker >= s2.Length)
                {
                    return 1;
                }
                char thisCh = s1[thisMarker];
                char thatCh = s2[thatMarker];

                StringBuilder thisChunk = new StringBuilder();
                StringBuilder thatChunk = new StringBuilder();

                while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0])))
                {
                    thisChunk.Append(thisCh);
                    thisMarker++;

                    if (thisMarker < s1.Length)
                    {
                        thisCh = s1[thisMarker];
                    }
                }

                while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0])))
                {
                    thatChunk.Append(thatCh);
                    thatMarker++;

                    if (thatMarker < s2.Length)
                    {
                        thatCh = s2[thatMarker];
                    }
                }

                int result = 0;
                // If both chunks contain numeric characters, sort them numerically
                if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
                {
                    thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
                    thatNumericChunk = Convert.ToInt32(thatChunk.ToString());

                    if (thisNumericChunk < thatNumericChunk)
                    {
                        result = -1;
                    }

                    if (thisNumericChunk > thatNumericChunk)
                    {
                        result = 1;
                    }
                }
                else
                {
                    result = thisChunk.ToString().CompareTo(thatChunk.ToString());
                }

                if (result != 0)
                {
                    return result;
                }
            }

            return 0;
        }
    }
}
6

このようにすることはできません:

public class MyComparer : IComparer<string>
{

    public int Compare(string stringA, string stringB)
    {
        string small = stringA;
        string big = stringB;
        if (stringA.Length > stringB.Length)
        {
            small = stringB;
            big = stringA;
        }
        else if (stringA.Length < stringB.Length)
        {
            small = stringA;
            big = stringB;
        }
        for (int j = 0; j < small.Length; j++)
        {
            if (Convert.ToInt32(small[j]) > Convert.ToInt32(big[j])) return -1;
            if (Convert.ToInt32(small[j]) < Convert.ToInt32(big[j])) return 1;
        }

        //big is indeed bigger
        if (big.Length > small.Length) return 1;

        //finally they are smae
        return 0;
    }
}

使用法:

string[] inputStrings = {"_abc*&","#almnp","abc" };
//string[] inputStrings = { "#", "_", "_a", "@", "_" };
MyComparer computer = new MyComparer();
var kola = inputStrings.OrderBy(x => x, new MyComparer()).ToArray();

これは次と同じです:

    Array.Sort(inputStrings, StringComparer.Ordinal);
3
Dr.Sai

並べ替えられたリストは、バインドリストのItemsプロパティではなく、ローカル変数アイテムにのみバインドされます。したがって、並べ替えられないままです。

[編集]基本的に、あなたは単にあなたのソート作業の結果を捨てているだけです;-)

2
Sven Künzler