it-swarm.com.de

Linq OrderBy gegen bestimmte Werte

Gibt es in Linq eine Möglichkeit, einen OrderBy gegen eine Menge von Werten (in diesem Fall Strings) auszuführen, ohne die Reihenfolge der Werte zu kennen?

Betrachten Sie diese Daten:

A
B
A
C
B
C
D
E

Und diese Variablen:

string firstPref, secondPref, thirdPref;

Wenn die Werte so eingestellt sind:

firstPref = 'A';
secondPref = 'B';
thirdPref = 'C';

Ist es möglich, die Daten so zu bestellen:

A
A
B
B
C
C
D
E
38
Jonathan Parker

Wenn Sie Ihre Einstellungen in eine Liste aufnehmen, wird es möglicherweise einfacher.

List<String> data = new List<String> { "A","B","A","C","B","C","D","E" };
List<String> preferences = new List<String> { "A","B","C" };

IEnumerable<String> orderedData = data.OrderBy(
   item => preferences.IndexOf(item));

Dadurch werden alle Elemente, die nicht in preferences erscheinen, in den Vordergrund gestellt, da IndexOf()-1 zurückgibt. Eine Ad-hoc-Lösung könnte preferences rückgängig machen und das Ergebnis absteigend sortieren. Das wird ziemlich hässlich, funktioniert aber.

IEnumerable<String> orderedData = data.OrderByDescending(
   item => Enumerable.Reverse(preferences).ToList().IndexOf(item));

Die Lösung wird etwas schöner, wenn Sie preferences und data verwenden.

IEnumerable<String> orderedData = data.OrderBy(
   item => preferences.Concat(data).ToList().IndexOf(item));

Ich mag Concat() und ToList() dort nicht. Aber im Moment habe ich keinen wirklich guten Weg. Ich suche nach einem schönen Trick, um den -1 des ersten Beispiels in eine große Zahl zu verwandeln.

97

Zusätzlich zu @Daniel Brückner answer und dem am Ende definierten Problem:

Ich mag Concat () und ToList () dort nicht. Aber im Moment habe ich keinen wirklich guten Weg, um das zu umgehen. Ich suche einen schönen Trick, um aus dem -1 des ersten Beispiels eine große Zahl zu machen.

Ich denke, dass die Lösung darin besteht, eine Anweisung Lambda anstelle eines Ausdrucks Lambda zu verwenden.

var data = new List<string> { "corge", "baz", "foo", "bar", "qux", "quux" };
var fixedOrder = new List<string> { "foo", "bar", "baz" };
data.OrderBy(d => {
                    var index = fixedOrder.IndexOf(d);
                    return index == -1 ? int.MaxValue : index; 
                  });

Die bestellten Daten sind:

foo 
bar 
baz 
corge 
qux 
quux 
16
alexqc

Fügen Sie die bevorzugten Werte in ein Wörterbuch ein. Das Nachschlagen von Schlüsseln in einem Wörterbuch ist eine O(1) -Operation, verglichen mit dem Finden von Werten in einer Liste, die eine O(n) -Operation ist, sodass sie viel besser skaliert wird.

Erstellen Sie eine Sortierzeichenfolge für jeden bevorzugten Wert, sodass sie vor den anderen Werten platziert wird. Für die anderen Werte wird der Wert selbst als Sortierzeichenfolge verwendet, damit sie tatsächlich sortiert werden. (Wenn Sie einen beliebig hohen Wert verwenden, werden diese nur am Ende der Liste unsortiert.).

List<string> data = new List<string> {
    "E", "B", "D", "A", "C", "B", "A", "C"
};
var preferences = new Dictionary<string, string> {
    { "A", " 01" },
    { "B", " 02" },
    { "C", " 03" }
};

string key;
IEnumerable<String> orderedData = data.OrderBy(
    item => preferences.TryGetValue(item, out key) ? key : item
);
4
Guffa

Ja, Sie müssen Ihren eigenen IComparer<string> implementieren und diesen dann als zweites Argument der LINQ-Methode OrderBy übergeben.

Ein Beispiel finden Sie hier: LINQ-Ergebnisse bestellen

1
Ben Hoffstein

Kombiniert alle Antworten (und mehr) in eine generische LINQ-Erweiterung, die das Zwischenspeichern unterstützt, die jeden Datentyp verarbeitet, die Groß- und Kleinschreibung nicht berücksichtigt und die Verkettung mit Vor- und Nachbestellung ermöglicht:

public static class SortBySample
{
    public static BySampleSorter<TKey> Create<TKey>(IEnumerable<TKey> fixedOrder, IEqualityComparer<TKey> comparer = null)
    {
        return new BySampleSorter<TKey>(fixedOrder, comparer);
    }

    public static BySampleSorter<TKey> Create<TKey>(IEqualityComparer<TKey> comparer, params TKey[] fixedOrder)
    {
        return new BySampleSorter<TKey>(fixedOrder, comparer);
    }

    public static IOrderedEnumerable<TSource> OrderBySample<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, BySampleSorter<TKey> sample)
    {
        return sample.OrderBySample(source, keySelector);
    }

    public static IOrderedEnumerable<TSource> ThenBySample<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, BySampleSorter<TKey> sample)
    {
        return sample.ThenBySample(source, keySelector);
    }
}

public class BySampleSorter<TKey>
{
    private readonly Dictionary<TKey, int> dict;

    public BySampleSorter(IEnumerable<TKey> fixedOrder, IEqualityComparer<TKey> comparer = null)
    {
        this.dict = fixedOrder
            .Select((key, index) => new KeyValuePair<TKey, int>(key, index))
            .ToDictionary(kv => kv.Key, kv => kv.Value, comparer ?? EqualityComparer<TKey>.Default);
    }

    public BySampleSorter(IEqualityComparer<TKey> comparer, params TKey[] fixedOrder)
        : this(fixedOrder, comparer)
    {
    }

    public IOrderedEnumerable<TSource> OrderBySample<TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.OrderBy(item => this.GetOrderKey(keySelector(item)));
    }

    public IOrderedEnumerable<TSource> ThenBySample<TSource>(IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.CreateOrderedEnumerable(item => this.GetOrderKey(keySelector(item)), Comparer<int>.Default, false);
    }

    private int GetOrderKey(TKey key)
    {
        int index;
        return dict.TryGetValue(key, out index) ? index : int.MaxValue;
    }
}

Verwendungsbeispiel mit LINQPad-Dump ():

var sample = SortBySample.Create(StringComparer.OrdinalIgnoreCase, "one", "two", "three", "four");
var unsorted = new[] {"seven", "six", "five", "four", "THREE", "tWo", "One", "zero"};
unsorted
    .OrderBySample(x => x, sample)
    .ThenBy(x => x)
    .Dump("sorted by sample then by content");
unsorted
    .OrderBy(x => x.Length)
    .ThenBySample(x => x, sample)
    .Dump("sorted by length then by sample");
1
springy76

Für große Listen nicht wirklich effizient, aber leicht lesbar:

public class FixedOrderComparer<T> : IComparer<T>
{
    private readonly T[] fixedOrderItems;

    public FixedOrderComparer(params T[] fixedOrderItems)
    {
        this.fixedOrderItems = fixedOrderItems;
    }

    public int Compare(T x, T y)
    {
        var xIndex = Array.IndexOf(fixedOrderItems, x);
        var yIndex = Array.IndexOf(fixedOrderItems, y);
        xIndex = xIndex == -1 ? int.MaxValue : xIndex;
        yIndex = yIndex == -1 ? int.MaxValue : yIndex;
        return xIndex.CompareTo(yIndex);
    }
}

Verwendungszweck:

var orderedData = data.OrderBy(x => x, new FixedOrderComparer<string>("A", "B", "C"));

Hinweis: Array.IndexOf<T>(....) verwendet EqualityComparer<T>.Default, um den Zielindex zu finden.

0
Mike Rowley

Die Danbrucs-Lösung ist eleganter, aber hier ist eine Lösung mit einem benutzerdefinierten IComparer. Dies kann nützlich sein, wenn Sie erweiterte Bedingungen für Ihre Sortierreihenfolge benötigen.

    string[] svals = new string[] {"A", "B", "A", "C", "B", "C", "D", "E"};
    List<string> list = svals.OrderBy(a => a, new CustomComparer()).ToList();

    private class CustomComparer : IComparer<string>
    {
        private string firstPref = "A";
        private string secondPref = "B";
        private string thirdPref = "C";
        public int Compare(string x, string y)
        {
            // first pref 
            if (y == firstPref && x == firstPref)
                return 0;
            else if (x == firstPref && y != firstPref)
                return -1;
            else if (y == firstPref && x != firstPref)
                return 1;
            // second pref
            else if (y == secondPref && x == secondPref)
                return 0;
            else if (x == secondPref && y != secondPref)
                return -1;
            else if (y == secondPref && x != secondPref)
                return 1;
            // third pref
            else if (y == thirdPref && x == thirdPref)
                return 0;
            else if (x == thirdPref && y != thirdPref)
                return -1;
            else
                return string.Compare(x, y);
        }
    }
0
James