两个富翁打赌

by Kevin Ghadyani

通过凯文·加迪亚尼(Kevin Ghadyani)

打赌您无法解决这个Google面试问题。 (Bet you can’t solve this Google interview question.)

将棘手的问题分解为小块。 (Breaking tough problems into small pieces.)

I wanted to see someone else’s take on software engineering and started binge watching TechLead on YouTube. I spent the next few days coming up with various solutions to an interview question he asked while working at Google.

我想看看别人对软件工程的看法,并开始在YouTube上疯狂观看TechLead。 接下来的几天中,我针对他在Google工作期间提出的面试问题提出了各种解决方案。

这部影片让我兴奋 (This Video Got Me Excited)

TechLead brought up a question he asked in over 100 interviews at Google. It got me curious to think up a solution in RxJS. This article’s going to go over traditional methods though.

TechLead Google的 100多次采访中提出了一个问题 。 我很想知道RxJS中的解决方案。 不过,本文将介绍传统方法。

The real goal of his question is to get information from the interviewee. Will they ask the right questions before coding? Is the solution going to fit into the guidelines of the project? He even notes it doesn’t matter at all if you get the right answer. He wants to figure out how you think and if you can even understand the problem.

他的问题的真正目的是从受访者那里获取信息。 他们在编码之前会问正确的问题吗? 解决方案是否适合该项目的准则? 他甚至指出,如果您得到正确的答案,那根本不重要。 他想弄清楚您的想法以及您是否可以理解问题。

He talked about a few solutions, one that was recursive (limited by stack size), another that was iterative (limited by memory size). We’ll be looking into both of these and more!

他谈到了一些解决方案,一种是递归的(受堆栈大小限制),另一种是迭代的(受内存大小限制)。 我们将研究这些以及更多内容!

TechLead的问题 (TechLead’s Question)

In his question, he asks us to take this grid of items and get the count of the largest contiguous block where all colors are the same.

在他的问题中,他要求我们采用该项目网格并获取所有颜色相同的最大连续块的计数。

When I heard his question and saw the picture, I was thinking “oh man, I’ve gotta do some 2D image modeling to figure this out”. Sounds near-impossible to answer during an interview.

当我听到他的问题并看到图片时,我在想“哦,老兄,我得做一些2D图像建模才能弄清楚这一点”。 在面试中听起来几乎不可能回答。

But after he explained it more, that’s really not the case. You’re processing the data that’s already been captured, not parsing an image. I realize now, the image is actually a misnomer.

但是在他进一步解释之后,事实并非如此。 您正在处理已经捕获的数据,而不是解析图像。 我现在知道,该图像实际上是错误的名称。

资料建模 (Data Modeling)

Before you write any code, you need to define your data model. I can’t stress this enough. Before coding anything this advanced, first figure out what you’re working with and gather business requirements.

在编写任何代码之前,您需要定义数据模型。 我不能太强调这一点。 在对这种高级功能进行编码之前,首先要弄清楚您正在使用什么并收集业务需求。

In our case, TechLead defined a lot of those requirements for us:

在我们的案例中,TechLead为我们定义了许多这些要求:

  • Concept of a colored square or “node” as we’ll call it.我们将其称为彩色正方形或“节点”的概念。
  • There are 10K nodes in our dataset.我们的数据集中有1万个节点。
  • Nodes are organized into columns and rows (2D).节点被组织为列和行(2D)。
  • The number of columns and rows can be uneven.列和行的数量可以不均匀。
  • Nodes have colors and some way to denote adjacencies.节点具有颜色和某种表示邻接的方式。

We can also derive some more information from our data:

我们还可以从数据中获取更多信息:

  • No two nodes will overlap.没有两个节点会重叠。
  • Nodes will never be adjacent to themselves.节点永远不会与自己相邻。
  • A node will never have duplicate adjacencies.节点永远不会有重复的邻接关系。
  • Nodes that reside on the sides and corners will be missing one or two adjacencies respectively.位于侧面和角落的节点将分别缺少一两个相邻关系。

What we don’t know:

我们不知道的是:

  • The ratio of rows to columns行与列之比
  • The number of possible colors.可能的颜色数。
  • The chances of having only 1 color.只有一种颜色的机会。
  • The rough distribution of colors.颜色的粗略分布。

The higher level you are as a developer, the more of these questions you’ll know to ask. It’s not a matter of experience either. While that helps, it doesn’t make you any better if you can’t pick out the unknowns.

作为开发人员的级别越高,您将要问的问题越多。 这也不是经验问题。 虽然这样做有帮助,但是如果您不能挑出未知数,也不会使您变得更好。

I don’t expect most people to pick out these unknowns. Until I started working the algorithm in my head, I didn’t know them all either. Unknowns take time to figure out. It’s a lot of discussion and back-and-forth with business people to find all the kinks.

我不希望大多数人会发现这些未知数。 直到我开始脑子里工作算法,我也不全都知道。 未知数需要时间才能弄清楚。 与商人进行大量讨论和来回查找所有问题。

Looking at his image, it appears as though the distribution is random. He used only 3 colors and never said anything otherwise, so we will too. We’ll also assume there’s a likelihood all colors will be the same.

查看他的图像,似乎分布是随机的。 他只使用了3种颜色,并且从不说其他任何东西,所以我们也一样。 我们还将假设所有颜色都可能相同。

Since it could kill our algorithm, I’m going to assume we’re working with a 100x100 grid. That way, we don’t have to deal with the odd cases of 1 row and 10K columns.

由于它可能杀死我们的算法,因此我假设我们正在使用100x100的网格。 这样,我们不必处理1行和10K列的奇数情况。

In a typical setting, I would ask all these questions within the first few hours of data-discovery. That’s what TechLead really cared about. Are you gonna start by coding a random solution or are you going to figure out the problem?

在典型的情况下,我会在数据发现的最初几个小时内问所有这些问题。 这就是TechLead真正关心的问题。 您将从编码随机解决方案开始还是要找出问题所在?

You’re going to make mistakes in your data model. I know I did when first writing this article, but if you plan ahead, those issues will be a lot easier to manage. I ended up having to only rewrite small portions of my code because of it.

您将在数据模型中犯错误。 我知道我第一次写这篇文章的时候就做过,但是如果您提前计划的话,这些问题将更容易处理。 因此,我最终只需要重写代码的一小部分。

创建数据模型 (Creating the Data Model)

We need to know how data is coming in and in what format we want to process it.

我们需要知道数据如何传入以及以何种格式处理。

Since we don’t have a system in place for processing the data, we need to come up with a visualization ourselves.

由于我们没有适当的系统来处理数据,因此我们需要自己进行可视化处理。

The basic building blocks of our data:

我们数据的基本构建块:

  • Color颜色
  • IDID
  • XX
  • Yÿ

Why do we need an ID? Because we could come across the same item more than once. We want to prevent infinite loops so we need to mark where we’ve been in those cases.

为什么我们需要一个ID? 因为我们可以多次遇到同一项目。 我们要防止无限循环,因此我们需要标记在这些情况下的去向。

Also, data like this will typically be assigned some sort of ID, hash, or other value. It’s a unique identifier so we have some way to identify that particular node. If we want to know the largest contiguous block, we need to know which nodes are in that block.

同样,通常会为此类数据分配某种ID,哈希或其他值。 这是一个唯一的标识符,因此我们可以通过某种方式来标识该特定节点。 如果我们想知道最大的连续块,则需要知道该块中有哪些节点。

Since he shaped out the data in a grid, I’m going to assume we’ll get it back with X and Y values. Using just those properties, I was able to generate some HTML to ensure what we’re generating looks like what he’s given us.

由于他将数据整理成网格状,因此我假设我们将使用X和Y值将其取回。 仅使用这些属性,我就可以生成一些HTML以确保我们生成的内容看起来像他给我们的一样。

This was done using absolute positioning just like his example:

就像他的例子一样,使用绝对定位来完成:

It even works with a larger dataset:

它甚至适用于更大的数据集:

He’s the code which generates the nodes:

他是生成节点的代码:

We take our columns and rows, create a 1D array out of the number of items, then generate our nodes off that data.

我们取出列和行,从项目数量中创建一维数组,然后根据该数据生成节点。

Instead of a color, I’m using colorId. First, because it’s cleaner to randomize. Second, we’d usually have to look up the color value ourselves.

我使用的是colorId ,而不是color 。 首先,因为随机化比较干净。 其次,我们通常必须自己查询颜色值。

While he never explicitly stated it, he only used 3 color values. I’m limiting our dataset to 3 colors as well. Just know it could be hundreds of colors and the final algorithms wouldn’t need to change.

尽管他从未明确声明,但他仅使用了3种颜色值。 我也将数据集限制为3种颜色。 只需知道这可能是数百种颜色,并且最终算法不需要更改。

As a simpler example, here’s a 2x2 nodes list:

作为一个简单的示例,这是一个2x2节点列表:

数据处理 (Data Processing)

No matter which method we’re going to use, we want to know the adjacencies for each of these nodes. X and Y values aren’t going to cut it.

无论我们要使用哪种方法,我们都想知道每个节点的邻接关系。 X和Y值不会减少它。

So given an X and Y, we need to figure out how to find adjacent X and Y values. It’s pretty simple though. We simply find nodes plus and minus 1 on both X and Y.

因此,给定X和Y,我们需要弄清楚如何找到相邻的X和Y值。 这很简单。 我们只需在X和Y上找到正负1的节点即可。

I wrote a helper function for that piece of the logic:

我为此逻辑编写了一个辅助函数:

The way we’re generating nodes, there’s actually a mathematical way to figure out the IDs of adjacent nodes. Instead, I’m going to assume nodes will come into our system in random order.

我们生成节点的方式实际上是一种数学方法来找出相邻节点的ID。 相反,我将假定节点将以随机顺序进入我们的系统。

I ran all nodes through a second pass to add adjacencies:

我对所有节点进行了第二遍添加邻接关系:

I’ve avoided making any unnecessary optimizations in this preprocessor code. It won’t affect our final performance stats and will only help to simplify our algorithms.

我避免在此预处理程序代码中进行任何不必要的优化。 它不会影响我们的最终性能数据,只会帮助简化算法。

I went ahead and changed the colorId into a color. It’s completely unnecessary for our algorithms, but I wanted to make it easier to visualize.

我继续将colorId更改为color 。 对于我们的算法而言,这完全没有必要,但我想使其更易于可视化。

We call getNodeAtLocation for each set of adjacent X and Y values and find our northId, eastId, southId, and westId. We don’t pass on our X and Y values since they’re no longer required.

我们为每组相邻的X和Y值调用getNodeAtLocation ,并找到我们的northIdeastIdsouthIdwestId 。 由于不再需要X和Y值,因此我们不会传递它们。

After getting our cardinal IDs, we convert them to a single adjacentIds array which includes only those that have a value. That way, if we have corners and sides, we don’t have to worry about checking if those IDs are null. It also allows us to loop an array instead of manually noting each cardinal ID in our algorithms.

让我们基数的ID后,我们将它们转换为单个adjacentIds阵列只包括那些具有价值。 这样,如果我们有边角,就不必担心检查这些ID是否为空。 它还允许我们循环一个数组,而不是手动注释算法中的每个基本ID。

Here’s another 2x2 example using a new set of nodes run through addAdjacencies:

这是另一个2x2的示例,该示例使用通过addAdjacencies运行的一组新节点:

预处理优化 (Preprocessing Optimizations)

I wanted to simplify the algorithms for this article greatly, so I added in another optimization pass. This one removes adjacent IDs that don’t match the current node’s color.

我想大大简化本文的算法,因此添加了另一个优化过程。 这将删除与当前节点的颜色不匹配的相邻ID。

After rewriting our addAdjacencies function, this is what we have now:

重写addAdjacencies函数之后,现在是这样的:

I slimmed down addAdjacencies while adding more functionality.

我减少了addAdjacencies同时添加了更多功能。

By removing nodes that don’t match in color, our algorithm can be 100% sure any IDs in the adjacentIds prop are contiguous nodes.

通过删除颜色不匹配的节点,我们的算法可以100%确保adjacentIds ID道具中的任何ID是连续的节点。

Lastly, I removed any nodes that don’t have same-color adjacencies. That simplifies our algorithms even more, and we’ve shrunk the total nodes to only those we care about.

最后,我删除了没有相同颜色邻接关系的所有节点。 这进一步简化了我们的算法,并且将总节点缩减为仅关心的节点。

错误的方式—递归 (The Wrong Way — Recursion)

TechLead stated we couldn’t do this algorithm recursively because we’d hit a stack overflow.

TechLead表示我们无法递归执行此算法,因为我们遇到了堆栈溢出的情况。

While he’s partly correct, there are a few ways to mitigate this problem. Either iterating or using tail recursion. We’ll get to the iterative example, but JavaScript no longer has tail recursion as a native language feature.

尽管他部分正确,但有几种方法可以缓解此问题。 迭代或使用尾递归。 我们将转到迭代示例,但是JavaScript不再具有尾部递归作为本机语言功能。

While we can still simulate tail recursion in JavaScript, we’re going to keep this simple and create a typical recursive function instead.

尽管我们仍然可以在JavaScript中模拟尾部递归,但我们将保持这种简单性,而是创建一个典型的递归函数。

Before we hit the code, we need to figure out our algorithm. For recursion, it makes sense to use a depth-first search. Don’t worry about knowing the computer science term. A coworker said it when I was showing him the different solutions I came up with.

在编写代码之前,我们需要弄清楚我们的算法。 对于递归,使用深度优先搜索是有意义的。 不必担心知道计算机科学术语。 当我向他展示我想出的不同解决方案时,一位同事说过。

算法 (The Algorithm)

We’ll start with a node and go as far as we can until we’ve hit an endpoint. Then we’ll come back and take the next branching path until we’ve scanned the entire contiguous block.

我们将从节点开始,并尽我们所能直到到达终点。 然后,我们将返回并采用下一个分支路径,直到我们扫描了整个连续块。

That’s part of it. We also have to keep track of where we’ve been and the length of the largest contiguous block.

这就是其中的一部分。 我们还必须跟踪我们去过的地方以及最大的连续块的长度。

What I did was divide our function into 2 pieces. One would hold the largest list and previously scanned IDs while looping every node at least once. The other would start at an unscanned root node and do our depth-first traversal.

我所做的就是将我们的功能分为两部分。 一个将拥有最大的列表和先前扫描的ID,同时至少每个节点循环一次。 另一个将从未扫描的根节点开始,然后进行深度优先遍历。

Here’s what those functions look like:

这些功能如下所示:

Insane right? I even debated showing the code because it gets so gnarly.

疯了吧? 我什至争论过显示代码,因为它太粗糙了。

To slim this down, let’s go step-by-step.

为了减少这种负担,让我们逐步进行。

递归函数 (The Recursive Function)

getContiguousIds is our recursive function. This gets called once for each node. Each time it returns, you get an updated list of contiguous nodes.

getContiguousIds是我们的递归函数。 每个节点调用一次。 每次返回时,您都会获得更新的连续节点列表。

There is only one condition in this function: is our node already in the list? If not, call getContiguousIds again. When it returns, we’ll have an updated list of contiguous nodes which returns to our reducer and used as the state for the next adjacentId.

此功能只有一个条件: 列表中是否已存在我们的节点? 如果不是,请再次调用getContiguousIds 。 当它返回时,我们将有一个更新的连续节点列表,该列表将返回到化简器,并用作下一个adjacentId ID的状态。

You might be wondering where we’re adding values to contiguousIds. That happens when we concat the current node onto contiguousIds. Each time we recurse further, we’re making sure to add the current node onto the list of contiguousIds before looping its adjacentIds.

您可能想知道我们在哪里向contiguousIds添加值。 当这种情况发生,我们concat当前节点上contiguousIds 。 每次我们进一步递归的时候,我们正在确认当前节点添加到列表中contiguousIds循环的前adjacentIds

Always adding the current node ensures we don’t infinitely recurse.

始终添加当前节点可确保我们不会无限递归。

循环 (The Loop)

The second half of this function also goes through each node once.

此功能的后半部分还将遍历每个节点一次。

We have reducer surrounding the recursive function. This one checks if our code’s been scanned. If so, keep looping until we find a node that hasn’t or until we fall out of the loop.

我们在递归函数周围有reducer。 这个检查我们的代码是否已被扫描。 如果是这样,请继续循环直到找到一个尚未存在的节点,或者直到退出循环为止。

If our node hasn’t been scanned, call getContiguousIds and wait till it’s done. This is synchronous, but it can take some time.

如果尚未扫描我们的节点,请调用getContiguousIds并等待其完成。 这是同步的,但可能需要一些时间。

Once it comes back with a list of contiguousIds, check those against the largestContiguousIds list. If larger, store that value.

一旦返回了contiguousIds列表,请对照largestContiguousIds列表进行检查。 如果更大,则存储该值。

At the same time, we’re going to add those contiguousIds to our scannedIds list to mark where we’ve been.

同时,我们将这些contiguousIds添加到scannedIds列表中,以标记我们去过的地方。

It’s pretty simple when you see it all laid out.

当您看到所有布局时,这非常简单。

执行 (Execution)

Even with 10K items, it didn’t run into stack overflow issues with 3 randomized colors. If I changed everything to use a single color, I was able to run into a stack overflow. That’s because our recursive function was going through 10K recursions.

即使有1万个项目,它也不会遇到3种随机颜色的堆栈溢出问题。 如果我将所有内容更改为仅使用一种颜色,就会遇到堆栈溢出的情况。 那是因为我们的递归函数要进行10K递归。

顺序迭代 (Sequential Iteration)

Since memory is larger than the function call stack, my next thought was doing the whole thing in a single loop.

由于内存大于函数调用堆栈,因此我的下一个想法是在单个循环中完成整个操作。

We’re going to keep track of a list of node lists. We’ll keep adding to them and linking them together until we fall out of the loop.

我们将跟踪节点列表的列表。 我们将继续添加它们并将它们链接在一起,直到脱离循环为止。

This method requires we keep all possible node lists in memory until we’ve completed the loop. In the recursive example, we only kept the largest list in memory.

此方法要求我们将所有可能的节点列表保留在内存中,直到完成循环。 在递归示例中,我们仅在内存中保留了最大的列表。

Another crazy one. Let’s break this down from the top. We’re looping each node once. But now we have to check if our id is in the list of node lists: contiguousIdsList.

另一个疯狂的。 让我们从顶部开始进行分解。 我们将每个节点循环一次。 但是现在我们必须检查我们的id是否在节点列表列表中: contiguousIdsList

If it’s not in any list of contiguousIds, we’ll add it and its adjacentIds. That way, while looping, something else will link up to it.

如果它不在任何contiguousIds列表中,则将其及其adjacentIds ID添加。 这样,在循环时,其他东西将链接到它。

If our node is in one of the lists, it’s possible it’s in quite a few of them. We want to link all those together, and remove the unlinked ones from the contiguousIdsList.

如果我们的节点在列表之一中,则可能在其中很多列表中。 我们希望将所有这些链接在一起,并从contiguousIdsList删除未链接的链接。

That’s it.

而已。

After we’ve come up with a list of node lists, then we check which one’s the largest, and we’re done.

在提出节点列表列表之后,我们检查哪个节点列表最大,然后完成。

执行 (Execution)

Unlike the recursive version, this one does finish when all 10K items are the same color.

与递归版本不同,当所有10K项都具有相同的颜色时,此函数确实会完成。

Other than that, it’s pretty slow; much slower than I originally expected. I’d forgot to account for looping the list of lists in my performance estimates, and that clearly had an impact on performance.

除此之外,它还很慢。 比我最初预期的要慢得多。 我忘了在性能评估中考虑循环列表列表,这显然会对性能产生影响。

随机迭代 (Random Iteration)

I wanted to take the methodology behind the recursive method and apply it iteratively.

我想将方法​​学放在递归方法的后面,并迭代地应用它。

I spent the good portion of a night trying to remember how to change the index in the loop dynamically and then I remembered while(true). It’s been so long since I’ve written traditional loops, I’d completely forgotten about it.

我花了一整夜的时间试图记住如何动态更改循环中的索引,然后想起了while(true) 。 自从我写了传统的循环已经很久了,我完全忘记了它。

Now that I’d had my weapon, I moved in for the attack. As I spent a lot of time trying to speed up the observable versions (more on that later), I decided to go in lazy and old-school-mutate the data.

现在我有了武器,就搬进去了。 由于我花了很多时间试图加快可观察版本的速度(稍后会详细介绍),因此我决定将数据懒散地进行老式修改。

The goal of this algorithm was to hit each node exactly once and only store the largest contiguous block:

该算法的目标是精确击中每个节点一次,并且仅存储最大的连续块:

Even though I wrote this like most people probably would, it’s by far the least readable. I can’t even tell you what it’s going without going through it myself first top-to-bottom.

即使我像大多数人一样写了这篇文章,但到目前为止,它的可读性最差。 我什至无法告诉你这是怎么回事,除非我自己从上到下进行。

Instead of adding to a list of previously scanned IDs, we’re splicing out values from our remainingNodes array.

而不是添加到先前扫描的ID的列表中,我们是从remainingNodes数组中拼接出值。

Lazy! I wouldn’t ever recommend doing this yourself, but I was at the end of my rope creating these samples and wanted to try something different.

懒! 我永远不会建议自己这样做,但是我在创建这些样本的最后阶段想尝试一些不同的尝试。

细目分类 (The Breakdown)

I broke this out into 3 sections separated by if blocks.

我将其分为3个部分,中间用if块分隔。

Let’s start with the middle section. We’re checking for queuedIds. If we have some, we do another loop through the queued items to see if they’re in our remainingNodes.

让我们从中间部分开始。 我们正在检查queuedIds 。 如果有的话,我们将对排队的项目进行另一个循环,以查看它们是否在remainingNodes

In the third section, it depends on the results of the second section. If we don’t have any queuedIds, and remainingNodesIndex is -1, then we’re done with that node list and we need to start at a new root node. The new root node is always at index 0 because we’re splicing our remainingNodes.

在第三部分中,它取决于第二部分的结果。 如果我们没有任何queuedIds ,而remainingNodesIndex节点queuedIds-1 ,那么我们已经完成了该节点列表,并且需要从一个新的根节点开始。 新的根节点始终位于索引0因为我们正在拼接remainingNodes

Back at the top of our loop, I could’ve used while(true), but I wanted a way out in case something went wrong. This was helpful while debugging since infinite loops can be a pain to figure out.

回到循环的顶部,我可以使用while(true) ,但是我想找到一条出路,以防出现问题。 这在调试时很有用,因为无限循环可能很难解决。

After that, we’re splicing out our node. We’re adding it to our list of contiguousIds, and adding the adjacentIds onto the queue.

之后,我们将拼接节点。 我们将它添加到我们的列表contiguousIds ,并添加adjacentIds到队列中。

执行 (Execution)

This ended up being nearly as fast as the recursive version. It was the fastest of all algorithms when all nodes are the same color.

最终这几乎与递归版本一样快。 当所有节点都是相同的颜色时,这是所有算法中最快的。

特定于数据的优化 (Data-Specific Optimizations)

分组相似的颜色 (Grouping Similar Colors)

Since we know only blues go with blues, we could’ve grouped similarly colored nodes together for the sequential iteration version.

因为我们知道只有蓝色与蓝色一起出现,所以我们可以将颜色相似的节点分组在一起以进行顺序迭代。

Splitting it up into 3 smaller arrays lowers our memory footprint and the amount of looping we need to do in our list of lists. Still, that doesn’t solve the situation where all colors are the same so this wouldn’t fix our recursive version.

将其分成3个较小的数组可以减少内存占用,并减少列表列表中需要执行的循环次数。 但是,这并不能解决所有颜色都相同的情况,因此无法解决我们的递归版本。

It also means we could multi-thread the operation, lowering the execution time by nearly a third.

这也意味着我们可以对操作进行多线程处理,从而将执行时间减少了近三分之一。

If we execute these sequentially, we just need to run the largest of the three first. If the largest is larger than the other two, you don’t need to check them.

如果我们按顺序执行这些命令,则只需要首先运行三个命令中的最大一个即可。 如果最大的大于其他两个,则无需检查它们。

可能的最大尺寸 (Largest Possible Size)

Instead of checking if we have the largest list at certain intervals, we could be checking each iteration.

可以检查每个迭代,而不是检查是否在特定时间间隔内拥有最大的列表。

If the largest set is greater than or equal to half the available nodes (5K or higher), it’s obvious we already have the largest.

如果最大的集合大于或等于可用节点的一半(5K或更高),则很明显我们已经拥有最大的集合。

Using the random iteration version, we could find the largest list size so far and see how many nodes are remaining. If there are less than the size of the largest, we’ve already got the largest.

使用随机迭代版本,我们可以找到到目前为止最大的列表大小,并查看剩余的节点数。 如果小于最大的大小,则我们已经拥有最大的大小。

使用递归 (Use Recursion)

While recursion has its limitations, we can still use it. All we have to do is check the number of remaining nodes. If it’s under the stack limit, we can switch to the faster recursive version. Risky, but it’d definitely improve the execution time as you got further along in your loop.

尽管递归有其局限性,但我们仍然可以使用它。 我们要做的就是检查剩余节点的数量。 如果低于堆栈限制,我们可以切换到更快的递归版本。 冒险,但是随着循环的进行,它肯定会缩短执行时间。

使用`for`循环 (Use a `for` Loop)

Since we know our max item count, there’ll be a minor benefit from switching the reduce function to a traditional for loop.

由于我们知道最大项目数,因此将reduce函数切换为传统的for循环会有一个次要的好处。

For whatever reason, Array.prototype methods are incredibly slow compared to for loops.

无论出于什么原因, 与for循环相比 , Array.prototype方法的速度都非常慢 。

使用尾递归 (Use Tail Recursion)

In the same way, I didn’t go over the observable versions in this particular article, I think tail recursion requires an article of its own.

同样,我没有在这篇特定的文章中介绍可观察的版本,我认为尾部递归需要自己的文章。

It’s a big topic with a lot to explain, but while it would allow the recursive version to run, it might not end up being faster than the while loop like you’d expect.

这是一个很大的话题,需要解释很多,但是尽管它允许递归版本运行,但最终可能不会比您期望的while循环更快。

RxJS:可维护性与性能 (RxJS: Maintainability vs Performance)

There are ways to rewrite these functions where you’ll have an easier time comprehending and maintaining them. The primary solution I came up with used RxJS in the Redux-Observable style, but without Redux.

有多种方法可以重写这些功能,从而使您可以更轻松地理解和维护它们。 我想到的主要解决方案是使用Redux-Observable风格的RxJS,但没有Redux。

That was actually my challenge for the article. I wanted to code the regular ways, then stream the data using RxJS to see how far I could push it.

这实际上是我对本文的挑战。 我想以常规方式编写代码,然后使用RxJS传输数据以查看将数据推送多远。

I made 3 versions in RxJS and took a few liberties to speed up the execution time. Unlike my transducer articles, all three wound up slower even if I increased rows and columns.

我在RxJS中制作了3个版本,并花了一些时间来加快执行时间。 与我的换能器文章不同,即使我增加了行数和列数,这三者的缠绕速度也变慢。

I spent my nights that week dreaming up possible solutions and combing over every inch of code. I’d even lie on the ground, close my eyes, and think think think. Each time, I came up with better ideas but kept hitting JavaScript speed limitations.

那一周我整夜都在做梦,想出可能的解决方案,梳理每英寸的代码。 我什至躺在地上,闭上眼睛,然后想想。 每次,我想出了更好的主意,但都遇到了JavaScript速度限制。

There’s a whole list of optimizations I could’ve done, but at the cost of code readability. I didn’t want that (still used one anyway).

我可以完成全部优化,但要牺牲代码的可读性。 我不想要那个(还是还是用了一个)。

I finally got one of the observable solutions — now the fastest — running in half the time. That was the best improvement overall.

我终于得到了一种可以观察到的解决方案(现在是最快的解决方案),运行了一半的时间。 这是总体上最好的改进。

The only time I could beat out the memory-heavy sequential iteration with observables was when every node is the same color. That was the only time. Technically that beats the recursive one too because it stack overflows in that scenario.

我唯一能用可观察的方法击败大量内存的顺序迭代的时间是每个节点的颜色相同。 那是唯一的时间。 从技术上讲,这也优于递归方法,因为在这种情况下,它的堆栈会溢出。

After all that work figuring out how to stream the data with RxJS, I realized it’s way too much for this one article. Expect a future article to go over those code examples in detail.

在完成所有工作,弄清楚如何使用RxJS传输数据之后,我意识到这篇文章太过分了。 希望以后的文章详细介绍这些代码示例。

If you want to see the code early, you can see it on GitHub:https://github.com/Sawtaytoes/JavaScript-Performance-Interview-Question

如果您想及早查看代码,可以在GitHub上查看: https : //github.com/Sawtaytoes/JavaScript-Performance-Interview-Question

最终统计 (Final Stats)

In general, the largest contiguous block was anywhere from 30–80 nodes on average.

通常,最大的连续块平均在30-80个节点之间。

These are my numbers:

这些是我的电话号码:

No matter how many times I ran the tests, the relative positions of each method remained the same.

无论我运行了多少次测试,每种方法的相对位置都保持不变。

The Redux-Observable Concurrent method suffered when all nodes were the same color. I tried a bunch of things to make it faster, but nothing worked :/.

当所有节点都是相同的颜色时,Redux-Observable并发方法会受到影响。 我尝试了很多方法使它更快,但没有任何效果:/。

游戏开发 (Game Development)

I’ve come across this code twice in my career. It was at a much smaller scale, in Lua, and happened while working on my indie game Pulsen.

在我的职业生涯中,我两次遇到此代码。 在Lua,它的规模要小得多,并且是在开发我的独立游戏Pulsen时发生的 。

In one situation, I was working on a world map. It had a predefined list of nodes, and I processed this list in real-time. This allowed hitting [LEFT], [RIGHT], [UP], and [DOWN] to move you around the world map even if the angle was slightly off.

在一种情况下,我正在制作世界地图。 它具有预定义的节点列表,我可以实时处理此列表。 即使角度稍微偏离,也可以按[LEFT][RIGHT][UP][DOWN]在世界地图上移动。

I also wrote a node generator for an unknown list of items with X and Y values. Sound familiar? I also had to center that grid on the screen. It was a heckuva lot easier to do this in HTML than it was in the game engine though. Although, centering a bunch of absolutely-positioned divs isn’t easy either.

我还为带有X和Y值的未知项目列表编写了一个节点生成器。 听起来有点熟? 我还必须将网格放在屏幕中央。 用HTML做到这一点比在游戏引擎中要容易得多。 虽然,将一堆绝对定位的div居中也不容易。

In both of these solutions, the real-time execution time wasn’t a big deal because I did a lot of preprocessing when you loaded up the game.

在这两种解决方案中,实时执行时间都没什么大不了的,因为在您加载游戏时我做了很多预处理。

I want to emphasize that TechLead’s question might be something you come across in your career; maybe, but it’s rare that speed is ever a factor in typical JavaScript applications.

我想强调,TechLead的问题可能是您在职业生涯中遇到的; 也许,但是在典型JavaScript应用程序中,速度从来都不是一个重要因素。

Based on TechLeads other videos, he was using Java at Google. I’m assuming the positions he interviewed cared about execution speed. They probably had a bunch of worker tasks processing huge chunks of data so a solution like this might’ve been necessary.

根据TechLeads的其他视频,他在Google使用Java。 我假设他采访的职位关心执行速度。 他们可能有一堆处理大量数据的工作者任务,因此可能需要这样的解决方案。

But then, it could’ve been a job working on HTML and CSS, and he was just trolling the interviewee; who knows!

但是,那可能是处理HTML和CSS的工作,而他只是在诱骗受访者。 谁知道!

结论 (Conclusion)

As you saw with the final stats, the worst-looking code was almost the fastest and accomplished all our requirements. Good luck maintaining that sucker!

正如您在最终统计数据中看到的那样,外观最差的代码几乎是最快的,并且可以满足我们的所有要求。 祝你好运,保持那个吸盘!

From my own experience, I spent longer developing the non-RxJS versions. I think it’s because the faster versions required thinking holistically. Redux-Observable allows you to think in small chunks.

根据我自己的经验,我花了更长的时间开发非RxJS版本。 我认为这是因为更快的版本需要整体考虑。 Redux-Observable允许您一小部分地思考。

This was a really fun and frustrating problem to solve. It seemed really difficult at first, but after breaking it up into pieces, the pieces all came together :).

这是一个非常有趣且令人沮丧的问题。 起初看起来确实很困难,但是将其分解成碎片后,所有碎片都汇集在一起​​了:)。

更多阅读 (More Reads)

If you wanna read more on JavaScript performance, check out my other articles:

如果您想了解有关JavaScript性能的更多信息,请查看我的其他文章:

  • Speed Up JavaScript Array Processing

    加速JavaScript数组处理

  • Using Transducers to Speed Up JavaScript Arrays

    使用换能器加速JavaScript数组

If you liked what you read, please check out my other articles on similar eye-opening topics:

如果您喜欢所阅读的内容,请查看我的其他文章,这些文章涉及类似的令人惊讶的话题:

  • Callbacks: The Definitive Guide

    回调:权威指南

  • Promises: The Definitive Guide

    承诺:权威指南

  • Feature Flags: Be Truly Agile

    功能标志:真正敏捷

  • Make Poop from Food Emojis

    用食物表情符号制作便便

翻译自: https://www.freecodecamp.org/news/bet-you-cant-solve-this-google-interview-question-4a6e5a4dc8ee/

两个富翁打赌

两个富翁打赌_打赌您无法解决这个Google面试问题。相关推荐

  1. 亚马逊面试有几轮_经过几个月的Google面试准备,我被亚马逊录用

    亚马逊面试有几轮 by Googley as Heck 由Googley饰演Heck 经过几个月的Google面试准备,我被亚马逊录用 (After months of preparing for t ...

  2. 两个链接合并_如何找到两个链接列表的合并点

    两个链接合并 了解问题 (Understand the Problem) We are given two singly linked lists and we have to find the po ...

  3. 综合能源系统优化 数据来源《考虑需求响应的社区综合能源系统两阶段优化调度_刘蓉晖》

    综合能源系统优化 数据来源<考虑需求响应的社区综合能源系统两阶段优化调度_刘蓉晖> %% 风电+储能+电网交易+燃气轮机+燃气锅炉+电制冷机+(%燃料电池FC+溴化锂制冷机LBR+余热锅炉 ...

  4. 两次被裁之后,我终于解决了数据库缓存一致性问题

    一 我是一名毕业两年的程序员. 算上实习,工作三年了,正是一个程序员的黄金时代,这让我普通而自信. 但是从实习期,我就被辞退两次了. 今天是我的又一场面试,而且是大厂面试.我要一洗前耻,证明自己. 好 ...

  5. 关于git中,两个branch自动同步的问题和解决

    关于git中,两个branch自动同步的问题和解决 1问题描述 2问题原因 3解决方法 1问题描述 在git中,如果在一个branch中对代码进行了修改,切不做任何操作直接checkout另一个bra ...

  6. pythonr语言三种基本结构_如何轻松搞定数据科学面试:Python&R语言篇

    作者: Carson Forter 编译: Mika本文为 CDA 数据分析师原创作品,转载需授权 对于数据科学家来说,工作的一大部分都需要在交互式编程环境中对数据进行处理.分析和可视化. 在过去几年 ...

  7. io静态功耗_如何使用Fast.io从Google云端硬盘直接在3分钟内免费部署静态网站

    io静态功耗 In this article, I'll show you how to deploy a static website for free in only 3 minutes, usi ...

  8. 约战精灵再临服务器维护,约战精灵再临进不了游戏怎么办_无法进入游戏解决方法...

    约战精灵再临进不了游戏怎么办?相信有小伙伴遇到了游戏开服后进不了游戏等各种情况,这些情况往往重启游戏也解决不了,比较棘手,本篇就为大家带来约战精灵再临进不了游戏解决方法介绍,一起来看看吧! 约战精灵再 ...

  9. sql 获取两个月内数据_如何在3个月的时间内自学成为数据分析师?

    从一名0基础的用户运营自学成为数据分析师,我花了大半年的时间,但是抛开工作时间,系统性的学习只花了3个月. 这篇文章会从学习资源和学习路径两个方面分享我的自学经验,希望能对大家有所帮助. 先来说说有哪 ...

最新文章

  1. shell脚本提取服务器一段时间的日志并输出异常结果
  2. java - 分页类
  3. 分别用递归和非递归方式实现二叉树先序、中序和后序遍历(java实现)
  4. [Swift]LeetCode873. 最长的斐波那契子序列的长度 | Length of Longest Fibonacci Subsequence...
  5. 机器视觉-特征点检测:【2】Harrs角点检测原理
  6. 如此火爆的ZooKeeper,到底如何选主?
  7. 雷蛇灯光配置文件_没想到吧,雷蛇竟然出61键小尺寸机械键盘了!雷蛇猎魂光蛛迷你版上手...
  8. 2G的MSN邮箱已经不能申请了
  9. 前端编程提高之旅(六)----backbone实现todoMVC
  10. 单例模式的5种实现方法及优缺点
  11. Hibernate3.x,hibernate3.x,Hibernate3.x整合Spring3.x不能实现自动创建表结构的解决办法:...
  12. 使用jquery datatables插件遇到fnReloadAjax的问题
  13. 一文把三个经典求和问题吃的透透滴。
  14. 《编写可维护的JavaScript》——JavaScript编码规范(七)
  15. 是时候考虑怎么用好云了
  16. 横向对比EXT4,带你感受真实的Linux文件系统F2FS
  17. 体检预约系统软件测试计划书,体检中心-管理软件需求分析.doc
  18. 计算机组成原理实验二八位寄存器,计算机组成原理课后参考答案
  19. javascript高级进阶
  20. BitMap生成艺术字体Bf文件

热门文章

  1. 安卓开发工程师面试题!春招我借这份PDF的复习思路,不吃透都对不起自己
  2. Java入门系列-26-JDBC
  3. springboot-添加拦截器
  4. composer 更改为中国镜像
  5. hibernate Criteria(条件查询接口)
  6. Robot Framework自动化测试(一)--- 安装
  7. Redis PHP连接操作
  8. netduino之电源参考电路MC33269DT-5.0G
  9. [架构] 分布式和集群的区别
  10. 通用的权限管理系统发布