5.6 גנריקס פתרון
פתרון - גנריקס¶
פתרון תרגיל 1¶
א. last:
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
console.log(last([1, 2, 3])); // 3
console.log(last(["a", "b"])); // "b"
console.log(last([])); // undefined
ב. reverse:
function reverse<T>(arr: T[]): T[] {
return [...arr].reverse();
}
console.log(reverse([1, 2, 3])); // [3, 2, 1]
let original = [1, 2, 3];
reverse(original);
console.log(original); // [1, 2, 3] - unchanged
ג. unique:
function unique<T>(arr: T[]): T[] {
return [...new Set(arr)];
}
console.log(unique([1, 2, 2, 3, 3, 3])); // [1, 2, 3]
console.log(unique(["a", "b", "a"])); // ["a", "b"]
ד. zip:
function zip<A, B>(arr1: A[], arr2: B[]): [A, B][] {
let length = Math.min(arr1.length, arr2.length);
let result: [A, B][] = [];
for (let i = 0; i < length; i++) {
result.push([arr1[i], arr2[i]]);
}
return result;
}
console.log(zip([1, 2, 3], ["a", "b", "c"])); // [[1, "a"], [2, "b"], [3, "c"]]
console.log(zip([1, 2], ["a", "b", "c"])); // [[1, "a"], [2, "b"]]
פתרון תרגיל 2¶
א. KeyValuePair:
interface KeyValuePair<K, V> {
key: K;
value: V;
}
let pair1: KeyValuePair<string, number> = { key: "age", value: 30 };
let pair2: KeyValuePair<number, string> = { key: 1, value: "hello" };
ב. Repository:
interface 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;
}
ג. InMemoryRepository:
class InMemoryRepository<T extends { id: number }> implements Repository<T> {
private items: T[] = [];
private nextId: number = 1;
getAll(): T[] {
return [...this.items];
}
getById(id: number): T | undefined {
return this.items.find(item => item.id === id);
}
create(item: T): T {
let newItem = { ...item, id: this.nextId++ };
this.items.push(newItem);
return newItem;
}
update(id: number, updates: Partial<T>): T | undefined {
let index = this.items.findIndex(item => item.id === id);
if (index === -1) return undefined;
this.items[index] = { ...this.items[index], ...updates };
return this.items[index];
}
delete(id: number): boolean {
let index = this.items.findIndex(item => item.id === id);
if (index === -1) return false;
this.items.splice(index, 1);
return true;
}
}
// usage
interface User {
id: number;
name: string;
email: string;
}
let userRepo = new InMemoryRepository<User>();
let alice = userRepo.create({ id: 0, name: "Alice", email: "alice@example.com" });
console.log(alice); // { id: 1, name: "Alice", email: "alice@example.com" }
let bob = userRepo.create({ id: 0, name: "Bob", email: "bob@example.com" });
console.log(userRepo.getAll()); // [alice, bob]
userRepo.update(1, { email: "new@example.com" });
console.log(userRepo.getById(1)); // { id: 1, name: "Alice", email: "new@example.com" }
פתרון תרגיל 3¶
א. merge:
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
let result = merge({ name: "Alice" }, { age: 30 });
console.log(result); // { name: "Alice", age: 30 }
ב. longest:
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
console.log(longest("hello", "hi")); // "hello"
console.log(longest([1, 2, 3], [4, 5])); // [1, 2, 3]
ג. groupBy:
function groupBy<T>(items: T[], key: keyof T): Record<string, T[]> {
let result: Record<string, T[]> = {};
for (let item of items) {
let groupKey = String(item[key]);
if (!result[groupKey]) {
result[groupKey] = [];
}
result[groupKey].push(item);
}
return result;
}
let users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Charlie", role: "admin" }
];
let grouped = groupBy(users, "role");
console.log(grouped);
// { admin: [{ name: "Alice", role: "admin" }, { name: "Charlie", role: "admin" }],
// user: [{ name: "Bob", role: "user" }] }
פתרון תרגיל 4¶
א. pluck:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
let users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 }
];
let names = pluck(users, "name"); // string[] -> ["Alice", "Bob"]
let ages = pluck(users, "age"); // number[] -> [30, 25]
ב. setProperty:
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): T {
return { ...obj, [key]: value };
}
let user = { name: "Alice", age: 30 };
let updated = setProperty(user, "age", 31); // ok
// setProperty(user, "age", "31"); // ERROR: string not assignable to number
ג. Optional:
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface User {
id: number;
name: string;
email: string;
bio: string;
}
type CreateUserInput = Optional<User, "id" | "bio">;
let input: CreateUserInput = {
name: "Alice",
email: "alice@example.com"
// id and bio are optional
};
הסבר: Omit<T, K> מסיר את השדות K מ-T. Partial<Pick<T, K>> לוקח רק את השדות K ומפך אותם לאופציונליים. ה-intersection (&) משלב את שניהם.
פתרון תרגיל 5¶
א. PaginatedResult:
interface PaginatedResult<T = unknown> {
items: T[];
total: number;
page: number;
pageSize: number;
hasNextPage: boolean;
}
ב. paginate:
function paginate<T>(items: T[], page: number, pageSize: number): PaginatedResult<T> {
let start = (page - 1) * pageSize;
let end = start + pageSize;
let paginatedItems = items.slice(start, end);
return {
items: paginatedItems,
total: items.length,
page,
pageSize,
hasNextPage: end < items.length
};
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let page1 = paginate(numbers, 1, 3);
// { items: [1, 2, 3], total: 10, page: 1, pageSize: 3, hasNextPage: true }
let page4 = paginate(numbers, 4, 3);
// { items: [10], total: 10, page: 4, pageSize: 3, hasNextPage: false }
ג. mapPaginated:
function mapPaginated<T, U>(
result: PaginatedResult<T>,
fn: (item: T) => U
): PaginatedResult<U> {
return {
...result,
items: result.items.map(fn)
};
}
let stringPage = mapPaginated(page1, (n) => `Item #${n}`);
// { items: ["Item #1", "Item #2", "Item #3"], total: 10, ... }
פתרון תרגיל 6¶
class BoundedStack<T> {
private items: T[] = [];
private maxSize: number;
constructor(maxSize: number) {
this.maxSize = maxSize;
}
push(item: T): boolean {
if (this.isFull()) {
return false;
}
this.items.push(item);
return true;
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
if (this.isEmpty()) return undefined;
return this.items[this.items.length - 1];
}
isFull(): boolean {
return this.items.length >= this.maxSize;
}
isEmpty(): boolean {
return this.items.length === 0;
}
get size(): number {
return this.items.length;
}
toArray(): T[] {
return [...this.items];
}
}
// usage with numbers
let numStack = new BoundedStack<number>(3);
console.log(numStack.push(1)); // true
console.log(numStack.push(2)); // true
console.log(numStack.push(3)); // true
console.log(numStack.push(4)); // false - full!
console.log(numStack.isFull()); // true
console.log(numStack.peek()); // 3
console.log(numStack.pop()); // 3
console.log(numStack.size); // 2
// usage with strings
let strStack = new BoundedStack<string>(2);
strStack.push("hello");
strStack.push("world");
console.log(strStack.toArray()); // ["hello", "world"]
פתרון תרגיל 7¶
א. AppEvents:
interface AppEvents {
userLogin: { userId: string; timestamp: number };
userLogout: { userId: string };
pageView: { path: string; referrer?: string };
error: { message: string; stack?: string };
}
ב. EventBus:
class EventBus<T extends Record<string, unknown>> {
private handlers: Map<keyof T, ((data: any) => void)[]> = new Map();
on<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
let existing = this.handlers.get(event) ?? [];
existing.push(handler as (data: any) => void);
this.handlers.set(event, existing);
}
off<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
let existing = this.handlers.get(event);
if (!existing) return;
let index = existing.indexOf(handler as (data: any) => void);
if (index !== -1) {
existing.splice(index, 1);
}
}
emit<K extends keyof T>(event: K, data: T[K]): void {
let existing = this.handlers.get(event);
if (!existing) return;
for (let handler of existing) {
handler(data);
}
}
once<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
let wrapper = (data: T[K]) => {
handler(data);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
ג. דוגמת שימוש:
let bus = new EventBus<AppEvents>();
// register handlers
bus.on("userLogin", (data) => {
// TS knows: data is { userId: string; timestamp: number }
console.log(`User ${data.userId} logged in at ${data.timestamp}`);
});
bus.on("error", (data) => {
// TS knows: data is { message: string; stack?: string }
console.log(`Error: ${data.message}`);
});
bus.once("pageView", (data) => {
console.log(`First page view: ${data.path}`);
});
// emit events - type safe!
bus.emit("userLogin", { userId: "123", timestamp: Date.now() }); // ok
bus.emit("pageView", { path: "/home" }); // ok
// these would cause compile errors:
// bus.emit("userLogin", { userId: 123 }); // ERROR: number not assignable to string
// bus.emit("unknownEvent", {}); // ERROR: not a key of AppEvents
// bus.emit("error", { msg: "oops" }); // ERROR: 'msg' doesn't exist, 'message' required