Live Coding

بررسی خالی بودن یک شیء

چگونه بررسی کنیم که یک شیء جاوااسکریپت خالی است؟

توضیح: یک شیء زمانی خالی است که هیچ ویژگی قابل شمارش (enumerable) خودش را نداشته باشد. می‌توانیم از 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'

بررسی پالیندروم (قرینه بودن)

تابعی بنویسید که بررسی کند آیا یک رشته داده شده پالیندروم است یا خیر.

توضیح: پالیندروم کلمه یا عبارتی است که از جلو و عقب یکسان خوانده می‌شود. می‌توانیم این را با معکوس کردن رشته (با نادیده گرفتن حروف بزرگ و کوچک و کاراکترهای غیرحروفی-عددی برای بررسی قوی‌تر، اما در اینجا یک بررسی ساده انجام می‌دهیم) و مقایسه آن با رشته اصلی بررسی کنیم.

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

یافتن بزرگترین عدد در یک آرایه

تابعی بنویسید که بزرگترین عدد را در آرایه‌ای از اعداد پیدا کند.

توضیح: می‌توانید در آرایه پیمایش کنید و بزرگترین عدد یافت شده تا کنون را پیگیری کنید. روش دیگر این است که از تابع Math.max() به همراه عملگر spread (...) برای ارسال عناصر آرایه به عنوان آرگومان استفاده کنید.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // یا یک خطا throw کنید return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

برنامه‌ای بنویسید که اعداد از 1 تا n را چاپ کند. اما برای مضرب‌های سه، به جای عدد 'Fizz' و برای مضرب‌های پنج، 'Buzz' را چاپ کند. برای اعدادی که هم مضرب سه و هم پنج هستند، 'FizzBuzz' را چاپ کند.

توضیح: این مسئله کلاسیک منطق پایه حلقه و شرطی را آزمایش می‌کند. شما باید از 1 تا n پیمایش کنید و از عملگر modulo (%) برای بررسی قابلیت تقسیم بر 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 است. Setها فقط مقادیر منحصر به فرد را ذخیره می‌کنند. می‌توانید آرایه را به یک Set تبدیل کرده و سپس دوباره به یک آرایه برگردانید.

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

بررسی آناگرام

تابعی بنویسید که بررسی کند آیا دو رشته آناگرام یکدیگر هستند یا خیر.

توضیح: آناگرام‌ها کلماتی هستند که با جابجایی حروف کلمه‌ای دیگر تشکیل می‌شوند. برای بررسی، می‌توانیم رشته‌ها را تمیز، مرتب و مقایسه کنیم. تمیز کردن شامل حذف کاراکترهای غیرحروفی-عددی و تبدیل به یک حالت ثابت (حروف کوچک یا بزرگ) است.

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

محاسبه فاکتوریل

تابعی بنویسید که فاکتوریل یک عدد صحیح غیر منفی را محاسبه کند.

توضیح: فاکتوریل (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 استفاده کنید که برای جمع‌آوری یک مقدار واحد از یک آرایه ایده‌آل است. این متد یک تابع callback و یک مقدار اولیه (0 برای جمع) را می‌گیرد.

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

تخت کردن یک آرایه تو در تو

تابعی بنویسید که یک آرایه تو در تو را تخت کند. برای سادگی، فرض کنید فقط یک سطح تو در تو بودن وجود دارد.

توضیح: برای یک سطح تو در تو بودن، می‌توانید از Array.prototype.concat() با عملگر spread یا متد flat() (ES2019) استفاده کنید.

function flattenArray(arr) { // Using flat() (simpler if ES2019+ is okay) // return arr.flat(); // Using reduce and 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

تابعی بنویسید که یک جمله را به 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'

مسئله دو مجموع

آرایه‌ای از اعداد صحیح nums و یک عدد صحیح target داده شده است، ایندکس‌های دو عددی را برگردانید که مجموع آنها برابر با target باشد.

توضیح: یک رویکرد متداول استفاده از یک نقشه هش (یا یک شیء جاوااسکریپت) برای ذخیره اعداد و ایندکس‌های آنها در حین پیمایش است. برای هر عدد، بررسی کنید که آیا 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، یا در صورت عدم وجود راه حل خطا throw کنید } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

پیاده‌سازی یک شمارنده با استفاده از Closures

تابعی ایجاد کنید که یک تابع شمارنده را برگرداند. هر بار که تابع برگشتی فراخوانی می‌شود، باید شمارنده را افزایش داده و مقدار آن را برگرداند.

توضیح: این مثال 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'

تقسیم آرایه به تکه‌های کوچک

تابعی بنویسید که یک آرایه را به گروه‌هایی با اندازه مشخص تقسیم کند.

توضیح: در آرایه پیمایش کنید، تکه‌هایی با اندازه مشخص را بردارید و آنها را به یک آرایه جدید اضافه کنید. حالت‌هایی را که آخرین تکه ممکن است کوچکتر باشد، مدیریت کنید.

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) { // Clean words if needed (e.g., remove punctuation) 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 یک callback را می‌گیرد و با اعمال callback به هر عنصر از آرایه اصلی، یک آرایه جدید ایجاد می‌کند. تابع شما باید در آرایه پیمایش کرده و آرایه جدید را بسازد.

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 یک callback را می‌گیرد و یک آرایه جدید با تمام عناصری که تست پیاده‌سازی شده توسط تابع callback ارائه شده را می‌گذرانند، ایجاد می‌کند.

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 یک تابع callback 'reducer' که توسط کاربر ارائه شده است را بر روی هر عنصر آرایه اجرا می‌کند و مقدار بازگشتی از محاسبه بر روی عنصر قبلی را به آن منتقل می‌کند. نتیجه نهایی اجرای reducer در تمام عناصر آرایه یک مقدار واحد است. آرگومان مقدار اولیه را مدیریت کنید.

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

Memoization - دنباله فیبوناچی

یک تابع فیبوناچی را با استفاده از memoization برای بهینه‌سازی عملکرد پیاده‌سازی کنید.

توضیح: دنباله فیبوناچی شامل محاسبات تکراری است. Memoization نتایج فراخوانی توابع گران‌قیمت را ذخیره می‌کند و در صورت تکرار ورودی‌های مشابه، نتیجه کش شده را برمی‌گرداند.

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 (much faster than non-memoized)

بررسی پرانتزهای متوازن

تابعی بنویسید که بررسی کند آیا یک رشته حاوی پرانتزهای {}[()] متوازن است یا خیر.

توضیح: از یک پشته استفاده کنید. هنگامی که به یک پرانتز باز برمی‌خورید، آن را به پشته فشار دهید. هنگامی که به یک پرانتز بسته برمی‌خورید، بررسی کنید که آیا پشته خالی است یا اینکه عنصر بالایی پرانتز باز مطابق است. اگر مطابق باشد، پشته را pop کنید. اگر نه، یا اگر پشته خالی است، نامتوازن است. در پایان، یک پشته خالی به معنای متوازن بودن است.

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

پیاده‌سازی یک صف ساده

ساختار داده Queue را با متدهای enqueue (افزودن به انتها) و dequeue (حذف از جلو) پیاده‌سازی کنید.

توضیح: یک صف از اصل First-In, First-Out (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

پیاده‌سازی یک پشته ساده

ساختار داده Stack را با متدهای push (افزودن به بالا) و pop (حذف از بالا) پیاده‌سازی کنید.

توضیح: یک پشته از اصل Last-In, First-Out (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

یک تابع debounce را پیاده‌سازی کنید. Debouncing تضمین می‌کند که یک تابع تا زمانی که مقدار مشخصی از زمان بدون فراخوانی آن سپری نشده باشد، دوباره فراخوانی نشود.

توضیح: از setTimeout و clearTimeout استفاده کنید. هر بار که تابع debounced فراخوانی می‌شود، timeout قبلی را پاک کرده و یک timeout جدید را تنظیم کنید. فراخوانی واقعی تابع تنها زمانی اتفاق می‌افتد که timeout کامل شود.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Example usage: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Called after 1 sec (if not called again) debouncedHello(); // Resets timer debouncedHello(); // Resets timer, will actually run 1 sec after this call.

تابع Throttle

یک تابع throttle را پیاده‌سازی کنید. Throttling تضمین می‌کند که یک تابع حداکثر یک بار در یک بازه زمانی مشخص فراخوانی شود.

توضیح: از یک پرچم برای نشان دادن اینکه آیا فراخوانی مجاز است یا خیر، استفاده کنید. هنگامی که فراخوانی شد، اگر مجاز بود، تابع را اجرا کنید، پرچم را به false تنظیم کنید و از setTimeout برای بازنشانی پرچم پس از بازه زمانی استفاده کنید.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Example usage: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Will log at most once per second.

تابع Currying

تابعی بنویسید که یک تابع با دو آرگومان را می‌گیرد و یک نسخه curried از آن را برمی‌گرداند.

توضیح: Currying یک تابع با آرگومان‌های متعدد را به دنباله‌ای از توابع تبدیل می‌کند که هر کدام یک آرگومان را می‌گیرند. 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) یک شیء

تابعی بنویسید که یک کپی عمیق از یک شیء جاوااسکریپت را انجام دهد.

توضیح: یک کپی سطحی (shallow clone) فقط ویژگی‌های سطح بالا را کپی می‌کند. یک کپی عمیق به صورت بازگشتی تمام ویژگی‌ها، از جمله اشیاء و آرایه‌های تو در تو را کپی می‌کند. یک راه ساده (با محدودیت‌هایی، مثلاً توابع، تاریخ‌ها، regex را به خوبی مدیریت نمی‌کند) استفاده از JSON.parse(JSON.stringify(obj)) است. یک رویکرد بازگشتی قوی‌تر است.

function deepClone(obj) { // Simple version (with limitations) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Cloning failed:", e); return null; } // More robust recursive (basic example): /* 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 (proves it's a deep clone) 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

ادغام دو آرایه

چگونه دو آرایه را در یک آرایه ادغام کنیم؟

توضیح: از عملگر spread (...) یا 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 را log می‌کند زیرا 'this' به ارث برده شده است }, 100); } new MyClass();

تفاوت بین `let`, `const`, و `var`

تفاوت‌های اصلی بین let, const, و var چیست؟

توضیح: var در حوزه تابع است و hoisted می‌شود. let و const در حوزه بلوک هستند و hoisted می‌شوند اما تا زمان اعلان در یک 'منطقه مرده زمانی' (temporal dead zone) قرار دارند. 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 در جاوااسکریپت چیست.

توضیح: یک Promise شیئی است که نشان‌دهنده اتمام (یا شکست) نهایی یک عملیات ناهمزمان و مقدار حاصل از آن است. می‌تواند در یکی از سه حالت باشد: در حال انتظار (pending)، انجام شده (fulfilled)، یا رد شده (rejected).

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('Success!'); // reject('Error!'); }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));

استفاده از `async/await`

چگونه async و await کار با Promiseها را ساده می‌کنند؟

توضیح: async/await یک sugar نحوی بر روی Promiseها ارائه می‌دهد که کد ناهمزمان را تا حدی شبیه به کد همزمان می‌کند. یک تابع async همیشه یک Promise را برمی‌گرداند، و await اجرا را تا زمانی که یک Promisesettle شود، متوقف می‌کند.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('Starting...'); await delay(1000); console.log('Waited 1 second.'); await delay(500); console.log('Waited another 0.5 seconds.'); return 'Finished!'; } 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 چه کاری انجام می‌دهد؟

توضیح: یک شیء یا مقدار جاوااسکریپت را به یک رشته JSON تبدیل می‌کند.

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

`JSON.parse` چه کاری انجام می‌دهد؟

JSON.parse چه کاری انجام می‌دهد؟

توضیح: یک رشته JSON را تجزیه می‌کند و مقدار یا شیء جاوااسکریپت توصیف شده توسط رشته را می‌سازد.

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

عملگر Spread در آرایه‌ها

عملگر spread چگونه با آرایه‌ها استفاده می‌شود؟

توضیح: به یک iterable (مانند یک آرایه) اجازه می‌دهد در جاهایی که صفر یا بیشتر آرگومان یا عنصر مورد انتظار است، گسترش یابد. برای کپی کردن، ادغام و افزودن عناصر مفید است.

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

عملگر Spread در اشیاء

عملگر spread چگونه با اشیاء استفاده می‌شود؟

توضیح: امکان کپی و ادغام ویژگی‌های شیء را فراهم می‌کند.

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)

تجزیه آرایه (array destructuring) را با یک مثال توضیح دهید.

توضیح: یک نحو است که امکان باز کردن مقادیر از آرایه‌ها را به متغیرهای مجزا فراهم می‌کند.

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)

تجزیه شیء (object destructuring) را با یک مثال توضیح دهید.

توضیح: امکان باز کردن ویژگی‌ها از اشیاء را به متغیرهای مجزا فراهم می‌کند.

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

پیاده‌سازی `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: 'Charlie' }, 'Hi', '!'); // Hi, Charlie!

پیاده‌سازی `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: 'David' }, ['Hello', '.']); // Hello, David.

توضیح حلقه رویداد (Event Loop)

حلقه رویداد جاوااسکریپت را به اختصار توضیح دهید.

توضیح: جاوااسکریپت تک‌رشته‌ای (single-threaded) است، اما با استفاده از حلقه رویداد به هم‌زمانی (concurrency) دست می‌یابد. پشته فراخوانی (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)

الگوریتم مرتب‌سازی سریع را پیاده‌سازی کنید.

توضیح: مرتب‌سازی سریع نیز یک الگوریتم تقسیم و غلبه است. این الگوریتم یک عنصر را به عنوان محور (pivot) انتخاب می‌کند و آرایه داده شده را حول محور انتخاب شده تقسیم می‌کند. عناصر کوچکتر از محور به سمت چپ و عناصر بزرگتر به سمت راست می‌روند. سپس به صورت بازگشتی زیرآرایه‌ها را مرتب می‌کند.

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]

طولانی‌ترین زیررشته بدون تکرار کاراکترها

با داشتن یک رشته، طول طولانی‌ترین زیررشته بدون تکرار کاراکترها را پیدا کنید.

توضیح: از تکنیک پنجره کشویی (sliding window) استفاده کنید. یک پنجره (زیررشته) و مجموعه‌ای از کاراکترها در آن پنجره را حفظ کنید. پنجره را با حرکت دادن اشاره‌گر راست گسترش دهید. اگر یک کاراکتر تکراری یافت شد، پنجره را از سمت چپ کوچک کنید تا کاراکتر تکراری حذف شود.

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 پیاده‌سازی کنید.

توضیح: لیست پیوندی یک ساختار داده خطی است که در آن عناصر در مکان‌های حافظه پیوسته ذخیره نمی‌شوند. هر عنصر (گره) به عنصر بعدی اشاره می‌کند. برای مدیریت سر (head) و اضافه کردن گره‌های جدید به یک کلاس 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

چرخش یک آرایه (Rotate an Array)

تابعی بنویسید که یک آرایه را به راست به اندازه 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) انجام شود. سپس، آرایه دوم را پیمایش کنید و بررسی کنید که آیا هر عنصر در Set وجود دارد یا خیر. موارد تطابق را جمع‌آوری کنید.

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]

گروه‌بندی آناگرام‌ها

با داشتن آرایه‌ای از رشته‌ها، آناگرام‌ها را با هم گروه‌بندی کنید.

توضیح: نکته اصلی این است که برای هر گروه از آناگرام‌ها یک امضای منحصر به فرد پیدا کنید. یک روش متداول، مرتب‌سازی هر کلمه به صورت الفبایی است. کلماتی که به یک رشته مرتب شده یکسان منجر می‌شوند، آناگرام هستند. از یک جدول هش (hash map) استفاده کنید که در آن کلیدها کلمات مرتب شده و مقادیر آرایه‌هایی از کلمات اصلی هستند.

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، تابعی بنویسید تا همه 0ها را به انتهای آن منتقل کند در حالی که ترتیب نسبی عناصر غیرصفر حفظ شود.

توضیح: از رویکرد دو اشاره‌گر (two-pointer) یا گلوله برفی (snowball) استفاده کنید. یک اشاره‌گر موقعیتی را که عنصر غیرصفر بعدی باید قرار گیرد، پیگیری می‌کند. آرایه را پیمایش کنید؛ اگر عنصری غیرصفر است، آن را در موقعیت اشاره‌گر قرار دهید و اشاره‌گر را افزایش دهید. در نهایت، بقیه را با صفر پر کنید.

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 (^) است. XOR کردن یک عدد با خودش منجر به 0 می‌شود. XOR کردن یک عدد با 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⌋ بار ظاهر می‌شود.

توضیح: الگوریتم رای‌گیری بویار-مور (Boyer-Moore Voting Algorithm) یک روش کارآمد است. یک candidate و یک count را مقداردهی اولیه کنید. آرایه را پیمایش کنید. اگر count صفر است، عنصر فعلی را به عنوان 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 پله طول می‌کشد. هر بار می‌توانید 1 یا 2 پله بالا بروید. به چند روش متمایز می‌توانید به بالا بروید؟

توضیح: این یک مسئله برنامه‌نویسی پویا (dynamic programming) کلاسیک است، بسیار شبیه به دنباله فیبوناچی. تعداد راه‌های رسیدن به پله n، مجموع راه‌های رسیدن به پله n-1 (با برداشتن 1 پله) و راه‌های رسیدن به پله n-2 (با برداشتن 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'ها (خشکی) و '0'ها (آب)، تعداد جزایر را بشمارید. یک جزیره با آب احاطه شده و با اتصال خشکی‌های مجاور به صورت افقی یا عمودی تشکیل می‌شود.

توضیح: هر سلول شبکه را پیمایش کنید. اگر '1' (خشکی) پیدا کردید، تعداد جزایر را افزایش دهید و یک جستجوی عمق اول (DFS) یا جستجوی عرض اول (BFS) را از آن سلول شروع کنید. در طول جستجو، تمام '1'های متصل را به عنوان '0' (یا بازدید شده) علامت‌گذاری کنید تا از شمارش مجدد آنها جلوگیری شود.

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 Cache) که به تازگی استفاده شده است، طراحی و پیاده‌سازی کنید. باید از عملیات get و put پشتیبانی کند.

توضیح: یک حافظه نهان LRU وقتی ظرفیت به حداکثر می‌رسد، کمترین آیتم استفاده شده را بیرون می‌اندازد. یک پیاده‌سازی رایج از یک Map (یا یک جدول هش) برای جستجوهای O(1) و یک لیست پیوندی دوطرفه (Doubly Linked List) برای به‌روزرسانی‌های O(1) وضعیت 'به تازگی استفاده شده' استفاده می‌کند. Map جاوااسکریپت ترتیب درج را حفظ می‌کند، که می‌تواند یک پیاده‌سازی پایه را ساده کند.

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 جفت پرانتز، تابعی برای تولید تمام ترکیبات پرانتزهای خوش‌ساخت بنویسید.

توضیح: این یک مسئله بازگشتی (backtracking) کلاسیک است. تعداد پرانتزهای باز و بسته را حفظ کنید. می‌توانید یک پرانتز باز اضافه کنید اگر 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) قرار دارند. دو خط را پیدا کنید که همراه با محور x یک ظرف را تشکیل می‌دهند، به طوری که ظرف بیشترین آب را در خود جای دهد.

توضیح: از رویکرد دو اشاره‌گر (two-pointer) استفاده کنید. با یک اشاره‌گر در ابتدا و دیگری در انتها شروع کنید. مساحت را محاسبه کنید. مساحت توسط خط کوتاه‌تر محدود می‌شود. برای یافتن مساحت بزرگتر، اشاره‌گر اشاره‌کننده به خط کوتاه‌تر را به سمت داخل حرکت دهید.

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

ادغام دو لیست مرتب شده (لیست‌های پیوندی)

دو لیست پیوندی مرتب شده را ادغام کنید و آن را به عنوان یک لیست مرتب شده جدید برگردانید. لیست جدید باید با بهم چسباندن گره‌های دو لیست اول ساخته شود.

توضیح: برای ساده‌سازی فرآیند، از یک گره سر ساختگی (dummy head node) استفاده کنید. از یک اشاره‌گر برای ساخت لیست جدید استفاده کنید. گره‌های فعلی هر دو لیست ورودی را مقایسه کنید و کوچکتر را به لیست جدید اضافه کنید، و اشاره‌گر آن لیست را جلو ببرید. این کار را تا زمانی که یکی از لیست‌ها تمام شود تکرار کنید، سپس بقیه لیست دیگر را اضافه کنید.

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

ادغام دو لیست مرتب شده (لیست‌های پیوندی)

دو لیست پیوندی مرتب شده را ادغام کرده و آن را به عنوان یک لیست مرتب شده جدید بازگردانید. لیست جدید باید با بهم چسباندن گره‌های دو لیست اول ایجاد شود.

توضیح: برای ساده‌سازی فرآیند، از یک گره سر ساختگی استفاده کنید. از یک اشاره‌گر برای ساخت لیست جدید استفاده کنید. گره‌های فعلی هر دو لیست ورودی را مقایسه کرده و گره کوچکتر را به لیست جدید اضافه کنید، و اشاره‌گر آن لیست را جلو ببرید. این کار را تا زمانی که یکی از لیست‌ها تمام شود تکرار کنید، سپس بقیه لیست دیگر را اضافه کنید.

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); } // Example: 1 -> [2, 2] -> [3, 4, 4, 3] is symmetric.

پیمایش سطحی (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; } // Example: 3 -> [9, 20] -> [null, null, 15, 7] // Output: [[3], [9, 20], [15, 7]]

تبدیل آرایه مرتب شده به BST با تعادل ارتفاع

آرایه‌ای که عناصر آن به ترتیب صعودی مرتب شده‌اند را به یک درخت جستجوی دودویی (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); } // Example: [-10, -3, 0, 5, 9] // Output: A tree like [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-امین بزرگترین

K-امین بزرگترین عنصر را در یک آرایه نامرتب پیدا کنید. توجه داشته باشید که این عنصر K-امین بزرگترین در ترتیب مرتب شده است، نه K-امین عنصر متمایز.

توضیح: یک رویکرد ساده این است که آرایه را مرتب کنید و سپس عنصر در شاخص n - k را انتخاب کنید. یک رویکرد کارآمدتر برای آرایه‌های بزرگ شامل استفاده از یک min-heap یا یک الگوریتم انتخاب مانند Quickselect است.

function findKthLargest(nums, k) { // Simple approach: Sort nums.sort((a, b) => b - a); // Sort in descending order return nums[k - 1]; // Note: Quickselect would be more efficient in an interview // but sorting is easier to implement quickly. } 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(`Using 'in': ${prop in obj}`); console.log(`Using 'hasOwnProperty': ${Object.prototype.hasOwnProperty.call(obj, prop)}`); } hasProp(person, 'name'); // true, true hasProp(person, 'toString'); // true, false (toString is inherited)

عدد صحیح به رومی

تابعی بنویسید تا یک عدد صحیح را به نمایش عددی رومی آن تبدیل کند.

توضیح: یک نگاشت از اعداد رومی و مقادیر متناظر آنها ایجاد کنید که از بزرگترین به کوچکترین مرتب شده باشد. این نگاشت را پیمایش کنید. برای هر مقدار، آن را به تعداد دفعات ممکن از عدد ورودی کم کنید و هر بار عدد رومی را به نتیجه اضافه کنید.

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 (نقشه هش) به عنوان فضای ذخیره‌سازی اصلی استفاده کنید. کلیدها عناصر Set خواهند بود (ممکن است نیاز به مدیریت نحوه ذخیره‌سازی انواع مختلف و اطمینان از منحصر به فرد بودن به عنوان کلید داشته باشید).

class MySet { constructor() { this.items = {}; this._size = 0; } add(element) { if (!this.has(element)) { this.items[element] = element; // Store the 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 شروع و با 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]]

جستجوی کلمه

با داشتن یک صفحه 2D و یک کلمه، پیدا کنید که آیا کلمه در گرید وجود دارد یا خیر. کلمه را می‌توان از حروف سلول‌های متوالی مجاور ساخت، که در آن سلول‌های مجاور به صورت افقی یا عمودی همسایه هستند.

توضیح: این نیاز به یک جستجوی عمق اول (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; // Word found if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // Mark as visited 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; // Backtrack 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 هر گره را تغییر دهید تا به گره قبلی اشاره کند. در طول پیمایش، گره‌های قبلی، فعلی و بعدی را ردیابی کنید.

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; // Store next node current.next = prev; // Reverse current node's pointer prev = current; // Move prev one step forward current = next; // Move current one step forward } return prev; // New head is the last 'prev' } // Example: 1->2->3->4->5 becomes 5->4->3->2->1

تشخیص چرخه در لیست پیوندی

با داشتن head، سر یک لیست پیوندی، تعیین کنید که آیا لیست پیوندی چرخه‌ای دارد یا خیر.

توضیح: از الگوریتم 'لاک‌پشت و خرگوش فلوید' استفاده کنید. دو اشاره‌گر داشته باشید، یکی یک گام در هر بار حرکت می‌کند (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; // Reached end, no cycle slow = slow.next; fast = fast.next.next; } return true; // Pointers met, cycle exists } // Example: 3->2->0->-4 with -4 pointing back to 2. hasCycle returns 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('Object prototype may only be an Object or null: ' + proto); } } function F() {} F.prototype = proto; return new F(); } const person = { isHuman: false, printIntroduction: function() { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = myObjectCreate(person); me.name = 'Matthew'; me.isHuman = true; me.printIntroduction(); // My name is Matthew. Am I human? true console.log(Object.getPrototypeOf(me) === person); // true

Hoisting چیست؟

Hoisting در جاوااسکریپت را توضیح دهید و مثالی ارائه دهید.

توضیح: Hoisting رفتار پیش‌فرض جاوااسکریپت برای انتقال اعلان‌ها به بالای محدوده فعلی (سراسری یا تابع) قبل از اجرای کد است. اعلان‌های متغیر (var) Hoist شده و با undefined مقداردهی اولیه می‌شوند. اعلان‌های تابع به طور کامل Hoist می‌شوند (هم نام و هم بدنه). let و const Hoist می‌شوند اما مقداردهی اولیه نمی‌شوند، که منجر به یک منطقه مرده زمانی می‌شود.

console.log(myVar); // undefined (var is hoisted and initialized with undefined) // console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ) myFunc(); // 'Hello!' (Function declaration is fully hoisted) var myVar = 'I am a var'; let myLet = 'I am a let'; function myFunc() { console.log('Hello!'); }

توضیح حباب‌سازی و گرفتن رویداد

حباب‌سازی (Bubbling) و گرفتن (Capturing) رویداد را در زمینه DOM توضیح دهید.

توضیح: اینها دو فاز از انتشار رویداد در HTML DOM هستند. فاز گرفتن: رویداد از window به عنصر هدف به سمت پایین حرکت می‌کند. فاز حباب‌سازی: رویداد از عنصر هدف به سمت بالا و به سمت window حرکت می‌کند. به طور پیش‌فرض، اکثر کنترل‌کننده‌های رویداد در فاز حباب‌سازی ثبت می‌شوند. می‌توانید از addEventListener(type, listener, useCapture) با useCapture = true برای مدیریت رویدادها در فاز گرفتن استفاده کنید.

// In an HTML structure: <div><p><span>Click Me</span></p></div> // If you click the <span>: // Capturing: window -> document -> html -> body -> div -> p -> span // Bubbling: span -> p -> div -> body -> html -> document -> window /* JS Example div.addEventListener('click', () => console.log('Div clicked'), true); // Capturing p.addEventListener('click', () => console.log('P clicked'), false); // Bubbling span.addEventListener('click', () => console.log('Span clicked'), false); // Bubbling // Output would be: Div clicked, Span clicked, P clicked */

پیاده‌سازی دستی (JSON.parse (پایه

سعی کنید یک نسخه بسیار پایه از JSON.parse را پیاده‌سازی کنید (شیءها، آرایه‌ها، رشته‌ها، اعداد ساده را مدیریت کنید).

توضیح: این یک کار بسیار پیچیده به طور کامل است، اما برای یک محیط کدنویسی زنده، ممکن است از شما خواسته شود که یک زیرمجموعه بسیار ساده را مدیریت کنید. شما باید رشته را تجزیه کنید، مرزهای شیء {} و آرایه []، جفت‌های کلید-مقدار (key: value)، و انواع داده‌های پایه را شناسایی کنید. eval یا new Function می‌توانند تقلب کنند اما خطرناک هستند. یک تجزیه‌کننده واقعی از یک lexer/tokenizer و parser استفاده می‌کند.

function basicJsonParse(jsonString) { // WARNING: Using new Function is insecure like eval. // This is a simplified, INSECURE example for demonstration only. // A real implementation requires a proper parser. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('Invalid 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+ provides a much simpler way: // 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

جابجایی (Shuffle) آرایه (Fisher-Yates)

تابعی بنویسید که یک آرایه را با استفاده از الگوریتم Fisher-Yates (یا Knuth) در جای خود جابجا کند.

توضیح: الگوریتم Fisher-Yates یک آرایه را از عنصر آخر به اولین عنصر پیمایش می‌کند. در هر تکرار، عنصر فعلی را با یک عنصر به صورت تصادفی انتخاب شده از ابتدای آرایه تا عنصر فعلی (شامل) جابجا می‌کند.

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]]; // Swap elements } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // e.g., [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 Functions)

یک تابع 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; // Found LCA } } return null; // Should not happen in a valid BST with p and q } // Example: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA is 6

سریال‌سازی و دسریال‌سازی درخت دودویی

الگوریتمی برای سریال‌سازی و دسریال‌سازی یک درخت دودویی طراحی کنید.

توضیح: سریال‌سازی یک درخت را به یک رشته یا آرایه تبدیل می‌کند. دسریال‌سازی درخت را بازسازی می‌کند. یک روش رایج پیمایش پیش‌سفارش (DFS) است. از یک نشانگر خاص (مثلاً '#') برای گره‌های null برای حفظ ساختار استفاده کنید.

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(); } // Example: 1 -> [2, 3] -> [null, null, 4, 5] // Serialized: '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(); // Schedule the next call }, delay); } run(); return interval; // Return an object to allow clearing } function myClearInterval(interval) { clearTimeout(interval.timerId); } // Example usage: let count = 0; const intervalId = mySetInterval(() => { console.log(`Hello: ${++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); // Add neighbors in reverse to process them in order later (optional) 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']

چرخاندن تصویر (ماتریس)

یک ماتریس 2 بعدی n x n که نمایانگر یک تصویر است به شما داده شده است. تصویر را 90 درجه (در جهت عقربه‌های ساعت) در محل بچرخانید.

توضیح: یک راه رایج برای انجام این کار این است که ابتدا ماتریس را ترانهاده کنید (جایگزینی matrix[i][j] با matrix[j][i]) و سپس هر سطر را معکوس کنید.

function rotate(matrix) { const n = matrix.length; // Transpose 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]]; } } // Reverse each row 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 x 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; } // Example: [[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; // Or resolve/reject depending on spec } 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 Pattern)

الگوی طراحی Singleton را در جاوااسکریپت پیاده‌سازی کنید.

توضیح: الگوی Singleton تضمین می‌کند که یک کلاس فقط یک نمونه دارد و یک نقطه دسترسی جهانی به آن فراهم می‌کند. این را می‌توان با استفاده از یک بسته (closure) برای نگهداری نمونه به دست آورد.

const Singleton = (function() { let instance; function createInstance() { // Private methods and variables const privateVar = 'I am private'; function privateMethod() { console.log('Private'); } return { // Public methods and variables 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); // true console.log(instance1.getPrivate()); // 'I am private'

اعتبارسنجی آدرس 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] بی‌نهایت منفی در نظر گرفته می‌شوند، می‌توانید از یک جستجوی دودویی اصلاح‌شده استفاده کنید. اگر 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; // Peak is to the right } else { right = mid; // Peak is mid or to the left } } return left; // 'left' will be the index of a peak } console.log(findPeakElement([1, 2, 3, 1])); // 2 (index of 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (index of 6) or 1 (index of 2)

شمارش بیت‌ها

با داشتن یک عدد صحیح n، آرایه‌ای ans به طول n + 1 برگردانید به طوری که برای هر i (0 <= i <= n)، ans[i] تعداد 1ها در نمایش باینری i باشد.

توضیح: این را می‌توانید با استفاده از برنامه‌نویسی پویا حل کنید. به رابطه زیر توجه کنید: dp[i] = dp[i >> 1] + (i & 1). تعداد 1ها در i برابر با تعداد 1ها در i شیفت داده شده به راست (یعنی 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); // Or: dp[i] = dp[i & (i - 1)] + 1; (Removes last set bit) } 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' تنظیم می‌کند. انجام عمل AND بر روی آنها منجر به 0 می‌شود.

function isPowerOfTwo(n) { return n > 0 && (n & (n - 1)) === 0; } console.log(isPowerOfTwo(16)); // true console.log(isPowerOfTwo(1)); // true console.log(isPowerOfTwo(18)); // false

ادغام بازه‌ها

با داشتن آرایه‌ای از 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]) { // Overlap: merge last[1] = Math.max(last[1], current[1]); } else { // No overlap: add new interval 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; // Base case: empty string 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'])); // true console.log(wordBreak('applepenapple', ['apple', 'pen'])); // true console.log(wordBreak('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'])); // false

پیاده‌سازی Array.prototype.flat

نسخه خود را از Array.prototype.flat پیاده‌سازی کنید، که یک آرایه جدید با تمام عناصر زیرآرایه به صورت بازگشتی تا عمق مشخص شده تولید می‌کند.

توضیح: از بازگشت استفاده کنید. آرایه را پیمایش کنید. اگر عنصری یک آرایه باشد و عمق فعلی کمتر از عمق مشخص شده باشد، به صورت بازگشتی flatten را روی آن فراخوانی کنید. در غیر این صورت، عنصر را اضافه کنید. عمق پیش‌فرض 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('The age is not an integer'); } if (value < 0 || value > 150) { throw new RangeError('The age seems invalid'); } } // Default behavior: Set the property obj[prop] = value; return true; } }; const person = {}; const personProxy = new Proxy(person, validator); personProxy.age = 30; // OK console.log(personProxy.age); // 30 // personProxy.age = 'thirty'; // Throws TypeError // personProxy.age = 200; // Throws 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; // Overlap detected } } return true; } console.log(canAttendMeetings([[0, 30], [5, 10], [15, 20]])); // false (5<30) console.log(canAttendMeetings([[7, 10], [2, 4]])); // true

پیاده‌سازی 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([], 'All promises were rejected')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // Resolve as soon as one fulfills .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'All promises were rejected')); } }); }); }); } // Example: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // Example: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

الگوی مشاهده‌گر (Observer Pattern)

الگوی طراحی مشاهده‌گر (Pub/Sub) را در جاوااسکریپت پیاده‌سازی کنید.

توضیح: الگوی مشاهده‌گر یک وابستگی یک به چند بین اشیا تعریف می‌کند. هنگامی که یک شیء (موضوع) وضعیت خود را تغییر می‌دهد، تمام وابستگان آن (مشاهده‌گرها) به طور خودکار مطلع و به روز می‌شوند. موضوع لیستی از مشاهده‌گرها را نگهداری می‌کند و متدهایی برای اضافه کردن، حذف و اطلاع‌رسانی آنها فراهم می‌کند.

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('Observer 1'); const obs2 = new Observer('Observer 2'); subject.subscribe(obs1); subject.subscribe(obs2); subject.notify('Something happened!'); // Observer 1 received: Something happened! // Observer 2 received: Something happened!

پیدا کردن عدد تکراری

با داشتن آرایه‌ای از اعداد صحیح nums حاوی n + 1 عدد صحیح که هر عدد صحیح در محدوده [1, n] (شامل) است، عدد تکراری را پیدا کنید.

توضیح: از آنجا که اعداد در [1, n] هستند و اندازه آرایه n+1 است، حداقل یک تکرار را تضمین می‌کند. این را می‌توان به یک مشکل یافتن چرخه در لیست پیوندی (Floyd's Tortoise and Hare) نگاشت. مقادیر آرایه را به عنوان اشاره‌گر در نظر بگیرید: index -> nums[index].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Phase 1: Find the intersection point do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Phase 2: Find the entrance to the cycle 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

پاک‌کننده (Sanitizer) اولیه HTML

تابعی بنویسید برای پاک کردن اولیه یک رشته HTML، حذف تگ‌های بالقوه مضر (مانند <script>) در حالی که تگ‌های امن (مانند <p>, <b>) را نگه می‌دارد.

توضیح: این یک موضوع پیچیده است، و پاک‌کننده‌های دنیای واقعی پیچیده هستند. برای یک مصاحبه، یک رویکرد اولیه ممکن است شامل استفاده از Regex برای حذف تگ‌های خاص یا فقط اجازه دادن به یک لیست سفید از تگ‌ها باشد. این برای تولید امن نیست.

function basicSanitize(html) { // WARNING: This is a very basic and insecure example. // Do NOT use this in production. Use a library like DOMPurify. // Remove script tags let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Example: Remove onclick attributes (very basic) 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>Hello <b >World</b></p>

فاصله ویرایش (Edit Distance)

با داشتن دو رشته word1 و word2، حداقل تعداد عملیات (درج، حذف، یا جایگزینی) مورد نیاز برای تبدیل word1 به word2 را برگردانید.

توضیح: این یک مشکل کلاسیک برنامه‌نویسی پویا است (فاصله لونشتین). یک آرایه 2D به نام 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, // Deletion dp[i][j - 1] + 1, // Insertion dp[i - 1][j - 1] + cost // Substitution/Match ); } } 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 وزیر

مسئله N وزیر، مسئله قرار دادن N وزیر شطرنج در یک صفحه شطرنج N×N است به طوری که هیچ دو وزیری یکدیگر را تهدید نکنند. با داشتن N، یک راه حل معتبر (یا همه راه حل‌ها) را برگردانید.

توضیح: از بازگشت به عقب (backtracking) استفاده کنید. وزیرها را سطر به سطر قرار دهید. برای هر سطر، سعی کنید یک وزیر را در هر ستون قرار دهید. قبل از قرار دادن، بررسی کنید که آیا موقعیت پیشنهادی امن است (توسط وزیرهای در سطرهای قبلی حمله نمی‌شود). اگر امن بود، آن را قرار دهید و برای سطر بعدی به صورت بازگشتی فراخوانی کنید. اگر بازگشت شکست خورد، به عقب برگردید (وزیر را حذف کنید) و ستون بعدی را امتحان کنید.

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; // Check col for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Check diag up-left for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Check diag up-right 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 } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

استفاده از WeakMap برای داده‌های خصوصی

نشان دهید که چگونه از WeakMap برای ذخیره داده‌های خصوصی برای نمونه‌های کلاس استفاده کنید.

توضیح: WeakMap به شما امکان می‌دهد داده‌ها را با یک شیء به گونه‌ای مرتبط کنید که اگر شیء دیگر مرجع نداشته باشد، از جمع‌آوری زباله جلوگیری نمی‌کند. این برای ایجاد اعضای 'خصوصی' در کلاس‌های جاوااسکریپت قبل از اینکه فیلدهای خصوصی بومی به طور گسترده در دسترس باشند یا برای موارد استفاده خاص مفید است.

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('Alice', 30); console.log(person.getName()); // Alice person.celebrateBirthday(); console.log(person.getAge()); // 31 // console.log(person.name); // undefined - 'name' isn't a public property // console.log(privateData.get(person)); // Accessible, but not directly via '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); } }); }); }); } // Example: 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]; // Ensure nums1 is smaller 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('Input arrays are not sorted'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

حل کننده سودوکو

برنامه‌ای بنویسید برای حل یک پازل سودوکو با پر کردن خانه‌های خالی.

توضیح: از بازگشت به عقب (backtracking) استفاده کنید. یک خانه خالی را پیدا کنید. سعی کنید آن را با اعداد 1 تا 9 پر کنید. برای هر عدد، بررسی کنید که آیا معتبر است (قوانین سودوکو را نقض نمی‌کند). اگر معتبر بود، حل کننده را به صورت بازگشتی فراخوانی کنید. اگر بازگشت true را برگرداند، یک راه حل پیدا شده است. در غیر این صورت، به عقب برگردید (خانه را بازنشانی کنید) و عدد بعدی را امتحان کنید.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Check row if (board[i][col] === numStr) return false; // Check col 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; // Check box } 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] = '.'; // Backtrack } } return false; // No valid number found } } } return true; // Board solved } backtrack(); return board; } // Example requires a 9x9 board with '.' for empty cells.

پیاده‌سازی یک الگوی میان‌افزار پایه

پیاده‌سازی یک الگوی میان‌افزار ساده که اغلب در فریم‌ورک‌های وب دیده می‌شود.

توضیح: توابع میان‌افزار یک درخواست را قبل از رسیدن به کنترل‌کننده نهایی پردازش می‌کنند. هر میان‌افزار می‌تواند درخواست/پاسخ را تغییر دهد یا کنترل را به میان‌افزار 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: Logging...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: Authenticating...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: Final Handler:', req); }); app.handle({ data: 'some request' }); // 1: Logging... // 2: Authenticating... // 3: Final Handler: { data: 'some request', logged: true, authed: true }

تشخیص چرخه در گراف جهت‌دار

با داشتن یک گراف جهت‌دار، تابعی بنویسید تا مشخص کند که آیا شامل یک چرخه است یا خیر.

توضیح: از جستجوی عمق اول (DFS) استفاده کنید. دو مجموعه را نگهداری کنید: visiting (گره‌هایی که در حال حاضر در پشته بازگشتی هستند) و visited (گره‌هایی که به طور کامل کاوش شده‌اند). اگر در طول DFS به گره‌ای در مجموعه visiting برخورد کنید، یک لبه برگشتی پیدا کرده‌اید، که به معنی وجود یک چرخه است.

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; // Cycle detected 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)); // true console.log(hasCycleDirected(acyclicGraph)); // false

پیاده‌سازی رفتار Object.freeze

Object.freeze را توضیح دهید و یک نسخه (کم‌عمق) از آن را پیاده‌سازی کنید.

توضیح: Object.freeze از اضافه کردن ویژگی‌های جدید، حذف ویژگی‌های موجود، و تغییر ویژگی‌های موجود یا قابلیت شمارش، پیکربندی یا قابلیت نوشتن آنها جلوگیری می‌کند. یک شیء را (به صورت کم‌عمق) تغییرناپذیر می‌کند. می‌توانید آن را با استفاده از Object.preventExtensions و Object.defineProperty برای غیرقابل نوشتن و غیرقابل پیکربندی کردن ویژگی‌های موجود پیاده‌سازی کنید.

function myFreeze(obj) { Object.preventExtensions(obj); // Prevent adding new properties 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; // Fails silently (or throws in strict mode) // delete obj.a; // Fails silently (or throws in strict mode) // obj.a = 10; // Fails silently (or throws in strict mode) 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; // Move 100px in 2 seconds (50px/sec) position = (elapsedTime / 2000) * 100; if (position < 200) { // Keep animating until it reaches 200px box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Start the animation // requestAnimationFrame(animate); // Uncomment to run in browser console.log('Use requestAnimationFrame for browser animations.');

پیاده‌سازی Array.prototype.some

نسخه خود را از Array.prototype.some پیاده‌سازی کنید.

توضیح: some بررسی می‌کند که آیا حداقل یک عنصر در آرایه از آزمون پیاده‌سازی شده توسط تابع ارائه شده عبور می‌کند یا خیر. اگر عنصری را پیدا کند که برای آن تابع callback مقدار true را برگرداند، true را برمی‌گرداند؛ در غیر این صورت، false را برمی‌گرداند.

function mySome(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { // Handle sparse arrays 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); // true

پیاده‌سازی 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); // true const arr2 = [1, 30, 39, 29, 10, 45]; const isBelow40_2 = myEvery(arr2, x => x < 40); console.log(isBelow40_2); // false

پیاده‌سازی 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) چیستند؟

توضیح دهید که کارگران وب چه هستند و مورد استفاده اصلی آنها چیست.

توضیح: کارگران وب ابزاری برای اجرای اسکریپت‌ها در رشته‌های پس‌زمینه در محتوای وب هستند. مورد استفاده اصلی آنها انتقال وظایف طولانی مدت یا محاسباتی فشرده از رشته اصلی (که به روز رسانی‌های رابط کاربری و تعاملات کاربر را انجام می‌دهد) است، بنابراین از عدم پاسخگویی یا 'قفل شدن' رابط کاربری جلوگیری می‌کند. کارگران از طریق ارسال پیام (postMessage و onmessage) با رشته اصلی ارتباط برقرار می‌کنند.

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // Send data to worker myWorker.onmessage = function(e) { console.log('Result from worker:', e.data); } } else { console.log('Your browser does not support Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('Message received from main script'); const result = e.data[0] * e.data[1]; console.log('Posting result back to main script'); self.postMessage(result); } */ console.log('Web Workers run scripts in background threads.');

روش‌های رمزگشایی

یک پیام حاوی حروف از A-Z می‌تواند با استفاده از نگاشت 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26 به اعداد رمزگذاری شود. با داشتن یک رشته s که فقط شامل ارقام است، تعداد راه‌های رمزگشایی آن را برگردانید.

توضیح: از برنامه‌نویسی پویا استفاده کنید. فرض کنید dp[i] تعداد راه‌های رمزگشایی s[0...i-1] است. dp[i] به dp[i-1] (اگر s[i-1] یک کد 1 رقمی معتبر باشد) و dp[i-2] (اگر s[i-2...i-1] یک کد 2 رقمی معتبر باشد) بستگی دارد.

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' or '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; // Calculate carry a = a ^ b; // Calculate sum without carry b = carry; // Carry becomes the new 'b' } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

بررسی پالیندروم (عدد)

با داشتن یک عدد صحیح x، اگر x یک پالیندروم است، true را برگردانید، در غیر این صورت false را برگردانید.

توضیح: یک عدد منفی پالیندروم نیست. عدد را به یک رشته تبدیل کنید و بررسی کنید که آیا رشته یک پالیندروم است (از جلو و عقب یکسان خوانده می‌شود). به صورت جایگزین، عدد را به صورت ریاضی معکوس کرده و مقایسه کنید.

function isPalindromeNumber(x) { if (x < 0) return false; // String approach // return String(x) === String(x).split('').reverse().join(''); // Mathematical approach 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)); // true console.log(isPalindromeNumber(-121)); // false console.log(isPalindromeNumber(10)); // false

الگوی کارخانه (Factory Pattern)

الگوی طراحی کارخانه را در جاوااسکریپت پیاده‌سازی کنید.

توضیح: الگوی کارخانه یک واسط برای ایجاد اشیاء در یک کلاس والد فراهم می‌کند، اما به کلاس‌های فرزند اجازه می‌دهد نوع اشیائی که ایجاد خواهند شد را تغییر دهند. این برای ایجاد اشیاء بدون مشخص کردن کلاس دقیق مفید است.

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('Unknown vehicle type'); } } } 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 در جاوااسکریپت چیست و یک مورد استفاده از آن را ارائه دهید، مانند ایجاد ویژگی‌های 'خصوصی' یا کلیدهای شیء منحصر به فرد.

توضیح: Symbol یک نوع داده اولیه است که در ES6 معرفی شد. نمونه‌های آن منحصر به فرد و تغییرناپذیر هستند. آنها اغلب به عنوان کلید برای ویژگی‌های شیء برای جلوگیری از تداخل نام بین کتابخانه‌های مختلف یا برای ایجاد ویژگی‌های شبه خصوصی (اگرچه واقعاً خصوصی نیستند، اما از طریق تکرار معمول یا Object.keys قابل دسترسی نیستند) استفاده می‌شوند.

const _privateName = Symbol('name'); const _privateMethod = Symbol('greet'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`Hello, my name is ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('Bob'); p.introduce(); // Hello, my name is Bob console.log(Object.keys(p)); // [] - Symbols are not included console.log(p[_privateName]); // Bob (Accessible if you have the Symbol)

تبدیل arguments به یک آرایه واقعی

چگونه می‌توانید شیء arguments (موجود در توابع غیر فلش) را به یک آرایه واقعی جاوااسکریپت تبدیل کنید؟

توضیح: شیء 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('Using Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Spread Syntax const args2 = [...arguments]; console.log('Using Spread:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Using Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Using Array.from: 10 // Using Spread: 10 // Using Slice: 10