Search code examples
c#arraysrecursionpattern-matchingswitch-expression

Super weird behavior of C# switch expression


I encountered a very strange behavior of the C# switch expression, which I have no idea how to explain. So, I am practicing solving general interview problems, and today it was Kth Largest Element in an Array. I am posting the whole function in the end of the post, but what is relevant for the issue is the last 5 lines of the function, where I have to decide whether to recursively process the left or the right side of the array:

return hi - (k - 1) switch
{
    > 0 => FindKthLargest(nums[..hi], k),
    < 0 => FindKthLargest(nums[(hi + 1)..], k - hi - 1),
    _ => nums[hi]
};

What I think it should do: if hi is greater than k - 1 then it recursively goes with the left side, if it's lesser - the right side, and if it's equal then it returns the element with this index. It looks pretty straightforward but does not work as expected. Not only it does not work, it produces very strange results. For instance, for the input nums = [3, 2, 1, 5, 6, 4], k = 2, the function returns 8 on my local machine, which is not even in the array (and as you can verify the code only returns array elements).

Anyway, after fiddling around with it, I found that this slightly modified code does work:

var wtf = hi - (k - 1);
return wtf switch
{
    > 0 => FindKthLargest(nums[..hi], k),
    < 0 => FindKthLargest(nums[(hi + 1)..], k - hi - 1),
    _ => nums[hi]
};

It is exactly the same, but the condition is moved to a separate variable. So what is going on there?

The full text of the function:

public int FindKthLargest(Span<int> nums, int k) 
{
    if (nums.Length == 1)
        return nums[0];

    var r = Random.Shared.Next(nums.Length);
    (nums[0], nums[r]) = (nums[r], nums[0]);
    int lo = 1, hi = nums.Length - 1, v = nums[0];
    while(true)
    {
        while (lo < hi && nums[lo] > v) lo++;
        while (nums[hi] < v) hi--;
        if (lo >= hi)
            break;
        (nums[lo], nums[hi]) = (nums[hi], nums[lo]);
        lo++; hi--;
    }
    (nums[0], nums[hi]) = (nums[hi], nums[0]);

    var wtf = hi - (k - 1);
    return wtf switch
    {
        > 0 => FindKthLargest(nums[..hi], k),
        < 0 => FindKthLargest(nums[(hi + 1)..], k - hi - 1),
        _ => nums[hi]
    };

    //this works too:
    /*        
    if (hi == k - 1)
        return nums[hi];
    return hi > k - 1 ? 
        FindKthLargest(nums[..hi], k) : 
        FindKthLargest(nums[(hi + 1)..], k - hi - 1);
    */
}

Solution

  • return hi - (k - 1) switch
    {
        ...
    };
    

    This is parsed as:

    hi - ((k - 1) switch
    { 
        ...
    });
    

    That is, it looks at k - 1, switches on the result, and then subtracts this from hi.

    You want:

    return (hi - (k - 1)) switch
    {
        ...
    }