根据特定标准对集合进行排序并排名

10

假设我有以下内容:

var searches = new ObservableCollection<Book>();

searches 包含书籍对象

public class Book
{
    public string Title { get; set;}
    public string Desc {get; set;}    
}
我想按匹配的字符串对搜索结果进行排序。首先检查Title,然后根据搜索字符串从Title开始的距离将它们排名。接下来检查Desc,并根据搜索字符串从开头出现的位置将它们排名。
例如,如果我有以下内容:
书1 标题:ABC书名 描述:书1的描述
书2 标题:仅书名 描述:书2的描述中有ABC
书3 标题:书名ABC 描述:ABC在开头
假设搜索关键字为ABC,我希望对搜索结果进行排序,以便获得以下结果。结果优先考虑包含搜索字符串的项目的标题。
书1 标题:ABC书名 描述:书1的描述
书3 标题:书名ABC 描述:ABC在开头
书2 标题:仅书名 描述:书2的描述中有ABC
如何使用LINQ实现这一点?

1
你目前已经尝试了什么?勇于尝试是学习的最佳途径,尽管我自己在LINQ更复杂的方面也有困难。 - Paul C
1
你尝试过使用自定义的 IComparerSort/OrderBy 吗? - Christoph Fink
@CodeBlend 我只能比较标题并按升序或降序排列。我在第二个比较和如何使用LINQ检测字符串从开头有多接近方面遇到了困难。 - PutraKg
@chrfin,我希望我熟悉IComparer。我不知道如何在LINQ中包含第二个要求“Desc”,并检查搜索字符串与开头的接近程度。 - PutraKg
3个回答

9
你可以使用排名函数为每本书定义一个“分数”,然后按分数排序。
例如:
var searchString = "ABC";
var results = books.Select(b => new { Book = b, Rank = RankBook(b, searchString) })
                   .OrderBy(r => r.Rank)
                   .Select(r => r.Book.Title);

还有 rank 函数:

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

这句话的意思是:在标题中发现10个点,在描述中发现5个点,因此您可以获得有更高得分的最相关的书籍。

1
好主意。不过我也想考虑一下搜索字符串与标题及其描述开头的距离。 - PutraKg
2
你可以轻松改进排名函数,根据字符串的索引调整分数。 - Stefano Altieri

5
你可以使用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方法。希望这可以帮助您。


非常有帮助。然而在我的情况下,虽然要求不太复杂,但我更倾向于不创建一个独立的集合。我将两个答案结合起来得出了解决方案。 - PutraKg

2
感谢@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);
    }

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