基于另一个列表进行排序的列表排序

35

我有两个泛型列表对象,其中一个包含id和排序信息,另一个包含一堆id,第二个列表中的每个id都与第一个列表中的某个id相关联,例如:

public class OptionType
{
    public int ID { get; set; }
    public int Ordering { get; set; }
}

public class Option
{
    public int ID { get; set; }
    public int Type_ID { get; set; }
}   

显然,我可以通过这样简单地对OptionTypes列表进行排序:

types_list.OrderBy(x => x.Ordering);

问题是,我该如何利用对象上的'Type_ID'来对'options_list'进行排序,以便与'types_list'的排序相关联。例如,类似于以下内容(显然这不是有效的 - 但希望您能理解我的意思!)

options_list.OrderBy(x => x.Type_ID == types_list.OrderBy(e => e.Ordering));

4
我不明白,你能否给我们展示一下在某些输入下排序后的输出可能是什么样子? - Kirk Woll
4个回答

41

您可以使用join来生成所需的输出。以下是使用查询语法的示例。

var orderedOptions = from option in options_list
                     join type in types_list
                     on option.Type_ID equals type.ID
                     orderby type.Ordering
                     select option;

当我使用这个方法时,我得到了“传入的请求参数过多”的错误,我该怎么办? - Mehdi Dehghani
1
做得好。干杯。我正在寻找类似的东西。经常忘记这种linq语法 :-/ - stuartw87

34

List.FindIndex() 可以在 listA 很小且已排序时派上用场:

var orderedB = listB.OrderBy(b => listA.FindIndex(a => a.id == b.id));

工作示例:https://dotnetfiddle.net/CpLeFU

如@goodeye在评论中指出的那样,对于较大的列表,性能将是一场噩梦。在这种情况下,请使用被接受的答案


3
这比连接操作简单得多。假设listA已按所需顺序(type.Ordering)排序。也就是说,它使用listA在列表中的位置而不是listA的属性。它还进行了多个FindIndex操作,因此在处理大型列表时可能存在性能问题。 - goodeye
兄弟,你真的救了我的一天。 - Chanikya

10

我喜欢Lambda的语法,所以我想出了这个等效的语句。我可以看到查询语法在连接方面更加简洁。

var orderedOptions = options_list
    .Join(
        types_list,
        option => option.Type_ID,
        type => type.ID,
        (option, type) => new { Option = option, Type = type })
    .OrderBy(x => x.Type.Ordering)
    .Select(x => x.Option);



为了轻微地减少(什么,我不确定),这个方法只创建具有排序属性的新对象,而不是整个类型类。在这里没有太大的区别,但是我的类非常大,有排序数据,我只需要排序属性。不知道这是否重要,但读起来更清晰。

var orderedOptions = options_list
    .Join(
        types_list,
        option => option.Type_ID,
        type => type.ID,
        (option, type) => new { Option = option, Ordering = type.Ordering })
    .OrderBy(x => x.Ordering)
    .Select(x => x.Option);

看起来查询语法允许在初始查询中排序,而 Lambda 要求在联接创建新对象之后排序。也许它们在幕后真正在做同样的事情:创建连接的对象,然后进行排序和选择。


1

您可以通过实现IComparer接口来使用更加结构化的方式:

public class OptionComparer : IComparer<Option>
{
    // <Id, Sort>
    private readonly Dictionary<int, int> _orderedOptionTypeIds;

    public OptionComparer(IEnumerable<OptionType> optionTypes)
    {
        _orderedOptionTypeIds = optionTypes
            .OrderBy(x => x.Ordering) //If its not sorted already
            .ToDictionary(x => x.ID, x => x.Ordering);
    }

    public int Compare(Option x, Option y)
    {
        int xIndex, yIndex;

        if (!_orderedOptionTypeIds.TryGetValue(x.Type_ID, out xIndex))
        {
            xIndex = int.MaxValue;
        }

        if (!_orderedOptionTypeIds.TryGetValue(y.Type_ID, out yIndex))
        {
            yIndex = int.MaxValue;
        }

        return xIndex.CompareTo(yIndex);
    }
}

然后可以这样使用:
IEnumerable<OptionType> optionTypes = Get(...);

options_list.OrderBy(x => x, new OptionComparer(optionTypes));

1
不错的想法,但是非常耗费资源,因为 _orderedOptionTypeIds 每次比较都会被枚举两次。使用字典(id => index)可以在一定程度上缓解这个问题,但是从资源的角度来看,其他解决方案更好。 - Gert Arnold
你是对的,我假设 _orderedOptionTypeIds 很小(我将其用于固定的小列表 ~10项),但我已经更新了答案以适应更广泛的用途。 - Shahar Shokrani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接