在不使用临时变量的情况下交换两个变量

Sre*_*har 63 c# algorithm swap

我希望能够在不使用C#中的临时变量的情况下交换两个变量.可以这样做吗?

decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);

// Swap each:
//   startAngle becomes: 355.87
//   stopAngle becomes: 159.9
Run Code Online (Sandbox Code Playgroud)

pax*_*blo 217

交换两个变量的正确方法是:

decimal tempDecimal = startAngle;
startAngle = stopAngle;
stopAngle = tempDecimal;
Run Code Online (Sandbox Code Playgroud)

换句话说,使用临时变量.

你有它.不聪明的技巧,没有你的代码的维护者骂你几十年来,没有任何条目,以每日WTF,并没有花太多时间试图找出为什么你需要它在一个操作无论如何,因为,在最低水平,即使是最复杂的语言功能是一系列简单的操作.

只是一个非常简单,易读,易于理解的t = a; a = b; b = t;解决方案.

在我看来,尝试使用技巧的开发人员,例如,"不使用临时交换变量"或"Duff的设备"只是试图表明他们有多聪明(并且悲惨地失败).

我将它们比作那些阅读高雅书籍的人,仅仅是为了在派对上看起来更有趣(而不是扩大你的视野).

您添加和减去的解决方案,或基于XOR的解决方案,其可读性较差,并且很可能比简单的"临时变量"解决方案(算术/布尔运算而不是汇编级别的普通移动)慢.

通过编写高质量的可读代码,为自己和他人提供服务.

那是我的咆哮.谢谢收听 :-)

顺便说一句,我很清楚这不会回答你的具体问题(我会为此道歉)但是在SO上有很多先例,人们已经问过如何做某事而且正确答案是"不要做它".

  • +1; 并且出于更多原因:使用+/-(等)技巧你正在做不必要的算术.对于整数,这可能只是接受*在推*(舍入/溢出不是问题,并且CPU成本几乎为零),但是对于小数,加法和减法是非平凡的操作.它甚至不能使用FPU,因为它们不是float/double.所以使用一个临时变量!!! (8认同)
  • 当然这是最好的方法,但是没有临时变量就明确要求了 (5认同)
  • 也许没有使用temp变量的真正原因.如果这两个变量非常大,你就不会想要创建一个新变量,因此即使不是很长时间也有3个非常大的变量. (2认同)

Wil*_*sem 111

首先,在没有C#语言的临时变量的情况下进行交换是一个非常糟糕的主意.

但为了回答,您可以使用以下代码:

startAngle = startAngle + stopAngle;
stopAngle = startAngle - stopAngle;
startAngle = startAngle - stopAngle;
Run Code Online (Sandbox Code Playgroud)

然而,如果这两个数字差别很大,则可能会出现问题.这是由于浮点数的性质.

如果要隐藏临时变量,可以使用实用程序方法:

public static class Foo {

    public static void Swap<T> (ref T lhs, ref T rhs) {
        T temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 解决此问题的唯一方法是使用临时变量.像这样的"聪明"代码(以及"聪明",我的意思是"愚蠢")远不如临时变量解决方案那么可读和明显.如果我从我的一个仆从看到这样的代码,他们将受到制裁并被送回去做正确的事.我没有特别关注你,@ CommuSoft(因为你回答了这个问题),但问题本身就是垃圾. (142认同)
  • 对于整数或定点数字来说这很好.对于浮点数,您最终会得到分钟舍入错误.它们可能重要也可能不重要,取决于你如何使用这些数字. (33认同)
  • 这种技术在计算上比使用临时变量更昂贵. (12认同)
  • 只要您没有遇到溢出问题,那就可以了 (7认同)
  • @Janusz Lenar:在使用指针操作的语言中,您可以使用相同的技巧来交换指针.在C#中,您可以在不安全的环境中执行此操作.:D但无论如何我承认交换对象,等等.没有第三个变量是一个坏主意(对此有所反应.__curious_geek).但问题明确表示没有额外的变量. (4认同)
  • 我不喜欢这句话"没有临时变量交换是一个非常糟糕的主意".许多语言都支持以明确的方式交换变量,而不会出现容易出错的3行混洗,因此询问C#是否合理是合乎逻辑的.Python:`a,b = b,a`,C++:`std :: swap(a,b)`. (4认同)
  • 是的,我知道,但问题是,是否可以在没有临时变量的情况下进行.如果使用临时变量编译代码,则可能甚至不需要额外的内存,因为临时变量只存储在处理器的寄存器中.但我想问题是家庭作业或类似的东西. (3认同)

Tim*_*hyP 86

C#7引入了元组,它可以在没有临时变量的情况下交换两个变量:

int a = 10;
int b = 2;
(a, b) = (b, a);
Run Code Online (Sandbox Code Playgroud)

这种分配baab.

  • @Ray https://www.reddit.com/r/ProgrammerTIL/comments/8ssiqb/cyou_can_swap_values_of_two_variable_with_the_new/e12301f/ (10认同)
  • 遗憾的是,此功能以前不存在,在那里他可能会学到一种强大的新语言结构,该结构将为他提供更多工具来破解问题,而不是上面所有无用的语。 (4认同)
  • 此代码适用于 .net 5.0。当我查看 IL 时,它实际上实现了交换,并带有一个附加变量。 (3认同)
  • 但这的性能如何?我总是担心在堆内部创建一些“元组”实例以及所有这些奇特的事情发生。 (2认同)
  • @TheBeardedQuack 您的链接显示正确的结果。为什么你说它不适用于原始类型? (2认同)

Pau*_*ier 72

是的,使用此代码:

stopAngle = Convert.ToDecimal(159.9);
startAngle = Convert.ToDecimal(355.87);
Run Code Online (Sandbox Code Playgroud)

任意值都难以解决问题.:-)


thi*_*eek 43

int a = 4, b = 6;
a ^= b ^= a ^= b;
Run Code Online (Sandbox Code Playgroud)

适用于所有类型,包括字符串和浮动.

  • 我希望有一天会忘记XOR交换. (21认同)
  • XOR交换接近于书呆子的顶峰.在学校学习之后,我有了几天的必杀技. (8认同)
  • 这似乎根本不起作用http://stackoverflow.com/questions/5577140/why-does-swapping-values-with-xor-fail-when-using-this-compound-form (8认同)
  • 1. 在 C# 中,这不起作用,请参阅 /sf/ask/947833631/。2. 在所有类似 C 的语言中,当“a”和“b”包含相等的值时,它就会中断,因为“x ^ x == 0”(您会丢失“a”和“b”中的值)。向新手程序员展示这个技巧是很酷的书呆子的东西,但为什么人们展示这个技巧而不教导为什么它永远不应该使用呢? (4认同)
  • 这是C#.正如@zespri所说,上面的代码没有交换.到你的最后一句话:在C#中,你不能使用运算符`^ =`with`string`或`float`,所以它不会用它们编译. (3认同)
  • 如果我乘坐美国政府.制造战斗机,或者必须有一个心脏起搏器等,我真的希望我不会死,因为一些程序员"希望有一天会忘记XOR交换"造成的堆栈溢出 (3认同)

Mar*_*cus 20

BenAlabaster展示了一种实现变量切换的实用方法,但不需要try-catch子句.这段代码就足够了.

static void Swap<T>(ref T x, ref T y)
{
     T t = y;
     y = x;
     x = t;
}
Run Code Online (Sandbox Code Playgroud)

用法与他所示的相同:

float startAngle = 159.9F
float stopAngle = 355.87F
Swap(ref startAngle, ref stopAngle);
Run Code Online (Sandbox Code Playgroud)

您还可以使用扩展方法:

static class SwapExtension
{
    public static T Swap<T>(this T x, ref T y)
    {
        T t = y;
        y = x;
        return t;
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

float startAngle = 159.9F;
float stopAngle = 355.87F;
startAngle = startAngle.Swap(ref stopAngle);
Run Code Online (Sandbox Code Playgroud)

两种方法都在方法中使用临时变量,但在交换时不需要临时变量.

  • 使用抽象是解决问题的好方法.它为常见问题提供了一般解决方案,使调用代码更易于阅读.当然它使用了一些额外的字节内存和一些额外的处理器周期,但除非你调用这个代码数百万次,否则你不会注意到任何差异. (3认同)
  • 是的,但仅限于方法,而不是您进行切换的地方. (2认同)
  • 所以问题是,为什么这个方便的功能不包含在.NET库中?这是[Generic Methods](https://msdn.microsoft.com/en-us/library/twcad0zb.aspx)文档中给出的第一个示例. (2认同)

Ste*_*uhr 15

带有详细示例的二进制XOR交换:

XOR真值表:

a b a^b
0 0  0
0 1  1
1 0  1
1 1  0
Run Code Online (Sandbox Code Playgroud)

输入:

a = 4;
b = 6;
Run Code Online (Sandbox Code Playgroud)

步骤1:a = a ^ b

a  : 0100
b  : 0110
a^b: 0010 = 2 = a
Run Code Online (Sandbox Code Playgroud)

步骤2:b = a ^ b

a  : 0010
b  : 0110
a^b: 0100 = 4 = b
Run Code Online (Sandbox Code Playgroud)

步骤3:a = a ^ b

a  : 0010
b  : 0100
a^b: 0110 = 6 = a
Run Code Online (Sandbox Code Playgroud)

输出:

a = 6;
b = 4;
Run Code Online (Sandbox Code Playgroud)


jnm*_*nm2 12

为了未来的学习者和人性,我将此修正提交给当前选择的答案.

如果您想避免使用临时变量,那么只有两个合理的选项可以考虑首次性能和可读性.

  • 在泛型Swap方法中使用temp变量.(绝对最佳性能,内联临时变量旁边)
  • 使用Interlocked.Exchange.(在我的机器上慢了5.9倍,但如果多个线程同时交换这些变量,这是你唯一的选择.)

永远不应该做的事情:

  • 切勿使用浮点运算.(缓慢,舍入和溢出错误,难以理解)
  • 永远不要使用非原始算术.(慢,溢出错误,难以理解)Decimal不是CPU原语,导致代码远远超出您的意识.
  • 永远不要使用算术期.或者有点黑客.(慢,难以理解)这是编译器的工作.它可以针对许多不同平台进行优化.

因为每个人都喜欢硬数字,所以这是一个比较你的选择的程序.从Visual Studio外部以发布模式运行它,以便Swap内联.在我的机器上的结果(Windows 7 64位i5-3470):

Inline:      00:00:00.7351931
Call:        00:00:00.7483503
Interlocked: 00:00:04.4076651
Run Code Online (Sandbox Code Playgroud)

码:

class Program
{
    static void Swap<T>(ref T obj1, ref T obj2)
    {
        var temp = obj1;
        obj1 = obj2;
        obj2 = temp;
    }

    static void Main(string[] args)
    {
        var a = new object();
        var b = new object();

        var s = new Stopwatch();

        Swap(ref a, ref b); // JIT the swap method outside the stopwatch

        s.Restart();
        for (var i = 0; i < 500000000; i++)
        {
            var temp = a;
            a = b;
            b = temp;
        }
        s.Stop();
        Console.WriteLine("Inline temp: " + s.Elapsed);


        s.Restart();
        for (var i = 0; i < 500000000; i++)
        {
            Swap(ref a, ref b);
        }
        s.Stop();
        Console.WriteLine("Call:        " + s.Elapsed);

        s.Restart();
        for (var i = 0; i < 500000000; i++)
        {
            b = Interlocked.Exchange(ref a, b);
        }
        s.Stop();
        Console.WriteLine("Interlocked: " + s.Elapsed);

        Console.ReadKey();
    }
}
Run Code Online (Sandbox Code Playgroud)


Jen*_*fke 11

不在C#中.在本机代码中,您可以使用三重XOR交换技巧,但不能使用高级类型安全语言.(无论如何,我听说XOR技巧实际上比在许多常见CPU架构中使用临时变量要慢.)

你应该只使用一个临时变量.没有理由你不能使用它; 它不像供应有限.


Ben*_*ter 7

<弃用>

您可以使用基本数学在3行中完成 - 在我的示例中,我使用了乘法,但简单的加法也可以.

float startAngle = 159.9F;
float stopAngle = 355.87F;

startAngle = startAngle * stopAngle;
stopAngle = startAngle / stopAngle;
startAngle = startAngle / stopAngle;
Run Code Online (Sandbox Code Playgroud)

编辑:如评论中所述,如果y = 0,这将不起作用,因为它会产生除以零错误,我没有考虑过.所以+/-解决方案交替提出将是最好的方式.

</弃用>


为了让我的代码立即易于理解,我更有可能做这样的事情.[总是想想那个必须维护你的代码的可怜家伙]:

static bool Swap<T>(ref T x, ref T y)
{
    try
    {
        T t = y;
        y = x;
        x = t;
        return true;
    }
    catch
    {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在一行代码中完成它:

float startAngle = 159.9F
float stopAngle = 355.87F
Swap<float>(ref startAngle, ref stopAngle);
Run Code Online (Sandbox Code Playgroud)

要么...

MyObject obj1 = new MyObject("object1");
MyObject obj2 = new MyObject("object2");
Swap<MyObject>(ref obj1, ref obj2);
Run Code Online (Sandbox Code Playgroud)

像晚餐一样......你现在可以传递任何类型的物体并将它们切换到......

  • 你的交换方法怎么了?为什么它会返回一个bool,为什么bool总是正确的(如果它存在)?为什么它会吞下所有异常(在这种情况下只能是ThreadAbortException,我相信,因为它不分配内存或扩大调用堆栈)? (2认同)

the*_*oop 6

为了完整性,这里是二进制XOR交换:

int x = 42;
int y = 51236;
x ^= y;
y ^= x;
x ^= y;
Run Code Online (Sandbox Code Playgroud)

这适用于所有原子对象/引用,因为它直接处理字节,但可能需要一个不安全的上下文来处理小数,或者,如果你感觉真的扭曲,指针.在某些情况下,它可能比临时变量慢.


Rob*_*cke 6

如果您可以从使用更改decimaldouble您可以使用Interlocked该类.据推测,这将是明智地交换变量性能的好方法.也比XOR稍微可读.

var startAngle = 159.9d;
var stopAngle = 355.87d;
stopAngle = Interlocked.Exchange(ref startAngle, stopAngle);
Run Code Online (Sandbox Code Playgroud)

Msdn:Interlocked.Exchange方法(双,双)


tom*_*zak 6

在C#7中:

(startAngle, stopAngle) = (stopAngle, startAngle);
Run Code Online (Sandbox Code Playgroud)


小智 5

a = a + b
b = a - b
a = a - b
Run Code Online (Sandbox Code Playgroud)

?


jdp*_*nix 5

使用C#7,您可以使用元组解构来在一行中实现所需的交换,并且可以清楚地知道发生了什么.

decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);

(startAngle, stopAngle) = (stopAngle, startAngle);
Run Code Online (Sandbox Code Playgroud)