【问题标题】:Sort a collection and rank the result based on certain criteria对集合进行排序并根据特定条件对结果进行排名
【发布时间】:2014-06-25 15:40:19
【问题描述】:

假设我有以下

var searches = new ObservableCollection<Book>();

searches 包含书籍对象

public class Book
{
    public string Title { get; set;}
    public string Desc {get; set;}    
}

我想通过匹配的字符串对searches 进行排序。首先,它检查Title,然后根据搜索字符串与Title 开头的接近程度对它们进行排名。接下来,它检查Desc 并根据搜索字符串与“Desc”开头的距离对它们进行排名。

例如,如果我有

书 1
书名:ABC书名
Desc: 书 1 的描述

书 2
书名:仅书名
描述:第2册的描述中有一个ABC

第 3 册
书名:书名 ABC
描述: ABC 是在开始

假设搜索关键字是ABC,我希望对searches 进行排序,以便得到以下信息。结果将更高的优先级放在标题中包含搜索字符串的项目。

书 1
书名:ABC书名
Desc: 书 1 的描述

第 3 册
书名:书名 ABC
描述: ABC 是在开始

书 2
书名:仅书名
描述:第2册的描述中有一个ABC

如何使用 LINQ 实现这一点?

【问题讨论】:

  • 到目前为止你尝试了什么。尝试是最好的学习方式,尽管我自己也为 LINQ 更复杂的一面而苦苦挣扎
  • 您是否尝试过使用自定义IComparerSort/OrderBy
  • @CodeBlend 我只能比较标题并按升序或降序排列。我在第二次比较以及如何使用 LINQ 检测字符串从头到尾的接近程度方面遇到了困难
  • @chrfin 我希望我熟悉 IComparer。我只是不知道如何在 LINQ 中包含第二个要求 Desc 并从头开始检查搜索字符串的接近程度。

标签: c# linq sorting


【解决方案1】:

您可以使用排名函数为每本书定义一个“分数”,然后按分数排序。

var searchString = "ABC";
var results = books.Select(b => new { Book = b, Rank = RankBook(b, searchString) })
                   .OrderBy(r => r.Rank)
                   .Select(r => r.Book.Title);

还有排名函数:

private int RankBook(Book b, string searchString)
{
    int rank = 0;
    if (b.Title.Contains(searchString)) rank += 10;

    if (b.Desc.Contains(searchString)) rank += 5;

    return rank;
}

这就是说:在 title=10 分中找到,在 desc=5 分中找到,所以你会以更高的分数获得最相关的书籍。

【讨论】:

  • 好主意。但是,我还想考虑搜索字符串与标题及其描述开头的距离有多近..
  • 可以很方便的改进rank函数,根据字符串的索引来调整分数。
【解决方案2】:

您可以使用 OrderBy 和 ThenBy

var searches = new ObservableCollection<Book>();

searches.Add(new Book()
{
    Desc = "The description of book 1",
    Title = "ABC Book Title"
});

searches.Add(new Book()
{
    Desc = "Book Title Only",
    Title = "There's an ABC in the description of book 2"
});

searches.Add(new Book()
{
    Desc = "Book Title ABC",
    Title = "ABC is in the beginning"
});

var ordered = new ObservableCollection<Book>(searches.OrderBy(book => book.Title).ThenBy(book => book.Desc.Contains("ABC")));

更新

我添加了一个排名系统,希望能帮助您找到所需的内容。我只使用 IndexOf 来确定您的条件的位置并将其存储在 Book 对象内的一个属性中。我的另一个建议是你为你的书创建一个独立的集合(使用继承),这样你就可以根据你的需要自定义它,而不必在对象本身的上下文之外编写太多代码

public class BookCollection : ObservableCollection<Book> // Notice the Inheritance to ObservableCollection
{
    public void SetCriteria(string search)
    {
        if(string.IsNullOrEmpty(search))
            return;

        foreach (var book in this)
        {
            if(book.Title.Contains(search))
                book.TitleRank = book.Title.IndexOf(search, StringComparison.InvariantCulture);

            if(book.Desc.Contains(search))
                book.DescRank = book.Desc.IndexOf(search, StringComparison.InvariantCulture);
        }

        var collection = new List<Book>(base.Items.OrderBy(book => book.Title)
                                                  .ThenBy(book => book.Desc)
                                                  .ThenBy(book => book.TitleRank)
                                                  .ThenBy(book => book.DescRank));
        Items.Clear();

        collection.ForEach(Add);
        collection.Clear();
    }
}

public class Book
{
    public string Title { get; set; }
    public string Desc { get; set; }
    public int TitleRank { get; internal set; }
    public int DescRank { get; internal set; }
}

现在要使用这个新的集合,你所要做的就是这样称呼它。

var collection = new BookCollection();
collection.Add(new Book { Desc = "Book Title ABC", Title = "ABC is in the beginning" });
// Add your other books here........
collection.SetCriteria("ABC");
// your new collection is now sorted and ready to use, no need to write any extra sorting code here

请记住,如果您需要在排序中添加更多条件,唯一需要这样做的地方是 SetCriteria 方法。希望这会有所帮助。

【讨论】:

  • 非常有帮助。但是在我的情况下,虽然我不想创建一个独立的集合,因为要求并不复杂。我将这两个答案结合起来得出了一个解决方案。
【解决方案3】:

感谢@M Patel 和 Stefano 的建议,我得到了以下解决方案

var sorted = searches.Select(tile => new { TileViewModel = tile, Rank = rankResult(tile, text) })
                    .OrderByDescending(r => r.Rank)
                    .Select(r => r.TileViewModel);

SearchResultsTilesVM = new ObservableCollection<TileViewModel>(sorted);

获取关键字位置的方法。如果在标题中找到匹配项,我会加分。

    private int rankResult(TileViewModel vm, string keyword)
    {
        double rank = 0;

        //Added 100 to give stronger weight when keyword found in title
        int index = vm.Title.IndexOf(keyword, StringComparison.InvariantCultureIgnoreCase);

        if (index >= 0 )
        {
            rank = (double)(vm.Title.Length - index) / (double)vm.Title.Length * 100 + 100;
        }         

        int index2 = vm.Information.IndexOf(keyword, StringComparison.InvariantCultureIgnoreCase);

        if (index2 >= 0)
        {
            rank += (double)(vm.Information.Length - index2) / (double)vm.Information.Length * 100;
        }

        return Convert.ToInt32(rank);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-25
    • 1970-01-01
    • 1970-01-01
    • 2021-03-11
    • 2017-06-03
    • 2015-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多