javascript

exercises

exercises.js
// ============================================
// 17.3 Symbols - Exercises
// ============================================

// Exercise 1: Create Unique Symbols
// Create two symbols with the same description
// Verify they are not equal

// Your code here:
// const sym1 = ...
// const sym2 = ...
// console.log(sym1 === sym2);  // Should be false

/*
Solution:
const sym1 = Symbol('test');
const sym2 = Symbol('test');
console.log(sym1 === sym2);  // false
console.log(sym1.description);  // 'test'
console.log(sym2.description);  // 'test'
*/

// --------------------------------------------

// Exercise 2: Symbol as Object Key
// Create an object with a regular property and a symbol property
// The symbol property should store a secret ID

// Your code here:
// const SECRET_ID = Symbol('secretId');
// const user = {
//     name: 'Alice',
//     // Add symbol property
// };

/*
Solution:
const SECRET_ID = Symbol('secretId');
const user = {
    name: 'Alice',
    [SECRET_ID]: 'user_12345_secret'
};

console.log(user.name);         // 'Alice'
console.log(user[SECRET_ID]);   // 'user_12345_secret'
console.log(Object.keys(user)); // ['name']
*/

// --------------------------------------------

// Exercise 3: Global Symbol Registry
// Create two global symbols with the same key
// Verify they are the same symbol

// Your code here:
// const globalA = Symbol.for(...);
// const globalB = Symbol.for(...);

/*
Solution:
const globalA = Symbol.for('shared.key');
const globalB = Symbol.for('shared.key');

console.log(globalA === globalB);  // true
console.log(Symbol.keyFor(globalA));  // 'shared.key'
*/

// --------------------------------------------

// Exercise 4: Hide Private Data
// Create a class that uses a symbol to store private balance
// Provide public methods to get and update the balance

// Your code here:
// const BALANCE = Symbol('balance');
//
// class Wallet {
//     constructor(initialBalance) {
//         ...
//     }
//
//     getBalance() {
//         ...
//     }
//
//     add(amount) {
//         ...
//     }
// }

/*
Solution:
const BALANCE = Symbol('balance');

class Wallet {
    constructor(initialBalance) {
        this[BALANCE] = initialBalance;
    }
    
    getBalance() {
        return this[BALANCE];
    }
    
    add(amount) {
        if (amount > 0) {
            this[BALANCE] += amount;
        }
    }
    
    spend(amount) {
        if (amount > 0 && amount <= this[BALANCE]) {
            this[BALANCE] -= amount;
            return true;
        }
        return false;
    }
}

const wallet = new Wallet(100);
console.log(wallet.getBalance());  // 100
wallet.add(50);
console.log(wallet.getBalance());  // 150
console.log(Object.keys(wallet));  // [] - BALANCE is hidden
*/

// --------------------------------------------

// Exercise 5: Implement Symbol.iterator
// Create an object that can be iterated using for...of
// It should yield numbers from min to max

// Your code here:
// const counter = {
//     min: 1,
//     max: 5,
//     [Symbol.iterator]() {
//         ...
//     }
// };
//
// for (const num of counter) {
//     console.log(num);  // 1, 2, 3, 4, 5
// }

/*
Solution:
const counter = {
    min: 1,
    max: 5,
    [Symbol.iterator]() {
        let current = this.min;
        const max = this.max;
        
        return {
            next() {
                if (current <= max) {
                    return { value: current++, done: false };
                }
                return { done: true };
            }
        };
    }
};

for (const num of counter) {
    console.log(num);  // 1, 2, 3, 4, 5
}

console.log([...counter]);  // [1, 2, 3, 4, 5]
*/

// --------------------------------------------

// Exercise 6: Custom toStringTag
// Create a class with a custom [Symbol.toStringTag]
// so Object.prototype.toString.call() returns '[object MyClass]'

// Your code here:
// class MyClass {
//     ...
// }
//
// const obj = new MyClass();
// console.log(Object.prototype.toString.call(obj));  // '[object MyClass]'

/*
Solution:
class MyClass {
    get [Symbol.toStringTag]() {
        return 'MyClass';
    }
}

const obj = new MyClass();
console.log(Object.prototype.toString.call(obj));  // '[object MyClass]'
*/

// --------------------------------------------

// Exercise 7: Custom hasInstance
// Create a class where instanceof checks if a value is a positive number

// Your code here:
// class PositiveNumber {
//     static [Symbol.hasInstance](value) {
//         ...
//     }
// }
//
// console.log(5 instanceof PositiveNumber);   // true
// console.log(-3 instanceof PositiveNumber);  // false

/*
Solution:
class PositiveNumber {
    static [Symbol.hasInstance](value) {
        return typeof value === 'number' && value > 0;
    }
}

console.log(5 instanceof PositiveNumber);    // true
console.log(-3 instanceof PositiveNumber);   // false
console.log(0 instanceof PositiveNumber);    // false
console.log('5' instanceof PositiveNumber);  // false
*/

// --------------------------------------------

// Exercise 8: Custom toPrimitive
// Create an object that converts to different values based on hint

// Your code here:
// const temperature = {
//     celsius: 25,
//     fahrenheit: 77,
//     [Symbol.toPrimitive](hint) {
//         ...
//     }
// };
//
// console.log(+temperature);     // 25 (number)
// console.log(`${temperature}`); // '25°C' (string)

/*
Solution:
const temperature = {
    celsius: 25,
    fahrenheit: 77,
    [Symbol.toPrimitive](hint) {
        switch (hint) {
            case 'number':
                return this.celsius;
            case 'string':
                return `${this.celsius}°C`;
            default:
                return this.celsius;
        }
    }
};

console.log(+temperature);       // 25
console.log(`${temperature}`);   // '25°C'
console.log(temperature + 5);    // 30
*/

// --------------------------------------------

// Exercise 9: Status Constants with Symbols
// Create a set of status constants using symbols
// Create a function that returns a message based on status

// Your code here:
// const OrderStatus = {
//     PENDING: Symbol('pending'),
//     SHIPPED: Symbol('shipped'),
//     DELIVERED: Symbol('delivered')
// };
//
// function getStatusMessage(status) {
//     ...
// }

/*
Solution:
const OrderStatus = {
    PENDING: Symbol('pending'),
    SHIPPED: Symbol('shipped'),
    DELIVERED: Symbol('delivered'),
    CANCELLED: Symbol('cancelled')
};

function getStatusMessage(status) {
    switch (status) {
        case OrderStatus.PENDING:
            return 'Your order is being processed';
        case OrderStatus.SHIPPED:
            return 'Your order is on the way';
        case OrderStatus.DELIVERED:
            return 'Your order has been delivered';
        case OrderStatus.CANCELLED:
            return 'Your order was cancelled';
        default:
            return 'Unknown status';
    }
}

console.log(getStatusMessage(OrderStatus.PENDING));   // 'Your order is being processed'
console.log(getStatusMessage(OrderStatus.SHIPPED));   // 'Your order is on the way'
console.log(getStatusMessage('pending'));  // 'Unknown status' - strings don't match
*/

// --------------------------------------------

// Exercise 10: Get All Properties Including Symbols
// Create a function that returns all property keys (strings and symbols)

// Your code here:
// function getAllKeys(obj) {
//     ...
// }

const testObj = {
  name: 'Test',
  value: 42,
  [Symbol('hidden')]: 'secret',
};

// console.log(getAllKeys(testObj));
// Should include: 'name', 'value', and Symbol(hidden)

/*
Solution:
function getAllKeys(obj) {
    return [
        ...Object.keys(obj),
        ...Object.getOwnPropertySymbols(obj)
    ];
    // Or simply: return Reflect.ownKeys(obj);
}

const SYM = Symbol('hidden');
const testObj = {
    name: 'Test',
    value: 42,
    [SYM]: 'secret'
};

console.log(getAllKeys(testObj));  // ['name', 'value', Symbol(hidden)]
*/

// --------------------------------------------

// Exercise 11: Symbol-based Event System
// Create a simple event emitter using symbols for event types

// Your code here:
// const EVENTS = Symbol('events');
//
// class EventEmitter {
//     constructor() {
//         this[EVENTS] = {};
//     }
//
//     on(event, callback) {
//         ...
//     }
//
//     emit(event, data) {
//         ...
//     }
// }

/*
Solution:
const EVENTS = Symbol('events');

class EventEmitter {
    constructor() {
        this[EVENTS] = {};
    }
    
    on(event, callback) {
        if (!this[EVENTS][event]) {
            this[EVENTS][event] = [];
        }
        this[EVENTS][event].push(callback);
    }
    
    emit(event, data) {
        const callbacks = this[EVENTS][event] || [];
        callbacks.forEach(cb => cb(data));
    }
    
    off(event, callback) {
        if (!this[EVENTS][event]) return;
        this[EVENTS][event] = this[EVENTS][event].filter(cb => cb !== callback);
    }
}

const emitter = new EventEmitter();
emitter.on('message', data => console.log('Received:', data));
emitter.emit('message', 'Hello!');  // Received: Hello!

// Events are hidden
console.log(Object.keys(emitter));  // []
*/

// --------------------------------------------

// Exercise 12: Reverse Iterator
// Create an array subclass that iterates in reverse by default

// Your code here:
// class ReverseArray extends Array {
//     [Symbol.iterator]() {
//         ...
//     }
// }

/*
Solution:
class ReverseArray extends Array {
    [Symbol.iterator]() {
        let index = this.length - 1;
        const arr = this;
        
        return {
            next() {
                if (index >= 0) {
                    return { value: arr[index--], done: false };
                }
                return { done: true };
            }
        };
    }
}

const reversed = new ReverseArray(1, 2, 3, 4, 5);
console.log([...reversed]);  // [5, 4, 3, 2, 1]

for (const item of reversed) {
    console.log(item);  // 5, 4, 3, 2, 1
}
*/

// --------------------------------------------

// Exercise 13: Type Checking with Symbol
// Create utility symbols and functions for type checking

// Your code here:
// Create symbols for custom types and a typeOf function

/*
Solution:
const TYPE = Symbol('type');

class Email {
    static [Symbol.hasInstance](value) {
        return typeof value === 'string' && value.includes('@');
    }
    
    get [Symbol.toStringTag]() {
        return 'Email';
    }
}

class PhoneNumber {
    static [Symbol.hasInstance](value) {
        return typeof value === 'string' && /^\d{10}$/.test(value);
    }
}

console.log('test@example.com' instanceof Email);  // true
console.log('not-email' instanceof Email);         // false
console.log('1234567890' instanceof PhoneNumber);  // true
console.log('123' instanceof PhoneNumber);         // false
*/

// --------------------------------------------

// Exercise 14: Linked List with Symbol Iterator
// Create a linked list that's iterable

// Your code here:
// class LinkedList {
//     ...
//     [Symbol.iterator]() {
//         ...
//     }
// }

/*
Solution:
class LinkedList {
    constructor() {
        this.head = null;
        this.tail = null;
    }
    
    append(value) {
        const node = { value, next: null };
        if (!this.head) {
            this.head = this.tail = node;
        } else {
            this.tail.next = node;
            this.tail = node;
        }
    }
    
    [Symbol.iterator]() {
        let current = this.head;
        
        return {
            next() {
                if (current) {
                    const value = current.value;
                    current = current.next;
                    return { value, done: false };
                }
                return { done: true };
            }
        };
    }
}

const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);

console.log([...list]);  // [1, 2, 3]

for (const item of list) {
    console.log(item);  // 1, 2, 3
}
*/

// --------------------------------------------

// Exercise 15: Private Methods with Symbols
// Create a class with "private" methods using symbols

// Your code here:
// const _validate = Symbol('validate');
// const _process = Symbol('process');
//
// class DataProcessor {
//     ...
// }

/*
Solution:
const _validate = Symbol('validate');
const _process = Symbol('process');
const _data = Symbol('data');

class DataProcessor {
    constructor(data) {
        this[_data] = data;
    }
    
    [_validate](item) {
        return item !== null && item !== undefined;
    }
    
    [_process](item) {
        return String(item).toUpperCase();
    }
    
    run() {
        return this[_data]
            .filter(item => this[_validate](item))
            .map(item => this[_process](item));
    }
}

const processor = new DataProcessor(['hello', null, 'world', undefined, 'test']);
console.log(processor.run());  // ['HELLO', 'WORLD', 'TEST']

// "Private" methods are hidden
console.log(Object.keys(processor));  // []
*/
Exercises - JavaScript Tutorial | DeepML