Chương 016: Vòng lặp foreach và nested loop

foreach có rất nhiều yếu điểm nhưng nó vẫn là một vòng lặp tiện dụng.

Tóm tắt chương trước

Bạn đang trở nên mạnh mẽ dần.

Nhưng dù vậy, việc thám hiểm di tích vẫn phải rất cẩn thận.

Kiến thức chương này

  • Vòng lặp foreach khiến đoạn code dễ đọc hơn rất nhiều.
  • Foreach có rất nhiều nhược điểm: chạy chậm hơn for, không biết vị trí phần tử đang xét là bao nhiêu, không thể chỉnh sửa phần tử đang xét,… nhưng dù vậy, sử dụng foreach đúng lúc vẫn có rất nhiều tác dụng.
  • Nested loop nghĩa là loop lồng trong loop. Hạn chế nested quá nhiều vì các loop lồng nhau rất nặng.

Cốt truyện

Đâu đó trong di tích, một đốm sáng bỗng xuất hiện giữa màn đêm, chiếu rọi không gian xung quanh.

Nó nhấp nháy, nhấp nháy, đều đều như nhịp thở.

Một sinh vật kì lạ đang thức giấc.

Đôi mắt đỏ rực của nó nhìn chằm chằm vào khoảng không, hướng đến phía của Kurobo và bạn.

Vòng lặp: foreach

Một vòng lặp bình thường có thể được code như sau:

string[] names = { "games", "nha", "lam" };
for (int index = 0; index < names.Length; index++)
{
    Console.WriteLine(names[index]);
}

Chúng có thể được viết lại dưới dạng foreach như sau:

string[] names = { "games", "nha", "lam" };
foreach (string name in names)
{
    Console.WriteLine(name);
}


Thí dụ trên được dịch là: Ứng với mỗi phần tử trong names thì in ra màn hình phần tử đó.

Để sử dụng foreach, đầu tiên phải khai báo một biến tạm thời cho mỗi vòng lặp (biến đó phải cùng kiểu với các phần tử trong dãy muốn xem). Sau đó là từ khoá in và tên dãy.

Tên biến tạm thời này có thể đặt là bất cứ thứ gì, nhưng thông thường, khi muốn xét duyệt một dãy nào đó qua vòng lặp, người ta thường thêm “s” ở dãy để khi nhắc đến các phần tử bên trong nó thì người ta bỏ s.

Chẳng hạn, ở thí dụ bên dưới: scores đại diện cho tên dãy, còn score (không có s) đại diện cho các phần tử bên trong.

int[] scores = { 1, 2, 5, 6, 9 };
foreach (int score in scores)
{
    //
}

Giống như for, các biến tạm thời này chỉ có thể sử dụng ở bên trong vòng lặp.

Foreach sẽ khiến đoạn code dễ đọc hơn rất nhiều. Và. Chỉ vậy thôi.

Đánh đổi với việc dễ đọc, foreach có rất nhiều nhược điểm khi so sánh với for.

  1. Chạy chậm hơn một chút so với for.
  2. Không thể biết vị trí phần tử đang xét là bao nhiêu. Chẳng hạn: Top #2 is GMA. Top #1 is GNL.
  3. Không thể chỉnh sửa các phần tử đang xét duyệt do foreach tạo ra một bản sao mới của dãy đang xét duyệt.
  4. Bắt buộc phải xét duyệt qua tất cả các phần tử trong dãy. Khác với for, bạn có thể xét một nửa dãy, hoặc xét vài phần tử, nhưng foreach một khi đã chạy là chạy hết.

Dù có nhiều hạn chế như vậy, nhưng nếu biết sử dụng foreach đúng lúc thì đoạn code sẽ thuận mắt hơn rất nhiều.

Vòng lặp trong vòng lặp: nested loop

Nested là một từ khoá thường hay gặp trong quá trình phát triển game. Nested If nghĩa là if lồng trong if. Nested Method nghĩa là Method lồng trong Method (đệ qui). Nested loop nghĩa là loop lồng trong loop.

Chẳng hạn, để thông báo toạ độ của kẻ địch, ta thường sử dụng 2 vòng for lồng nhau như sau:

int x = 5;
int y = 5;

int enemyX = 3;
int enemyY = 2;

for (int i = 1; i <= x; i++)
{
    for (int j = 1; j <= y; j++)
    {
        if (enemyX == i && enemyY == j)
        Console.WriteLine($"Enemy spotted! X = {i}, Y = {j}");
    }
}

Nested loop quá nhiều lớp (chẳng hạn từ 3 trở lên) sẽ khá nặng, nên nếu bạn bắt đầu mở vòng loop thứ 3 thì hãy thử nghĩ bài toán theo một hướng khác xem sao nhé.

Quest: Máy quét kẻ địch 2

Kuroko cho biết, với trình độ hiện giờ, bạn có thể tạo ra một loại ma thuật phát hiện kẻ địch trong phạm vi ma lực của mình với độ hiệu quả gấp đôi! Không, gấp ba lần!

Hứa!

Việc này sẽ tăng khả năng sinh tồn của bạn!

Không có gì lợi thế hơn là biết được vị trí của kẻ thù. Bạn có thể dễ dàng hạ những kẻ đi lẻ.

Ngày trước, bạn có thể phát hiện kẻ địch ở phạm vi 2D (hình dưới)

Bây giờ, hãy nâng cấp lên phiên bản 3D nào!

Quest: Máy quét kẻ địch 2 ✦✦✦✧✧

Máy quét kẻ địch (bản nâng cấp)

Bạn chọn ra 1 điểm trong không gian. Điểm này sẽ quét toàn bộ không gian xung quanh nó với độ lớn bán kính là 2.

Nếu có bất kì kẻ địch nào phát hiện trong bán kính, bạn sẽ nhận được thông báo vị trí của chúng.

Hãy viết chương trình thoả mãn:

  1. Tình huống: gốc toạ độ bây giờ không đặt dưới chân bạn. Vị trí của bạn là X = 3, Y = 2, Z = 8.
  2. Khi bắt đầu chương trình, nhập số lượng quái vật = 3 và toạ độ của chúng. Lần lượt theo toạ độ x, y, z là: (1, 2, 5), (4, 4, 4), (6, 2, 3).
  3. Nhập toạ độ đặt kĩ năng: X = 6, Y = 3, Z = 4.
  4. Đảm bảo kĩ năng đúng như miêu tả.
  5. Khi kết thúc, thông báo vị trí kẻ địch và khoảng cách của chúng với bạn. (Gợi ý: sử dụng method tính căn bậc 2 ở chương 11).
  6. Thử nghiệm với các thông số khác để chắc chắn kết quả là đúng.

Xem đáp án

[mycred_sell_this]

Giải thích

Chương trình này có 2 điểm mấu chốt:

  1. Làm sao tính khoảng cách giữa 2 điểm trong không gian 3D?
  2. Làm sao thay đổi số lượng kẻ địch toạ độ của chúng để chương trình cho ra các kết quả khác nhau với các input khác nhau?

Tính khoảng cách giữa 2 điểm trong không gian 3D

Tương tự như 2D, tính khoảng cách giữa 2 điểm trong không gian 3D cũng rất đơn giản.

Công thức này có thể dễ dàng chứng minh ngược lại bằng Pythagoras

Ta sử dụng lại cách tính căn bậc 2 đã được trình bày ở chương 11 như sau:

double SquareRootOfX(double x)
{
    double y = x / 2;
    double precision = x / 1000;
    while (y * y - x > precision || x - y * y > precision)
    {
        if (y * y > x)
        {
            y -= y / 2;
        }
        else
        {
            y += y / 2;
        }
    }
    return y;
}

Input: số lượng kẻ địch toạ độ của chúng

Với kiến thức hiện tại, chỉ có array có thể làm được điều này. Ta có đoạn code sau:

//enemies input
int enemyNumber;
Console.WriteLine("Please input the number of enemy: ");
enemyNumber = Convert.ToInt32(Console.ReadLine());

int[] enemiesPosX = new int[enemyNumber];
int[] enemiesPosY = new int[enemyNumber];
int[] enemiesPosZ = new int[enemyNumber];
for (int i = 0; i < enemyNumber; i++)
{
    Console.WriteLine($"Please Input Enemy {i + 1} Pos X:");
    enemiesPosX[i] = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine($"Please Input Enemy {i + 1} Pos Y:");
    enemiesPosY[i] = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine($"Please Input Enemy {i + 1} Pos Z:");
    enemiesPosZ[i] = Convert.ToInt32(Console.ReadLine());
}

Ý tưởng đoạn code: phân ra mỗi array xử lí 1 toạ độ tương ứng của kẻ địch. Bây giờ số lượng kẻ địch và vị trí của chúng sẽ tuỳ vào người chơi khởi tạo.

Kết quả

int[] yourPos = { 3, 2, 8 };
int skillRadius = 2;

//enemies input
int enemyNumber;
Console.WriteLine("Please input the number of enemy: ");
enemyNumber = Convert.ToInt32(Console.ReadLine());

int[] enemiesPosX = new int[enemyNumber];
int[] enemiesPosY = new int[enemyNumber];
int[] enemiesPosZ = new int[enemyNumber];
for (int i = 0; i < enemyNumber; i++)
{
    Console.WriteLine($"Please Input Enemy {i + 1} Pos X:");
    enemiesPosX[i] = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine($"Please Input Enemy {i + 1} Pos Y:");
    enemiesPosY[i] = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine($"Please Input Enemy {i + 1} Pos Z:");
    enemiesPosZ[i] = Convert.ToInt32(Console.ReadLine());
}

//skill pos input
int[] skillPos = { 6, 3, 4 };
Console.WriteLine("Where would you like to scout? (X, Y, Z):");
skillPos[0] = Convert.ToInt32(Console.ReadLine());
skillPos[1] = Convert.ToInt32(Console.ReadLine());
skillPos[2] = Convert.ToInt32(Console.ReadLine());

float[] distanceSqrToSkill = new float[enemyNumber];

Console.Clear();

for (int i = 0; i < enemyNumber; i++)
{
    distanceSqrToSkill[i] = (enemiesPosX[i] - skillPos[0]) * (enemiesPosX[i] - skillPos[0]) +
        (enemiesPosY[i] - skillPos[1]) * (enemiesPosY[i] - skillPos[1]) +
        (enemiesPosZ[i] - skillPos[2]) * (enemiesPosZ[i] - skillPos[2]);
    
    if (distanceSqrToSkill[i] <= skillRadius * skillRadius)
    {
        Console.WriteLine("------------------------------");
        double distanceToYou = SquareRootOfX((enemiesPosX[i] - yourPos[0]) * (enemiesPosX[i] - yourPos[0]) +
        (enemiesPosY[i] - yourPos[1]) * (enemiesPosY[i] - yourPos[1]) +
        (enemiesPosZ[i] - yourPos[2]) * (enemiesPosZ[i] - yourPos[2]));
        Console.WriteLine("Detected an enemy!");
        Console.WriteLine($"X = {enemiesPosX[i]}, Y = {enemiesPosY[i]}, Z = {enemiesPosZ[i]}");
        Console.WriteLine($"Distance: {distanceToYou:F3}");
    }
}

double SquareRootOfX(double x)
{
    double y = x / 2;
    double precision = x / 1000;
    while (y * y - x > precision || x - y * y > precision)
    {
        if (y * y > x)
        {
            y -= y / 2;
        }
        else
        {
            y += y / 2;
        }
    }
    return y;
}

Cách giải của mình chỉ để tham khảo. Trong lập trình, để giải quyết 1 vấn đề có rất nhiều cách khác nhau, vì thế nếu cách giải của bạn không giống mẫu thì cũng đừng lo lắng quá.

Nếu thấy có thắc mắc, xin hãy comment ngay xuống bên dưới hoặc báo bên discord nhé.

[/mycred_sell_this]

Chương Trước | Tổng quan | Chương Kế

Kurrwo

Công nhân👷gõ 💬 văn bản ⌨️ trò chơi 🎮 điện tử 📱 👏👏👏

Post navigation

Leave a Reply

Your email address will not be published. Required fields are marked *