Як написати вираз потрійного оператора (він же, якщо), не повторюючи себе


104

Наприклад, щось подібне:

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

Чи є кращий спосіб написати це? Знову ж таки, я не шукаю відповіді на точне запитання вище, лише приклад того, коли ви могли повторити операнди в термінальних виразах операторів ...


2
Отже, ifа не anif/else
zer00ne

53
just an example of when you might have repeated things in the ternaryне повторювати обчислені вирази. Ось для чого змінні.
vol7ron

4
Як ми визначаємо , що 3не в індексі 0з someArray?
гість271314

3
Яка ваша мета тут? Ви намагаєтесь зменшити довжину рядка чи конкретно намагаєтесь уникнути повторення змінної у потрійному? Перший можливий, другий - ні (принаймні, не використовує тернарії).
асгалант

5
Чому б не використовувати Math.max(someArray.indexOf(3), 0)замість цього?
301_Moved_Постійно

Відповіді:


176

Особисто я вважаю, що найкращий спосіб зробити це все ще добре старе ifтвердження:

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}

32
Щоб було зрозуміло, ця відповідь виступає за використання змінної для зберігання проміжного результату, а не використання ifоператора замість потрійного. Потрійні висловлювання все ще часто корисні для вибору як частини вирази.
Триптих

4
@Triptych: Поглянь на це ще раз. Він взагалі не використовує проміжну змінну. Замість цього він присвоює результат безпосередньо кінцевій змінній, а потім перезаписує її, якщо умова виконана.
slebetman

14
Неправильно. Значення, що зберігається valueпісля першого рядка, є проміжним. Він не містить правильного значення, яке потім фіксується на наступному рядку і, таким чином, є проміжним. Лише після того, як ifтвердження укладається, valueміститься правильне значення. Трійця в ОП є кращим рішенням, оскільки це тому, що воно ніколи не переходить у проміжний стан.
Джек Едлі

44
@JackAidley: "Трійця в ОП є кращим рішенням, оскільки це тому, що він ніколи не переходить в проміжний стан". - Мені доведеться не погодитися. Це набагато читабельніше, ніж код ОП, і цілком ідіоматичне. Це також робить помилку в логіці ОП для мене трохи більш очевидною (а саме, що відбувається, якщо indexOf () повертає нуль? Як відрізнити "реальний" нуль від "не знайденого" нуля?).
Кевін

2
це близько до того, що я б робив, за винятком того, що valueтут технічно мутують, чого я намагаюся уникати.
Дьюз Кузен

102

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

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

Але якщо ви дійсно хочете скоротити цей вираз, оскільки ви жорстокий садист зі своїми колегами чи співробітниками проектів, то ось чотири підходи, які ви можете використати:

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 зазначив, що використання кома-оператора (у попередньому прикладі) є зайвим, якщо призначення призначене для 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 дуже читабельний і піддається ремонту, а інших двох не дуже.
прощання

6
Я вважаю, що для №3 ви можете спростити var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);замість кома.
IllusiveBrian

2
У другому прикладі самовиконання анонімної функції ви можете опустити дужки з функції стрілки. var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );тому що це лише один параметр.
Алекс Чар

54

Для чисел

Ви можете використовувати Math.max()функцію.

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

Він буде зберігати межі результату 0до моменту, коли перший результат буде більшим, ніж 0якщо це так. І якщо результат з indexOfцього, -1він поверне 0, як більше -1.

Для булевих та булевих значень y

Для JS немає загального правила AFAIK спеціально, тому що, як оцінюються помилкові значення.

Але якщо щось може допомогти вам більшість часу - це або оператор ( ||):

// 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?
гість271314

На Math.maxприкладі? indexOfповертає індекс елемента, і якщо елемента не знайдено, повертається -1, тож у вас є шанси отримати число від -1 до довжини рядка, тоді Math.maxви просто встановите межі від 0 до довжини, щоб видалити шанс повернути -1,
Крісофоро Гаспар

@mitogh Логіка коду в ОП створює проблему, загальний приклад, хоча вона є; де 0 може і вказувати індекс 0відповідного елемента в масиві, або 0встановлювати в Math.max(); або у умовного оператора на ОП. Розглянемо var value = Math.max( ["y"].indexOf("y"), 0 ). Як визначити, що 0повертається? 0передано на Math.max()виклик або 0відображає індекс "y"масиву?
гість271314

@ guest271314 Гарна думка, але я думаю, що це залежить від контексту, чи це проблема чи ні. Можливо, не має значення, звідки взявся 0, і єдине важливе, що це не -1. Приклад: можливо, вам потрібно вибрати елемент з масиву. Ви хочете певного елемента (в ОП, число 3), але якщо цього немає в масиві, вам все-таки потрібен елемент, і ви будете в порядку з дефолтом до першого пункту, якщо припустити, що ви знаєте масив не ' t порожній.
Кев

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

EDIT: Ось пропозиція про 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 # також підтримують оператор з’єднання нуля. Синтаксичний цукор, але, безумовно, прекрасний.
Гіссвард

5
Це працює лише тоді, коли функція повертає значення фальси, коли вона не працює, але indexOf()не може бути використана в цьому шаблоні.
Бармар

1
Правильно, але він не просить конкретного прикладу> "Знову ж, не шукаючи відповіді на точне вище запитання, лише приклад того, коли ви, можливо, повторили речі в потрійному віці" <, майте на увазі, що він швидше просить загальний спосіб замініть повторювані трійкові (результат = a? a: b). І повторення потрійного рівнозначно || (результат = а || б)
Любомир

2
Це справді, як ||працює JavaScript? Якщо я вас правильно розумію, то він працює інакше, ніж багато інших первинних мов (я думаю, перш за все, про C та його нащадків [C ++, Java тощо). Навіть якщо це дійсно так ||працює в JavaScript, я б радив не використовувати подібні трюки, які вимагають від технічного персоналу знати особливі примхи про мову. Хоча цей трюк крутий, я вважаю це поганою практикою.
Loduwijk

1
Також зауважте, що питання порівнює значення зі значенням -1. Знову ж таки, я не можу говорити за JavaScript та його химерності, але, як правило, це -1було б справжньою цінністю, а не хибною, і тому ваша відповідь не працювала б у справі питання, і, звичайно, не в загальному випадку, а працювала б лише в конкретному ( але досить поширений) підряд.
Loduwijk

8

Використовуйте змінний рефакторинг :

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 True, але, як правило, не рекомендується використовувати оператор 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, і сприяє модульності.
Девсман

5

Мені подобається відповідь @ slebetman. У коментарі під ним висловлюється занепокоєння тим, що змінна знаходиться в "проміжному стані". якщо це викликає велике занепокоєння для вас, я пропоную інкапсулювати його у функції:

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

Я пропустив тег javas у запитанні; видалено C #.
асгалант

3

Використовуйте функцію помічника:

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

Тепер ваш код дуже читабельний, і повторення немає.

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

Ієрархія проблем кодування:

  1. Правильно (включаючи справжню ефективність чи проблеми угод за умовами угоди)
  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

У деяких випадках така функція помічника може бути корисною. У цьому випадку, коли він замінює лише конкретний випадок потрійного оператора, ваша функція буде менш зрозумілою. Я не збираюсь пам'ятати, що робить ваша функція, і мені доведеться переглядати її знову, кожного разу, коли я натрапляю на неї, і ніколи б не згадав її використовувати.
Лодувейк

1
@Aaron Це розумна оцінка для конкретного випадку використання. Для чого це варто, моє оригінальне ім’я функції було те, translateValueIfEqualщо я вважав більш описовим, але я змінив його після того, як хтось вважав, що це занадто довго. NzНаче подібна функція в Access, якщо ви знаєте її, ви знаєте її, а якщо ні, то не. У сучасних ІДЕ ви можете просто натиснути клавішу, щоб перейти до визначення. І резервною мірою буде проміжна змінна. Я насправді не бачу тут великого нижнього боку.
ErikE

1
Це просто свідчить про те, що якщо ви задасте одне і те ж питання 5 різним інженерам, ви отримаєте 10 різних відповідей.
Loduwijk

2

Я думаю, що ||оператор може бути налаштований на indexOf:

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

Повернене значення зміщується вгору на 1, роблячи 0 з -1, що є фальсею, і тому його замінюють другим 1. Потім воно зміщується назад.

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


2

Це просте рішення з розрядним NOT і значенням за замовчуванням, -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

Ви можете використовувати повторне призначення:

  • ініціалізувати змінну до одного значення
  • використовувати серіалізацію &&оператора для перепризначення, тому що якщо перша умова помилкова, другий вираз не буде оцінено

Вих.

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


3
@MinusFour До певної міри. Змінна повторюється, а не вираз someArray.indexOfвиконується лише один раз
vol7ron

Ви повторюєте value.
MinusFour

3
@MinusFour Правильно, але це корисніше для більших виразів, повторення змінної є незначним порівняно із збереженням ops. Я здогадуюсь, що ОП не працює -1і 0; інакше max()найкращий варіант буде
vol7ron

2
Правильно ... але питання в тому, що ... "Як писати тернари, не повторюючись "
MinusFour

4
У ньому також сказано Again, not seeking an answer to the exact question above, що залишає питання для тлумачення;)
vol7ron

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


0

Потрійник - це як інше, якщо інша частина вам не потрібна, то чому б не один, якщо замість цього ..

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.