HolyJS 2019: استخلاص المعلومات من SEMrush (الجزء 1)



في المؤتمر العادي لمطوري HolyJS JavaScript الذي تم عقده يومي 24 و 25 مايو في سان بطرسبرغ ، عرضت مقصورة شركتنا على الجميع مهام جديدة. هذه المرة كان هناك 3 منهم! تم إعطاء المهام بدورها ، وتم اعتماد الشارة (JS Brave> JS Adept> JS Master) على حل كل واحدة تالية ، والتي كانت بمثابة حافز جيد لعدم التوقف. في المجموع ، جمعنا حوالي 900 إجابة ونحن في عجلة من أمرنا لتبادل تحليل للحلول الأكثر شعبية وفريدة من نوعها.

تتوقع الاختبارات المقترحة من الشجاعة وفهم "الميزات" الأساسية للغة ، والوعي بالميزات الجديدة لبرنامج ECMAScript 2019 (في الواقع ، هذا الأخير ليس ضروريًا). من المهم ألا تكون هذه المهام مخصصة للمقابلات ، وليست عملية ، ويتم التفكير فيها فقط لغرض المتعة.

المهمة 1 ~ تعبير العد التنازلي


ماذا سيعود التعبير؟ إعادة ترتيب أي حرف واحد ل

  1. عاد التعبير 2
  2. عاد التعبير 1

+(_ => [,,~1])().length 

بالإضافة إلى ذلك : هل من الممكن الحصول على 0 عن طريق التباديل؟

ماذا سيعود التعبير؟


لم يمض وقت طويل للتفكير هنا: التعبير سيعود 3 . لدينا وظيفة مجهولة المصدر تقوم ببساطة بإرجاع مجموعة من ثلاثة عناصر ، أولهما فارغ. استدعاء الوظيفة يعطينا هذا الصفيف ، نأخذ الطول منه ، والمشغل الأحادي زائد لا يحل أي شيء.

تعبير إرجاع 2


يبدو أن الإصلاح السريع يقلل من عدد العناصر في الصفيف المرتجع. للقيام بذلك ، ما عليك سوى رمي فاصلة واحدة:

 [,~1].length // 2 

من المستحيل إلقاء رمز مثل هذا: يجب إعادة ترتيبه إلى مكان آخر في التعبير الأصلي. لكننا نعلم أن خاصية الطول للصفيف تأخذ في الاعتبار العناصر الفارغة المدرجة في الصفيف الحرفي ، باستثناء حالة واحدة :

إذا تم وضع عنصر في نهاية صفيف ، فإن هذا العنصر لا يسهم في طول الصفيف.

أي إذا كان عنصر فارغ في نهاية المصفوفة ، فسيتم تجاهله:

 [,10,] // [empty, 10] 

وهكذا ، فإن التعبير المصحح يشبه هذا:

 +(_ => [,~1,])().length // 2 

هل هناك خيار آخر للتخلص من هذه الفاصلة؟ أو قاد على.

تعبير إرجاع 1


لم يعد من الممكن الحصول على واحدة عن طريق تقليل حجم المصفوفة ، حيث سيتعين عليك القيام برمزين على الأقل. يجب أن نبحث عن خيار آخر.

الوظيفة نفسها تشير إلى القرار. تذكر أن وظيفة في JavaScript هي كائن له خصائصه وطرقه الخاصة. وأحد الخصائص هو الطول أيضًا ، والذي يحدد عدد الوسائط المتوقعة من الدالة. في حالتنا ، تحتوي الوظيفة على وسيطة واحدة ( تسطير أسفل السطر ) - ما تحتاجه!

لكي يتم أخذ الطول من دالة ، وليس من صفيف ، يجب أن تتوقف عن الاتصال به من خلال الأقواس. يصبح من الواضح أن واحدة من هذه الأقواس هي منافس للتبديل. وهناك عدة خيارات تتبادر إلى الذهن:

 +((_ => [,,~1])).length // 1 +(_ => ([,,~1])).length // 1 

ربما هناك شيء آخر؟ أو المستوى التالي.

تعبير إرجاع 0


في المهمة الإضافية ، لا يقتصر عدد التباديل. ولكن يمكننا أن نحاول تحقيق ذلك عن طريق القيام بحركات بسيطة.

تطوير تجربة سابقة مع الطول من كائن دالة ، يمكنك التوصل بسرعة إلى حل:

 +(() => [,_,~1]).length // 0 

هناك العديد من المشتقات هنا ، لكن النقطة الأساسية هي أننا قمنا بتقليل عدد وسيطات الدوال إلى صفر. للقيام بذلك ، كنا بحاجة إلى ما يصل إلى ثلاثة التباديل : اثنين من الأقواس والشخصية السفلية ، والتي أصبحت عنصرا في مجموعة.

حسنًا ، ولكن ربما حان الوقت لإيقاف تجاهل الإضافة (+) وعوامل التشغيل NOT (~) في تفكيرنا؟ يبدو أن الحساب في هذه المشكلة يمكن أن يلعب في أيدينا. لكي لا تنقلب إلى البداية ، إليك التعبير الأصلي:

 +(_ => [,,~1])().length 

بادئ ذي بدء ، نحسب ~ 1 . سيعود bitwise NOT of x - (x + 1) . هذا هو ، ~ 1 = -2 . ولدينا أيضًا عامل تشغيل إضافي في التعبير ، والذي ، كما كان ، يشير إلى أنك في مكان آخر تحتاج إلى العثور على 2 آخرين وكل شيء سينجح.

لقد تذكرنا مؤخرًا "عدم تأثير" العنصر الفارغ الأخير في صفيف حرفي بحجمه ، مما يعني أن الشيطان لدينا موجود في مكان ما هنا:

 [,,].length // 2 

وكل ذلك يضاف بنجاح كبير: نحصل على العنصر ~ 1 من الصفيف ، ونخفض طوله إلى 2 ، ونضيفه كأول المعامل لإضافتنا إلى بداية التعبير:

 ~1+(_ => [,,])().length // 0 

وهكذا ، حققنا الهدف بالفعل في التباديل اثنين !

ولكن ماذا لو لم يكن هذا هو الخيار الوحيد؟ لفة طبل صغيرة ...

 +(_ => [,,~1.])(),length // 0 

يتطلب أيضًا تغييرين: النقطة بعد الوحدة (هذا ممكن ، لأن النوع العددي هو الرقم فقط) والفاصلة قبل الطول . يبدو هراء ، ولكن "في بعض الأحيان" يعمل. لماذا في بعض الاحيان؟

في هذه الحالة ، يتم تقسيم التعبير من خلال مشغل الفاصلة إلى تعبيرين وستكون النتيجة هي القيمة المحسوبة للثاني. لكن التعبير الثاني هو مجرد طول ! الحقيقة هي أننا هنا نصل إلى قيمة المتغير في سياق عالمي. إذا كان وقت التشغيل عبارة عن مستعرض ، فانتقل إلى window.length . ويكون لكائن النافذة خاصية طول تُرجع عدد الإطارات على الصفحة. إذا كانت وثيقتنا فارغة ، فسيتم إرجاع الطول 0. نعم ، هناك خيار بافتراض ... لذلك ، دعونا نتحدث عن الخيار السابق.

وإليك بعض الخيارات الأكثر إثارة للاهتمام التي تم اكتشافها (بالفعل لعدد مختلف من التباديل):

 (_ => [,,].length+~1)() // 0 +(~([,,].len_gth) >= 1) // 0 ~(_ => 1)()+[,,].length // 0 ~(_ => 1)().length,+[,] // 0 ~[,,]+(_ => 1()).length // 0 

لا توجد تعليقات. هل يجد أي شخص شيئا أكثر متعة؟

حافز


مهمة جيدة من الطراز القديم حول "إعادة ترتيب مباراة أو مباراتين لإنشاء مربع". يتحول اللغز المعروف لتطوير المنطق والتفكير الإبداعي إلى الظلامية في مثل هذه الاختلافات في جافا سكريبت. هل هذا مفيد؟ على الأرجح لا من نعم. تطرقنا إلى العديد من ميزات اللغة ، بعضها حتى مفاهيمي تمامًا ، للوصول إلى أسفل الحلول. ومع ذلك ، لا تتعلق المشروعات الحقيقية بالطول من الوظيفة والتعبير إلى المنشطات. تم اقتراح هذه المهمة في المؤتمر كنوع من التمارين التي تسبب الإدمان لتذكر شكل جافا سكريبت.

التوافقية إيفال


لم يتم النظر في جميع الإجابات الممكنة ، ولكن حتى لا تفوت أي شيء على الإطلاق ، ننتقل إلى جافا سكريبت حقيقية ! إذا كان من المثير للاهتمام محاولة العثور عليهم بنفسك ، فهناك ما يكفي من النصائح أعلاه ، ومن الأفضل عدم قراءة المزيد.



لذلك ، لدينا تعبير من 23 حرفًا ، في سجل السلسلة الذي سنفعل فيه التباديل. في الإجمال ، نحتاج إلى تنفيذ n * (n - 1) = 506 التباديل في السجل الأصلي للتعبير من أجل الحصول على جميع المتغيرات برمز واحد منفرد (كما هو مطلوب في شروط المشكلة 1 و 2).

نحدد وظيفة الجمع التي تأخذ تعبير الإدخال كسلسلة وتوقع لاختبار مدى ملاءمة القيمة التي تم الحصول عليها عن طريق تنفيذ هذا التعبير. ستعمل الوظيفة على إخماد جميع الخيارات الممكنة وتقييم التعبير من خلال eval ، مع توفير النتيجة في كائن: مفتاح - القيمة المستلمة ، القيمة - قائمة طفرات تعبيرنا لهذه القيمة. حدث شيء مثل هذا:

 const combine = (expr, cond) => { let res = {}; let indices = [...Array(expr.length).keys()]; indices.forEach(i => indices.forEach(j => { if (i !== j) { let perm = replace(expr, i, j); try { let val = eval(perm); if (cond(val)) { (res[val] = res[val] || []).push(perm); } } catch (e) { /* do nothing */ } } })); return res; } 

حيث تقوم دالة استبدال من السلسلة التي تم تمريرها من التعبير بإرجاع سلسلة جديدة مع إعادة ترتيب الحرف من الموضع i إلى الموضع j . والآن ، دون خوف كبير ، سنفعل:

 console.dir(combine('+(_ => [,,~1])().length', val => typeof val === 'number' && !isNaN(val))); 

نتيجة لذلك ، حصلنا على مجموعات من الحلول:

 { "1": [ "+(_ => [,,~1]()).length", "+((_ => [,,~1])).length", "+(_ =>( [,,~1])).length", "+(_ => ([,,~1])).length" ], "2": [ "+(_ => [,~1,])().length" ] "3": [/* ... */] "-4": [/* ... */] } 

لا تهمنا حلول 3 و -4 ، حيث وجدنا الحلين الوحيدين ، وبالنسبة للوحدة هناك حالة جديدة مثيرة للاهتمام مع [، ~ 1] () . لماذا لا TypeError: bla-bla ليست وظيفة ؟ وكل شيء بسيط: هذا التعبير ليس له أي خطأ في المحلل اللغوي النحوي ، وفي وقت التشغيل ببساطة لا ينفذ ، لأنه لا يتم استدعاء الوظيفة.

كما ترون ، فمن المستحيل حل مشكلة الصفر. يمكن أن نحاول في اثنين؟ لحل المشكلة من خلال هذا البحث الشامل ، في هذه الحالة ، سيكون لدينا تعقيد O (n ^ 4) لطول السلسلة و "تقييم" عدة مرات متابعتها ومعاقبتها ، ولكن الفضول يسود. ليس من الصعب تحسين وظيفة الجمع المعطاة بشكل مستقل أو كتابة تعداد أفضل يأخذ في الاعتبار ميزات تعبير معين.

 console.dir(combine2('+(_ => [,,~1])().length', val => val === 0)); 

في النهاية ، ستكون مجموعة الحلول للصفر هي:

 { "0": [ "+(_ => [,~.1])(),length", "+(_ => [,~1.])(),length", "~1+(_ => [,,])().length" ] } 

من الغريب أننا في المنطق قمنا بإعادة ترتيب النقطة بعد الوحدة ، لكننا ننسى أن النقطة أمام الوحدة ممكنة أيضًا: سجل 0.1 مع حذف صفر.

إذا قمت بإجراء جميع التباديل الممكنة من حرفين لكل منهما ، يمكنك أن تجد أن هناك الكثير من الإجابات للقيم في النطاق من 3 إلى -4:

 { "3": 198, "2": 35, "1": 150, "0": 3, "-1": 129, "-2": 118, "-3": 15, "-4": 64 } 

وبالتالي ، يمكن أن يكون مسار حل Countdown Expression في التباديلين أطول من المسار المقترح من 3 إلى 0 على واحد.

كان هذا الجزء الأول من تحليل مهامنا في HolyJS 2019 ، وسرعان ما سيظهر الجزء الثاني ، حيث سننظر في حلول الاختبارين الثاني والثالث. سنكون على اتصال!

Source: https://habr.com/ru/post/ar452114/


All Articles