5.5 טיפוסי איחוד וחיתוך פתרון
פתרון - טיפוסי איחוד וחיתוך¶
פתרון תרגיל 1¶
א. describeInput:
type Input = string | number | boolean;
function describeInput(value: Input): string {
if (typeof value === "string") {
return `string: ${value}`;
}
if (typeof value === "number") {
return `number: ${value}`;
}
return `boolean: ${value}`;
}
ב. Nullish ו-unwrap:
type Nullish<T> = T | null | undefined;
function unwrap(value: Nullish<string>, defaultValue: string): string {
if (value === null || value === undefined) {
return defaultValue;
}
return value;
}
// or shorter:
function unwrap2(value: Nullish<string>, defaultValue: string): string {
return value ?? defaultValue;
}
ג. toArray:
type StringOrArray = string | string[];
function toArray(value: StringOrArray): string[] {
if (typeof value === "string") {
return [value];
}
return value;
}
// or shorter:
function toArray2(value: StringOrArray): string[] {
return Array.isArray(value) ? value : [value];
}
פתרון תרגיל 2¶
א-ב. Suit, Rank, Card:
type Suit = "hearts" | "diamonds" | "clubs" | "spades";
type Rank = 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | "J" | "Q" | "K" | "A";
interface Card {
suit: Suit;
rank: Rank;
}
ג. getCardValue:
function getCardValue(card: Card): number {
if (typeof card.rank === "number") {
return card.rank;
}
switch (card.rank) {
case "J":
case "Q":
case "K":
return 10;
case "A":
return 11;
}
}
ד. formatCard:
פתרון תרגיל 3¶
א. טיפוסי תשלום:
interface CreditCardPayment {
method: "creditCard";
cardNumber: string;
expiryDate: string;
cvv: string;
}
interface PayPalPayment {
method: "paypal";
email: string;
}
interface BankTransferPayment {
method: "bankTransfer";
bankName: string;
accountNumber: string;
routingNumber: string;
}
interface CryptoPayment {
method: "crypto";
walletAddress: string;
currency: "BTC" | "ETH" | "USDT";
}
type Payment = CreditCardPayment | PayPalPayment | BankTransferPayment | CryptoPayment;
ב. getPaymentSummary:
function getPaymentSummary(payment: Payment): string {
switch (payment.method) {
case "creditCard":
let last4 = payment.cardNumber.slice(-4);
return `Credit card ending in ${last4}`;
case "paypal":
return `PayPal: ${payment.email}`;
case "bankTransfer":
return `Bank transfer: ${payment.bankName}`;
case "crypto":
let shortAddress = payment.walletAddress.slice(0, 8) + "...";
return `${payment.currency}: ${shortAddress}`;
default:
const exhaustiveCheck: never = payment;
return exhaustiveCheck;
}
}
ג. getProcessingFee:
function getProcessingFee(payment: Payment, amount: number): number {
switch (payment.method) {
case "creditCard":
return amount * 0.029;
case "paypal":
return amount * 0.035;
case "bankTransfer":
return amount * 0.01;
case "crypto":
return amount * 0.005;
}
}
פתרון תרגיל 4¶
א. Result:
ב. parseJSON:
function parseJSON(input: string): Result<unknown> {
try {
let data = JSON.parse(input);
return { success: true, value: data };
} catch (e) {
let message = e instanceof Error ? e.message : "Unknown error";
return { success: false, error: message };
}
}
let good = parseJSON('{"name": "Alice"}');
if (good.success) {
console.log(good.value); // { name: "Alice" }
}
let bad = parseJSON("not json");
if (!bad.success) {
console.log(bad.error); // error message
}
ג. divide:
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return { success: false, error: "Division by zero" };
}
return { success: true, value: a / b };
}
ד. chain:
function chain<T, U>(result: Result<T>, fn: (value: T) => Result<U>): Result<U> {
if (!result.success) {
return result; // pass through the failure
}
return fn(result.value);
}
// usage: parse JSON, then divide a value
let result = chain(
parseJSON('{"value": 10}'),
(data) => {
let obj = data as { value: number };
return divide(obj.value, 3);
}
);
פתרון תרגיל 5¶
א. טיפוסי traits:
type Identifiable = {
id: string;
};
type Timestamped = {
createdAt: Date;
updatedAt: Date;
};
type Ownable = {
ownerId: string;
ownerName: string;
};
ב. טיפוסים מורכבים:
type Document = {
title: string;
content: string;
} & Identifiable & Timestamped & Ownable;
type Comment = {
text: string;
parentId: string;
} & Identifiable & Timestamped;
type Tag = {
name: string;
color: string;
} & Identifiable;
ג. getAge:
function getAge(item: Timestamped): number {
let now = new Date();
let diff = now.getTime() - item.createdAt.getTime();
return Math.floor(diff / (1000 * 60 * 60 * 24));
}
// works with Document, Comment, or anything with Timestamped
let doc: Document = {
id: "1",
title: "Hello",
content: "World",
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-06-01"),
ownerId: "u1",
ownerName: "Alice"
};
console.log(getAge(doc)); // number of days since 2025-01-01
פתרון תרגיל 6¶
א. PlayerState:
interface Stopped {
state: "stopped";
}
interface Playing {
state: "playing";
track: string;
position: number;
volume: number;
}
interface Paused {
state: "paused";
track: string;
position: number;
volume: number;
}
interface Buffering {
state: "buffering";
track: string;
progress: number;
}
type PlayerState = Stopped | Playing | Paused | Buffering;
ב. getPlayerDisplay:
function getPlayerDisplay(state: PlayerState): string {
switch (state.state) {
case "stopped":
return "Player stopped";
case "playing":
return `Playing: ${state.track} (${state.position}s) - Volume: ${state.volume}%`;
case "paused":
return `Paused: ${state.track} at ${state.position}s`;
case "buffering":
return `Buffering: ${state.track} (${state.progress}%)`;
}
}
ג. handlePlayerAction:
type PlayerAction =
| { type: "play"; track: string }
| { type: "pause" }
| { type: "stop" }
| { type: "seek"; position: number };
function handlePlayerAction(state: PlayerState, action: PlayerAction): PlayerState {
switch (action.type) {
case "play":
return {
state: "buffering",
track: action.track,
progress: 0
};
case "pause":
if (state.state === "playing") {
return {
state: "paused",
track: state.track,
position: state.position,
volume: state.volume
};
}
if (state.state === "paused") {
return {
state: "playing",
track: state.track,
position: state.position,
volume: state.volume
};
}
return state;
case "stop":
return { state: "stopped" };
case "seek":
if (state.state === "playing") {
return { ...state, position: action.position };
}
if (state.state === "paused") {
return { ...state, position: action.position };
}
return state; // can't seek when stopped or buffering
}
}
פתרון תרגיל 7¶
א. טיפוסים:
type BaseMessage = {
id: string;
timestamp: number;
senderId: string;
};
type TextMessage = BaseMessage & {
type: "text";
content: string;
};
type ImageMessage = BaseMessage & {
type: "image";
url: string;
width: number;
height: number;
caption?: string;
};
type FileMessage = BaseMessage & {
type: "file";
fileName: string;
fileSize: number;
mimeType: string;
};
type SystemMessage = BaseMessage & {
type: "system";
action: "join" | "leave" | "rename";
details: string;
};
type ChatMessage = TextMessage | ImageMessage | FileMessage | SystemMessage;
ב. renderMessage:
function renderMessage(message: ChatMessage): string {
switch (message.type) {
case "text":
return `<div class="message text"><p>${message.content}</p></div>`;
case "image":
let caption = message.caption ? `<p class="caption">${message.caption}</p>` : "";
return `<div class="message image"><img src="${message.url}" width="${message.width}" height="${message.height}" />${caption}</div>`;
case "file":
let sizeMB = (message.fileSize / (1024 * 1024)).toFixed(2);
return `<div class="message file"><a href="#">${message.fileName}</a> (${sizeMB} MB)</div>`;
case "system":
return `<div class="message system"><em>${message.details}</em></div>`;
}
}
ג. searchMessages:
function searchMessages(messages: ChatMessage[], query: string): ChatMessage[] {
let lowerQuery = query.toLowerCase();
return messages.filter(message => {
switch (message.type) {
case "text":
return message.content.toLowerCase().includes(lowerQuery);
case "image":
return message.caption?.toLowerCase().includes(lowerQuery) ?? false;
case "file":
return message.fileName.toLowerCase().includes(lowerQuery);
case "system":
return message.details.toLowerCase().includes(lowerQuery);
}
});
}
ד. getMessageStats:
interface MessageStats {
textCount: number;
imageCount: number;
fileCount: number;
systemCount: number;
totalFileSize: number;
}
function getMessageStats(messages: ChatMessage[]): MessageStats {
let stats: MessageStats = {
textCount: 0,
imageCount: 0,
fileCount: 0,
systemCount: 0,
totalFileSize: 0
};
for (let message of messages) {
switch (message.type) {
case "text":
stats.textCount++;
break;
case "image":
stats.imageCount++;
break;
case "file":
stats.fileCount++;
stats.totalFileSize += message.fileSize;
break;
case "system":
stats.systemCount++;
break;
}
}
return stats;
}