有没有更好的方法来写 v = (v == 0 ? 1 : 0)?

490
我想在0和1之间切换一个变量。如果它是0,我想将其设置为1;如果它是1,我想将其设置为0。
这是一种非常基本的操作,我经常写,所以我想探索最短、最清晰的方法来完成它。以下是我目前为止最好的方法:
v = (v == 0 ? 1 : 0);

你能改进这个吗?
编辑:问题是问如何以最少的字符数写出上述语句,同时保持清晰度 - 这怎么会是“不是一个真正的问题”?虽然有些有趣的答案来自人们把它当作高尔夫练习,但这并不是旨在成为一项代码高尔夫练习,很高兴看到高尔夫被用于积极和发人深省的方式。

20
我认为这已经很简单/清晰/简短了。 - Mob
145
欺骗手段:v = +!v; (注:该语句为计算机编程语言中的代码,意义为将变量v的值取反后再转换为数字类型并赋值给v) - jAndy
48
如果“更好”还意味着“更快”,请查看:http://jsperf.com/v-0-1-0。 - pimvdb
7
@Mobinga:+1。这是应该变得简单的地方。我看到的所有其他答案都令人困惑,其中一些会改变逻辑,引入错误。 - Ian Boyd
8
更好的表达你的观点的解决方案是创建一个回答,表明你认为原始方式是最好的,并详细阐述为什么你有这样的看法。这也让其他人可以轻松地通过投票支持你的答案。 - Chuck van der Linden
显示剩余23条评论
31个回答

761

你可以简单地使用:

v = 1 - v;

当然,这假定变量已经被正确初始化了,也就是说它只有值0或1。

另一种更短的方法,但使用的运算符较不常见:

v ^= 1;

编辑:

为了明确,我从未将这个问题视为代码高尔夫,只是想找到一种在不使用任何模糊技巧(如操作符的副作用)的情况下完成任务的简短方法。


215
这些代码行好看得像精致的玻璃或珍稀的花卉。我喜欢你直接通过逻辑层并用最优操作处理了1和0的数学运算。我将在自己的项目中使用它们,但不幸的是,当需要让同事理解代码时,我将不得不采用更基于逻辑的方法。非常感谢你,你的回答让我很开心。 - Ollie Glass
37
你可以随时添加一段注释来说明代码的作用。 - Prusse
25
@Kevin:这显然并不是不回答所问的问题。问题说:“我想在0和1之间切换一个变量。”一个非常合理的解释是,这意味着变量的值已经是0或1。这种解释/限制已经在回答中明确说明了。你到底有什么反对的呢? - LarsH
51
如果有人认为v ^= 1不清晰,就要让他停止编程,这种说法有些严厉。正如先前所述,它不是最常见的运算符之一,也与v = v ^ 1的作用不同。此外,该运算符在不同的语言(如VB)中意义完全不同。是的,快速查询将告诉您它是一个异或运算符,并且您将理解它的作用,但对许多人来说,一眼并不明显。我认为这并不意味着你需要辞职。 - JD Isaacks
81
如果你在写作中节省了15个字符,但是又用了40个字符的注释来解释它,那真的算是一种改进吗? - Michael Myers
显示剩余44条评论

358

因为0false值,而1true值。

v = (v ? 0 : 1);
如果你愿意使用 truefalse 而不是数字,那就可以这样做。
v = !v;

或者如果它们必须是数字:

v = +!v; /* Boolean invert v then cast back to a Number */

57
在那个“!”前面加上魔法符号“+”。哦,看起来好污啊!但很酷 :p - jAndy
34
制作一个笑脸:({v :+! v}).v - pimvdb
18
@Brian:不要用那个神奇的 + 符号!¯\(ツ) - jAndy
11
在 JavaScript 中,将 v 赋值为 (v ? 0 : 1) 是一种清晰、惯用的写法。 - Bennett McElwee
2
@Maxim - 这是JavaScript(请参见问题中的标签)。我回答中的评论解释了它的工作原理。一元加运算符将其转换为数字。 - Quentin
显示剩余11条评论

204

v = (v + 1) % 2。如果你需要循环多个值,只需将2替换为(n + 1)即可。比如你需要循环0、1、2这三个值,只需写成v = (v + 1) % 3


2
我喜欢这个循环!那真的很聪明。 - Ollie Glass
17
感谢您考虑将切换视为循环。简单而言,它非常灵活。 - Guffa
3
提供模块化且健壮的解决方案值得肯定。实际应用中,你可能希望用 const 变量来代替魔法数字 2、3 等等。 - oosterwal
2
更短(更精巧)的方式:如果你想循环遍历0、1、2,可以使用++v % 3 - haraldmartin

77
你可以编写一个函数并像这样使用它:
``` v = inv(v) ```

31
我非常同意这是最清晰的解决方案(但是inv函数应该填写什么呢:P)。 - Lea Hayes
4
我希望我能够给出更多的回答支持,而不仅是+1。如果通用代码不是非常明显的话,它应该被包装在一个具有描述性名称的函数中。 - TehShrike
11
你认为inv是一个描述性的名称吗? - Bennett McElwee
3
@Bennett McElwee: inv = invert。我认为这是一个常见的缩写。 - Daniel
52
那为什么要缩写呢?直接叫做“invert”或者“toggle”,这样代码就更易读了。我们又不是在尝试将这些内容塞进 80 列的穿孔卡片里。 - Chuck van der Linden
显示剩余8条评论

52

如果你只关心1这种情况,而不在意其他可能性:

v = v ? 0 : 1;
在上述情况中,如果v为0、false、undefined或null,则v最终将成为1。使用这种方法时要小心 - 即使v是“hello world”,v也将为0。

2
那不会翻转该值。你的意思是 v = v ? 0 : 1 - Guffa
3
感谢 @Guffa 注意到我的词汇错误,给他点赞。 - Brian
3
这是我最喜欢的基于逻辑的答案,我可以用它来与同事交流(我看到 @Guffa 的答案是基于数字的)。我喜欢你如何去掉了不必要的测试(v==0)和括号,将其简化到绝对的最小字符计数。谢谢。 - Ollie Glass
1
我也是@Guffa答案的忠实粉丝 - 用Lynard Skynard的话来说,保持简单吧 ;) - Brian
2
理解“truthy”值加1。 - zzzzBov
我认为这比超级紧凑的版本更易读。我真的很喜欢在“?”中指示一个“测试”。像1-x这样做更聪明的版本很酷,但更加神秘,而且代码可读性(对所有人)和可维护性(对所有人)非常重要,所以我喜欢这个解决方案。 - Michael Durrant

48

v = 1 - v, v ^= 1 或者 v= +!v 这样的代码都可以实现要达到的效果,但是它们可以被称为“hack”。这些并不是优美的代码,而只是为了达到目的而采用的把戏。 1 - v 并不能清晰地表达“在0和1之间切换变量的值”的意思。这会使你的代码缺乏表达力,并引入了一个(虽然很小的)需要其他开发人员解析你代码的点。

相比之下,拥有一个像 v = toggle(v) 的函数可以在一瞥之间传达出意图。


8
"严肃地说, v=1-v 没有传达切换的意思吗?" - Blindy
23
1-v 的确可以表示开关状态,但这是它唯一能表示的意思。例如,它也可以表示一个在 v=1 处为零的线,或者以 v=0.5 为中心的镜像变换。从这个意义上说,它有些模糊不清。虽然知道 v 只可能是 01 限制了它的意义,但这也迫使其他开发人员(或者您未来的自己)在理解这条简单语句之前先要了解上下文。 v = toggle(v) 已经非常明确了。 - Ray
10
如果你是一位程序员,理解逻辑运算的话,v ^= 1 就非常清晰明了。我认为这就是 v ^= 1v=1-v 的区别。前者是一个逻辑操作,后者是一个算术操作,我们试图表示的是一个逻辑概念而不是一个数学概念。请注意,不要改变原文意思。 - Matthew Read
6
@Matthew Read :这是一个很棒的陈述,很好地总结了我的想法:“我们试图代表一个逻辑概念而不是数学概念。”然而,即使 v ^= 1,也存在一些歧义,因为它可以被解释为按位异或。 - Ray
6
对于程序员(是的,我指的是像我们这样的极客),这些解决方案非常清晰。它们不是黑客技巧,因为它们通过简单的方法展现出优雅的解决方式。 - gion_13
显示剩余18条评论

40
(由于投票数量和诚实的数学原则,我对这个“答案”的编辑。我尽可能地拖延,因为它旨在作为一个简短的俏皮话,而不是任何“深奥”的东西,因此放入任何解释似乎与目的背道而驰。但是,评论使清楚我应该清楚地避免误解。)

我的原始回答:

这部分规范的措辞:

如果它是0,则我要将其设置为1,否则将其设置为0。

暗示最准确的解决方案是:
v = dirac_delta(0,v)
首先,我得坦白:我把我的Delta函数搞混了。Kronecker delta可能更合适一些,但是因为我想要一个与域无关的东西(Kronecker delta仅用于整数),所以它们的差别并不大。不过实际上我本不应该使用Delta函数,而应该说:
v = characteristic_function({0},v)

让我澄清一下。回忆一下,一个 函数 是一个三元组,(X,Y,f),其中 XY 是集合(分别称为 定义域值域),f 是将每个 X 的元素映射到一个 Y 值的规则。我们通常把三元组写成 f:X → Y。给定 X 的一个子集,比如 A,有一个称为特征函数的函数,它是一个函数 χA: X → {0,1}(也可以看作是一个到更大值域如 ℕ 或 ℝ 的函数)。这个函数是通过以下规则定义的:

χA(x) = 1,如果 x ∈ A,并且 χA(x) = 0,如果 x ∉ A

如果您喜欢真值表,那么它就是问题“元素 x 是否为集合 A 的元素?”的真值表。

因此,从这个定义可以看出,在这里需要特征函数,其中 X 是一个包含 0 的大集合,A = {0}。这是我应该写的。

现在来讲一下 delta 函数。为此,我们需要了解积分。要么您已经知道它,要么您不知道。如果您不知道,那么我在这里所说的也无法告诉您理论的细节,但我可以给出一个简短的摘要。一个对集合 X测度 本质上是“使平均数起作用所必需的东西”。也就是说,如果我们有一个集合 X 和一个该集合上的测度 μ,那么存在一类称为 可测函数 的函数 X → ℝ,满足表达式 X f dμ 有意义并且在某种模糊的意义下是 fX 上的“平均值”。

给定一个集合的测度,可以为该集合的子集定义一种“测度”。这是通过将其特征函数的积分赋值给一个子集来完成的(假设它是可测函数)。这个值可能是无穷大或未定义的(两者有微妙的区别)。

有很多测度方法,但这里有两种比较重要。一种是定义在实数轴上的标准测度 ℝ。对于这种测度, f dμ 基本就是学校里教的内容(学校还教微积分吗?):把小矩形加起来,然后宽度越来越小。在这种测度下,区间的测度就是它的长度。点的测度是0。

另一种重要的测度可以用于任何集合,称为点测度。它被定义为函数的积分等于其值的:

X f dμ = ∑x ∈X f(x)

这个测度将每个单点集的测度赋值为1。这意味着如果子集本身是有限的,那么它有有限的测量。非常少量的函数具有有限积分。如果一个函数有有限积分,则其仅在可数个点处非零。所以你可能知道的大多数函数在这种测度下没有有限积分。

现在来谈一谈狄拉克函数。让我们给出一个非常广泛的定义。我们有一个可测空间(X,μ)(即具有度量的集合)和一个元素a ∈ X。根据a,“定义”“delta函数”(δa)为具有以下特性的“函数”δa: X → ℝ:如果x ≠ a,则δa(x) = 0X δa dμ = 1

需要掌握的最重要的事实是:狄拉克函数不一定是函数。它没有被正确地定义:我并没有说明δa(a)是什么。

接下来要做的取决于您是谁。这里的世界分为两类人。如果你是数学家,你会说:

好的,这里的Delta函数可能没有被准确定义。让我们看看它的假设性质,并找到一个适当的定义它的位置。我们可以这样做,并且最终会得到分布。这些不一定是函数,但它们的行为有点像函数,通常我们可以像使用函数一样处理它们; 但是它们缺少某些东西(如“值”),因此我们需要小心。

如果您不是数学家,则可以说:

  

好的,那么Delta函数可能没有被准确定义。谁这么说?一群数学家?忽略他们!他们知道什么?

现在我已经冒犯了我的听众,所以我将继续。

Dirac Delta通常被认为是实线上一个点(通常为0)的delta函数及其标准测度。因此,那些在评论中抱怨我不知道我的delta的人之所以这样做是因为他们正在使用此定义。对于他们,我道歉:虽然我可以通过使用“数学家的辩护”(由Humpty Dumpty流行化)来摆脱这种情况:只需重新定义一切使其正确即可,但使用标准术语表示不同的含义是不恰当的。

但是,确实有一个Delta函数可以做到我希望它做的事情,这就是我在这里需要的。如果我对集合X进行一次点测度,则存在一个真实的函数δa:X→ ℝ满足delta函数的标准。这是因为我们正在寻找一个函数X → ℝ,它除了在a处为零以外,在其他地方都为零,并且其所有值的总和为1。这样的函数很简单:唯一缺少的信息是它在a处的值,为使总和为1,我们只需将其赋值为1。这就是单点集上的特征函数。然后:

X δa dμ = ∑x ∈ X δa(x) = δa(a) = 1.

因此,在这种情况下,对于单例集,特征函数和delta函数是相同的。

总之,这里有三个“函数”家族:

  1. 单例集的特征函数,
  2. Delta函数,
  3. Kronecker Delta函数。
< p>其中第二个是最通用的,因为在使用点度量时其他任何选项都是它的示例。但是,第一个和第三个具有它们始终是真实函数的优势。第三个实际上是第一个的一个特殊情况,适用于特定的域族(整数或某些子集)。

因此,最后,在我最初撰写答案时,我没有认真思考(我不会说我很困惑,因为我希望我刚刚展示了当我实际思考时我知道自己在说什么,只是我没有太多思考)。 Dirac delta的通常含义不是这里想要的,但是我的答案的一个要点是输入域未定义,因此Kronecker delta也不正确。 因此,最好的数学答案(我旨在)将是特性函数。

希望那一切都清楚; 我也希望我永远不必再次使用HTML实体而不是TeX宏来编写数学文章!


38
如果我可以给Quentin打负分,因为他不知道Dirac Delta函数的话,我一定会这么做。我想知道他上的哪所大学,因为如果他连数字信号处理中那么基础的东西都不懂,我不认为我会雇用来自那里的任何人。我还会因为他无法理解你试图表达的幽默而进一步对他进行负分。 - Dunk
24
如果我对每个我不理解的答案都点“踩”……那我会点很多次“踩”。 - Seamus
4
你好 Andrew,有一段时间没见了。 - Tim Down
4
@Tim: 天啊,事情是这样的!百慕大主教怎么样了?(我记错了吗?)我猜这个评论区不是最好的聊天地点,毕竟已经17年了,对吧? - Andrew Stacey
3
“这个答案失败了…” 叹气。你确实看到我是一个数学家,是吗?这个答案对于处于真空中的球形程序员完美适用,所以我不知道你在抱怨什么。 - Andrew Stacey
显示剩余10条评论

37
你可以这样做。
v = Math.abs(--v);

减量操作将值设置为0或-1,然后Math.abs将-1转换为+1。


27
这已经演变成一个“生成10000种完成简单任务的方法”的竞赛。我喜欢所有的方法,哈哈。 - Josh
27
v = Math.round(Math.sin(Math.PI/(v+3))); - Benoit
15
@Benoit - 你的公式在v=0和v=1时都等于1。但是,这个公式是正确的!v = Math.round(Math.cos(Math.PI/((v*2+1)+2)-2*v)); :D - Teo.sk
2
这不会改变值。如果 v = 1,那么 v = Math.Abs(-1) 就是 +1。如果 v = 0,那么 v = Math.Abs(-0) 就是 0。 - Paul
1
+1 这是我的另一个... 嗷。 - recursive
显示剩余3条评论

37

通常情况下,当你需要在两个值之间切换时,只需将当前值从两个切换值的总和中减去:

    0,1 -> v = 1 - v
    1,2 -> v = 3 - v
    4,5 -> v = 9 - v 

2
这很有趣和创造性,但潜在危险。如果 v 变得损坏,它将突然开始在两个不同的值之间切换。(只是需要考虑一下...) - oosterwal
1
+1 针对这个有趣的想法。并不是说原帖或其他人会在这种情况下真正使用它,但这个信息应该永远存在于大脑某处。 - Shimmy Weitzhandler
3
那么问题肯定出在腐败上了... - GManNickG
@GManNickG:数据损坏迟早会发生,因此我们需要意识到未检测到它的后果。如果v通常用于从三个或更多状态的列表中交替执行两个状态,则v的损坏可能导致程序交替执行两个完全不同的状态--这可能会导致意外和不良结果。从中得出的教训是:始终对您的数据执行合理性检查。 - oosterwal

35

如果必须是整数1或0,那么你现在的做法没问题,尽管括号不是必需的。如果这些a要用作布尔值,那么你可以直接这样做:

v = !v;

10
这样做会导致 Ollie 的“v”被设置为布尔值结果,而不是整数吗? - Brian
12
是的,它会生效,并且我在上面那行代码的句子中警告过了。如果这些要用作布尔值的话。 - Michael Berkowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接