【问题标题】:Sort a C# list by word按单词对 C# 列表进行排序
【发布时间】:2016-06-30 21:38:25
【问题描述】:

我想按单词对 C# 列表进行排序。假设我有一个 C# 列表(对象),其中包含以下单词:

[{id:1, name: "ABC"},
 {id:2, name: "XXX"},
 {id:3, name: "Mille"},
 {id:4, name: "YYY"},
 {id:5, name: "Mill",
 {id:6, name: "Millen"},
 {id:7, name: "OOO"},
 {id:8, name: "GGGG"},
 {id:9, name: null},
 {id:10, name: "XXX"},
 {id:11, name: "mil"}]  

如果用户将Mil 作为搜索键传递,我想返回以搜索键开头的所有单词,然后返回所有不符合条件的单词并按字母顺序排序。

我能想到的最简单的方法是在结果集上运行一个 for 循环,将所有以搜索键开头的单词放入一个列表中,并将重命名的单词放入另一个列表中。对第二个列表进行排序,然后它们将两个列表结合起来以返回结果。

我想知道是否有更智能或内置的方法来获得所需的结果。

【问题讨论】:

  • 所以您想对您的集合进行排序,以便以您的搜索值开头的条目排在第一位,然后是不匹配的条目,并且这两个组本身都是按字母顺序排列的?
  • @JamesFaix 这就是我想要的!!

标签: c# .net linq sorting c#-4.0


【解决方案1】:

当然!您将按匹配项的存在排序,然后按名称排序,如下所示:

var results = objects.OrderByDescending(o => o.Name.StartsWith(searchKey))
                     .ThenBy(o => o.Name);

请注意,false 在排序中位于 true 之前,因此您需要使用 OrderByDescending。

正如 AlexD 指出的那样,名称可以为空。你必须决定你想如何对待它。最简单的方法是使用o.Name?.StartsWith(searchKey) ?? false,但您必须根据自己的需要做出决定。此外,并非所有 Linq 场景都支持 null 传播(想到 Linq To Entities)。

【讨论】:

  • o.Name 不能是 null,如果你编写的类不能是 null。我会这样做 - 除非有空词的用途,否则将其排除在等式之外。
  • 嗯,SharpCoder的例子中有一个空值。
  • o => o.Name?.StartsWith(searchKey),空安全导航,或者,o => !string.IsNullOrEmpty(o.Name) && o.Name.StartsWith(searchKey) 用于 C#5 或更低版本
  • @user6407578,您编写它的方式将首先放置空值。我的答案中的方法将正确排序空值。
【解决方案2】:

应该这样做,但可能有更快的方法,可能以某种方式使用GroupBy。

var sorted = collection
    .Where(x => x.Name.StartsWith(criteria))
    .OrderBy(x => x.Name)
    .Concat(collection
           .Where(x => !x.Name.StartsWith(criteria))
           .OrderBy(x => x.Name))

【讨论】:

  • 那么这个 linq 解决方案与 Easiest way I can think of is to run a for loop over the result set, put all the words starting with search key into one list and put the renaming words into another list. Sort the second list and them combine both the list to return the result. 有何不同@
  • @yar - 此外,LINQ 是惰性且可组合的,因此它可以在比循环解决方案更多的上下文中使用。
【解决方案3】:

您可以像这样尝试GroupBy:

var sorted = collection
  .GroupBy(item => item.Name.StartsWith(criteria))
  .OrderByDescending(chunk => chunk.Key)
  .SelectMany(chunk => chunk
    .OrderBy(item => item.Name));
  • 将项目分成两组(符合和不符合标准)
  • 对组进行整体排序(第一次会面)
  • 在每个组中订购商品
  • 最后组合项目

【讨论】:

  • 创建一个自定义比较器(首先放置前缀字符串)并且只有collection.OrderBy() 有意义吗?
  • @AlexD:这取决于criteria;在当前解决方案中,criteria 每个项目只计算一次(例如,N 次);如果是自定义比较器,则应至少计算 N * Log (N) 次。因此,如果您有一个复杂的 criteria 然后分组,在简单的情况下 - 自定义比较器是一个不错的选择。
  • @DmitryBychenko 看起来您的解决方案是 O(N^2) - GroupBy 一个循环,SelectMany 另一个循环。做一个自定义比较器,它足够智能以识别搜索的术语,否则按字母顺序排序将是 O(n log n)。除非我误读了某些内容,否则这似乎是一种改进。
  • 我原来的评论是错误的,它是 O(N),因为它们的列表是平行的。我讨厌 Big O 及其所有的默默无闻。
  • @TimCopenhaver 不能是O(N)。我们不能比O(N log N)更好。
【解决方案4】:

没有什么特定于 C# 的东西可以解决这个问题,但听起来你真的在寻找算法设计指南。

您应该首先对列表进行排序。如果这是一个静态列表,您应该一直保持排序。如果列表很大,您可以考虑使用更适合这种情况的不同数据结构(二叉搜索树、跳过列表等)。

排序后,查找匹配元素就变成了简单的二分搜索。将匹配的元素移动到结果集的开头,然后返回。

【讨论】:

  • 是的,这里的关键词是“静态”。
  • 我不同意。 .NET 有特定的解决方案 - IComparable
【解决方案5】:

在选择中添加一个匹配的指示符,然后对其进行排序:

void Main()
{
    word[] Words = new word[11]
    {new word {id=1, name= "ABC"},
    new word {id=2, name= "XXX"},
    new word {id=3, name= "Mille"},
    new word {id=4, name= "YYY"},
    new word {id=5, name= "Mill"},
    new word {id=6, name= "Millen"},
    new word {id=7, name= "OOO"},
    new word {id=8, name= "GGGG"},
    new word {id=9, name= null},
    new word {id=10, name= "XXX"},
    new word {id=11, name= "mil"}};

    var target = "mil";
    var comparison = StringComparison.InvariantCultureIgnoreCase;

    var q =  (from w in Words
              where w.name != null
              select new {
                           Match = w.name.StartsWith(target, comparison)?1:2, 
                           name = w.name})
                .OrderBy(w=>w.Match).ThenBy(w=>w.name);
    q.Dump();       
}

public struct word
{
    public int id;
    public string name;
}

【讨论】:

  • 旁白:原因是CLR针对这种事情优化了ToUpper而不是ToLower。此外,ToUpperInvariant 应优先于普通 ToUpper 以避免本地化问题。
  • 我决定使用 StartsWith 的 StringComparison 版本(然后重写整个查询)
【解决方案6】:

这可能并不容易,但您可以创建一个实现IComparable Interface 的类并拥有一个供 CompareTo 使用的属性 Mil。

然后你可以打电话给List.Sort()。您可以将 IComparer 传递给 List.Sort。

这可能是最有效的,您可以就地排序而不是生成新列表。

平均来说,这个方法是一个O(n log n)的操作,其中n是Count; 在最坏的情况下,它是一个 O(n ^ 2) 操作。

 public int CompareTo(object obj) 
 {
    if (obj == null) return 1;

    Temperature otherTemperature = obj as Temperature;
    if (otherTemperature != null)
    {
        if(string.IsNullOrEmpty(Mil) 
            return this.Name.CompareTo(otherTemperature.Name); 
        else if(this.Name.StartsWith(Mill) && otherTemperature.Name.StartsWith(Mill)
            return this.Name.CompareTo(otherTemperature.Name);
        else if(!this.Name.StartsWith(Mill) && !otherTemperature.Name.StartsWith(Mill)
            return this.Name.CompareTo(otherTemperature.Name); 
        else if(this.Name.StartsWith(Mill))
            return 1;
        else 
            return 0;
    }
    else
       throw new ArgumentException("Object is not a Temperature");
 }

您需要添加您希望 null Name 的排序方式

【讨论】:

    【解决方案7】:

    首先创建一个匹配的单词列表,排序。 然后将所有未添加到第一个列表中的单词添加到该列表中,也已排序。

    public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
    {
        var result = new List<Word>(words.Where(word => word.Name.StartsWith(keyword))
            .OrderBy(word => word.Name));
        result.AddRange(words.Except(result).OrderBy(word => word.Name));
        return result;
    } 
    

    一些 cmets 建议它应该不区分大小写。那是

    public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
    {
        var result = new List<Word>(
            words.Where(word => word.Name.StartsWith(keyword, true)) //<-- ignoreCase
            .OrderBy(word => word.Name));
        result.AddRange(words.Except(result).OrderBy(word => word.Name));
        return result;
    } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-07
      • 2016-02-08
      • 1970-01-01
      • 2019-02-06
      • 2010-11-18
      • 1970-01-01
      相关资源
      最近更新 更多