web-dev-qa-db-ja.com

一時変数を使用せずに2つの変数を交換する

C#で一時変数を使用せずに2つの変数を交換できるようにしたいと思います。これはできますか?

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

// Swap each:
//   startAngle becomes: 355.87
//   stopAngle becomes: 159.9
61
Sreedhar

まず第一に、C#としての言語の一時変数なしでのスワップは非常に悪い考えです。

しかし、答えのために、次のコードを使用できます。

startAngle = startAngle + stopAngle;
stopAngle = startAngle - stopAngle;
startAngle = startAngle - stopAngle;

ただし、2つの数値が大きく異なる場合、四捨五入で問題が発生する可能性があります。これは、浮動小数点数の性質によるものです。

一時変数を非表示にする場合は、ユーティリティメソッドを使用できます。

public static class Foo {

    public static void Swap<T> (ref T lhs, ref T rhs) {
        T temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}
111

2つの変数を交換するrightの方法は次のとおりです。

decimal tempDecimal = startAngle;
startAngle = stopAngle;
stopAngle = tempDecimal;

つまり、一時変数を使用します。

そこにあります。巧妙なトリックも、今後数十年間あなたを呪うコードのメンテナーも、 The Daily WTF へのエントリーも、そしてとにかく一度の操作でそれが必要だった理由を理解しようとしてあまり時間を費やすこともありません。最下位レベルでは、最も複雑な言語機能でさえ、一連の簡単な操作です。

非常にシンプルで読みやすく、理解しやすい、t = a; a = b; b = t; 解決。

私の意見では、たとえば「tempを使用せずに変数を交換する」または「Duffのデバイス」などのトリックを使用しようとする開発者は、どれだけ賢いか(そして惨めに失敗するか)を見せようとしています。

私は彼らを、あなたの視野を広げるのではなく、パーティーでもっと面白そうに見えるためだけに高額な本を読む人に例えます。

加算と減算、またはXORベースのソリューションは、単純な「一時変数」ソリューション(アセンブリレベルでの単純な移動ではなく、算術演算/ブール演算)よりも読みにくく、おそらく遅くなります。

質の高い読みやすいコードを記述して、自分や他の人にサービスを提供してください。

それは私の暴言です。聞いてくれてありがとう :-)

余談ですが、これがあなたの特定の質問に答えていないことは十分承知しています(そして、それについて謝罪します)が、SO正しい答えは「やってはいけない」です。

213
paxdiablo

C#7が導入されましたtuples これにより、一時変数なしで2つの変数を交換できます。

int a = 10;
int b = 2;
(a, b) = (b, a);

これにより、baに、abに割り当てられます。

76
TimothyP

はい、このコードを使用します:

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

この問題は、任意の値の場合により困難です。 :-)

71
Paul Sonier
int a = 4, b = 6;
a ^= b ^= a ^= b;

文字列とフロートを含むすべてのタイプで動作します。

43

BenAlabasterは、変数切り替えを行う実用的な方法を示しましたが、try-catch句は必要ありません。このコードで十分です。

static void Swap<T>(ref T x, ref T y)
{
     T t = y;
     y = x;
     x = t;
}

使い方は彼が示したものと同じです:

float startAngle = 159.9F
float stopAngle = 355.87F
Swap(ref startAngle, ref stopAngle);

拡張メソッドを使用することもできます:

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

次のように使用します。

float startAngle = 159.9F;
float stopAngle = 355.87F;
startAngle = startAngle.Swap(ref stopAngle);

どちらの方法でもメソッドで一時変数を使用しますが、スワッピングを行う場所に一時変数は必要ありません。

19
Marcus

バイナリXOR=詳細な例とのスワップ:

XOR真理値表

a b a^b
0 0  0
0 1  1
1 0  1
1 1  0

入力:

a = 4;
b = 6;

ステップ1a = a ^ b

a  : 0100
b  : 0110
a^b: 0010 = 2 = a

ステップ2b = a ^ b

a  : 0010
b  : 0110
a^b: 0100 = 4 = b

ステップa = a ^ b

a  : 0010
b  : 0100
a^b: 0110 = 6 = a

出力:

a = 6;
b = 4;
15
Steven Muhr

将来の学習者と人類のために、私はこの修正を現在選択されている答えに提出します。

一時変数の使用を避けたい場合は、最初のパフォーマンスを考慮してから読みやすさを考慮した2つの賢明なオプションのみがあります。

  • ジェネリックSwapメソッドで一時変数を使用します。 (インライン一時変数の横の絶対最高のパフォーマンス)
  • つかいます - Interlocked.Exchange 。 (私のマシンでは5.9倍遅くなりますが、複数のスレッドがこれらの変数を同時にスワップする場合、これが唯一のオプションです。)

あなたがすべきことnever

  • 浮動小数点演算を使用しないでください。 (遅いエラー、丸めエラー、オーバーフローエラー、わかりにくい)
  • 非プリミティブ算術を使用しないでください。 (遅い、オーバーフローエラー、理解するのが難しい) Decimal はCPUプリミティブではなく、実現するよりもはるかに多くのコードになります。
  • 算術ピリオドを使用しないでください。 またはビットハック。 (遅い、理解しにくい)それはコンパイラの仕事です。多くの異なるプラットフォーム向けに最適化できます。

誰もがハードナンバーを愛しているので、ここにあなたのオプションを比較するプログラムがあります。 Swapがインライン化されるように、Visual Studioの外部からリリースモードで実行します。私のマシンでの結果(Windows 7 64ビットi5-3470):

Inline:      00:00:00.7351931
Call:        00:00:00.7483503
Interlocked: 00:00:04.4076651

コード:

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();
    }
}
12
jnm2

C#ではありません。ネイティブコードでは、トリプルXORスワップトリックを使用できる場合がありますが、高レベルのタイプセーフ言語では使用できません。 (とにかく、XORトリックは実際に多くの一般的なCPUアーキテクチャで一時変数を使用するよりも遅くなると聞きました。)

一時変数を使用するだけです。使用できない理由はありません。供給が限られているわけではありません。

11
Jens Alfke

<非推奨>

基本的な数学を使用して3行でそれを行うことができます-私の例では乗算を使用しましたが、単純な加算でも動作します。

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

startAngle = startAngle * stopAngle;
stopAngle = startAngle / stopAngle;
startAngle = startAngle / stopAngle;

編集:コメントで述べたように、y = 0の場合、これは機能しません。これは、私が考慮していなかったゼロ除算エラーを生成するためです。したがって、代わりに提示される+/-ソリューションが最善の方法です。

</ deprecated>


コードをすぐに理解できるようにするには、このようなことをする可能性が高くなります。 [あなたのコードを維持しなければならない貧しい人について常に考えてください]:

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

そしてcanを1行のコードで実行します:

float startAngle = 159.9F
float stopAngle = 355.87F
Swap<float>(ref startAngle, ref stopAngle);

または...

MyObject obj1 = new MyObject("object1");
MyObject obj2 = new MyObject("object2");
Swap<MyObject>(ref obj1, ref obj2);

夕食のように...任意の種類のオブジェクトを渡して、それらを切り替えることができます...

7
BenAlabaster

decimalからdoubleに変更できる場合は、Interlockedクラスを使用できます。おそらく、これはパフォーマンスの観点から変数を交換する良い方法でしょう。また、XORよりわずかに読みやすくなっています。

var startAngle = 159.9d;
var stopAngle = 355.87d;
stopAngle = Interlocked.Exchange(ref startAngle, stopAngle);

MSDN:Interlocked.Exchangeメソッド(ダブル、ダブル)

6
Robert Fricke

C#7の場合:

(startAngle, stopAngle) = (stopAngle, startAngle);

完全を期すために、バイナリXORスワップ:

int x = 42;
int y = 51236;
x ^= y;
y ^= x;
x ^= y;

これは、バイトを直接処理するため、すべてのアトミックオブジェクト/参照に対して機能しますが、小数や、実際にひねりを感じている場合はポインタを操作するには安全でないコンテキストが必要になる場合があります。また、状況によっては一時変数よりも遅い場合があります。

5
thecoop

C#7を使用すると、Tuple分解を使用して1行で目的のスワップを実現でき、何が起こっているかが明確になります。

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

(startAngle, stopAngle) = (stopAngle, startAngle);
5
jdphenix

環境に注意してください!

たとえば、これはECMAscriptでは機能しないようです

y ^= x ^= y ^= x;

しかし、これは

x ^= y ^= x; y ^= x;

私のアドバイス?できるだけ少ないと仮定します。

4
Codzart
3
Utsav Dusad
a = a + b
b = a - b
a = a - b

َ

3
srinivasan

タプル付き

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

(startAngle, stopAngle) = (stopAngle, startAngle);
2
Zu1779

これが役立つことを願っています...

using System;

public class Program
{
    public static void Main()
    {
        int a = 1234;
        int b = 4321;

        Console.WriteLine("Before: a {0} and b {1}", a, b);

        b = b - a;
        a = a + b;
        b = a - b;

        Console.WriteLine("After: a {0} and b {1}", a, b);
    }
}
2
PalakM

バイナリタイプの場合、このファンキーなトリックを使用できます。

a %= b %= a %= b;

Aとbがまったく同じ変数(たとえば、同じメモリのエイリアス)でない限り機能します。

2
BCS

私たちは簡単なトリックを行うことでそれを行うことができます

a = 20;
b = 30;
a = a+b; // add both the number now a has value 50
b = a-b; // here we are extracting one number from the sum by sub
a = a-b; // the number so obtained in above help us to fetch the alternate number from sum
System.out.print("swapped numbers are a = "+ a+"b = "+ b);
2
cammando

2つの文字列変数を交換する場合:

a = (a+b).Substring((b=a).Length);

それに応じたヘルパーメソッド:

public static class Foo {
    public static void SwapString (ref string a, ref string b) {
       a = (a+b).Substring((b=a).Length);
    }
}

使用方法は次のとおりです。

string a="Test 1";
string b="Test 2";
Foo.SwapString(a, b);
1
HGMamaci
startAngle = (startAngle + stopAngle) - (stopAngle = startAngle);
1
kokabi

ここで別のアプローチを1行で:

decimal a = 159.9m;
decimal b = 355.87m;

a = b + (b = a) - b;
0
fubo

ここに2つの変数を交換するいくつかの異なるプロセスがあります

//process one
a=b+a;
b=a-b;
a=a-b;
printf("a= %d  b=  %d",a,b);

//process two
a=5;
b=10;
a=a+b-(b=a);
printf("\na= %d  b=  %d",a,b);

//process three
a=5;
b=10;
a=a^b;
b=a^b;
a=b^a;
printf("\na= %d  b=  %d",a,b);

//process four
a=5;
b=10;
a=b-~a-1;
b=a+~b+1;
a=a+~b+1;
printf("\na= %d  b=  %d",a,b);
0
A.A Noman