
قصة حب جديدة في سلسلة من تحليلات البطولة الأخيرة. يجب على المشاركين المؤهلين الذين اختاروا قسم الواجهة الأمامية حل العديد من المهام ذات التعقيد المختلف للغاية: الأول (حسب توقعاتنا) استغرق 20 دقيقة ، والأخير - حوالي ساعة. لقد اختبرنا مجموعة واسعة من مهارات مطور الواجهة ، بما في ذلك القدرة على فهم مجال موضوع غير عادي.
إبادة ذلك
المؤلفون: مكسيم سيسوف ، كونستانتين بيترييفالمهمة الأولى هي الاحماء. حصل كل مشارك على واحد من أربعة خيارات للمهمة ، تشبه بعضها البعض. لقد اقترحنا ليس فقط شرطًا نصيًا ، ولكن أيضًا حل تكراري "سيء". كان من الضروري إعادة الرمز (كتابة خوارزمية جشعة أنتجت الحل الأسرع) ، وإزالة العودية ومختلف الهراء مثل العمليات والحسابات غير الضرورية.
حالة
حصلت على وظيفة في مختبر لدراسة المادة المضادة ، حيث أجروا تجارب مختلفة. يقوم قسمك بدراسة العمليات التي تحدث عند الجمع بين المادة والمواد المضادة. تحتاج إلى إجراء سلسلة من التجارب على عدد معين من الجزيئات.
طورت الدائرة المجاورة جهازًا يحول المادة إلى مادة مضادة لفترة قصيرة. سيكون مفيدًا لك في إجراء تجارب تستخدم فيها الخوارزمية التالية:
- نجد 2 من أثقل الجزيئات.
- نحول واحد منهم إلى مادة مضادة.
- الجمع بينهما. علاوة على ذلك ، إذا كان الوزن هو نفسه ، يتم إزالتها. إذا كان الوزن مختلفًا ، فسنحصل على جزيء جديد ، وزنه يساوي الفرق في أوزان السابقتين. الجزيء الناتج نفسه هو المسألة.
- إذا كان هناك جزيء واحد متبقي ، فأنت بحاجة لمعرفة وزنه. إذا كان هناك العديد من الجزيئات ، نعود إلى الخطوة 1.
تحتاج إلى معرفة جزيء الوزن الذي سيبقى في نهاية التجربة ، فهذه المعرفة مطلوبة من قبل علماء قسم آخر.
قام المطور السابق برسم الكود الذي تم تضمينه في هذه الحسابات ، لكن لا يمكن للكود أن ينهي الحسابات عندما يتم إجراء التجربة على عدد كبير من الجزيئات. تحتاج إلى تحسين الكود بحيث يعمل في فترة زمنية معقولة.
رمز الموروثة لككمدخلات ، سيكون لديك مجموعة من الأوزان الجزيئية. كإخراج ، تحتاج إلى إرجاع رقم يشير إلى وزن الجزيء الأخير. في حالة عدم وجود جزيئات متبقية ، فمن الضروري إرجاع 0.
var findLatestWeight = function(weights, i = weights.length - 1) { const cur = weights.length - 1 === i; if (i === 0) return weights[0]; weights.sort((a, b) => a - b); weights[i - 1] = (weights[i] === weights[i-1]) ? 0 : weights[i] - weights[i-1]; return findLatestWeight(weights, i - 1); }
مثال وملاحظاتمثال
المدخل: [2،7،4،1،8،1]
الإخراج: 1
نأخذ جزيئات يبلغ وزنها 7 و 8 ، ونحول 7 إلى جزيء مضاد ونصطدم به بجزيء من الوزن 8. لا يزال هناك جزيء من الوزن 1. أوزان الجزيئات المتبقية من الصلب [2،4،1،1،1]. نأخذ جزيئات بوزن 2 و 4 ، ونحول 2 إلى جزيء مضاد ونصطدم به بجزيء من الوزن 4. لا يزال هناك جزيء من الوزن 2. أوزان الجزيئات المتبقية من الصلب [2،1،1،1]. نأخذ جزيئات بوزن 2 و 1 ، ونحول 1 إلى جزيء مضاد ونصطدم به بجزيء من الوزن 2. لا يزال هناك جزيء من الوزن 1. أوزان الجزيئات المتبقية من الصلب [1،1،1]. نحن نأخذ جزيئات بوزن 1 و 1 ، ونحول أحدها إلى جزيء مضاد ويصطدم به مع ثاني. هم مبيدون. أوزان الجزيئات المتبقية [1]. جزيء واحد اليسار. والنتيجة هي 1.
الملاحظات
كحل ، قم بتوفير ملف يقوم بتصدير الإصدار الذي تم تصحيحه من دالة findLatestWeight:
function findLatestWeight(weights) {
سيتم تشغيل الحل في Node.js 12.
قرار
يحتوي الحل "السيئ" المقدم على العديد من المشكلات في وقت واحد. الأول هو العودية. كما هو مذكور في الشرط ، سنقوم بمعالجة صفائف كبيرة من الأرقام ، مما يلغي على الفور حل تكراري.
var findLatestWeight = function(weights) { let i = weights.length - 1; do { if (i === 0) return weights[0] || 0; weights.sort((a, b) => a - b); weights[i-1] = (weights[i]=== weights[i-1]) ? 0 : weights[i]-weights[i-1]; i--; } while (true); }
توسيع العودية هنا بسيط للغاية ، ولكن تنشأ مشكلة أخرى - هناك إعادة فرز مستمرة (من الأصغر إلى الكبير) وتعمل مع نهاية الصفيف. نتيجة لذلك ، حصلنا على انخفاض في العنصر قبل الأخير في الصفيف. لكن بعد ذلك ، لا نقوم بقص الصفيف ، وإذا تم تمرير مجموعة من مليون عنصر إلى الوظيفة ، فسنقوم بإعادة فرزها حتى النهاية.
خيار حل هذه المشكلة هو محاولة تقليم الصفيف باستمرار.
var findLatestWeight = function(weights) { let i = weights.length - 1; do { if (i === 0) return weights[0] || 0; weights.sort((a, b) => a - b); weights[i-1] = (weights[i]=== weights[i-1]) ? 0 : weights[i]-weights[i-1]; weights.length = i;
ليس سيئًا ، لكننا نحتاج أيضًا إلى التخلص من الفرز ، وهو في حد ذاته عملية مكلفة. بشكل عام ، في أي وقت ، سنكون مهتمين بأكبر عضوين في المجموعة. وهذا هو ، هو البحث عن اثنين من أعلى المستويات ، والتي تتم في مرور واحد بسيط للغاية. للراحة ، نقوم بتنفيذ هذا البحث في وظيفة منفصلة.
const maximumTwo = (arr) => { let max1 = arr[0]; let max2 = arr[1]; let max1I = 0; let max2I = 1; for(let i = 2; i < arr.length; i++) { if (arr[i] > max1) { if (max1 > max2) { max2 = arr[i]; max2I = i; } else { max1 = arr[i]; max1I = i; } } else if (arr[i] > max2) { max2 = arr[i]; max2I = i; } } if (max1 > max2) return [max2, max1, max2I, max1I]; return [max1, max2, max1I, max2I]; };
ونغير وظيفة البحث كما يلي:
const fn = function(weights) { if (weights.length <= 1) { return weights[0]; } do { const [x, y, xI, yI] = maximumTwo(weights); if (x === 0) { return y; } weights[xI] = 0; weights[yI] = y - x; } while(true); };
وبالتالي ، سنقوم دائمًا بتجاهل أصغر عنصرين ، ونحول أكبر إلى الفرق بينهما. تخلصنا من الفرز وحصلنا على خطية واحدة بدلاً من ذلك.
من بين الأخطاء الشائعة التي لاحظناها ، أخذ المشاركون الحد الأقصى للعنصر ، واضربوه في -1 ثم أضافوه إلى ثاني أكبر حجر. والنتيجة هي رقم سالب ، والذي تم استخدامه بعد ذلك في حساب "كما هي". بالإضافة إلى ذلك ، تتضمن المهمة فخًا عقليًا مرتبطًا بحقيقة أنه يمكنك محاولة ترك أحجار ذات وزن فريد وحساب الفرق بينها. ومع ذلك ، فإن هذا النهج لا يعطي النتيجة الصحيحة.
ب
المؤلفون: يوجين ميششينكو ، فلاديمير غرينينكو تاداتوتاحالة
يشارك Layout Alexander في العديد من المشاريع باستخدام منهجية BEM. حتى أنه أنشأ مكونًا مفيدًا لمُعرّف IDE المفضل لديه ، والذي يسمح له بكتابة أسماء الفئات بترميز مختصر ونشرها بالكامل. لكن المشكلة هي أنه بالنسبة لكل مشروع ، يقوم الأشخاص بتعيين محددات مختلفة بين الكتلة والعنصر والمعدل (block__mod__val - elem ، block - mod - val ___ elem) ، وفي كل مرة يتعين عليه تعديل هذا يدويًا في البرنامج المساعد الخاص به. ساعد ألكساندر في كتابة وحدة ستحدد الفاصل للكيانات بناءً على الفصل. قاعدة المحددات هي عدد تعسفي من الأحرف (وليس الحروف). أمثلة للرموز المحتملة (قد تكون المعدلات الخاصة بلوك في بيانات الإدخال بدون قيمة):
block_mod__elem
التحسينات:
- يتم كتابة الدروس في المشاريع فقط بأحرف صغيرة.
- يتم تغذية سلسلة مع فئة CSS صالحة لإدخال الوحدة النمطية.
يجب على الوحدة النمطية إرجاع استجابة النموذج:
{ mod: "_",
يجب أن تصدر الوحدة كوحدة نمطية مشتركة:
module.exports = function(str) { }
قرار
استغرقت المهمة الثانية حوالي 20 دقيقة. بمساعدتها ، أردنا اختبار معرفة التعبيرات المنتظمة بين المشاركين.
من الشرط ، علمنا أن الإدخال إلى الوظيفة سيكون عبارة عن سلسلة تحتوي على فئة CSS صالحة مع قيود إضافية ، حيث يتم فصل تسلسل الحروف عن طريق تسلسل تعسفي للأحرف غير الحرفية. مهمتنا هي إيجاد فواصل وفهم دلالاتها.
الجزء الأول من اسم الفصل سيكون دائمًا اسم الكتلة. هذا تسلسل واحد أو أكثر من الحروف. نكتب التعبير العادي المقابل: [az] +.
سنحتاج إلى تعبيرات مماثلة للبحث عن الأجزاء المتبقية: اسم المعدل وقيمته ، أو اسم العنصر الذي يحتوي على المعدل والقيمة المطابقين.
للبحث عن المحددات ، نحتاج إلى تسلسل غير حرفي ، التعبير: [^ az] + مناسب.
ضعها معًا وحدد المجموعات التي سنستخدم قيمها:
let [, mod, elem ] = str.match(/[az]+(?:([^az]+)[az]+(?:\1)?[az]+)([^az]+)[az]+(?:\2)?[az]+/);
أنت الآن بحاجة إلى التأكد من أننا قمنا بتعريف دلالات المجموعات الموجودة بشكل صحيح. يمكنك الاستفادة من حقيقة أن معدّل فقط يمكنه الاجتماع مرتين.
سوف نكتب دالة ستأخذ السلسلة الأصلية وسيجد الفاصل لحساب عدد التكرارات:
const substringCount = (source, substr) => (source.match(new RegExp('[az]' + substr + '[az]', 'g')) || []).length;
إذا اتضح أن عنصر delimiter يحدث مرتين ، و mod - مرة واحدة ، في الواقع فإن العكس هو الصحيح. القرار النهائي:
module.exports = function(str) { let [, mod, elem ] = str.match(/[az]+(?:([^az]+)[az]+(?:\1)?[az]+)([^az]+)[az]+(?:\2)?[az]+/); const substringCount = (source, substr) => (source.match(new RegExp('[az]' + substr + '[az]', 'g')) || []).length; if (substringCount(str, elem) === 2 && substringCount(str, mod) === 1) { [mod, elem] = [elem, mod]; } return { mod, elem }; }
مصنع جيم استنساخ
المؤلفون: ديمتري أندريانوف ديما 117 ، أليكسي غوسيفحالة
خارج النافذة هو 2319. الشركات استنساخ الموظفين الناجحين لأداء مهام معقدة.
في إنتاج الحيوانات المستنسخة ، قرروا تسمية "منتجات" جديدة مع وشم الباركود على كتفهم - لتمييز الحيوانات المستنسخة عن بعضها البعض.
ساعد موظفي المصنع في كتابة وظيفة من شأنها رسم رمز شريطي يحتوي على معلومات حول النسخ.
استنساخ تنسيق المعلوماتيتم تخزين المعلومات حول استنساخ على النحو التالي:
type CloneInfo = { sex: string; id: string; name: string; }
خوارزمية تقديم الباركودالرموز الشريطية المستخدمة في مصنع النسخ تبدو كما يلي:

حجم الباركود ثابت - 148 × 156 بكسل. حول محيط الباركود إطارات سوداء وبيضاء من 3 بكسل لكل منهما. يوجد داخل الإطارات محتوى الباركود ، ويتألف من 18 سطرًا من 17 مربعًا أسود أو أبيض لكل سطر. حجم كل مربع هو 8 × 8 بكسل.
المربعات البيضاء في المحتوى ترميز 0 ، أسود - 1.
خوارزمية إنشاء محتوى الباركودعند تقاطع الصف الأول والعمود الأول للمحتوى ، يتم رسم مربع يشفر جنس المستنسخة. يتم ترميز قيمة الأنثى بصفر (أبيض) ، ذكر بواحد (أسود).
علاوة على ذلك ، يتم تشكيل سطر من النموذج <id> <name> من معرف الحقول واسمها. حقل الاسم مبطن بمسافات في نهاية 26 حرفًا.
يتم تحويل السلسلة الناتجة إلى صفيف بايت - يتم تعيين رمز ASCII المقابل لكل حرف في السلسلة (رقم من 0 إلى 255).
ثم يُترجم كل عنصر من الصفيف الناتج إلى ترميز ثنائي (ثمانية أحرف 0 أو 1) ويتم ترميزه بسلسلة من ثمانية مربعات (0 - ربع أبيض ، 1 - مربع أسود). يتم رسم المربعات في محتوى الباركود بالتتابع والسطر سطراً.
يحتوي السطر الأخير من المحتوى على معلومات التحكم.
خوارزمية التحكم في معلومات التحكمتحدد كل مربع في سطر معلومات التحكم تكافؤ مجموع قيم المحتوى في العمود المقابل. إذا كان مجموع الأصفار وتلك الموجودة في العمود متساويًا ، فسيتم رسم مربع أبيض في معلومات التحكم ، وإلا ، فإن المربع الأسود.
شكل الحل والأمثلةتنسيق الحليجب أن يحتوي الحل الذي تقوم بتحميله على وظيفة renderBarcode:
function renderBarcode(cloneInfo, element) {
الباركود:

مثال 2
استنساخ المعلومات:
{ "sex": "female", "id": "0owrgqqwfw", "name": "Dazdraperma Petrovna" }
الباركود:

قرار
كان من الضروري تكوين التمثيل الثنائي للبيانات بشكل صحيح ، وحساب المجموع الاختباري لها ورسم هذه البيانات في التخطيط. دعنا نحاول القيام بذلك بأسلوب بسيط وبجبهة قدر الإمكان - بدون تحسينات الكود.
لنبدأ بالتمثيل الثنائي. أولاً ، أعلن وظائف المساعد:
نقوم بتكوين سلسلة من البيانات المصدر تتكون من أصفار وأخرى:
let dataString = (cloneInfo.sex === 'female' ? '0' : '1') + cloneInfo.id.split('').map(charToByte).map(byteToString).join('') + cloneInfo.name.padEnd(26, ' ').split('').map(charToByte).map(byteToString).join('');
ثم اكتب التصميم والأنماط الخاصة بالباركود الخاص بنا:
تقديم البيانات الثنائية في التخطيط:
dataString .split('') .forEach((bit) => { const bitDiv = document.createElement('div'); bitDiv.className = 'content__bit content__bit_' + (bit === '0' ? 'zero' : 'one'); contentDiv.appendChild(bitDiv); });
يبقى لحساب وعرض الاختباري. هذا يمكن القيام به مثل هذا:
for (let i = 0; i < 17; i++) {
D. أتمتة ذلك
المؤلفون: فلاديمير روسوف ، ديمتري كاناتنيكوففي كل خيار من خيارات التأهيل ، كانت هناك مهمة تم فيها اقتراح صفحة HTML مع جدول أو قائمة كمدخلات. كان لمهام هذه السلسلة أسطورة مختلفة ، لكنهم جميعا توصلوا إلى حقيقة أنك بحاجة إلى نقل الصفحة إلى تنسيق مشابه لـ Markdown. سنقوم بتحليل الحل لأحد المشاكل.
حالة
على بوابة الولاية لتوفير الخدمات ، أتاحوا تقديم طلب للحصول على المستندات تلقائيًا بالكامل ، لذلك تحتاج فقط إلى ملء جدول بالبيانات الشخصية.
ثم يتم نقل هذه البيانات للتحقق إلى العديد من السلطات ، بما في ذلك وزارة الشؤون الداخلية. بعد بدء الاختبار ، تبين أن وزارة الشؤون الداخلية تقبل البيانات بتنسيق تخفيض السعر ، وأن خدمات الدولة تستخدم تنسيق HTML. ساعدني في كتابة برنامج نصي لترحيل أحد التنسيقات إلى تنسيق آخر ، بحيث يبدأ اللاعبين في أسرع وقت ممكن.
تحتاج إلى كتابة وظيفة تأخذ جدول HTML كمدخلات وتحويلها إلى علامة تشبه Markdown.
كحل لهذه المهمة ، أرسل ملف .js الذي يتم فيه الإعلان عن وظيفة الحل:
function solution(input) {
تنسيق الإدخال / الإخراج والملاحظاتتنسيق الإدخال
يأتي جدول HTML كسلسلة:
<table> <colgroup> <col align="right" /> <col /> <col align="center" /> </colgroup> <thead> <tr> <td>Command </td> <td>Description </td> <th>Is implemented </th> </tr> </thead> <tbody> <tr> <th>git status</th> <td>List all new or modified files</td> <th>Yes</th> </tr> <tr> <th>git diff</th> <td>Show file differences that haven't been staged</td> <td>No</td> </tr> </tbody> </table>
قد يحتوي الجدول على علامات colgroup و thead و tbody بترتيب ثابت. كل هذه العلامات اختيارية ، ولكن على الأقل سيكون هناك دائمًا tead أو tbody.
- يحتوي colgroup على علامات col يمكن أن تحتوي على سمة المحاذاة الاختيارية مع إحدى القيم الثلاث (يسار | مركز | يمين)
- thead و tbody يحتوي على 1 أو أكثر tr
- tr ، بدوره ، يحتوي على كل من td و th
- سيكون الجدول دائما صف واحد على الأقل. - سيكون للصف دائمًا خلية واحدة على الأقل. - يوجد رمز غير أبيض على الأقل في الخلية.
- يتطابق عدد العناصر th / td في الخطوط دائمًا بين جميع الخطوط ومع عدد عناصر col في colgroup ، إذا كان هناك colgroup.
- يمكن أن تحدث المسافات وفواصل الأسطر في مصدر HTML في أي مكان لا ينتهك صلاحية HTML.
تنسيق الإخراج
يجب أن يكون الإخراج عبارة عن خط مع العلامات الترميزية:
| Command | Description | **Is implemented** |
| ---: | :--- | :---: |
| **git status** | List all new or modified files | **Yes** |
| **git diff** | Show file differences that haven't been staged | No |
- يجب أن يتحول الصف الأول الذي يتم مواجهته في جدول دائمًا إلى صف رأس في علامة تمييز.
- جميع الصفوف الأخرى تذهب إلى جسم الجدول.
- يتم عرض فاصل الرأس دائمًا.
- يتم إدراج محتويات td كما هي ، محتويات th كـ ** bold **.
- هناك دائمًا مسافة واحدة بين محتويات الخلية في علامة الترميز ومحددات الخلية (|).
- يجب إزالة المسافات على حواف محتويات علامات td و th.
- يجب حذف فواصل الأسطر في محتويات الخلية.
- يجب استبدال أكثر من مساحة واحدة على التوالي في محتويات الخلايا بمسافة واحدة.
- للمحاذاة في خلايا أعمدة جدول تخفيض السعر ، يكون تنسيق فاصل الرأس مسؤولاً:
| : --- | يعني اليسار المحاذاة
| : ---: | يعني محاذاة الوسط
| ---: | يعني المحاذاة الصحيحة
إذا لم يكن هناك سمة محاذاة محددة في علامة col ، فيجب تعيين المحاذاة إلى اليسار.
الملاحظات
- بالنسبة لخلاصة السطر ، يلزم استخدام الحرف \ n.
- سيتم اختبار الحل في بيئة مستعرض
(Chrome 78) مع إمكانية الوصول إلى كائنات المستند والنافذة.
- يمكنك استخدام بناء الجملة حتى
es2018 ضمناً.
قرار
يتم حل المشكلة ببساطة عن طريق اجتياز شجرة DOM في الجدول. يتم تنفيذ دعم شجرة DOM على مستوى المتصفح ، وهو جزء لا يتجزأ منه ، لذلك لن تكون هناك مشاكل. لحل المشكلة ، يكفي ترجمة شجرة DOM من HTML إلى ترميز العلامات.
بعد فحص الأمثلة ، يمكنك أن ترى أن التحويل بسيط للغاية. يوجد أدناه الكود الذي يمثل نص وظيفة الحل (الإدخال).
أولاً ، نحتاج إلى تحويل السلسلة من HTML إلى شجرة DOM:
const div = document.createElement('div'); div.innerHTML = input; const table = div.firstChild;
بعد تلقي شجرة DOM ، يمكننا فقط المرور عبرها ومعالجة البيانات من عقد DOM مختلفة. للقيام بذلك ، يكفي تجاوز تسلسل العناصر المختلفة لعناصر DOM:
const processors = { 'colgroup': processColgroup, 'thead': processThead, 'tbody': processTbody, }; for (let child of table.children) { processors[child.tagName.toLowerCase()](child); }
من علامات colgroup و col ، نحن مهتمون بمعرفة محاذاة أعمدة الجدول:
const alignments = []; const defaultAlign = 'left'; const processColgroup = (colgroup) => { alignments.push(...Array(...colgroup.children).map(col => { return col.align || defaultAlign; })); };
في العلامات ، tbody و tr ، نحن مهتمون فقط بالأطفال:
const rows = []; const processThead = (thead) => { rows.push(...Array(...thead.children).map(processTr)); }; const processTbody = (tbody) => { rows.push(...Array(...tbody.children).map(processTr)); }; const processTr = (tr) => { return Array(...tr.children).map(processCell); };
من المهم ألا ننسى أنه ، وفقًا للاتفاقية ، يتم تنسيق td و th بشكل مختلف:
const processCell = (cell) => { const tag = cell.tagName.toLowerCase(); const content = clearString(cell.innerHTML); return { 'td': content, 'th': `**${content}**`, }[tag]; };
للعمل مع محتوى اختبار DOM ، يجب عليك تلبية المتطلبات الموضحة في الشرط:
const clearLineBreaks = (str) => str.replace(/\r?\n|\r/g, ''); const clearSpaces = (str) => str.replace(/\s+/g, ' '); const clearString = (str) => clearSpaces(clearLineBreaks(str)).trim();
بعد أن تجولنا حول شجرة DOM ، تم كتابة الجزء الأكبر من طاولتنا إلى مجموعة الصفوف:
[
["Command","Description","**Is implemented**"],
["**git status**","List all new or modified files","**Yes**"],
["**git diff**","Show file differences that haven't been staged","No"]
]
كانت معلومات محاذاة العمود في صفيف المحاذاة:
["right","left","center"]
من المهم أن تتذكر أن معلومات محاذاة العمود قد لا تكون في الإدخال:
const updateAlignments = () => { if (alignments.length > 0) return; alignments.push(...rows[0].map(x => defaultAlign)); }; updateAlignments();
تحويل المحاذاة إلى النموذج النهائي:
const alignmentsContents = alignments.map(align => { return { 'left': ' :--- ', 'center': ' :---: ', 'right': ' ---: ' }[align]; }); const delimiter = `|${alignmentsContents.join('|')}|`;
مثال على قيمة المحدد:
"| ---: | :--- | :---: |"
ستكون الخطوة الأخيرة هي تشكيل خط تخفيض السعر الذي يحتوي على جميع البيانات المقروءة من HTML:
const lineEnd = '\n'; rows.forEach((row, i) => { if (i > 0) markdown += lineEnd; const mdRow = `| ${row.join(' | ')} |`; markdown += mdRow; if (i === 0) { markdown += lineEnd; markdown += delimiter; } }); return markdown;
عودة بناء يعني أن كل من التعليمات البرمجية أعلاه كان نص وظيفة الحل (المدخلات). نتيجة لهذه الوظيفة ، نحصل على كود جدول تخفيض السعر المطلوب الموضح في مثال الإخراج من حالة المهمة.
E. وباء الفيروس
المؤلفون: أندريه موكروسوف ، إيفان بيتوخوفنشرت منظمة الصحة العالمية تقريرا عن علامات الوباء الوشيك لفيروس جديد يهدد المطورين الأماميين. من المعروف أن الفيروس لا يظهر نفسه حتى يرى المضيف رمز JS الذي يحتوي على بعض التعبيرات. بمجرد أن يرى الشخص المصاب هذا التعبير ، يفقد قدرته على كتابة التعليمات البرمجية في JS ويبدأ في كتابة التعليمات البرمجية تلقائيًا في فورتران.
يذكر التقرير أن الفيروس قد تم تنشيطه من خلال النظر في استخدام الوسيطة الأولى من الدالة التي تم تمريرها بواسطة الوسيطة إلى استدعاء دالة Zyn ، أي أن الشخص المصاب لا يمكنه إظهار تعبير مثل Zyn (دالة (a ، b ، c) {console.log (a)}).
من أجل عدم فقد جميع الواجهات الأمامية عن طريق الخطأ ، قررت AST & Co التحقق مما إذا كانت الكودات الخاصة بها تحتوي على التعبير أعلاه. ساعد مهندسي الشركة في كتابة هذا الشيك.
حول رمز AST & Co ، نعلم أن:
- هو مكتوب في ES3 ،
- الوصول إلى خصائص كائن ممكن من خلال نقطة وعبر الأقواس (ab و ['b']) ،
- يمكن تخزين جزء من التعبير في متغير ، لكن لا يتم تمريره إلى الدالة بواسطة المعلمة (a (x) - محظور) ،
- لا توجد وظائف ترجع جزءًا من التعبير المطلوب ،
— , ,
— (a[x], x — ),
— , . . var a = x; a = y; var a = b = 1.
CommonJS-, , (ast) .
ast-, callback-, Zyn , .
module.exports = function (ast) { ... return [...]; }
الملاحظات
.
function traverse( ast, onNodeEnter = (node, scope) => {}, onNodeLeave = (node, scope) => {} ) { const rootScope = new Scope(ast); _inner(ast, rootScope); function resolveScope(astNode, currentScope) { let isFunctionExpression = ast.type === 'FunctionExpression', isFunctionDeclaration = ast.type === 'FunctionDeclaration'; if (!isFunctionExpression && !isFunctionDeclaration) {
قرار
.
— ES3
, . , .
— , (ab a['b'])
Zyn, Z['y'].n, Zy['n'] Z['y']['n'].
, (a(x) — )
, . , : var x = Zy; xn(...).
— , ,
— , ,
— , .. var a = x; a = y; var a = b = 1.
( ) , - .
— , (a[x], x — )
, : var x = 'y'; Z[x].n(...).
C :
1. , , .
2. , .
, , — . 2.
: Zyn(function(a, b, c){...}), — .
FunctionExpression — CallExpression, callee — MemberExpression. property — n, object ( MemberExpression object property y) — Z.
, — — . — Identifier , MemberExpression ObjectLiteral (xa var x = {a: ...} ).
+++ b/traverse.js @@ -120,3 +120,59 @@ Scope.prototype = { return this._vars.has(name) || (this._parent && this._parent.isDefined(name)); } }; + +module.exports = function (ast) { + var result = []; + + traverse(ast, (node, scope) => { + if (node.type !== 'CallExpression') { + return; + } + let args = node.arguments; + if (args.length !== 1 || + args[0].type !== 'FunctionExpression') { + return; + } + let callee = node.callee; + if (callee.type !== 'MemberExpression') { + return; + } + let property = callee.property, + object = callee.object; + if (property.name !== 'n') { + return; + } + if (object.type !== 'MemberExpression') { + return; + } + property = object.property; + object = object.object; + if (property.name !== 'y') { + return; + } + if (object.type !== 'Identifier' || + object.name !== 'Z') { + return; + } + + checkFunction(args[0]); + }); + + function checkFunction(ast) { + let firstArg = ast.params[0]; + if (!firstArg) { + return; + } + + traverse(ast.body, (node, scope) => { + if (node.type !== 'Identifier') { + return; + } + if (node.name === firstArg.name) { + result.push(node); + } + }); + } + + return result; +};
traverse , , MemberExpression ObjectProperty. :
--- a/traverse.js +++ b/traverse.js @@ -60,16 +60,16 @@ function traverse( * @param {object} astNode ast- * @param {Scope} scope ast- */ - function _inner(astNode, scope) { + function _inner(astNode, scope, parent) { if (Array.isArray(astNode)) { astNode.forEach(node => { /* . * , , */ - _inner(node, scope); + _inner(node, scope, parent); }); } else if (astNode && typeof astNode === 'object') { - onNodeEnter(astNode, scope); + onNodeEnter(astNode, scope, parent); const innerScope = resolveScope(astNode, scope), keys = Object.keys(astNode).filter(key => { @@ -80,10 +80,10 @@ function traverse( keys.forEach(key => { // - _inner(astNode[key], innerScope); + _inner(astNode[key], innerScope, astNode); }); - onNodeLeave(astNode, scope); + onNodeLeave(astNode, scope, parent); } } } @@ -164,10 +164,22 @@ module.exports = function (ast) { return; } - traverse(ast.body, (node, scope) => { + traverse(ast.body, (node, scope, parent) => { if (node.type !== 'Identifier') { return; } + if (!parent) { + return; + } + if (parent.type === 'MemberExpression' && + parent.computed === false && + parent.property === node) { + return; + } + if (parent.type === 'ObjectProperty' && + parent.key === node) { + return; + } if (node.name === firstArg.name) { result.push(node); }
. getPropName:
--- a/traverse.js +++ b/traverse.js @@ -121,6 +121,18 @@ Scope.prototype = { } }; +function getPropName(node) { + let prop = node.property; + + if (!node.computed) { + return prop.name; + } + + if (prop.type === 'StringLiteral') { + return prop.value; + } +} + module.exports = function (ast) { var result = []; @@ -137,17 +149,17 @@ module.exports = function (ast) { if (callee.type !== 'MemberExpression') { return; } - let property = callee.property, + let property = getPropName(callee), object = callee.object; - if (property.name !== 'n') { + if (property !== 'n') { return; } if (object.type !== 'MemberExpression') { return; } - property = object.property; + property = getPropName(object); object = object.object; - if (property.name !== 'y') { + if (property !== 'y') { return; } if (object.type !== 'Identifier' ||
: . . 1.
ScopeScope . , , traverse:
--- a/traverse.js +++ b/traverse.js @@ -1,3 +1,12 @@ +const scopeStorage = new Map(); + +function getScopeFor(ast, outerScope) { + if (!scopeStorage.has(ast)) { + scopeStorage.set(ast, new Scope(ast, outerScope)); + } + + return scopeStorage.get(ast); +} - add(name) { - this._vars.add(name); + add(name, value) { + this._vars.set(name, { + value: value, + scope: this + }); + }, + resolve(node) { + if (!node) { + return node; + } + if (node.type === 'Identifier') { + let value = this._vars.get(node.name); + if (value) { + return value; + } + value = (this._parent && this._parent.resolve(node)); + return value; + } }, /** * . @@ -136,6 +161,12 @@ function getPropName(node) { module.exports = function (ast) { var result = []; + traverse(ast, (node, scope) => { + if (node.type === 'VariableDeclarator') { + scope.add(node.id.name, node.init); + } + }); + traverse(ast, (node, scope) => { if (node.type !== 'CallExpression') { return;
Scope. , Scope . , Scope , :
--- a/traverse.js +++ b/traverse.js @@ -146,13 +146,17 @@ Scope.prototype = { } }; -function getPropName(node) { +function getPropName(node, scope) { let prop = node.property; if (!node.computed) { return prop.name; } + let resolved = scope.resolve(prop); + if (resolved) { + prop = resolved.value; + } if (prop.type === 'StringLiteral') { return prop.value; } @@ -177,22 +181,43 @@ module.exports = function (ast) { return; } let callee = node.callee; + + let resolved = scope.resolve(callee); + if (resolved) { + callee = resolved.value; + scope = resolved.scope; + } + if (callee.type !== 'MemberExpression') { return; } - let property = getPropName(callee), + let property = getPropName(callee, scope), object = callee.object; if (property !== 'n') { return; } + + resolved = scope.resolve(object); + if (resolved) { + object = resolved.value; + scope = resolved.scope; + } + if (object.type !== 'MemberExpression') { return; } - property = getPropName(object); + property = getPropName(object, scope); object = object.object; if (property !== 'y') { return; } + + resolved = scope.resolve(object); + if (resolved) { + object = resolved.value; + scope = resolved.scope; + } + if (object.type !== 'Identifier' || object.name !== 'Z') { return;
: . :
— , Z — , - .
— , , .
— , var a = 'x', b = a.
, .
--- a/traverse.js +++ b/traverse.js @@ -128,10 +128,23 @@ Scope.prototype = { } if (node.type === 'Identifier') { let value = this._vars.get(node.name); - if (value) { - return value; + if (!value) { + if (this._parent) { + value = this._parent.resolve(node); + } else { +
:
const scopeStorage = new Map(); function getScopeFor(ast, outerScope) { if (!scopeStorage.has(ast)) { scopeStorage.set(ast, new Scope(ast, outerScope)); } return scopeStorage.get(ast); } function traverse( ast, onNodeEnter = (node, scope) => {}, onNodeLeave = (node, scope) => {} ) { const rootScope = getScopeFor(ast); _inner(ast, rootScope); function resolveScope(ast, currentScope) { let isFunctionExpression = ast.type === 'FunctionExpression', isFunctionDeclaration = ast.type === 'FunctionDeclaration'; if (!isFunctionExpression && !isFunctionDeclaration) {
F. Framework-
: , collapsusAPI. — , . .
— . . , . !
. , . , , , ( ). , , 0 (0 , 0 , 0 ).
, , . JavaScript JS- Framework.
: , . ( , ). () . ( ).
0. , ( time) .
const ONE_SECOND_DEGREES = 6; const ONE_SECOND_FACTOR = 1 / Framework.SPEED * ONE_SECOND_DEGREES; class MyClock extends Framework.Clock { constructor() { super(); this.arrows.push(new Framework.Arrow("seconds", { color: "red" })); this.arrows.push(new Framework.Arrow("minutes", { weight: 3, length: 80 })); this.arrows.push(new Framework.Arrow("hours", { weight: 3, length: 60 })); this.buttons.push(new Framework.Button("A", () => { alert("A"); })); this.tick = 0; } onBeforeTick() { const [arrow] = this.arrows; this.tick++; arrow.rotateFactor = this.tick % 10 ? 0 : ONE_SECOND_FACTOR; console.log("before: " + arrow.pos); } onAfterTick() { const [arrow] = this.arrows; console.log("after: " + arrow.pos); } }
:
— — , ,
— ,
— , ; (100 ) ; , .
قرار
, -, « », . , , , .
: , . . , , .
:
const TPS = 1000 / Framework.INTERVAL;
// .
function getTarget(ticks, planet) { const { h, m, s } = planet;
, — rotateFactor. getRotateFactor, , , . :
1. ,
2. .
. .
function getRotateFactor(pos, target, forward = true) { let angle = target - pos;
, MAX_SPEED . getRotateFactor.
const MAX_FACTOR = Framework.MAX_SPEED / Framework.SPEED; function getRotateFactor(pos, target, forward = true) { let angle = target - pos; if (forward) { angle < 0 && (angle += 360); } else { Math.abs(angle) > 180 && (angle -= Math.sign(angle) * 360) } const factor = angle / Framework.SPEED;
:
buttonAHandler() {
, :
onBeforeTick() { const [sec, min, hour] = this.arrows; const time = ++this.ticks; const planet = this.planets[this.pos];
:
const TPS = 1000 / Framework.INTERVAL; const MAX_FACTOR = Framework.MAX_SPEED / Framework.SPEED; function getTarget(ticks, planet) { const { h, m, s } = planet; const ts = Math.floor(ticks / TPS);
. . , , , .
: , , , , (, , ).
استنتاج. . — , . .
, . , ( ) 18 .
المراجع:
—
ML-—
-—
-