如何在不重复自己的情况下编写三元运算符(又名 if)表达式

Posted

技术标签:

【中文标题】如何在不重复自己的情况下编写三元运算符(又名 if)表达式【英文标题】:How to write a ternary operator (aka if) expression without repeating yourself 【发布时间】:2017-09-07 15:44:11 【问题描述】:

例如,像这样的:

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

有没有更好的写法?同样,我不是在寻求上述确切问题的答案,只是一个示例,说明您何时可能在三元运算符表达式中重复操作数...

【问题讨论】:

所以if 而不是if/else just an example of when you might have repeated things in the ternary 不要重复计算表达式。这就是变量的用途。 我们如何确定3 不在someArray 的索引0 中? 你的目标是什么?您是要减少行长,还是特别要避免在三元组中重复变量?前者是可能的,后者不是(至少,不使用三元组)。 为什么不改用Math.max(someArray.indexOf(3), 0) 【参考方案1】:

对于这种特殊情况,您可以使用逻辑 || 运算符进行短路。由于0 被认为是虚假的,您可以将1 添加到您的索引中,因此,如果index+10,那么您将得到逻辑或的右侧作为结果,否则,您会得到你的index+1。由于您想要的结果被1 抵消,因此您可以从中减去1 以获得您的索引:

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

【讨论】:

【参考方案2】:

代码应该是可读的,所以简洁并不意味着不惜一切代价保持简洁——因为你应该重新发布到https://codegolf.stackexchange.com/——所以我建议使用第二个名为index的局部变量来最大限度地提高阅读的可理解性(我注意到,运行时成本也最低):

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

但如果你真的想减少这种表达,因为你对你的同事或项目合作者是一个残忍的虐待狂,那么你可以使用以下 4 种方法:

1:var 语句中的临时变量

您可以使用var 语句的功能来定义(和分配)第二个临时变量index(用逗号分隔):

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

2:自执行匿名函数

另一种选择是自执行匿名函数:

// 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:逗号运算符

还有 javascript 支持的臭名昭著的“逗号运算符”,它也存在于 C 和 C++ 中。

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

当您想在需要单个表达式的位置包含多个表达式时,可以使用逗号运算符。

您可以使用它来引入副作用,在这种情况下通过重新分配给value

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

这是因为var value首先被解释(因为它是一个语句),然后最左边,最里面的value赋值,然后是逗号的右边运算符,然后是三元运算符 - 所有合法的 JavaScript。

4:在子表达式中重新赋值

评论员@IllusiveBrian 指出,如果将value 的赋值用作带括号的子表达式,则不需要使用逗号运算符(在前面的示例中):

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

请注意,在逻辑表达式中使用否定词对人类来说可能更难理解 - 因此可以通过将idx !== -1 ? x : y 更改为idx == -1 ? y : x 来简化上述所有示例以方便阅读:

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

【讨论】:

所有这些都是那种“聪明”的编码,让我盯着它看了几秒钟,然后想“嗯,我想这行得通”。它们无助于清晰,而且由于阅读的代码比编写的多,因此更重要。 @g.rocket 方法 1 是 100% 可读且清晰的,如果您想避免重复,这是唯一的方法(如果您调用一些复杂且有问题的函数而不是一个简单的indefOf) 同意,#1 的可读性和可维护性很好,其他两个就没那么多了。 对于#3,我相信你可以简化为var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);而不是使用逗号。 在第二个例子中自执行匿名函数你可以省略箭头函数的括号。 var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) ); 因为只有一个参数。【参考方案3】:

编辑:在这里,Nullary-coalescing 的提案现在在 JavaScript 中!


使用||

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

等价于

const result = a || 'fallback value';

如果将a 转换为Boolean 返回false,则result 将被分配'fallback value',否则为a 的值。


注意极端情况a === 0,它转换为falseresult 将(错误地)采用'fallback value'。使用此类技巧需要您自担风险。


PS。诸如 Swift 之类的语言具有 nil-coalescing 运算符 (??),其用途类似。例如,在 Swift 中,您可以编写 result = a ?? "fallback value",它非常接近 JavaScript 的 const result = a || 'fallback value';

【讨论】:

php (>7.0) 和 C# 也支持空值合并运算符。语法糖,但肯定很可爱。 这仅适用于函数在失败时返回错误值的情况,但indexOf()不能用于此模式。 正确,但他并没有要求具体的例子>“再次,不寻求上述确切问题的答案,只是一个例子,说明你何时可能在三元中重复了一些事情” 真的||在JavaScript中是如何工作的吗?如果我对您的理解正确,那么它的工作方式与许多其他主要语言不同(我主要考虑 C 及其后代 [C++、Java 等])即使这确实是 || 在 JavaScript 中的工作方式,我也会建议不要使用这样的技巧,因为这些技巧需要维护人员了解该语言的特殊怪癖。虽然这个技巧很酷,但我认为这是不好的做法。 另请注意,问题是将值与-1 进行比较。同样,我不能代表 JavaScript 及其怪癖,但通常 -1 将是一个真值,而不是一个假值,因此您的答案在问题的情况下不起作用,当然也不是一般情况,而是只适用于一个特定(但足够常见)的子案例。【参考方案4】:

三元就像一个 if-else,如果你不需要 else 部分,为什么不只是一个 if 代替..

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

【讨论】:

【参考方案5】:

这是一个简单的解决方案,使用 bitwise NOT 和默认值 -1,稍后会为零。

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

它基本上适用于双位非,它返回原始值或默认值,应用位非后返回零。

让我们来看看真值表:

 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

【讨论】:

【参考方案6】:

使用extract variable refactoring:

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

使用const 而不是var 会更好。您还可以进行额外的提取:

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

在实践中,使用比indexconditionvalue 更有意义的名称。

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

【讨论】:

什么是“提取变量”?这是一个既定的术语吗?【参考方案7】:

我可以通过两种方式查看您的问题:您要么想减少行长,要么特别想避免在三元组中重复变量。第一个是微不足道的(许多其他用户已经发布了示例):

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

可以(并且应该,给定函数调用)像这样缩短:

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

如果您正在寻找一种更通用的解决方案来防止三元中的变量重复,如下所示:

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

foo 只出现一次。丢弃形式的解决方案:

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

技术上正确但没有抓住重点,那么你就不走运了。有些运算符、函数和方法拥有您所寻求的简洁语法,但根据定义,这些构造不是三元运算符

例子:

javascript,当LHS为falsey时使用||返回RHS:

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

【讨论】:

这个问题被标记为 javascript 并且没有提到 C#。只是想知道为什么以 C# 特定示例结尾。 我错过了问题上的 javascript 标签;删除了 C#。【参考方案8】:

我个人更喜欢两种变体:

    如果像@slebetman 建议的那样纯属

    分离函数,将无效值替换为默认值,如下例所示:

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,选项2尊重问题的内联意图,可以有效地应用于javascript以外的其他语言,并促进模块化。【参考方案9】:

我认为||运算符可以定制为indexOf

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

返回值上移 1,从 -1 变为 0,这是错误的,因此被第二个 1 替换。然后它被移回。

但是,请记住,可读性优于避免重复。

【讨论】:

【参考方案10】:

使用辅助函数:

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

现在你的代码可读性很强,没有重复。

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

编码关注点的层次结构是:

    正确(包括真正的性能或 SLA 问题) 清除 简洁 快速

到目前为止,页面上的所有答案似乎都是正确的,但我认为我的版本具有最高的清晰度,这比简洁更重要。如果你不计算辅助函数——因为它可以被重用——它也是最简洁的。不幸的是,使用辅助函数的有点类似的建议使用了一个 lambda,对我来说,它只是掩盖了它在做什么。一个更简单的函数,它的目的是不使用 lambda,只使用值,对我来说要好得多。

附:如果你喜欢 ES6 语法:

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

【讨论】:

“我的版本清晰度最高” - 我不同意。函数名太长,命名参数 inputoutput 根本没有帮助。 你能推荐更好的名字吗?我很乐意招待他们。您的投诉完全是关于化妆品的,所以让我们修理化妆品吧。正确的?否则你只是自行车脱落。 在某些情况下,这样的辅助函数会很有用。在这种情况下,它仅替换三元运算符的特定情况,您的功能将不太清楚。我不会记住你的函数做了什么,每次遇到它我都得再去查找它,我永远不会记得使用它。 @Aaron 这是对特定用例的合理评估。值得一提的是,我最初的函数名称是 translateValueIfEqual,我认为它更具描述性,但在有人认为它太长后我更改了它。很像 Access 中的Nz 函数,如果你知道它,你就知道它,如果你不知道,你就不知道。在现代 IDE 中,您只需按一个键即可跳转到定义。而后备将是中间变量。我真的没有看到这里有很大的缺点。 这只是表明,如果您向 5 个不同的工程师提出同一个问题,您将得到 10 个不同的答案。【参考方案11】:

我喜欢@slebetman 的回答。它下面的评论表达了对变量处于“中间状态”的担忧。如果这对您来说是一个大问题,那么我建议将其封装在一个函数中:

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

然后打电话

var value = get_value( someArray );

如果您在其他地方使用它们,您可以做更多通用功能,但如果是非常具体的情况,请不要过度设计。

但老实说,除非我需要在多个地方重复使用,否则我只会像 @slebetman 那样做。

【讨论】:

【参考方案12】:

您可能正在寻找合并运算符。幸运的是,我们可以利用 Array 原型创建一个:

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

这可以通过向函数添加参数来进一步推广到您的情况:

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

【讨论】:

添加到数组的原型中是有风险的,因为新元素成为每个数组中的索引。这意味着遍历索引还包括新方法:for (let x in ['b','c']) console.log(x); 打印 0,1,"coalesce" @CharlieHarding 是的,但一般不建议在遍历数组时使用 for-in 运算符。见***.com/a/4374244/1486100【参考方案13】:

不是真的,只是使用另一个变量。

您的示例可以概括为这样的内容。

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

您正在测试一个计算值,然后将该值分配给一个变量,如果它通过了某个谓词。避免重新计算计算值的方法很明显:使用变量来存储结果。

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

我明白你的意思 - 似乎应该有某种方法可以做到这一点,看起来更干净一些。但我认为这是最好的方式(习惯上)做到这一点。如果您出于某种原因在代码中多次重复这种模式,您可能会编写一个小辅助函数:

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

【讨论】:

【参考方案14】:

对于数字

您可以使用Math.max() 函数。

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

如果是这种情况,它将保持结果的边界从0 直到第一个结果大于0。如果indexOf 的结果是-1,它将返回大于-1 的0。

对于布尔值和布尔值

对于 JS,AFAIK 没有特别的通用规则,因为如何评估 falsy 值。

但如果大多数时候可以帮助您的是 or 运算符 (||):

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

您必须非常小心,因为在您的第一个示例中,indexOf 可以返回0,如果您评估0 || -1,它将返回-1,因为0 是falsy 值。

【讨论】:

谢谢。我想我的例子很糟糕,我只是举一个一般的例子哈哈,而不是寻求确切问题的解决方案。我遇到了一些 scnarios,例如我想使用三元的示例,但最终重复了:( 第一个例子,我们如何确定3"y" 不在someArray 的索引0 中? Math.max 示例中? indexOf 返回元素的索引,如果未找到该元素返回 -1,因此您有机会从 -1 获取到字符串长度的数字,然后使用 Math.max 您只需将边界设置为 0 到消除返回 -1 机会的长度, @mitogh OP 代码中的逻辑造成了一个问题,尽管它是通用示例;其中 0 既可以表示数组中匹配元素的索引0,也可以表示0 设置为Math.max();或在 OP 的条件运算符处。考虑var value = Math.max( ["y"].indexOf("y"), 0 )。您如何确定返回的是哪个00 传递给Math.max() 调用,或者0 反映数组中"y" 的索引? @guest271314 好主意,但我认为这是否是一个问题取决于上下文。也许 0 来自哪里并不重要,唯一重要的是它不是 -1。一个例子:也许你需要从一个数组中挑选一个项目。你想要一个特定的项目(在 OP 中,数字 3),但如果它不在数组中,你仍然需要一个项目,并且你可以默认为第一个项目是什么,假设你知道数组不是t 为空。【参考方案15】:

鉴于问题中的示例代码,尚不清楚如何确定3 是否设置在someArray 的索引0 处。从.indexOf() 返回的-1 在这种情况下很有价值,目的是排除可能是匹配的假定不匹配。

如果数组中不包含3,则返回-1。我们可以将1 添加到.indexOf() 的结果中,以评估为false,结果为-1,然后是|| OR 运算符和0。当引用value 时,减去1 得到数组元素的索引或-1

这导致简单地使用.indexOf() 并在if 条件下检查-1。或者,将value 定义为undefined 以避免可能混淆与原始参考相关的评估条件的实际结果。

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

【讨论】:

【参考方案16】:

您可以使用重新分配:

将变量初始化为一个值 使用&amp;&amp; 运算符的序列化进行重新赋值,因为如果第一个条件为假,则不会计算第二个表达式

例如

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

【讨论】:

@MinusFour 在一定程度上。变量是重复的,不是表达式someArray.indexOf只执行一次 你重复value @MinusFour 正确,但这对于较大的表达式更有用,与保存操作相比,重复变量是微不足道的。我的猜测是 OP 不适用于 -10;否则max() 将是最好的选择 对...但问题是...“如何编写三元组不重复自己” 它还写着Again, not seeking an answer to the exact question above,这将问题留给了解释;)【参考方案17】:

我个人认为最好的方法仍然是旧的if 声明:

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

【讨论】:

要明确,这个答案提倡使用变量来存储中间结果,而不是使用if 语句代替三元组。三元语句在作为表达式的一部分执行选择时仍然经常有用。 @Triptych:再看一遍。它根本不使用中间变量。相反,它将结果直接分配给最终变量,然后在满足条件时覆盖它。 不正确。第一行之后存储在value 中的值是中间值。它不包含正确的值,然后将其固定在下一行,因此是中间值。只有在if 语句得出结论value 包含正确的值之后。 OP 中的三元组是一个更好的解决方案,因为它永远不会进入中间状态。 @JackAidley:“OP 中的三元组是一个更好的解决方案,因为它永远不会进入中间状态。” - 我将不得不不同意。这比 OP 的代码可读性强得多,而且完全是惯用的。它还使 OP 逻辑中的错误对我来说更加明显(即,如果 indexOf() 返回零会发生什么?如何区分“真实”零和“未找到”零?)。 这和我会做的差不多,只是这里的value 在技术上发生了变异,我尽量避免。

以上是关于如何在不重复自己的情况下编写三元运算符(又名 if)表达式的主要内容,如果未能解决你的问题,请参考以下文章

没有 else 子句的单行 If 条件 [重复]

在格式化字符串(又名插值字符串)中使用三元运算符时出错 [重复]

如何在不超过最大文档大小的情况下编写聚合?

你好!无论如何要在不使用联合的情况下编写类似的查询吗?

如何在不重复预定义三元组中的特定元素的情况下随机化向量?

如何在不创建新输入信号的情况下编写具有多个输入的多路复用器?