C# 用布尔函数对列表排序的较短方法

C# 用布尔函数对列表排序的较短方法,c#,sorting,predicate,C#,Sorting,Predicate,我有一个列表,需要以特定的方式进行排序。我现在已经这样解决了: var files = GetFiles() .OrderByDescending(x => x.Filename.StartsWith("ProjectDescription_")) .ThenByDescending(x => x.Filename.StartsWith("Budget_")) .ThenByDescending(x => x.Filename.StartsWith("CV_"))

我有一个列表,需要以特定的方式进行排序。我现在已经这样解决了:

var files = GetFiles()
  .OrderByDescending(x => x.Filename.StartsWith("ProjectDescription_"))
  .ThenByDescending(x => x.Filename.StartsWith("Budget_"))
  .ThenByDescending(x => x.Filename.StartsWith("CV_"))
  .ToArray();
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);
var files = GetFiles().OrderBy(f => (int)f.FileType)
                      .ThenBy(f => f.FileName)
                      .Select(f => f.FileName);
var orderer = new FileNameOrderer();
var f = files.OrderBy(x => orderer.Ordinal(x.Filename)).ToArray();
这些文件将被合并成一个单独的PDF文件,这里的要点是,某些文件应该在开头,其余的文件应该在结尾

我想知道是否有更好的方法来写这个“模式”,因为它感觉很无聊,如果有更多的案例,它会变得更无聊


我想避免的事情,但不确定如何避免:多次通过列表,每个文件的
启动次数比需要的次数多,代码比需要的次数多,等等

基本上,我想我想要一个
OrderByPredicates
类型的东西,它巧妙地满足了这些标准,它的API是这样使用的:

var files = GetFiles()
  .OrderByDescending(x => x.Filename.StartsWith("ProjectDescription_"))
  .ThenByDescending(x => x.Filename.StartsWith("Budget_"))
  .ThenByDescending(x => x.Filename.StartsWith("CV_"))
  .ToArray();
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);
var files = GetFiles().OrderBy(f => (int)f.FileType)
                      .ThenBy(f => f.FileName)
                      .Select(f => f.FileName);
var orderer = new FileNameOrderer();
var f = files.OrderBy(x => orderer.Ordinal(x.Filename)).ToArray();
var谓词=newfunc[]{
x=>x.Filename==“第一个”
x=>x.Filename.StartsWith(“Foo_u2;”),
x=>x.Filename.StartsWith(“Bar”),
};
var files=GetFiles()
.OrderBy谓词(谓词)
.ThenBy(x=>x.Filename);
二的幂

var files = GetFiles()
  .Order(x => (x.Filename.StartsWith("ProjectDescription_") ? 4 : 0) + 
              (x.Filename.StartsWith("Budget_") ? 2 : 0) +
              (x.Filename.StartsWith("CV_") ? 1 : 0))
  .ToArray()
请注意,我删除了下降,并为StartsWith使用了反向权重

它可能比您的算法更慢,因为此算法每次比较都需要3x
StartsWith
,而您的算法可以在第一次
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();
string[]orders=新字符串[]{“项目描述”、“预算”、“简历”};
var files=GetFiles()
.OrderByDescending(x=>x.Filename.StartsWith(orders[0]);
for(int i=1;ix.Filename.StartsWith(orders[i]);
}
var files2=files.ToArray();

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

我可以看到的一种方法是,使其更简洁,这将增加一点总体复杂性,但提供更简洁的排序机制

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

public enum FileType
{
    ProjectDescription,
    Budget,
    CV
}
然后,为文件创建一个小包装器:

public class FileWrapper
{
    public FileType FileType { get; set; }
    public string FileName { get; set; }
}
最后,当您收集所有文件时,您将在新类中设置它们,您的查询将如下所示:

var files = GetFiles()
  .OrderByDescending(x => x.Filename.StartsWith("ProjectDescription_"))
  .ThenByDescending(x => x.Filename.StartsWith("Budget_"))
  .ThenByDescending(x => x.Filename.StartsWith("CV_"))
  .ToArray();
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);
var files = GetFiles().OrderBy(f => (int)f.FileType)
                      .ThenBy(f => f.FileName)
                      .Select(f => f.FileName);
var orderer = new FileNameOrderer();
var f = files.OrderBy(x => orderer.Ordinal(x.Filename)).ToArray();
如果你不介意,你可以先省略
,然后再省略

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

紧凑(除了一个小助手方法)且易于扩展:

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));
private static readonly string[]前缀={“ProjectDescription”、“Budget”、“CV”};
公共静态int-PrefixIndex(字符串名称)
{
for(int i=0;iPrefixIndex(x.Name));

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

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

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

基于您的几个答案和进一步思考,我提出了这个类,我认为它相当干净。应该是非常通用的,并且应该相当容易维护,即使有更多的谓词或顺序变化

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;
    }
}
公共类OrderedPredicatesComparer:IComparer
{
私有只读Func[]序数;
public OrderedPredicatesComparer(IEnumerable谓词)
{
序数=谓词。ToArray();
}
公共整数比较(TX,TY)
{
返回GetOrdinal(x)-GetOrdinal(y);
}
私有整数GetOrdinal(T项)
{
for(int i=0;i

基于我的原始问题的示例用法:

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();
var排序=新函数[]
{
x=>x.StartsWith(“项目描述”),
x=>x.StartsWith(“预算”),
x=>x.StartsWith(“CV_”),
};
var files=GetFiles()
.OrderBy(x=>x.Filename,新的OrderedPredicatesComparer(排序))
.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();
公共类mySpecificRecording:OrderedPredicatesComparer
{
私有静态只读Func[]顺序=新Func[]
{
x=>x.StartsWith(“项目描述”),
x=>x.StartsWith(“预算”),
x=>x.StartsWith(“CV_”),
};
public MySpecificOrdering():基(顺序){}
}
var files=GetFiles()
.OrderBy(x=>x.Filename,新的MySpecificOrdering())
.ThenBy(x=>x.Filename)
.ToArray();

欢迎评论中的反馈:)

尽管我同意其他人的观点,最好将order封装在另一个类中,但下面是将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);
  }
}

这是尽可能通用的

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));
}
但实际上你需要它是递减的,但是如果你需要一些字段是递增的而另一些字段不是呢?(上升为true,下降为false)

public静态IOrderedEnumerable OrderByPredicates(此IOrderedEnumerable集合,IEnumerable funcs)
{
如果(!funcs.Any())
{
抛出新ArgumentException();
}
var firstFunction=funcs.First();
返回函数跳过(1).聚合(
firstFunction.Key?collection.OrderBy(firstFunction.Value):collection.OrderByDescending(firstFunction.Value)
,(lst,f)=>f.键?lst.ThenBy(f.值):lst.ThenBy降序(f.值));
}
但这将更难使用

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);
var谓词=新的KeyValuePair[]{
新的KeyValuePair(false,x=>x.FileName==“First”),
新的KeyValuePair(false,x=>x.FileName.StartsWith(“Foo_”)),
新的KeyValuePair(false,x=>x.File