Cómo escribir una expresión de operador ternario (también conocido como if) sin repetirse

104

Por ejemplo, algo como esto:

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

¿Existe una mejor manera de escribir eso? Nuevamente, no estoy buscando una respuesta a la pregunta exacta anterior, solo un ejemplo de cuándo podría tener operandos repetidos en expresiones de operador ternario ...

usuario1354934
fuente
2
Entonces an ify no anif/else
zer00ne
53
just an example of when you might have repeated things in the ternaryno repita expresiones calculadas. Para eso son las variables.
vol7ron
4
¿Cómo determinamos que 3no está en el índice 0de someArray?
guest271314
3
Cual es tu meta aqui? ¿Está tratando de reducir la longitud de la línea o, específicamente, está tratando de evitar la repetición de una variable en un ternario? Lo primero es posible, lo segundo no (al menos, no usa ternaries).
asgallant
5
¿Por qué no usar Math.max(someArray.indexOf(3), 0)en su lugar?
301_Moved_Permanently

Respuestas:

176

Personalmente, encuentro que la mejor manera de hacer esto sigue siendo la vieja ifdeclaración:

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}
slebetman
fuente
32
Para ser claros, esta respuesta aboga por el uso de una variable para almacenar un resultado intermedio, no el uso de una ifdeclaración en lugar de un ternario. Las declaraciones ternarias siguen siendo útiles con frecuencia para realizar una elección como parte de una expresión.
Tríptico
4
@Triptych: Míralo de nuevo. No utiliza una variable intermedia en absoluto. En su lugar, asigna el resultado directamente a la variable final y luego lo sobrescribe si se cumple la condición.
slebetman
14
Incorrecto. El valor almacenado valuedespués de la primera línea es intermedio. No contiene el valor correcto que luego se fija en la siguiente línea y, por lo tanto, es un intermedio. Es solo después de que la ifdeclaración concluye que valuecontiene el valor correcto. El ternario en el OP es una mejor solución que este porque nunca entra en un estado intermedio.
Jack Aidley
44
@JackAidley: "El ternario en el OP es una mejor solución que este porque nunca entra en un estado intermedio". - Voy a tener que estar en desacuerdo. Esto es mucho más legible que el código de OP y completamente idiomático. También hace que un error en la lógica de OP sea un poco más obvio para mí (es decir, ¿qué sucede si indexOf () devuelve cero? ¿Cómo se distingue un cero "real" de un cero "no encontrado"?).
Kevin
2
esto está cerca de lo que haría, excepto que valueaquí está técnicamente mutado, lo cual trato de evitar.
Dave Cousineau
102

El código debe ser legible, por lo que ser conciso no debe significar ser conciso sea cual sea el costo, para eso debe volver a publicar en https://codegolf.stackexchange.com/ , por lo que recomendaría usar una segunda variable local nombrada indexpara maximizar la comprensión de lectura ( con un costo mínimo de tiempo de ejecución también, observo):

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

Pero si realmente desea reducir esta expresión, porque es un sádico cruel con sus compañeros de trabajo o colaboradores del proyecto, aquí hay 4 enfoques que puede usar:

1: variable temporal en una vardeclaración

Puede usar la varcapacidad de la declaración para definir (y asignar) una segunda variable temporal indexcuando se separa con comas:

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

2: función anónima autoejecutable

Otra opción es una función anónima autoejecutable:

// 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: operador de coma

También está el infame "operador de coma" que admite JavaScript, que también está presente en C y C ++.

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

Puede utilizar el operador de coma cuando desee incluir varias expresiones en una ubicación que requiera una sola expresión.

Puede usarlo para introducir efectos secundarios, en este caso reasignándolo a value:

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

Esto funciona porque var valuese interpreta primero (como una declaración), y luego el más a la izquierda, el más internovalue asignación , y luego la parte derecha del operador de coma, y ​​luego el operador ternario, todo JavaScript legal.

4: reasignar en una subexpresión

El comentarista @IllusiveBrian señaló que el uso del operador de coma (en el ejemplo anterior) no es necesario si la asignación a valuese usa como una subexpresión entre paréntesis:

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

Tenga en cuenta que el uso de negativos en expresiones lógicas puede ser más difícil de seguir para los humanos, por lo que todos los ejemplos anteriores se pueden simplificar para su lectura cambiando idx !== -1 ? x : ya idx == -1 ? y : x:

var value = ( ( value = someArray.indexOf(3) ) == -1 ? 0 : value );
Dai
fuente
97
Todos estos son el tipo de codificación "inteligente" que me haría mirarlo durante unos segundos antes de pensar "eh, supongo que funciona". No ayudan con la claridad, y dado que el código se lee más de lo que se escribe, es más importante.
Gavin S. Yancey
18
El enfoque 1 de @ g.rocket es 100% legible y claro, y el único camino a seguir si desea evitar la repetición (eso podría ser realmente dañino si está llamando a una función compleja y problemática en lugar de una simple indefOf)
edc65
5
De acuerdo, el # 1 es muy legible y fácil de mantener, los otros dos no tanto.
perdona el
6
Para el n. ° 3, creo que puede simplificar en var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);lugar de usar una coma.
IllusiveBrian
2
En el segundo ejemplo , función anónima autoejecutable , puede omitir los paréntesis de la función de flecha. var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );porque es solo un parámetro.
Alex Char
54

Para números

Puede utilizar la Math.max()función.

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

Mantendrá los límites del resultado 0hasta el primer resultado mayor que 0si ese fuera el caso. Y si el resultado de indexOfes -1devolverá 0 ya que es mayor que-1 .

Para valores booleanos y booleanos-y

Para JS no hay una regla general AFAIK especialmente porque cuán falsa valores .

Pero si algo puede ayudarte la mayor parte del tiempo es el operador or ( ||):

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

Tienes que tener mucho cuidado con esto, porque en tu primer ejemplo, indexOfpuede regresar 0y si lo evalúas0 || -1 regresará -1porque 0es un valor falso .

Crisoforo Gaspar
fuente
2
Gracias. Supongo que mi ejemplo es malo, solo estoy dando un ejemplo general, jaja, no busco una solución a la pregunta exacta. Me he enfrentado a algunos escenarios como el ejemplo en el que me gustaría usar un ternario, pero termino repitiendo :(
user1354934
1
En el primer ejemplo, ¿cómo determinamos que 3 o "y"no está en el índice 0de someArray?
guest271314
¿En el Math.maxejemplo? indexOfdevuelve el índice del elemento y, si el elemento no se encuentra, devuelve -1, por lo que tiene una posibilidad de obtener un número de -1 a la longitud de la cadena, luego Math.max, simplemente establezca los límites de 0 a la longitud para eliminar la posibilidad para devolver un -1,
Crisoforo Gaspar
@mitogh La lógica en el código en OP crea un problema, aunque sea un ejemplo genérico; donde 0 podría indicar el índice 0del elemento coincidente dentro de la matriz, o 0establecido en Math.max(); o en el operador condicional en OP. Considere var value = Math.max( ["y"].indexOf("y"), 0 ). ¿Cómo se determina cuál 0se devuelve? 0pasado para Math.max()llamar, o 0índice reflectante de "y"dentro de la matriz?
guest271314
@ guest271314 Buen pensamiento, pero creo que depende del contexto si es un problema o no. Quizás no importa de dónde vino el 0, y lo único importante es que no es -1. Un ejemplo: tal vez necesite elegir un elemento de una matriz. Quiere un elemento en particular (en el OP, el número 3), pero si no está en la matriz, aún necesita un elemento, y está bien con el valor predeterminado del primer elemento, asumiendo que sabe que la matriz no es ' t vacío.
Kev
27

En realidad no, solo usa otra variable.

Tu ejemplo se generaliza a algo como esto.

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

Está probando un valor calculado y luego asigna ese valor a una variable si pasa algún predicado. La forma de evitar volver a calcular el valor calculado es obvia: utilice una variable para almacenar el resultado.

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

Entiendo lo que quieres decir: parece que debería haber alguna forma de hacer esto que se vea un poco más limpia. Pero creo que esa es la mejor manera (idiomáticamente) de hacer esto. Si estuviera repitiendo mucho este patrón en su código por alguna razón, podría escribir una pequeña función auxiliar:

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

EDITAR: ¡Aquí está, la propuesta de fusión de Nullary ahora en JavaScript!


Utilizar ||

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

es equivalente a

const result = a || 'fallback value';

Si se lanza aa Booleandevoluciones false, resultse le asignará 'fallback value', de lo contrario el valor de a.


Tenga en cuenta el caso de borde a === 0, que se lanza falsey resulttomará (incorrectamente) 'fallback value'. Utilice trucos como este bajo su propio riesgo.


PD. Los lenguajes como Swift tienen un operador de fusión nula ( ??), que tiene un propósito similar. Por ejemplo, en Swift escribiría lo result = a ?? "fallback value"que está bastante cerca de JavaScriptconst result = a || 'fallback value';

Lyubomir
fuente
3
PHP (> 7.0) y C # también admiten el operador de fusión nula. Azúcar sintáctico pero seguramente encantador.
Hissvard
5
Esto solo funciona cuando la función devuelve un valor falso cuando falla, pero indexOf()no se puede usar en este patrón.
Barmar
1
Correcto, pero no está pidiendo el ejemplo específico> "De nuevo, no busco una respuesta a la pregunta exacta anterior, solo un ejemplo de cuándo puede haber repetido cosas en el ternario" <, tenga en cuenta que prefiere una forma genérica de reemplazar ternario repetitivo (resultado = a? a: b). Y ternario repetido equivale a || (resultado = a || b)
Lyubomir
2
¿Es así realmente como ||funciona JavaScript? Si lo entiendo correctamente, entonces funciona de manera diferente a muchos otros lenguajes primarios (estoy pensando principalmente en C y sus descendientes [C ++, Java, etc.]). Incluso si eso es realmente como ||funciona en JavaScript, desaconsejaría usar trucos como este que requieren que los mantenedores conozcan peculiaridades especiales del idioma. Si bien ese truco es genial, lo consideraría una mala práctica.
Loduwijk
1
También observe que la pregunta es comparar el valor con -1. Nuevamente, no puedo hablar por JavaScript y sus peculiaridades, pero en general -1sería un valor verdadero, no falso, y por lo tanto su respuesta no funcionaría en el caso de la pregunta y ciertamente no en el caso general, sino que funcionaría solo en un ( pero bastante común) sub-caso.
Loduwijk
8

Utilice una refactorización de variables de extracción :

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

Es incluso mejor con en constlugar de var. También puede hacer una extracción adicional:

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

En la práctica, utilizar nombres más significativos que index, condition, y value.

const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;
Dave Cousineau
fuente
¿Qué es una "variable de extracción"? ¿Es ese un término establecido?
Peter Mortensen
6

Probablemente esté buscando un operador coalescente. Afortunadamente, podemos aprovechar el Arrayprototipo para crear 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

Esto podría generalizarse aún más a su caso, agregando un parámetro a la función:

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
fuente
Agregar al prototipo de matrices es arriesgado, porque el nuevo elemento se convierte en un índice dentro de cada matriz. Esto significa que la iteración a través de los índices también incluye el nuevo método: for (let x in ['b','c']) console.log(x);impresiones 0, 1, "coalesce".
Charlie Harding
@CharlieHarding Verdadero, pero generalmente no se recomienda usar el operador for-in al recorrer las matrices. Ver stackoverflow.com/a/4374244/1486100
Tyzoid
6

Yo personalmente prefiero dos variantes:

  1. Puro si, como sugirió @slebetman

  2. Función separada, que reemplaza el valor no válido con uno predeterminado, como en este ejemplo:

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
fuente
1
+1, opción 2 respeta la intención en línea de la pregunta, se puede aplicar de manera eficiente a otros lenguajes además de javascript y promueve la modularidad.
Devsman
5

Me gusta la respuesta de @ slebetman. El comentario debajo expresa preocupación acerca de que la variable esté en un "estado intermedio". si esto es una gran preocupación para usted, le sugiero encapsularlo en una función:

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

Entonces solo llama

var value = get_value( someArray );

Podrías hacer funciones más genéricas si tienes usos para ellas en otros lugares, pero no sobredimensiones si se trata de un caso muy específico.

Pero para ser honesto, solo haría como @slebetman a menos que necesite reutilizarlo desde varios lugares.

Adán
fuente
4

Hay dos formas en las que puedo ver su pregunta: desea reducir la longitud de la línea o desea evitar específicamente la repetición de una variable en un ternario. El primero es trivial (y muchos otros usuarios han publicado ejemplos):

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

puede ser (y debería ser, dadas las llamadas de función) acortado así:

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

Si está buscando una solución más genérica que evite la repetición de una variable en un ternario, así:

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

donde foosolo aparece una vez. Descartando soluciones de la forma:

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

como técnicamente correcto pero sin entender, entonces no tienes suerte. Hay operadores, funciones y métodos que poseen la sintaxis concisa que busca, pero tales construcciones, por definición, no son operadores ternarios .

Ejemplos:

javascript, usando ||para devolver el RHS cuando el LHS es falsey:

var value = foo || bar; // equivalent to !foo ? bar : foo
valiente
fuente
1
La pregunta está etiquetada como javascript y no menciona C #. Me pregunto por qué termina con ejemplos específicos de C #.
Loduwijk
Perdí la etiqueta javascript en la pregunta; eliminó el C #.
asgallant
3

Utilice una función auxiliar:

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

Ahora su código es muy legible y no hay repetición.

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

La jerarquía de preocupaciones de codificación es:

  1. Correcto (incluido el rendimiento real o preocupaciones de SLA)
  2. Claro
  3. Conciso
  4. Rápido

Todas las respuestas en la página hasta ahora parecen ser correctas, pero creo que mi versión tiene la mayor claridad, que es más importante que la concisión. Si no cuenta la función auxiliar, ya que puede reutilizarse, también es la más concisa. La sugerencia algo similar de usar una función de ayuda desafortunadamente usa una lambda que, para mí, simplemente oscurece lo que está haciendo. Una función más simple con un propósito que no toma una lambda, solo valores, es para mí mucho mejor.

PD: si te gusta la sintaxis de ES6:

const translateValue = (value, match, translated) => value === match ? translated : value;
let value = translateValue(someArray.indexOf(3), -1, 0); // or const
ErikE
fuente
2
"mi versión tiene la mayor claridad" - no estoy de acuerdo. El nombre de la función es demasiado largo y nombrar los parámetros de entrada y salida no es útil en absoluto.
adelphus
Entonces, ¿puedes sugerir mejores nombres? Estaría feliz de entretenerlos. Su queja es únicamente sobre cosméticos, así que arreglemos los cosméticos. ¿Correcto? De lo contrario, solo estás perdiendo la bicicleta.
ErikE
En algunos casos, esta función auxiliar puede resultar útil. En este caso, donde está reemplazando solo un caso específico de operador ternario, su función será menos clara. No voy a recordar qué hace su función, y tendré que buscarla de nuevo cada vez que la encuentre, y nunca recordaré usarla.
Loduwijk
1
@Aaron Esa es una evaluación razonable para un caso de uso particular. Por lo que vale, el nombre de mi función original era el translateValueIfEqualque pensé que era más descriptivo, pero lo cambié después de que alguien pensó que era demasiado largo. Al igual que la Nzfunción en Access, si la conoce, la conoce, y si no la conoce, no la conoce. En los IDE modernos, puede simplemente presionar una tecla para saltar a la definición. Y la alternativa sería la variable intermedia. Realmente no veo un gran inconveniente aquí.
ErikE
1
Esto solo demuestra que si hace la misma pregunta a 5 ingenieros diferentes, obtendrá 10 respuestas diferentes.
Loduwijk
2

Creo que el ||operador se puede adaptar a indexOf:

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

El valor devuelto se desplaza hacia arriba en 1, haciendo 0 de -1, que es falso y, por lo tanto, se reemplaza por el segundo 1. Luego se desplaza hacia atrás.

Sin embargo, tenga en cuenta que la legibilidad es superior a evitar la repetición.

IllidanS4 quiere que Monica vuelva
fuente
2

Esta es una solución simple con NOT bit a bit y un valor predeterminado -1cuyo resultado más tarde es cero.

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

Funciona básicamente con un NOT doble bit a bit, que devuelve el valor original o un valor predeterminado, que después de aplicar NOT bit a bit devuelve cero.

Echemos un vistazo a la tabla de la verdad:

 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
fuente
0

Podría usar la reasignación:

  • inicializar variable a un valor
  • use la serialización del &&operador para la reasignación, porque si la primera condición es falsa, la segunda expresión no se evaluará

Ex.

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

vol7ron
fuente
3
@MinusFour Hasta cierto punto. La variable se repite, no la expresión someArray.indexOfsolo se realiza una vez
vol7ron
Repites value.
MinusFour
3
@MinusFour Correcto, pero esto es más útil para expresiones más grandes, repetir una variable es insignificante en comparación con guardar las operaciones. Supongo que el OP no funciona con -1y 0; de lo contrario max(), sería la mejor opción
vol7ron
2
Correcto ... pero la pregunta es ... "Cómo escribir ternaries sin repetirse "
MinusFour
4
También dice Again, not seeking an answer to the exact question above, lo que deja la pregunta a la interpretación;)
vol7ron
0

Dado el código de ejemplo en la pregunta, no está claro cómo se determinaría si 3se establece o no en el índice 0de someArray. -1devuelto de .indexOf()sería valioso en este caso, con el propósito de excluir una supuesta no coincidencia que podría ser una coincidencia.

Si 3no está incluido en la matriz, -1se devolverá. Podemos agregar 1al resultado de .indexOf()para evaluar como falseresultado -1, donde seguido del || ORoperador y 0. Cuando valuese hace referencia, reste 1para obtener el índice del elemento de la matriz o -1.

Lo que nos lleva a simplemente usar .indexOf()y verificar -1una ifcondición. O, definiendo valuecomo undefinedpara evitar una posible confusión en cuanto al resultado real de la condición evaluada en relación con la referencia original.

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

invitado271314
fuente
0

Un ternario es como un if-else, si no necesitas la parte else, ¿por qué no solo un if?

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

Para este caso particular, podría utilizar un cortocircuito con el ||operador lógico . Como 0se considera falso, puede agregar 1a su índice, por lo tanto, si index+1es 0, obtendrá el lado derecho del lógico, o como resultado, de lo contrario, obtendrá su index+1. Como el resultado deseado se compensa con 1, luego puede restarlo 1para obtener su índice:

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

Nick Parsons
fuente