前端,算法和负鼠Frederick。 我们分析了Yandex竞赛的任务

通过这篇文章,我们将完成一系列有关2018年Yandex.Blitz竞赛的出版物。 我们希望您有机会参与或至少看到我们建议接近生产的任务。 上一场竞赛专门针对前端中算法的使用。 今天,我们发布了对12个问题的详细分析:在资格赛中提出了前6个问题,在最后一轮中提出了7-12个问题。 我们试图解释条件是如何形成的,我们关注哪些技能。 感谢所有参与者的关注!



任务1


第一个任务是预热20分钟,然后我们决定对其进行设置,以便使用正则表达式轻松解决它。

任务的所有选项都以类似的方式构造-细分为多个点,每个点都可以由最终正则表达式中的组表示。

请考虑以下选项之一:邮局。

条件


在Jopan-14-53星球上,当地人希望互相发送信件,但发送信件的鸽子机器人却对地址感到困惑。 您必须教他们解析字母并检查其有效性。

地址基本部分的结构由收件人的地区,直辖市,姓名和姓氏组成。 直辖市可以分为县和邮局。 所有部分均以逗号分隔。

地区,市镇和地区的名称是单词,每个单词的第一个字母较大,其余的较小。 可以使用两个单词的名称,以空格或减号分隔。 每个字由三到八个字母AZ组成。

居民手中有6个手指,在日常生活中为十二进制系统。 数字0-9照原样使用,而10和11则由符号~

组合中的邮局编号连续3位,或4位分为2组,每组2位,带减号。 示例: 44-99

有时,居民可应要求向市政当局发送信件。 在这种情况下,没有地址:只有市政当局和收件人的名字。

这很有趣,但地球上的人们仅被称为六个名字和九个姓氏。 名称:Shob,Ryob,Mob,Xian,Ryan,Mans。 姓:Sho,Ryo,Mo,Xia,Rya,Ma,Syu,Ryu,Mu。 这个星球上的居民根本不是梦想家。

解析中


地址基本部分的结构由收件人的地区,直辖市,姓名和姓氏组成。 直辖市可以分为县和邮局。 所有部分均以逗号分隔。

从第一段中,我们学习如何指示地区,城市,地区,邮局,姓名和姓氏,以及在测试行中应遵循的顺序。

地区,市镇和地区的名称是单词,每个单词的第一个字母较大,其余的较小。 可以使用两个单词的名称,以空格或减号分隔。 每个字由三到八个字母AZ组成。

从这一段可以明显看出,该组对应于单词([-][-]{2,7}) 。 以及名称,分别是([-][-]{2,7}(?:[- ][-][-]{2,7}))

居民手中有6个手指,在日常生活中为十二进制系统。 数字0-9照原样使用,而10和11则由符号~

在这里,我们知道仅使用\d作为数字是不够的-您需要使用[0-9~≈]

组合中的邮局编号连续3位,或4位分为2组,每组2位,带减号。 示例: 44-99

因此,该组对应于邮局编号([0-9~≈]{3}|[0-9~≈]{2}-[0-9~≈]{2})

有时,居民可应要求向市政当局发送信件。 在这种情况下,没有地址:只有市政当局和收件人的名字。

我们了解,记住并考虑到地区和邮局可能同时缺席的最终职能集会。

这很有趣,但地球上的人们仅被称为六个名字和九个姓氏。 名称:Shob,Ryob,Mob,Xian,Ryan,Mans。 姓:Sho,Ryo,Mo,Xia,Rya,Ma,Syu,Ryu,Mu。 这个星球上的居民根本不是梦想家。

这是条件的最后一部分。 在这里,我们需要另一个简单的小组(||||||||) (|||||)或更短的([][]|[]) ([C]|[]||)

为简单起见,我们将这些组保存到变量中,并在结果正则表达式中使用它们。

 const w = '[-][-]{2,7}'; // word const d = '[0-9~≈]'; // digit const name = `(?:${w}(?:[- ]${w})?)`; const number = `(?:${d}{3}|${d}{2}-${d}{2})`; const person = '(?:[][]|[]) (?:|||||)'; // , , (,  , )?  const re = new RegExp(`^(${name}),\\s*(${name}),\\s*(?:(${name}),\\s*(${number}),\\s*)?(${person})$`); module.exports = function(str) { //      if (typeof str !== 'string') return null; const res = str.match(re); //   -   if (!res) return null; //    //    ,     return res.slice(1); }; 

任务2.出现错误的代码


条件


白天,开发团队做了几个新功能。 但是在准备发布时,发生了合并冲突,解决了这些冲突后,布局便分开了。 迫切需要消除由于代码中的更正次数最少而导致的错误,以使发行版有时间投入生产。

为您提供了样式损坏的HTML页面和PNG格式(具有预期的结果)。 您需要修正错误,以便在Chrome浏览器中查看时的结果与原始屏幕截图相同。 发送更正的页面作为解决问题的方法。

样式损坏的HTML页面 。 版面:



解析中


在此任务中,我们试图模仿Yandex开发人员经常遇到的情况:在庞大的代码库中,找到很少的简单代码行,对其进行更正可以得到所需的结果。 也就是说,困难不在于编写代码,而在于理解确切地在哪里编写(或删除)代码。

我们采用了真正的搜索引擎代码,并进行了一些修改,破坏了版面。 竞赛的参与者不到一个小时就可以处理大约250 KB的布局,并将代码置于与布局相对应的状态。

很明显,比赛的时间限制不允许阅读全部代码,因此参赛者应在浏览器中使用针对开发人员的工具。

要更正四个任务选项之一,进行以下更改就足够了:

  diff --git a / blitz.html b / blitz.html 
 索引36b9af8..1e30545 100644 
  ---一个/ blitz.html 
  +++ b / blitz.html 
  @@ -531.10 +531.6 @@ 
  iframe [src $ ='ext-analytics.html'] { 
 高度:自动; 
  } 
  -.search2__button .suggest2-form__button:nth-​​child(1){ 
  -背景:#ff0!重要; 
  -} 
  -- 
  / * ../../blocks-desktop/input/__control/input__control.styl end * / 
  / * ../../node_modules/islands/common.blocks/input/__clear/input__clear.css开始* / 
  / *相对于input__box定位。 
  @@ -744.10 +740.6 @@ 
  iframe [src $ ='ext-analytics.html'] { 
 背景剪辑:padding-box; 
  } 
  .input_theme_websearch .input__clear { 
 背景图片:url(“ / static / web4 / node_modules / islands / common.blocks / input / _theme / input_theme_websearch.assets / clear.svg”); 
 背景尺寸:16px; 
  @@ -857.6 +849.7 @@ 
  iframe [src $ ='ext-analytics.html'] { 
 背景颜色:#f2cf46; 
  } 
  .websearch-button__text:{之前 
  +位置:绝对; 
 顶部:-6px; 
 右:-9px; 
 宽度:0; 
  @@ -866.8 +859.6 @@ 
  iframe [src $ ='ext-analytics.html'] { 
 边框样式:实心; 
  border-color:rgba(255,219,76,0); 
  border-left-color:继承; 
  -职位:相对; 
  -z-index:-1000; 
  } 
  / * ../../blocks-deskpad/websearch-button/websearch-button.styl结尾* / 
  @@ -1349.6 +1340.7 @@ 
  iframe [src $ ='ext-analytics.html'] { 
  font-size:14px; 
 行高:40px; 
 职位:相对 
  +显示:内联块; 
 高度:自动; 
 填充:0; 
 垂直对齐:中间; 


任务3.具有给定变异性的图片


条件




设计师设计徽标。 它需要在各种条件下使用。 为了使它尽可能方便,请在纯CSS中用一个HTML元素组成它。

您无法使用图片(即使通过数据:uri)。

解析中


由于任务仅使用一个div,因此我们只剩下div本身以及伪元素:: before和:: after。

幸运的是,布局上的图片仅包含三个不同颜色的区域。 我们为每个可访问元素,正确的位置和圆角创建自己的背景。 布局的灰色区域上有阴影-我们使用渐变使其阴影。

 div { background: #0C0C0C; border-radius: 10px; position: relative; } div:before { border-radius: 9px 9px 0 0; position: absolute; width: 100%; height: 50%; background: #F8E34B; content: ''; } div:after { content: ''; background: linear-gradient(178deg, #C8C8C8 0px , transparent 7px), #EEEDEF; position: absolute; width: 50%; height: 50%; bottom: 0; border-radius: 0 0 0 9px; } 

任务4


条件


Petya是《莫斯科前沿报》(Moscow Frontender)的高级排版员。 在报纸的版面设计中,Petya使用了一堆网络技术。 Petya面临的最困难的任务是报纸文章标题的布局:每期的报纸列宽度不同,标题具有不同的字体和字符数。

Petya需要为每个标题选择自己的字体大小,以便最大字体大小,但同时所有标题文本都适合分配给他的空间。

帮助Petya:实现calcFontSize函数,该函数可让您将传输的文本输入到容器中,以使其适合整个容器并具有最大可能的大小。 如果失败,则解决方案应返回null。 输入行的最大长度为100个字符。 输入字符串不能为空。 您的解决方案应包含整个功能代码,并且不应使用外部依赖关系,以便Petya可以将其复制到他的项目中并在他的代码中调用它。

我们将检查您的解决方案的最佳工作方式,如果产生过多的DOM操作,则会对其进行优化。

解析中


要做的第一件事是学习如何确定文本是否适合容器,因为容器中的文本可能需要多行。 最简单的方法是使用element.getBoundingClientRect()函数,该函数可让您获取元素的尺寸。

您可以在容器内使用单独的span元素绘制文本,并检查此元素的宽度或高度是否超过容器的大小。 如果是,则文本不适合容器。

接下来,检查边界条件。 如果即使最小字体也不能容纳该文本,则无法输入。 如果文本以允许的最大尺寸中断,则max将是正确的答案。 在其他情况下,所需的字体大小在间隔[min,max]中。

为了找到一种解决方案,您需要对整个间隙进行排序,并找到一个将文本放置在容器中的字体大小值,但是如果将其增加1-它将不再适合。

您可以使用简单的for循环在[min,max]范围内执行此操作,但是该解决方案将对页面进行大量检查和重绘-对于该范围内检查的每个值至少要执行一次。 这种解决方案的算法复杂度将是线性的。

为了最大程度地减少检查次数并获得适用于O的解决方案(log n),您需要使用二进制搜索算法。 该算法的思想是在每次搜索迭代时,将值的范围分为两半,并且搜索将在解决方案所在的一半中递归地继续。 当范围缩小到单个值时,搜索将结束。 在Wikipedia文章中阅读有关二进制搜索算法的更多信息。

我们使用MutationObserver检查了解决方案的算法复杂性:我们将其放在页面上,并计算DOM在寻找答案的过程中做出决定的突变次数。 对于某些测试,严格限制了突变的数量,因此只有基于二进制搜索的解决方案才能通过此限制。

要获得任务的总分,还必须仔细检查不同的边界条件(匹配最小和最大,输入处为空行),并提供运行代码的几种环境条件(例如,使用!固定!CSS页面中重要的字体大小) )

任务5.沟通困难


在每个限定选项中,都有一个任务是提供带有某些界面的HTML页面作为输入。 这些任务有不同的图例,但是它们全都归结为以下事实:必须从页面中提取一些数据,并使用此数据以某种方式与界面进行交互。

让我们分析该系列任务之一的解决方案,即“沟通中的困难”。

条件


Horse Adolf喜欢在电话上和朋友聊天。 不幸的是,这种情况很少见。 每次Adolf想要打电话时,他都要花一个多小时才能拨打朋友的号码。 这是因为他很难用厚实的蹄敲打正确的钥匙。

幸运的是,阿道夫的手机支持JavaScript。 请编写一个程序,通过单击键盘来拨打Adolf的朋友的电话。 所有必要的数字都记录在笔记本中。 不幸的马将非常感谢您!

解析中


这是作为输入提供的页面:



解决方案的第一部分是检索数据
电话号码的每个数字都是通过背景图片通过图片设置的。 同时,在验证期间,将动态替换数字。 如果在浏览器中打开调试器并查看页面的DOM树,您将注意到每个DOM元素都使用清晰的类:

 <div class="game"> <div class="target"> <div class="line"> <div class="symbol nine"></div> <div class="symbol eight"></div> <div class="symbol five"></div> <div class="symbol separator"></div> <div class="symbol four"></div> <div class="symbol one"></div> <div class="symbol two"></div> <div class="symbol separator"></div> </div> <!-- ... --> </div> <!-- ... --> </div> 

在这种情况下,仅创建一个字典,提取CSS类并将其转换为带有数字的字符串就足够了,该数字不包含分隔符(CSS类分隔符)。 例如,像这样:

 const digits = document.querySelectorAll('.game .target .symbol:not(.separator)'); const dict = { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'zero': 0, }; const phoneNumber = Array.from(digits).reduce((res, elem) => { for (const className of elem.classList) { if (className in dict) { res.push(dict[className]); break; } } return res; }, []); 

结果,我们得到以下数组:[9,8,5,4,1,2,8,8,0,9,0]。

解决方案的第二部分是与界面进行交互
在这一阶段,我们已经有了一个数组,其中包含该数字的所有数字,并且我们需要了解键盘上的哪个按钮与哪个数字相对应。 如果查看HTML代码,将会看到没有特殊的提示类在键盘上指示数字:

 <div class="keys"> <div class="key"></div> <div class="key"></div> <!-- … --> </div> 

可以简单地通过索引手动创建另一本词典,但是有一种更简单的方法。 如果仔细查看Web检查器中的样式,您会注意到按钮上的数字是通过CSS属性内容为伪元素之前设置的:。 例如,对于“ 3”键,样式如下所示:

 .key:nth-child(3):before { content: '3'; } 

要获取此属性的值,我们使用window.getComputedStyle方法:

 const keys = Array.from(document.querySelectorAll('.game .key')).reduce((res, elem) => { const key = window //  CSSStyleDeclaration  - .getComputedStyle(elem, ':before') //    .getPropertyValue('content') //     ,      .replace(/"/g, ''); res[key] = elem; return res; }, {}); 

结果,我们得到一个对象,其中键文本将是按钮上的文本(数字或“呼叫”),而值将是DOM节点。

剩下的只是从第一个数组(phoneNumber)中获取值,遍历它们,然后使用我们的按键字典单击相应的按钮:

 phoneNumber.push('call'); const call = () => { const event = new Event('click'); const next = phoneNumber.shift(); keys[next].dispatchEvent(event); if (phoneNumber.length) { setTimeout(call, 100); } } call(); 

请注意:在拨号之前,我们将调用值添加到数组的末尾。 这是任务条件所必需的,因为如果您拨打该号码并且不按“呼叫”,则解决方案将无法通过测试。

另一个功能是异步按下键盘按钮。 如果拨号时两次击键之间的时间间隔小于50毫秒,则此解决方案也将无法通过测试。 在出现问题的情况下未明确描述此功能,我们希望参与者通过阅读页面的源代码来查找。 顺便说一句,源代码中的参与者正在等待着一些惊喜。 ;)

任务6


条件


Fedor Rakushkin在他的公司中管理任务管理系统。 系统所在的服务器发生故障(Fedor未执行备份)。

在服务器的内存中,保留了描述任务,执行者和观察者以及这些结构之间的关系的结构缓存。 此外,保留了指向最后更改的结构的缓存链接。

帮助Fedor编写一个函数,该函数可以还原任务和用户之间的所有可能的连接,并将它们保存为Markdown格式的文档,然后Fedor将从该文档中将数据库还原到新服务器。

降价文件应采用以下格式:

 ##  - %  1%,  %username1%, : %username2% - %  2%,  %username1%, : %username2%, %username3% - %  3%,  %username1% //     - %  4%, : %username1%, %username2% //     - %  5% //       - %  6%, : %username1% - %  1%,  %username1% //  - %  2% - %  3%, : %username1% ##  - %username1% * %  1% // ,    * %  2% * %  3% * %  1% - %username2% * %  3% 

任务列表,任务中的执行者列表,任务中的观察者列表,用户列表以及用户执行的任务列表应按字典顺序排序。

缓存中结构的描述


用户以“用户”类型的结构形式存储在缓存中:

 type User = { login: string; tasks: Task[]; spectating: Task[]; }; 

任务以“任务”类型的结构形式存储在缓存中:

 type Task = { title: string; assignee: User; spectators: User[]; subtasks: Task[]; parent: Task | null; }; 

解决方案模板


您的解决方案应包含一个CommonJS模块,该模块导出与以下签名相对应的函数:

 /** * @param {User|Task} data -     , *        (User  Task) * @return {string} */ module.exports = function (data) { //   return '…'; } 

例子


 //    const User1 = { type: 'user', login: 'fedor', tasks: [], spectating: [] }; const User2 = { type: 'user', login: 'arkady', tasks: [], spectating: [] }; //    const Task1 = { type: 'task', title: 'Do something', assignee: null, spectators: [], subtasks: [], parent: null }; const Task2 = { type: 'task', title: 'Something else', assignee: null, spectators: [], subtasks: [], parent: null }; const Task3 = { type: 'task', title: 'Sub task', assignee: null, spectators: [], subtasks: [], parent: null }; //    : //      Task1.assignee = User1; User1.tasks.push(Task1); // ...    —  Task1.spectators.push(User2); User2.spectating.push(Task1); //      , //       Task2.spectators.push(User1); User1.spectating.push(Task2); //      Task3.parent = Task2; Task2.subtasks.push(Task3); // ,     —  3 const lastEdited = Task3; 

如果显示指向“ lastEdited”的链接,则会得到以下结构:

 { type: 'task', title: 'Sub task', assignee: null, spectators: [], subtasks: [], parent: { type: 'task', title: 'Something else', assignee: null, spectators: [ { type: 'user', login: 'fedor', tasks: [ { type: 'task', title: 'Do something', assignee: [Circular], spectators: [ { type: 'user', login: 'arkady', tasks: [], spectating: [ [Circular] ] } ], subtasks: [], parent: null } ], spectating: [ [Circular] ] } ], subtasks: [ [Circular] ], parent: null } } 

Markdown , :

 ##  - Do something,  fedor, : arkady - Something else, : fedor - Sub task ##  - arkady - fedor * Do something 


, .

, , . , :

 /** *  ,     * @param {{ref: object, visited: ?boolean}} ctx * @param {object} handlers —     */ function traverse(ctx, handlers) { //    ,  ctx.ref , — ,     task.parent if (!ctx.ref) { return; } //   ,    ,       const visited = ctx.visited || new Set(); if (visited.has(ctx.ref)) { return; } visited.add(ctx.ref); //       handlers[ctx.ref.type](ctx.ref, goDeeper); //  ,       function goDeeper(subrefs) { //       for (const subref of [].concat(subrefs)) { traverse({ visited, ref: subref }, handlers); } } } //      const users = []; const tasks = []; // ref   —     traverse({ ref }, { user(user, deeper) { users.push(user); deeper(user.tasks); // to task.assignee deeper(user.spectating); // to task.spectators }, task(task, deeper) { tasks.push(task); deeper(task.assignee); // to user.tasks deeper(task.spectators); // to user.spectating deeper(task.parent); // to task.subtasks deeper(task.subtasks); // to task.parent } ); 

. , , …

 users.sort((u1, u2) => u1.login < u2.login ? -1 : (u1.login > u2.login ? 1 : 0)); tasks.sort((t1, t2) => t1.title < t2.title ? -1 : (t1.title > t2.title ? 1 : 0)); 

… — :

 //    const taskLine = t => `${ t.title }${ t.assignee ? `,  ${t.assignee.login}` : '' }${ t.spectators.length ? `, : ${t.spectators.map(u => u.login).join(', ')}` : '' }`; function renderTasks (parent = null, indent = 0) { return tasks .filter(t => t.parent === parent) .map(t => [ '\n', ' '.repeat(indent), //  '- ', taskLine(t), //    t.subtasks.length ? printTasks(t, indent + 2) : '' //   ].join('')) .join(''); } function renderUsers () { return ${users.map(u => `\n- ${u.login}${ u.tasks.map(t => `\n * ${t.title}`).join('') }`).join('')} } const result = ` ##  ${renderTasks()} ##  ${renderUsers()} `.trim(); 

7.


条件


, .. :



, . , «».



.



, . , . . . HTML CSS. JavaScript .

, , - . , .


HTML/CSS, , , - .

CSS, , — float, . float , , .

— , . ( jsfiddle.net .)

— padding-top, margin-top ( ). DOM- (). ( .)

8.


条件


HTML-. , . , . .

(pixel perfect). .

. .

, , . , . , : - , , .

, . , , .


, — . — CSS- border ( ), background ( ) box-shadow ( ).

- « » ( ) . , , .

— , 70 70 . : , . CSS- , CSS- , .



— 210 210 , 70 70 .



9.


条件


— , -. JavaScript, . , .

: . , , . .

, — . — . , JavaScript API . , -, , . 10 , HTTP- .

— . , , , .

-.

:
— API npm- @yandex-blitz/phone.
API .
— -, : task.js .
— - runkit- .

-, .


— GET- return connect.

: - . , , . .

, : , . :

 const writeQueue = []; const processQueue = () => { if (writeQueue.length) { const fn = writeQueue.shift(); fn().then(() => { processQueue(); }); } } 

, « ».

 const writeQueue = []; let isWriteInProgress = false; const processQueue = () => { if (isWriteInProgress) { return; } if (writeQueue.length) { isWriteInProgress = true; const fn = writeQueue.shift(); fn().then(() => { isWriteInProgress = false; processQueue(); }); } } 

, POST- . , , .

 app.post("/speeddial/:digit/:phonenumber", (req, res) => { writeQueue.push(makeWriteJob(phone, req, res)); processQueue(); }); 

:

 const express = require('express'); const { BEEP_CODES } = require('@yandex-blitz/phone'); const writeQueue = []; let isWriteInProgress = false; const processQueue = () => { if (isWriteInProgress) { return; } if (writeQueue.length) { isWriteInProgress = true; const fn = writeQueue.shift(); fn().then(() => { isWriteInProgress = false; processQueue(); }); } } const makeWriteJob = (phone, req, res) => { return () => { return phone.getData() .then(value => { const speeddialDict = JSON.parse(value); speeddialDict[req.params.digit] = Number(req.params.phonenumber); return phone .setData(JSON.stringify(speeddialDict)) .then(() => phone.beep(BEEP_CODES.SUCCESS)) .then(() => { res.sendStatus(200); }) }) .catch(e => { phone.beep(BEEP_CODES.ERROR).then(() => { res.sendStatus(500); }) }) } }; const createApp = ({ phone }) => { const app = express(); //   ,   « »   digit app.get("/speeddial/:digit", (req, res) => { phone.getData().then(value => { const speeddialDict = JSON.parse(value); return phone.connect() .then(async () => { await phone.dial(speeddialDict[req.params.digit]); res.sendStatus(200); }, async() => { await phone.beep(BEEP_CODES.FATAL); res.sendStatus(500); }); }).catch(async (e) => { await phone.beep(BEEP_CODES.ERROR); res.sendStatus(500); }); }); //   « »   digit  phonenumber app.post("/speeddial/:digit/:phonenumber", (req, res) => { writeQueue.push(makeWriteJob(phone, req, res)); processQueue(); }); return app; }; exports.createApp = createApp; 

10.


条件


. , «».

:
— , .
— , , .
— , , .
— , : ← ↓ ↑ →.
— — , .

, , , , .

.

.

HTML- ( ).

, window.onMazeReady(). . 2 , .

CSS- map.

click CSS-:
— — control_direction_left,
— — control_direction_down,
— — control_direction_up,
— — control_direction_right.

CSS- :

 background: radial-gradient(circle at 5px 5px, #eee, #000); 

25 , 500 . 例如:









window.map String. :
# —
。 —
o —
x —

, , , — . .

,

 window.map = ` ##### #o#x# #.#.# #...# ##### `; 

:



:

 <!DOCTYPE html> <html lang=ru/> <head> <style> body { padding: 100px 0 0 100px; } .game-box { perspective: 500px; perspective-origin: center; } .map { transform-style: preserve-3d; } .map__tilt_left { transform: rotateY(-25deg); } .map__tilt_down { transform: rotateX(-25deg); } .map__tilt_up { transform: rotateX(25deg); } .map__tilt_right { transform: rotateY(25deg); } </style> <title></title> </head> <body> <div class="game-box"> <div class="map"> <!--  --> </div> </div> <script> // JavaScript </script> </body> </html> 


(HTML, CSS, JS). , «» .

. ( ), , .

, , .

, :

 <table class="map map__tilt_none"> <!-- ... --> <tr> <td class="map__cell map__cell_content_wall"></td> <td class="map__cell map__cell_content_empty"></td> <td class="map__cell map__cell_content_ball"></td> <td class="map__cell map__cell_content_exit"></td> <td class="map__cell map__cell_content_wall"></td> </tr> <!-- ... --> </table> 

:

 .map { border: 0; border-spacing: 0; border-collapse: separate; background-color: #ccc; transform-style: preserve-3d; } .map__cell { box-sizing: border-box; border: 1px solid; border-color: #9b9b9b #575757 #575757 #9b9b9b; width: 30px; height: 30px; text-align: center; vertical-align: middle; font-size: 0; line-height: 0; background-color: #707070; } .map__cell_content_ball:after { content: ''; display: inline-block; width: 20px; height: 20px; border-radius: 50%; background: radial-gradient(circle at 5px 5px, #eee, #000); } .map__cell_content_wall { border-width: 4px; } .map__cell_content_exit { background-color: #000; border: 5px solid; border-color: #222 #555 #555 #222; } 

— — .

, .

«» , . , . , :

 window.map = ` ####### ##.#### #..o..# ##x.#.# ###...# ####### `; 

:

 function convertMap(mapInput) { return mapInput .trim() .split(/\n\s*/) .map(row => row.split('')); } 

, HTML :

 const CELL_CONTENT = { '#': 'wall', 'o': 'ball', '.': 'empty', 'x': 'exit' }; function buildGameBoxHtml(map) { return ` <div class="game-box"> <table class="map map__tilt_none"> ${map.map(row => ` <tr> ${row.map(cell => ` <td class="map__cell map__cell_content_${CELL_CONTENT[cell]}"></td> `).join('')} </tr> `).join('')} </table> <!--      --> <div class="controls"> <button class="control control_direction_left">←</button> <button class="control control_direction_down">↓</button> <button class="control control_direction_up">↑</button> <button class="control control_direction_right">→</button> </div> </div> `; } 

, :

 let gameBox = document.querySelector('.game-box'); let map = gameBox.querySelector('.map'); //           gameBox.addEventListener('click', ({ target }) => { //      if (!target.classList.contains('control')) { return; }; //      - const direction = target.className.match(/\bcontrol_direction_(\w+)/)[1]; //  ,   - map.className = map.className.replace(/\bmap__tilt_\w+/, `map__tilt_${direction}`); //  ,     let ball = map.querySelector('.map__cell_content_ball'); //       let nextBall = getNextCell(map, ball, direction); //      ball.classList.remove('map__cell_content_ball'); ball.classList.add('map__cell_content_empty'); //     ,            while ( !nextBall.classList.contains('map__cell_content_wall') && !ball.classList.contains('map__cell_content_exit') ) { ball = nextBall; nextBall = getNextCell(map, ball, direction); } //      ball.classList.remove('map__cell_content_empty'); ball.classList.add('map__cell_content_ball'); }); const DIRECTIONS = { 'left': [-1, 0], 'up': [0, -1], 'down': [0, 1], 'right': [1, 0] }; //  DOM API ,        function getNextCell(map, cell, direction) { const directionDiff = DIRECTIONS[direction]; return map.rows[cell.parentNode.rowIndex + directionDiff[1]].cells[cell.cellIndex + directionDiff[0]]; } 

— callback : window.onMazeReady(). .

, . , , . HTML, CSS JS — , .

, :
— ,
— , ,
— , ,
— , ,
— , ,
— , .

, :
— ,
— ,
— DOM API ,
— .

11.


条件


, , . , . , .

, . , . . .js, :

 module.exports = function solveCaptcha(captcha) { // ... } 

. .
:

 captcha = ' TRABWARH THSCAHAW WWBSCWAA CACACHCR ' 

:
— S — (sign)
— T — (tree)
— R — (road)
—…

:

 [ 'TRABWARH THSCAHAW' , 'WWBSCWAA CACACHCR' ] 

:
— 1 10.
— .
— , .
— ( ).
— , , .
— , .

Cut the cake codewars.com.


, 10. , . , . :
— ;
— , .

, . : «… , ». . .



. . , . .

. «», .

 module.exports = function solveCaptcha(captcha) { const n = //     const sizes = getAllSizes(); //      // board —    //   —   ,   //  ,    const board = []; //    function placeNext(remains) { //    ... if (remains === 0) { // ... ,   ,   return board; } else { // ... //         // ,     const pos = getEmptyPos(); //        for (let i = 0; i < sizes.length; i++) { //  ,    const size = sizes[i]; //          //     (      //     !== 1),  null const layer = getLayer(pos, size); //     if (layer) { //     board.push(layer); //     const res = placeNext(remains - 1); //  ,  if (res) return res; //      //    board.pop(); } } } } //   return placeNext(n); } 

12. -


条件


X . VCS , .

, -. — , .

, . , , . .

, . ( ) .

.



. — 1000, - — 20.

:

 type PullRequest = { /** *     ( ) *   N: 1 <= N <= 1000 */ files: string[], /** *     VCS */ id: string, /** * Unix-timestamp  - */ created: number, } 

(created) (id) – .



CommonJS- :

 /** * @param {PullRequest[]} pullRequests  PR,     * @returns {string[]}      */ module.exports = function (pullRequests) { //   } 



NodeJS v9.11.2. .



 function mergeAllPRs(prs) { /* solution */ } console.assert( mergeAllPRs([ { id: '#1', created: 1536077100, files: ['.gitignore', 'README.md'] }, { id: '#2', created: 1536077700, files: ['index.js', 'package-lock.json', 'package.json'] }, { id: '#3', created: 1536077800, files: ['.pnp.js', 'yarn.lock'] } ]) .join(',') === [ "#1", "#2", "#3" ].join(',') ); console.assert( mergeAllPRs([ { id: '#1', created: 1536074100, files: ['README.md'] }, { id: '#2', created: 1536078700, files: ['README.md'] }, { id: '#3', created: 1536097800, files: ['README.md'] } ]).join(',') === [ "#1" ].join(',') ); console.assert( mergeAllPRs([ { id: '#1', created: 1536077100, files: ['.gitignore', 'README.md'] }, { id: '#2', created: 1536077700, files: ['index.js', 'package-lock.json', 'package.json'] }, { id: '#3', created: 1536077800, files: ['.pnp.js', 'package-lock.json', 'yarn.lock'] }, { id: '#4', created: 1536077900, files: ['index.spec.js', 'index.spec.ts', 'index.ts'] } ]) .join(',') === [ "#1", "#2", "#4" ].join(',') ); 


— , .

, « » (, ).

, , , . ( some includes). — O(n 2 ).

, , ( . ). — O(n).

:

 function conflicts(a, b) {  let i = 0;  let j = 0;  while (i < a.length && j < b.length) {      if (a[i] === b[j]) {          return true;      } else if (a[i] > b[j]) {          j++;      } else {          i++;      }  }  return false; } function mergeAllPrs (input) {  let i = 0;  const mergedFiles = [];  const mergedPrs = [];  while (i < input.length) {      const pr = input[i];      if (!conflicts(mergedFiles, pr.files)) {          mergedPrs.push(pr);          mergedFiles.push(...pr.files);      }      i++;  }  return mergedPrs.map(x => x.id); }; 

, , . , :

 console.assert(  mergeAllPrs([      {          "id": "1",          "created": 1538179200,          "files": [ "a", "b", "c", "d" ]      },      {          "id": "2",          "created": 1538189200,          "files": [ "a", "x" ]      },      {          "id": "3",          "created": 1538199200,          "files": [ "b", "g" ]      },      {          "id": "4",          "created": 1538209200,          "files": [ "c",  "f" ]      },      {          "id": "5",          "created": 1538219200,          "files": [ "d", "w" ]      }  ])  .join(',') === ['2', '3', '4', '5'].join(',') ); 

, ( , ).

: «» -, «» — ( ). ( ).

:

 [  {      "id": "#1",      "created": 1536077100,      "files": [ ".gitignore", "README.md" ]  },  {      "id": "#2",      "created": 1536077700,      "files": [ "index.js", "package-lock.json", "package.json" ]  },  {      "id": "#3",      "created": 1536077800,      "files": [ "index.js" ]  } ] 

#2 #3 , ["#1", "#2"]. .



, .

, — O(n 2 ), . .

, , . , , .

conflicts , . :

 const conflictMatrix = new Uint8Array(prs.length ** 2); const prToIndex = new WeakMap(); for (let i = 0; i < prs.length; i++) {  const pr1 = prs[i];  prToIndex.set(pr1, i);  conflictMatrix[i * prs.length + i] = 0;  for (let j = i + 1; j < prs.length; j++) {      const pr2 = prs[j];      conflictMatrix[i * prs.length + j] = conflictMatrix[j * prs.length + i] = conflicts(pr1.files, pr2.files);  } } /** *     PR (    ) */ function doPRsConflict(pr1, pr2) {  const i = prToIndex.get(pr1);  const j = prToIndex.get(pr2);  return conflictMatrix[i * prs.length + j] === 1; } 

«» , . ( ) , . , , - .

, , .

 /** *     prsSet,           */ function getNonConflictingPRs (prsSet, mergedPrs) {  const result = [];  const prsToTest = [...prsSet, ...mergedPrs];  prsSet.forEach((pr) => {      if (!conflictsWithSomePR(pr, prsToTest)) {          result.push(pr);      }  });  return result; } 

.

 const fullSearch = (prsSet, mergedPrs = [], mergedFilesCount = 0) => {  hits++;  //  ,            //   ,      const safeToMergePRs = getNonConflictingPRs(prsSet, mergedPrs);  mergedPrs = mergedPrs.concat(safeToMergePRs);  safeToMergePRs.forEach((pr) => {      prsSet.delete(pr);      mergedFilesCount += pr.files.length;  });  const pr = prsSet.values().next().value; // ...      

.

Source: https://habr.com/ru/post/zh-CN430560/


All Articles