【问题标题】:Array.Sort / IComparable sometimes doesn't sort correctly on first callArray.Sort / IComparable 有时在第一次调用时无法正确排序
【发布时间】:2019-04-20 15:24:46
【问题描述】:

我正在尝试使用 C# 解决 11321 - Sort! Sort!! and Sort!!!,直接从 cpp 和 Java 翻译我的解决方案(以及其他人的解决方案)。

我的问题是listName.Sort() 或Array.Sort(listName, ...Comparer.Create()...) 在第一次通过时没有正确排序输出。我必须调用它两次才能正确排序。

在某些情况下,我在调用 Array.Sort 时在 CompareTo() 中手动设置断点,故意在闭包内添加对列​​表的引用,这样我就可以在排序时观察值,并且它会正确排序,直到Array.Sort() 方法返回,然后我看到一些值返回到不正确的顺序。

我正在使用Morass' test cases from uDebug 进行测试,我得到的一个不正确排序结果的示例在输出的第 10919 行:

Accepted            My Output
10919   457         10919   461
10920   461         10920   457

如您所见,数字 461 和 457 应按其模 500 值的升序排序,分别为 461 和 457。如果我在下面的代码中再次调用 sort 方法,那么我终于得到了正确的输出。

我想我的问题是,为什么会这样?我的实施有什么问题吗?我的实现几乎是接受的 Java 或 cpp 代码的一对一翻译。请注意,我也尝试过使用 LINQ 的 OrderBy(),它会产生不同的结果,但在调用足够多次时最终会产生正确的结果。

我有以下 Number 类和相应的 IComparable 实现:

class Number : IComparable<Number>
{
    public int Value { get; }
    public int Mod { get; }
    public bool IsOdd { get; }

    public Number(int val, int mod)
    {
        Value = val;
        Mod = mod;
        IsOdd = val % 2 != 0;
    }

    public int CompareTo(Number other)
    {
        var leftVal = Value;
        var leftMod = Mod;
        var rightVal = other.Value;
        var rightMod = other.Mod;

        var leftOdd = IsOdd;
        var rightOdd = other.IsOdd;

        if (leftMod < rightMod) return -1;
        else if (leftMod > rightMod) return 1;
        else
        {
            if (leftOdd && rightOdd)
            {
                return leftVal > rightVal ? -1 : 1;
            }
            else if (!leftOdd && !rightOdd)
            {
                return leftVal > rightVal ? 1 : -1;
            }
            else if (leftOdd)
            {
                return -1;
            }
            else// (rightOdd)
            {
                return 1;
            }
        }
    }
}

还有我的主要方法:

public static void Main(string[] args)
    {
        while (true)
        {
            var settings = Console.ReadLine().Split(' ');
            var N = int.Parse(settings[0]);
            var M = int.Parse(settings[1]);

            if (N == 0 && M == 0) break;

            Console.WriteLine($"{N} {M}");
            var output = new List<Number>();

            var i = 0;
            while (i < N)
            {
                var line = Console.ReadLine();
                var val = int.Parse(line);
                var mod = val % M;
                output.Add(new Number(val, mod));
                i++;
            }

            output.Sort();
            // uncomment to produce acceptable answer
            // output.Sort();

            foreach (var line in output)
            {
                Console.WriteLine(line.Value);
            }
        }

        Console.WriteLine("0 0");
    }

编辑 1:

请注意,我正在将标准输入和标准输出从文件重定向到 StringBuilder,因此我可以自动化测试。

    static void Main(string[] args)
    {
        var builder = new StringBuilder();
        var output = new StringWriter(builder);
        Console.SetOut(output);

        var solution = File.ReadAllText("P11321_Outputs");
        var problem = new StreamReader("P11321_Inputs");
        Console.SetIn(problem);

        P11321_1.Main(args);
    }

编辑 2: 这是发生奇怪行为的测试用例的一部分。具体的重现步骤是,如果您将测试用例更改为只有 38 个项目,并从输入中删除 11 个,则 457 和 461 排序正确。

输入:

39 500
-121
582
163
457
-86
-296
740
220
-867
-333
-773
11
-446
-259
-238
782
461
756
-474
-21
-358
593
548
-962
-411
45
-604
-977
47
-561
-647
926
578
516
382
-508
-781
-322
712
0 0

输出:

39 500
-977
-474
-962
-446
-411
-867
-358
-333
-322
-296
-781
-773
-259
-238
-647
-121
-604
-86
-561
-21
-508
11
516
45
47
548
578
582
593
163
712
220
740
756
782
382
926
457
461
0 0

【问题讨论】:

  • 此代码对这两个数字进行了正确排序。我想您正在从文件中读取更大的输入样本?发布该代码。
  • 是的。我分别从输入文件和 StringBuilder 重定向标准输入和标准输出,以便我可以自动化测试。我会在几秒钟内发布。
  • @MarkBenningfield 手动跟踪纸张,排序正确。我遇到的一个奇怪的行为是,如果我通过添加或删除几行来更改测试用例,那么有时它会正确排序。

标签: c# arrays sorting


【解决方案1】:

您设法检查了布尔测试中的所有情况,除非值相等。排序算法不仅需要知道元素是否大于或小于彼此,还需要知道它们是否相等。

  if (leftMod < rightMod)
    return -1;
  else if (leftMod > rightMod)
    return 1;
  else
  {
    if (leftVal == rightVal)
    {
      return 0; // need this so you don't orphan an element when tested against itself
    }
    if (leftOdd && rightOdd)
    {
      return leftVal > rightVal ? -1 : 1;
    }
    else if (!leftOdd && !rightOdd)
    {
      return leftVal > rightVal ? 1 : -1;
    }
    else if (leftOdd)
    {
      return -1;
    }
    else// (rightOdd)
    {
      return 1;
    }
  }

【讨论】:

  • 这工作,出于某种原因。 Array.Sort 是否将元素与自身进行比较?此外,错误的测试用例是 457 和 461,它们不相等并且永远不会进入这个附加条件。至少对于样本输入,这如何解决问题?
  • 根据算法的不同,排序例程将元素与自身进行比较并不少见。请记住,一个重要的排序例程会移动很多元素。
【解决方案2】:

在 Mark Benningfield 的回答上加倍,我想提出一个概念证明,说明为什么在自定义比较器的实现中包含相等性很重要。不仅有结果不正确的风险,还有永远得不到结果的风险!

尝试使用有问题的比较器对两个数字 (2, 1) 进行排序:

class BuggyComparer : IComparer<int>
{
    public int Compare(int x, int y) => x < y ? -1 : 1; // Equality?
}

var source = new int[] { 2, 1 };
var sorted = source.OrderBy(n => n, new BuggyComparer());
Console.WriteLine(String.Join(", ", sorted)); // Infinite loop

程序没有终止,因为排序无法完成。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-08
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多