web-dev-qa-db-fra.com

Échangez deux variables sans utiliser de variable temporaire

J'aimerais pouvoir échanger deux variables sans utiliser de variable temporaire en C #. Cela peut-il être fait?

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

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

Tout d'abord, permuter sans variable temporaire dans une langue comme C # est une très mauvaise idée .

Mais pour des raisons de réponse, vous pouvez utiliser ce code:

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

L'arrondi peut toutefois poser problème si les deux nombres diffèrent largement. Cela est dû à la nature des nombres en virgule flottante.

Si vous souhaitez masquer la variable temporaire, vous pouvez utiliser une méthode utilitaire:

public static class Foo {

    public static void Swap<T> (ref T lhs, ref T rhs) {
        T temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}
98
Willem Van Onsem

La méthode right pour échanger deux variables est la suivante:

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

En d'autres termes, utilise une variable temporaire.

Voilà. Pas d'astuces intelligentes, pas de mainteneurs de votre code vous maudire pendant des décennies, pas d'entrées dans The Daily WTF , et pas de temps à essayer de comprendre pourquoi vous en aviez besoin de toute façon depuis, au plus bas niveau le plus compliqué est une série d’opérations simples.

Juste une solution très simple, lisible, facile à comprendre, t = a; a = b; b = t;.

À mon avis, les développeurs qui essaient d'utiliser des astuces pour, par exemple, "échanger des variables sans utiliser de temp" ou "du périphérique de Duff" ne font qu'essayer de montrer leur intelligence (et leur échec lamentable).

Je les compare à ceux qui lisent des livres intellectuels uniquement dans le but de paraître plus intéressants lors de fêtes (au lieu d'élargir vos horizons).

Les solutions où vous ajoutez et soustrayez, ou celles basées sur XOR, sont moins lisibles et probablement plus lentes qu'une simple solution "variable temp" (arithmétique/boolean-ops au lieu de simples mouvements au niveau de l'assemblage).

Faites vous-même, et d'autres, un service en écrivant un code lisible de bonne qualité.

C'est mon coup de gueule. Merci pour l'écoute :-)

En passant, je suis tout à fait conscient que cela ne répond pas à votre question spécifique (et je m'en excuse), mais il existe de nombreux précédents sur SO dans lesquels des personnes ont demandé comment faire quelque chose et la bonne réponse est: "Ne le fais pas".

209
paxdiablo

Oui, utilisez ce code:

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

Le problème est plus difficile pour les valeurs arbitraires. :-)

71
Paul Sonier

C # 7 introduit tuples qui permet de permuter deux variables sans variable temporaire:

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

Ceci assigne b à a et a à b.

54
TimothyP
int a = 4, b = 6;
a ^= b ^= a ^= b;

Fonctionne pour tous les types, y compris les chaînes et les flotteurs. 

42

BenAlabaster a montré un moyen pratique d'effectuer un changement de variable, mais la clause try-catch n'est pas nécessaire. Ce code est suffisant.

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

L'utilisation est la même comme il l'a montré:

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

Vous pouvez également utiliser une méthode d'extension:

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

Utilisez-le comme ceci:

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

Les deux méthodes utilisent une variable temporaire dans la méthode, mais vous n'avez pas besoin de la variable temporaire dans laquelle vous effectuez le swap.

19
Marcus

Un échange binaire XOR avec un exemple détaillé:

XOR table de vérité:

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

Contribution:

a = 4;
b = 6;

Étape 1 : a = a ^ b

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

Étape 2 : b = a ^ b

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

Étape 3 : a = a ^ b

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

Sortie:

a = 6;
b = 4;
15
Steven Muhr

Pas en C #. Dans le code natif, vous pourrez peut-être utiliser l'astuce d'échange triple-XOR, mais pas dans un langage de niveau supérieur compatible avec les caractères. (Quoi qu'il en soit, j'ai entendu dire que l'astuce XOR finissait par être plus lente que l'utilisation d'une variable temporaire dans de nombreuses architectures de CPU courantes.)

Vous devriez juste utiliser une variable temporaire. Il n'y a aucune raison pour que vous ne puissiez pas en utiliser un; ce n'est pas comme si l'offre était limitée.

11
Jens Alfke

Pour le bien des futurs apprenants et de l'humanité, je soumets cette correction à la réponse actuellement sélectionnée.

Si vous voulez éviter d'utiliser des variables temporaires, il existe seulement deux options sensibles qui prennent en compte les performances en premier lieu, puis la lisibilité.

  • Utilisez une variable temporaire dans une méthode générique Swap. (Meilleure performance absolue, à côté de la variable temporaire en ligne)
  • Utilisez Interlocked.Exchange . (5,9 fois plus lente sur ma machine, mais c'est votre seule option si plusieurs threads vont échanger ces variables simultanément.)

Les choses que vous devriez jamais faire:

  • N'utilisez jamais d'arithmétique en virgule flottante. (erreurs lentes, d'arrondis et de débordement, difficiles à comprendre)
  • N'utilisez jamais d'arithmétique non primitive. (lentes, erreurs de débordement, difficiles à comprendre) Decimal n'est pas une primitive de l'UC et génère beaucoup plus de code que vous ne le réalisez.
  • N'utilisez jamais de période arithmétique. Ou peu de piratage. (lent, difficile à comprendre) C'est le travail du compilateur. Il peut optimiser pour de nombreuses plates-formes différentes. 

Parce que tout le monde aime les chiffres durs, voici un programme qui compare vos options. Exécutez-le en mode édition en dehors de Visual Studio pour que Swap soit en ligne. Résultats sur ma machine (Windows 7 64 bits i5-3470):

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

Code:

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

<obsolète>

Vous pouvez le faire en 3 lignes en utilisant les mathématiques de base - dans mon exemple, j'ai utilisé la multiplication, mais une simple addition fonctionnerait également.

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

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

Edit: Comme indiqué dans les commentaires, cela ne fonctionnerait pas si y = 0 car cela générerait une erreur de division par zéro que je n’avais pas prise en compte. Donc, la solution +/- présentée alternativement serait la meilleure solution.

</ obsolète>


Pour que mon code soit immédiatement compréhensible, je serais plus susceptible de faire quelque chose comme ça. [Pensez toujours au pauvre gars qui va devoir maintenir votre code]:

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

Et puis vous pouvez le faire en une seule ligne de code:

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

Ou...

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

Fait comme un dîner ... vous pouvez maintenant passer dans n'importe quel type d'objet et les échanger ...

7
BenAlabaster

Si vous pouvez passer de decimal à double, vous pouvez utiliser la classe Interlocked . Ce sera probablement un bon moyen d'échanger des variables en termes de performances. Aussi légèrement plus lisible que XOR.

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

Msdn: méthode Interlocked.Exchange (Double, Double)

6
Robert Fricke

Pour être complet, voici le swap binaire XOR:

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

Cela fonctionne pour tous les objets/références atomiques, car il traite directement avec les octets, mais peut nécessiter un contexte peu sûr pour travailler sur des décimales ou, si vous vous sentez vraiment tordu, des pointeurs. Et cela peut être plus lent qu'une variable de temp dans certaines circonstances.

5
thecoop

Méfiez-vous de votre environnement!

Par exemple, cela ne semble pas fonctionner dans ECMAscript

y ^= x ^= y ^= x;

Mais cela ne

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

Mon conseil? Supposer le moins possible.

5
Codzart

Avec C # 7, vous pouvez utiliser la déconstruction Tuple pour réaliser l’échange souhaité en une ligne, et le résultat est clair. 

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

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

En C # 7:

(startAngle, stopAngle) = (stopAngle, startAngle);
5
a = a + b
b = a - b
a = a - b

3
srinivasan

J'espère que cela pourrait aider ...

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

Pour les types binaires, vous pouvez utiliser cette astuce géniale:

a %= b %= a %= b;

Tant que a et b ne sont pas exactement la même variable (par exemple, des alias pour la même mémoire), cela fonctionne.

2
BCS

Avec des tuples

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

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

nous pouvons le faire en faisant un tour simple

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);
1
cammando

Si vous voulez échanger 2 variables de chaîne:

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

Une méthode d'assistance en conséquence:

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

L'utilisation serait alors:

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

Voici une autre approche en une ligne:

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

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

Voici un processus différent pour échanger deux variables 

//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