Come scrivere un'espressione con operatore ternario (aka if) senza ripetersi


104

Ad esempio, qualcosa del genere:

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

C'è un modo migliore per scriverlo? Ancora una volta, non sto cercando una risposta alla domanda esatta sopra, solo un esempio di quando potresti avere operandi ripetuti nelle espressioni di operatori ternari ...


2
Quindi an ife non anif/else
zer00ne

53
just an example of when you might have repeated things in the ternarynon ripetere espressioni calcolate. Ecco a cosa servono le variabili.
vol7ron

4
Come determiniamo che 3non è a indice 0di someArray?
guest271314

3
Qual è il tuo obiettivo qui? Stai cercando di ridurre la lunghezza della linea o in particolare di evitare la ripetizione di una variabile in un ternario? Il primo è possibile, il secondo no (almeno, non usando i ternari).
asgallant

5
Perché non usare Math.max(someArray.indexOf(3), 0)invece?
301_Moved_Permanently

Risposte:


176

Personalmente trovo che il modo migliore per farlo sia ancora la buona vecchia ifaffermazione:

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

32
Per essere chiari, questa risposta sostiene l'uso di una variabile per memorizzare un risultato intermedio, non l'uso di ifun'istruzione al posto di un ternario. Le affermazioni ternarie sono ancora spesso utili per eseguire una scelta come parte di un'espressione.
Trittico

4
@ Trittico: guardalo di nuovo. Non utilizza affatto una variabile intermedia. Invece assegna il risultato direttamente alla variabile finale e quindi lo sovrascrive se la condizione è soddisfatta.
Slebetman

14
Non corretto. Il valore memorizzato valuedopo la prima riga è intermedio. Non contiene il valore corretto che viene poi fissato sulla riga successiva ed è quindi un intermedio. È solo dopo che l' ifaffermazione si conclude che valuecontiene il valore corretto. Il ternario nell'OP è una soluzione migliore di questo perché non entra mai in uno stato intermedio.
Jack Aidley

44
@JackAidley: "Il ternario nell'OP è una soluzione migliore di questo perché non entra mai in uno stato intermedio." - Dovrò non essere d'accordo. Questo è molto più leggibile del codice di OP e del tutto idiomatico. Inoltre rende un bug nella logica dell'OP un po 'più ovvio per me (vale a dire, cosa succede se indexOf () restituisce zero? Come si distingue uno zero "reale" da uno zero "non trovato"?).
Kevin

2
questo è vicino a quello che farei io, tranne che valuequi è tecnicamente mutato, cosa che cerco di evitare.
Dave Cousineau

102

Il codice dovrebbe essere leggibile, quindi essere succinti non dovrebbe significare essere conciso a qualsiasi costo - per questo dovresti ripubblicare su https://codegolf.stackexchange.com/ - quindi invece consiglierei di utilizzare una seconda variabile locale denominata indexper massimizzare la comprensibilità di lettura ( anche con un costo di runtime minimo, noto):

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

Ma se vuoi davvero ridurre questa espressione, perché sei un sadico crudele per i tuoi colleghi o collaboratori del progetto, allora ecco 4 approcci che potresti usare:

1: variabile temporanea in varun'istruzione

È possibile utilizzare la varcapacità dell'istruzione di definire (e assegnare) una seconda variabile temporanea indexse separata da virgole:

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

2: funzione anonima autoeseguibile

Un'altra opzione è una funzione anonima autoeseguibile:

// 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: operatore virgola

C'è anche il famigerato "operatore virgola" supportato da JavaScript, che è presente anche in C e C ++.

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

È possibile utilizzare l'operatore virgola quando si desidera includere più espressioni in una posizione che richiede una singola espressione.

Puoi usarlo per introdurre effetti collaterali, in questo caso riassegnando a value:

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

Funziona perché var valueviene interpretato prima (poiché è un'istruzione), quindi l' valueassegnazione più a sinistra, più interna , e poi la mano destra dell'operatore virgola, e poi l'operatore ternario - tutto JavaScript legale.

4: riassegna in una sottoespressione

Il commentatore @IllusiveBrian ha sottolineato che l'uso dell'operatore virgola (nell'esempio precedente) non è necessario se l'assegnazione a valueè usata come sottoespressione tra parentesi:

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

Nota che l'uso di negativi nelle espressioni logiche può essere più difficile da seguire per gli esseri umani, quindi tutti gli esempi sopra possono essere semplificati per la lettura cambiando idx !== -1 ? x : yin idx == -1 ? y : x:

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

97
Tutti questi sono il tipo di codice "intelligente" che mi farebbe fissarlo per alcuni secondi prima di pensare "eh, immagino che funzioni". Non aiutano con la chiarezza e poiché il codice viene letto più di quanto non sia scritto, è più importante.
Gavin S. Yancey

18
@ g.rocket l'approccio 1 è leggibile e chiaro al 100% e l'unico modo per evitare la ripetizione (che potrebbe essere davvero dannoso se chiami una funzione complessa e problematica invece di una semplice indefOf)
edc65

5
D'accordo, il numero 1 è molto leggibile e gestibile, gli altri due non così tanto.
perdonato il

6
Per # 3, credo che tu possa semplificare var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);invece di usare una virgola.
IllusiveBrian

2
Nel secondo esempio Funzione anonima autoeseguente è possibile omettere le parentesi dalla funzione freccia. var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );perché è solo un parametro.
Alex Char

54

Per i numeri

Puoi usare la Math.max()funzione.

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

Manterrà i confini del risultato 0fino al primo risultato maggiore di 0se fosse così. E se il risultato di indexOfè -1restituirà 0 poiché è maggiore di -1.

Per valori booleani e booleani-y

Per JS non esiste una regola generale AFAIK specialmente perché vengono valutati i valori falsi .

Ma se qualcosa può aiutarti la maggior parte delle volte è l'operatore or ( ||):

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

Devi stare molto attento con questo, perché nel tuo primo esempio, indexOfpuò tornare 0e se lo valuti 0 || -1tornerà -1perché 0è un valore falso .


2
Grazie. Immagino che il mio esempio sia pessimo, sto solo dando un esempio generale ahah non cercando una soluzione alla domanda esatta. Ho affrontato alcuni scnarios come l'esempio in cui vorrei usare un ternario, ma
finisco

1
Al primo esempio, come determinarlo 3o "y"non è all'indice 0di someArray?
guest271314

Sul Math.max esempio? indexOfrestituisce l'indice dell'elemento e se l'elemento non viene trovato restituisce -1 quindi hai una possibilità di ottenere un numero da -1 alla lunghezza della stringa, quindi Math.maximposta i limiti da 0 alla lunghezza per rimuovere la possibilità per restituire un -1,
Crisoforo Gaspar

@mitogh La logica al codice in OP crea un problema, sebbene sia un esempio generico; dove 0 potrebbe indicare sia l'indice 0dell'elemento corrispondente all'interno della matrice, sia 0impostato su Math.max(); o all'operatore condizionale all'OP. Considera var value = Math.max( ["y"].indexOf("y"), 0 ). Come si determina quale 0viene restituito? 0passato a Math.max()chiamare, o0 riflette l'indice di "y"all'interno dell'array?
guest271314

@ guest271314 Ottima idea, ma penso che dipenda dal contesto se si tratta o meno di un problema. Forse non importa da dove provenga lo 0 e l'unica cosa importante è che non sia -1. Un esempio: forse devi scegliere un elemento da un array. Vuoi un oggetto particolare (nell'OP, il numero 3), ma se non è nell'array, hai ancora bisogno di un elemento e stai bene con l'impostazione predefinita di qualunque sia il primo elemento, supponendo che tu sappia che l'array non è ' t vuoto.
Kev

27

Non proprio, usa solo un'altra variabile.

Il tuo esempio generalizza a qualcosa di simile.

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

Stai testando un valore calcolato, quindi assegnando quel valore a una variabile se passa un predicato. Il modo per evitare di ricalcolare il valore calcolato è ovvio: utilizzare una variabile per memorizzare il risultato.

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

Capisco cosa intendi: sembra che dovrebbe esserci un modo per farlo che sembri un po 'più pulito. Ma penso che sia il modo migliore (idiomatico) per farlo. Se per qualche motivo ripetessi spesso questo schema nel tuo codice, potresti scrivere una piccola funzione di aiuto:

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

17

EDIT: Eccola qui, la proposta di coalescenza Nullary ora in JavaScript!


Uso ||

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

è equivalente a

const result = a || 'fallback value';

Se il cast adi Booleanritorni false, resultverrà assegnato 'fallback value', altrimenti il ​​valore di a.


Siate consapevoli del caso limite a === 0, che lancia falsee resultprenderà (in modo errato) 'fallback value'. Usa trucchi come questo a tuo rischio e pericolo.


PS. Linguaggi come Swift hanno un operatore a coalescenza nulla ( ??), che ha uno scopo simile. Ad esempio, in Swift scriveresti result = a ?? "fallback value"che è abbastanza vicino a JavaScriptconst result = a || 'fallback value';


3
PHP (> 7.0) e C # supportano anche l'operatore di coalescenza null. Zucchero sintattico ma sicuramente delizioso.
Hissvard

5
Funziona solo quando la funzione restituisce un valore falsey quando fallisce, ma indexOf()non può essere utilizzata in questo modello.
Barmar

1
Esatto, ma non sta chiedendo l'esempio specifico> "Di nuovo, non sta cercando una risposta alla domanda esatta sopra, solo un esempio di quando potresti aver ripetuto le cose nel ternario" <, tieni presente che sta piuttosto chiedendo un modo generico per sostituire il ternario ripetuto (risultato = a? a: b). E ripetere il ternario equivale a || (risultato = a || b)
Lyubomir

2
È davvero così che ||funziona in JavaScript? Se ti capisco correttamente, allora funziona in modo diverso da molti altri linguaggi primari (penso principalmente al C e ai suoi discendenti [C ++, Java, ecc.]) Anche se questo è davvero il modo ||in cui funziona JavaScript, sconsiglierei di usare trucchi come questo che richiedono ai manutentori di conoscere stranezze speciali sul linguaggio. Anche se questo trucco è interessante, lo considero una cattiva pratica.
Loduwijk

1
Notare anche che la domanda sta confrontando il valore con -1. Ancora una volta, non posso parlare per JavaScript e le sue stranezze, ma in genere -1sarebbe un valore vero, non falso, e quindi la tua risposta non funzionerebbe nel caso della domanda e certamente non nel caso generale, piuttosto funzionerebbe solo in uno specifico ( ma abbastanza comune) sottocaso.
Loduwijk

8

Usa un refactoring di una variabile di estrazione :

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

È ancora meglio con constinvece di var. Puoi anche eseguire un'estrazione aggiuntiva:

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

In pratica, utilizzare nomi più significativi rispetto index, conditione value.

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

Che cos'è una "variabile di estrazione"? È un termine stabilito?
Peter Mortensen

6

Probabilmente stai cercando un operatore di coalescenza. Fortunatamente, possiamo sfruttare il Arrayprototipo per crearne uno:

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

Questo potrebbe essere ulteriormente generalizzato al tuo caso, aggiungendo un parametro alla funzione:

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

L'aggiunta al prototipo di array è rischioso, perché il nuovo elemento diventa un indice all'interno di ogni array. Ciò significa che scorrendo gli indici comprende anche il nuovo metodo: for (let x in ['b','c']) console.log(x);stampe 0, 1, "coalesce".
Charlie Harding

@CharlieHarding Vero, ma in genere non è consigliabile utilizzare l'operatore for-in durante il ciclo degli array. Vedere stackoverflow.com/a/4374244/1486100
Tyzoid

6

Personalmente preferisco due varianti:

  1. Pure if, come suggerito da @slebetman

  2. Funzione separata, che sostituisce il valore non valido con quello predefinito, come in questo esempio:

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, l'opzione 2 rispetta l'intento inline della domanda, può essere applicata in modo efficiente ad altri linguaggi oltre a javascript e promuove la modularità.
Devsman

5

Mi piace la risposta di @ slebetman. Il commento sotto esprime preoccupazione per il fatto che la variabile sia in uno "stato intermedio". se questa è una grande preoccupazione per te, suggerisco di incapsularla in una funzione:

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

Allora chiama

var value = get_value( someArray );

Potresti eseguire funzioni più generiche se hai usi per loro in altri luoghi, ma non esagerare se si tratta di un caso molto specifico.

Ma ad essere onesto, farei semplicemente come @slebetman a meno che non avessi bisogno di riutilizzarlo da diversi posti.


4

Ci sono due modi in cui posso vedere di guardare la tua domanda: o vuoi ridurre la lunghezza della linea o vuoi specificamente evitare di ripetere una variabile in un ternario. Il primo è banale (e molti altri utenti hanno pubblicato esempi):

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

può essere (e dovrebbe essere, date le chiamate di funzione) abbreviato in questo modo:

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

Se stai cercando una soluzione più generica che impedisca la ripetizione di una variabile in un ternario, in questo modo:

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

dove fooappare solo una volta. Scartare le soluzioni del modulo:

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

come tecnicamente corretto ma manca il punto, quindi sei sfortunato. Ci sono operatori, funzioni e metodi che possiedono la sintassi concisa che cerchi, ma tali costrutti, per definizione, non sono operatori ternari .

Esempi:

javascript, utilizzando ||per restituire l'RHS quando l'LHS è falsey:

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

1
La domanda è contrassegnata da javascript e non menziona C #. Mi chiedo solo perché finisci con esempi specifici di C #.
Loduwijk

Ho perso il tag javascript sulla domanda; rimosso il C #.
asgallant

3

Usa una funzione di supporto:

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

Ora il tuo codice è molto leggibile e non c'è ripetizione.

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

La gerarchia delle preoccupazioni di codifica è:

  1. Corretto (comprese le prestazioni reali o i problemi di SLA)
  2. Chiaro
  3. Conciso
  4. Veloce

Finora tutte le risposte sulla pagina sembrano corrette, ma penso che la mia versione abbia la massima chiarezza, che è più importante della concisione. Se non si conta la funzione di supporto, poiché può essere riutilizzata, è anche la più concisa. Il suggerimento in qualche modo simile di utilizzare una funzione di supporto utilizza sfortunatamente un lambda che, per me, oscura solo ciò che sta facendo. Una funzione più semplice con uno scopo che non richiede un lambda, solo valori, è per me molto meglio.

PS Se ti piace la sintassi di ES6:

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

2
"la mia versione ha la massima chiarezza" - Non sono d'accordo. Il nome della funzione è troppo lungo e la denominazione dei parametri di input e output non è affatto utile.
adelphus

Quindi puoi suggerire nomi migliori? Sarei felice di intrattenerli. Il tuo reclamo riguarda esclusivamente i cosmetici, quindi sistemiamo i cosmetici. Destra? Altrimenti stai solo perdendo la bici.
ErikE

In alcuni casi, una tale funzione di supporto può essere utile. In questo caso, dove si sostituisce solo un caso specifico di operatore ternario, la tua funzione risulterà meno chiara. Non ricorderò cosa fa la tua funzione, e dovrò cercarla di nuovo ogni volta che la incontrerò e non mi ricorderei mai di usarla.
Loduwijk

1
@Aaron Questa è una valutazione ragionevole per un caso d'uso particolare. Per quel che vale, il nome della mia funzione originale era translateValueIfEqualche pensavo fosse più descrittivo, ma l'ho cambiato dopo che qualcuno pensava che fosse troppo lungo. Proprio come la Nzfunzione in Access, se lo conosci, lo sai e se non lo fai, non lo sai. Negli IDE moderni puoi semplicemente premere un tasto per passare alla definizione. E il fallback sarebbe la variabile intermedia. Non vedo davvero un grande svantaggio qui.
ErikE

1
Questo dimostra che se poni la stessa domanda a 5 ingegneri diversi otterrai 10 risposte diverse.
Loduwijk

2

Penso che l' ||operatore possa essere personalizzato per indexOf:

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

Il valore restituito viene spostato verso l'alto di 1, facendo 0 da -1, che è falso e quindi viene sostituito dal secondo 1. Quindi viene spostato indietro.

Tuttavia, tieni presente che la leggibilità è superiore all'evitare la ripetizione.


2

Questa è una soluzione semplice con NOT bit per bit e un valore predefinito il cui valore -1successivamente viene azzerato.

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

Funziona fondamentalmente con un doppio NOT bit per bit, che restituisce il valore originale o un valore predefinito, che dopo aver applicato il NOT bit per bit restituisce zero.

Diamo uno sguardo alla tavola della verità:

 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

Potresti usare la riassegnazione:

  • inizializza la variabile su un valore
  • utilizzare la serializzazione &&dell'operatore per la riassegnazione, perché se la prima condizione è falsa, la seconda espressione non verrà valutata

Ex.

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


3
@MinusFour Fino a un certo punto. La variabile viene ripetuta, non l'espressione someArray.indexOfviene eseguita una sola volta
vol7ron

Ripeti value.
MinusFour

3
@MinusFour Corretto, ma è più utile per espressioni più grandi, la ripetizione di una variabile è insignificante rispetto al salvataggio delle operazioni. La mia ipotesi è che l'OP non funzioni con -1e 0; altrimenti max()sarebbe l'opzione migliore
vol7ron

2
Giusto ... ma la domanda è ... "Come scrivere ternari senza ripetersi "
MinusFour

4
Dice anche Again, not seeking an answer to the exact question above, il che lascia la domanda all'interpretazione;)
vol7ron

0

Dato il codice di esempio in Question non è chiaro come verrebbe determinato se 3è o non è impostato su index 0of someArray. -1restituito da .indexOf()sarebbe utile in questo caso, allo scopo di escludere una presunta non corrispondenza che potrebbe essere una corrispondenza.

Se 3non è incluso nell'array, -1verrà restituito. Possiamo aggiungere 1al risultato di .indexOf()valutare come falserisultato essere -1, dove seguito da || ORoperatore e 0. Quando valueviene fatto riferimento, sottrarre 1per ottenere l'indice dell'elemento dell'array o -1.

Il che riconduce al semplice utilizzo .indexOf()e al controllo di -1una determinata ifcondizione. Oppure, definendo valuein modo undefinedda evitare possibili confusioni sul risultato effettivo della condizione valutata relativa al riferimento 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);


0

Un ternario è come un se-altro, se non hai bisogno dell'altra parte, perché non solo un singolo se invece ..

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

0

In questo caso particolare, potresti utilizzare il cortocircuito con l' ||operatore logico . Poiché 0è considerato falso, puoi aggiungere 1al tuo indice, quindi, se index+1è 0allora otterrai il lato destro del logico o come risultato, altrimenti otterrai il tuo index+1. Poiché il risultato desiderato è compensato da 1, puoi quindi sottrarlo 1per ottenere il tuo indice:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.