לדלג לתוכן

5.6 גנריקס תרגול

תרגול - גנריקס

תרגיל 1 - פונקציות גנריות בסיסיות

א. כתבו פונקציה גנרית last שמקבלת מערך ומחזירה את האיבר האחרון (או undefined אם המערך ריק).

ב. כתבו פונקציה גנרית reverse שמקבלת מערך ומחזירה מערך חדש בסדר הפוך (בלי לשנות את המקורי).

ג. כתבו פונקציה גנרית unique שמקבלת מערך ומחזירה מערך חדש ללא כפילויות.

ד. כתבו פונקציה גנרית zip שמקבלת שני מערכים (יכולים להיות מטיפוסים שונים) ומחזירה מערך של טאפלים:

zip([1, 2, 3], ["a", "b", "c"]); // [[1, "a"], [2, "b"], [3, "c"]]

אם המערכים באורכים שונים, המערך המוחזר יהיה באורך הקצר מביניהם.

תרגיל 2 - אינטרפייסים גנריים

א. הגדירו אינטרפייס גנרי KeyValuePair<K, V> עם שדות key: K ו-value: V.

ב. הגדירו אינטרפייס גנרי Repository<T> עם המתודות:
- getAll(): T[]
- getById(id: number): T | undefined
- create(item: T): T
- update(id: number, item: Partial<T>): T | undefined
- delete(id: number): boolean

ג. ממשו class InMemoryRepository<T extends { id: number }> שמממש את Repository<T>. השתמשו במערך פנימי לאחסון. הפונקציה create צריכה להוסיף id אוטומטית.

interface User { id: number; name: string; email: string; }

let userRepo = new InMemoryRepository<User>();
userRepo.create({ id: 0, name: "Alice", email: "alice@example.com" });
let users = userRepo.getAll(); // User[]

תרגיל 3 - אילוצים

א. כתבו פונקציה גנרית merge שמקבלת שני אובייקטים ומחזירה אובייקט חדש שמכיל את כל השדות משניהם. אילוצים: שני הפרמטרים חייבים להיות אובייקטים (extends object).

let result = merge({ name: "Alice" }, { age: 30 });
// result: { name: string } & { age: number }

ב. כתבו פונקציה גנרית longest שמקבלת שני ערכים מאותו טיפוס (שחייב להיות לו length: number) ומחזירה את הארוך מביניהם:

longest("hello", "hi");       // "hello"
longest([1, 2, 3], [4, 5]);   // [1, 2, 3]

ג. כתבו פונקציה גנרית groupBy שמקבלת מערך של אובייקטים ושם שדה (מוגבל ל-keyof של הטיפוס), ומחזירה אובייקט שהמפתחות שלו הם ערכי השדה:

let users = [
    { name: "Alice", role: "admin" },
    { name: "Bob", role: "user" },
    { name: "Charlie", role: "admin" }
];

groupBy(users, "role");
// { admin: [{...}, {...}], user: [{...}] }

תרגיל 4 - keyof ו-lookup types

א. כתבו פונקציה גנרית pluck שמקבלת מערך של אובייקטים ושם שדה, ומחזירה מערך של ערכי השדה:

let users = [
    { name: "Alice", age: 30 },
    { name: "Bob", age: 25 }
];

pluck(users, "name"); // ["Alice", "Bob"] - type: string[]
pluck(users, "age");  // [30, 25] - type: number[]

ב. כתבו פונקציה גנרית setProperty שמקבלת אובייקט, מפתח, וערך (מהטיפוס המתאים למפתח), ומחזירה אובייקט חדש עם הערך המעודכן:

let user = { name: "Alice", age: 30 };
let updated = setProperty(user, "age", 31);    // ok
let updated2 = setProperty(user, "age", "31"); // ERROR: string not assignable to number

ג. כתבו טיפוס גנרי Optional<T, K> שהופך רק את השדות שב-K לאופציונליים:

interface User {
    id: number;
    name: string;
    email: string;
    bio: string;
}

type CreateUserInput = Optional<User, "id" | "bio">;
// { id?: number; name: string; email: string; bio?: string }

תרגיל 5 - ברירת מחדל לטיפוסים

א. הגדירו אינטרפייס גנרי PaginatedResult<T = unknown> עם:
- items: T[]
- total: number
- page: number
- pageSize: number
- hasNextPage: boolean

ב. כתבו פונקציה paginate שמקבלת מערך של T, מספר עמוד, וגודל עמוד, ומחזירה PaginatedResult<T>.

ג. כתבו פונקציה mapPaginated שמקבלת PaginatedResult<T> ופונקציה (item: T) => U, ומחזירה PaginatedResult<U> (ממפה את ה-items).

תרגיל 6 - class גנרי - מחסנית מורחבת

בנו class גנרי BoundedStack<T> - מחסנית עם גודל מקסימלי:

  • constructor מקבל maxSize: number
  • push(item: T): boolean - מוסיף ומחזיר true, או מחזיר false אם המחסנית מלאה
  • pop(): T | undefined - מוציא ומחזיר את האיבר העליון
  • peek(): T | undefined - מחזיר את האיבר העליון בלי להוציא
  • isFull(): boolean
  • isEmpty(): boolean
  • size: number (getter)
  • toArray(): T[] - מחזיר עותק של כל האיברים

כתבו דוגמאות שימוש עם BoundedStack<number> ו-BoundedStack<string>.

תרגיל 7 - שילוב גנריקס - מערכת אירועים מוטייפת

בנו מערכת אירועים גנרית:

א. הגדירו אינטרפייס EventDefinitions שמתאר את מפת האירועים של האפליקציה:

interface AppEvents {
    userLogin: { userId: string; timestamp: number };
    userLogout: { userId: string };
    pageView: { path: string; referrer?: string };
    error: { message: string; stack?: string };
}

ב. בנו class גנרי EventBus<T> עם המתודות:
- on<K extends keyof T>(event: K, handler: (data: T[K]) => void): void - רישום handler
- off<K extends keyof T>(event: K, handler: (data: T[K]) => void): void - הסרת handler
- emit<K extends keyof T>(event: K, data: T[K]): void - שליחת אירוע
- once<K extends keyof T>(event: K, handler: (data: T[K]) => void): void - handler שרץ פעם אחת

ג. כתבו דוגמת שימוש שמדגימה שהטיפוסים נאכפים:
- emit("userLogin", { userId: "123", timestamp: Date.now() }) - תקין
- emit("userLogin", { userId: 123 }) - שגיאה (userId צריך להיות string)
- emit("unknownEvent", {}) - שגיאה (אירוע לא קיים)