web-dev-qa-db-fra.com

Comment écrire une expression d'opérateur ternaire (aka si) sans se répéter

Par exemple, quelque chose comme ceci:

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

Y a-t-il une meilleure façon d'écrire ça? Encore une fois, je ne cherche pas une réponse à la question exacte ci-dessus, mais juste un exemple de cas où vous pourriez avoir répété des opérandes dans des expressions d'opérateur ternaire ...

99
user1354934

Personnellement, je trouve que la meilleure façon de faire est toujours la bonne vieille déclaration if

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

Le code doit être lisible, donc être succinct ne signifie pas être concis, quel que soit le coût - pour que vous deviez republier vers https://codegolf.stackexchange.com/ - je vous recommanderais donc plutôt d'utiliser une deuxième variable locale. nommé index pour optimiser la compréhension de la lecture (avec un coût d’exécution minimal, je remarque):

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

Mais si vous voulez vraiment réduire cette expression, parce que vous êtes un sadique cruel pour vos collègues ou collaborateurs du projet, voici 4 approches que vous pouvez utiliser:

1: Variable temporaire dans une instruction var

Vous pouvez utiliser la capacité de l'instruction var pour définir (et affecter) une deuxième variable temporaire index, une fois séparée par des virgules:

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

2: fonction anonyme auto-exécutable

Une autre option est une fonction anonyme à exécution automatique:

// 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: opérateur de virgule

Il y a aussi le fameux "opérateur de virgule" supporté par JavaScript, également présent en C et C++.

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

Vous pouvez utiliser l'opérateur de virgule lorsque vous souhaitez inclure plusieurs expressions dans un emplacement nécessitant une seule expression. 

Vous pouvez l’utiliser pour introduire des effets secondaires, dans ce cas en réaffectant à value:

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

Cela fonctionne parce que var value est interprété en premier (comme c'est une déclaration), et then l'assignation value la plus à gauche et la plus à l'intérieur, puis la droite de l'opérateur de virgule, puis l'opérateur ternaire - tout ce qui est légal. JavaScript.

4: réaffecter dans une sous-expression

Commentator @IllusiveBrian a souligné que l'utilisation de l'opérateur de virgule (dans l'exemple précédent) n'est pas nécessaire si l'affectation à value est utilisée comme sous-expression entre parenthèses:

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

Notez que l'utilisation de négatifs dans les expressions logiques peut être plus difficile à suivre pour les humains. Par conséquent, la lecture de tous les exemples ci-dessus peut être simplifiée en remplaçant idx !== -1 ? x : y par idx == -1 ? y : x:

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

Pour les nombres

Vous pouvez utiliser la fonction Math.max().

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

Il gardera les limites du résultat de 0 jusqu'au premier résultat supérieur à 0 si c'est le cas. Et si le résultat de indexOf est -1, il retournera 0, car il est supérieur à -1.

Pour les valeurs booléennes et booléennes-y

Pour JS, il n’ya pas de règle générale AFAIK spécialement parce que les valeurs de { falsy } _ sont évaluées.

Mais si quelque chose peut vous aider la plupart du temps, c'est l'opérateur or (||):

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

Vous devez faire très attention à cela, car dans votre premier exemple, indexOf peut renvoyer 0 et si vous évaluez 0 || -1, il renverra -1 car 0 est une valeur falsy .

53
Crisoforo Gaspar

Pas vraiment, utilisez simplement une autre variable. 

Votre exemple se généralise à quelque chose comme ça.

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

Vous testez une valeur calculée, puis vous l'assignez à une variable si elle transmet un prédicat. La façon d'éviter de re-calculer la valeur calculée est évidente: utilisez une variable pour stocker le résultat.

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

Je comprends ce que vous voulez dire - il semble qu'il devrait y avoir un moyen de faire cela qui a l'air un peu plus propre. Mais je pense que c'est la meilleure façon (idiomatiquement) de faire cela. Si, pour une raison quelconque, vous répétiez souvent ce motif dans votre code, vous pourriez écrire une petite fonction d'assistance:

var setif = (value, predicate, default) => predicate(value) ? value : default;
var x = setif(someArray.indexOf(3), x => x !== -1, 0)
26
Triptych

EDIT: La proposition de Nullary-coalescing maintenant en JavaScript!


Utilisez ||

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

est équivalent à

const result = a || 'fallback value';

Si la conversion de a en Boolean renvoie false, result se verra attribuer 'fallback value', sinon la valeur de a


Soyez conscient du cas Edge a === 0, qui transforme en false et result prendra (à tort) prendre 'fallback value'. Utilisez des astuces comme celle-ci à vos risques et périls.


PS. Les langues telles que Swift ont nil-coalescing operator (??), qui remplit des fonctions similaires. Par exemple, dans Swift, vous écririez result = a ?? "fallback value", ce qui est assez proche de const result = a || 'fallback value'; de JavaScript.

17
Lyubomir

Utilisez un refactoring de variable extract :

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

C'est encore mieux avec const au lieu de var. Vous pouvez aussi faire une extraction supplémentaire:

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

En pratique, utilisez des noms plus significatifs que index, condition et value.

const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;
8
Dave Cousineau

Personnellement, je préfère deux variantes:

  1. Pure si, comme @slebetman a suggéré

  2. Fonction distincte, qui remplace la valeur non valide par la valeur par défaut, comme dans cet exemple:

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

6
Iłya Bursov

Vous recherchez probablement un opérateur de coalescence. Heureusement, nous pouvons utiliser le prototype Array pour en créer un:

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

Cela pourrait être plus généralisé à votre cas, en ajoutant un paramètre à la fonction:

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
6
Tyzoid

J'aime la réponse de @ slebetman. Le commentaire en dessous exprime l'inquiétude que suscite la variable dans un "état intermédiaire". si cela vous préoccupe beaucoup, je suggère de l’encapsuler dans une fonction:

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

Ensuite, il suffit d'appeler

var value = get_value( someArray );

Vous pourriez faire des fonctions plus génériques si vous en avez déjà utilisé d'autres, mais ne faites pas trop d'ingénierie s'il s'agit d'un cas très spécifique.

Mais pour être honnête, je ne ferais que @slebetman à moins que je n’aie besoin de le réutiliser à plusieurs endroits.

5
Adam

Je peux envisager votre question de deux manières différentes: soit vous voulez réduire la longueur de ligne, soit vous voulez éviter de répéter une variable dans un ternaire. Le premier est trivial (et de nombreux autres utilisateurs ont posté des exemples):

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

peut être (et devrait être, étant donné les appels de fonction) raccourci comme ceci:

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

Si vous recherchez une solution plus générique qui empêche la répétition d'une variable dans un ternaire, procédez comme suit:

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

foo n'apparaît qu'une fois. Éliminer les solutions de la forme:

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

techniquement correct mais manquant le but, alors vous n'avez pas de chance. Il existe des opérateurs, des fonctions et des méthodes qui possèdent la syntaxe abrégée que vous recherchez, mais de telles constructions, par définition, ne sont pas des opérateurs ternaires .

Exemples:

javascript, en utilisant || pour renvoyer le RHS lorsque le LHS est falsey:

var value = foo || bar; // equivalent to !foo ? bar : foo
4
asgallant

Utilisez une fonction d'assistance:

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

Maintenant, votre code est très lisible et il n'y a pas de répétition.

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

La hiérarchie des problèmes de codage est la suivante:

  1. Correct (y compris les véritables performances ou les préoccupations relatives à SLA)
  2. Clair
  3. Concis
  4. Vite

Jusqu'ici, toutes les réponses sur la page semblent être correctes, mais je pense que ma version est très claire, ce qui est plus important que la concision. Si vous ne comptez pas la fonction d'assistance (car elle peut être réutilisée), elle est également la plus concise. La suggestion quelque peu similaire d’utiliser une fonction d’aide utilise malheureusement un lambda qui, pour moi, masque tout simplement son travail. Une fonction plus simple avec un objectif qui ne prend pas un lambda, mais des valeurs, est pour moi bien meilleure.

P.S. Si vous aimez la syntaxe ES6:

const translateValue = (value, match, translated) => value === match ? translated : value;
let value = translateValue(someArray.indexOf(3), -1, 0); // or const
3
ErikE

Je pense que l'opérateur || peut être adapté à indexOf:

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

La valeur renvoyée est augmentée de 1, faisant de 0 de -1, qui est falsey et est donc remplacée par la seconde 1. Ensuite, elle est décalée.

Cependant, gardez à l'esprit que la lisibilité est supérieure à la prévention des répétitions.

2
IllidanS4

Il s’agit d’une solution simple avec bitwise NOT et une valeur par défaut de -1 qui résulte ultérieurement à zéro.

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

Cela fonctionne essentiellement avec un NOT bitwise au double, qui renvoie la valeur d'origine ou une valeur par défaut, qui après l'application du bit NOT renvoie zéro.

Regardons la table de la vérité:

 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
2
Nina Scholz

Vous pouvez utiliser la réaffectation:

  • initialiser la variable à une valeur
  • utilisez la sérialisation de l'opérateur && pour la réaffectation, car si la première condition est fausse, la seconde expression ne sera pas évaluée

Ex.

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

var someArray = [4,3,2,1];

var value = someArray.indexOf(1);
value == -1 && (value=0);
console.log('Found:',value);

var value = someArray.indexOf(5);
value == -1 && (value=0);
console.log('Not Found:',value);

0
vol7ron

Étant donné l'exemple de code dans Question, la manière dont il serait déterminé que 3 est défini ou non à l'index 0 de someArray n'est pas claire. -1 renvoyé par .indexOf() serait utile dans ce cas, afin d'exclure une non-correspondance présumée qui pourrait être une correspondance.

Si 3 n'est pas inclus dans le tableau, -1 sera renvoyé. Nous pouvons ajouter 1 au résultat de .indexOf() pour l'évaluer comme étant false, le résultat étant -1, suivi de ||OR et de 0. Lorsque value est référencé, soustrayez 1 pour obtenir l'index de l'élément du tableau ou -1

Ce qui nous ramène à simplement utiliser .indexOf() et à rechercher -1 dans une condition if. Ou bien, définir value comme undefined pour éviter toute confusion quant au résultat réel de la condition évaluée relative à la référence d'origine.

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
guest271314

Un ternaire est comme un if-else, si vous n'avez pas besoin de l'autre partie, pourquoi ne pas en choisir un seul si.

if ((value = someArray.indexOf(3)) < 0) value = 0;
0
Khaled.K