Union & Intersection · CPE-211
งานกลุ่ม · CPE-211

โปรแกรมคำนวณ Union & Intersection

พร้อมวาด Venn Diagram ด้วย MATLAB

อณุภา โตมีบุญ2511310795
ปภาวิน เต้าทิพย์ภูมิรักษ์2511310605
กลุ่มที่ ___  ·  นำเสนอ พุธ 14 ตุลาคม 2569  ·  ห้อง D502  ·  ~14 นาที

โจทย์

การดำเนินการทางเซต (Set Operations) — กำหนดเซต A และ B

A = {101, 102, 103, 104, 105}

B = {103, 104, 105, 106, 107}

จงหา

  • Union  (A ∪ B) — สมาชิกทั้งหมดที่อยู่ใน A หรือ B
  • Intersection  (A ∩ B) — สมาชิกที่อยู่ทั้ง A และ B
  • ส่วนที่ต่างกัน  A − B  และ  B − A

วิธีคิดด้วยมือ

คิดมือก่อน แล้วค่อยให้โปรแกรมคำนวณให้อัตโนมัติ

วิธีคิดด้วยมือ

A ∪ B = 7 ตัว (101–107) · A ∩ B = 3 ตัว (103, 104, 105)

ภาพรวมการทำงาน (Flowchart)

จาก flowchart.png — 7 ขั้นตอนหลัก

flowchart

แผนผังการเรียกใช้ไฟล์

โปรแกรมแยกเป็นฟังก์ชันย่อย แต่ละไฟล์ทำงานเฉพาะอย่าง

main_script.m
 ├── read_set.m ──────► parse_set_input.m
 ├── calculate_set_ops.m
 └── plot_venn.m

run_tests.m
 ├── parse_set_input.m
 └── calculate_set_ops.m
  • main_script.m — ตัวควบคุมหลัก
  • read_set / parse_set_input — รับและตรวจสอบ input
  • calculate_set_ops — คำนวณ · plot_venn — วาดรูป · run_tests — ทดสอบ

main_script.m

ตัวควบคุมหลัก: รับค่า → ตัดค่าซ้ำ → คำนวณ → แสดงผล → วาดรูป (ครอบ try/catch)

clc; clear; close all;
setA = read_set('Set A');
setB = read_set('Set B');

% ตัดค่าซ้ำ (คงลำดับเดิม) + รายงานจำนวนที่ตัด
nRawA = numel(setA);
setA  = unique(setA, 'stable');
if nRawA > numel(setA)
    fprintf('หมายเหตุ: Set A มีค่าซ้ำ %d ตัว ระบบตัดออกให้แล้ว\n', nRawA - numel(setA));
end

try
    [set_U, set_I] = calculate_set_ops(setA, setB);
    only_A = setdiff(setA, setB);   % อยู่ใน A แต่ไม่อยู่ใน B
    only_B = setdiff(setB, setA);   % อยู่ใน B แต่ไม่อยู่ใน A
catch err
    fprintf(2, 'เกิดข้อผิดพลาดในการคำนวณ: %s\n', err.message);
    return;
end

res = struct('setA', setA, 'setB', setB, 'union', set_U, ...
             'intersect', set_I, 'onlyA', only_A, 'onlyB', only_B);
plot_venn(res, 'NameA', 'Set A', 'NameB', 'Set B');

read_set.m

ถามผู้ใช้กรอกเซต และวนถามซ้ำจนกว่าจะถูก — รับได้ทั้งตัวเลขและชื่อตัวแปรใน Workspace

while true
    raw = input(sprintf('กรุณากรอกสมาชิกของ %s ...: ', label), 's');
    v = local_try_variable(raw);
    if ~isempty(v)
        values = v; return;          % ผู้ใช้พิมพ์ชื่อตัวแปรใน Workspace
    end
    [v, ok, msg] = parse_set_input(raw);
    if ok
        values = v; return;          % แปลงเป็นตัวเลขสำเร็จ
    end
    fprintf(2, '  [ผิดพลาด] %s กรุณาลองใหม่\n', msg);   % วนถามใหม่
end

function v = local_try_variable(raw)
    if isempty(s) || ~isvarname(s), return; end
    if evalin('base', sprintf('exist(''%s'', ''var'')', s)) ~= 1, return; end
    val = evalin('base', s);
    if isnumeric(val) && isvector(val)
        v = val(:).';
    end
end

parse_set_input.m

แปลงข้อความ input หลากหลายรูปแบบเป็นเวกเตอร์ตัวเลข (ใช้ regexprep + str2double)

% ตัดวงเล็บ [] () {} แล้วเปลี่ยนตัวคั่น , ; ให้เป็นช่องว่าง
str = regexprep(str, '[\[\]\(\)\{\}]', ' ');
str = regexprep(str, '[;,]+', ' ');
tokens = regexp(str, '\S+', 'match');       % แยกเป็นคำๆ

% แปลงทีละคำ (รองรับ range ด้วย ':')
if ~isempty(strfind(t, ':'))
    parts = str2double(strsplit(t, ':'));
    if numel(parts) < 2 || numel(parts) > 3 || any(isnan(parts))
        ok = false; msg = sprintf('รูปแบบช่วง (range) ไม่ถูกต้อง: %s', t); return;
    end
    switch numel(parts)
        case 2, r = parts(1):parts(2);
        case 3, r = parts(1):parts(2):parts(3);
    end
else
    v = str2double(t);
    if isnan(v)
        ok = false; msg = sprintf('ค่าที่ไม่ใช่ตัวเลข: %s', t); return;
    end
end

calculate_set_ops.m

หัวใจการคำนวณ Union/Intersection — มีตัวเลือก Stable และ Tolerance

function [set_union, set_intersect] = calculate_set_ops(A, B, varargin)
    [A, B, stable, tol] = local_parseOptions(A, B, varargin{:});

    if tol > 0                      % ปัดลงบนกริดของ tolerance ก่อนเทียบ
        A = round(A ./ tol) .* tol;
        B = round(B ./ tol) .* tol;
    end

    if stable                      % 'stable' = คงลำดับการป้อนเดิม
        set_union     = union(A, B, 'stable');
        set_intersect = intersect(A, B, 'stable');
    else                           % ค่าเริ่มต้น = เหมือน MATLAB มาตรฐานเป๊ะ
        set_union     = union(A, B);
        set_intersect = intersect(A, B);
    end
end

plot_venn.m

วาด Venn Diagram 2 วง ปรับขนาดฟอนต์ และเลือกฟอนต์ไทยอัตโนมัติ

theta = linspace(0, 2*pi, 300);
x_A = r * cos(theta) - 0.75;   y_A = r * sin(theta);
x_B = r * cos(theta) + 0.75;   y_B = r * sin(theta);

fill(x_A, y_A, [0.2 0.6 0.8], 'FaceAlpha', 0.4, 'EdgeColor', [0.1 0.4 0.7]);
fill(x_B, y_B, [0.9 0.5 0.2], 'FaceAlpha', 0.4, 'EdgeColor', [0.8 0.3 0.1]);
axis equal; axis off;

% ปรับขนาดฟอนต์ตามจำนวนสมาชิก
nMax = max([numel(onlyA), numel(onlyB), numel(set_I)]);
if nMax <= 8
    titleFs = 14; labelFs = 11; valueFs = 10;
elseif nMax <= 20
    titleFs = 13; labelFs = 10; valueFs = 9;
end

run_tests.m

ชุดทดสอบ 10 เคส (parse, range, stable, tolerance, เซตว่าง ฯลฯ)

tests = { @test_parse_formats; @test_parse_ranges; @test_parse_invalid; ...
          @test_default_matches_builtin; @test_stable_order; @test_tolerance_floats; ... };

nPass = 0; nFail = 0;
for k = 1:numel(tests)
    try
        tests{k}();
        fprintf('  PASS  %s\n', func2str(tests{k}));
        nPass = nPass + 1;
    catch err
        fprintf('  FAIL  %s -> %s\n', func2str(tests{k}), err.message);
        nFail = nFail + 1;
    end
end

ผลลัพธ์ใน Command Window

รัน main_script แล้วป้อน Set A / Set B

console output

Union = 7 ตัว · Intersection = 3 ตัว · A−B = {101,102} · B−A = {106,107}

Venn Diagram

ผลลัพธ์จากฟังก์ชัน plot_venn

venn diagram

เดโมสด

ขั้นตอนสาธิต — และมีเครื่องคิดเซตให้ลองกดด้านล่าง

  1. รัน main_script
  2. Set A = [101 102 103 104 105]
  3. Set B = [103 104 105 106 107]
  4. ลองป้อนผิด เช่น abc → โปรแกรมถามซ้ำ
  5. รัน run_tests → PASS ทั้งหมด

เครื่องคิดนี้ทำงานเหมือนโปรแกรม MATLAB (ตัดค่าซ้ำ → Union เรียงลำดับ → Intersection) ใช้เป็น demo สำรองได้

สรุป

  • โปรแกรมรับเซต 2 เซต → หา Union / Intersection + ส่วนต่าง
  • รับ input ได้หลากหลาย — [1 2 3], 1,2,3, 1:5, ชื่อตัวแปร ฯลฯ
  • ตรวจสอบข้อมูลและถามซ้ำได้ ไม่ crash
  • คำนวณแม่นยำ — ค่าเริ่มต้นเหมือน union/intersect มาตรฐาน
  • มีชุดทดสอบ 10 เคสยืนยันผล
  • แยกเป็นฟังก์ชันย่อย อ่านง่าย นำไปใช้ซ้ำได้

Q&A

คำถามที่พบบ่อย

คำถามแนวคำตอบ
ทำไมใช้ unique ก่อนคำนวณ?ให้จำนวนสมาชิกถูกต้อง เพราะเซตไม่นับค่าซ้ำ
setdiff ใช้ทำอะไร?หาสมาชิกที่อยู่เฉพาะเซตหนึ่ง (โซน A-only / B-only)
'stable' ต่างจากปกติอย่างไร?คงลำดับที่ป้อนเดิม แทนการเรียงจากน้อยไปมาก
Tolerance มีไว้ทำไม?เทียบเลขทศนิยมที่คลาดเคลื่อน เช่น 0.1+0.2 กับ 0.3
เซตว่างจะพังไหม?ไม่พัง มีเทสต์ยืนยันแล้ว

ขอบคุณครับ / ค่ะ

บทพูดนำเสนอ (Presentation Script)

โปรเจกต์: โปรแกรมคำนวณ Union & Intersection ของ 2 เซต พร้อมวาด Venn Diagram (MATLAB)

สมาชิก: 1) อณุภา โตมีบุญ (2511310795) · 2) ปภาวิน เต้าทิพย์ภูมิรักษ์ (2511310605)

งาน: งานกลุ่ม CPE-211 · นำเสนอ พุธ 14 ตุลาคม 2569 · ห้อง D502 · เวลารวมประมาณ 14 นาที

เอกสารนี้เป็นสคริปต์สำหรับพูด อธิบายทีละไฟล์ เน้นโค้ดและการทำงาน ใช้คู่กับภาพในโฟลเดอร์ docs/ (flowchart, วิธีคิดมือ, ผลลัพธ์)

ลำดับการนำเสนอและเวลา

ช่วงหัวข้อเวลาผู้นำเสนอ
1เปิด + โจทย์ + ภาพรวม2 นาทีคนที่ 1
2แผนผังการทำงาน (flowchart)2 นาทีคนที่ 1
3อธิบายทีละไฟล์ (โค้ด)8 นาทีคนที่ 1–2
4เดโมสด1.5 นาทีทั้งคู่
5สรุป + Q&A0.5 นาทีคนที่ 2
รวม14 นาที

ช่วงที่ 1 — เปิด + โจทย์ (คนที่ 1, ~2 นาที)

"สวัสดีครับ/ค่ะ พวกเราคือกลุ่ม ... มีสมาชิก 2 คน ได้แก่ อณุภา โตมีบุญ และ ปภาวิน เต้าทิพย์ภูมิรักษ์"
"โจทย์ของเราเป็นเรื่อง การดำเนินการทางเซต (Set Operations) ในวิชา CPE-211 กำหนดให้เซต A และ B แล้วให้หา Union (A ∪ B) และ Intersection (A ∩ B) พร้อมนำเสนอผลลัพธ์เป็น Venn Diagram"

โจทย์ที่ใช้สาธิต

  • A = {101, 102, 103, 104, 105}
  • B = {103, 104, 105, 106, 107}
"เราคิดวิธีทำด้วยมือก่อน แล้วจึงเขียนโปรแกรม MATLAB เพื่อให้คำนวณได้กับเซตขนาดใหญ่หรือซ้ำกันได้อัตโนมัติ"

เปิดภาพ docs/result_manual.png เพื่อโชว์วิธีคิดมือ

"ผลที่ได้คือ A ∪ B มี 7 ตัว (101–107) และ A ∩ B มี 3 ตัว (103, 104, 105) ขั้นตอนนี้คิดมือได้ไม่ยาก แต่ถ้าเซตมีเป็นหลักพัน การเขียนโปรแกรมจะตอบได้ทันที"

ช่วงที่ 2 — ภาพรวมการทำงาน (คนที่ 1, ~2 นาที)

เปิดภาพ docs/flowchart.png

"ภาพนี้คือ flowchart การทำงานของโปรแกรมครับ/ค่ะ ขั้นตอนคือ"
  1. เริ่มโปรแกรม → แสดงข้อความต้อนรับ
  2. รับค่า Set A (ถ้าป้อนผิด จะถามซ้ำจนถูก)
  3. รับค่า Set B (เหมือนกัน)
  4. ตัดค่าซ้ำของแต่ละเซต พร้อมรายงานว่าตัดไปกี่ตัว
  5. คำนวณ Union / Intersection และส่วนที่ต่างกัน
  6. แสดงผลลัพธ์ใน Command Window
  7. วาด Venn Diagram แล้วจบการทำงาน

แผนผังการเรียกใช้ไฟล์ (dependency)

main_script.m
 ├── read_set.m ──────► parse_set_input.m
 ├── calculate_set_ops.m
 └── plot_venn.m

run_tests.m
 ├── parse_set_input.m
 └── calculate_set_ops.m
"หัวใจสำคัญคือ โปรแกรมถูกแยกเป็นฟังก์ชันย่อย แต่ละไฟล์ทำงานเฉพาะอย่าง ทำให้อ่านง่าย ทดสอบง่าย และนำไปใช้ซ้ำได้"

ช่วงที่ 3 — อธิบายทีละไฟล์ (เน้นโค้ด)

3.1 main_script.m — สคริปต์หลัก (คนที่ 1, ~2 นาที)

"ไฟล์นี้เป็นตัวควบคุมหลัก ทำหน้าที่รับค่า → คำนวณ → แสดงผล → วาดรูป"

1) เตรียมและรับค่า

clc; clear; close all;
setA = read_set('Set A');
setB = read_set('Set B');
  • clc; clear; close all → เคลียร์หน้าจอ ตัวแปร และรูปเก่า เริ่มใหม่ทุกครั้ง
  • read_set เป็นฟังก์ชันที่เราเขียนเอง รับประกันว่าค่าที่ได้ถูกต้องเสมอ (รายละเอียดอยู่ในไฟล์ถัดไป)

2) ตัดค่าซ้ำก่อนคำนวณ

nRawA = numel(setA);
setA  = unique(setA, 'stable');
if nRawA > numel(setA)
    fprintf('หมายเหตุ: Set A มีค่าซ้ำ %d ตัว ระบบตัดออกให้แล้ว\n', nRawA - numel(setA));
end
  • เก็บจำนวนเดิม (nRawA) ไว้เทียบ แล้วใช้ unique(..., 'stable') ตัดค่าซ้ำ โดยคงลำดับเดิม
  • ถ้าจำนวนลดลง ก็พิมพ์บอกผู้ใช้ว่าตัดไปกี่ตัว → ทำให้จำนวนสมาชิกที่แสดงถูกต้อง

3) คำนวณใน try/catch

try
    [set_U, set_I] = calculate_set_ops(setA, setB);
    only_A = setdiff(setA, setB);   % อยู่ใน A แต่ไม่อยู่ใน B
    only_B = setdiff(setB, setA);   % อยู่ใน B แต่ไม่อยู่ใน A
catch err
    fprintf(2, 'เกิดข้อผิดพลาดในการคำนวณ: %s\n', err.message);
    return;
end
  • เรียกฟังก์ชัน calculate_set_ops เพื่อเอา Union กับ Intersection
  • ใช้ setdiff ของ MATLAB หาส่วนที่อยู่เฉพาะ A และเฉพาะ B (ไว้ใส่ในแผนภาพ)
  • ถ้าเกิด error กลางทาง จะไม่ทำโปรแกรมพัง แต่แจ้งเตือนแล้วจบอย่างสวยงาม

4) ส่งผลให้ฟังก์ชันวาดรูป

res = struct('setA', setA, 'setB', setB, 'union', set_U, ...
             'intersect', set_I, 'onlyA', only_A, 'onlyB', only_B);
plot_venn(res, 'NameA', 'Set A', 'NameB', 'Set B');
  • รวมผลลัพธ์ทั้งหมดเป็น struct ชื่อ res แล้วส่งเข้า plot_venn
  • การส่งเป็น struct ทำให้เพิ่ม/แก้ข้อมูลในอนาคตได้ง่าย โดยไม่ต้องแก้พารามิเตอร์เยอะ

3.2 read_set.m — รับค่าอย่างปลอดภัย (คนที่ 2, ~1 นาที)

"หน้าที่คือถามผู้ใช้กรอกเซต และวนถามซ้ำจนกว่าจะได้ค่าที่ถูกต้อง"
while true
    raw = input(sprintf('กรุณากรอกสมาชิกของ %s ...: ', label), 's');
    v = local_try_variable(raw);
    if ~isempty(v)
        values = v; return;
    end
    [v, ok, msg] = parse_set_input(raw);
    if ok
        values = v; return;
    end
    fprintf(2, '  [ผิดพลาด] %s กรุณาลองใหม่\n', msg);
end
  • ใช้ input(..., 's') รับเป็นข้อความ ไม่ใช่ให้ MATLAB ประมวลผลเป็นโค้ด → ปลอดภัย
  • ลำดับการตรวจ: ลองเป็นชื่อตัวแปรก่อน ถ้าไม่ใช่ค่อยไป parse เป็นตัวเลข
  • ถ้า parse ไม่ผ่าน จะพิมพ์สาเหตุแล้ว วนกลับไปถามใหม่ (loop while true)
function v = local_try_variable(raw)
    if isempty(s) || ~isvarname(s), return; end
    if evalin('base', sprintf('exist(''%s'', ''var'')', s)) ~= 1, return; end
    val = evalin('base', s);
    if isnumeric(val) && isvector(val)
        v = val(:).';
    end
end
  • isvarname เช็คว่าเป็นชื่อตัวแปรที่ถูกต้อง
  • evalin('base', ...) ดึงค่าจาก Workspace → ผู้ใช้พิมพ์ชื่อตัวแปร เช่น mySet ก็ได้
  • คืนค่าเฉพาะเมื่อเป็น เวกเตอร์ตัวเลข เท่านั้น

3.3 parse_set_input.m — แปลงข้อความหลากหลายรูปแบบ (คนที่ 2, ~1.5 นาที)

"ไฟล์นี้ใจดีกับผู้ใช้มาก รับ input ได้หลายแบบ เช่น [1 2 3], 1,2,3, 1:5"

ขั้นที่ 1–2: ทำความสะอาดข้อความ

str = regexprep(str, '[\[\]\(\)\{\}]', ' ');   % ตัดวงเล็บ [] () {}
str = regexprep(str, '[;,]+', ' ');            % เปลี่ยน , ; ให้เป็นช่องว่าง
tokens = regexp(str, '\S+', 'match');          % แยกเป็นคำๆ
  • ใช้ regular expression ตัดวงเล็บทุกชนิดออก แล้วเปลี่ยนตัวคั่นเป็นช่องว่าง
  • ผลคือทั้ง [1 2 3], (1,2,3), 1;2;3 กลายเป็นรูปแบบเดียวกัน → จัดการง่าย

ขั้นที่ 3: แปลงทีละคำ (รองรับ range)

if ~isempty(strfind(t, ':'))          % เจอ ':' = เป็นช่วง เช่น 1:5 หรือ 1:2:9
    parts = str2double(strsplit(t, ':'));
    if numel(parts) < 2 || numel(parts) > 3 || any(isnan(parts))
        ok = false; msg = sprintf('รูปแบบช่วง (range) ไม่ถูกต้อง: %s', t); return;
    end
    switch numel(parts)
        case 2, r = parts(1):parts(2);
        case 3, r = parts(1):parts(2):parts(3);
    end
else
    v = str2double(t);
    if isnan(v)                       % แปลงไม่ได้ = ไม่ใช่ตัวเลข
        ok = false; msg = sprintf('ค่าที่ไม่ใช่ตัวเลข: %s', t); return;
    end
    vals = [vals, v];
end
  • ถ้าคำนั้นมี : จะถือว่าเป็น ช่วง (range) → สร้างด้วย a:b หรือ a:s:b
  • ถ้าแปลงด้วย str2double ไม่ได้ (ได้ NaN) จะคืน ok = false พร้อมข้อความสาเหตุ
  • ฟังก์ชันคืนค่า 3 อย่าง [values, ok, msg] → ตัวเรียกเอา ok ไปตัดสินใจวนถามใหม่

3.4 calculate_set_ops.m — หัวใจการคำนวณ (คนที่ 1, ~1.5 นาที)

"นี่คือฟังก์ชันคำนวณหลัก มี signature แบบนี้"
function [set_union, set_intersect] = calculate_set_ops(A, B, varargin)
  • varargin คือ ตัวเลือกเพิ่มเติมแบบ name-value เช่น 'Stable', true

1) ตรวจสอบ input และอ่านตัวเลือก

if ~isnumeric(A) || (~isempty(A) && ~isvector(A))
    error('calculate_set_ops:badInput', 'Set A ต้องเป็นเวกเตอร์ตัวเลข');
end
...
switch lower(name)
    case 'stable'
        stable = logical(val);
    case 'tolerance'
        if ~isnumeric(val) || ~isscalar(val) || val < 0
            error('calculate_set_ops:badOption', 'Tolerance ต้องเป็นจำนวนจริงไม่ติดลบ');
        end
        tol = double(val);
    otherwise
        error('calculate_set_ops:badOption', 'ไม่รู้จักตัวเลือก: %s', name);
end
  • ตรวจว่า A, B เป็นเวกเตอร์ตัวเลขจริง ถ้าไม่ใช่จะ error ทันที
  • รองรับตัวเลือก 2 ตัว: Stable (คงลำดับ) และ Tolerance (เผื่อทศนิยมคลาดเคลื่อน)

2) คำนวณ

if tol > 0
    A = round(A ./ tol) .* tol;   % ปัดค่าลงบนกริดของ tolerance ก่อนเทียบ
    B = round(B ./ tol) .* tol;
end
if stable
    set_union     = union(A, B, 'stable');
    set_intersect = intersect(A, B, 'stable');
else
    set_union     = union(A, B);
    set_intersect = intersect(A, B);
end
  • Tolerance: ใช้ round(A./tol).*tol ปัดค่าเข้าหากึ่งกลางกริด → จับคู่เลขทศนิยมที่ควรเท่ากันได้ (เช่น 0.1+0.2 กับ 0.3)
  • ค่าเริ่มต้น (ไม่ใส่ตัวเลือก): เรียก union / intersect ตรงๆ → ผลเหมือนฟังก์ชัน MATLAB มาตรฐานเป๊ะ
  • Stable: เพิ่ม 'stable' เพื่อคงลำดับการป้อนเดิม

3.5 plot_venn.m — วาดแผนภาพ (คนที่ 1, ~1.5 นาที)

"วาดวงกลม 2 วง ใส่จำนวนสมาชิกในแต่ละโซน และปรับขนาดข้อความอัตโนมัติ"

1) วาดวงกลม 2 วง

theta = linspace(0, 2*pi, 300);
x_A = r * cos(theta) - 0.75;   y_A = r * sin(theta);
x_B = r * cos(theta) + 0.75;   y_B = r * sin(theta);

fill(x_A, y_A, [0.2 0.6 0.8], 'FaceAlpha', 0.4, 'EdgeColor', [0.1 0.4 0.7]);
fill(x_B, y_B, [0.9 0.5 0.2], 'FaceAlpha', 0.4, 'EdgeColor', [0.8 0.3 0.1]);
axis equal; axis off;
  • ใช้ cos/sin สร้างพิกัดวงกลม แล้วขยับซ้าย/ขวา (−0.75 / +0.75) ให้วงทับกัน
  • FaceAlpha 0.4 ทำให้ เห็นส่วนที่ทับกัน (intersection) ชัดเจน
  • axis equal ทำให้วงกลมกลมจริง ไม่บี้

2) ปรับขนาดฟอนต์ตามจำนวนสมาชิก

nMax = max([numel(onlyA), numel(onlyB), numel(set_I)]);
if nMax <= 8
    titleFs = 14; labelFs = 11; valueFs = 10;
elseif nMax <= 20
    titleFs = 13; labelFs = 10; valueFs = 9;
...
  • ยิ่งเซตมีสมาชิกเยอะ ยิ่งย่อฟอนต์ เพื่อไม่ให้ข้อความล้นวง

3) เลือกฟอนต์ไทยอัตโนมัติ

candidates = {'TH Sarabun New', 'Sarabun', 'Leelawadee UI', 'Leelawadee', ...
              'Tahoma', 'Arial Unicode MS', 'Angsana New', 'Cordia New', ...
              'Noto Sans Thai', 'Sukhumvit Set'};
  • ไล่หาฟอนต์ที่รองรับภาษาไทยจากที่ติดตั้งในเครื่อง → ป้องกัน ภาษาไทยเป็นกล่องสี่เหลี่ยม

3.6 run_tests.m — ชุดทดสอบ (คนที่ 2, ~0.5 นาที)

"เราเขียน test ไว้ 10 เคส เพื่อพิสูจน์ว่าโค้ดทำงานถูกจริง"
tests = { @test_parse_formats; @test_parse_ranges; ... };
for k = 1:numel(tests)
    try
        tests{k}();
        fprintf('  PASS  %s\n', func2str(tests{k}));
        nPass = nPass + 1;
    catch err
        fprintf('  FAIL  %s -> %s\n', func2str(tests{k}), err.message);
        nFail = nFail + 1;
    end
end
  • เก็บชื่อเทสต์เป็น function handle ใน cell array แล้ววนรันทีละตัว
  • ใช้ try/catch จับผล: ถ้าไม่ error = PASS, ถ้า error = FAIL (ใช้ assert เป็นตัวตัดสิน)
  • ครอบคลุม: parse ทุกรูปแบบ, range, ค่าที่ผิด, matrix, ค่าเริ่มต้นต้องเหมือน MATLAB, stable, tolerance, เซตว่าง, input ผิด

ช่วงที่ 4 — เดโมสด (ทั้งคู่, ~1.5 นาที)

"ขอสาธิตการใช้งานจริงครับ/ค่ะ"
  1. cd เข้าโฟลเดอร์โปรเจกต์ แล้วพิมพ์ main_script
  2. ตอนถาม Set A ให้พิมพ์ [101 102 103 104 105]
  3. ตอนถาม Set B ให้พิมพ์ [103 104 105 106 107]
  4. โชว์ผลลัพธ์: Union = 7 ตัว, Intersection = 3 ตัว, A−B = {101,102}, B−A = {106,107}
  5. โชว์ Venn Diagram ที่เด้งขึ้นมา
  6. โชว์การกันพลาด: พิมพ์ค่าผิด เช่น abc → โปรแกรมจะแจ้ง error และถามซ้ำ (ไม่พัง)
  7. โชว์ความยืดหยุ่น: ลองพิมพ์ 1:10 (แบบ range) หรือ 1,2,3
  8. ปิดท้าย: รัน run_tests ให้เห็นผล PASS ทั้งหมด
เคล็ดลับ: ซ้อมเดโมล่วงหน้า และเตรียมค่าที่จะพิมพ์ไว้ให้พร้อม

ช่วงที่ 5 — สรุป + Q&A (คนที่ 2, ~0.5 นาที)

"สรุปคือ โปรแกรมนี้รับเซต 2 เซต แล้วหา Union / Intersection พร้อมวาด Venn Diagram จุดเด่นคือ รับ input ได้หลากหลาย ตรวจสอบข้อมูลและถามซ้ำได้ คำนวณแม่นยำ และมีชุดทดสอบยืนยันผล ขอบคุณครับ/ค่ะ ยินดีตอบคำถาม"

ภาคผนวก — ข้อที่อาจถูกถาม (เตรียมตอบไว้)

คำถามแนวคำตอบ
ทำไมใช้ unique ก่อนคำนวณ?เพื่อให้จำนวนสมาชิกถูกต้อง เพราะเซตไม่นับค่าซ้ำ และแจ้งผู้ใช้ว่าตัดไปกี่ตัว
setdiff ใช้ทำอะไร?หาสมาชิกที่อยู่เฉพาะเซตหนึ่ง เอาไปแสดงในโซน A-only / B-only ของแผนภาพ
'stable' ต่างจากปกติอย่างไร?ปกติผลจะเรียงจากน้อยไปมาก แต่ 'stable' คงลำดับที่ป้อนเดิม
Tolerance มีไว้ทำไม?เทียบเลขทศนิยมที่คลาดเคลื่อน เช่น 0.1+0.2 ไม่เท่ากับ 0.3 เป๊ะๆ
ทำไมใช้ input(..., 's')?รับเป็นข้อความล้วน ปลอดภัยกว่า ไม่ให้ผู้ใช้รันโค้ดผ่าน input
ถ้าเซตว่างจะพังไหม?ไม่พัง มีเทสต์ test_edge_empty ยืนยันว่าได้ผลลัพธ์ว่างถูกต้อง
try/catch จำเป็นไหม?จำเป็น เพื่อไม่ให้โปรแกรมหยุดกลางทาง และแจ้ง error ให้ผู้ใช้ทราบ

ภาคผนวก — การแบ่งหน้าที่พูด (2 คน)

คนรับผิดชอบพูดไฟล์ที่เชี่ยวชาญ
คนที่ 1เปิด + ภาพรวม + main_script.m + calculate_set_ops.m + plot_venn.mmain_script.m, calculate_set_ops.m, plot_venn.m
คนที่ 2read_set.m + parse_set_input.m + run_tests.m + สรุป + เดโมread_set.m, parse_set_input.m, run_tests.m
สมาชิกทุกคนต้องมานำเสนอ และควรช่วยกันตอบ Q&A

โค้ดเต็มทั้ง 6 ไฟล์

โค้ดจริงจากไฟล์ในโปรเจกต์ (คัดลอกได้)

main_script.m

% ==========================================================
% MATLAB Project: Interactive Set Union & Intersection
% Visualisation: Venn Diagram Representation
% เวอร์ชันปรับปรุง (MATLAB_DRIVE_v2)
%   - รองรับ input หลากหลายรูปแบบ และตรวจสอบข้อมูล
%   - จัดการค่าซ้ำอย่างถูกต้อง
%   - แผนภาพเวนน์ปรับขนาดได้ + ฟอนต์ไทย
%   - คงผลลัพธ์ของฟังก์ชันเดิมไว้ครบถ้วน
% ==========================================================
clc; clear; close all;

fprintf('====================================================\n');
fprintf('   โปรแกรมคำนวณหา Union และ Intersection ทางเซต\n');
fprintf('====================================================\n');
fprintf('รูปแบบที่ป้อนได้ เช่น [1 2 3], 1,2,3, 1;2;3, 1:5, {1 2 3}\n');
fprintf('หรือพิมพ์ชื่อตัวแปรตัวเลขที่มีอยู่ใน Workspace ก็ได้\n\n');

% ----------------------------------------------------------
% 1. รับค่า Input จาก User (พร้อมตรวจสอบและถามซ้ำถ้าผิด)
% ----------------------------------------------------------
setA = read_set('Set A');
setB = read_set('Set B');

% ตัดค่าซ้ำออก (พร้อมรายงานจำนวนที่ตัด) เพื่อให้จำนวนสมาชิกถูกต้อง
nRawA = numel(setA);
nRawB = numel(setB);
setA  = unique(setA, 'stable');
setB  = unique(setB, 'stable');
if nRawA > numel(setA)
    fprintf('หมายเหตุ: Set A มีค่าซ้ำ %d ตัว ระบบตัดออกให้แล้ว\n', nRawA - numel(setA));
end
if nRawB > numel(setB)
    fprintf('หมายเหตุ: Set B มีค่าซ้ำ %d ตัว ระบบตัดออกให้แล้ว\n', nRawB - numel(setB));
end

% ----------------------------------------------------------
% 2. เรียกใช้ Function คำนวณ Set Operations
% ----------------------------------------------------------
try
    [set_U, set_I] = calculate_set_ops(setA, setB);
    only_A = setdiff(setA, setB); % อยู่ใน A แต่ไม่อยู่ใน B
    only_B = setdiff(setB, setA); % อยู่ใน B แต่ไม่อยู่ใน A
catch err
    fprintf(2, 'เกิดข้อผิดพลาดในการคำนวณ: %s\n', err.message);
    return;
end

% ----------------------------------------------------------
% 3. แสดงผลลัพธ์ใน Command Window
% ----------------------------------------------------------
fprintf('\n----------------------------------------------------\n');
fprintf('                    ผลลัพธ์การคำนวณ\n');
fprintf('----------------------------------------------------\n');
fprintf('Set A                  : %s (จำนวน %d ตัว)\n', num2str(setA), numel(setA));
fprintf('Set B                  : %s (จำนวน %d ตัว)\n', num2str(setB), numel(setB));
fprintf('1. Union (A U B)       : %s (จำนวน %d ตัว)\n', num2str(set_U), numel(set_U));
fprintf('2. Intersection (A n B): %s (จำนวน %d ตัว)\n', num2str(set_I), numel(set_I));
fprintf('3. A เท่านั้น (A - B)  : %s (จำนวน %d ตัว)\n', num2str(only_A), numel(only_A));
fprintf('4. B เท่านั้น (B - A)  : %s (จำนวน %d ตัว)\n', num2str(only_B), numel(only_B));
fprintf('----------------------------------------------------\n\n');

% ----------------------------------------------------------
% 4. วาดกราฟ Venn Diagram
% ----------------------------------------------------------
res = struct('setA', setA, 'setB', setB, 'union', set_U, ...
             'intersect', set_I, 'onlyA', only_A, 'onlyB', only_B);

try
    plot_venn(res, 'NameA', 'Set A', 'NameB', 'Set B');
catch err
    fprintf(2, 'วาดแผนภาพไม่สำเร็จ: %s\n', err.message);
end

read_set.m

function values = read_set(label)
%READ_SET  ถามผู้ใช้กรอกเซตซ้ำจนกว่าจะได้ค่าที่ถูกต้อง
%
%   values = read_set(label)
%
%   รองรับทั้งการพิมพ์ตัวเลขในรูปแบบต่างๆ (ดู parse_set_input)
%   และการพิมพ์ชื่อตัวแปรตัวเลขที่มีอยู่ใน Workspace

    while true
        raw = input(sprintf('กรุณากรอกสมาชิกของ %s (เช่น [101 102 103 104 105]): ', label), 's');

        v = local_try_variable(raw);
        if ~isempty(v)
            values = v;
            return;
        end

        [v, ok, msg] = parse_set_input(raw);
        if ok
            values = v;
            return;
        end

        fprintf(2, '  [ผิดพลาด] %s กรุณาลองใหม่\n', msg);
    end
end

% -------------------------------------------------------------------------
function v = local_try_variable(raw)
%LOCAL_TRY_VARIABLE  ถ้า raw เป็นชื่อตัวแปรตัวเลขใน Workspace ให้คืนค่าเวกเตอร์นั้น
    v = [];
    s = strtrim(raw);
    if isempty(s) || ~isvarname(s)
        return;
    end

    try
        if evalin('base', sprintf('exist(''%s'', ''var'')', s)) ~= 1
            return;
        end
        val = evalin('base', s);
    catch
        return;
    end

    if isnumeric(val) && isvector(val)
        v = val(:).';
    end
end

parse_set_input.m

function [values, ok, msg] = parse_set_input(raw)
%PARSE_SET_INPUT  แปลงข้อความที่ผู้ใช้กรอกให้เป็นเวกเตอร์ตัวเลขแถวเดียว
%
%   [values, ok, msg] = parse_set_input(raw)
%
%   รองรับรูปแบบการป้อนที่หลากหลาย เช่น
%       [1 2 3]         {1,2,3}         (1 2 3)
%       1 2 3           1,2,3           1;2;3
%       -3.5, 0, 2.25   1:5             1:2:9
%       [1 2 3; 4 5 6]  (เมทริกซ์ จะถูกคลี่เป็นแถวเดียว)
%       1e3             1000             +7
%
%   Input : raw    - ข้อความดิบจากผู้ใช้ (string หรือ char)
%   Output: values - เวกเตอร์แถวของตัวเลข (ว่างถ้า parse ไม่สำเร็จ)
%           ok     - true ถ้า parse สำเร็จ
%           msg    - ข้อความอธิบายสาเหตุเมื่อ ok เป็น false

    values = [];
    ok = true;
    msg = '';

    if nargin < 1 || isempty(raw)
        ok = false;
        msg = 'Input ว่างเปล่า';
        return;
    end

    str = strtrim(char(raw));
    if isempty(str)
        ok = false;
        msg = 'Input ว่างเปล่า';
        return;
    end

    % 1) ตัดวงเล็บ/ปีกกา/วงกลมทั้งหมดออก (รองรับทั้งแบบครบและไม่ครบ)
    str = regexprep(str, '[\[\]\(\)\{\}]', ' ');

    % 2) เปลี่ยนตัวคั่น comma/semicolon ให้เป็นช่องว่าง
    str = regexprep(str, '[;,]+', ' ');
    str = strtrim(str);

    if isempty(str)
        ok = false;
        msg = 'ไม่พบตัวเลขใน Input';
        return;
    end

    % 3) แยกเป็น token และแปลงทีละตัว (รองรับ range ด้วย ':')
    tokens = regexp(str, '\S+', 'match');
    vals = [];
    for k = 1:numel(tokens)
        t = tokens{k};

        if ~isempty(strfind(t, ':')) %#ok<STREMP>
            parts = str2double(strsplit(t, ':'));
            if numel(parts) < 2 || numel(parts) > 3 || any(isnan(parts))
                ok = false;
                msg = sprintf('รูปแบบช่วง (range) ไม่ถูกต้อง: %s', t);
                return;
            end
            switch numel(parts)
                case 2
                    r = parts(1):parts(2);
                case 3
                    r = parts(1):parts(2):parts(3);
            end
            if isempty(r)
                ok = false;
                msg = sprintf('ช่วงว่างหรือค่าขั้น (step) เป็น 0: %s', t);
                return;
            end
            vals = [vals, r]; %#ok<AGROW>
        else
            v = str2double(t);
            if isnan(v)
                ok = false;
                msg = sprintf('ค่าที่ไม่ใช่ตัวเลข: %s', t);
                return;
            end
            vals = [vals, v]; %#ok<AGROW>
        end
    end

    if isempty(vals)
        ok = false;
        msg = 'ไม่พบตัวเลขใน Input';
        return;
    end

    values = vals(:).';
end

calculate_set_ops.m

function [set_union, set_intersect] = calculate_set_ops(A, B, varargin)
%CALCULATE_SET_OPS  คำนวณ Union และ Intersection ของ 2 เซต
%
%   [set_union, set_intersect] = calculate_set_ops(A, B)
%   [set_union, set_intersect] = calculate_set_ops(A, B, 'Stable', true)
%   [set_union, set_intersect] = calculate_set_ops(A, B, 'Tolerance', 1e-9)
%
%   Input : A, B - เวกเตอร์ตัวเลข
%   Output: set_union     - สมาชิกทั้งหมดแบบไม่ซ้ำ (A U B) : เวกเตอร์แถว
%           set_intersect - สมาชิกที่ซ้ำกัน (A n B)        : เวกเตอร์แถว
%
%   ตัวเลือก (name-value, ไม่บังคับ):
%       'Stable'    (logical, ค่าเริ่มต้น false)
%                   true  = คงลำดับการป้อนเดิม ('stable')
%                   false = เรียงลำดับจากน้อยไปมาก (เหมือนฟังก์ชันเดิม)
%       'Tolerance' (จำนวนจริง >= 0, ค่าเริ่มต้น 0)
%                   0  = เทียบแบบเป๊ะๆ (เหมือนฟังก์ชันเดิม)
%                   >0 = ปัดค่าลงบนกริดของ tolerance ก่อนเทียบ
%                        เหมาะกับเลขทศนิยมที่อาจคลาดเคลื่อน
%
%   หมายเหตุ: เมื่อไม่ส่งตัวเลือกใดๆ ผลลัพธ์จะเหมือนฟังก์ชันเวอร์ชันเดิมทุกประการ

    [A, B, stable, tol] = local_parseOptions(A, B, varargin{:});

    if tol > 0
        A = round(A ./ tol) .* tol;
        B = round(B ./ tol) .* tol;
    end

    if stable
        set_union     = union(A, B, 'stable');
        set_intersect = intersect(A, B, 'stable');
    else
        set_union     = union(A, B);
        set_intersect = intersect(A, B);
    end
end

% -------------------------------------------------------------------------
function [A, B, stable, tol] = local_parseOptions(A, B, varargin)
    stable = false;
    tol    = 0;

    if ~isnumeric(A) || (~isempty(A) && ~isvector(A))
        error('calculate_set_ops:badInput', 'Set A ต้องเป็นเวกเตอร์ตัวเลข');
    end
    if ~isnumeric(B) || (~isempty(B) && ~isvector(B))
        error('calculate_set_ops:badInput', 'Set B ต้องเป็นเวกเตอร์ตัวเลข');
    end

    if mod(numel(varargin), 2) ~= 0
        error('calculate_set_ops:badOption', 'ตัวเลือกต้องส่งมาเป็นคู่ name-value');
    end

    for k = 1:2:numel(varargin)
        name = varargin{k};
        val  = varargin{k + 1};

        if ~ischar(name)
            error('calculate_set_ops:badOption', 'ชื่อตัวเลือกต้องเป็นข้อความ');
        end

        switch lower(name)
            case 'stable'
                stable = logical(val);
            case 'tolerance'
                if ~isnumeric(val) || ~isscalar(val) || val < 0
                    error('calculate_set_ops:badOption', 'Tolerance ต้องเป็นจำนวนจริงไม่ติดลบ');
                end
                tol = double(val);
            otherwise
                error('calculate_set_ops:badOption', 'ไม่รู้จักตัวเลือก: %s', name);
        end
    end

    A = A(:).';
    B = B(:).';
end

plot_venn.m

function hFig = plot_venn(res, varargin)
%PLOT_VENN  วาดแผนภาพเวนน์ 2 เซต พร้อมจำนวน/สมาชิกในแต่ละโซน
%
%   plot_venn(res)
%   plot_venn(res, 'NameA', 'Set A', 'NameB', 'Set B', ...
%                  'MaxItems', 12, 'FontName', 'Tahoma', 'Title', '...')
%
%   Input : res - struct ที่มีฟิลด์
%                 .setA, .setB, .union, .intersect, .onlyA, .onlyB
%
%   ตัวเลือก (name-value, ไม่บังคับ):
%       'NameA'/'NameB' - ชื่อที่ใช้แสดงของแต่ละเซต
%       'MaxItems'      - จำนวนสมาชิกสูงสุดที่แสดงต่อโซน (ค่าเริ่มต้น 12)
%       'FontName'      - ฟอนต์ที่ใช้ (ถ้าไม่ระบุ จะเลือกรองรับภาษาไทยอัตโนมัติ)
%       'Title'         - หัวเรื่องของแผนภาพ
%
%   Output: hFig - handle ของ figure ที่สร้างขึ้น

    % -------------------- อ่านตัวเลือก --------------------
    nameA    = 'Set A';
    nameB    = 'Set B';
    maxItems = 12;
    fontName = '';
    titleStr = 'แผนภาพเวนน์แสดงผลลัพธ์การดำเนินการทางเซต (Venn Diagram)';

    for k = 1:2:numel(varargin)
        switch lower(varargin{k})
            case 'namea',    nameA    = varargin{k + 1};
            case 'nameb',    nameB    = varargin{k + 1};
            case 'maxitems', maxItems = varargin{k + 1};
            case 'fontname', fontName = varargin{k + 1};
            case 'title',    titleStr = varargin{k + 1};
            otherwise
                error('plot_venn:badOption', 'ไม่รู้จักตัวเลือก: %s', varargin{k});
        end
    end

    if isempty(fontName)
        fontName = local_pickThaiFont();
    end

    onlyA = res.onlyA;
    onlyB = res.onlyB;
    set_I = res.intersect;
    set_U = res.union;

    % -------------------- กำหนดขนาดตามจำนวนสมาชิก --------------------
    nMax = max([numel(onlyA), numel(onlyB), numel(set_I)]);
    if nMax <= 8
        titleFs = 14; labelFs = 11; valueFs = 10;
    elseif nMax <= 20
        titleFs = 13; labelFs = 10; valueFs = 9;
    elseif nMax <= 50
        titleFs = 12; labelFs = 9;  valueFs = 7;
    else
        titleFs = 11; labelFs = 8;  valueFs = 6;
    end

    % -------------------- สร้างรูป --------------------
    hFig = figure('Name', 'Venn Diagram Visualisation', ...
                  'NumberTitle', 'off', 'Color', 'w');

    % วงกลม 2 วง (ตำแหน่งเดิมเพื่อให้หน้าตาเหมือนเวอร์ชันแรก)
    r = 1.5;
    theta = linspace(0, 2*pi, 300);
    x_A = r * cos(theta) - 0.75;  y_A = r * sin(theta);
    x_B = r * cos(theta) + 0.75;  y_B = r * sin(theta);

    fill(x_A, y_A, [0.2 0.6 0.8], 'FaceAlpha', 0.4, ...
         'EdgeColor', [0.1 0.4 0.7], 'LineWidth', 2);
    hold on;
    fill(x_B, y_B, [0.9 0.5 0.2], 'FaceAlpha', 0.4, ...
         'EdgeColor', [0.8 0.3 0.1], 'LineWidth', 2);

    axis equal;
    axis off;
    xlim([-3.4 3.4]);
    ylim([-3.6 2.4]);

    title(titleStr, 'FontSize', titleFs, 'FontWeight', 'bold', 'FontName', fontName);

    % -------------------- ข้อความในแต่ละโซน --------------------
    strA = local_fmt_list(sort(onlyA), maxItems, 22);
    strB = local_fmt_list(sort(onlyB), maxItems, 22);
    strI = local_fmt_list(sort(set_I), maxItems, 22);

    % Set A Only (ซ้าย)
    text(-1.4, 0.5, sprintf('%s Only\n(%d ตัว)', nameA, numel(onlyA)), ...
        'HorizontalAlignment', 'center', 'FontWeight', 'bold', ...
        'FontSize', labelFs, 'Color', [0.1 0.3 0.6], 'FontName', fontName);
    text(-1.4, -0.4, strA, 'HorizontalAlignment', 'center', ...
        'FontSize', valueFs, 'Color', [0.2 0.2 0.2], 'FontName', fontName);

    % Set B Only (ขวา)
    text(1.4, 0.5, sprintf('%s Only\n(%d ตัว)', nameB, numel(onlyB)), ...
        'HorizontalAlignment', 'center', 'FontWeight', 'bold', ...
        'FontSize', labelFs, 'Color', [0.7 0.2 0.1], 'FontName', fontName);
    text(1.4, -0.4, strB, 'HorizontalAlignment', 'center', ...
        'FontSize', valueFs, 'Color', [0.2 0.2 0.2], 'FontName', fontName);

    % Intersection (กลาง)
    text(0, 0.5, sprintf('Intersection (%s n %s)\n(%d ตัว)', nameA, nameB, numel(set_I)), ...
        'HorizontalAlignment', 'center', 'FontWeight', 'bold', ...
        'FontSize', labelFs, 'Color', [0.2 0.5 0.2], 'FontName', fontName);
    text(0, -0.4, strI, 'HorizontalAlignment', 'center', ...
        'FontSize', valueFs, 'FontWeight', 'bold', 'Color', 'k', 'FontName', fontName);

    % Union (สรุปด้านล่าง)
    unionStr = local_wrap_text(sprintf('จำนวนสมาชิกรวมทั้งหมด Union (%s U %s) = %d ตัว', ...
                                       nameA, nameB, numel(set_U)), 60);
    unionStr = sprintf('%s\nสมาชิก: %s', unionStr, local_fmt_list(sort(set_U), maxItems, 60));
    text(0, -2.6, unionStr, 'HorizontalAlignment', 'center', 'FontWeight', 'bold', ...
        'FontSize', valueFs, 'FontName', fontName, ...
        'BackgroundColor', [0.95 0.95 0.95], 'EdgeColor', [0.7 0.7 0.7], 'Margin', 8);

    hold off;
end

% =========================================================================
function s = local_fmt_list(v, maxItems, wrapWidth)
%LOCAL_FMT_LIST  แปลงเวกเตอร์ตัวเลขเป็นข้อความ {a, b, c} แบบจำกัดจำนวน
    v = v(:).';
    if isempty(v)
        s = '{}';
        return;
    end

    total = numel(v);
    tail = '';
    if total > maxItems
        v = v(1:maxItems);
        tail = sprintf(', … +%d', total - maxItems);
    end

    isInt = all(abs(v - round(v)) < 1e-9);
    parts = cell(1, numel(v));
    for k = 1:numel(v)
        if isInt
            parts{k} = sprintf('%d', round(v(k)));
        else
            parts{k} = sprintf('%g', v(k));
        end
    end
    s = ['{', strjoin(parts, ', '), tail, '}'];
    s = local_wrap_text(s, wrapWidth);
end

% =========================================================================
function out = local_wrap_text(s, width)
%LOCAL_WRAP_TEXT  แทรกขึ้นบรรทัดใหม่เมื่อข้อความยาวเกิน width
    if numel(s) <= width
        out = s;
        return;
    end

    words = strsplit(s, ' ');
    lines = {};
    cur = '';
    for k = 1:numel(words)
        w = words{k};
        if isempty(cur)
            cur = w;
        elseif numel(cur) + 1 + numel(w) <= width
            cur = [cur ' ' w]; %#ok<AGROW>
        else
            lines{end + 1} = cur; %#ok<AGROW>
            cur = w;
        end
    end
    if ~isempty(cur)
        lines{end + 1} = cur;
    end

    out = sprintf('%s\n', lines{:});
    out = out(1:end - 1);
end

% =========================================================================
function fn = local_pickThaiFont()
%LOCAL_PICKTHAIFONT  เลือกฟอนต์ที่รองรับภาษาไทยจากที่มีในระบบ
    try
        fn = get(0, 'defaultAxesFontName');
    catch
        fn = 'Helvetica';
    end

    try
        avail = listfonts();
    catch
        return;
    end

    candidates = {'TH Sarabun New', 'Sarabun', 'Leelawadee UI', 'Leelawadee', ...
                  'Tahoma', 'Arial Unicode MS', 'Angsana New', 'Cordia New', ...
                  'Noto Sans Thai', 'Sukhumvit Set'};
    for k = 1:numel(candidates)
        if any(strcmpi(avail, candidates{k}))
            fn = candidates{k};
            return;
        end
    end
end

run_tests.m

function run_tests()
%RUN_TESTS  ชุดทดสอบสำหรับฟังก์ชันในโปรเจกต์ (ไม่ต้องพึ่งพา toolbox เพิ่มเติม)
%
%   รันจากโฟลเดอร์นี้ด้วยคำสั่ง:  run_tests
%
%   ครอบคลุม: การ parse input หลากหลายรูปแบบ, พฤติกรรมเริ่มต้นที่ต้องเหมือน
%   เวอร์ชันเดิม, ตัวเลือก Stable/Tolerance, และ edge case ต่างๆ

    fprintf('=== Running tests ===\n');

    tests = {
        @test_parse_formats
        @test_parse_ranges
        @test_parse_invalid
        @test_parse_matrix_flatten
        @test_default_matches_builtin
        @test_stable_order
        @test_tolerance_floats
        @test_duplicates_dedupe
        @test_edge_empty
        @test_bad_input_error
    };

    nPass = 0;
    nFail = 0;
    for k = 1:numel(tests)
        name = func2str(tests{k});
        try
            tests{k}();
            fprintf('  PASS  %s\n', name);
            nPass = nPass + 1;
        catch err
            fprintf('  FAIL  %s -> %s\n', name, err.message);
            nFail = nFail + 1;
        end
    end

    fprintf('=== %d passed, %d failed ===\n', nPass, nFail);
    if nFail > 0
        error('run_tests:failed', '%d test(s) failed', nFail);
    end
end

% =========================================================================
function test_parse_formats
    cases = {
        '[1 2 3]',        [1 2 3]
        '1 2 3',          [1 2 3]
        '1,2,3',          [1 2 3]
        '1;2;3',          [1 2 3]
        '{1, 2, 3}',      [1 2 3]
        '(1 2 3)',        [1 2 3]
        '  -3.5, 0, 2.25 ', [-3.5 0 2.25]
        '1e3',            1000
        '+7',             7
        '[1 2 3',         [1 2 3]   % วงเล็บไม่ครบ ก็ยังคงผ่อนปรนให้
    };
    for k = 1:size(cases, 1)
        [v, ok, msg] = parse_set_input(cases{k, 1});
        assert(ok, 'parse ล้มเหลว: "%s" (%s)', cases{k, 1}, msg);
        assert_close(v, cases{k, 2}, 1e-12, cases{k, 1});
    end
end

% =========================================================================
function test_parse_ranges
    [v1, ok1] = parse_set_input('1:5');
    assert(ok1, 'range 1:5 ควร parse ได้');
    assert(isequal(v1, [1 2 3 4 5]), 'range 1:5 ผลไม่ถูกต้อง');

    [v2, ok2] = parse_set_input('1:2:9');
    assert(ok2, 'range 1:2:9 ควร parse ได้');
    assert(isequal(v2, [1 3 5 7 9]), 'range 1:2:9 ผลไม่ถูกต้อง');

    [v3, ok3] = parse_set_input('-2:2');
    assert(ok3 && isequal(v3, [-2 -1 0 1 2]), 'range -2:2 ผลไม่ถูกต้อง');

    [v4, ok4] = parse_set_input('[1 2 3] 10:12');
    assert(ok4 && isequal(v4, [1 2 3 10 11 12]), 'ผสม list + range ผลไม่ถูกต้อง');
end

% =========================================================================
function test_parse_invalid
    bad = {'', 'abc', '1,2,x', '1::3', ':', '1:', '--5', '1.2.3', 'NaN'};
    for k = 1:numel(bad)
        [~, ok] = parse_set_input(bad{k});
        assert(~ok, 'ควร parse ไม่ผ่าน: "%s"', bad{k});
    end
end

% =========================================================================
function test_parse_matrix_flatten
    [v, ok] = parse_set_input('[1 2 3; 4 5 6]');
    assert(ok, 'เมทริกซ์ควร parse ได้');
    assert(isequal(v, [1 2 3 4 5 6]), 'การคลี่เมทริกซ์ไม่ถูกต้อง');
end

% =========================================================================
function test_default_matches_builtin
    A = [3 1 2 2];
    B = [2 4];
    [U, I] = calculate_set_ops(A, B);
    assert(isequal(U, union(A, B)), 'default Union ต้องเหมือน union() เป๊ะ');
    assert(isequal(I, intersect(A, B)), 'default Intersect ต้องเหมือน intersect() เป๊ะ');
end

% =========================================================================
function test_stable_order
    A = [3 1];
    B = [2 1];
    [U, I] = calculate_set_ops(A, B, 'Stable', true);
    assert(isequal(U, [3 1 2]), 'Stable Union ควรคงลำดับการป้อน');
    assert(isequal(I, [1]), 'Stable Intersect ผลไม่ถูกต้อง');
end

% =========================================================================
function test_tolerance_floats
    A = 0.1 + 0.2;   % ~0.30000000000000004
    B = 0.3;

    [~, Iexact] = calculate_set_ops(A, B);
    assert(isempty(Iexact), 'แบบเป๊ะๆ ควรได้ intersection ว่าง');

    [~, Itol] = calculate_set_ops(A, B, 'Tolerance', 1e-9);
    assert(numel(Itol) == 1, 'แบบ tolerance ควรจับคู่เลขทศนิยมได้');
    assert(abs(Itol - 0.3) < 1e-12, 'ค่าที่ได้ควรใกล้ 0.3');
end

% =========================================================================
function test_duplicates_dedupe
    v = [5 5 1 1 3];
    u = unique(v, 'stable');
    assert(isequal(u, [5 1 3]), 'unique stable ควรคงลำดับแรกที่พบ');
    assert(numel(v) - numel(u) == 2, 'จำนวนค่าซ้ำที่ตัดต้องเป็น 2');
end

% =========================================================================
function test_edge_empty
    [U, I] = calculate_set_ops([], []);
    assert(isempty(U) && isempty(I), 'เซตว่างทั้งคู่ควรได้ผลว่าง');

    [U2, I2] = calculate_set_ops([1 2], []);
    assert(isequal(U2, [1 2]) && isempty(I2), 'B ว่าง ผลไม่ถูกต้อง');
end

% =========================================================================
function test_bad_input_error
    didThrow = false;
    try
        calculate_set_ops('abc', [1 2]); %#ok<CTCH>
    catch
        didThrow = true;
    end
    assert(didThrow, 'input ที่ไม่ใช่ตัวเลขควร throw error');
end

% =========================================================================
function assert_close(a, b, tol, label)
    assert(isequal(size(a), size(b)), 'ขนาดไม่ตรงกัน: %s', label);
    assert(max(abs(a(:) - b(:))) < tol, 'ค่าไม่ตรงกัน: %s', label);
end
1 / 16
← → เลื่อน · N โน้ต · F เต็มจอ · D เนื้อหาเต็ม

Presenter notes