通过布尔函数排序列表的更短方式

时间:2015-03-04 13:31:41

标签: c# sorting predicate

我有一个需要以特定方式订购的清单。我现在解决了这个问题:

var files = GetFiles()
  .OrderByDescending(x => x.Filename.StartsWith("ProjectDescription_"))
  .ThenByDescending(x => x.Filename.StartsWith("Budget_"))
  .ThenByDescending(x => x.Filename.StartsWith("CV_"))
  .ToArray();

这些文件将合并为一个PDF文件,这里的重点是某些文件应该在开头,其余文件应该在最后。

我想知道是否有更好的方式来编写这种“模式”,因为如果有更多的情况,它会感觉相当啰嗦并且会变得更加黯然失色。


我想避免的事情,但不确定如何:多次通过列表,每个文件的调用次数超过必要的StartsWith次,代码超出必要的数量等等。

基本上我认为我想要OrderByPredicates类似于巧妙地满足这些标准的东西,其API使用方式如下:

var predicates = new Func<boolean, File>[] {
  x => x.Filename == "First"
  x => x.Filename.StartsWith("Foo_"),
  x => x.Filename.StartsWith("Bar_"),
};

var files = GetFiles()
  .OrderByPredicates(predicates)
  .ThenBy(x => x.Filename);

7 个答案:

答案 0 :(得分:7)

紧凑(除了一点辅助方法)并且易于扩展:

private static readonly string[] Prefixes = {"ProjectDescription_", "Budget_", "CV_"};

public static int PrefixIndex(string name)
{
  for (int i = 0; i < Prefixes.Length; i++)
  {
    if (name.StartsWith(Prefixes[i]))
    {
      return i;
    }
  }
  return int.MaxValue;
}

// ...

var files = GetFiles().OrderBy(x => PrefixIndex(x.Name));

答案 1 :(得分:3)

两个人的权力?

var files = GetFiles()
  .Order(x => (x.Filename.StartsWith("ProjectDescription_") ? 4 : 0) + 
              (x.Filename.StartsWith("Budget_") ? 2 : 0) +
              (x.Filename.StartsWith("CV_") ? 1 : 0))
  .ToArray()

请注意,我删除了Descending并使用了StartsWith的反向权重。

它可能甚至比你的慢,因为这个算法总是需要3x StartsWith进行每次比较,而你的可以&#34;阻止&#34;在第一个StartsWith

请注意,我可能会这样做:

string[] orders = new string[] { "ProjectDescription_", "Budget_", "CV_" };

var files = GetFiles()
    .OrderByDescending(x => x.Filename.StartsWith(orders[0]));

for (int i = 1; i < orders.Length; i++) {
    files = files.ThenByDescending(x => x.Filename.StartsWith(orders[i]));
}

var files2 = files.ToArray();

通过这种方式,我将订单保存在字符串数组中。为了使代码更容易,我还没有检查orders.Length > 0

答案 2 :(得分:2)

我会将排序逻辑封装在一个单独的类中,例如:

class FileNameOrderer
{
    public FileNameOrderer()
    {
        // Add new prefixes to the following list in the order you want:

        orderedPrefixes = new List<string>
        {
            "CV_",
            "Budget_",
            "ProjectDescription_"
        };
    }

    public int Ordinal(string filename)
    {
        for (int i = 0; i < orderedPrefixes.Count; ++i)
            if (filename.StartsWith(orderedPrefixes[i]))
                return i;

        return orderedPrefixes.Count;
    }

    private readonly List<string> orderedPrefixes;
}

然后,如果你需要添加一个新项目,你只需要将它添加到前缀列表中,而不需要更改其他代码。

您可以这样使用它:

var orderer = new FileNameOrderer();
var f = files.OrderBy(x => orderer.Ordinal(x.Filename)).ToArray();

当然,这是更多的代码行,但它似乎更好地封装并且更容易更改。

答案 3 :(得分:1)

我可以看到一种让它更清洁的方法会增加一点整体复杂性,但提供更清洁的订购机制。

首先,我将创建不同类型文件的枚举:

public enum FileType
{
    ProjectDescription,
    Budget,
    CV
}

然后,为文件创建一个小包装器:

public class FileWrapper
{
    public FileType FileType { get; set; }
    public string FileName { get; set; }
}

最后,当你收集所有文件时,你会在新课程中设置它们,你的查询将是这样的:

var files = GetFiles().OrderBy(f => (int)f.FileType)
                      .ThenBy(f => f.FileName)
                      .Select(f => f.FileName);

如果您不在乎,可以随时省略ThenBy

总的来说,有三个有点矫枉过正,但如果你的流程中添加了其他类型的文件,这将为您提供最大的灵活性,并允许您的查询保持不变。

答案 4 :(得分:1)

基于你的几个答案和一些进一步的思考,我想出了这个课程,我认为这个课程相当干净。应该非常通用,即使有更多的谓词或顺序更改, 也应该很容易维护。

public class OrderedPredicatesComparer<T> : IComparer<T>
{
    private readonly Func<T, bool>[] ordinals;
    public OrderedPredicatesComparer(IEnumerable<Func<T, bool>> predicates)
    {
        ordinals = predicates.ToArray();
    }

    public int Compare(T x, T y)
    {
        return GetOrdinal(x) - GetOrdinal(y);
    }

    private int GetOrdinal(T item)
    {
        for (int i = 0; i < ordinals.Length; i++)
            if (ordinals[i](item))
                return i - ordinals.Length;
        return 0;
    }
}

基于原始问题的使用示例:

var ordering = new Func<string, bool>[]
    {
        x => x.StartsWith("ProjectDescription_"),
        x => x.StartsWith("Budget_"),
        x => x.StartsWith("CV_"),
    };

var files = GetFiles()
    .OrderBy(x => x.Filename, new OrderedPredicatesComparer<string>(ordering))
    .ThenBy(x => x.Filename)
    .ToArray();

或者,可以将订单封装在子类中,以使最终代码更清晰:

public class MySpecificOrdering : OrderedPredicatesComparer<string>
{
    private static readonly Func<string, bool>[] order = new Func<string, bool>[]
        {
            x => x.StartsWith("ProjectDescription_"),
            x => x.StartsWith("Budget_"),
            x => x.StartsWith("CV_"),
        };

    public MySpecificOrdering() : base(order) {}
}

var files = GetFiles()
    .OrderBy(x => x.Filename, new MySpecificOrdering())
    .ThenBy(x => x.Filename)
    .ToArray();

欢迎评论中的反馈:)

答案 5 :(得分:1)

虽然我同意其他人认为最好将订单封装在另一个类中,但这是尝试将OrderByPredicates()作为扩展方法:

public static class FileOrderExtensions
{
  public static IOrderedEnumerable<File> OrderByPredicates(this IEnumerable<File> files, Func<File, bool>[] predicates)
  {
    var lastOrderPredicate = new Func<File, bool>(file => true);

    var predicatesWithIndex = predicates
      .Concat(new [] { lastOrderPredicate })
      .Select((predicate, index) => new {Predicate = predicate, Index = index});

    return files
      .OrderBy(file => predicatesWithIndex.First(predicateWithIndex => predicateWithIndex.Predicate(file)).Index);
  }
}

使用此扩展方法,您可以完全按照自己的意愿执行操作:

using FileOrderExtensions;

var files = GetFiles()
  .OrderByPredicates(predicates)
  .ThenBy(x => x.Filename); 

答案 6 :(得分:1)

这是通用的

public static IOrderedEnumerable<T> OrderByPredicates<T, U>(this IEnumerable<T> collection, IEnumerable<Func<T, U>> funcs)
{
    if(!funcs.Any())
    {
        throw new ArgumentException();
    }
    return funcs.Skip(1)
       .Aggregate(collection.OrderBy(funcs.First()), (lst, f) => lst.ThenBy(f));
}

并像那样使用它。如果你想合并最后一个&#34;那么&#34;使用OrderByPredicates,只需使用Func集合

var predicates = new Func<File, bool>[]
{
    x => x.FileName == "First",
    x => x.FileName.StartsWith("Foo_"),
    x => x.FileName.StartsWith("Bar_")
};
var files = GetFiles()
            .OrderByPredicates(predicates)
            .ThenBy(x => x.Filename);

你可以给函数一个已经有序的集合,这样实现起来就会简单得多。

public static IOrderedEnumerable<T> ThenByPredicates<T,U>(this IOrderedEnumerable<T> collection, IEnumerable<Func<T, U>> funcs)
{
    return funcs.Aggregate(collection, (lst, f) => lst.ThenBy(f));
}

主要优点是您可以实现&#34; ThenByDescendingPredicates&#34;功能也是。

GetFiles().OrderByDescending(x=>...).ThenByPredicates(predicates).ThenByPredicatesDescending(descendingsPredicate);

但实际上你需要它才能下降,但是如果你需要一些领域升级而其他领域没有呢? (对于升序为true,对于降序为false)

public static IOrderedEnumerable<T> OrderByPredicates<T, U>(this IOrderedEnumerable<T> collection, IEnumerable<KeyValuePair<bool, Func<T, U>>> funcs)
{

    if(!funcs.Any())
    {
        throw new ArgumentException();
    }
    var firstFunction = funcs.First();
    return funcs.Skip(1).Aggregate(
         firstFunction.Key?collection.OrderBy(firstFunction.Value):collection.OrderByDescending(firstFunction.Value)
        , (lst, f) => f.Key ? lst.ThenBy(f.Value) : lst.ThenByDescending(f.Value));
}

但是使用

会更难
var predicates = new KeyValuePair<bool, Func<File, bool>>[] {
          new KeyValuePair<bool, Func<string, bool>>(false, x => x.FileName == "First"),
          new KeyValuePair<bool, Func<string, bool>>(false, x => x.FileName.StartsWith("Foo_")),
          new KeyValuePair<bool, Func<string, bool>>(false, x => x.FileName.StartsWith("Bar_")),
        };

var files = GetFiles()
            .OrderByPredicates(predicates)
            .ThenBy(x => x.Filename);