确保函数永远不会两次返回相同的值[关闭]


23

这是我在求职面试中被问到的一个问题,我无法弄清楚他们正在寻找的答案,所以我希望这里的人可能有一些想法。目的是编写一个保证永远不会两次返回相同值的函数。假设该功能将被多台机器同时访问。

我的想法是为每台机器分配一个唯一的ID,并将该值传递到唯一的值生成器函数中:

var i = 0;
function uniq(process_id, machine_id) {
   return (i += 1).toString() + machine_id + "-" + process_id;
}

这将避免竞争条件带来的影响,因为即使两个或多个进程读取的相同值i,每个返回值都被标记为进程ID和计算机ID的唯一组合。但是,我的面试官不喜欢这个答案,因为使另一台计算机联机需要为其分配ID。

因此,有人能想到另一种解决此问题的方法,而不涉及将每台机器配置为具有唯一的ID吗?如果这个问题再次出现,我想得到一个答案。谢谢。


31
严格意义上的保证?我的意思是,甚至Guid都会在某个时候开始重复自己。我们可能不再生活了,但是可以保证..而且,顺便说一句,进程ID 绝不是唯一的
JensG

7
@CodesInChaos-这是一个非常糟糕的假设,因为在某些操作系统中更改您的mac地址很简单。
Telastyn 2014年

7
“假设该功能将同时被多台计算机访问”-老实说,这可能意味着“代码分别在每台计算机上运行,​​而各计算机之间没有通信”,或者“有一个中央计算机/中央数据库,该功能提供给其他可通过网络使用的机器”。您应该首先澄清这一点。
Doc Brown

28
这是一个棘手的问题吗?例如,含有一个无限循环的功能将永远不会返回相同的值的两倍..
布伦丹

8
也许他们正在寻找一个询问可疑需求的程序员,而不是做出假设并
付诸实践

Answers:


60

不要花哨,只需在一些通信端点(WCF,Web服务等)后面扔一个简单的(线程安全的)计数器即可:

   long x = long.MinValue;
   public long ID(){
       return Interlocked.Increment(ref x);
   }

是的,它最终会溢出。是的,它不处理重启。是的,它不是随机的。是的,有人可以在多个服务器上运行它。

这是满足实际要求的最简单的方法。然后让他们成为解决这些问题的人(以确保他们了解这些限制,他们是否真的认为您需要2 ^ 64个以上的id),因此您可以询问可以进行哪些取舍。它需要重启后才能生存吗?硬盘故障怎么办?核战争呢?是否需要是随机的?如何随机?


7
这是一个很好的答案,因为面试官从不问任何问题以获得直接答案。他们希望您给出一个可以证明您的决定合理的答案。如果您了解该领域,则只要有理由,几乎所有答案都将是合适的。

7
如果代码在不同的机器上运行(显然在不同的进程中),这应该如何工作?每个进程都有的不同副本x。而且我认为,如果没有解释您所考虑的联锁机制,这个答案就很模糊了。
布朗

7
@DocBrown“被多个计算机并发访问”似乎意味着多台计算机访问单个服务器上的单个功能。否则应写为“多台机器将同时运行此功能的副本”
Falco 2014年

3
@LightnessRacesinOrbit:我想这应该是C#和System.Threading.Interlocked提供原子增量的类。但是您也可以将其读为某种伪代码。
Doc Brown

3
如果我是问这个人的人,我会对这个提议感到非常不满。在不知道需求是什么的情况下开始实施某项工作是一个很大的危险信号。我希望你能问。
JensG 2014年

25

如果有人问我这个问题,并且他们清楚地表明它在重新启动和不同机器之间必须是唯一的,那么我将为他们提供一个函数,该函数调用创建新GUID的标准机制,无论发生在哪种情况下使用的语言。


v4 GUID的问题在于它们仅极有可能是唯一的,而不能保证是唯一的。实际上不是什么大问题,但是如果面试官从字面上看是不能满足要求的。
CodesInChaos 2014年

特别是,如果标准的GUID机制不能满足访问者的要求,则应弄清访问者与GUID普通用户之间的要求差异。一位明智的面试官问这样的问题(“您如何做<一些通常已知的标准事物,也许与通常的要求略有不同>”)应该从知道最新技术水平的候选人那里得到不同的答案。适用于从头开始发明某些东西的GUID和候选人。
史蒂夫·杰索普

假设有灵活的要求,这可能是最简单的答案。
2014年

9
+1,因为这基本上是引导解决的问题。不论其格式如何,制作重复的Guid都是地球上最困难的彩票。显然,许多人对碰撞的指数似然性没有任何感觉。
usr

3
哦,如果您为任何此类问题提供“使用标准功能”的答案,请期待后续问题“以及如何实现标准功能?”。您可能会很好地回答“我不知道,但是我肯定会查找它,而不是尝试发明一些东西”,这是一个完全准确的答案,完全无法维持预期的在面试条件下的不信任,你会永远什么重要的不先研究它;-)
史蒂夫·杰索普

22

面试官说,该方法将同时调用,而不是并行调用。只需将日期/时间返回到尽可能多的小数位即可。

为什么每个人都对此过度考虑?您将在很有限的时间里死掉很长一段时间,并且没有碰撞的机会。

如果您担心它会在同一时间返回,请添加可延迟的最小可测量时间。

如果您担心要为夏令时设置时钟(两次经历1次),请在第二次遇到该时间时添加一个常数。


12
或仅返回UTC时间,而不管请求者的时区如何。由于UTC尚未本地化,因此不会受到DST更改的影响。
毛罗2014年

1
System.currentTimeNanos():-)
Falco

1
除非您以易于理解的格式返回日期和时间,否则您的值中始终不应包含任何时区信息。
莫妮卡(Monica)

12
如果足够频繁/并发地调用,那么最短的时间仍将产生冲突。由于时钟同步漂移,恶意时钟操纵,以及如果您不小心-夏令时,它也会产生冲突。
Telastyn 2014年

1
至少很有创意。恕我直言,依靠时不时要调整的时钟仍然不是一个好主意。偏移量不会使您免于碰撞。
JensG 2014年

15

首先,您将要问面试官两个问题。


问题1

面试官是否期望一个或多个“中央机器”来分配某些唯一编号或唯一编号块。


问题2。

面试官是否期望一种碰撞检测机制,还是在没有明确检测到它们的情况下接受所计算出的微小碰撞机会的风险。

还有一种纵深防御方法,其中将用户ID的一部分合并到随机性中(因此,不是完全随机的)。因此,降低了同一用户在该同一用户创建的内容内发生冲突的机会。


有一个隐式问题3,...

但这是您必须不问自己就对自己进行衡量的一种方法,因为询问面试官是非常不礼貌的。

访调员是否假设掌握概率,风险知识以及密码和信息安全系统中采用的一些简单技术。

第一种知识可确保您不会试图说服非科学的人接受他们不会接受的科学概念。

第二种知识可确保您解决的问题不仅仅是概率。换句话说,如何通过操纵计算机或它们的虚拟主机来强制两台计算机生成相同的值,从而防御想要故意破坏您的随机化方案的“攻击者”。


为什么这么问。

原因是,如果面试官以一种或另一种方式期望,尝试以相反的方式回答将永远不会使面试官感到高兴。

更深层的原因是有些人不喜欢说想法1.0e-20,失败的机会。(在这里,我将尽量避免挑起哲学或宗教论点。)


首先,将随机数的“命名空间”划分为一个层次结构,将一定数量的位分配给一个随机化源,将其他数量的位分配给其他方式,等等。

集中式方法依赖于一些中央权限来唯一地分配第一级比特。然后,其他机器可以填充其余的位。

有几种分散的方法:

  • 只需生成一个尽可能好的随机数,并接受通过计算证明失败的机会几乎为零。
  • 使用加密方法从确定性源生成随机值,例如递增值。

我认为这是最好的答案。其他是没有要求的解决方案。
杰克·艾德利2014年

关于您的第三个问题-能力似乎是一个安全的假设,或者至少是一个不相关的假设。如果一家公司没有提供一名合格的面试官,则选拔过程中可能会存在更大的缺陷。如果他们这样做了,那么他/她将很欣赏这些问题。
2014年

1
为什么不能通过以下方式来解决“问题3”:“我们需要真正保证的唯一性,还是仅需要非常非常低的碰撞概率?” 并且,“这需要有多安全?我们是否需要假设攻击者将试图破坏该机制?我们关注哪种攻击?” 对这些问题的回答应阐明询问者是否理解这些问题以及他们的期望。
jpmc26 2014年

12

因此,请记住,这是一个面试问题,而不是实际的实际情况,我认为正确的方法(可能是面试官在寻找什么)是提出一个明确的问题,或者写上“它不能完成”,继续前进。这就是为什么。

采访者的要求:

写一个保证永远不会两次返回相同值的函数。假设该功能将被多台机器同时访问。

采访者需要什么:

该候选人是否有效评估需求并在需要时寻求其他投入?

永远不要假设。

当工程师(通过SOW或规范或其他一些需求文件)提交需求时,有些是不言而喻的,而另一些则完全不清楚。这是后者的完美示例。如先前的答案所示,如果不对(a)问题的性质或(b)关于系统的性质做出几个主要假设,就无法对这一要求做出回应,因为无法满足该要求照原样(不可能)。

大多数答案都通过一系列假设来尝试解决该问题。一个人特别建议尽快完成它,并让客户担心它是否有错。

这确实是一个糟糕的方法。作为客户,如果我提出了一个不清楚的要求,而工程师离开并为我建立了一个行不通的解决方案,那么我会为他们投入工作而花我的钱而又不打扰先问我而感到沮丧。这种轻率的决策表明缺乏团队合作精神,无法批判性思考和判断力差。它可能导致任何形式的负面后果,包括安全关键系统中的生命损失。

为什么问这个问题?

进行此练习的目的在于,要建立模棱两可的需求既昂贵又耗时。对于OP,您将获得一项不可能完成的任务。您的第一个动作应该是要求澄清-要求什么?需要什么程度的唯一性?如果值不唯一会怎样?这些问题的答案可能是几周时间和几分钟之间的差异。在现实世界中,复杂系统(包括许多软件系统)中最大的成本驱动因素之一就是不清楚的需求和难以理解的需求。如果项目足够大,这将导致昂贵且耗时的错误,重新设计,客户和团队的挫败感,以及令人尴尬的媒体报道。

承担时会发生什么?

考虑到我在航空航天行业的背景,并且由于航空航天故障的高度可见性,我想举这个领域的例子来说明重点。让我们检查一下一对失败的火星任务-火星气候轨道飞行器和火星极地着陆器。两次任务均由于软件问题而失败-因为工程师做出的无效假设部分是由于需求不明确且沟通不畅。

火星气候轨道器 -这种情况通常被引用为NASA尝试将英语转换为公制单位时发生的情况。但是,这是对真正发生的事情的过于简单和糟糕的表示。的确,存在转换问题,但这是由于在设计阶段沟通要求不高以及验证/验证方案不当所致。此外,当两名不同的工程师注意到该问题是因为从飞行轨迹数据中可以明显看出来时,他们没有将问题提出到适当的水平,因为他们认为这是传输错误。如果使任务行动小组知道了这个问题,那么有足够的时间来纠正它并保存任务。在这种情况下,存在一种不可能的逻辑条件,其根本无法识别,导致代价高昂的任务失败。

火星极地着陆器-这种情况鲜为人知,但由于其暂时靠近火星气候轨道器故障而更加尴尬。在此任务中,该软件控制了火箭的推进器辅助下降到火星表面。在距地面40米的位置,着陆器的腿部展开以准备着陆。腿上还有一个传感器,该传感器检测运动(在受到冲击时发出信号),以告知软件关闭发动机。NASA关于发生的事情的最佳猜测(由于存在多个可能的故障和不完整的数据)是腿部的振动由于腿部的同时部署和不适当地触发了地面40m以上的关闭机制,导致110美元坠毁和破坏。 M飞船。这种可能性是在开发中提出的,但从未得到解决。最终,软件团队对这段代码的运行方式做出了无效的假设(一种假设是,尽管测试显示相反的结果,但虚假信号的生命周期太短而无法被拾取),这些假设直到之后才被质疑。事实。

其他注意事项

面试和评估人是一件棘手的事情。面试官可能希望探索候选人的多个方面,但最重要的一个因素是个人的批判性思考能力。由于多种原因,尤其是批判性思维定义不清,我们很难评估批判性思维技能。

作为一名工程老师,我评价学生批判性思考能力的最喜欢的方法之一是提出一个模棱两可的问题。敏锐的学生会选择问题的前提,然后注意,并在给出前提的情况下回答或拒绝回答。通常,我会问类似以下的问题:

您可以从工作堆中拾取图纸。该图形包含各种不同的标注,但最重要的是指向水平面并说“完全平坦”。表面宽5英寸,长16英寸,部件由铝制成。您将如何加工零件以创建此功能?

(顺便说一句,您会对如此糟糕的规范在工作场所中出现的频率感到震惊。)

我希望学生会认识到不可能创造出完美的功能,并会在答案中说明这一点。如果他们说他们会回馈设计师并在制造零件之前要求澄清,我通常会奖励一个积分。如果学生继续告诉我他们将如何实现.001平面度或其他弥补的价值,我将给予零分。这有助于我向我的学生指出他们需要考虑更大的范围。

底线

如果要面试工程师(或类似专业),我正在寻找可以批判性思考并质疑摆在他面前的东西的人。我希望有人问“这有意义吗?” 。

要求完美的扁平零件是没有意义的,因为没有完美的零件。要求一个永远不返回重复值的函数是没有意义的,因为不可能做出这样的保证。在编程中,我们经常听到短语“垃圾进,垃圾出”。如果您对要求不满,则道德上的责任是停止并提出任何可以帮助您激发真正意图的问题。如果我正在面试候选人,并且给他们一个不清楚的要求,我将期待澄清问题。


5

由于计算机没有无限大的变量,因此很难保证唯一性。现实世界中没有图灵机。

我的看法是,这里有两个问题,两个都有完善的解决方案。

  • 并发。多台机器可能同时需要一个值。值得庆幸的是,现代CPU内置了并发功能,某些语言提供了开发人员友好的功能来利用此功能。
  • 独特性 虽然不可能保证唯一性,我们可以有可容纳值如此之大,一个真实的系统将有一个任意大型变量非常困难的时刻用尽所有的独特价值

这是我在Java中的解决方案:

public class Foo {
  private static BigInteger value = BigInteger.ZERO;
  private static final Lock lock = new ReentrantLock();

  public static BigInteger nextValue() {
    try {
      lock.lock();
      value = value.add(BigInteger.ONE);
      return value;
    }
    finally {
      lock.unlock();
    }
  }
}

BigInteger是任意大小的整数类型。它可以增长以容纳非常大的值,即使不是无限的。该锁可确保并发性,因此相同的值不能由单独的线程处理的两个同时请求返回两次。


我认为该代码将只使用不到500年的假设是一个有效的假设。如果仅在64位存储中返回递增值,则可以在相当长的一段时间内使用。在584555年内,每人给我们打电话1次。
Mooing Duck

1
至少在Java中,它是2 ^ 63个值(那么长的一半)。考虑到我们相互残杀的趋势,人类的生存时间可能会比人类更长。无论如何,我都采用了更理论的方法。实际上,64位(或63位)应该足够了。

1
@Snowman:什么?!?您的解决方案仅在25万年有效?!?!?下一个候选人!!!!!! :-)
鲍勃·贾维斯

0

我将通过服务器上的端口公开该功能;为了调用该函数,请求机器请求一个连接并被授予一个连接,同时为该机器分配一个标识码(为简单起见,为序号)。每当将消息发送到端口请求唯一值时,就通过将当前日期和时间的MD5哈希值与识别代码的MD5哈希值连接起来来生成该值。

如果他们想要更防弹的解决方案,则他们必须指定自己的实际要求,而不是一头雾水。


-1
string uniq(string machine_id) 
{
   static long u = long.MinValue;
   Interlocked.Increment(ref u);

   //Time stamp with millisecond precison
   string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff",
                                            CultureInfo.InvariantCulture);

   return machine_id + "-" + timestamp + "-" + u;
}

通过以上方法,即使重新启动或从其他计算机同时调用,也可以确保返回值是不同的。


程序员是关于概念性问题的,答案应能解释问题。抛出代码转储而不是进行解释就像将代码从IDE复制到白板一样:它看起来很熟悉,甚至有时是可以理解的,但是感觉很奇怪……只是很奇怪。白板没有编译器
gna14

感谢gnat指出来,我们会谨慎地从下次解释解决方案
techExplorer 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.