这个循环可以更简洁地编写吗?

4

我有两个队列,假设为A和B,我在上面执行以下算法:

while (queueA.Count > 0)
{
    var elemA = queueA.Peek();
    var elemB = queueB.Peek();
    if (AreSimilar(elemA, elemB))
    {
        Debug.Assert(elemA.SomeProperty == elemB.SomeProperty);
        queueA.Dequeue();
        queueB.Dequeue();
    }
    else
    {
        break;
    }    
}

有些东西告诉我这段代码可以更简洁; Peek() 和 Dequeue() 可以合并为一个操作,因为 Dequeue() 返回的元素与 Peek() 相同,if 语句可能会与 while 语句融合在一起,避免显式 break。我只是没有看到如何完全保留相同的行为,即除非满足“if”中的条件,否则不想删除元素。

2
我认为PeekDequeue不能结合使用,因为它们在不同的条件下执行。 - Ben Voigt
你可以通过这样做来简化代码:if (AreSimilar(queueA.Peek(), queueB.Peek())) {,但是这样会失去断言的能力。 - cdhowie
1
听起来像是微观优化,而真正的答案是,除非你确实遇到了性能问题,否则这是浪费时间,特别是当很难判断编译器如何进行优化时。 - Erik Philips
@ErikPhilips 我不担心性能问题,只是担心可能会错过显而易见的东西。 - Asik
1
啊,听起来像是适合在http://codereview.stackexchange.com/上讨论的东西 :) - Erik Philips
3个回答

4
您可以尝试将赋值移动到AreSimilar的调用中的代码:
QueueElement elemA, elemB
while (queueA.Count > 0 && AreSimilar(elemA = queueA.Peek(), elemB = queueB.Peek())) {
    Debug.Assert(elemA.SomeProperty == elemB.SomeProperty);
    queueA.Dequeue();
    queueB.Dequeue();
}

请注意,这并不一定更易读。实际上,从可读性的角度来看,你的版本已经相当不错了。唯一的建议是将条件反转以减少嵌套,但其他方面都保持不变:
while (queueA.Count > 0)
{
    var elemA = queueA.Peek();
    var elemB = queueB.Peek();
    if (!AreSimilar(elemA, elemB))
    {
        break;
    }
    Debug.Assert(elemA.SomeProperty == elemB.SomeProperty);
    queueA.Dequeue();
    queueB.Dequeue();
}

1
内联赋值?这是什么鬼东西? - p.s.w.g
请注意,更小的代码并不总是更易读。这段代码不会更快,并且比原始代码难以阅读得多。 - cdhowie
@p.s.w.g 嗯,编程了25年以上的C语言往往会在人们感知美的方式上留下痕迹 :) 当我意识到K&R中while(*dest++ = *src++) ;示例中分号的作用时,我就爱上了嵌入条件的赋值语句。 - Sergey Kalinichenko
1
@dasblinkenlight 聪明的代码并不总是易读的。阅读您的示例片段的人将不得不问自己,您是否真的意味着“=”,还是那是不正确的,而“==”是预期的运算符。试图弄清楚您的意思并不是读者应该花时间的地方。 - cdhowie
@cdhowie嗯,原帖确实要求提供更简洁的代码,这就是为什么我写了一些接近C代码但实际上是C#的代码。我编辑了答案,增加了一个更易读的替代方案,不出所料,它几乎完全复制了原帖中的代码,并稍微改进以减少嵌套。 - Sergey Kalinichenko
实际上,我只是指出“更简洁”并不意味着“更好”。正如您编辑后的答案所述,应该让OP知道这一点。 - cdhowie

2

A和B相似的可能性有多大? 如果概率很高,您可以始终在假定共同情况下运行并将它们弹出(或Dequeue),假设它们将类似,然后只需担心如果它们不相似再将它们推回...


这并不一定使代码更易读,它只是反转了条件,实际上使其更难以阅读(因为队列上没有记录在队列前面插入对象的文档化方法,必须采用反射获取对象的内部)。 - cdhowie
我并没有认为可读性和简洁性是同一个意思... Dr_Asik也没有说把项目放到队列末尾不是一个选项... 我同意,如果大部分时间都需要将东西重新推回队列,那么速度可能会慢得多... 这似乎是一个非常玄妙的问题,所以我试图挑战一些假设。 - Rikon

1
你可以借助一个方法简化循环,但是你可能会认为这是作弊:
static bool DequeuePairIf<T>(
    Func<T, T, bool> predicate,
    Queue<T> queueA,
    Queue<T> queueB)
{
    if (queueA.Count != 0 &&
        queueB.Count != 0 &&
        predicate(queueA.Peek(), queueB.Peek())
    ) {
        queueA.Dequeue();
        queueB.Dequeue();

        return true;
    }

    return false;
}

然后你的循环变成了:
while (DequeuePairIf(AreSimilar, queueA, queueB)) { }

但我质疑这种重构是否有助于可读性,或者会损害它。 首先,它比原始代码要大得多。 另一方面,较小的代码并不总是更易读。

(我删除了assert以简化此处的逻辑。 如果您仍需要assert,则必须保存Peek()调用的结果,就像在原始代码中所做的那样。)


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