habrahabr

10 способов поменять местами два значения в JavaScript

  • понедельник, 28 марта 2022 г. в 00:35:49
https://habr.com/ru/post/657625/
  • JavaScript


В какой-то момент своей карьеры разработчики сталкиваются с задачей обмена значениями. Большую часть времени мы пользуемся классическим методом с использованием дополнительной переменной. Ах, если бы был способ лучше. Но подождите-ка! Такой способ есть, и не один. В моменты отчаяния мы серфим интернет в поисках решений, находим одно, копируем его без какого-либо понимания, как работает этот кусочек кода. К счастью для вас, сейчас самое время понять, как поменять местами два значения просто и эффективно.

1) Использование временной переменной

Сразу отсечем самое очевидное решение.

function swapWithTemp(num1,num2){
  console.log(num1,num2)

  var temp = num1;
  num1 = num2;
  num2 = temp;

  console.log(num1,num2)
}

swapWithTemp(2.34,3.45)

2) Использование сложения и вычитания

Да, вы прочитали правильно. Мы можем использовать немного магии математики, чтобы обменять парочку значений.

function swapWithPlusMinus(num1,num2){
  console.log(num1,num2)

  num1 = num1+num2;
  num2 = num1-num2;
  num1 = num1-num2;

  console.log(num1,num2)
}

swapWithPlusMinus(2.34,3.45)

Что-о-о!? Ага, а теперь давайте посмотрим, как это работает. Мы получаем сумму двух чисел на 4 строке. Сейчас, если мы вычтем одно число из суммы, то получим другое число. Это как раз то, что мы делаем на 5 строке. Вычитание num2 из суммы, которая находится в num1, дает нам изначальное значение num1, которое помещается в num2. Аналогично мы получаем num2 и помещаем его в num1 на 6 строке.

Осторожно: на просторах интернета гуляет еще один метод обмена в одну строчку лишь с операцией сложения.

Вот как он выглядит:

function swapWithPlusMinusShort(num1,num2){
  console.log(num1,num2)

  num2 = num1+(num1=num2)-num2;

  console.log(num1,num2)
}

swapWithPlusMinusShort(2,3)

Код выше дает ожидаемый результат. Выражение внутри () хранит num2 в num1, затем мы вычитаем num1 - num2, а это ноль, так как num2 - num2 = 0. Следовательно, мы получаем нужный результат. Но когда мы используем числа с плавающей точкой, иногда можем увидеть неожиданный результат.

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

function swapWithPlusMinusShort(num1,num2){
  console.log(num1,num2)

  num2 = num1+(num1=num2)-num2;

  console.log(num1,num2)
}

swapWithPlusMinusShort(2,3.1)

3) Использование сложения или вычитания

Мы можем получить тот же результат, используя только операцию сложения.

Посмотрим, как этого добиться:

function swapWithPlus(num1,num2){
  console.log(num1,num2)

  num2 = num1 + (num1=num2, 0)

  console.log(num1,num2)
}

//Попробуйте реализовать это с помощью только вычитания
swapWithPlus(2.3,3.4)

Эта программа работает, но ее читабельность явно страдает. На 4 строке в скобках мы присваиваем num1 num2 и возвращаем 0. По сути наша строка выглядит так:

num2 = num1 + 0 => num2 = num1

Отсюда мы и получаем наш результат.

Замечание: некоторые движки JavaScript могут выполнять свои собственные оптимизации приведенного выше кода, который игнорирует + 0.

4) Использование умножения и деления

Еще немного магии при помощи операторов умножения и деления.

Принцип тот же, что и в предыдущем методе, но с парочкой "причудов".

function swapWithMulDiv(num1,num2){
  console.log(num1,num2)

  num1 = num1*num2;
  num2 = num1/num2;
  num1 = num1/num2;

  console.log(num1,num2)
}

swapWithMulDiv(2.34,3.45)

Собственно, здесь все похоже. Получаем произведения двух чисел и храним его в одном из чисел. Это то, что происходит на 4 строке. Затем делим произведение на второе число и получаем первое, потом повторяем процесс и получаем второе число.

Вы сделали это! Да вы "математический фокусник".

Погодите! Что насчет тех "причудов"?

Давайте попробуем запустить следующий код.

function swapWithMulDiv(num1,num2){
  console.log(num1,num2)

  num1 = num1*num2;
  num2 = num1/num2;
  num1 = num1/num2;

  console.log(num1,num2)
}

//Попробуйте обменять вот таки числа и посмотрите, что выйдет
swapWithMulDiv(2.34,0)

Наши значения не поменяются местами, и вместо этого мы получим NaN. Если вы посещали уроки математики, то помните, что вам всегда говорили не делить на 0, потому что получится неопределенность. Причина кроется в работе ограничений и в некоторых других вещах, в которые мы погружаться не будем. Сейчас же мы посмотрим на еще одну проблему этого метода.

Рассмотрим пример:

function swapWithMulDiv(num1,num2){
  console.log(num1,num2)

  num1 = num1*num2;
  num2 = num1/num2;
  num1 = num1/num2;

  console.log(num1,num2)
}
//Попробуйте обменять вот таки числа и посмотрите, что выйдет
swapWithMulDiv(2.34,Infinity)

Да, вновь NaN. Потому что мы не можем делить на бесконечность, это также неопределенность.

Хотите увидеть еще одну причуду? Я так и думал!

function swapWithMulDiv(num1,num2){
  console.log(num1,num2)

  num1 = num1*num2;
  num2 = num1/num2;
  num1 = num1/num2;

  console.log(num1,num2)
}

//Попробуйте обменять вот таки числа и посмотрите, что выйдет
swapWithMulDiv(2.34,-Infinity)

-бесконечность будет возвращать то же значение, как в прошлом примере, по той же причине.

Даже "математический фокусник" при всех своих силах не может сделать невозможного.

Ниже представлена краткая версия обмена с умножением и делением с теми же проблемами:

function swapWithMulDivShort(num1,num2){
  console.log(num1,num2)

  num2 = num1*(num1=num2)/num2;

  console.log(num1,num2)
}

swapWithMulDivShort(2.3,3.4)

Этот код аналогичен краткой версии обмена со сложением и вычитанием. Мы присваиваем num1 num2, а потом 4 строка выглядит так:

num2 = num1 * num2 / num2
=> num2 = num1

Вуаля! Наши значения поменялись местами.

5) Использование умножения или деления

function swapWithMul(num1,num2){
  console.log(num1,num2)

  num2 = num1 * (num1=num2, 1)

  console.log(num1,num2)
}

//попробуйте сделать обмен через деление и возведение в степень
swapWithMul(2.3,3.4)

Эта программа работает, но ее читабельность явно страдает. На 4 строке в скобках мы присваиваем num1 num2 и возвращаем 1. На деле наша строка выглядит так:

num2 = num1 * 1 => num2 = num1

И мы вновь получаем требуемый результат.

6) Использование побитового исключающего ИЛИ

XOR манипулирует битами. Возвращает 1, когда значения переменных различны, и 0 в противном случае.

X

Y

X^Y

1

1

0

1

0

1

0

1

1

0

0

0

Теперь поймем, как это работает.

function swapWithXOR(num1,num2){
  console.log(num1,num2)

  num1 = num1^num2;
  num2 = num1^num2; 
  num1 = num1^num2;

  console.log(num1,num2)
}

// протестируйте также отрицательные значения
swapWithXOR(10,1)

4-ех битное представление 10 -> 1010

4-ех битное представление 1 -> 0001

Тогда:

На 4 строке: num1 = num1 ^ num2 => 1010 ^ 0001 => 1011 => 7 
На 5 строке: num2 = num1 ^ num2 => 1011 ^ 0001 => 1010 => 10
На 6 строке: num1 = num1 ^ num2 => 1011 ^ 1010 => 0001 => 1

Вуаля! Мы вновь поменяли значения местами.

Посмотрим другой пример.

function swapWithXOR(num1,num2){
  console.log(num1,num2)

  num1 = num1^num2;
  num2 = num1^num2;
  num1 = num1^num2;

  console.log(num1,num2)
}

swapWithXOR(2.34,3.45)

Хм, где же обмен? Мы получили только целую часть числа. И в этом проблема такого способа. XOR предполагает, что входные параметры это целые числа, и уже в соответствии с этим производит вычисления. Но числа с плавающей точкой не являются целыми и представлены стандартом IEEE 754, согласно которому число разбивается на 3 части: знаковый бит, группа битов, представляющих показатель степени, еще одна группа битов, представляющая число между 1 (включая) и 2 (не включая), то есть мантиссу. Поэтому мы и получаем не тот результат.

Еще один пример:

function swapWithXOR(num1,num2){
  console.log(num1,num2)

  num1 = num1^num2;
  num2 = num1^num2;
  num1 = num1^num2;

  console.log(num1,num2)
}
//Поэксперементируйте с целыми числами и бесконечностью.
swapWithXOR(-Infinity,Infinity)

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

7) Использование побитового исключающего НЕ ИЛИ

Эта операция противоположна исключающему ИЛИ. XNOR возвращает 0, если значения различны, и 1 в противном случае. В JavaScript нет специального оператора для XNOR, так что мы используем NOT оператор, чтобы инвертировать XOR.

X

Y

XNOR

1

1

1

1

0

0

0

1

0

0

0

1

Давайте поймем, как это работает!

function swapWithXNOR(num1,num2){
  console.log(num1,num2)

  num1 = ~(num1^num2);
  num2 = ~(num1^num2);
  num1 = ~(num1^num2);

  console.log(num1,num2)
}

//Попробуйте отрицательные значения
swapWithXNOR(10,1)

4-ех битное представление 10 -> 1010

4-ех битное представление 1 -> 0001

На 4 строчке:

num1 = ~(num1 ^ num2) => ~(1010 ^ 0001) =>~(1011) => ~11 => -12

Так как мы получили отрицательное значение, нам необходимо конвертировать его в двоичное, инвертировать биты и прибавить единицу:

-12 => 1100 => 0011 + 1 => 0100

На 5 строчке:

num2 = ~(num1 ^ num2) => ~(0100 ^ 0001) => ~(0101) => ~5 => -6
-6 => 0110 => 1001 + 1 => 1010 => 10

На 6 строчке:

num1 = ~(num1 ^ num2) => ~(0100^ 1010) => ~(1110) => ~14 => -15
-15 => 1111 => 0000 + 1 => 0001 => 1

Это займет некоторое время, но мы получим обмен значений. Но, к сожалению, у этого способа будут те же проблемы, что и у метода с XOR. Все плохо с числами с плавающей точкой и бесконечностями.

Попробуйте поэкспериментировать со значениями, как в коде ниже.

function swapWithXNOR(num1,num2){
  console.log(num1,num2)

  num1 = ~(num1^num2);
  num2 = ~(num1^num2);
  num1 = ~(num1^num2);

  console.log(num1,num2)
}

swapWithXNOR(2.3,4.5)

8) Использование присваивания внутри массива

Это трюк в одну строку. Правда! Вам просто нужна одна строка для обмена и, что более важно, никакой математики. Понадобятся лишь базовые знания о массивах. Может выглядеть странно, но оно работает.

Посмотрим на этот метод в действии!

function swapWithArray(num1,num2){
  console.log(num1,num2)

  num2 = [num1, num1 = num2][0];

  console.log(num1,num2)
}

swapWithArray(2.3,Infinity)

В нулевом индексе массива хранится num1, в 1 индексе мы присваиваем num1 num2 и, соответственно, храним num2. Затем мы просто обращаемся к 0 индексу, чтобы получить num1 и присвоить это значения num2. Таким образом мы может менять и целые числа, и числа с плавающей точкой, и бесконечности, и даже строки. Выглядит довольно аккуратно, но мало читабельно. Взглянем на похожий способ.

9) Использование деструктуризации

Это нововведение, появившееся в ES6. И оно очень простое. В одну линию мы можем поменять значения местами:

let num1 = 23.45;
let num2 = 45.67;

console.log(num1,num2);

[num1,num2] = [num2,num1];

console.log(num1,num2);

10) Использование немедленно вызываемой функции (IIFE)

Еще один странный вариант обмена. IIFE - это функция, которая вызывается сразу после ее объявления.

Посмотрим, как мы можем ее использовать:

function swapWithIIFE(num1,num2){
  console.log(num1,num2)

  num1 = (function (num2){ return num2; })(num2, num2=num1)

  console.log(num1,num2)
}

swapWithIIFE(2.3,3.4)

В этом примере мы сразу же вызываем функцию на 4 строке. Скобки в конце это аргументы. Во втором аргументе мы присваиваем num2 num1, а первый просто возвращается из функции. Наши значения вновь поменялись местами. Но имейте в виду, этот способ не эффективный.

Заключение

В этой статье мы рассмотрели несколько способов обмена значениями в JavaScript. Надеюсь, вы подчерпнули для себя что-то новое. Спасибо за прочтение!