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

104

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 cela? Encore une fois, je ne cherche pas une réponse à la question exacte ci-dessus, juste un exemple de cas où vous pourriez avoir répété des opérandes dans des expressions d'opérateurs ternaires ...

user1354934
la source
2
So an ifand not anif/else
zer00ne
53
just an example of when you might have repeated things in the ternaryne répétez pas les expressions calculées. C'est à cela que servent les variables.
vol7ron
4
Comment déterminer que ce 3n'est pas à l'index 0de someArray?
invité271314
3
Quel est votre objectif ici? Essayez-vous de réduire la longueur de la ligne, ou essayez-vous spécifiquement d'éviter la répétition d'une variable dans un ternaire? Le premier est possible, le second ne l'est pas (du moins, ne pas utiliser de ternaires).
asgallant
5
Pourquoi ne pas utiliser à la Math.max(someArray.indexOf(3), 0)place?
301_Moved_Permanently

Réponses:

176

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

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}
dormeur
la source
32
Pour être clair, cette réponse préconise l'utilisation d'une variable pour stocker un résultat intermédiaire, et non l'utilisation d'une ifinstruction à la place d'un ternaire. Les instructions ternaires sont encore fréquemment utiles pour effectuer un choix dans le cadre d'une expression.
Triptyque du
4
@Triptych: Regardez-le à nouveau. Il n'utilise pas du tout de variable intermédiaire. Au lieu de cela, il affecte le résultat directement à la variable finale, puis l'écrase si la condition est remplie.
slebetman
14
Incorrect. La valeur stockée valueaprès la première ligne est intermédiaire. Il ne contient pas la valeur correcte qui est ensuite fixée sur la ligne suivante et est donc un intermédiaire. Ce n'est qu'après la ifconclusion de l' instruction que valuecontient la valeur correcte. Le ternaire dans l'OP est une meilleure solution que cela car il n'entre jamais dans un état intermédiaire.
Jack Aidley
44
@JackAidley: "Le ternaire dans l'OP est une meilleure solution que cela car il n'entre jamais dans un état intermédiaire." - Je vais devoir être en désaccord. C'est beaucoup plus lisible que le code d'OP et entièrement idiomatique. Cela rend aussi un bogue dans la logique d'OP un peu plus évident pour moi (à savoir, que se passe-t-il si indexOf () retourne zéro? Comment distingue-t-on un zéro "réel" d'un zéro "non trouvé"?).
Kevin
2
c'est proche de ce que je ferais, sauf valuequ'ici est techniquement muté, ce que j'essaye d'éviter.
Dave Cousineau
102

Le code doit être lisible, donc être succinct ne doit pas signifier être laconique quel que soit le coût - pour cela, vous devez republier sur https://codegolf.stackexchange.com/ - donc à la place, je recommanderais d'utiliser une deuxième variable locale nommée indexpour maximiser la compréhension de la lecture ( avec un coût d'exécution minimal aussi, je note):

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 envers vos collègues ou collaborateurs de projet, alors voici 4 approches que vous pouvez utiliser:

1: variable temporaire dans une varinstruction

Vous pouvez utiliser la varcapacité de l' instruction pour définir (et affecter) une deuxième variable temporaire indexlorsqu'elle est 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 auto-exécutable:

// 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 virgule

Il existe également le tristement célèbre "opérateur virgule" pris en charge par JavaScript, qui est é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 virgule lorsque vous souhaitez inclure plusieurs expressions dans un emplacement qui ne nécessite qu'une seule expression.

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

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

Cela fonctionne car il var valueest interprété en premier (car il s'agit d'une instruction), puis l' valueaffectation la plus à gauche, la plus intérieure , puis la main droite de l'opérateur virgule, puis l'opérateur ternaire - tout du JavaScript légal.

4: réassigner dans une sous-expression

Le commentateur @IllusiveBrian a souligné que l'utilisation de l'opérateur virgule (dans l'exemple précédent) est inutile si l'assignation à valueest 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 - donc tous les exemples ci-dessus peuvent être simplifiés pour la lecture en passant idx !== -1 ? x : yà idx == -1 ? y : x:

var value = ( ( value = someArray.indexOf(3) ) == -1 ? 0 : value );
Dai
la source
97
Tous ces éléments sont le genre de codage «intelligent» qui me ferait le regarder pendant quelques secondes avant de penser «hein, je suppose que ça marche». Ils n'aident pas à la clarté, et puisque le code est lu plus qu'il n'est écrit, c'est plus important.
Gavin S.Yancey
18
L'approche @ g.rocket 1 est 100% lisible et claire, et la seule façon de procéder si vous voulez éviter la répétition (cela pourrait être vraiment dangereux si vous appelez une fonction complexe et problématique au lieu d'une simple indefOf)
edc65
5
D'accord, le n ° 1 est très lisible et maintenable, les deux autres pas tellement.
pardonner le
6
Pour le n ° 3, je pense que vous pouvez simplifier au var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);lieu d'utiliser une virgule.
IllusiveBrian
2
Dans le deuxième exemple Fonction anonyme auto-exécutable, vous pouvez omettre les parenthèses de la fonction flèche. var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );car ce n'est qu'un paramètre.
Alex Char
54

Pour les nombres

Vous pouvez utiliser la Math.max()fonction.

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

Cela gardera les limites du résultat du 0jusqu'au premier résultat plus grandes que 0si tel était le cas. Et si le résultat de indexOfest -1il renverra 0 comme supérieur à -1.

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

Pour JS, il n'y a pas de règle générale AFAIK spécialement parce que les valeurs fausses 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 être très prudent avec cela, car dans votre premier exemple, indexOfpeut retourner 0et si vous l'évaluez, 0 || -1il retournera -1parce que 0c'est une valeur erronée .

Crisoforo Gaspar
la source
2
Merci. Je suppose que mon exemple est mauvais. Je donne juste un exemple général haha ​​ne cherche pas une solution à la question exacte. J'ai fait face à des scénarios comme l'exemple où je voudrais utiliser un ternaire, mais
je finis
1
Dans le premier exemple, comment pouvons-nous déterminer que 3ou "y"n'est pas à l'index 0desomeArray ?
invité271314
Sur le Math.max exemple? indexOfrenvoie l'index de l'élément et si l'élément n'est pas trouvé renvoie -1 donc vous avez une chance d'obtenir un nombre de -1 à la longueur de la chaîne, alors avec Math.maxvous définissez simplement les limites de 0 à la longueur pour supprimer la chance pour retourner un -1,
Crisoforo Gaspar
@mitogh La logique au niveau du code à OP crée un problème, même si c'est un exemple générique; où 0 pourrait à la fois indiquer l'index 0de l'élément correspondant dans le tableau, ou 0fixé à Math.max(); ou à l'opérateur conditionnel à OP. Considérez var value = Math.max( ["y"].indexOf("y"), 0 ). Comment déterminez-vous lequel 0est retourné? 0transmis àMath.max() appel, ou 0reflétant l'index de l' "y"intérieur du tableau?
invité271314
@ guest271314 Bonne réflexion, mais je pense que cela dépend du contexte pour savoir si c'est un problème ou non. L'origine du 0 n'a peut-être pas d'importance, et la seule chose importante est que ce n'est pas -1. Un exemple: vous devez peut-être choisir un élément dans un tableau. Vous voulez un élément particulier (dans l'OP, le numéro 3), mais si ce n'est pas dans le tableau, vous avez toujours besoin d'un élément, et vous êtes d'accord avec le choix par défaut du premier élément, en supposant que vous savez que le tableau n'est pas t vide.
Kev
27

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 affectez cette valeur à une variable si elle passe un prédicat. La manière d'éviter de recalculer 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 semble un peu plus propre. Mais je pense que c'est la meilleure façon (idiomatique) de le faire. Si vous répétiez beaucoup ce modèle dans votre code pour une raison quelconque, vous pourriez écrire une petite fonction d'aide:

var setif = (value, predicate, default) => predicate(value) ? value : default;
var x = setif(someArray.indexOf(3), x => x !== -1, 0)
Triptyque
la source
17

EDIT: Le voici, la proposition de Nullary-coalescing maintenant en JavaScript!


Utilisation ||

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

est équivalent à

const result = a || 'fallback value';

Si la conversion aen Booleanretourne false, resultsera attribuée 'fallback value', sinon la valeur de a.


Soyez conscient du cas de bord a === 0, qui lance falseet resultprendra (incorrectement) 'fallback value'. Utilisez des trucs comme celui-ci à vos propres risques.


PS. Les langages tels que Swift ont l' opérateur nil-coalescing ( ??), qui a un objectif similaire. Par exemple, dans Swift, vous écririez result = a ?? "fallback value"ce qui est assez proche de JavaScriptconst result = a || 'fallback value';

Lyubomir
la source
3
PHP (> 7.0) et C # prennent également en charge l'opérateur de fusion nul. Sucre syntaxique mais sûrement charmant.
Hissvard
5
Cela ne fonctionne que lorsque la fonction renvoie une valeur falsey en cas d'échec, mais indexOf()ne peut pas être utilisée dans ce modèle.
Barmar
1
Correct, mais il ne demande pas l'exemple spécifique> "Encore une fois, je ne cherche pas une réponse à la question exacte ci-dessus, juste un exemple de cas où vous avez peut-être répété des choses dans le ternaire" <, remarquez qu'il demande plutôt un moyen générique de remplacer la répétition ternaire (résultat = a? a: b). Et répéter ternaire équivaut à || (résultat = a || b)
Lyubomir
2
Est-ce vraiment ainsi que ||fonctionne JavaScript? Si je vous comprends bien, cela fonctionne différemment de nombreux autres langages primaires (je pense principalement à C et à ses descendants [C ++, Java, etc.]) Même si c'est vraiment ainsi que ||fonctionne en JavaScript, je déconseille d'utiliser des trucs comme celui-ci qui obligent les responsables à connaître les particularités du langage. Bien que cette astuce soit cool, je la considérerais comme une mauvaise pratique.
Loduwijk
1
Notez également que la question compare la valeur à -1. Encore une fois, je ne peux pas parler de JavaScript et de ses bizarreries, mais ce -1serait généralement une valeur vraie, pas une fausse, et donc votre réponse ne fonctionnerait pas dans le cas de la question et certainement pas dans le cas général, mais plutôt dans un cas spécifique ( mais assez commun) sous-cas.
Loduwijk
8

Utilisez un refactoring de variable d'extrait :

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

C'est encore mieux avec constau lieu de var. Vous pouvez également faire une extraction supplémentaire:

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

Dans la pratique, utiliser des noms plus significatifs que index, conditionet value.

const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;
Dave Cousineau
la source
Qu'est-ce qu'une "variable d'extraction"? Est-ce un terme établi?
Peter Mortensen
6

Vous recherchez probablement un opérateur de fusion. Heureusement, nous pouvons tirer parti du Arrayprototype 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 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
Tyzoid
la source
L'ajout au prototype de tableaux est risqué, car le nouvel élément devient un index à l'intérieur de chaque tableau. Cela signifie que les indices itérer comprend également la nouvelle méthode: for (let x in ['b','c']) console.log(x);gravures 0, 1, "coalesce".
Charlie Harding
@CharlieHarding True, mais il n'est généralement pas recommandé d'utiliser l'opérateur for-in lors d'une boucle dans des tableaux. Voir stackoverflow.com/a/4374244/1486100
Tyzoid
6

Personnellement, je préfère deux variantes:

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

  2. Fonction séparée, qui remplace la valeur invalide par une 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

Iłya Bursov
la source
1
+1, l'option 2 respecte l'intention en ligne de la question, peut s'appliquer efficacement à d'autres langages que javascript et favorise la modularité.
Devsman
5

J'aime la réponse de @ slebetman. Le commentaire sous celui-ci exprime une inquiétude quant au fait que la variable se trouve dans un "état intermédiaire". si c'est un gros problème pour vous, je vous suggère de l'encapsuler dans une fonction:

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

Alors appelez simplement

var value = get_value( someArray );

Vous pouvez faire des fonctions plus génériques si vous en avez des utilisations ailleurs, mais ne sur-ingénierie pas si c'est un cas très spécifique.

Mais pour être honnête, je ferais simplement comme @slebetman à moins d'avoir besoin de réutiliser à plusieurs endroits.

Adam
la source
4

Je peux voir deux façons de regarder votre question: vous voulez soit réduire la longueur de la ligne, soit vous voulez spécifiquement éviter de répéter une variable dans un ternaire. Le premier est trivial (et de nombreux autres utilisateurs ont publié des exemples):

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

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

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

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

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

foon'apparaît qu'une seule fois. Rejeter les solutions du formulaire:

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

comme techniquement correct mais manquant le point, alors vous n'avez pas de chance. Il existe des opérateurs, des fonctions et des méthodes qui possèdent la syntaxe laconique que vous recherchez, mais ces 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
asgallant
la source
1
La question est balisée en javascript et ne mentionne pas C #. Je me demande simplement pourquoi vous terminez par des exemples spécifiques à C #
Loduwijk
J'ai manqué la balise javascript sur la question; supprimé le C #.
asgallant
3

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 préoccupations de codage est:

  1. Correct (y compris les véritables problèmes de performances ou de SLA)
  2. Clair
  3. Concis
  4. Vite

Toutes les réponses sur la page semblent jusqu'à présent être correctes, mais je pense que ma version est la plus 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'assistance utilise malheureusement un lambda qui, pour moi, obscurcit simplement ce qu'il fait. Une fonction plus simple avec un objectif qui ne prend pas de lambda, juste des valeurs, est pour moi bien meilleure.

PS 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
ErikE
la source
2
"ma version est la plus claire" - je ne suis pas d'accord. Le nom de la fonction est beaucoup trop long et la dénomination des paramètres d' entrée et de sortie n'est pas du tout utile.
adelphus
Alors, pouvez-vous suggérer de meilleurs noms? Je serais ravi de les divertir. Votre plainte concerne uniquement les cosmétiques, alors réparons les cosmétiques. Droite? Sinon, vous ne faites que perdre du vélo.
ErikE
Dans certains cas, une telle fonction d'assistance peut être utile. Dans ce cas, où il ne remplace qu'un cas spécifique d'opérateur ternaire, votre fonction sera moins claire. Je ne vais pas me souvenir de ce que fait votre fonction, et je devrai la chercher à nouveau chaque fois que je la croiserai, et je ne me souviendrai jamais de l'utiliser.
Loduwijk
1
@Aaron C'est une évaluation raisonnable pour un cas d'utilisation particulier. Pour ce que ça vaut, mon nom de fonction d'origine était translateValueIfEqualque je pensais plus descriptif, mais je l'ai changé après que quelqu'un ait pensé qu'il était trop long. Tout comme la Nzfonction dans Access, si vous la connaissez, vous la connaissez, et si vous ne le faites pas, vous ne le savez pas. Dans les IDE modernes, vous pouvez simplement appuyer sur une touche pour accéder à la définition. Et le repli serait la variable intermédiaire. Je ne vois pas vraiment de gros inconvénient ici.
ErikE
1
Cela montre simplement que si vous posez la même question à 5 ingénieurs différents, vous obtiendrez 10 réponses différentes.
Loduwijk
2

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

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

La valeur retournée est décalée de 1 vers le haut, faisant 0 de -1, ce qui est faux et est donc remplacée par la seconde 1. Puis elle est décalée vers l'arrière.

Cependant, gardez à l'esprit que la lisibilité est supérieure à éviter les répétitions.

IllidanS4 veut que Monica revienne
la source
2

Il s'agit d'une solution simple avec NOT au niveau du bit et une valeur par défaut -1dont les résultats ultérieurs à zéro.

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

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

Jetons un coup d'œil à la table de 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
Nina Scholz
la source
0

Vous pouvez utiliser la réaffectation:

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

Ex.

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

vol7ron
la source
3
@MinusFour Dans une certaine mesure. La variable est répétée, pas l'expression someArray.indexOfn'est exécutée qu'une seule fois
vol7ron
Vous répétez value.
MinusFour
3
@MinusFour Correct, mais c'est plus utile pour les expressions plus grandes, la répétition d'une variable est insignifiante par rapport à la sauvegarde des opérations. Je suppose que l'OP ne fonctionne pas avec -1et 0; sinon ce max()serait la meilleure option
vol7ron
2
Bon ... mais la question est ... "Comment écrire des ternaires sans se répéter "
MinusFour
4
Il dit aussi Again, not seeking an answer to the exact question above, ce qui laisse la question à l'interprétation;)
vol7ron
0

Compte tenu de l'exemple de code à Question, il n'est pas clair comment déterminer s'il 3est défini ou non à l'index 0de someArray. -1renvoyé de .indexOf()serait précieux dans ce cas, dans le but d'exclure une non-correspondance présumée qui pourrait être une correspondance.

Si 3n'est pas inclus dans le tableau, -1sera retourné. Nous pouvons ajouter 1au résultat de .indexOf()pour évaluer comme falserésultat étant -1, où suivi par l' || ORopérateur et 0. Quand valueest référencé, soustraire 1pour obtenir l'index de l'élément du tableau ou -1.

Ce qui revient à simplement utiliser .indexOf()et vérifier -1une ifcondition. Ou, définir valuecomme undefinedpour éviter toute confusion quant à la suite de l' état réel évalué relatif à la référence originale.

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

invité271314
la source
0

Un ternaire est comme un if-else, si vous n'avez pas besoin de l'autre partie, pourquoi pas juste un if à la place ...

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

Dans ce cas particulier, vous pouvez utiliser un court-circuit avec l' ||opérateur logique . Comme cela 0est considéré comme faux, vous pouvez ajouter 1à votre index, donc, si index+1c'est le cas, 0vous obtiendrez le côté droit de la logique - ou comme résultat, sinon, vous obtiendrez votre index+1. Comme votre résultat souhaité est compensé par 1, vous pouvez ensuite en soustraire 1pour obtenir votre index:

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

Nick Parsons
la source