Live Coding

التحقق مما إذا كان الكائن فارغًا

كيف تتحقق مما إذا كان كائن JavaScript فارغًا؟

الشرح: يكون الكائن فارغًا إذا لم يكن لديه أي خصائص قابلة للتعداد خاصة به. يمكننا استخدام Object.keys(obj) التي تُرجع مصفوفة بأسماء الخصائص القابلة للتعداد الخاصة بكائن معين. إذا كان طول هذه المصفوفة 0، فإن الكائن فارغ.

function isEmpty(obj) { return Object.keys(obj).length === 0; } console.log(isEmpty({})); // true console.log(isEmpty({ a: 1 })); // false

عكس سلسلة نصية

اكتب دالة لعكس سلسلة نصية معينة.

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

function reverseString(str) { return str.split('').reverse().join(''); } console.log(reverseString('hello')); // 'olleh'

التحقق من الكلمة المتطابقة (Palindrome)

اكتب دالة تتحقق مما إذا كانت السلسلة النصية المعطاة هي كلمة متطابقة (palindrome).

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

function isPalindrome(str) { const reversed = str.split('').reverse().join(''); return str === reversed; } console.log(isPalindrome('racecar')); // true console.log(isPalindrome('hello')); // false

العثور على أكبر رقم في مصفوفة

اكتب دالة للعثور على أكبر رقم في مصفوفة من الأرقام.

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

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // أو قم بإلقاء خطأ return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

فز بز (FizzBuzz)

اكتب برنامجًا يطبع الأرقام من 1 إلى n. ولكن بالنسبة لمضاعفات الرقم ثلاثة، اطبع 'Fizz' بدلاً من الرقم، وبالنسبة لمضاعفات الرقم خمسة، اطبع 'Buzz'. أما بالنسبة للأرقام التي هي مضاعفات لكل من ثلاثة وخمسة، اطبع 'FizzBuzz'.

الشرح: تختبر هذه المشكلة الكلاسيكية حلقة أساسية ومنطق شرطي. تحتاج إلى التكرار من 1 إلى n واستخدام عامل القسمة (%) للتحقق من قابلية القسمة على 3 و 5.

function fizzBuzz(n) { for (let i = 1; i <= n; i++) { if (i % 3 === 0 && i % 5 === 0) { console.log('FizzBuzz'); } else if (i % 3 === 0) { console.log('Fizz'); } else if (i % 5 === 0) { console.log('Buzz'); } else { console.log(i); } } } fizzBuzz(15);

إزالة التكرارات من مصفوفة

اكتب دالة لإزالة العناصر المكررة من مصفوفة.

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

function removeDuplicates(arr) { return [...new Set(arr)]; } console.log(removeDuplicates([1, 2, 2, 3, 4, 4, 5])); // [1, 2, 3, 4, 5]

التحقق من الجناس (Anagram)

اكتب دالة للتحقق مما إذا كانت سلسلتان نصيتان هما جناس لبعضهما البعض.

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

function isAnagram(str1, str2) { const clean = (str) => str.replace(/[^a-z0-9]/gi, '').toLowerCase(); const sorted = (str) => clean(str).split('').sort().join(''); return sorted(str1) === sorted(str2); } console.log(isAnagram('listen', 'silent')); // true console.log(isAnagram('hello', 'world')); // false

حساب المضروب (Factorial)

اكتب دالة لحساب مضروب عدد صحيح غير سالب.

الشرح: مضروب العدد (n!) هو حاصل ضرب جميع الأعداد الصحيحة الموجبة الأقل من أو تساوي n. يمكننا حساب ذلك بشكل تكراري عن طريق ضرب الأرقام من 1 حتى n.

function factorial(n) { if (n < 0) return undefined; // المضروب غير معرف للأرقام السالبة if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

جمع كل الأرقام في مصفوفة

اكتب دالة تُرجع مجموع جميع الأرقام في مصفوفة.

الشرح: يمكنك استخدام طريقة reduce، وهي مثالية لتجميع قيمة واحدة من مصفوفة. تأخذ دالة رد نداء وقيمة أولية (0 للجمع).

function sumArray(arr) { return arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); } console.log(sumArray([1, 2, 3, 4])); // 10

تسوية مصفوفة متداخلة

اكتب دالة لتسوية مصفوفة متداخلة. للتبسيط، افترض مستوى واحد فقط من التداخل.

الشرح: لمستوى واحد من التداخل، يمكنك استخدام Array.prototype.concat() مع عامل الانتشار أو طريقة flat() (ES2019).

function flattenArray(arr) { // استخدام flat() (أبسط إذا كان ES2019+ مقبولاً) // return arr.flat(); // استخدام reduce و concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

عد حروف العلة في سلسلة نصية

اكتب دالة تعد عدد حروف العلة (a, e, i, o, u) في سلسلة نصية معينة.

الشرح: كرر عبر السلسلة (بدون تمييز الحالة) وتحقق مما إذا كان كل حرف حرف علة. حافظ على عداد.

function countVowels(str) { const vowels = 'aeiou'; let count = 0; for (let char of str.toLowerCase()) { if (vowels.includes(char)) { count++; } } return count; } console.log(countVowels('Hello World')); // 3

تحويل جملة إلى عنوان (Title Case)

اكتب دالة تحول جملة إلى حالة العنوان (الحرف الأول من كل كلمة كبير).

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

function titleCase(str) { return str.toLowerCase().split(' ').map(word => { return word.charAt(0).toUpperCase() + word.slice(1); }).join(' '); } console.log(titleCase('i am a little tea pot')); // 'I Am A Little Tea Pot'

مشكلة المجموع الثنائي (Two Sum Problem)

بالنظر إلى مصفوفة من الأعداد الصحيحة nums وعدد صحيح target، أرجع مؤشرات الرقمين اللذين مجموعهما يساوي target.

الشرح: يتمثل أحد الأساليب الشائعة في استخدام خريطة التجزئة (أو كائن JavaScript) لتخزين الأرقام ومؤشراتها أثناء التكرار. لكل رقم، تحقق مما إذا كان target - current_number موجودًا في الخريطة.

function twoSum(nums, target) { const map = {}; for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (map[complement] !== undefined) { return [map[complement], i]; } map[nums[i]] = i; } return []; // أو null، أو ألقِ خطأ إذا لم يكن هناك حل } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

تطبيق عداد باستخدام الإغلاقات (Closures)

أنشئ دالة تُرجع دالة عداد. في كل مرة يتم فيها استدعاء الدالة المرجعة، يجب أن تزيد العدد وتُرجع العد.

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

function createCounter() { let count = 0; return function() { count++; return count; }; } const counter1 = createCounter(); console.log(counter1()); // 1 console.log(counter1()); // 2 const counter2 = createCounter(); console.log(counter2()); // 1

العثور على أول حرف غير متكرر

اكتب دالة تجد أول حرف غير متكرر في سلسلة نصية.

الشرح: يمكنك استخدام خريطة تجزئة لعد تكرارات الأحرف. أولاً، كرر عبر السلسلة لإنشاء خريطة التكرار. ثم، كرر عبر السلسلة مرة أخرى وأرجع أول حرف بتكرار 1.

function firstNonRepeatingChar(str) { const charCount = {}; for (const char of str) { charCount[char] = (charCount[char] || 0) + 1; } for (const char of str) { if (charCount[char] === 1) { return char; } } return null; // أو بعض المؤشر إذا تكرر كل شيء } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

تقسيم المصفوفة (Array Chunking)

اكتب دالة تقسم مصفوفة إلى مجموعات بحجم محدد.

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

function chunkArray(arr, size) { const chunked = []; let index = 0; while (index < arr.length) { chunked.push(arr.slice(index, index + size)); index += size; } return chunked; } console.log(chunkArray([1, 2, 3, 4, 5, 6, 7], 3)); // [[1, 2, 3], [4, 5, 6], [7]]

التحقق مما إذا كان العدد أوليًا

اكتب دالة لتحديد ما إذا كان العدد المعطى هو عدد أولي.

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

function isPrime(num) { if (num <= 1) return false; if (num <= 3) return true; if (num % 2 === 0 || num % 3 === 0) return false; for (let i = 5; i * i <= num; i = i + 6) { if (num % i === 0 || num % (i + 2) === 0) return false; } return true; } console.log(isPrime(7)); // true console.log(isPrime(10)); // false

العثور على أطول كلمة في سلسلة نصية

اكتب دالة تجد أطول كلمة في جملة.

الشرح: قسّم السلسلة إلى مصفوفة من الكلمات. ثم، كرر عبر المصفوفة، وتتبع أطول كلمة تم العثور عليها حتى الآن (أو طولها).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // نظف الكلمات إذا لزم الأمر (على سبيل المثال، إزالة علامات الترقيم) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

تطبيق `Array.prototype.map`

طبق نسختك الخاصة من دالة Array.prototype.map.

الشرح: تأخذ دالة map دالة رد نداء وتنشئ مصفوفة جديدة عن طريق تطبيق رد النداء على كل عنصر من عناصر المصفوفة الأصلية. يجب أن تقوم دالتك بالتكرار عبر المصفوفة وإنشاء المصفوفة الجديدة.

function myMap(arr, callback) { const mappedArray = []; for (let i = 0; i < arr.length; i++) { mappedArray.push(callback(arr[i], i, arr)); } return mappedArray; } const numbers = [1, 4, 9]; const roots = myMap(numbers, Math.sqrt); console.log(roots); // [1, 2, 3]

تطبيق `Array.prototype.filter`

طبق نسختك الخاصة من دالة Array.prototype.filter.

الشرح: تأخذ دالة filter دالة رد نداء وتنشئ مصفوفة جديدة تحتوي على جميع العناصر التي تجتاز الاختبار الذي تم تنفيذه بواسطة دالة رد النداء المقدمة.

function myFilter(arr, callback) { const filteredArray = []; for (let i = 0; i < arr.length; i++) { if (callback(arr[i], i, arr)) { filteredArray.push(arr[i]); } } return filteredArray; } const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']; const result = myFilter(words, word => word.length > 6); console.log(result); // ['exuberant', 'destruction', 'present']

تطبيق `Array.prototype.reduce`

طبق نسختك الخاصة من دالة Array.prototype.reduce.

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

function myReduce(arr, callback, initialValue) { let accumulator = initialValue; let startIndex = 0; if (initialValue === undefined) { if (arr.length === 0) throw new TypeError('Reduce of empty array with no initial value'); accumulator = arr[0]; startIndex = 1; } for (let i = startIndex; i < arr.length; i++) { accumulator = callback(accumulator, arr[i], i, arr); } return accumulator; } const array1 = [1, 2, 3, 4]; const sum = myReduce(array1, (acc, curr) => acc + curr, 0); console.log(sum); // 10

الاستذكار - متتالية فيبوناتشي

طبق دالة فيبوناتشي باستخدام الاستذكار لتحسين الأداء.

الشرح: تتضمن متتالية فيبوناتشي حسابات متكررة. يخزن الاستذكار نتائج استدعاءات الدالة المكلفة ويعيد النتيجة المخزنة مؤقتًا عندما تحدث نفس المدخلات مرة أخرى.

function memoizedFib() { const cache = {}; function fib(n) { if (n in cache) { return cache[n]; } if (n <= 1) { return n; } const result = fib(n - 1) + fib(n - 2); cache[n] = result; return result; } return fib; } const fibonacci = memoizedFib(); console.log(fibonacci(10)); // 55 console.log(fibonacci(40)); // 102334155 (أسرع بكثير من غير الاستذكار)

التحقق من توازن الأقواس

اكتب دالة للتحقق مما إذا كانت السلسلة النصية التي تحتوي على أقواس {}[]() متوازنة.

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

function isBalanced(str) { const stack = []; const map = { '(': ')', '{': '}', '[': ']' }; for (let char of str) { if (map[char]) { stack.push(char); } else if (Object.values(map).includes(char)) { if (stack.length === 0) return false; const lastOpen = stack.pop(); if (map[lastOpen] !== char) return false; } } return stack.length === 0; } console.log(isBalanced('({[]})')); // true console.log(isBalanced('([)]')); // false

تطبيق قائمة انتظار بسيطة (Simple Queue)

طبق بنية بيانات قائمة الانتظار (Queue) مع طريقتي enqueue (إضافة إلى الخلف) وdequeue (إزالة من الأمام).

الشرح: تتبع قائمة الانتظار مبدأ الأول يدخل، الأول يخرج (FIFO). يمكن استخدام مصفوفة، مع push لـ enqueue وshift لـ dequeue.

class Queue { constructor() { this.items = []; } enqueue(element) { this.items.push(element); } dequeue() { if (this.isEmpty()) return 'Underflow'; return this.items.shift(); } front() { if (this.isEmpty()) return 'No elements in Queue'; return this.items[0]; } isEmpty() { return this.items.length === 0; } } const q = new Queue(); q.enqueue(10); q.enqueue(20); console.log(q.dequeue()); // 10 console.log(q.front()); // 20

تطبيق مكدس بسيط (Simple Stack)

طبق بنية بيانات المكدس (Stack) مع طريقتي push (إضافة إلى الأعلى) وpop (إزالة من الأعلى).

الشرح: يتبع المكدس مبدأ الأخير يدخل، الأول يخرج (LIFO). يمكن استخدام مصفوفة، مع طريقتي push وpop.

class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { if (this.isEmpty()) return 'Underflow'; return this.items.pop(); } peek() { return this.items[this.items.length - 1]; } isEmpty() { return this.items.length === 0; } } const s = new Stack(); s.push(10); s.push(20); console.log(s.pop()); // 20 console.log(s.peek()); // 10

العثور على الرقم المفقود في المتتالية

بالنظر إلى مصفوفة تحتوي على n أرقام مميزة مأخوذة من 0، 1، 2، ...، n، ابحث عن الرقم المفقود من المصفوفة.

الشرح: يمكن حساب مجموع الأرقام من 0 إلى n باستخدام الصيغة n*(n+1)/2. يمكن حساب المجموع الفعلي لعناصر المصفوفة. الفرق بين هذين المجموعين سيكون الرقم المفقود.

function findMissingNumber(nums) { const n = nums.length; const expectedSum = n * (n + 1) / 2; const actualSum = nums.reduce((sum, num) => sum + num, 0); return expectedSum - actualSum; } console.log(findMissingNumber([3, 0, 1])); // 2 console.log(findMissingNumber([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8

دالة إزالة الارتداد (Debounce Function)

طبق دالة إزالة ارتداد. تضمن إزالة الارتداد عدم استدعاء دالة مرة أخرى حتى مرور فترة زمنية معينة دون استدعائها.

الشرح: استخدم setTimeout وclearTimeout. في كل مرة يتم فيها استدعاء الدالة المزالة الارتداد، امسح المهلة السابقة واضبط مهلة جديدة. يحدث استدعاء الدالة الفعلي فقط عند اكتمال المهلة.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // مثال على الاستخدام: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // يتم استدعاؤها بعد 1 ثانية (إذا لم يتم استدعاؤها مرة أخرى) debouncedHello(); // تعيد ضبط المؤقت debouncedHello(); // تعيد ضبط المؤقت، وستعمل فعليًا بعد 1 ثانية من هذا الاستدعاء.

دالة التحديد (Throttle Function)

طبق دالة التحديد. تضمن التحديد أن الدالة تُستدعى مرة واحدة على الأكثر في فترة زمنية محددة.

الشرح: استخدم علامة للإشارة إلى ما إذا كان الاستدعاء مسموحًا به. عند الاستدعاء، إذا كان مسموحًا به، قم بتنفيذ الدالة، واضبط العلامة على false، واستخدم setTimeout لإعادة ضبط العلامة بعد الفترة.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // مثال على الاستخدام: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // ستسجل مرة واحدة على الأكثر في الثانية.

دالة التجزئة (Currying Function)

اكتب دالة تأخذ دالة ذات وسيطتين وتُرجع نسخة مجزأة منها.

الشرح: تقوم التجزئة بتحويل دالة ذات وسيطات متعددة إلى سلسلة من الدوال، تأخذ كل منها وسيطة واحدة. تصبح f(a, b) f(a)(b).

function curry(fn) { return function(a) { return function(b) { return fn(a, b); }; }; } function add(a, b) { return a + b; } const curriedAdd = curry(add); const add5 = curriedAdd(5); console.log(add5(3)); // 8 console.log(curriedAdd(10)(20)); // 30

نسخ كائن عميق (Deep Clone an Object)

اكتب دالة لإجراء نسخ عميق لكائن JavaScript.

الشرح: النسخ السطحي ينسخ الخصائص العلوية فقط. النسخ العميق ينسخ جميع الخصائص بشكل متكرر، بما في ذلك الكائنات والمصفوفات المتداخلة. طريقة بسيطة (مع قيود، على سبيل المثال، لا تتعامل مع الدوال والتواريخ والتعبيرات النمطية بشكل جيد) هي استخدام JSON.parse(JSON.stringify(obj)). النهج المتكرر أكثر قوة.

function deepClone(obj) { // نسخة بسيطة (مع قيود) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("فشل الاستنساخ:", e); return null; } // نسخة متكررة أكثر قوة (مثال أساسي): /* if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = deepClone(obj[key]); } } return clone; */ } const original = { a: 1, b: { c: 2 } }; const cloned = deepClone(original); cloned.b.c = 3; console.log(original.b.c); // 2 (يثبت أنه استنساخ عميق) console.log(cloned.b.c); // 3

الحصول على مفاتيح الكائن

كيف تحصل على مصفوفة من مفاتيح كائن؟

الشرح: استخدم Object.keys(obj).

function getKeys(obj) { return Object.keys(obj); } console.log(getKeys({a: 1, b: 2})); // ['a', 'b']

الحصول على قيم الكائن

كيف تحصل على مصفوفة من قيم كائن؟

الشرح: استخدم Object.values(obj).

function getValues(obj) { return Object.values(obj); } console.log(getValues({a: 1, b: 2})); // [1, 2]

التحقق مما إذا كانت المصفوفة تتضمن قيمة

كيف تتحقق مما إذا كانت المصفوفة تحتوي على قيمة محددة؟

الشرح: استخدم Array.prototype.includes(value).

function checkIncludes(arr, val) { return arr.includes(val); } console.log(checkIncludes([1, 2, 3], 2)); // true

دمج مصفوفتين

كيف تدمج مصفوفتين في واحدة؟

الشرح: استخدم بناء الجملة المنتشر (...) أو Array.prototype.concat().

function mergeArrays(arr1, arr2) { return [...arr1, ...arr2]; } console.log(mergeArrays([1, 2], [3, 4])); // [1, 2, 3, 4]

شرح 'this' في النطاق العام

إلى ماذا يشير this في النطاق العام في المتصفح؟

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

console.log(this === window); // true (في بيئة المتصفح)

شرح 'this' في طريقة كائن

إلى ماذا يشير this عند استخدامه داخل طريقة كائن؟

الشرح: عندما تُستدعى دالة كطريقة لكائن، يشير this إلى الكائن الذي تم استدعاء الطريقة عليه.

const myObject = { prop: 'Hello', greet() { return this.prop; } }; console.log(myObject.greet()); // 'Hello'

شرح 'this' مع دوال السهم (Arrow Functions)

كيف تتعامل دوال السهم مع this؟

الشرح: لا تملك دوال السهم سياقها الخاص بـ this. بدلاً من ذلك، ترث this من النطاق المحيط (المعجمي) الذي تم تعريفها فيه.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // تسجل 42 لأن 'this' موروثة }, 100); } new MyClass();

الاختلاف بين `let` و`const` و`var`

ما هي الاختلافات الرئيسية بين let وconst وvar؟

الشرح: var محدد النطاق بالدالة ويتم رفعه (hoisted). let وconst محددين النطاق بالكتلة ويتم رفعهما ولكن في 'منطقة زمنية ميتة' حتى الإعلان. يجب تهيئة const ولا يمكن إعادة تعيينه.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // إعادة تعريف وتؤثر على 'a' الخارجية let b = 20; // 'b' جديدة داخل الكتلة // const c = 30; // ستكون 'c' جديدة console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

ما هو الوعد (Promise)؟

اشرح ما هو الوعد (Promise) في JavaScript.

الشرح: الوعد هو كائن يمثل الاكتمال النهائي (أو الفشل) لعملية غير متزامنة وقيمتها الناتجة. يمكن أن يكون في إحدى الحالات الثلاث: معلق (pending)، مكتمل (fulfilled)، أو مرفوض (rejected).

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('نجاح!'); // reject('خطأ!'); }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));

استخدام `async/await`

كيف تبسط async وawait العمل مع الوعود؟

الشرح: يوفر async/await سكرًا نحويًا (syntactic sugar) فوق الوعود، مما يجعل الكود غير المتزامن يبدو ويتصرف أكثر شبهاً بالكود المتزامن. ترجع دالة async دائمًا وعدًا، وawait توقف التنفيذ حتى يستقر الوعد.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('جارٍ البدء...'); await delay(1000); console.log('انتظرنا ثانية واحدة.'); await delay(500); console.log('انتظرنا 0.5 ثانية أخرى.'); return 'انتهى!'; } run().then(console.log);

تحويل سلسلة نصية إلى رقم

كيف تحول سلسلة نصية إلى رقم؟

الشرح: استخدم parseInt()، parseFloat()، أو عامل الزائد الأحادي (+).

const str = '123.45'; console.log(parseInt(str)); // 123 console.log(parseFloat(str)); // 123.45 console.log(+str); // 123.45

تحويل رقم إلى سلسلة نصية

كيف تحول رقمًا إلى سلسلة نصية؟

الشرح: استخدم String()، number.toString()، أو دمج السلاسل النصية.

const num = 123; console.log(String(num)); // '123' console.log(num.toString()); // '123' console.log('' + num); // '123'

ما هو `JSON.stringify`؟

ماذا يفعل JSON.stringify؟

الشرح: يحول كائن أو قيمة JavaScript إلى سلسلة JSON.

const obj = { name: 'أليس', age: 30 }; const jsonString = JSON.stringify(obj); console.log(jsonString); // '{"name":"Alice","age":30}'

ما هو `JSON.parse`؟

ماذا يفعل JSON.parse؟

الشرح: يقوم بتحليل سلسلة JSON، وإنشاء قيمة أو كائن JavaScript الموصوف بواسطة السلسلة.

const jsonString = '{"name":"أليس","age":30}'; const obj = JSON.parse(jsonString); console.log(obj.name); // 'أليس'

عامل الانتشار في المصفوفات (Spread Operator in Arrays)

كيف يُستخدم عامل الانتشار مع المصفوفات؟

الشرح: يسمح بفتح كائن قابل للتكرار (مثل مصفوفة) في أماكن يُتوقع فيها صفر أو أكثر من الوسيطات أو العناصر. مفيد للنسخ والدمج وإضافة العناصر.

const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4] const copy = [...arr1]; // [1, 2]

عامل الانتشار في الكائنات (Spread Operator in Objects)

كيف يُستخدم عامل الانتشار مع الكائنات؟

الشرح: يسمح بنسخ ودمج خصائص الكائن.

const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 } const copy = { ...obj1 }; // { a: 1, b: 2 }

تفكيك المصفوفات (Destructuring Arrays)

اشرح تفكيك المصفوفات مع مثال.

الشرح: هو بناء جملة يجعل من الممكن فك قيم المصفوفات إلى متغيرات مميزة.

const [a, b] = [10, 20]; console.log(a); // 10 console.log(b); // 20 const [x, , z] = [1, 2, 3]; console.log(z); // 3

تفكيك الكائنات (Destructuring Objects)

اشرح تفكيك الكائنات مع مثال.

الشرح: يجعل من الممكن فك خصائص الكائنات إلى متغيرات مميزة.

const person = { name: 'بوب', age: 25 }; const { name, age } = person; console.log(name); // 'بوب' console.log(age); // 25 const { name: personName } = person; console.log(personName); // 'بوب'

تطبيق `call`

كيف يمكنك تطبيق نسخة أساسية من Function.prototype.call؟

الشرح: تستدعي call دالة بقيمة this محددة ووسيطات مقدمة بشكل فردي. يمكنك إرفاق الدالة بسياق this، واستدعائها، ثم إزالتها.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // استخدم مفتاحًا فريدًا context[uniqueId] = this; const result = context[uniqueId](...args); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myCall({ name: 'تشارلي' }, 'مرحباً', '!'); // مرحباً، تشارلي!

تطبيق `apply`

كيف يمكنك تطبيق نسخة أساسية من Function.prototype.apply؟

الشرح: تشبه apply call، لكنها تقبل الوسيطات كمصفوفة.

Function.prototype.myApply = function(context, argsArray) { context = context || window; const uniqueId = Symbol(); context[uniqueId] = this; const result = context[uniqueId](...(argsArray || [])); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myApply({ name: 'ديفيد' }, ['أهلاً', '.']); // أهلاً، ديفيد.

شرح حلقة الأحداث (Event Loop)

شرح موجز لدورة الأحداث في JavaScript.

الشرح: JavaScript أحادية الخيط (single-threaded)، ولكنها تحقق التزامن باستخدام حلقة الأحداث (event loop). يتعامل مكدس الاستدعاءات (call stack) مع الكود المتزامن. تتعامل واجهات برمجة تطبيقات الويب (Web APIs) مع العمليات غير المتزامنة (مثل setTimeout، fetch). عندما تنتهي عملية غير متزامنة، ينتقل رد الاتصال (callback) الخاص بها إلى قائمة انتظار ردود الاتصال (callback queue) (أو قائمة انتظار المهام الدقيقة microtask queue للوعود Promises). تتحقق حلقة الأحداث باستمرار مما إذا كان مكدس الاستدعاءات فارغًا؛ إذا كان كذلك، فإنها تنقل رد الاتصال التالي من قائمة الانتظار إلى المكدس للتنفيذ.

console.log('Start'); setTimeout(() => { console.log('Timeout Callback'); // Goes to Callback Queue }, 0); Promise.resolve().then(() => { console.log('Promise Resolved'); // Goes to Microtask Queue }); console.log('End'); // Output Order: Start, End, Promise Resolved, Timeout Callback // (Microtasks run before Macrotasks/Callbacks)

البحث الثنائي (Binary Search)

تنفيذ دالة بحث ثنائي لمصفوفة مرتبة.

الشرح: يبحث البحث الثنائي بكفاءة عن عنصر في مصفوفة مرتبة عن طريق تقسيم نطاق البحث إلى النصف بشكل متكرر. إذا كانت قيمة مفتاح البحث أقل من العنصر في منتصف النطاق، فقم بتضييق النطاق إلى النصف السفلي. وإلا، قم بتضييقه إلى النصف العلوي. تستمر في ذلك حتى يتم العثور على القيمة أو يصبح النطاق فارغًا.

function binarySearch(sortedArray, key) { let start = 0; let end = sortedArray.length - 1; while (start <= end) { let middle = Math.floor((start + end) / 2); if (sortedArray[middle] === key) { return middle; // Found } else if (sortedArray[middle] < key) { start = middle + 1; // Search right half } else { end = middle - 1; // Search left half } } return -1; // Not found } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

الفرز بالدمج (Merge Sort)

تنفيذ خوارزمية الفرز بالدمج.

الشرح: الفرز بالدمج هي خوارزمية 'فرق تسد'. تقوم بتقسيم مصفوفة الإدخال إلى نصفين، وتستدعي نفسها للنصفين، ثم تدمج النصفين المرتبين. دالة الدمج حاسمة؛ فهي تجمع مصفوفتين فرعيتين مرتبتين في مصفوفة واحدة مرتبة.

function mergeSort(arr) { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); return merge(left, right); } function merge(left, right) { let result = []; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); } console.log(mergeSort([38, 27, 43, 3, 9, 82, 10])); // [3, 9, 10, 27, 38, 43, 82]

الفرز السريع (Quick Sort)

تنفيذ خوارزمية الفرز السريع.

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

function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[arr.length - 1]; const left = []; const right = []; for (let i = 0; i < arr.length - 1; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...quickSort(left), pivot, ...quickSort(right)]; } console.log(quickSort([10, 8, 2, 1, 6, 3, 9, 4, 7, 5])); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

فرز الفقاعة (Bubble Sort)

تنفيذ خوارزمية فرز الفقاعة.

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

function bubbleSort(arr) { let n = arr.length; let swapped; do { swapped = false; for (let i = 0; i < n - 1; i++) { if (arr[i] > arr[i + 1]) { [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Swap swapped = true; } } n--; // Optimization: last element is already in place } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

أطول سلسلة فرعية بدون أحرف متكررة

بمعلومية سلسلة نصية، أوجد طول أطول سلسلة فرعية بدون أحرف متكررة.

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

function lengthOfLongestSubstring(s) { let maxLength = 0; let start = 0; const charMap = {}; for (let end = 0; end < s.length; end++) { const char = s[end]; if (charMap[char] >= start) { start = charMap[char] + 1; } charMap[char] = end; maxLength = Math.max(maxLength, end - start + 1); } return maxLength; } console.log(lengthOfLongestSubstring('abcabcbb')); // 3 ('abc') console.log(lengthOfLongestSubstring('bbbbb')); // 1 ('b') console.log(lengthOfLongestSubstring('pwwkew')); // 3 ('wke')

تنفيذ قائمة متصلة (Linked List)

تنفيذ قائمة متصلة مفردة (Singly Linked List) مع طريقتي add و print.

الشرح: القائمة المتصلة هي بنية بيانات خطية لا يتم تخزين العناصر فيها في مواقع ذاكرة متجاورة. يشير كل عنصر (عقدة) إلى العنصر التالي. تحتاج إلى فئة Node وفئة LinkedList لإدارة الرأس وإضافة عقد جديدة.

class Node { constructor(data, next = null) { this.data = data; this.next = next; } } class LinkedList { constructor() { this.head = null; } add(data) { const newNode = new Node(data); if (!this.head) { this.head = newNode; } else { let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } } print() { let current = this.head; let list = ''; while (current) { list += current.data + ' -> '; current = current.next; } console.log(list + 'null'); } } const list = new LinkedList(); list.add(10); list.add(20); list.add(30); list.print(); // 10 -> 20 -> 30 -> null

تنفيذ شجرة البحث الثنائي (BST)

تنفيذ شجرة بحث ثنائي (Binary Search Tree) مع طريقتي insert و find.

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

class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } } class BST { constructor() { this.root = null; } insert(data) { const newNode = new Node(data); if (!this.root) { this.root = newNode; return; } this._insertNode(this.root, newNode); } _insertNode(node, newNode) { if (newNode.data < node.data) { if (!node.left) node.left = newNode; else this._insertNode(node.left, newNode); } else { if (!node.right) node.right = newNode; else this._insertNode(node.right, newNode); } } find(data) { return this._findNode(this.root, data); } _findNode(node, data) { if (!node) return null; if (data < node.data) return this._findNode(node.left, data); else if (data > node.data) return this._findNode(node.right, data); else return node; } } const bst = new BST(); bst.insert(10); bst.insert(5); bst.insert(15); bst.insert(7); console.log(bst.find(7)); // Node { data: 7, left: null, right: null } console.log(bst.find(12)); // null

تدوير مصفوفة

اكتب دالة لتدوير مصفوفة إلى اليمين بمقدار k خطوة.

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

function rotateArray(nums, k) { k = k % nums.length; // Handle cases where k > length if (k === 0) return nums; const partToMove = nums.splice(nums.length - k); nums.unshift(...partToMove); return nums; } console.log(rotateArray([1, 2, 3, 4, 5, 6, 7], 3)); // [5, 6, 7, 1, 2, 3, 4]

إيجاد تقاطع مصفوفتين

بمعلومية مصفوفتين، اكتب دالة لحساب تقاطعهما (العناصر المشتركة بينهما).

الشرح: طريقة جيدة هي تحويل إحدى المصفوفتين إلى Set للبحث في وقت O(1) في المتوسط. ثم، قم بالتكرار عبر المصفوفة الثانية وتحقق مما إذا كان كل عنصر موجودًا في المجموعة. اجمع التطابقات.

function intersection(nums1, nums2) { const set1 = new Set(nums1); const resultSet = new Set(); for (const num of nums2) { if (set1.has(num)) { resultSet.add(num); } } return [...resultSet]; } console.log(intersection([1, 2, 2, 1], [2, 2])); // [2] console.log(intersection([4, 9, 5], [9, 4, 9, 8, 4])); // [9, 4]

تجميع المتجانسات (Anagrams)

بمعلومية مصفوفة من السلاسل النصية، قم بتجميع المتجانسات معًا.

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

function groupAnagrams(strs) { const map = {}; for (const str of strs) { const sortedStr = str.split('').sort().join(''); if (!map[sortedStr]) { map[sortedStr] = []; } map[sortedStr].push(str); } return Object.values(map); } console.log(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])); // Output: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]

نقل الأصفار إلى النهاية

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

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

function moveZeroes(nums) { let nonZeroIndex = 0; // Move all non-zero elements to the front for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Fill the rest with zeros for (let i = nonZeroIndex; i < nums.length; i++) { nums[i] = 0; } return nums; } console.log(moveZeroes([0, 1, 0, 3, 12])); // [1, 3, 12, 0, 0]

الحد الأقصى للمصفوفة الفرعية (خوارزمية كادين)

بمعلومية مصفوفة من الأعداد الصحيحة nums، أوجد المصفوفة الفرعية المتجاورة (التي تحتوي على رقم واحد على الأقل) التي لها أكبر مجموع وأعد مجموعها.

الشرح: خوارزمية كادين هي طريقة فعالة لحل هذه المشكلة. قم بالتكرار عبر المصفوفة، مع تتبع الحد الأقصى للمجموع currentMax الذي ينتهي عند الموضع الحالي والحد الأقصى للمجموع globalMax الذي تم العثور عليه حتى الآن. إذا أصبح currentMax سالبًا، أعد تعيينه إلى 0 (أو بالأحرى، العنصر الحالي).

function maxSubArray(nums) { let globalMax = -Infinity; let currentMax = 0; for (let i = 0; i < nums.length; i++) { currentMax = Math.max(nums[i], currentMax + nums[i]); if (currentMax > globalMax) { globalMax = currentMax; } } return globalMax; } console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6 (from [4, -1, 2, 1])

تباديل سلسلة نصية

اكتب دالة لإنشاء جميع تباديل سلسلة نصية معينة.

الشرح: يتم حل هذا عادةً باستخدام العودية (recursion) والتعقب الخلفي (backtracking). لكل حرف، قم بتثبيته وقم بإنشاء تباديل بشكل متكرر لبقية السلسلة. الحالة الأساسية: عندما تحتوي السلسلة على حرف واحد فقط، أعدها.

function stringPermutations(str) { if (str.length === 0) return ['']; if (str.length === 1) return [str]; const results = []; for (let i = 0; i < str.length; i++) { const char = str[i]; const remainingChars = str.slice(0, i) + str.slice(i + 1); const perms = stringPermutations(remainingChars); for (const perm of perms) { results.push(char + perm); } } return [...new Set(results)]; // Use Set to handle duplicate chars if needed } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

القاسم المشترك الأكبر (GCD)

اكتب دالة لإيجاد القاسم المشترك الأكبر (GCD) لعددين.

الشرح: خوارزمية إقليدس هي طريقة فعالة. إذا كان b يساوي 0، فإن a هو GCD. وإلا، فإن GCD لـ a و b هو نفسه GCD لـ b و a % b (باقي قسمة a على b).

function gcd(a, b) { while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } console.log(gcd(48, 18)); // 6 console.log(gcd(101, 103)); // 1

المضاعف المشترك الأصغر (LCM)

اكتب دالة لإيجاد المضاعف المشترك الأصغر (LCM) لعددين.

الشرح: يمكن حساب LCM باستخدام GCD بالصيغة: LCM(a, b) = |a * b| / GCD(a, b).

function gcd(a, b) { // Helper from previous problem while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } function lcm(a, b) { if (a === 0 || b === 0) return 0; return Math.abs(a * b) / gcd(a, b); } console.log(lcm(15, 20)); // 60 console.log(lcm(7, 5)); // 35

تنفيذ Promise.all

نفّذ دالة تتصرف مثل Promise.all.

الشرح: تقبل Promise.all مصفوفة من الوعود وتُرجع وعدًا واحدًا يحل عندما يتم حل جميع الوعود المدخلة، أو يرفض إذا تم رفض أي وعد مدخل. القيمة المحلولة هي مصفوفة من القيم المحلولة.

function myPromiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let completedCount = 0; const numPromises = promises.length; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = value; completedCount++; if (completedCount === numPromises) { resolve(results); } }) .catch(reject); // Reject immediately on any error }); }); } // Example usage: const p1 = Promise.resolve(3); const p2 = 42; const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); myPromiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, 'foo']

عكس الشجرة الثنائية

اكتب دالة لعكس شجرة ثنائية.

الشرح: لعكس شجرة ثنائية، يمكنك تبديل الأبناء الأيسر والأيمن لكل عقدة. يمكن القيام بذلك بشكل متكرر أو تكراري (باستخدام قائمة انتظار أو مكدس).

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function invertTree(root) { if (root === null) { return null; } // Swap the children [root.left, root.right] = [root.right, root.left]; // Recur for left and right children invertTree(root.left); invertTree(root.right); return root; } // Example: 4 -> [2, 7] -> [1, 3, 6, 9] // Becomes: 4 -> [7, 2] -> [9, 6, 3, 1]

أقصى عمق للشجرة الثنائية

بمعلومية شجرة ثنائية، أوجد أقصى عمق لها.

الشرح: أقصى عمق هو عدد العقد على طول أطول مسار من العقدة الجذرية إلى أبعد عقدة ورقية. يمكن العثور على ذلك بشكل متكرر: MaxDepth = 1 + max(MaxDepth(left), MaxDepth(right)). الحالة الأساسية هي عندما تكون العقدة فارغة (null)، يكون عمقها 0.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function maxDepth(root) { if (root === null) { return 0; } const leftDepth = maxDepth(root.left); const rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; } // Example: 3 -> [9, 20] -> [null, null, 15, 7] const tree = new Node(3, new Node(9), new Node(20, new Node(15), new Node(7))); console.log(maxDepth(tree)); // 3

أفضل وقت للشراء/البيع

تُعطى مصفوفة prices حيث prices[i] هو سعر سهم معين في اليوم i. تريد تحقيق أقصى ربح عن طريق اختيار يوم واحد للشراء سهم واختيار يوم مختلف في المستقبل لبيع هذا السهم. أعد أقصى ربح.

الشرح: قم بالتكرار عبر الأسعار، مع تتبع الحد الأدنى للسعر الذي تم مواجهته حتى الآن (minPrice) والحد الأقصى للربح الذي تم العثور عليه حتى الآن (maxProfit). لكل يوم، احسب الربح المحتمل إذا بعت اليوم (السعر الحالي - minPrice) وقم بتحديث maxProfit إذا كان أعلى.

function maxProfit(prices) { let minPrice = Infinity; let maxProfit = 0; for (let i = 0; i < prices.length; i++) { if (prices[i] < minPrice) { minPrice = prices[i]; } else if (prices[i] - minPrice > maxProfit) { maxProfit = prices[i] - minPrice; } } return maxProfit; } console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5 (Buy at 1, Sell at 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (No profit possible)

الرقم الفردي

بمعلومية مصفوفة غير فارغة من الأعداد الصحيحة nums، يظهر كل عنصر مرتين باستثناء واحد. أوجد هذا الرقم الفردي.

الشرح: طريقة فعالة جدًا لحل هذا هي استخدام عامل XOR (التبديل الثنائي) (^). يؤدي XORing لعدد بنفسه إلى 0. يؤدي XORing لعدد بـ 0 إلى العدد نفسه. إذا قمت بعمل XOR لجميع الأرقام في المصفوفة، فستلغي الأزواج بعضها البعض (تصبح 0)، تاركة الرقم الفردي فقط.

function singleNumber(nums) { let result = 0; for (const num of nums) { result ^= num; } return result; } console.log(singleNumber([2, 2, 1])); // 1 console.log(singleNumber([4, 1, 2, 1, 2])); // 4

العنصر الأكثري

بمعلومية مصفوفة nums بحجم n، أعد العنصر الأكثري. العنصر الأكثري هو العنصر الذي يظهر أكثر من ⌊n / 2⌋ مرة.

الشرح: خوارزمية تصويت بوير-مور هي طريقة فعالة. قم بتهيئة candidate و count. كرر عبر المصفوفة. إذا كان count يساوي 0، فعيّن العنصر الحالي كـ candidate. إذا كان العنصر الحالي يطابق candidate، فزد count؛ وإلا، فقلل count.

function majorityElement(nums) { let candidate = null; let count = 0; for (const num of nums) { if (count === 0) { candidate = num; } count += (num === candidate) ? 1 : -1; } return candidate; } console.log(majorityElement([3, 2, 3])); // 3 console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); // 2

تسلق السلالم

أنت تتسلق سلمًا. يستغرق n خطوة للوصول إلى القمة. في كل مرة يمكنك تسلق خطوة واحدة أو خطوتين. بكم طريقة مميزة يمكنك الوصول إلى القمة؟

الشرح: هذه مشكلة برمجة ديناميكية كلاسيكية، تشبه إلى حد كبير متتالية فيبوناتشي. عدد الطرق للوصول إلى الخطوة n هو مجموع طرق الوصول إلى الخطوة n-1 (بأخذ خطوة واحدة) وطرق الوصول إلى الخطوة n-2 (بأخذ خطوتين). dp[n] = dp[n-1] + dp[n-2].

function climbStairs(n) { if (n <= 2) return n; let oneStepBefore = 2; let twoStepsBefore = 1; let allWays = 0; for (let i = 3; i <= n; i++) { allWays = oneStepBefore + twoStepsBefore; twoStepsBefore = oneStepBefore; oneStepBefore = allWays; } return allWays; } console.log(climbStairs(2)); // 2 (1+1, 2) console.log(climbStairs(3)); // 3 (1+1+1, 1+2, 2+1) console.log(climbStairs(4)); // 5

ناتج المصفوفة باستثناء الذات

بمعلومية مصفوفة من الأعداد الصحيحة nums، أعد مصفوفة answer بحيث يكون answer[i] مساويًا لناتج جميع عناصر nums باستثناء nums[i]. لا تستخدم عامل القسمة.

الشرح: يمكنك حل هذا عن طريق حساب النواتج السابقة واللاحقة. تكون النتيجة عند الفهرس i هي ناتج جميع العناصر قبل i مضروبة في ناتج جميع العناصر بعد i.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Calculate prefix products let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Calculate suffix products and multiply with prefix let suffix = 1; for (let i = n - 1; i >= 0; i--) { answer[i] *= suffix; suffix *= nums[i]; } return answer; } console.log(productExceptSelf([1, 2, 3, 4])); // [24, 12, 8, 6]

عدد الجزر

بمعلومية خريطة شبكية ثنائية الأبعاد من '1's (أرض) و '0's (ماء)، احسب عدد الجزر. تُحاط الجزيرة بالماء وتتشكل عن طريق ربط الأراضي المتجاورة أفقيًا أو رأسيًا.

الشرح: قم بالتكرار عبر كل خلية من الشبكة. إذا وجدت '1' (أرض)، فزد عدد الجزر وابدأ بحثًا في العمق أولاً (DFS) أو بحثًا في العرض أولاً (BFS) من تلك الخلية. أثناء البحث، ضع علامة '0' (أو زار) على جميع '1's المتصلة لتجنب إعادة عدها.

function numIslands(grid) { if (!grid || grid.length === 0) return 0; let count = 0; const rows = grid.length; const cols = grid[0].length; function dfs(r, c) { if (r < 0 || c < 0 || r >= rows || c >= cols || grid[r][c] === '0') { return; } grid[r][c] = '0'; // Mark as visited dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1); } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === '1') { count++; dfs(r, c); } } } return count; } const grid1 = [ ['1', '1', '1', '1', '0'], ['1', '1', '0', '1', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '0', '0', '0'] ]; // console.log(numIslands(grid1)); // 1 (Need to run on a copy or restore grid)

ذاكرة التخزين المؤقت LRU

صمم ونفّذ بنية بيانات لذاكرة التخزين المؤقت الأقل استخدامًا مؤخرًا (LRU). يجب أن تدعم عمليات get و put.

الشرح: تقوم ذاكرة التخزين المؤقت LRU بإزالة العنصر الأقل استخدامًا مؤخرًا عند الوصول إلى السعة. يستخدم تنفيذ شائع Map (أو خريطة تجزئة) لعمليات البحث في وقت O(1) في المتوسط وقائمة متصلة مزدوجة (Doubly Linked List) لتحديثات O(1) لحالة 'المستخدمة مؤخرًا'. تحافظ Map في JavaScript على ترتيب الإدراج، مما يمكن أن يبسط التنفيذ الأساسي.

class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); } get(key) { if (!this.cache.has(key)) return -1; const value = this.cache.get(key); this.cache.delete(key); // Remove to re-insert at the 'end' (most recent) this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } this.cache.set(key, value); } } const cache = new LRUCache(2); cache.put(1, 1); cache.put(2, 2); console.log(cache.get(1)); // 1 cache.put(3, 3); // Evicts 2 console.log(cache.get(2)); // -1

إنشاء الأقواس

بمعلومية n زوجًا من الأقواس، اكتب دالة لإنشاء جميع مجموعات الأقواس المنسقة جيدًا.

الشرح: هذه مشكلة تتبع خلفي كلاسيكية. حافظ على عدادات للأقواس المفتوحة والمغلقة. يمكنك إضافة قوس مفتوح إذا كان open < n. يمكنك إضافة قوس مغلق إذا كان close < open. الحالة الأساسية هي عندما يصل طول السلسلة إلى 2 * n.

function generateParenthesis(n) { const results = []; function backtrack(currentString, openCount, closeCount) { if (currentString.length === n * 2) { results.push(currentString); return; } if (openCount < n) { backtrack(currentString + '(', openCount + 1, closeCount); } if (closeCount < openCount) { backtrack(currentString + ')', openCount, closeCount + 1); } } backtrack('', 0, 0); return results; } console.log(generateParenthesis(3)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

الحاوية ذات أكبر قدر من الماء

بمعلومية n من الأعداد الصحيحة غير السالبة a1, a2, ..., an، حيث يمثل كل منها نقطة بالإحداثيات (i, ai). يتم رسم n خطوط رأسية بحيث تكون النقطتان النهائيتان للخط i عند (i, ai) و (i, 0). أوجد خطين يشكلان، مع المحور السيني، حاوية تحتوي على أكبر قدر من الماء.

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

function maxArea(height) { let max = 0; let left = 0; let right = height.length - 1; while (left < right) { const h = Math.min(height[left], height[right]); const w = right - left; max = Math.max(max, h * w); if (height[left] < height[right]) { left++; } else { right--; } } return max; } console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49

مجموع ثلاثة أصفار (3Sum)

بمعلومية مصفوفة nums من n من الأعداد الصحيحة، هل توجد عناصر a, b, c في nums بحيث a + b + c = 0؟ أوجد جميع المجموعات الثلاثية الفريدة في المصفوفة التي تعطي مجموع صفر.

الشرح: أولاً، قم بفرز المصفوفة. ثم، كرر عبر المصفوفة بمؤشر واحد i. لكل i، استخدم مؤشرين آخرين، left (يبدأ عند i+1) و right (يبدأ من النهاية)، للعثور على رقمين مجموعهما -nums[i]. تعامل مع التكرارات لضمان مجموعات ثلاثية فريدة.

function threeSum(nums) { nums.sort((a, b) => a - b); const results = []; for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; // Skip duplicates for i let left = i + 1; let right = nums.length - 1; let target = -nums[i]; while (left < right) { let sum = nums[left] + nums[right]; if (sum === target) { results.push([nums[i], nums[left], nums[right]]); while (left < right && nums[left] === nums[left + 1]) left++; // Skip duplicates while (left < right && nums[right] === nums[right - 1]) right--; // Skip duplicates left++; right--; } else if (sum < target) { left++; } else { right--; } } } return results; } console.log(threeSum([-1, 0, 1, 2, -1, -4])); // [[-1, -1, 2], [-1, 0, 1]]

موضع البحث والإدخال

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

الشرح: هذا اختلاف في البحث الثنائي. قم بإجراء بحث ثنائي، ولكن إذا لم يتم العثور على العنصر، فسينتهي المؤشر start في الموضع الذي يجب إدراج العنصر فيه.

function searchInsert(nums, target) { let start = 0; let end = nums.length - 1; while (start <= end) { let mid = Math.floor((start + end) / 2); if (nums[mid] === target) { return mid; } else if (nums[mid] < target) { start = mid + 1; } else { end = mid - 1; } } return start; // If not found, start is the insertion point } console.log(searchInsert([1, 3, 5, 6], 5)); // 2 console.log(searchInsert([1, 3, 5, 6], 2)); // 1 console.log(searchInsert([1, 3, 5, 6], 7)); // 4

دمج قائمتين مرتبتين (القوائم المتصلة)

ادمج قائمتين متصلتين مرتبتين وأعدهما كقائمة مرتبة جديدة. يجب أن تُنشأ القائمة الجديدة عن طريق ربط عقد القائمتين الأوليين معًا.

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

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function mergeTwoLists(l1, l2) { let dummyHead = new ListNode(); let current = dummyHead; while (l1 !== null && l2 !== null) { if (l1.val < l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = l1 !== null ? l1 : l2; return dummyHead.next; } // Example: 1->2->4 and 1->3->4 => 1->1->2->3->4->4

شجرة متناظرة

بالنظر إلى شجرة ثنائية، تحقق مما إذا كانت مرآة لنفسها (أي متناظرة حول مركزها).

الشرح: يمكن حل هذا بشكل متكرر. تكون الشجرة متناظرة إذا كانت الشجرة الفرعية اليسرى صورة مرآة للشجرة الفرعية اليمنى. أنشئ دالة مساعدة isMirror(t1, t2) تتحقق مما إذا كانت شجرتان متطابقتين. يجب أن تتحقق من: 1. t1.val === t2.val. 2. t1.left هي مرآة لـ t2.right. 3. t1.right هي مرآة لـ t2.left.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function isSymmetric(root) { if (!root) return true; function isMirror(t1, t2) { if (!t1 && !t2) return true; if (!t1 || !t2 || t1.val !== t2.val) return false; return isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left); } return isMirror(root.left, root.right); } // مثال: 1 -> [2, 2] -> [3, 4, 4, 3] متناظر.

اجتياز ترتيب المستوى (BST/شجرة)

بالنظر إلى شجرة ثنائية، أعد اجتياز ترتيب المستوى لقيم العقد الخاصة بها. (أي، من اليسار إلى اليمين، مستوى تلو الآخر).

الشرح: هذا هو بحث أول بالعمق (BFS). استخدم قائمة انتظار. ابدأ بإضافة الجذر إلى قائمة الانتظار. بينما قائمة الانتظار ليست فارغة، عالج جميع العقد في المستوى الحالي. لكل عقدة معالجة، أضف أبنائها (إذا كانوا موجودين) إلى قائمة الانتظار للمستوى التالي.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; const currentLevel = []; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); currentLevel.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(currentLevel); } return result; } // مثال: 3 -> [9, 20] -> [null, null, 15, 7] // الإخراج: [[3], [9, 20], [15, 7]]

تحويل مصفوفة مرتبة إلى شجرة بحث ثنائية متوازنة الارتفاع (BST)

بالنظر إلى مصفوفة مرتبة تصاعديًا، قم بتحويلها إلى شجرة بحث ثنائية متوازنة الارتفاع (BST).

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

class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function sortedArrayToBST(nums) { if (!nums || nums.length === 0) return null; function buildBST(start, end) { if (start > end) return null; const mid = Math.floor((start + end) / 2); const node = new TreeNode(nums[mid]); node.left = buildBST(start, mid - 1); node.right = buildBST(mid + 1, end); return node; } return buildBST(0, nums.length - 1); } // مثال: [-10, -3, 0, 5, 9] // الإخراج: شجرة مثل [0, -3, 9, -10, null, 5, null]

تنفيذ `bind`

قم بتنفيذ نسختك الخاصة من Function.prototype.bind.

الشرح: bind تنشئ دالة جديدة، عند استدعائها، يكون الكلمة الرئيسية this الخاصة بها مضبوطة على القيمة المقدمة، مع تسلسل معين من الوسيطات يسبق أي وسيطات مقدمة عند استدعاء الدالة الجديدة. استخدم apply أو call داخل دالة مرتجعة، مع التعامل مع التطبيق الجزئي (الوسيطات المحددة مسبقًا).

Function.prototype.myBind = function(context, ...bindArgs) { const originalFunc = this; return function(...callArgs) { return originalFunc.apply(context, [...bindArgs, ...callArgs]); }; } const module = { x: 42, getX: function() { return this.x; } }; const unboundGetX = module.getX; console.log(unboundGetX()); // undefined (this is global/window) const boundGetX = unboundGetX.myBind(module); console.log(boundGetX()); // 42

البحث عن العنصر K-th الأكبر

ابحث عن العنصر K-th الأكبر في مصفوفة غير مرتبة. لاحظ أنه العنصر K-th الأكبر في الترتيب المرتب، وليس العنصر K-th المميز.

الشرح: طريقة بسيطة هي ترتيب المصفوفة ثم اختيار العنصر عند الفهرس n - k. تتضمن طريقة أكثر كفاءة للمصفوفات الكبيرة استخدام كومة حد أدنى أو خوارزمية اختيار مثل Quickselect.

function findKthLargest(nums, k) { // طريقة بسيطة: ترتيب nums.sort((a, b) => b - a); // ترتيب تنازلي return nums[k - 1]; // ملاحظة: Quickselect سيكون أكثر كفاءة في مقابلة // ولكن الترتيب أسهل في التنفيذ بسرعة. } console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2)); // 5 console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)); // 4

التحقق مما إذا كان الكائن يحتوي على خاصية

كيف يمكنك التحقق مما إذا كان الكائن يحتوي على خاصية معينة؟

الشرح: يمكنك استخدام عامل التشغيل in (يتحقق من الخصائص الخاصة والموروثة)، Object.prototype.hasOwnProperty.call(obj, prop) (يتحقق من الخصائص الخاصة فقط)، أو ببساطة obj.prop !== undefined (يمكن أن يكون معقدًا مع قيم undefined).

const person = { name: 'Eve', age: 28 }; function hasProp(obj, prop) { console.log(`باستخدام 'in': ${prop in obj}`); console.log(`باستخدام 'hasOwnProperty': ${Object.prototype.hasOwnProperty.call(obj, prop)}`); } hasProp(person, 'name'); // true, true hasProp(person, 'toString'); // true, false (toString موروثة)

عدد صحيح إلى روماني

اكتب دالة لتحويل عدد صحيح إلى تمثيله الروماني.

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

function intToRoman(num) { const map = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 }; let result = ''; for (let key in map) { while (num >= map[key]) { result += key; num -= map[key]; } } return result; } console.log(intToRoman(3)); // III console.log(intToRoman(58)); // LVIII console.log(intToRoman(1994)); // MCMXCIV

روماني إلى عدد صحيح

اكتب دالة لتحويل رقم روماني إلى عدد صحيح.

الشرح: أنشئ خريطة للرموز الرومانية إلى قيمها. كرر خلال السلسلة. إذا كانت قيمة الرمز الحالي أقل من قيمة الرمز التالي (مثل 'IV' أو 'IX')، اطرح القيمة الحالية؛ وإلا، أضفها.

function romanToInt(s) { const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; let result = 0; for (let i = 0; i < s.length; i++) { const currentVal = map[s[i]]; const nextVal = map[s[i + 1]]; if (nextVal > currentVal) { result -= currentVal; } else { result += currentVal; } } return result; } console.log(romanToInt('III')); // 3 console.log(romanToInt('LVIII')); // 58 console.log(romanToInt('MCMXCIV')); // 1994

تنفيذ `Set`

قم بتنفيذ بنية بيانات Set أساسية (بدون استخدام Set المدمج) مع طرق add و has و delete و size.

الشرح: يمكنك استخدام كائن JavaScript (خريطة تجزئة) كالتخزين الأساسي. ستكون المفاتيح عناصر المجموعة (قد تحتاج إلى التعامل مع كيفية تخزين أنواع مختلفة وضمان التفرد كمفاتيح).

class MySet { constructor() { this.items = {}; this._size = 0; } add(element) { if (!this.has(element)) { this.items[element] = element; // تخزين العنصر this._size++; return true; } return false; } delete(element) { if (this.has(element)) { delete this.items[element]; this._size--; return true; } return false; } has(element) { return Object.prototype.hasOwnProperty.call(this.items, element); } get size() { return this._size; } } const set = new MySet(); set.add(1); set.add(2); console.log(set.has(1)); // true console.log(set.size); // 2 set.delete(1); console.log(set.has(1)); // false

مثلث باسكال

بالنظر إلى عدد صحيح numRows، قم بإنشاء أول numRows من مثلث باسكال.

الشرح: في مثلث باسكال، كل رقم هو مجموع الرقمين الموجودين فوقه مباشرة. ابدأ بـ [[1]]. لكل صف لاحق، ابدأ وانتهِ بـ 1. كل عنصر وسطي هو مجموع العنصر في نفس الفهرس والفهرس السابق من الصف الأعلى.

function generatePascalsTriangle(numRows) { if (numRows === 0) return []; const triangle = [[1]]; for (let i = 1; i < numRows; i++) { const prevRow = triangle[i - 1]; const newRow = [1]; for (let j = 1; j < i; j++) { newRow.push(prevRow[j - 1] + prevRow[j]); } newRow.push(1); triangle.push(newRow); } return triangle; } console.log(generatePascalsTriangle(5)); // [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]

البحث عن كلمة

بالنظر إلى لوحة ثنائية الأبعاد وكلمة، ابحث عما إذا كانت الكلمة موجودة في الشبكة. يمكن بناء الكلمة من حروف الخلايا المجاورة بشكل متسلسل، حيث تكون الخلايا 'المجاورة' متجاورة أفقيًا أو رأسيًا.

الشرح: يتطلب هذا بحثًا أول بالعمق (DFS) مع التراجع. كرر خلال كل خلية. إذا تطابقت الخلية مع الحرف الأول من الكلمة، ابدأ DFS. تتحقق دالة DFS من الجيران، مع التأكد من أنها تتطابق مع الحرف التالي ولم يتم زيارتها في المسار الحالي. إذا فشل المسار، تراجع عن طريق إلغاء تحديد الخلية.

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // تم العثور على الكلمة if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // وضع علامة تم الزيارة const found = dfs(r + 1, c, index + 1) || dfs(r - 1, c, index + 1) || dfs(r, c + 1, index + 1) || dfs(r, c - 1, index + 1); board[r][c] = temp; // التراجع return found; } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (board[r][c] === word[0] && dfs(r, c, 0)) { return true; } } } return false; } const board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']]; console.log(exist(board, 'ABCCED')); // true console.log(exist(board, 'SEE')); // true console.log(exist(board, 'ABCB')); // false

النافذة الفرعية الدنيا

بالنظر إلى سلسلتين s و t، ابحث عن النافذة الدنيا في s التي ستحتوي على جميع الأحرف في t. إذا لم تكن هناك مثل هذه النافذة في s تغطي جميع الأحرف في t، أعد السلسلة الفارغة "".

الشرح: استخدم أسلوب النافذة المنزلقة بمؤشرين (left و right) وخرائط تجزئة. تخزن إحدى الخرائط عدد الأحرف المطلوبة من t. وتخزن خريطة أخرى عدد الأحرف في النافذة الحالية. قم بتوسيع النافذة باستخدام right. بمجرد أن تحتوي النافذة على جميع الأحرف المطلوبة، حاول تقليصها باستخدام left للعثور على أصغر نافذة ممكنة.

function minWindow(s, t) { if (!t || !s || s.length < t.length) return ""; const tMap = {}; for (const char of t) tMap[char] = (tMap[char] || 0) + 1; let required = Object.keys(tMap).length; let formed = 0; const windowMap = {}; let left = 0; let minLen = Infinity; let result = ""; for (let right = 0; right < s.length; right++) { const char = s[right]; windowMap[char] = (windowMap[char] || 0) + 1; if (tMap[char] && windowMap[char] === tMap[char]) { formed++; } while (left <= right && formed === required) { if (right - left + 1 < minLen) { minLen = right - left + 1; result = s.substring(left, right + 1); } const leftChar = s[left]; windowMap[leftChar]--; if (tMap[leftChar] && windowMap[leftChar] < tMap[leftChar]) { formed--; } left++; } } return result; } console.log(minWindow('ADOBECODEBANC', 'ABC')); // 'BANC'

عكس قائمة مرتبطة

بالنظر إلى head، رأس قائمة مرتبطة فردية، اعكس القائمة، وأعد القائمة المعكوسة.

الشرح: تحتاج إلى تكرار القائمة، وتغيير مؤشر next لكل عقدة ليشير إلى العقدة السابقة. تتبع العقد previous و current و next أثناء التكرار.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function reverseList(head) { let prev = null; let current = head; let next = null; while (current !== null) { next = current.next; // تخزين العقدة التالية current.next = prev; // عكس مؤشر العقدة الحالية prev = current; // نقل prev خطوة إلى الأمام current = next; // نقل current خطوة إلى الأمام } return prev; // الرأس الجديد هو آخر 'prev' } // مثال: 1->2->3->4->5 تصبح 5->4->3->2->1

اكتشاف دورة في قائمة مرتبطة

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

الشرح: استخدم خوارزمية 'Floyd's Tortoise and Hare'. اجعل مؤشرين، أحدهما يتحرك خطوة واحدة في كل مرة (slow) والآخر يتحرك خطوتين في كل مرة (fast). إذا كانت هناك دورة، فسيلحق المؤشر fast في النهاية بالمؤشر slow.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function hasCycle(head) { if (!head || !head.next) return false; let slow = head; let fast = head.next; while (slow !== fast) { if (!fast || !fast.next) return false; // وصل إلى النهاية، لا توجد دورة slow = slow.next; fast = fast.next.next; } return true; // التقت المؤشرات، توجد دورة } // مثال: 3->2->0->-4 مع -4 يشير مرة أخرى إلى 2. hasCycle يعيد true.

تنفيذ `Object.create`

قم بتنفيذ دالة تحاكي سلوك Object.create(proto).

الشرح: Object.create تنشئ كائنًا جديدًا، باستخدام كائن موجود كنموذج أولي للكائن الذي تم إنشاؤه حديثًا. يمكنك تحقيق ذلك عن طريق إنشاء دالة بناء مؤقتة، وتعيين النموذج الأولي الخاص بها إلى proto المدخل، ثم إرجاع مثيل جديد منها.

function myObjectCreate(proto) { if (typeof proto !== 'object' && typeof proto !== 'function') { if (proto !== null) { throw new TypeError('يجب أن يكون النموذج الأولي للكائن كائنًا أو لا شيء: ' + proto); } } function F() {} F.prototype = proto; return new F(); } const person = { isHuman: false, printIntroduction: function() { console.log(`اسمي ${this.name}. هل أنا إنسان؟ ${this.isHuman}`); } }; const me = myObjectCreate(person); me.name = 'Matthew'; me.isHuman = true; me.printIntroduction(); // اسمي Matthew. هل أنا إنسان؟ true console.log(Object.getPrototypeOf(me) === person); // true

ما هو Hoisting؟

اشرح Hoisting في JavaScript وقدم مثالاً.

الشرح: Hoisting هو سلوك JavaScript الافتراضي لنقل الإعلانات إلى أعلى النطاق الحالي (عام أو دالة) قبل تنفيذ الكود. يتم رفع إعلانات المتغيرات (var) وتهيئتها بـ undefined. يتم رفع إعلانات الدوال بالكامل (كل من الاسم والجسم). يتم رفع let و const ولكن لا يتم تهيئتهما، مما يؤدي إلى منطقة زمنية ميتة.

console.log(myVar); // undefined (var مرفوع ومهيأ بـ undefined) // console.log(myLet); // ReferenceError: لا يمكن الوصول إلى 'myLet' قبل التهيئة (TDZ) myFunc(); // 'مرحباً!' (إعلان الدالة مرفوع بالكامل) var myVar = 'أنا var'; let myLet = 'أنا let'; function myFunc() { console.log('مرحباً!'); }

اشرح تغلغل الأحداث والالتقاط

اشرح تغلغل الأحداث والالتقاط في سياق DOM.

الشرح: هاتان مرحلتان من انتشار الأحداث في HTML DOM. مرحلة الالتقاط: ينتقل الحدث من window إلى العنصر المستهدف. مرحلة التغلغل: ينتقل الحدث من العنصر المستهدف مرة أخرى إلى window. بشكل افتراضي، يتم تسجيل معظم معالجات الأحداث خلال مرحلة التغلغل. يمكنك استخدام addEventListener(type, listener, useCapture) مع useCapture = true للتعامل مع الأحداث خلال مرحلة الالتقاط.

// في هيكل HTML: <div><p><span>انقر علي</span></p></div> // إذا نقرت على <span>: // الالتقاط: window -> document -> html -> body -> div -> p -> span // التغلغل: span -> p -> div -> body -> html -> document -> window /* مثال JS div.addEventListener('click', () => console.log('تم النقر على Div'), true); // التقاط p.addEventListener('click', () => console.log('تم النقر على P'), false); // تغلغل span.addEventListener('click', () => console.log('تم النقر على Span'), false); // تغلغل // سيكون الإخراج: تم النقر على Div، تم النقر على Span، تم النقر على P */

تنفيذ `JSON.parse` يدويًا (أساسي)

حاول تنفيذ نسخة أساسية جدًا من JSON.parse (تعامل مع الكائنات البسيطة، المصفوفات، السلاسل، الأرقام).

الشرح: هذه مهمة معقدة جدًا بالكامل، ولكن في إعداد برمجة حية، قد يُطلب منك التعامل مع مجموعة فرعية مبسطة جدًا. ستحتاج إلى تحليل السلسلة، وتحديد حدود الكائنات {} والمصفوفات []، وأزواج المفتاح-القيمة ("key": value)، وأنواع البيانات الأساسية. يمكن لـ eval أو new Function الغش في هذا ولكنها خطيرة. سيستخدم المحلل اللغوي الحقيقي أداة تحليل lexical/tokenization ومحلل لغوي.

function basicJsonParse(jsonString) { // تحذير: استخدام new Function غير آمن مثل eval. // هذا مثال مبسط وغير آمن للعرض فقط. // يتطلب التنفيذ الحقيقي محلل لغوي صحيح. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('JSON غير صالح: ' + e.message); } } console.log(basicJsonParse('{"a": 1, "b": ["x", "y"]}')); // { a: 1, b: ['x', 'y'] }

تسوية مصفوفة متداخلة بعمق

اكتب دالة لتسوية مصفوفة متداخلة بعمق.

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

function deepFlatten(arr) { let flattened = []; arr.forEach(item => { if (Array.isArray(item)) { flattened = flattened.concat(deepFlatten(item)); } else { flattened.push(item); } }); return flattened; // يوفر ES2019+ طريقة أبسط بكثير: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

تنفيذ Trie (شجرة البادئة)

قم بتنفيذ بنية بيانات Trie مع طرق insert و search و startsWith.

الشرح: Trie هي بنية بيانات تشبه الشجرة تستخدم لتخزين واسترداد المفاتيح بكفاءة في مجموعة بيانات من السلاسل. تمثل كل عقدة حرفًا، وتمثل المسارات من الجذر كلمات أو بادئات.

class TrieNode { constructor() { this.children = {}; this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } insert(word) { let node = this.root; for (const char of word) { if (!node.children[char]) { node.children[char] = new TrieNode(); } node = node.children[char]; } node.isEndOfWord = true; } search(word) { let node = this.root; for (const char of word) { if (!node.children[char]) return false; node = node.children[char]; } return node.isEndOfWord; } startsWith(prefix) { let node = this.root; for (const char of prefix) { if (!node.children[char]) return false; node = node.children[char]; } return true; } } const trie = new Trie(); trie.insert('apple'); console.log(trie.search('apple')); // true console.log(trie.search('app')); // false console.log(trie.startsWith('app')); // true

خلط مصفوفة (فيشر-يايتس)

اكتب دالة لخلط مصفوفة في مكانها باستخدام خوارزمية فيشر-يايتس (أو كنوث).

الشرح: تكرر خوارزمية فيشر-يايتس مصفوفة من العنصر الأخير إلى الأول. في كل تكرار، تقوم بتبديل العنصر الحالي بعنصر يتم اختياره عشوائيًا من بداية المصفوفة حتى العنصر الحالي (شاملًا).

function shuffleArray(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; // تبديل العناصر } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // على سبيل المثال، [3, 5, 1, 2, 4]

تكوين الدوال

قم بتنفيذ دالة compose التي تأخذ عدة دوال وتعيد دالة جديدة تطبقها من اليمين إلى اليسار.

الشرح: تكوين الدالة (f ∘ g)(x) = f(g(x)) يطبق دالة واحدة على نتيجة دالة أخرى. compose(f, g, h) عام يعني f(g(h(x))). استخدم reduceRight لتطبيق أنيق.

function compose(...funcs) { return function(initialArg) { return funcs.reduceRight((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const composedFunc = compose(subtract2, multiply3, add5); console.log(composedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

توجيه الدوال

قم بتنفيذ دالة pipe التي تأخذ عدة دوال وتعيد دالة جديدة تطبقها من اليسار إلى اليمين.

الشرح: على غرار compose، ولكن ترتيب التطبيق معكوس: pipe(f, g, h) يعني h(g(f(x))). استخدم reduce للتطبيق.

function pipe(...funcs) { return function(initialArg) { return funcs.reduce((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const pipedFunc = pipe(add5, multiply3, subtract2); console.log(pipedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

مشكلة تغيير العملة

بالنظر إلى مصفوفة من فئات العملات ومبلغ، ابحث عن الحد الأدنى لعدد العملات اللازمة لتكوين هذا المبلغ. افترض توفرًا لا نهائيًا من كل عملة.

الشرح: هذه مشكلة برمجة ديناميكية كلاسيكية. أنشئ مصفوفة dp حيث تخزن dp[i] الحد الأدنى من العملات اللازمة للمبلغ i. dp[i] = min(dp[i - coin]) + 1 لجميع العملات.

function coinChange(coins, amount) { const dp = new Array(amount + 1).fill(Infinity); dp[0] = 0; for (let i = 1; i <= amount; i++) { for (const coin of coins) { if (i - coin >= 0) { dp[i] = Math.min(dp[i], dp[i - coin] + 1); } } } return dp[amount] === Infinity ? -1 : dp[amount]; } console.log(coinChange([1, 2, 5], 11)); // 3 (5 + 5 + 1) console.log(coinChange([2], 3)); // -1

أدنى سلف مشترك (BST)

بالنظر إلى شجرة بحث ثنائية (BST)، ابحث عن أدنى سلف مشترك (LCA) لعقدتين معطيتين في BST.

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

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function lowestCommonAncestor(root, p, q) { let current = root; while (current) { if (p.val > current.val && q.val > current.val) { current = current.right; } else if (p.val < current.val && q.val < current.val) { current = current.left; } else { return current; // تم العثور على LCA } } return null; // لا يجب أن يحدث هذا في BST صالح مع p و q } // مثال: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA هو 6

تسلسل وإلغاء تسلسل الشجرة الثنائية

صمم خوارزمية لتسلسل وإلغاء تسلسل شجرة ثنائية.

الشرح: يحول التسلسل شجرة إلى سلسلة أو مصفوفة. يعيد إلغاء التسلسل بناء الشجرة. طريقة شائعة هي اجتياز ما قبل الترتيب (DFS). استخدم علامة خاصة (على سبيل المثال، '#') للعقد الخالية للحفاظ على البنية.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function serialize(root) { const values = []; function dfs(node) { if (!node) { values.push('#'); return; } values.push(node.val); dfs(node.left); dfs(node.right); } dfs(root); return values.join(','); } function deserialize(data) { const values = data.split(','); let index = 0; function buildTree() { if (values[index] === '#') { index++; return null; } const node = new Node(parseInt(values[index])); index++; node.left = buildTree(); node.right = buildTree(); return node; } return buildTree(); } // مثال: 1 -> [2, 3] -> [null, null, 4, 5] // متسلسل: '1,2,#,#,3,4,#,#,5,#,#'

تنفيذ `setInterval` باستخدام `setTimeout`

قم بتنفيذ دالة mySetInterval التي تحاكي setInterval ولكن تستخدم setTimeout بشكل متكرر.

الشرح: setInterval تشغل دالة بشكل متكرر كل N مللي ثانية. يمكنك تحقيق ذلك عن طريق جعل دالة تستدعي نفسها باستخدام setTimeout بعد كل تنفيذ. تحتاج أيضًا إلى طريقة لمسحها (myClearInterval).

function mySetInterval(callback, delay, ...args) { const interval = { timerId: null }; function run() { interval.timerId = setTimeout(() => { callback.apply(this, args); run(); // جدولة الاستدعاء التالي }, delay); } run(); return interval; // إرجاع كائن للسماح بالمسح } function myClearInterval(interval) { clearTimeout(interval.timerId); } // مثال على الاستخدام: let count = 0; const intervalId = mySetInterval(() => { console.log(`مرحباً: ${++count}`); if (count === 3) myClearInterval(intervalId); }, 500);

اجتياز الرسم البياني (BFS & DFS)

قم بتنفيذ بحث أول بالعمق (BFS) وبحث أول بالعمق (DFS) لرسوم بيانية معينة (تمثيل قائمة التجاور).

الشرح: يستكشف BFS الجيران أولاً (باستخدام قائمة انتظار)، بينما يستكشف DFS أبعد ما يمكن على طول كل فرع (باستخدام مكدس أو تكرار). تتبع العقد التي تمت زيارتها لتجنب الدورات والعمل الزائد.

const graph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: ['F'], F: [] }; function bfs(graph, startNode) { const queue = [startNode]; const visited = new Set(); const result = []; visited.add(startNode); while (queue.length > 0) { const node = queue.shift(); result.push(node); for (const neighbor of graph[node]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } } return result; } function dfs(graph, startNode) { const stack = [startNode]; const visited = new Set(); const result = []; while (stack.length > 0) { const node = stack.pop(); if (!visited.has(node)) { visited.add(node); result.push(node); // أضف الجيران بترتيب عكسي لمعالجتهم لاحقًا بالترتيب (اختياري) for (let i = graph[node].length - 1; i >= 0; i--) { stack.push(graph[node][i]); } } } return result; } console.log('BFS:', bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E', 'F'] console.log('DFS:', dfs(graph, 'A')); // ['A', 'B', 'D', 'C', 'E', 'F']

تدوير الصورة (مصفوفة)

يُعطى لك مصفوفة ثنائية الأبعاد n × n تمثل صورة. قم بتدوير الصورة بمقدار 90 درجة (باتجاه عقارب الساعة) في مكانها.

الشرح: طريقة شائعة لتحقيق ذلك هي عن طريق تبديل المصفوفة أولاً (تبديل matrix[i][j] بـ matrix[j][i]) ثم عكس كل صف.

function rotate(matrix) { const n = matrix.length; // تبديل for (let i = 0; i < n; i++) { for (let j = i; j < n; j++) { [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; } } // عكس كل صف for (let i = 0; i < n; i++) { matrix[i].reverse(); } return matrix; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(rotate(matrix)); // [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

اجتياز المصفوفة اللولبية

بالنظر إلى مصفوفة m × n، أعد جميع عناصر المصفوفة بترتيب لولبي.

الشرح: استخدم أربعة مؤشرات لتحديد الحدود: top و bottom و left و right. اجتز الصف العلوي، ثم العمود الأيمن، ثم الصف السفلي، ثم العمود الأيسر، مع تقليص الحدود بعد كل اجتياز، حتى يتقاطع left مع right أو top مع bottom.

function spiralOrder(matrix) { if (!matrix || matrix.length === 0) return []; const rows = matrix.length, cols = matrix[0].length; const result = []; let top = 0, bottom = rows - 1, left = 0, right = cols - 1; while (top <= bottom && left <= right) { for (let i = left; i <= right; i++) result.push(matrix[top][i]); top++; for (let i = top; i <= bottom; i++) result.push(matrix[i][right]); right--; if (top <= bottom) { for (let i = right; i >= left; i--) result.push(matrix[bottom][i]); bottom--; } if (left <= right) { for (let i = bottom; i >= top; i--) result.push(matrix[i][left]); left++; } } return result; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(spiralOrder(matrix)); // [1, 2, 3, 6, 9, 8, 7, 4, 5]

ضبط مصفوفة الأصفار

إذا كان العنصر في مصفوفة m x n هو 0، فقم بتعيين صفه وعموده بالكامل إلى 0. قم بذلك في مكانه.

الشرح: يتطلب النهج الساذج مساحة إضافية تبلغ O(m*n). للقيام بذلك في مساحة O(1) (أو O(m+n))، يمكنك استخدام الصف الأول والعمود الأول لتخزين معلومات حول الصفوف/الأعمدة التي تحتاج إلى تصفير. تحتاج إلى أعلام منفصلة لتحديد ما إذا كان الصف/العمود الأول نفسه يحتاج إلى تصفير.

function setZeroes(matrix) { const rows = matrix.length, cols = matrix[0].length; let firstColZero = false; for (let r = 0; r < rows; r++) { if (matrix[r][0] === 0) firstColZero = true; for (let c = 1; c < cols; c++) { if (matrix[r][c] === 0) { matrix[r][0] = 0; matrix[0][c] = 0; } } } for (let r = rows - 1; r >= 0; r--) { for (let c = cols - 1; c >= 1; c--) { if (matrix[r][0] === 0 || matrix[0][c] === 0) { matrix[r][c] = 0; } } if (firstColZero) matrix[r][0] = 0; } return matrix; } // مثال: [[1,1,1],[1,0,1],[1,1,1]] => [[1,0,1],[0,0,0],[1,0,1]]

تطبيق `Promise.race`

قم بتطبيق دالة تتصرف مثل Promise.race.

الشرح: تأخذ Promise.race مصفوفة من الوعود وتُرجع وعدًا واحدًا. يستقر هذا الوعد المرجع (يُحل أو يُرفض) بمجرد استقرار أي من الوعود المدخلة، بالقيمة أو السبب من ذلك الوعد.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // أو حل/رفض اعتمادًا على المواصفات } promises.forEach(promise => { Promise.resolve(promise).then(resolve, reject); }); }); } const p1 = new Promise((resolve) => setTimeout(resolve, 500, 'one')); const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'two')); myPromiseRace([p1, p2]).then(value => console.log(value)); // 'two'

نمط Singleton

قم بتطبيق نمط تصميم Singleton في JavaScript.

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

const Singleton = (function() { let instance; function createInstance() { // طرق ومتغيرات خاصة const privateVar = 'I am private'; function privateMethod() { console.log('Private'); } return { // طرق ومتغيرات عامة publicVar: 'I am public', publicMethod: function() { console.log('Public'); }, getPrivate: function() { return privateVar; } }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // صحيح console.log(instance1.getPrivate()); // 'أنا خاص'

التحقق من صحة عنوان IP

اكتب دالة للتحقق مما إذا كانت السلسلة المعطاة هي عنوان IPv4 أو IPv6 صالح.

الشرح: بالنسبة لـ IPv4، تحقق من وجود 4 أرقام (0-255) مفصولة بنقاط، بدون أصفار بادئة (باستثناء '0' نفسها). بالنسبة لـ IPv6، تحقق من وجود 8 مجموعات من 1-4 أرقام سداسية عشرية مفصولة بنقاط (تعامل مع الاختلافات مثل '::'). يمكن استخدام Regex، ولكن التحليل اليدوي غالبًا ما يكون أوضح للمقابلات.

function validIPAddress(IP) { function isIPv4(s) { const parts = s.split('.'); if (parts.length !== 4) return false; for (const part of parts) { if (!/^[0-9]+$/.test(part)) return false; if (part.length > 1 && part.startsWith('0')) return false; const num = parseInt(part); if (num < 0 || num > 255) return false; } return true; } function isIPv6(s) { const parts = s.split(':'); if (parts.length !== 8) return false; // Simplified: No '::' for (const part of parts) { if (part.length < 1 || part.length > 4) return false; if (!/^[0-9a-fA-F]+$/.test(part)) return false; } return true; } if (IP.includes('.')) return isIPv4(IP) ? 'IPv4' : 'Neither'; if (IP.includes(':')) return isIPv6(IP) ? 'IPv6' : 'Neither'; return 'Neither'; } console.log(validIPAddress('172.16.254.1')); // IPv4 console.log(validIPAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); // IPv6 (Simplified) console.log(validIPAddress('256.256.256.256')); // Neither

إيجاد العنصر الذروة

العنصر الذروة هو عنصر أكبر تمامًا من جيرانه. بالنظر إلى مصفوفة إدخال nums، ابحث عن عنصر ذروة وأرجع مؤشره.

الشرح: نظرًا لأن أي ذروة ستفي بالغرض، وتعتبر nums[-1] و nums[n] مساوية لـ -Infinity، يمكنك استخدام بحث ثنائي معدل. إذا كان nums[mid] أقل من nums[mid+1]، فيجب أن توجد ذروة إلى اليمين. بخلاف ذلك، يجب أن توجد ذروة إلى اليسار (أو mid نفسه هو ذروة).

function findPeakElement(nums) { let left = 0; let right = nums.length - 1; while (left < right) { let mid = Math.floor((left + right) / 2); if (nums[mid] < nums[mid + 1]) { left = mid + 1; // الذروة إلى اليمين } else { right = mid; // الذروة هي mid أو إلى اليسار } } return left; // 'left' سيكون مؤشر الذروة } console.log(findPeakElement([1, 2, 3, 1])); // 2 (مؤشر 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (مؤشر 6) أو 1 (مؤشر 2)

حساب البتات

بالنظر إلى عدد صحيح n، أرجع مصفوفة ans بطول n + 1 بحيث يكون لكل i (0 <= i <= n)، ans[i] هو عدد الآحاد في التمثيل الثنائي لـ i.

الشرح: يمكنك حل هذه المشكلة باستخدام البرمجة الديناميكية. لاحظ العلاقة: dp[i] = dp[i >> 1] + (i & 1). عدد الآحاد في i هو عدد الآحاد في i shifted right (أي، i/2)، بالإضافة إلى 1 إذا كان i فرديًا.

function countBits(n) { const dp = new Array(n + 1).fill(0); for (let i = 1; i <= n; i++) { dp[i] = dp[i >> 1] + (i & 1); // أو: dp[i] = dp[i & (i - 1)] + 1; (يزيل آخر بت مضبوط) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

قوة الاثنين

بالنظر إلى عدد صحيح n، أرجع true إذا كان قوة لاثنين. بخلاف ذلك، أرجع false.

الشرح: تتميز قوة الاثنين في التمثيل الثنائي بوجود بت '1' واحد بالضبط (على سبيل المثال، 1=1، 2=10، 4=100، 8=1000). خدعة ذكية على مستوى البت هي التحقق مما إذا كان n > 0 و (n & (n - 1)) === 0. إذا كان n قوة لاثنين، فإن n-1 سيكون لديه جميع البتات التي تقل عن '1' مضبوطة على '1'. ونتيجة ANDing لهم هي 0.

function isPowerOfTwo(n) { return n > 0 && (n & (n - 1)) === 0; } console.log(isPowerOfTwo(16)); // صحيح console.log(isPowerOfTwo(1)); // صحيح console.log(isPowerOfTwo(18)); // خطأ

دمج الفترات

بالنظر إلى مصفوفة من الفترات intervals حيث intervals[i] = [starti, endi]، ادمج جميع الفترات المتداخلة، وأرجع مصفوفة من الفترات غير المتداخلة.

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

function mergeIntervals(intervals) { if (intervals.length === 0) return []; intervals.sort((a, b) => a[0] - b[0]); const merged = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = merged[merged.length - 1]; const current = intervals[i]; if (current[0] <= last[1]) { // تداخل: دمج last[1] = Math.max(last[1], current[1]); } else { // لا يوجد تداخل: أضف فترة جديدة merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

فصل الكلمات

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

الشرح: استخدم البرمجة الديناميكية. قم بإنشاء مصفوفة منطقية dp حيث dp[i] صحيح إذا كان يمكن تقسيم السلسلة الفرعية s[0...i-1]. dp[i] صحيح إذا كان هناك j < i بحيث يكون dp[j] صحيحًا وكانت السلسلة الفرعية s[j...i-1] موجودة في القاموس.

function wordBreak(s, wordDict) { const wordSet = new Set(wordDict); const n = s.length; const dp = new Array(n + 1).fill(false); dp[0] = true; // حالة الأساس: سلسلة فارغة for (let i = 1; i <= n; i++) { for (let j = 0; j < i; j++) { if (dp[j] && wordSet.has(s.substring(j, i))) { dp[i] = true; break; } } } return dp[n]; } console.log(wordBreak('leetcode', ['leet', 'code'])); // صحيح console.log(wordBreak('applepenapple', ['apple', 'pen'])); // صحيح console.log(wordBreak('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'])); // خطأ

تطبيق `Array.prototype.flat`

قم بتطبيق نسختك الخاصة من Array.prototype.flat، والتي تنشئ مصفوفة جديدة تحتوي على جميع عناصر المصفوفات الفرعية مدمجة فيها بشكل متكرر حتى عمق محدد.

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

function myFlat(arr, depth = 1) { let flattened = []; arr.forEach(item => { if (Array.isArray(item) && depth > 0) { flattened.push(...myFlat(item, depth - 1)); } else { flattened.push(item); } }); return flattened; } console.log(myFlat([1, [2, [3, 4], 5], 6])); // [2, [3, 4], 5] console.log(myFlat([1, [2, [3, 4], 5], 6], 2)); // [1, 2, 3, 4, 5, 6]

عكس ترتيب الكلمات في سلسلة

بالنظر إلى سلسلة إدخال s، قم بعكس ترتيب الكلمات.

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

function reverseWords(s) { return s.trim().split(/\s+/).reverse().join(' '); } console.log(reverseWords('the sky is blue')); // 'blue is sky the' console.log(reverseWords(' hello world ')); // 'world hello' console.log(reverseWords('a good example')); // 'example good a'

محلل سلسلة الاستعلام

اكتب دالة لتحليل سلسلة استعلام URL إلى كائن.

الشرح: قسّم السلسلة بواسطة &. لكل جزء، قسّم بواسطة = للحصول على المفتاح والقيمة. فك تشفير مكونات URI لكل من المفتاح والقيمة. تعامل مع الحالات التي قد يظهر فيها المفتاح عدة مرات (إنشاء مصفوفة) أو لا توجد لديه قيمة.

function parseQueryString(query) { if (query.startsWith('?')) { query = query.substring(1); } const result = {}; if (!query) return result; query.split('&').forEach(pair => { const [key, value] = pair.split('='); const decodedKey = decodeURIComponent(key); const decodedValue = value !== undefined ? decodeURIComponent(value) : true; if (result.hasOwnProperty(decodedKey)) { if (Array.isArray(result[decodedKey])) { result[decodedKey].push(decodedValue); } else { result[decodedKey] = [result[decodedKey], decodedValue]; } } else { result[decodedKey] = decodedValue; } }); return result; } console.log(parseQueryString('?foo=bar&baz=qux&baz=quux&corge')); // { foo: 'bar', baz: ['qux', 'quux'], corge: true }

استخدام `Proxy` للتحقق من الصحة

أظهر كيفية استخدام كائن Proxy للتحقق من صحة خصائص الكائن.

الشرح: يسمح لك Proxy باعتراض وتخصيص العمليات التي يتم إجراؤها على كائن. استخدم مصيدة set للتحقق من القيم قبل تعيينها لخاصية. ألقِ خطأً إذا فشل التحقق من الصحة.

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('العمر ليس عددًا صحيحًا'); } if (value < 0 || value > 150) { throw new RangeError('العمر يبدو غير صالح'); } } // السلوك الافتراضي: تعيين الخاصية obj[prop] = value; return true; } }; const person = {}; const personProxy = new Proxy(person, validator); personProxy.age = 30; // موافق console.log(personProxy.age); // 30 // personProxy.age = 'ثلاثون'; // يرمي TypeError // personProxy.age = 200; // يرمي RangeError

غرف الاجتماعات

بالنظر إلى مصفوفة من فترات الاجتماعات [[start1, end1], [start2, end2], ...]، حدد ما إذا كان بإمكان شخص حضور جميع الاجتماعات.

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

function canAttendMeetings(intervals) { if (intervals.length < 2) return true; intervals.sort((a, b) => a[0] - b[0]); for (let i = 1; i < intervals.length; i++) { if (intervals[i][0] < intervals[i - 1][1]) { return false; // تم الكشف عن تداخل } } return true; } console.log(canAttendMeetings([[0, 30], [5, 10], [15, 20]])); // خطأ (5<30) console.log(canAttendMeetings([[7, 10], [2, 4]])); // صحيح

تطبيق `Promise.any`

قم بتطبيق دالة تتصرف مثل Promise.any.

الشرح: تأخذ Promise.any مصفوفة من الوعود وتُرجع وعدًا واحدًا. يفي هذا الوعد بمجرد أن يفي أي من الوعود المدخلة. إذا تم رفض جميع الوعود المدخلة، فإنه يرفض باستخدام AggregateError يحتوي على جميع أسباب الرفض.

function myPromiseAny(promises) { return new Promise((resolve, reject) => { const numPromises = promises.length; if (numPromises === 0) { reject(new AggregateError([], 'تم رفض جميع الوعود')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // حل بمجرد أن يفي أحدهم .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'تم رفض جميع الوعود')); } }); }); }); } // مثال: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // مثال: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

نمط المراقبة

قم بتطبيق نمط تصميم المراقبة (Pub/Sub) في JavaScript.

الشرح: يحدد نمط المراقبة تبعية واحد إلى متعدد بين الكائنات. عندما يغير كائن واحد (الموضوع) حالته، يتم إخطار جميع الكائنات التابعة له (المراقبون) وتحديثها تلقائيًا. يحافظ الموضوع على قائمة بالمراقبين ويوفر طرقًا لإضافتهم وإزالتهم وإخطارهم.

class Subject { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); } unsubscribe(observer) { this.observers = this.observers.filter(obs => obs !== observer); } notify(data) { this.observers.forEach(observer => observer.update(data)); } } class Observer { constructor(name) { this.name = name; } update(data) { console.log(`${this.name} received: ${data}`); } } const subject = new Subject(); const obs1 = new Observer('المراقب 1'); const obs2 = new Observer('المراقب 2'); subject.subscribe(obs1); subject.subscribe(obs2); subject.notify('حدث شيء ما!'); // المراقب 1 استقبل: حدث شيء ما! // المراقب 2 استقبل: حدث شيء ما!

إيجاد الرقم المكرر

بالنظر إلى مصفوفة من الأعداد الصحيحة nums تحتوي على n + 1 عدد صحيح حيث كل عدد صحيح في النطاق [1, n] شاملًا، ابحث عن الرقم المكرر الوحيد.

الشرح: نظرًا لأن الأرقام في [1, n] وحجم المصفوفة هو n+1، فإنه يضمن وجود تكرار واحد على الأقل. يمكن رسم هذا على مشكلة 'العثور على دورة في قائمة مرتبطة' (سلحفاة وفيل فلويد). تعامل مع قيم المصفوفة كمؤشرات: index -> nums[index].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // المرحلة 1: إيجاد نقطة التقاطع do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // المرحلة 2: إيجاد مدخل الدورة let ptr1 = nums[0]; let ptr2 = tortoise; while (ptr1 !== ptr2) { ptr1 = nums[ptr1]; ptr2 = nums[ptr2]; } return ptr1; } console.log(findDuplicate([1, 3, 4, 2, 2])); // 2 console.log(findDuplicate([3, 1, 3, 4, 2])); // 3

منقي HTML أساسي

اكتب دالة أساسية لتنقية سلسلة HTML، وإزالة العلامات التي يحتمل أن تكون ضارة (مثل <script>) مع الاحتفاظ بالعلامات الآمنة (مثل <p>، <b>).

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

function basicSanitize(html) { // تحذير: هذا مثال أساسي وغير آمن للغاية. // لا تستخدم هذا في الإنتاج. استخدم مكتبة مثل DOMPurify. // إزالة علامات السكريبت let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // مثال: إزالة سمات onclick (أساسي للغاية) sanitized = sanitized.replace(/\s(on\w+)=['"]?[^'"]*['"]?/gi, ''); return sanitized; } console.log(basicSanitize('<p>Hello <script>alert("XSS")</script> <b onclick="danger()">World</b></p>')); // <p>مرحباً <b >عالم</b></p>

مسافة التحرير

بالنظر إلى سلسلتين word1 و word2، أرجع الحد الأدنى لعدد العمليات (إدراج أو حذف أو استبدال) المطلوبة لتحويل word1 إلى word2.

الشرح: هذه مشكلة برمجة ديناميكية كلاسيكية (مسافة ليفنشتاين). قم بإنشاء مصفوفة ثنائية الأبعاد dp حيث dp[i][j] هي مسافة التحرير بين الأحرف i الأولى من word1 والأحرف j الأولى من word2. تتضمن علاقة التكرار النظر في تكلفة الإدراج والحذف والاستبدال.

function minDistance(word1, word2) { const m = word1.length, n = word2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const cost = word1[i - 1] === word2[j - 1] ? 0 : 1; dp[i][j] = Math.min( dp[i - 1][j] + 1, // حذف dp[i][j - 1] + 1, // إدراج dp[i - 1][j - 1] + cost // استبدال/تطابق ); } } return dp[m][n]; } console.log(minDistance('horse', 'ros')); // 3 console.log(minDistance('intention', 'execution')); // 5

أطول تسلسل متزايد (LIS)

بالنظر إلى مصفوفة الأعداد الصحيحة nums، أرجع طول أطول تسلسل متزايد تمامًا.

الشرح: استخدم البرمجة الديناميكية. لتكن dp[i] طول LIS الذي ينتهي عند المؤشر i. لحساب dp[i]، ابحث عن جميع j < i بحيث يكون nums[j] < nums[i]، وخذ dp[i] = 1 + max(dp[j]). الإجابة النهائية هي القيمة القصوى في مصفوفة dp.

function lengthOfLIS(nums) { if (nums.length === 0) return 0; const n = nums.length; const dp = new Array(n).fill(1); let maxLength = 1; for (let i = 1; i < n; i++) { for (let j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLength = Math.max(maxLength, dp[i]); } return maxLength; } console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); // 4 (2, 3, 7, 101)

مشكلة N-Queens

لغز N-Queens هو مشكلة وضع N من ملكات الشطرنج على رقعة شطرنج N×N بحيث لا تهدد ملكتان بعضهما البعض. بالنظر إلى N، أرجع حلاً واحدًا صالحًا (أو جميع الحلول).

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

function solveNQueens(n) { const results = []; const board = Array(n).fill('.').map(() => Array(n).fill('.')); function isSafe(row, col) { for (let i = 0; i < row; i++) if (board[i][col] === 'Q') return false; // تحقق من العمود for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // تحقق من القطر العلوي الأيسر for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // تحقق من القطر العلوي الأيمن return true; } function backtrack(row) { if (row === n) { results.push(board.map(r => r.join(''))); return; } for (let col = 0; col < n; col++) { if (isSafe(row, col)) { board[row][col] = 'Q'; backtrack(row + 1); board[row][col] = '.'; // تراجع } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

استخدام `WeakMap` للبيانات الخاصة

أظهر كيفية استخدام WeakMap لتخزين البيانات الخاصة لنسخ الفئات.

الشرح: يسمح لك WeakMap بربط البيانات بكائن بطريقة لا تمنع جمع البيانات المهملة إذا لم يعد الكائن مشارًا إليه. وهذا مفيد لإنشاء أعضاء 'خاصة' في فئات JavaScript قبل أن تكون الحقول الخاصة الأصلية متاحة على نطاق واسع أو لحالات استخدام محددة.

const privateData = new WeakMap(); class Person { constructor(name, age) { privateData.set(this, { name: name, age: age }); } getName() { return privateData.get(this).name; } getAge() { return privateData.get(this).age; } celebrateBirthday() { privateData.get(this).age++; } } const person = new Person('أليس', 30); console.log(person.getName()); // أليس person.celebrateBirthday(); console.log(person.getAge()); // 31 // console.log(person.name); // غير معرف - 'name' ليست خاصية عامة // console.log(privateData.get(person)); // يمكن الوصول إليه، ولكن ليس مباشرة عبر 'person.'

تطبيق `Promise.allSettled`

قم بتطبيق دالة تتصرف مثل Promise.allSettled.

الشرح: تأخذ Promise.allSettled مصفوفة من الوعود وتُرجع وعدًا واحدًا. يفي هذا الوعد عندما تستقر جميع الوعود المدخلة (إما وفيت أو رفضت). تكون قيمة الوفاء عبارة عن مصفوفة من الكائنات، يصف كل منها نتيجة وعد واحد ({status: 'fulfilled', value: ...} أو {status: 'rejected', reason: ...}) .

function myPromiseAllSettled(promises) { return new Promise((resolve) => { const numPromises = promises.length; const results = []; let settledCount = 0; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = { status: 'fulfilled', value: value }; }) .catch(reason => { results[index] = { status: 'rejected', reason: reason }; }) .finally(() => { settledCount++; if (settledCount === numPromises) { resolve(results); } }); }); }); } // مثال: myPromiseAllSettled([Promise.resolve(1), Promise.reject('err')]) // -> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}]

إيجاد وسيط مصفوفتين مفرزتين

بالنظر إلى مصفوفتين مفرزتين nums1 و nums2 بحجم m و n على التوالي، أرجع وسيط المصفوفتين المفرزتين.

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

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // تأكد أن nums1 أصغر const m = nums1.length, n = nums2.length; let low = 0, high = m; while (low <= high) { const partitionX = Math.floor((low + high) / 2); const partitionY = Math.floor((m + n + 1) / 2) - partitionX; const maxX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1]; const minX = (partitionX === m) ? Infinity : nums1[partitionX]; const maxY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1]; const minY = (partitionY === n) ? Infinity : nums2[partitionY]; if (maxX <= minY && maxY <= minX) { if ((m + n) % 2 === 0) { return (Math.max(maxX, maxY) + Math.min(minX, minY)) / 2; } else { return Math.max(maxX, maxY); } } else if (maxX > minY) { high = partitionX - 1; } else { low = partitionX + 1; } } throw new Error('المصفوفات المدخلة غير مفرزة'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

حل السودوكو

اكتب برنامجًا لحل لغز السودوكو عن طريق ملء الخلايا الفارغة.

الشرح: استخدم التراجع. ابحث عن خلية فارغة. حاول ملءها بأرقام من 1 إلى 9. لكل رقم، تحقق مما إذا كان صالحًا (لا ينتهك قواعد السودوكو). إذا كان صالحًا، فاستدعِ الحل بشكل متكرر. إذا أعادت التكرار صحيحًا، فقد تم العثور على حل. إذا لم يكن كذلك، تراجع (أعد تعيين الخلية) وحاول الرقم التالي.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // تحقق من الصف if (board[i][col] === numStr) return false; // تحقق من العمود const boxRow = 3 * Math.floor(row / 3) + Math.floor(i / 3); const boxCol = 3 * Math.floor(col / 3) + i % 3; if (board[boxRow][boxCol] === numStr) return false; // تحقق من المربع } return true; } function backtrack() { for (let r = 0; r < 9; r++) { for (let c = 0; c < 9; c++) { if (board[r][c] === '.') { for (let n = 1; n <= 9; n++) { const numStr = String(n); if (isValid(r, c, numStr)) { board[r][c] = numStr; if (backtrack()) return true; board[r][c] = '.'; // تراجع } } return false; // لم يتم العثور على رقم صالح } } } return true; // تم حل اللوحة } backtrack(); return board; } // يتطلب المثال لوحة 9x9 مع '.' للخلايا الفارغة.

تطبيق نمط برنامج وسيط أساسي

قم بتطبيق نمط برنامج وسيط بسيط يُرى غالبًا في أطر عمل الويب.

الشرح: تعالج وظائف البرنامج الوسيط طلبًا قبل أن يصل إلى المعالج النهائي. يمكن لكل برنامج وسيط تعديل الطلب/الاستجابة أو تمرير التحكم إلى البرنامج الوسيط next. قم بإنشاء فئة أو كائن يدير قائمة بوظائف البرنامج الوسيط وينفذها بالتسلسل.

class App { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); } handle(request) { let index = 0; const next = () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; middleware(request, next); } }; next(); } } const app = new App(); app.use((req, next) => { console.log('1: تسجيل...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: المصادقة...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: المعالج النهائي:', req); }); app.handle({ data: 'بعض الطلبات' }); // 1: تسجيل... // 2: المصادقة... // 3: المعالج النهائي: { data: 'بعض الطلبات', logged: true, authed: true }

الكشف عن دورة في رسم بياني موجه

بالنظر إلى رسم بياني موجه، اكتب دالة لتحديد ما إذا كان يحتوي على دورة.

الشرح: استخدم البحث في العمق أولاً (DFS). احتفظ بمجموعتين: visiting (العقد الموجودة حاليًا في مكدس التكرار) و visited (العقد التي تم استكشافها بالكامل). إذا صادفت عقدة في مجموعة visiting أثناء DFS، فقد وجدت حافة خلفية، مما يعني وجود دورة.

function hasCycleDirected(graph) { const visiting = new Set(); const visited = new Set(); const nodes = Object.keys(graph); function dfs(node) { visiting.add(node); for (const neighbor of graph[node]) { if (visiting.has(neighbor)) return true; // تم الكشف عن دورة if (!visited.has(neighbor)) { if (dfs(neighbor)) return true; } } visiting.delete(node); visited.add(node); return false; } for (const node of nodes) { if (!visited.has(node)) { if (dfs(node)) return true; } } return false; } const cyclicGraph = { A: ['B'], B: ['C'], C: ['A'] }; const acyclicGraph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: [] }; console.log(hasCycleDirected(cyclicGraph)); // صحيح console.log(hasCycleDirected(acyclicGraph)); // خطأ

تطبيق سلوك `Object.freeze`

اشرح Object.freeze وقم بتطبيق نسخة (ضحلة).

الشرح: يمنع Object.freeze إضافة خصائص جديدة، أو إزالة خصائص موجودة، أو تغيير خصائص موجودة أو قابليتها للتعداد أو قابليتها للتكوين أو قابليتها للكتابة. إنه يجعل الكائن غير قابل للتغيير (بشكل سطحي). يمكنك تنفيذه باستخدام Object.preventExtensions و Object.defineProperty لجعل الخصائص الموجودة غير قابلة للكتابة وغير قابلة للتكوين.

function myFreeze(obj) { Object.preventExtensions(obj); // منع إضافة خصائص جديدة Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { writable: false, configurable: false }); }); return obj; } const obj = { a: 1, b: 2 }; myFreeze(obj); // obj.c = 3; // يفشل بصمت (أو يرمي في الوضع الصارم) // delete obj.a; // يفشل بصمت (أو يرمي في الوضع الصارم) // obj.a = 10; // يفشل بصمت (أو يرمي في الوضع الصارم) console.log(obj); // { a: 1, b: 2 }

استخدام `requestAnimationFrame` للرسوم المتحركة السلسة

اشرح requestAnimationFrame وكيفية استخدامه لحلقة رسوم متحركة بسيطة.

الشرح: يخبر requestAnimationFrame (rAF) المتصفح أنك ترغب في تنفيذ رسوم متحركة ويطلب من المتصفح استدعاء دالة محددة لتحديث الرسوم المتحركة قبل إعادة الرسم التالية. إنه أكثر كفاءة وسلاسة من استخدام setTimeout أو setInterval للرسوم المتحركة، لأنه يتزامن مع معدل تحديث المتصفح ويتوقف مؤقتًا عندما لا تكون علامة التبويب مرئية.

// HTML: <div id='box' style='width: 50px; height: 50px; background: red; position: absolute; left: 0px;'></div> const box = document.getElementById('box'); let position = 0; let startTime = null; function animate(currentTime) { if (!startTime) startTime = currentTime; const elapsedTime = currentTime - startTime; // تحريك 100 بكسل في ثانيتين (50 بكسل/ثانية) position = (elapsedTime / 2000) * 100; if (position < 200) { // استمر في التحريك حتى يصل إلى 200 بكسل box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // بدء الرسوم المتحركة // requestAnimationFrame(animate); // قم بإلغاء التعليق للتشغيل في المتصفح console.log('استخدم requestAnimationFrame للرسوم المتحركة في المتصفح.');

تطبيق `Array.prototype.some`

قم بتطبيق نسختك الخاصة من Array.prototype.some.

الشرح: يختبر some ما إذا كان عنصر واحد على الأقل في المصفوفة يجتاز الاختبار الذي تم تنفيذه بواسطة الدالة المقدمة. تُرجع true إذا وجدت عنصرًا تعيد فيه الدالة الاستدعاء true؛ وإلا فإنها تُرجع false.

function mySome(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { // التعامل مع المصفوفات المتفرقة if (callback.call(thisArg, arr[i], i, arr)) { return true; } } } return false; } const arr = [1, 2, 3, 4, 5]; const hasEven = mySome(arr, x => x % 2 === 0); console.log(hasEven); // صحيح

تطبيق `Array.prototype.every`

قم بتطبيق نسختك الخاصة من Array.prototype.every.

الشرح: يختبر every ما إذا كانت جميع العناصر في المصفوفة تجتاز الاختبار الذي تم تنفيذه بواسطة الدالة المقدمة. تُرجع true إذا اجتازت جميع العناصر (أو إذا كانت المصفوفة فارغة)، و false إذا فشل أي عنصر.

function myEvery(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (!callback.call(thisArg, arr[i], i, arr)) { return false; } } } return true; } const arr1 = [1, 30, 39, 29, 10, 13]; const isBelow40 = myEvery(arr1, x => x < 40); console.log(isBelow40); // صحيح const arr2 = [1, 30, 39, 29, 10, 45]; const isBelow40_2 = myEvery(arr2, x => x < 40); console.log(isBelow40_2); // خطأ

تطبيق `Array.prototype.findIndex`

قم بتطبيق نسختك الخاصة من Array.prototype.findIndex.

الشرح: تُرجع findIndex فهرس العنصر الأول في المصفوفة الذي يفي بالدالة الاختبارية المقدمة. وإلا، فإنها تُرجع -1، مما يشير إلى أن لا يوجد عنصر اجتاز الاختبار.

function myFindIndex(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (callback.call(thisArg, arr[i], i, arr)) { return i; } } } return -1; } const arr = [5, 12, 8, 130, 44]; const isLargeNumber = (element) => element > 13; console.log(myFindIndex(arr, isLargeNumber)); // 3

ما هي Web Workers؟

اشرح ما هي Web Workers وحالة استخدامها الأساسية.

الشرح: Web Workers هي وسيلة لمحتوى الويب لتشغيل البرامج النصية في سلاسل محادثات خلفية. حالة الاستخدام الرئيسية هي تفريغ المهام طويلة الأمد أو كثيفة الحساب من سلسلة المحادثات الرئيسية (التي تتعامل مع تحديثات واجهة المستخدم وتفاعلات المستخدم)، وبالتالي منع واجهة المستخدم من أن تصبح غير مستجيبة أو 'تتجمد'. تتواصل Workers مع سلسلة المحادثات الرئيسية عبر تمرير الرسائل (postMessage و onmessage).

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // إرسال بيانات إلى العامل myWorker.onmessage = function(e) { console.log('النتيجة من العامل:', e.data); } } else { console.log('متصفحك لا يدعم Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('تم استلام رسالة من السكريبت الرئيسي'); const result = e.data[0] * e.data[1]; console.log('نشر النتيجة مرة أخرى إلى السكريبت الرئيسي'); self.postMessage(result); } */ console.log('Web Workers تشغل البرامج النصية في سلاسل محادثات الخلفية.');

طرق فك التشفير

يمكن تشفير رسالة تحتوي على أحرف من A-Z إلى أرقام باستخدام التعيين 'A' -> 1، 'B' -> 2، ...، 'Z' -> 26. بالنظر إلى سلسلة s تحتوي على أرقام فقط، أرجع عدد طرق فك تشفيرها.

الشرح: استخدم البرمجة الديناميكية. لتكن dp[i] عدد طرق فك تشفير s[0...i-1]. تعتمد dp[i] على dp[i-1] (إذا كان s[i-1] رمزًا صالحًا من رقم واحد) و dp[i-2] (إذا كان s[i-2...i-1] رمزًا صالحًا من رقمين).

function numDecodings(s) { if (!s || s[0] === '0') return 0; const n = s.length; const dp = new Array(n + 1).fill(0); dp[0] = 1; dp[1] = 1; for (let i = 2; i <= n; i++) { const oneDigit = parseInt(s.substring(i - 1, i)); const twoDigits = parseInt(s.substring(i - 2, i)); if (oneDigit >= 1) { dp[i] += dp[i - 1]; } if (twoDigits >= 10 && twoDigits <= 26) { dp[i] += dp[i - 2]; } } return dp[n]; } console.log(numDecodings('12')); // 2 ('AB' أو 'L') console.log(numDecodings('226')); // 3 ('BBF', 'BZ', 'VF') console.log(numDecodings('06')); // 0

الجمع الثنائي (بدون `+`)

اكتب دالة لجمع عددين صحيحين دون استخدام العامل +.

الشرح: استخدم العوامل الثنائية. يمكن حساب المجموع كـ a ^ b (المجموع بدون حمل)، ويمكن حساب الحمل كـ (a & b) << 1. كرر هذه العملية حتى يصبح الحمل 0.

function getSum(a, b) { while (b !== 0) { const carry = (a & b) << 1; // حساب الحمل a = a ^ b; // حساب المجموع بدون حمل b = carry; // يصبح الحمل هو 'b' الجديد } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

التحقق من التناظر (رقم)

بالنظر إلى عدد صحيح x، أرجع true إذا كان x متناظرًا، و false بخلاف ذلك.

الشرح: الرقم السالب ليس متناظرًا. حوّل الرقم إلى سلسلة وتحقق مما إذا كانت السلسلة متناظرة (تُقرأ بنفس الطريقة إلى الأمام والخلف). بدلاً من ذلك، اعكس الرقم رياضيًا وقارن.

function isPalindromeNumber(x) { if (x < 0) return false; // نهج السلسلة // return String(x) === String(x).split('').reverse().join(''); // نهج رياضي let original = x; let reversed = 0; while (x > 0) { reversed = reversed * 10 + (x % 10); x = Math.floor(x / 10); } return original === reversed; } console.log(isPalindromeNumber(121)); // صحيح console.log(isPalindromeNumber(-121)); // خطأ console.log(isPalindromeNumber(10)); // خطأ

نمط المصنع

قم بتطبيق نمط تصميم المصنع في JavaScript.

الشرح: يوفر نمط المصنع واجهة لإنشاء الكائنات في فئة رئيسية، ولكنه يسمح للفئات الفرعية بتغيير نوع الكائنات التي سيتم إنشاؤها. إنه مفيد لإنشاء الكائنات دون تحديد الفئة الدقيقة.

class Car { constructor(options) { this.type = 'Car'; this.doors = options.doors || 4; } } class Truck { constructor(options) { this.type = 'Truck'; this.bedSize = options.bedSize || 'long'; } } class VehicleFactory { createVehicle(options) { if (options.vehicleType === 'car') { return new Car(options); } else if (options.vehicleType === 'truck') { return new Truck(options); } else { throw new Error('نوع مركبة غير معروف'); } } } const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 2 }); const truck = factory.createVehicle({ vehicleType: 'truck', bedSize: 'short' }); console.log(car); // Car { type: 'Car', doors: 2 } console.log(truck); // Truck { type: 'Truck', bedSize: 'short' }

اشرح `Symbol` وحالة استخدام

اشرح ما هو Symbol في JavaScript وقدم حالة استخدام، مثل إنشاء خصائص 'خاصة' أو مفاتيح كائن فريدة.

الشرح: Symbol هو نوع بيانات بدائي تم تقديمه في ES6. مثيلاته فريدة وغير قابلة للتغيير. غالبًا ما تُستخدم كمفاتيح لخصائص الكائنات لتجنب تصادم الأسماء بين المكتبات المختلفة أو لإنشاء خصائص شبه خاصة (على الرغم من أنها ليست خاصة حقًا، إلا أنها لا يمكن الوصول إليها عبر التكرار النموذجي أو Object.keys).

const _privateName = Symbol('name'); const _privateMethod = Symbol('greet'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`مرحباً، اسمي هو ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('بوب'); p.introduce(); // مرحباً، اسمي هو بوب console.log(Object.keys(p)); // [] - الرموز غير متضمنة console.log(p[_privateName]); // بوب (يمكن الوصول إليه إذا كان لديك الرمز)

تحويل `arguments` إلى مصفوفة حقيقية

كيف يمكنك تحويل كائن arguments (المتوفر في الدوال غير السهمية) إلى مصفوفة JavaScript حقيقية؟

الشرح: يبدو كائن arguments كمصفوفة ولكنه ليس كذلك؛ فهو يفتقر إلى طرق مثل map و filter وما إلى ذلك. يمكنك تحويله باستخدام:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (ES6 Spread Syntax)
  3. Array.prototype.slice.call(arguments) (طريقة أقدم)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('باستخدام Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Spread Syntax const args2 = [...arguments]; console.log('باستخدام Spread:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('باستخدام Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // باستخدام Array.from: 10 // باستخدام Spread: 10 // باستخدام Slice: 10