【问题标题】:Check if a string is sorted检查字符串是否已排序
【发布时间】:2017-01-01 10:40:34
【问题描述】:

我有一个字符串,简化后的"12345" 已排序。字符串可以包含数字 (0-9) 或字母 (a-z)。在混合使用的情况下,自然排序顺序。我需要一种方法来验证这是否属实。

尝试使用 linq 技术:

string items1 = "2349"; //sorted
string items2 = "2476"; //not sorted, 6<>7

bool sorted1 = Enumerable.SequenceEqual(items1.OrderBy(x => x), items1); //true
bool sorted2 = Enumerable.SequenceEqual(items2.OrderBy(x => x), items2); //false

但也可以是降序排列。

那有没有更好的办法

string items3 = "4321";
bool sorted3 = Enumerable.SequenceEqual(items3.OrderBy(x => x), items3) || Enumerable.SequenceEqual(items3.OrderByDescending(x => x), items3);

检查一个字符串是否已排序?也许一些内置的解决方案?

【问题讨论】:

  • 如果字符串包含字母怎么办?你刚刚向我们展示了最好的情况。如果字符不是英文字母(例如-?)怎么办?
  • @c0rd 所以123abcabc123ab12cd34 被认为是排序的?
  • @user3185569 "123abc" 或 "cba321" 有效
  • 请定义“更好”。是“更快”还是“更具可读性”或“消耗更少的内存”?

标签: c# .net string linq sorting


【解决方案1】:

您的解决方案很好且非常易读。它的一个问题是它需要对stringwhich is O(n * log(n)) 进行排序,这可以通过迭代string 而不对其进行排序来解决。

例如:

var firstDifs = items1.Zip(items1.Skip(1), (x, y) => y - x);

这个Linq 将第一个string 中的每2 个项目投影到一个表示它们差异的数字,所以如果你有items1 = "1245",输出将是:

firstDifs:{1, 2, 1}

现在您需要做的就是验证 firstDifs 是升序还是降序:

bool firstSorted = firstDifs.All(x => x > 0) || firstDifs.All(x => x < 0); //true

现在:

  • SkipO(1),因为跳过 1 个单元格所需的操作量是 常数。
  • ZipO(n)
  • AllO(n)

所以整个解决方案是O(n)

请注意,使用简单的循环会更有效,如果第一个 All 已返回 false,因为第 3487 个项目改变了它的方向(对于例如:1234567891),第二个All 将无缘无故地运行,Zip 也运行两次 (直到All 需要) - 因为AllLinq 懒惰地评估它们。

【讨论】:

  • 您确定要迭代多次吗?是不是 ZIP 只遍历真正需要的元素,所以如果我在 ALL 的第三次迭代中发现结果为假,因此 ALL 的其余部分没有被迭代,那么其余部分也没有被压缩?
  • @HaraldDutch 压缩将进行两次,因为AllLinq 的两次迭代都会懒惰地评估它们。
  • @TamirVered All() 如果发现一个无效字符则停止处理,所以最多 O(n) 对吗?
  • @c0rd 如果有重复的字符,改成x &gt;= 0x &lt;= 0
【解决方案2】:

它需要reducer。在 C# 中,它是 Enumerable.Aggregate。这是 O(n) 算法。

var query = "123abc".Aggregate(new { asceding = true, descending = true, prev = (char?)null }, 
(result, currentChar) =>
    new 
    { 
       asceding = result.prev == null || result.asceding && currentChar >= result.prev, 
       descending = result.prev == null || result.descending && currentChar <= result.prev, 
       prev = (char?)currentChar 
    }
);
Console.WriteLine(query.asceding || query.descending );

【讨论】:

    【解决方案3】:

    我曾经不得不检查与您的情况类似但具有大量数据流的东西,因此性能很重要。我想出了这个性能很好的小型扩展类:

    public static bool IsOrdered<T>(this IEnumerable<T> enumerable) where T: IComparable<T>
    {
        using (var enumerator = enumerable.GetEnumerator())
        {
            if (!enumerator.MoveNext())
                return true; //empty enumeration is ordered
    
            var left = enumerator.Current;
            int previousUnequalComparison = 0;
    
            while (enumerator.MoveNext())
            {
                var right = enumerator.Current;
                var currentComparison = left.CompareTo(right);
    
                if (currentComparison != 0)
                {
                    if (previousUnequalComparison != 0
                        && currentComparison != previousUnequalComparison)
                        return false;
    
                    previousUnequalComparison = currentComparison;
                    left = right;
                }
            }
        }
    
        return true;
    }
    

    使用起来显然很简单:

    var items1 = "2349";
    var items2 = "2476"; //not sorted, 6<>7
    items1.IsOrdered(); //true
    items2.IsOrdered(); //false
    

    【讨论】:

    • 这确实是正确的方法。比任何“简洁”的纯 LINQ 尝试都要好得多。 DRY,用法更加简洁易读。
    【解决方案4】:

    不必比较所有元素,您可以比公认的答案做得更好:

    var s = "2349"; 
    var r = Enumerable.Range(1, s.Length - 1);
    //var isAscending = r.All(i => s[i - 1] <= s[i]);
    //var isDescending = r.All(i => s[i - 1] >= s[i]);
    var isOrdered = r.All(i => s[i - 1] <= s[i]) || r.All(i => s[i - 1] >= s[i]);
    

    【讨论】:

      【解决方案5】:
      var items = "4321";
      var sortedItems = items.OrderBy(i => i); // Process the order once only
      var sorted = sortedItems.SequenceEqual(items) || sortedItems.SequenceEqual(items.Reverse()); // Reverse using yield return
      

      【讨论】:

      • OrderBy 的结果是一个 IEnumberable,每次调用都应该处理
      【解决方案6】:

      我会对所有元素进行简单的迭代:

      string str = "whatever123";
      Func<char, char, bool> pred;
      bool? asc = str.TakeWhile((q, i) => i < str.Length - 1)
                     .Select((q, i) => str[i] == str[i+1] ? (bool?)null : str[i] < str[i+1])
                     .FirstOrDefault(q => q.HasValue);
      if (!asc.HasValue)
          return true; //all chars are the same
      if (asc.Value)
          pred = (c1, c2) => c1 <= c2;
      else
          pred = (c1, c2) => c1 >= c2;
      
      for (int i = 0; i < str.Length - 1; ++i)
      {
          if (!pred(str[i], str[i + 1]))
              return false;
      }
      return true;
      

      【讨论】:

      • 运算符 '>=' 不能应用于 'bool' 和 'char' 类型的操作数
      • 我总是把Func声明错误,我改了,请现在试试
      • ok 现在可以工作但返回 "aabcdefghijklmnopqrstuvwxyz"false
      • 这是因为注释的 TODO
      • @c0rd 也为您修复了它
      猜你喜欢
      • 2021-10-16
      • 1970-01-01
      • 2017-11-20
      • 2012-04-08
      • 1970-01-01
      • 1970-01-01
      • 2015-09-11
      • 1970-01-01
      • 2012-02-14
      相关资源
      最近更新 更多