לדלג לתוכן

2.7 אובייקטים פתרון

פתרון - אובייקטים


תרגיל 1 - כרטיס אישי

const person = {
  name: "Alice",
  age: 25,
  city: "Tel Aviv",
  hobbies: ["reading", "coding", "hiking"],
  address: {
    street: "Herzl 10",
    city: "Tel Aviv",
    zip: "6120001"
  }
};

// 1. print name and city from address
console.log(person.name);          // "Alice"
console.log(person.address.city);  // "Tel Aviv"

// 2. add email
person.email = "alice@example.com";

// 3. add a hobby
person.hobbies.push("swimming");

// 4. change zip code
person.address.zip = "6130002";

// 5. delete age
delete person.age;

// 6. print all keys
console.log(Object.keys(person));
// ["name", "city", "hobbies", "address", "email"]

// 7. print all values
console.log(Object.values(person));

// 8. print each key-value pair
Object.entries(person).forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});

תרגיל 2 - מתודות של אובייקט

const bankAccount = {
  owner: "Alice",
  balance: 0,
  history: [],

  deposit(amount) {
    this.balance += amount;
    this.history.push({ type: "deposit", amount, date: new Date().toISOString() });
    console.log(`Deposited ${amount}. New balance: ${this.balance}`);
  },

  withdraw(amount) {
    if (amount > this.balance) {
      console.log(`Withdrawal of ${amount} failed - insufficient funds. Balance: ${this.balance}`);
      return false;
    }
    this.balance -= amount;
    this.history.push({ type: "withdrawal", amount, date: new Date().toISOString() });
    console.log(`Withdrew ${amount}. New balance: ${this.balance}`);
    return true;
  },

  getBalance() {
    return this.balance;
  },

  getHistory() {
    return this.history;
  },

  printStatement() {
    console.log(`\n=== Account Statement for ${this.owner} ===`);
    this.history.forEach((entry) => {
      const sign = entry.type === "deposit" ? "+" : "-";
      console.log(`${entry.type}: ${sign}${entry.amount}`);
    });
    console.log(`Current balance: ${this.balance}`);
    console.log("================================\n");
  }
};

// test
bankAccount.deposit(1000);   // Deposited 1000. New balance: 1000
bankAccount.deposit(500);    // Deposited 500. New balance: 1500
bankAccount.withdraw(200);   // Withdrew 200. New balance: 1300
bankAccount.withdraw(2000);  // Withdrawal of 2000 failed - insufficient funds
bankAccount.printStatement();
// === Account Statement for Alice ===
// deposit: +1000
// deposit: +500
// withdrawal: -200
// Current balance: 1300
// ================================

תרגיל 3 - מיזוג ועדכון

const defaultSettings = {
  theme: "light",
  language: "en",
  fontSize: 14,
  notifications: true,
  autoSave: true
};

const userSettings = {
  theme: "dark",
  language: "he",
  fontSize: 16
};

// 1. merge with spread (user overrides defaults)
const mergedSettings = { ...defaultSettings, ...userSettings };
console.log(mergedSettings);
// { theme: "dark", language: "he", fontSize: 16, notifications: true, autoSave: true }

// 2. create a copy and change theme
const copy = { ...mergedSettings };
copy.theme = "blue";
console.log(mergedSettings.theme); // "dark" - not affected
console.log(copy.theme);           // "blue"

// 3. add nested sidebar property
mergedSettings.sidebar = { width: 250, visible: true };
console.log(mergedSettings.sidebar); // { width: 250, visible: true }

// 4. freeze defaults
Object.freeze(defaultSettings);
defaultSettings.theme = "rainbow"; // does nothing
console.log(defaultSettings.theme); // "light" - unchanged

תרגיל 4 - ספר טלפונים

function addContact(phoneBook, name, phone) {
  phoneBook[name] = phone;
  console.log(`Added: ${name} -> ${phone}`);
}

function removeContact(phoneBook, name) {
  if (name in phoneBook) {
    delete phoneBook[name];
    console.log(`Removed: ${name}`);
  } else {
    console.log(`Contact "${name}" not found`);
  }
}

function findContact(phoneBook, name) {
  if (name in phoneBook) {
    return phoneBook[name];
  }
  return `Contact "${name}" not found`;
}

function updateContact(phoneBook, name, newPhone) {
  if (name in phoneBook) {
    phoneBook[name] = newPhone;
    console.log(`Updated: ${name} -> ${newPhone}`);
  } else {
    console.log(`Contact "${name}" not found`);
  }
}

function listContacts(phoneBook) {
  const sorted = Object.entries(phoneBook).sort((a, b) =>
    a[0].localeCompare(b[0])
  );
  console.log("=== Phone Book ===");
  sorted.forEach(([name, phone]) => {
    console.log(`${name}: ${phone}`);
  });
}

function searchContacts(phoneBook, query) {
  const lowerQuery = query.toLowerCase();
  return Object.entries(phoneBook)
    .filter(([name]) => name.toLowerCase().includes(lowerQuery))
    .map(([name, phone]) => ({ name, phone }));
}

// test
const phoneBook = {};
addContact(phoneBook, "Alice", "050-1234567");
addContact(phoneBook, "Bob", "052-9876543");
addContact(phoneBook, "Alice Smith", "054-1112222");
addContact(phoneBook, "Charlie", "053-5555555");

console.log(findContact(phoneBook, "Bob")); // "052-9876543"

listContacts(phoneBook);
// === Phone Book ===
// Alice: 050-1234567
// Alice Smith: 054-1112222
// Bob: 052-9876543
// Charlie: 053-5555555

console.log(searchContacts(phoneBook, "alice"));
// [{ name: "Alice", phone: "050-1234567" }, { name: "Alice Smith", phone: "054-1112222" }]

updateContact(phoneBook, "Bob", "052-0000000");
removeContact(phoneBook, "Charlie");
listContacts(phoneBook);

תרגיל 5 - השוואת אובייקטים

function deepEqual(obj1, obj2) {
  // handle exact same reference or both primitive and equal
  if (obj1 === obj2) return true;

  // handle null/undefined
  if (obj1 === null || obj2 === null) return false;
  if (obj1 === undefined || obj2 === undefined) return false;

  // handle different types
  if (typeof obj1 !== typeof obj2) return false;

  // handle non-object types (primitives already compared above)
  if (typeof obj1 !== "object") return false;

  // handle arrays
  if (Array.isArray(obj1) !== Array.isArray(obj2)) return false;

  if (Array.isArray(obj1)) {
    if (obj1.length !== obj2.length) return false;
    return obj1.every((item, index) => deepEqual(item, obj2[index]));
  }

  // handle objects
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);

  if (keys1.length !== keys2.length) return false;

  return keys1.every((key) => deepEqual(obj1[key], obj2[key]));
}

// tests
console.log(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 }));           // true
console.log(deepEqual({ a: 1, b: 2 }, { a: 1, b: 3 }));           // false
console.log(deepEqual({ a: { b: 1 } }, { a: { b: 1 } }));         // true
console.log(deepEqual({ a: [1, 2, 3] }, { a: [1, 2, 3] }));       // true
console.log(deepEqual({ a: [1, 2, 3] }, { a: [1, 2, 4] }));       // false
console.log(deepEqual(null, null));                                 // true
console.log(deepEqual({ a: 1 }, { a: 1, b: 2 }));                 // false

תרגיל 6 - Optional Chaining

const apiResponse = {
  status: 200,
  data: {
    user: {
      id: 1,
      name: "Alice",
      profile: {
        avatar: "alice.jpg",
        bio: "Developer"
      },
      posts: [
        { id: 101, title: "Hello World", comments: [{ text: "Nice!" }] },
        { id: 102, title: "JavaScript Tips", comments: [] }
      ]
    }
  }
};

// 1. user name
console.log(apiResponse.data?.user?.name);
// "Alice"

// 2. user avatar
console.log(apiResponse.data?.user?.profile?.avatar);
// "alice.jpg"

// 3. first comment of first post
console.log(apiResponse.data?.user?.posts?.[0]?.comments?.[0]?.text);
// "Nice!"

// 4. title of third post (doesn't exist)
console.log(apiResponse.data?.user?.posts?.[2]?.title);
// undefined

// 5. user phone (doesn't exist)
console.log(apiResponse.data?.user?.phone);
// undefined

// 6. user city (doesn't exist)
console.log(apiResponse.data?.user?.profile?.address?.city);
// undefined

תרגיל 7 - מערכת ניהול מלאי

const inventory = {};

function addProduct(inventory, name, price, quantity) {
  inventory[name] = { price, quantity };
  console.log(`Added: ${name} (price: ${price}, qty: ${quantity})`);
}

function sellProduct(inventory, name, quantity) {
  if (!(name in inventory)) {
    return `Product "${name}" not found`;
  }
  if (inventory[name].quantity < quantity) {
    return `Not enough stock for "${name}". Available: ${inventory[name].quantity}`;
  }
  inventory[name].quantity -= quantity;
  const total = inventory[name].price * quantity;
  console.log(`Sold ${quantity}x ${name} for ${total}`);
  return total;
}

function restock(inventory, name, quantity) {
  if (!(name in inventory)) {
    console.log(`Product "${name}" not found`);
    return;
  }
  inventory[name].quantity += quantity;
  console.log(`Restocked ${name}: +${quantity} (now: ${inventory[name].quantity})`);
}

function getReport(inventory) {
  const entries = Object.entries(inventory);

  return {
    totalProducts: entries.length,
    totalItems: entries.reduce((sum, [, p]) => sum + p.quantity, 0),
    totalValue: entries.reduce((sum, [, p]) => sum + p.price * p.quantity, 0),
    outOfStock: entries
      .filter(([, p]) => p.quantity === 0)
      .map(([name]) => name),
    lowStock: entries
      .filter(([, p]) => p.quantity > 0 && p.quantity < 5)
      .map(([name]) => name)
  };
}

function getMostExpensive(inventory) {
  const entries = Object.entries(inventory);
  if (entries.length === 0) return null;

  const [name, product] = entries.reduce((max, current) =>
    current[1].price > max[1].price ? current : max
  );
  return { name, ...product };
}

function search(inventory, query) {
  const lowerQuery = query.toLowerCase();
  return Object.entries(inventory)
    .filter(([name]) => name.toLowerCase().includes(lowerQuery))
    .map(([name, product]) => ({ name, ...product }));
}

// test
addProduct(inventory, "Laptop", 5000, 10);
addProduct(inventory, "Mouse", 100, 50);
addProduct(inventory, "Keyboard", 250, 3);
addProduct(inventory, "Monitor", 1500, 8);
addProduct(inventory, "Headphones", 350, 0);

sellProduct(inventory, "Mouse", 50); // sells all mice
sellProduct(inventory, "Laptop", 2);

console.log(getReport(inventory));
// {
//   totalProducts: 5,
//   totalItems: 69,
//   totalValue: 43750,
//   outOfStock: ["Mouse", "Headphones"],
//   lowStock: ["Keyboard"]
// }

console.log(getMostExpensive(inventory));
// { name: "Laptop", price: 5000, quantity: 8 }

console.log(search(inventory, "key"));
// [{ name: "Keyboard", price: 250, quantity: 3 }]

תרגיל 8 - טרנספורמציות אובייקטים

// 1. invertObject
function invertObject(obj) {
  return Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [value, key])
  );
}
console.log(invertObject({ a: 1, b: 2, c: 3 }));
// { 1: "a", 2: "b", 3: "c" }

// 2. pick
function pick(obj, keys) {
  return Object.fromEntries(
    Object.entries(obj).filter(([key]) => keys.includes(key))
  );
}
console.log(pick({ a: 1, b: 2, c: 3, d: 4 }, ["a", "c"]));
// { a: 1, c: 3 }

// 3. omit
function omit(obj, keys) {
  return Object.fromEntries(
    Object.entries(obj).filter(([key]) => !keys.includes(key))
  );
}
console.log(omit({ a: 1, b: 2, c: 3, d: 4 }, ["b", "d"]));
// { a: 1, c: 3 }

// 4. mapValues
function mapValues(obj, fn) {
  return Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [key, fn(value)])
  );
}
console.log(mapValues({ a: 1, b: 2, c: 3 }, (val) => val * 2));
// { a: 2, b: 4, c: 6 }

// 5. mapKeys
function mapKeys(obj, fn) {
  return Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [fn(key), value])
  );
}
console.log(mapKeys({ name: "Alice", age: 25 }, (key) => key.toUpperCase()));
// { NAME: "Alice", AGE: 25 }