Как написать выражение с тернарным оператором (иначе, если), не повторяя себя

104

Например, примерно так:

var value = someArray.indexOf(3) !== -1 ? someArray.indexOf(3) : 0

Есть ли лучший способ написать это? Опять же, я не ищу ответа на точный вопрос выше, просто пример того, когда вы могли повторять операнды в выражениях тернарных операторов ...

user1354934
источник
2
So an, ifа не anif/else
zer00ne
53
just an example of when you might have repeated things in the ternaryне повторяйте вычисленные выражения. Вот для чего нужны переменные.
vol7ron
4
Как мы определяем , что 3не в индексе 0из someArray?
guest271314
3
Какая у вас здесь цель? Вы пытаетесь уменьшить длину строки или специально пытаетесь избежать повторения переменной в троичной системе? Первое возможно, второе - нет (по крайней мере, без использования троичных).
asgallant
5
Почему бы не использовать Math.max(someArray.indexOf(3), 0)вместо этого?
301_Moved_Permanently

Ответы:

176

Лично я считаю, что лучший способ сделать это - по-прежнему старое доброе ifутверждение:

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}
слебетман
источник
32
Чтобы быть ясным, этот ответ защищает использование переменной для хранения промежуточного результата, а не использование ifоператора вместо троичного. Тернарные операторы по-прежнему часто используются для выполнения выбора как части выражения.
Триптих
4
@Triptych: Посмотрите еще раз. Он вообще не использует промежуточную переменную. Вместо этого он присваивает результат непосредственно последней переменной, а затем перезаписывает его, если условие выполняется.
slebetman
14
Неправильно. Значение, сохраненное valueпосле первой строки, является промежуточным. Он не содержит правильного значения, которое затем фиксируется в следующей строке и, таким образом, является промежуточным. Только после того, как ifутверждение valueсодержит правильное значение. Тернар в OP - лучшее решение, потому что он никогда не входит в промежуточное состояние.
Джек Эйдли,
44
@JackAidley: «Тернар в OP - лучшее решение, чем это, потому что он никогда не переходит в промежуточное состояние». - Мне придется не согласиться. Это намного более читабельно, чем код OP, и полностью идиоматично. Это также делает ошибку в логике OP более очевидной для меня (а именно, что произойдет, если indexOf () вернет ноль? Как отличить «настоящий» ноль от «не найденного» нуля?).
Кевин
2
это близко к тому, что я сделал бы, за исключением того, что valueздесь технически видоизменено, чего я стараюсь избегать.
Дэйв Кузино,
102

Код должен быть удобочитаемым, поэтому краткость не должна означать, что вы должны быть краткими независимо от стоимости - для этого вам следует сделать репост на https://codegolf.stackexchange.com/ - поэтому вместо этого я бы рекомендовал использовать вторую локальную переменную с именем, indexчтобы максимизировать понятность чтения ( с минимальными затратами времени выполнения тоже отмечу):

var index = someArray.indexOf( 3 );
var value = index == -1 ? 0 : index;

Но если вы действительно хотите сократить это выражение, потому что вы жестокий садист по отношению к своим коллегам или сотрудникам проекта, то вот 4 подхода, которые вы можете использовать:

1: временная переменная в varоператоре

Вы можете использовать возможность varоператора определять (и назначать) вторую временную переменную, indexразделенную запятыми:

var index = someArray.indexOf(3), value = index !== -1 ? index: 0;

2: Самостоятельно выполняющаяся анонимная функция

Другой вариант - самоисполняющаяся анонимная функция:

// Traditional syntax:
var value = function( x ) { return x !== -1 ? x : 0 }( someArray.indexOf(3) );

// ES6 syntax:
var value = ( x => x !== -1 ? x : 0 )( someArray.indexOf(3) );

3: оператор запятой

Есть также печально известный «оператор запятой», который поддерживает JavaScript, который также присутствует в C и C ++.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

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

Вы можете использовать его для введения побочных эффектов, в этом случае переназначив value:

var value = ( value = someArray.indexOf(3), value !== -1 ? value : 0 );

Это работает, потому что var valueсначала интерпретируется (как оператор), а затем самое левое, самое внутреннее valueприсваивание, затем правая часть оператора запятой, а затем тернарный оператор - все это допустимый JavaScript.

4. Переназначить в подвыражении

Комментатор @IllusiveBrian указал, что в использовании оператора-запятой (в предыдущем примере) нет необходимости, если присвоение to valueиспользуется в качестве подвыражения в скобках:

var value = ( ( value = someArray.indexOf(3) ) !== -1 ? value : 0 );

Обратите внимание, что использование отрицательных чисел в логических выражениях может быть труднее для понимания людьми, поэтому все приведенные выше примеры можно упростить для чтения, изменив idx !== -1 ? x : yна idx == -1 ? y : x:

var value = ( ( value = someArray.indexOf(3) ) == -1 ? 0 : value );
Дай
источник
97
Все это своего рода «умное» кодирование, которое заставило бы меня смотреть на него несколько секунд, прежде чем подумать: «Ага, я думаю, это работает». Они не помогают с ясностью, и поскольку код больше читается, чем пишется, это более важно.
Гэвин С. Янси
18
Подход @ g.rocket 1 на 100% читабелен и понятен, и это единственный выход, если вы хотите избежать повторения (это может быть действительно вредно, если вы вызываете какую-то сложную и проблемную функцию вместо простой indefOf)
edc65
5
Согласны, №1 очень удобочитаем и обслуживаем, два других - не очень.
forgivenson
6
Что касается №3, я считаю, что вы можете упростить его до var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);вместо использования запятой.
Призрачный
2
Во втором примере Самостоятельно выполняющаяся анонимная функция вы можете опустить скобки в стрелочной функции. var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );потому что это всего лишь один параметр.
Alex Char
54

Для чисел

Вы можете использовать Math.max()функцию.

var value = Math.max( someArray.indexOf('y'), 0 );

Он сохранит границы результата от времени 0до первого результата выше, чем 0в этом случае. И если результат из indexOfis, -1он вернет 0 как больше чем -1.

Для логических и логических значений y

Для JS нет общего правила AFAIK, особенно из-за того, как оцениваются ложные значения.

Но если что-то может вам помочь, так это оператор or ( ||):

// Instead of
var variable = this_one === true ? this_one : or_this_one;
// you can use
var variable = this_one || or_this_one;

Вы должны быть очень осторожны с этим, потому что в вашем первом примере indexOfможет возвращаться, 0и если вы оцените, 0 || -1он вернется, -1потому что 0это ложное значение.

Кришофоро Гаспар
источник
2
Спасибо. Думаю, мой пример плох, я просто привожу общий пример, ха-ха, не ищу решения точного вопроса. Я столкнулся с некоторыми сценариями, такими как пример, в котором я хотел бы использовать троичную систему, но в конечном итоге повторяю :(
user1354934
1
В первом примере, как мы определяем , что 3или "y"не по индексу 0в someArray?
guest271314
На Math.maxпримере? indexOfвозвращает индекс элемента, и если элемент не найден, возвращает -1, поэтому у вас есть шансы получить число от -1 до длины строки, тогда Math.maxвы просто установите границы от 0 до длины, чтобы удалить шанс чтобы вернуть -1,
Кришофоро Гаспар
@mitogh Логика кода в OP создает проблему, хотя общий пример есть; где 0 может указывать как индекс 0совпадающего элемента в массиве, так и 0установить в Math.max(); или в условном операторе OP. Посмотрим var value = Math.max( ["y"].indexOf("y"), 0 ). Как определить, что 0возвращается? 0передается в Math.max()вызов или 0отражает индекс "y"внутри массива?
guest271314
@ guest271314 Хорошая мысль, но я думаю, это зависит от контекста, проблема в этом или нет. Возможно, не имеет значения, откуда взялся 0, и важно только то, что это не -1. Пример: возможно, вам нужно выбрать элемент из массива. Вам нужен конкретный элемент (в OP, номер 3), но если его нет в массиве, вам все равно нужен элемент, и вас устраивает выбор по умолчанию того, что является первым элементом, если вы знаете, что массив не ' т пусто.
Кев
27

Не совсем, просто используйте другую переменную.

Ваш пример обобщается примерно на это.

var x = predicate(f()) ? f() : default;

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

var computed = f();
var x = predicate(computed) ? computed : default;

Я понимаю, что вы имеете в виду - похоже, должен быть какой-то способ сделать это, чтобы это выглядело немного чище. Но я думаю, что это лучший способ (идиоматически) сделать это. Если вы по какой-то причине часто повторяете этот шаблон в своем коде, вы можете написать небольшую вспомогательную функцию:

var setif = (value, predicate, default) => predicate(value) ? value : default;
var x = setif(someArray.indexOf(3), x => x !== -1, 0)
Триптих
источник
17

РЕДАКТИРОВАТЬ: Вот оно, предложение об объединении значений Nullary теперь в JavaScript!


Использовать ||

const result = a ? a : 'fallback value';

эквивалентно

const result = a || 'fallback value';

Если приведение aк Booleanвозврату false, resultбудет присвоено 'fallback value', в противном случае значение a.


Будьте в курсе края случае a === 0, когда отливают к falseи resultбудет (неправильно) принять 'fallback value'. Используйте подобные уловки на свой страх и риск.


PS. В таких языках, как Swift, есть nil-coalescing operator ( ??), который служит аналогичной цели. Например, в Swift вы бы написали, result = a ?? "fallback value"что довольно близко к JavaScriptconst result = a || 'fallback value';

Любомир
источник
3
PHP (> 7.0) и C # также поддерживают нулевой оператор объединения. Синтаксический сахар, но, безусловно, прекрасный.
Hissvard
5
Это работает только тогда, когда функция возвращает ложное значение при сбое, но indexOf()не может использоваться в этом шаблоне.
Barmar
1
Правильно, но он не просит конкретный пример> «Опять же, не ищу ответа на точный вопрос выше, просто пример того, когда вы могли повторить что-то в троичном» <, заметьте, он скорее просит общий способ заменить повторяющийся троичный (результат = a? a: b). Повторяющийся троичный эквивалент || (result = a || b)
Любомир
2
Это действительно , как ||работает в JavaScript? Если я вас правильно понимаю, то он работает иначе, чем многие другие основные языки (я думаю в первую очередь о C и его потомках [C ++, Java и т. Д.]). Даже если это действительно так ||работает в JavaScript, я бы посоветовал не использовать подобные уловки, требующие от сопровождающих особого знания языка. Хотя этот трюк и хорош, я считаю его плохой практикой.
Loduwijk
1
Также обратите внимание, что вопрос заключается в сравнении значения с -1. Опять же, я не могу говорить о JavaScript и его причудах, но, как правило -1, это истинное значение, а не ложное, и поэтому ваш ответ не будет работать в случае вопроса и, конечно, не в общем случае, а будет работать только в конкретном ( но достаточно распространенный) подслучай.
Loduwijk
8

Используйте рефакторинг переменной extract :

var index = someArray.indexOf(3);
var value = index !== -1 ? index : 0

Даже лучше с constвместо var. Вы также можете выполнить дополнительное извлечение:

const index = someArray.indexOf(3);
const condition = index !== -1;
const value = condition ? index : 0;

На практике используют более осмысленные имена , чем index, conditionи value.

const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;
Дэйв Кузино
источник
Что такое «извлекаемая переменная»? Это установленный срок?
Питер Мортенсен
6

Вероятно, вы ищете оператора объединения. К счастью, мы можем использовать Arrayпрототип для его создания:

Array.prototype.coalesce = function() {
    for (var i = 0; i < this.length; i++) {
        if (this[i] != false && this[i] != null) return this[i];
    }
}

[null, false, 0, 5, 'test'].coalesce(); // returns 5

В дальнейшем это можно обобщить на ваш случай, добавив параметр к функции:

Array.prototype.coalesce = function(valid) {
    if (typeof valid !== 'function') {
        valid = function(a) {
            return a != false && a != null;
        }
    }

    for (var i = 0; i < this.length; i++) {
        if (valid(this[i])) return this[i];
    }
}

[null, false, 0, 5, 'test'].coalesce(); // still returns 5
[null, false, 0, 5, 'test'].coalesce(function(a){return a !== -1}); // returns null
[null, false, 0, 5, 'test'].coalesce(function(a){return a != null}); //returns false
Тизоид
источник
Добавление к прототипу массивов рискованно, потому что новый элемент становится индексом внутри каждого массива. Это означает , что перебор индексов также включает новый метод: for (let x in ['b','c']) console.log(x);принты 0, 1, "coalesce".
Чарли Хардинг
@CharlieHarding Верно, но обычно не рекомендуется использовать оператор for-in при просмотре массивов. См stackoverflow.com/a/4374244/1486100
Tyzoid
6

Лично я предпочитаю два варианта:

  1. Чисто, если, как предложил @slebetman

  2. Отдельная функция, которая заменяет недопустимое значение на значение по умолчанию, как в этом примере:

function maskNegative(v, def) {
  return v >= 0 ? v : def;
}

Array.prototype.indexOfOrDefault = function(v, def) {
  return maskNegative(this.indexOf(v), def);
}

var someArray = [1, 2];
console.log(someArray.indexOfOrDefault(2, 0)); // index is 1
console.log(someArray.indexOfOrDefault(3, 0)); // default 0 returned
console.log(someArray.indexOfOrDefault(3, 123)); // default 123 returned

Илья Бурсов
источник
1
+1, вариант 2 учитывает внутреннюю цель вопроса, может эффективно применяться к другим языкам, кроме javascript, и способствует модульности.
Devsman
5

Мне нравится ответ @leebetman. Комментарий под ним выражает озабоченность по поводу того, что переменная находится в «промежуточном состоянии». если это вас очень беспокоит, я предлагаю инкапсулировать его в функцию:

function get_value(arr) {
   var value = arr.indexOf(3);
   if (value === -1) {
     value = 0;
   }
   return value;
}

Тогда просто позвони

var value = get_value( someArray );

Вы могли бы выполнять более общие функции, если у вас есть их использование в других местах, но не переусердствуйте, если это очень специфический случай.

Но, честно говоря, я бы просто сделал это как @slebetman, если бы мне не нужно было повторно использовать из нескольких мест.

Адам
источник
4

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

var value = someArray.indexOf(3) !== -1 ? someArray.indexOf(3) : 0;

может быть (и должно быть, учитывая вызовы функций) сокращено следующим образом:

var value = someArray.indexOf(3);
value = value !== -1 ? value : 0;

Если вы ищете более общее решение, которое предотвращает повторение переменной в тернарном виде, например:

var value = conditionalTest(foo) ? foo : bar;

где fooпоявляется только один раз. Отказ от решений вида:

var cad = foo;
var value = conditionalTest(foo) ? cad : bar;

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

Примеры:

javascript, используя ||для возврата RHS, когда LHS falsey:

var value = foo || bar; // equivalent to !foo ? bar : foo
благородный
источник
1
Вопрос помечен как javascript и не упоминает C #. Просто интересно, почему вы заканчиваете конкретными примерами C #.
Loduwijk
Я пропустил тег javascript в вопросе; удалил C #.
asgallant
3

Используйте вспомогательную функцию:

function translateValue(value, match, translated) {
   return value === match ? translated : value;
}

Теперь ваш код очень удобочитаем и не требует повторения.

var value = translateValue(someArray.indexOf(3), -1, 0);

Иерархия проблем кодирования:

  1. Правильно (включая проблемы с реальной производительностью или SLA)
  2. Очистить
  3. Краткий
  4. Быстро

Все ответы на странице пока кажутся правильными, но я думаю, что моя версия отличается высочайшей ясностью, что важнее краткости. Если вы не учитываете вспомогательную функцию - поскольку ее можно использовать повторно - она ​​также является наиболее краткой. В чем-то похожем предложении использовать вспомогательную функцию, к сожалению, используется лямбда, которая, как мне кажется, просто скрывает то, что она делает. Для меня гораздо лучше более простая функция с одной целью, которая не принимает лямбда, а только значения.

PS Если вам нравится синтаксис ES6:

const translateValue = (value, match, translated) => value === match ? translated : value;
let value = translateValue(someArray.indexOf(3), -1, 0); // or const
Эрике
источник
2
«моя версия имеет высочайшую ясность» - я не согласен. Имя функции слишком длинное, и присвоение имен входным и выходным параметрам совершенно бесполезно.
adelphus
Так вы можете предложить лучшие имена? Я был бы счастлив их развлечь. Ваша жалоба касается исключительно косметики, давайте исправим косметику. Правильно? В противном случае вы просто потеряете велосипед.
ErikE
В некоторых случаях такая вспомогательная функция может оказаться полезной. В этом случае, когда он заменяет только конкретный случай тернарного оператора, ваша функция будет менее понятной. Я не собираюсь вспоминать, что делает ваша функция, и мне придется искать ее снова каждый раз, когда я сталкиваюсь с ней, и я никогда не забуду ее использовать.
Loduwijk
1
@Aaron Это разумная оценка для конкретного случая использования. Как бы то ни было, мое первоначальное имя функции translateValueIfEqualбыло более наглядным, но я изменил его после того, как кто-то решил, что оно слишком длинное. Как и Nzфункция в Access, если вы это знаете, вы это знаете, а если нет, то нет. В современных IDE вы можете просто нажать клавишу, чтобы перейти к определению. И запасным вариантом будет промежуточная переменная. Я не вижу здесь большого минуса.
ErikE
1
Это просто показывает, что если вы зададите один и тот же вопрос 5 разным инженерам, вы получите 10 разных ответов.
Loduwijk
2

Думаю, ||оператора можно настроить на indexOf:

var value = ((someArray.indexOf(3) + 1) || 1) - 1;

Возвращаемое значение сдвигается вверх на 1, делая 0 из -1, что является ложным и поэтому заменяется вторым 1. Затем оно сдвигается назад.

Однако имейте в виду, что удобочитаемость лучше, чем исключение повторений.

IllidanS4 хочет вернуть Монику
источник
2

Это простое решение с побитовым НЕ и значением по умолчанию, -1которое позже обнуляется.

index = ~(~array.indexOf(3) || -1);

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

Посмотрим на таблицу истины:

 indexOf    ~indexOf   boolean    default     value      result         comment
---------  ---------  ---------  ---------  ---------  ---------  ------------------
      -1          0     falsy          -1         -1          0   take default value
       0         -1    truthy                     -1          0
       1         -2    truthy                     -2          1
       2         -3    truthy                     -3          2
Нина Шольц
источник
0

Вы можете использовать переназначение:

  • инициализировать переменную одним значением
  • используйте сериализацию &&оператора для переназначения, потому что если первое условие ложно, второе выражение не будет оцениваться

Ex.

var value = someArray.indexOf(3);
value == -1 && (value=0);

vol7ron
источник
3
@MinusFour В некоторой степени. Переменная повторяется, а не выражение someArray.indexOfвыполняется только один раз
vol7ron
Вы повторяете value.
MinusFour
3
@MinusFour Верно, но это более полезно для больших выражений, повторение переменной несущественно по сравнению с сохранением операций. Я предполагаю, что OP не работает с -1и 0; в противном случае это max()был бы лучший вариант
vol7ron
2
Верно ... но вопрос в том ... «Как писать троичные, не повторяя себя»
MinusFour
4
Again, not seeking an answer to the exact question above
Там
0

Учитывая пример кода на вопрос, не ясно , как это будет определено , что 3является или не установлен на уровне индекса 0в someArray. -1возвращенный из .indexOf()будет ценен в этом случае для исключения предполагаемого несоответствия, которое могло бы быть совпадением.

Если 3не входит в массив, -1будет возвращено. Мы можем добавить 1к результату .indexOf()для оценки как falseрезультат -1, за которым следует || ORоператор и 0. При valueссылке вычтите, 1чтобы получить индекс элемента массива или -1.

Который ведет обратно просто используя .indexOf()и проверки -1при ifусловии. Или, определяя , valueкак , undefinedчтобы избежать возможной путаницы в результате фактический оцениваемого состояния , относящегося к первоначальной ссылке.

var someArray = [1,2,3];
var value = someArray.indexOf(3) + 1 || 1;
console.log(value -= 1);

var someArray = [1,2,3];
var value = someArray.indexOf(4) + 1 || 1;
// how do we know that `4` is not at index `0`?
console.log(value -= 1);

var someArray = [1,2,3];
var value = someArray.indexOf(4) + 1 || void 0;
// we know for certain that `4` is not found in `someArray`
console.log(value, value = value || 0);

гость271314
источник
0

Тернар похож на if-else, если вам не нужна часть else, почему бы вместо этого не использовать одно if ..

if ((value = someArray.indexOf(3)) < 0) value = 0;
Халед К.
источник
0

В этом конкретном случае вы можете использовать короткое замыкание с помощью логического ||оператора. Поскольку 0это считается ложным, вы можете добавить 1в свой индекс, таким образом, если index+1это так, 0вы получите правую часть логического - или в качестве своего результата, в противном случае вы получите свой index+1. Поскольку желаемый результат компенсируется 1, вы можете вычесть 1из него, чтобы получить свой индекс:

const someArray = [1, 2, 3, 4];
const v = ((someArray.indexOf(3)+1) || 1)-1;
console.log(v);

Ник Парсонс
источник