Como escrever uma expressão de operador ternário (também conhecido como if) sem se repetir

104

Por exemplo, algo assim:

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

Existe uma maneira melhor de escrever isso? Novamente, não estou procurando uma resposta para a pergunta exata acima, apenas um exemplo de quando você pode ter operandos repetidos em expressões de operador ternário ...

usuário1354934
fonte
2
Portanto, um ife não umif/else
zer00ne
53
just an example of when you might have repeated things in the ternarynão repita expressões calculadas. É para isso que servem as variáveis.
vol7ron
4
Como determinamos que 3não está no índice 0de someArray?
guest271314
3
Qual é o seu objetivo aqui? Você está tentando reduzir o comprimento da linha ou, especificamente, tentando evitar a repetição de uma variável em um ternário? O primeiro é possível, o último não (pelo menos, não usando ternários).
asgallant
5
Por que não usar Math.max(someArray.indexOf(3), 0)?
301_Moved_Permanently

Respostas:

176

Pessoalmente, acho que a melhor maneira de fazer isso ainda é a boa e velha ifdeclaração:

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}
slebetman
fonte
32
Para ser claro, essa resposta defende o uso de uma variável para armazenar um resultado intermediário, não o uso de uma ifinstrução no lugar de um ternário. As declarações ternárias ainda são frequentemente úteis para realizar uma escolha como parte de uma expressão.
Tríptico de
4
@Triptych: Olhe de novo. Ele não usa uma variável intermediária. Em vez disso, ele atribui o resultado diretamente à variável final e, em seguida, o substitui se a condição for atendida.
slebetman de
14
Incorreta. O valor armazenado valueapós a primeira linha é intermediário. Ele não contém o valor correto que é então fixado na próxima linha e, portanto, é um intermediário. É somente após a ifconclusão da instrução que valuecontém o valor correto. O ternário no OP é uma solução melhor que este porque nunca entra em um estado intermediário.
Jack Aidley
44
@JackAidley: "O ternário no OP é uma solução melhor que esta porque nunca entra em um estado intermediário." - Vou ter que discordar. Isso é muito mais legível do que o código do OP e totalmente idiomático. Também torna um bug na lógica do OP um pouco mais óbvio para mim (a saber, o que acontece se indexOf () retornar zero? Como você distingue um zero "real" de um zero "não encontrado"?).
Kevin
2
isso é próximo ao que eu faria, exceto que valueaqui está tecnicamente mutado, o que tento evitar.
Dave Cousineau
102

O código deve ser legível, portanto, ser sucinto não deve significar ser conciso custe o que custar - para isso, você deve reenviar para https://codegolf.stackexchange.com/ - então, em vez disso, eu recomendaria usar uma segunda variável local chamada indexpara maximizar a compreensão de leitura ( com custo mínimo de tempo de execução também, eu observo):

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

Mas se você realmente deseja reduzir essa expressão, porque você é um sádico cruel com seus colegas de trabalho ou colaboradores do projeto, então aqui estão 4 abordagens que você pode usar:

1: Variável temporária em uma vardeclaração

Você pode usar a varcapacidade da instrução para definir (e atribuir) uma segunda variável temporária indexquando separada por vírgulas:

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

2: Função anônima autoexecutável

Outra opção é uma função anônima autoexecutável:

// 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 vírgula

Há também o infame "operador de vírgula" que o JavaScript suporta, que também está presente em C e C ++.

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

Você pode usar o operador vírgula quando quiser incluir várias expressões em um local que requer uma única expressão.

Você pode usá-lo para introduzir efeitos colaterais, neste caso, reatribuindo a value:

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

Isso funciona porque var valueé interpretado primeiro (como se fosse uma instrução) e, em seguida , a valueatribuição mais à esquerda, mais interna , e então à direita do operador vírgula e, em seguida, o operador ternário - todo JavaScript válido.

4: Reatribuir em uma subexpressão

O comentador @IllusiveBrian apontou que o uso do operador vírgula (no exemplo anterior) é desnecessário se a atribuição a valuefor usada como uma subexpressão entre parênteses:

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

Observe que o uso de negativos em expressões lógicas pode ser mais difícil para humanos seguirem - portanto, todos os exemplos acima podem ser simplificados para leitura mudando idx !== -1 ? x : ypara idx == -1 ? y : x:

var value = ( ( value = someArray.indexOf(3) ) == -1 ? 0 : value );
Dai
fonte
97
Todos esses são o tipo de codificação "inteligente" que me faria olhar fixamente para ele por alguns segundos antes de pensar "huh, acho que funciona." Eles não ajudam com clareza e, como o código é mais lido do que escrito, isso é mais importante.
Gavin S. Yancey
18
@ g.rocket approach 1 é 100% legível e claro, e o único caminho a percorrer se você quiser evitar a repetição (que pode ser muito prejudicial se você estiver chamando alguma função complexa e problemática em vez de uma simples indefOf)
edc65
5
Concordo, o número 1 é muito legível e sustentável, os outros dois nem tanto.
perdoado em
6
Para o nº 3, acredito que você pode simplificar para em var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);vez de usar uma vírgula.
IllusiveBrian
2
No segundo exemplo de função anônima autoexecutável, você pode omitir parênteses da função de seta. var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );porque é apenas um parâmetro.
Alex Char
54

Para números

Você pode usar a Math.max()função.

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

Isso manterá os limites do resultado 0até o primeiro resultado maiores do que 0se for esse o caso. E se o resultado de indexOffor, -1ele retornará 0, pois é maior que -1.

Para valores booleanos e booleanos

Para JS, não há regra geral AFAIK, especialmente porque os valores falsos são avaliados.

Mas se algo pode ajudá-lo na maioria das vezes é o operador ou ( ||):

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

Você tem que ter muito cuidado com isso, pois no seu primeiro exemplo, indexOfpode retornar 0e se avaliar 0 || -1retornará -1porque 0é um valor falso .

Crisoforo Gaspar
fonte
2
Obrigado. Acho que meu exemplo é ruim, estou apenas dando um exemplo geral haha ​​não estou procurando uma solução para a pergunta exata. Enfrentei alguns cenários como o exemplo em que gostaria de usar um ternário, mas
acabo
1
No primeiro exemplo, como determinamos isso 3ou "y"não está no índice 0de someArray?
guest271314
No Math.maxexemplo? indexOfretorna o índice do elemento e se o elemento não for encontrado retorna -1 então você tem uma chance de obter um número de -1 ao comprimento da string então com Math.maxvocê apenas defina os limites de 0 ao comprimento para remover a chance para retornar a -1,
Crisoforo Gaspar
@mitogh A lógica no código no OP cria um problema, embora seja um exemplo genérico; em que 0 pode indicar o índice 0do elemento correspondente na matriz ou 0definido como Math.max(); ou no operador condicional no OP. Considere var value = Math.max( ["y"].indexOf("y"), 0 ). Como você determina o que 0está sendo devolvido? 0passado para Math.max()chamar, ou 0refletindo o índice de "y"dentro da matriz?
guest271314
@ guest271314 Boa ideia, mas acho que depende do contexto se isso é um problema ou não. Talvez não importe de onde veio o 0, e a única coisa importante é que não seja -1. Um exemplo: talvez você precise escolher um item de uma matriz. Você quer um item específico (no OP, o número 3), mas se ele não estiver na matriz, você ainda precisa de um item, e está tudo bem com o padrão para qualquer que seja o primeiro item, assumindo que você sabe que a matriz não é t vazio.
Kev
27

Não realmente, apenas use outra variável.

Seu exemplo se generaliza para algo assim.

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

Você está testando um valor calculado e, em seguida, atribuindo esse valor a uma variável se ela passar algum predicado. A maneira de evitar o recálculo do valor calculado é óbvia: use uma variável para armazenar o resultado.

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

Entendi o que você quis dizer - parece que deve haver alguma maneira de fazer isso que pareça um pouco mais limpa. Mas acho que essa é a melhor maneira (linguística) de fazer isso. Se você estiver repetindo muito esse padrão em seu código por algum motivo, poderá escrever uma pequena função auxiliar:

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

EDIT: Aqui está, a proposta de coalescência nular agora em JavaScript!


Usar ||

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

é equivalente a

const result = a || 'fallback value';

Se lançar apara Booleanretorna false, resultserá atribuído 'fallback value', caso contrário, o valor de a.


Esteja ciente do caso extremo a === 0, que lança para falsee resultirá (incorretamente) levar 'fallback value'. Use truques como este por sua própria conta e risco.


PS. Linguagens como Swift têm operador de coalescência nil ( ??), que serve a um propósito semelhante. Por exemplo, em Swift você escreveria o result = a ?? "fallback value"que é muito próximo do JavaScriptconst result = a || 'fallback value';

Lyubomir
fonte
3
PHP (> 7.0) e C # também suportam o operador de coalescência nulo. Açúcar sintático, mas certamente adorável.
Hissvard
5
Isso só funciona quando a função retorna um valor falsey quando falha, mas indexOf()não pode ser usado neste padrão.
Barmar de
1
Correto, mas ele não está pedindo o exemplo específico> "Novamente, não estou buscando uma resposta para a pergunta exata acima, apenas um exemplo de quando você pode ter repetido coisas no ternário" <, lembre-se, ele está pedindo uma maneira genérica de substitua a repetição ternária (resultado = a? a: b). E repetir ternário é equivalente a || (resultado = a || b)
Lyubomir
2
É realmente assim que ||funciona em JavaScript? Se estou entendendo você corretamente, então funciona diferente de muitas outras linguagens primárias (estou pensando principalmente em C e seus descendentes [C ++, Java, etc.]) Mesmo que seja realmente assim que ||funciona em JavaScript, eu desaconselho o uso truques como esse que exigem que os mantenedores conheçam peculiaridades da linguagem. Embora esse truque seja legal, eu consideraria uma prática ruim.
Loduwijk,
1
Observe também que a questão está comparando o valor com -1. Novamente, não posso falar em JavaScript e suas peculiaridades, mas geralmente -1seria um valor verdadeiro, não falso e, portanto, sua resposta não funcionaria no caso da pergunta e certamente não no caso geral, ao invés disso, funcionaria apenas em um caso específico ( mas bastante comum) sub-caso.
Loduwijk,
8

Use uma refatoração de variável de extração :

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

É ainda melhor com em constvez de var. Você também pode fazer uma extração adicional:

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

Na prática, usar nomes mais significativos do que index, conditione value.

const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;
Dave Cousineau
fonte
O que é uma "variável de extração"? Esse é um prazo estabelecido?
Peter Mortensen,
6

Você provavelmente está procurando um operador de coalescência. Felizmente, podemos aproveitar o Arrayprotótipo para criar um:

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

Isso poderia ser generalizado para o seu caso, adicionando um parâmetro à função:

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
fonte
Adicionar ao protótipo de arrays é arriscado, porque o novo elemento se torna um índice dentro de cada array. Isto significa que a iteração através dos índices também inclui o novo método: for (let x in ['b','c']) console.log(x);gravuras 0, 1, "coalesce".
Charlie Harding
@CharlieHarding True, mas geralmente não é recomendado usar o operador for-in durante o loop em matrizes. Consulte stackoverflow.com/a/4374244/1486100
Tyzoid
6

Eu pessoalmente prefiro duas variantes:

  1. Puro se, como @slebetman sugeriu

  2. Função separada, que substitui o valor inválido pelo padrão, como neste exemplo:

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
fonte
1
+1, a opção 2 respeita a intenção inline da pergunta, pode ser aplicada com eficiência a outras linguagens além do javascript e promove a modularidade.
Devsman de
5

Eu gosto da resposta de @ slebetman. O comentário abaixo expressa preocupação sobre a variável estar em um "estado intermediário". se isso é uma grande preocupação para você, sugiro encapsulá-lo em uma função:

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

Então é só ligar

var value = get_value( someArray );

Você poderia fazer funções mais genéricas se tiver usos para elas em outros lugares, mas não faça engenharia excessiva se for um caso muito específico.

Mas para ser honesto, eu faria apenas como @slebetman, a menos que precisasse reutilizar em vários lugares.

Adão
fonte
4

Há duas maneiras de ver sua pergunta: ou você deseja reduzir o comprimento da linha ou deseja evitar especificamente a repetição de uma variável em um ternário. O primeiro é trivial (e muitos outros usuários postaram exemplos):

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

pode ser (e deve ser, dadas as chamadas de função) encurtado assim:

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

Se você está procurando uma solução mais genérica que evite a repetição de uma variável em um ternário, assim:

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

onde fooaparece apenas uma vez. Descartando soluções do formulário:

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

como tecnicamente correto, mas perdendo o ponto, você está sem sorte. Existem operadores, funções e métodos que possuem a sintaxe concisa que você procura, mas tais construções, por definição, não são operadores ternários .

Exemplos:

javascript, usando ||para retornar o RHS quando o LHS é falsey:

var value = foo || bar; // equivalent to !foo ? bar : foo
asgalante
fonte
1
A pergunta está marcada como javascript e não menciona C #. Apenas me perguntando por que você termina com exemplos específicos de C #.
Loduwijk
Perdi a tag javascript na pergunta; removeu o C #.
asgallant
3

Use uma função auxiliar:

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

Agora seu código é muito legível e não há repetição.

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

A hierarquia de interesses de codificação é:

  1. Correto (incluindo o verdadeiro desempenho ou questões de SLA)
  2. Claro
  3. Conciso
  4. Rápido

Todas as respostas da página até agora parecem corretas, mas acho que minha versão tem a maior clareza, o que é mais importante do que a concisão. Se você não contar a função auxiliar, pois ela pode ser reutilizada, ela também é a mais concisa. A sugestão um tanto semelhante de usar uma função auxiliar infelizmente usa um lambda que, para mim, apenas obscurece o que está fazendo. Uma função mais simples com um propósito que não leva lambda, apenas valores, é muito melhor para mim.

PS Se você gosta da sintaxe ES6:

const translateValue = (value, match, translated) => value === match ? translated : value;
let value = translateValue(someArray.indexOf(3), -1, 0); // or const
ErikE
fonte
2
“minha versão tem a maior clareza” - eu discordo. O nome da função é muito longo e nomear os parâmetros de entrada e saída não ajuda em nada.
adelphus
Então, você pode sugerir nomes melhores? Eu ficaria feliz em entretê-los. Sua reclamação é apenas sobre cosméticos, então vamos consertar os cosméticos. Certo? Caso contrário, você está apenas trocando de bicicleta.
ErikE
Em alguns casos, essa função auxiliar pode ser útil. Neste caso, onde está substituindo apenas um caso específico de operador ternário, sua função ficará menos clara. Não vou me lembrar do que a sua função faz e terei que procurá-la novamente sempre que a encontrar, e nunca me lembraria de usá-la.
Loduwijk,
1
@Aaron Essa é uma avaliação razoável para um caso de uso específico. Pelo que vale a pena, meu nome de função original era o translateValueIfEqualque eu achei mais descritivo, mas mudei depois que alguém achou que era muito longo. Muito parecido com a Nzfunção no Access, se você conhece, você sabe, e se não conhece, você não conhece. Em IDEs modernos, você pode simplesmente pressionar uma tecla para pular para a definição. E o fallback seria a variável intermediária. Eu realmente não vejo um grande lado negativo aqui.
ErikE
1
Isso só mostra que, se você fizer a mesma pergunta a 5 engenheiros diferentes, obterá 10 respostas diferentes.
Loduwijk,
2

Acho que o ||operador pode ser adaptado para indexOf:

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

O valor retornado é deslocado para cima em 1, tornando 0 de -1, que é falsey e, portanto, é substituído pelo segundo 1. Em seguida, ele é deslocado de volta.

No entanto, lembre-se de que a legibilidade é superior a evitar a repetição.

IllidanS4 quer Monica de volta
fonte
2

Esta é uma solução simples com NOT bit a bit e um valor padrão de-1 que resulta posteriormente em zero.

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

Funciona basicamente com um NOT bit a bit duplo, que retorna o valor original ou um valor padrão, que após aplicar o NOT bit a bit retorna zero.

Vamos dar uma olhada na tabela da verdade:

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

Você pode usar a reatribuição:

  • inicializar a variável com um valor
  • usar a serialização do &&operador para reatribuição, pois se a primeira condição for falsa, a segunda expressão não será avaliada

Ex.

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

Vol7ron
fonte
3
@MinusFour Até certo ponto. A variável é repetida, não a expressão someArray.indexOfé executada apenas uma vez
vol7ron
Você repete value.
MinusFour
3
@MinusFour Correto, mas é mais útil para expressões maiores, repetir uma variável é insignificante em comparação com salvar as operações. Meu palpite é que o OP não está funcionando com -1e 0; caso contrário, max()seria a melhor opção
vol7ron
2
Certo ... mas a questão é ... "Como escrever ternários sem se repetir "
MinusFour
4
Também diz Again, not seeking an answer to the exact question above, o que deixa a questão para interpretação;)
vol7ron
0

Dado o código de exemplo em Question, não está claro como seria determinado se 3é ou não definido no índice 0de someArray. -1retornado de .indexOf()seria valioso neste caso, com o propósito de excluir uma não correspondência presumida que poderia ser uma correspondência.

Se 3não estiver incluído na matriz, -1será retornado. Podemos adicionar 1ao resultado de .indexOf()avaliar como falsesendo o resultado -1, onde seguido pelo || ORoperador e 0. Quando valueé referenciado, subtraia 1para obter o índice do elemento da matriz ou -1.

O que leva de volta a simplesmente usar .indexOf()e verificar -1uma ifcondição. Ou, definindo valuecomo undefinedpara evitar possível confusão quanto ao resultado real da condição avaliada relativa a referência 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);

guest271314
fonte
0

Um ternário é como um if-else, se você não precisa da parte else, por que não apenas um único if.

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

Para este caso específico, você pode usar o curto-circuito com o ||operador lógico . Como 0é considerado falso, você pode adicionar 1ao seu índice, portanto, se index+1for 0, você obterá o lado direito da lógica - ou como resultado, caso contrário, obterá o seu index+1. Como o resultado desejado é compensado por 1, você pode subtrair 1dele para obter seu índice:

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

Nick Parsons
fonte