Поменяйте местами две переменные без использования временной переменной - PullRequest
58 голосов
/ 30 апреля 2009

Я бы хотел иметь возможность поменять две переменные без использования временной переменной в C #. Можно ли это сделать?

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

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

Ответы [ 28 ]

210 голосов
/ 30 апреля 2009

Способ вправо для замены двух переменных:

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

Другими словами, использовать временную переменную.

Вот, пожалуйста. Никаких хитрых трюков, никаких сопровождающих вашего кода, проклинающих вас на протяжении десятилетий, никаких записей в Daily WTF , и не тратите слишком много времени, пытаясь выяснить, зачем вам это нужно в одной операции, поскольку самый низкий уровень, даже самая сложная языковая функция - это серия простых операций.

Просто очень простое, читаемое, простое для понимания решение t = a; a = b; b = t;.

По моему мнению, разработчики, которые пытаются использовать уловки, например, для "подмены переменных без использования temp" или "устройства Даффа", просто пытаются показать, насколько они умны (и терпят неудачу).

Я сравниваю их с теми, кто читает высокопоставленные книги исключительно для того, чтобы казаться более интересными на вечеринках (в отличие от расширения вашего кругозора).

Решения, в которых вы добавляете и вычитаете, или решения на основе XOR, менее читабельны и, скорее всего, медленнее, чем простое решение «временная переменная» (арифметические / логические операции вместо простых перемещений на уровне сборки).

Предоставьте себе и другим услуги, написав качественный читаемый код.

Это моя напыщенная речь. Спасибо за прослушивание: -)

Кроме того, я прекрасно знаю, что это не отвечает на ваш конкретный вопрос (и я извинюсь за это), но есть много прецедентов на SO, когда люди спрашивают, как что-то сделать, и правильный ответ " Не делай этого ".

103 голосов
/ 30 апреля 2009

Прежде всего, замена без временной переменной в языке, так как C # - очень плохая идея .

Но ради ответа вы можете использовать этот код:

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

Однако могут возникнуть проблемы с округлением, если два числа сильно различаются. Это связано с природой чисел с плавающей запятой.

Если вы хотите скрыть временную переменную, вы можете использовать служебный метод:

public static class Foo {

    public static void Swap<T> (ref T lhs, ref T rhs) {
        T temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}
71 голосов
/ 30 апреля 2009

Да, используйте этот код:

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

Проблема сложнее для произвольных значений. : -)

70 голосов
/ 28 августа 2016

C # 7 введено кортежи , что позволяет менять две переменные без временной:

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

Это присваивает b a и a b.

43 голосов
/ 29 декабря 2009
int a = 4, b = 6;
a ^= b ^= a ^= b;

Работает для всех типов, включая строки и числа с плавающей запятой.

19 голосов
/ 05 марта 2013

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

Оба способа используют временную переменную в методе, но вам не нужна временная переменная, в которой вы меняете местами.

15 голосов
/ 27 октября 2013

Бинарный XOR-своп с подробным примером:

Таблица истинности XOR :

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

Введите:

a = 4;
b = 6;

Шаг 1 : a = a ^ b

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

Шаг 2 : b = a ^ b

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

Шаг 3 : a = a ^ b

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

Выход:

a = 6;
b = 4;
11 голосов
/ 18 июля 2014

Ради будущих учеников и человечества я отправляю это исправление на текущий выбранный ответ.

Если вы хотите избежать использования временных переменных, есть только два разумных варианта , которые учитывают сначала производительность, а затем удобочитаемость.

  • Используйте временную переменную в общем методе Swap. (Абсолютная лучшая производительность, рядом со встроенной временной переменной)
  • Использовать Interlocked.Exchange. (В 5.9 раз медленнее на моей машине, но это единственный вариант, если несколько переменных будут менять эти переменные одновременно.)

Вещи, которые вы должны никогда делать:

  • Никогда не используйте арифметику с плавающей запятой. (медленные, ошибки округления и переполнения, трудно понять)
  • Никогда не используйте непримитивную арифметику. (медленно, ошибки переполнения, трудно понять) Decimal не является примитивным процессором и приводит к гораздо большему количеству кода, чем вы думаете.
  • Никогда не используйте арифметический период. Или немного хаков. (медленно, трудно понять) Это работа компилятора. Он может оптимизировать для разных платформ.

Поскольку все любят строгие числа, вот программа, которая сравнивает ваши варианты. Запустите его в режиме выпуска из-за пределов Visual Studio, чтобы Swap было встроено. Результаты на моей машине (Windows 7 64-bit 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();
    }
}
11 голосов
/ 30 апреля 2009

Не в C #. В нативном коде вы можете использовать трюк с тройной заменой XOR, но не на языке высокого уровня, обеспечивающем безопасность типов. (Во всяком случае, я слышал, что трюк XOR в действительности оказывается медленнее, чем использование временной переменной во многих распространенных архитектурах ЦП.)

Вы должны просто использовать временную переменную. Там нет причин, вы не можете использовать один; это не значит, что предложение ограничено.

7 голосов
/ 30 апреля 2009

Вы можете сделать это в 3 строки, используя базовую математику - в моем примере я использовал умножение, но простое сложение также подойдет.

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

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

Редактировать: Как отмечено в комментариях, это не будет работать, если у = 0, поскольку это приведет к ошибке деления на ноль, которую я не рассматривал. Таким образом, альтернативное решение +/- было бы наилучшим способом.


Чтобы мой код был сразу понятен, я бы с большей вероятностью сделал что-то подобное. [Всегда думай о бедняге, которому придется поддерживать твой код]:

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

И тогда вы сможете сделать это в одной строке кода:

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

Готово как ужин ... теперь вы можете передавать любые объекты и переключать их ...

...