javascript

exercises

exercises.js
/**
 * ========================================
 * 13.2 Regular Expressions - Exercises
 * ========================================
 *
 * Practice working with JavaScript regex.
 */

/**
 * EXERCISE 1: Basic Pattern Matching
 *
 * Write functions to test for:
 * - Contains digits
 * - Contains only letters
 * - Contains only alphanumeric
 */

function patternChecks(str) {
    // YOUR CODE HERE:
    // Return { hasDigits, lettersOnly, alphanumericOnly }
}

// console.log(patternChecks('Hello123'));
// { hasDigits: true, lettersOnly: false, alphanumericOnly: true }
// console.log(patternChecks('Hello'));
// { hasDigits: false, lettersOnly: true, alphanumericOnly: true }

/*
 * SOLUTION:
 *
 * function patternChecks(str) {
 *     return {
 *         hasDigits: /\d/.test(str),
 *         lettersOnly: /^[a-zA-Z]+$/.test(str),
 *         alphanumericOnly: /^[a-zA-Z0-9]+$/.test(str)
 *     };
 * }
 */


/**
 * EXERCISE 2: Email Validation
 *
 * Create a comprehensive email validator that checks:
 * - Has @ symbol
 * - Has valid domain
 * - Has valid TLD (2+ chars)
 */

function validateEmail(email) {
    // YOUR CODE HERE:
    // Return true if valid email format
}

// console.log(validateEmail('user@example.com'));     // true
// console.log(validateEmail('user.name@sub.domain.org')); // true
// console.log(validateEmail('invalid'));              // false
// console.log(validateEmail('@example.com'));         // false

/*
 * SOLUTION:
 *
 * function validateEmail(email) {
 *     const pattern = /^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
 *     return pattern.test(email);
 * }
 */


/**
 * EXERCISE 3: Password Strength Checker
 *
 * Check password strength based on:
 * - minLength (default 8)
 * - requireUppercase
 * - requireLowercase
 * - requireDigit
 * - requireSpecial (!@#$%^&*)
 */

function checkPasswordStrength(password, options = {}) {
    // YOUR CODE HERE:
    // Return { valid, issues: [] }
}

// console.log(checkPasswordStrength('Pass123!', {
//     minLength: 8,
//     requireUppercase: true,
//     requireLowercase: true,
//     requireDigit: true,
//     requireSpecial: true
// }));
// { valid: true, issues: [] }

/*
 * SOLUTION:
 *
 * function checkPasswordStrength(password, options = {}) {
 *     const {
 *         minLength = 8,
 *         requireUppercase = false,
 *         requireLowercase = false,
 *         requireDigit = false,
 *         requireSpecial = false
 *     } = options;
 *     
 *     const issues = [];
 *     
 *     if (password.length < minLength) {
 *         issues.push(`Must be at least ${minLength} characters`);
 *     }
 *     if (requireUppercase && !/[A-Z]/.test(password)) {
 *         issues.push('Must contain uppercase letter');
 *     }
 *     if (requireLowercase && !/[a-z]/.test(password)) {
 *         issues.push('Must contain lowercase letter');
 *     }
 *     if (requireDigit && !/\d/.test(password)) {
 *         issues.push('Must contain digit');
 *     }
 *     if (requireSpecial && !/[!@#$%^&*]/.test(password)) {
 *         issues.push('Must contain special character');
 *     }
 *     
 *     return { valid: issues.length === 0, issues };
 * }
 */


/**
 * EXERCISE 4: Extract All Matches
 *
 * Extract all occurrences with their positions.
 */

function findAllMatches(str, pattern) {
    // YOUR CODE HERE:
    // Return array of { match, index }
}

// console.log(findAllMatches('test1 test2 test3', /test\d/g));
// [
//     { match: 'test1', index: 0 },
//     { match: 'test2', index: 6 },
//     { match: 'test3', index: 12 }
// ]

/*
 * SOLUTION:
 *
 * function findAllMatches(str, pattern) {
 *     return [...str.matchAll(pattern)].map(match => ({
 *         match: match[0],
 *         index: match.index
 *     }));
 * }
 */


/**
 * EXERCISE 5: Parse Key-Value Pairs
 *
 * Parse a string of key=value pairs.
 */

function parseKeyValue(str) {
    // YOUR CODE HERE:
    // Return object with key-value pairs
}

// console.log(parseKeyValue('name=John; age=30; city=NYC'));
// { name: 'John', age: '30', city: 'NYC' }

/*
 * SOLUTION:
 *
 * function parseKeyValue(str) {
 *     const pattern = /(\w+)=([^;]+)/g;
 *     const result = {};
 *     
 *     for (const match of str.matchAll(pattern)) {
 *         result[match[1]] = match[2].trim();
 *     }
 *     
 *     return result;
 * }
 */


/**
 * EXERCISE 6: Phone Number Formatter
 *
 * Detect and format various phone number formats.
 */

function formatPhoneNumber(phone) {
    // YOUR CODE HERE:
    // Accept various formats, output (XXX) XXX-XXXX
}

// console.log(formatPhoneNumber('5551234567'));      // '(555) 123-4567'
// console.log(formatPhoneNumber('555-123-4567'));    // '(555) 123-4567'
// console.log(formatPhoneNumber('(555) 123-4567')); // '(555) 123-4567'
// console.log(formatPhoneNumber('555.123.4567'));   // '(555) 123-4567'

/*
 * SOLUTION:
 *
 * function formatPhoneNumber(phone) {
 *     const digits = phone.replace(/\D/g, '');
 *     
 *     if (digits.length !== 10) {
 *         return null; // Invalid
 *     }
 *     
 *     return digits.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
 * }
 */


/**
 * EXERCISE 7: URL Parser
 *
 * Parse URL components using regex.
 */

function parseURL(url) {
    // YOUR CODE HERE:
    // Return { protocol, domain, port, path, query, hash }
}

// console.log(parseURL('https://example.com:8080/path/to/page?q=test#section'));
// {
//     protocol: 'https',
//     domain: 'example.com',
//     port: '8080',
//     path: '/path/to/page',
//     query: 'q=test',
//     hash: 'section'
// }

/*
 * SOLUTION:
 *
 * function parseURL(url) {
 *     const pattern = /^(?<protocol>\w+):\/\/(?<domain>[\w.-]+)(?::(?<port>\d+))?(?<path>\/[^?#]*)?(?:\?(?<query>[^#]*))?(?:#(?<hash>.*))?$/;
 *     const match = url.match(pattern);
 *     
 *     if (!match) return null;
 *     
 *     return {
 *         protocol: match.groups.protocol || null,
 *         domain: match.groups.domain || null,
 *         port: match.groups.port || null,
 *         path: match.groups.path || '/',
 *         query: match.groups.query || null,
 *         hash: match.groups.hash || null
 *     };
 * }
 */


/**
 * EXERCISE 8: Credit Card Validation
 *
 * Validate and identify credit card type.
 */

function validateCreditCard(number) {
    // YOUR CODE HERE:
    // Return { valid, type } where type is Visa, Mastercard, Amex, or Unknown
}

// console.log(validateCreditCard('4111111111111111'));  // { valid: true, type: 'Visa' }
// console.log(validateCreditCard('5500000000000004'));  // { valid: true, type: 'Mastercard' }
// console.log(validateCreditCard('371449635398431'));   // { valid: true, type: 'Amex' }

/*
 * SOLUTION:
 *
 * function validateCreditCard(number) {
 *     const cleaned = number.replace(/\D/g, '');
 *     
 *     const patterns = {
 *         Visa: /^4\d{12}(?:\d{3})?$/,
 *         Mastercard: /^5[1-5]\d{14}$/,
 *         Amex: /^3[47]\d{13}$/
 *     };
 *     
 *     for (const [type, pattern] of Object.entries(patterns)) {
 *         if (pattern.test(cleaned)) {
 *             return { valid: true, type };
 *         }
 *     }
 *     
 *     return { valid: false, type: 'Unknown' };
 * }
 */


/**
 * EXERCISE 9: Highlight Search Terms
 *
 * Wrap all occurrences of search terms with HTML tags.
 * Handle case-insensitivity and word boundaries.
 */

function highlightTerms(text, terms, tag = 'mark') {
    // YOUR CODE HERE:
    // Wrap each term occurrence with <tag></tag>
}

// console.log(highlightTerms('The quick brown fox', ['the', 'fox']));
// '<mark>The</mark> quick brown <mark>fox</mark>'

/*
 * SOLUTION:
 *
 * function highlightTerms(text, terms, tag = 'mark') {
 *     const escaped = terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
 *     const pattern = new RegExp(`\\b(${escaped.join('|')})\\b`, 'gi');
 *     return text.replace(pattern, `<${tag}>$1</${tag}>`);
 * }
 */


/**
 * EXERCISE 10: Extract Markdown Links
 *
 * Extract all markdown links [text](url) from text.
 */

function extractMarkdownLinks(markdown) {
    // YOUR CODE HERE:
    // Return array of { text, url }
}

// const md = 'Check out [Google](https://google.com) and [GitHub](https://github.com)';
// console.log(extractMarkdownLinks(md));
// [
//     { text: 'Google', url: 'https://google.com' },
//     { text: 'GitHub', url: 'https://github.com' }
// ]

/*
 * SOLUTION:
 *
 * function extractMarkdownLinks(markdown) {
 *     const pattern = /\[([^\]]+)\]\(([^)]+)\)/g;
 *     return [...markdown.matchAll(pattern)].map(match => ({
 *         text: match[1],
 *         url: match[2]
 *     }));
 * }
 */


/**
 * EXERCISE 11: Validate IP Address
 *
 * Validate IPv4 addresses (0-255 for each octet).
 */

function isValidIPv4(ip) {
    // YOUR CODE HERE:
    // Return true if valid IPv4
}

// console.log(isValidIPv4('192.168.1.1'));   // true
// console.log(isValidIPv4('255.255.255.0')); // true
// console.log(isValidIPv4('256.1.1.1'));     // false (256 > 255)
// console.log(isValidIPv4('1.1.1'));         // false (only 3 octets)

/*
 * SOLUTION:
 *
 * function isValidIPv4(ip) {
 *     const octet = '(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)';
 *     const pattern = new RegExp(`^${octet}\\.${octet}\\.${octet}\\.${octet}$`);
 *     return pattern.test(ip);
 * }
 */


/**
 * EXERCISE 12: Template Variable Replacement
 *
 * Replace {{variable}} placeholders with values from object.
 */

function templateReplace(template, data) {
    // YOUR CODE HERE:
    // Replace all {{key}} with data[key]
}

// const template = 'Hello {{name}}, you have {{count}} messages';
// console.log(templateReplace(template, { name: 'John', count: 5 }));
// 'Hello John, you have 5 messages'

/*
 * SOLUTION:
 *
 * function templateReplace(template, data) {
 *     return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
 *         return data.hasOwnProperty(key) ? data[key] : match;
 *     });
 * }
 */


/**
 * EXERCISE 13: Tokenize Expression
 *
 * Tokenize a simple math expression.
 */

function tokenize(expression) {
    // YOUR CODE HERE:
    // Return array of tokens { type, value }
}

// console.log(tokenize('3 + 42 * 5 - 10'));
// [
//     { type: 'number', value: '3' },
//     { type: 'operator', value: '+' },
//     { type: 'number', value: '42' },
//     { type: 'operator', value: '*' },
//     { type: 'number', value: '5' },
//     { type: 'operator', value: '-' },
//     { type: 'number', value: '10' }
// ]

/*
 * SOLUTION:
 *
 * function tokenize(expression) {
 *     const pattern = /(\d+)|([+\-*/])/g;
 *     const tokens = [];
 *     
 *     for (const match of expression.matchAll(pattern)) {
 *         if (match[1]) {
 *             tokens.push({ type: 'number', value: match[1] });
 *         } else if (match[2]) {
 *             tokens.push({ type: 'operator', value: match[2] });
 *         }
 *     }
 *     
 *     return tokens;
 * }
 */


/**
 * EXERCISE 14: Find Repeated Words
 *
 * Find consecutive repeated words in text.
 */

function findRepeatedWords(text) {
    // YOUR CODE HERE:
    // Return array of repeated words
}

// console.log(findRepeatedWords('The the quick brown fox fox jumps'));
// ['the', 'fox']

/*
 * SOLUTION:
 *
 * function findRepeatedWords(text) {
 *     const pattern = /\b(\w+)\s+\1\b/gi;
 *     return [...text.matchAll(pattern)].map(match => match[1].toLowerCase());
 * }
 */


/**
 * EXERCISE 15: Validate Date Format
 *
 * Validate dates in various formats and normalize.
 */

function validateAndNormalizeDate(dateStr) {
    // YOUR CODE HERE:
    // Accept: MM/DD/YYYY, MM-DD-YYYY, YYYY-MM-DD
    // Return: { valid, normalized } where normalized is YYYY-MM-DD
}

// console.log(validateAndNormalizeDate('12/25/2024'));  // { valid: true, normalized: '2024-12-25' }
// console.log(validateAndNormalizeDate('2024-12-25')); // { valid: true, normalized: '2024-12-25' }
// console.log(validateAndNormalizeDate('13/25/2024')); // { valid: false, normalized: null }

/*
 * SOLUTION:
 *
 * function validateAndNormalizeDate(dateStr) {
 *     // MM/DD/YYYY or MM-DD-YYYY
 *     const mdyPattern = /^(0[1-9]|1[0-2])[\/\-](0[1-9]|[12]\d|3[01])[\/\-](\d{4})$/;
 *     // YYYY-MM-DD
 *     const ymdPattern = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
 *     
 *     let match = dateStr.match(mdyPattern);
 *     if (match) {
 *         return {
 *             valid: true,
 *             normalized: `${match[3]}-${match[1]}-${match[2]}`
 *         };
 *     }
 *     
 *     match = dateStr.match(ymdPattern);
 *     if (match) {
 *         return { valid: true, normalized: dateStr };
 *     }
 *     
 *     return { valid: false, normalized: null };
 * }
 */


/**
 * EXERCISE 16: Clean and Normalize Text
 *
 * Clean text by:
 * - Removing extra whitespace
 * - Removing special characters (keep alphanumeric and basic punctuation)
 * - Normalizing quotes
 */

function cleanText(text) {
    // YOUR CODE HERE:
    // Return cleaned text
}

// console.log(cleanText('  Hello   "World"!!   How\'s  it  going???  '));
// 'Hello "World"! How\'s it going?'

/*
 * SOLUTION:
 *
 * function cleanText(text) {
 *     return text
 *         .replace(/\s+/g, ' ')              // Collapse whitespace
 *         .replace(/[""]/g, '"')             // Normalize quotes
 *         .replace(/['']/g, "'")             // Normalize apostrophes
 *         .replace(/([!?.])\1+/g, '$1')      // Remove repeated punctuation
 *         .replace(/[^\w\s.,!?'"()-]/g, '')  // Remove other special chars
 *         .trim();
 * }
 */


/**
 * EXERCISE 17: Extract Code Blocks
 *
 * Extract code blocks from markdown text.
 */

function extractCodeBlocks(markdown) {
    // YOUR CODE HERE:
    // Return array of { language, code }
}

// const md = `
// Here's some JavaScript:
// \`\`\`javascript
// const x = 1;
// console.log(x);
// \`\`\`
// And some Python:
// \`\`\`python
// x = 1
// print(x)
// \`\`\`
// `;
// console.log(extractCodeBlocks(md));
// [
//     { language: 'javascript', code: 'const x = 1;\nconsole.log(x);' },
//     { language: 'python', code: 'x = 1\nprint(x)' }
// ]

/*
 * SOLUTION:
 *
 * function extractCodeBlocks(markdown) {
 *     const pattern = /```(\w*)\n([\s\S]*?)```/g;
 *     return [...markdown.matchAll(pattern)].map(match => ({
 *         language: match[1] || 'text',
 *         code: match[2].trim()
 *     }));
 * }
 */


/**
 * EXERCISE 18: Mask Sensitive Data
 *
 * Mask various types of sensitive data.
 */

function maskSensitiveData(text) {
    // YOUR CODE HERE:
    // Mask emails, phone numbers, credit cards
}

// const text = 'Email: john@example.com, Phone: 555-123-4567, Card: 4111111111111111';
// console.log(maskSensitiveData(text));
// 'Email: j***@***.com, Phone: ***-***-4567, Card: ************1111'

/*
 * SOLUTION:
 *
 * function maskSensitiveData(text) {
 *     return text
 *         // Mask email (show first char and domain suffix)
 *         .replace(/([\w.-]+)@([\w.-]+)\.(\w+)/g, (m, user, domain, tld) =>
 *             `${user[0]}***@***.${tld}`
 *         )
 *         // Mask phone (show last 4 digits)
 *         .replace(/(\d{3})[-.]?(\d{3})[-.]?(\d{4})/g, '***-***-$3')
 *         // Mask credit card (show last 4 digits)
 *         .replace(/\d{12}(\d{4})/g, '************$1');
 * }
 */


/**
 * EXERCISE 19: Slugify with Transliteration
 *
 * Create URL-friendly slugs, handling accented characters.
 */

function slugify(text) {
    // YOUR CODE HERE:
    // Convert to lowercase, replace accents, remove special chars, replace spaces with hyphens
}

// console.log(slugify('Hello World!'));         // 'hello-world'
// console.log(slugify('Café & Restaurant'));    // 'cafe-restaurant'
// console.log(slugify('  Multiple   Spaces  ')); // 'multiple-spaces'

/*
 * SOLUTION:
 *
 * function slugify(text) {
 *     const accents = {
 *         'à|á|â|ã|ä|å': 'a',
 *         'è|é|ê|ë': 'e',
 *         'ì|í|î|ï': 'i',
 *         'ò|ó|ô|õ|ö': 'o',
 *         'ù|ú|û|ü': 'u',
 *         'ñ': 'n',
 *         'ç': 'c'
 *     };
 *     
 *     let slug = text.toLowerCase();
 *     
 *     for (const [pattern, replacement] of Object.entries(accents)) {
 *         slug = slug.replace(new RegExp(pattern, 'g'), replacement);
 *     }
 *     
 *     return slug
 *         .replace(/[^\w\s-]/g, '')  // Remove special chars
 *         .replace(/\s+/g, '-')       // Replace spaces with hyphens
 *         .replace(/-+/g, '-')        // Remove multiple hyphens
 *         .replace(/^-|-$/g, '');     // Remove leading/trailing hyphens
 * }
 */


/**
 * EXERCISE 20: Build Dynamic Regex
 *
 * Create a function that builds a regex from user input safely.
 */

function buildSearchRegex(searchTerm, options = {}) {
    // YOUR CODE HERE:
    // Build regex with options: wholeWord, caseSensitive, startsWith, endsWith
}

// const regex = buildSearchRegex('test', { wholeWord: true, caseSensitive: false });
// console.log(regex.test('This is a TEST'));  // true
// console.log(regex.test('testing'));         // false (not whole word)

/*
 * SOLUTION:
 *
 * function buildSearchRegex(searchTerm, options = {}) {
 *     const {
 *         wholeWord = false,
 *         caseSensitive = true,
 *         startsWith = false,
 *         endsWith = false
 *     } = options;
 *     
 *     // Escape special regex characters
 *     let pattern = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
 *     
 *     if (wholeWord) {
 *         pattern = `\\b${pattern}\\b`;
 *     }
 *     if (startsWith) {
 *         pattern = `^${pattern}`;
 *     }
 *     if (endsWith) {
 *         pattern = `${pattern}$`;
 *     }
 *     
 *     const flags = caseSensitive ? '' : 'i';
 *     return new RegExp(pattern, flags);
 * }
 */


console.log('Regular expressions exercises loaded!');
Exercises - JavaScript Tutorial | DeepML