问题 通过布尔函数排序列表的更短方式
我有一个需要以特定方式订购的清单。我现在解决了这个问题:
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);
3057
2018-03-04 13:31
起源
答案:
紧凑(除了一点辅助方法)并且易于扩展:
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));
7
2018-03-04 13:46
两个人的权力?
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的反向权重。
它可能比你的慢,因为这个算法总是需要3倍 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();
通过这种方式,我将订单保存在字符串数组中。为了使代码更容易,我没有检查 orders.Length > 0
3
2018-03-04 13:38
我会将排序逻辑封装在一个单独的类中,例如:
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();
当然,这是更多的代码行,但它似乎更好地封装并且更容易更改。
2
2018-03-04 13:52
我可以看到使其更清洁的一种方式将增加一点整体复杂性,但提供更清洁的订购机制。
首先,我将创建不同类型文件的枚举:
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
如果你不在乎。
总的来说,有三个有点矫枉过正,但如果在您的流程中添加了其他类型的文件,这将为您提供最大的灵活性,并允许您的查询保持不变。
1
2018-03-04 13:43
虽然我同意其他人认为最好将订单封装在另一个类中,但这里尝试将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);
1
2018-03-04 15:01
基于你的几个答案和一些进一步的思考,我想出了这个课程,我认为这个课程相当干净。应该是非常通用的 应该 即使有更多的谓词或顺序变化,也要相当容易维护。
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();
评论中的反馈欢迎:)
1
2018-03-04 14:41
这是尽可能通用的
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));
}
并使用它。如果要将最后一个“ThenBy”与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));
}
主要优点是您可以实现“ThenByDescendingPredicates”功能。
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);
1
2018-03-04 21:13