web-dev-qa-db-fra.com

Linq OrderBy contre des valeurs spécifiques

Existe-t-il un moyen dans Linq d’effectuer une commande OrderBy contre un ensemble de valeurs (des chaînes dans ce cas) sans connaître l’ordre des valeurs?

Considérez ces données:

A
B
A
C
B
C
D
E

Et ces variables:

chaîne firstPref, secondPref, thirdPref;

Lorsque les valeurs sont définies comme suit:

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

Est-il possible de commander les données comme suit:

A
A
B
B
C
C
D
E
38
Jonathan Parker

Si vous mettez vos préférences dans une liste, cela deviendra peut-être plus facile.

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));

Cela mettra tous les éléments ne figurant pas dans preferences devant parce que IndexOf() renvoie -1. Un travail ad hoc peut inverser preferences et ordonner le résultat en descendant. Cela devient assez moche, mais fonctionne.

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

La solution devient un peu plus agréable si vous concattez preferences et data.

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

Je n'aime pas Concat() et ToList() ici. Mais pour le moment, je n'ai pas vraiment de solution de rechange. Je cherche une astuce intéressante pour transformer le -1 du premier exemple en un grand nombre.

97
Daniel Brückner

En plus de @Daniel Brückner answer et du problème défini à la fin de celui-ci:

Je n'aime pas Concat () et ToList (). Mais pour le moment, je n'ai pas vraiment de bonne solution. Je cherche une astuce intéressante pour transformer le -1 du premier exemple en un grand nombre.

Je pense que la solution consiste à utiliser une déclaration lambda au lieu d'une expression lambda.

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; 
                  });

Les données commandées sont:

foo 
bar 
baz 
corge 
qux 
quux 
16
alexqc

Mettez les valeurs préférées dans un dictionnaire. La recherche de clés dans un dictionnaire est une opération O(1) par rapport à la recherche de valeurs dans une liste qui est une opération O(n), de sorte que son échelle est bien meilleure.

Créez une chaîne de tri pour chaque valeur préférée afin qu'elles soient placées avant les autres valeurs. Pour les autres valeurs, la valeur elle-même sera utilisée comme chaîne de tri afin d'être effectivement triée. (L'utilisation de n'importe quelle valeur élevée arbitraire les placerait uniquement à la fin de la liste, sans les trier).

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

Oui, vous devez implémenter votre propre IComparer<string>, puis le transmettre comme deuxième argument de la méthode OrderBy de LINQ.

Vous trouverez un exemple ici: Commande de résultats LINQ

1
Ben Hoffstein

La combinaison de toutes les réponses (et bien plus encore) dans une extension LINQ générique prenant en charge la mise en cache, qui gère tout type de données, peut être sensible à la casse et permet d’être chaînée avec des commandes préalables et ultérieures:

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

Exemple d'utilisation avec 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

Pas vraiment efficace pour les grandes listes mais assez facile à lire:

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);
    }
}

Usage:

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

Remarque: Array.IndexOf<T>(....) utilise EqualityComparer<T>.Default pour rechercher l'index cible.

0
Mike Rowley

La solution Danbrucs est plus élégante, mais voici une solution utilisant un IComparer personnalisé. Cela peut être utile si vous avez besoin de conditions plus avancées pour votre ordre de tri.

    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