Thử lập trình

Liên hệ QC
Em xin được góp chút xíu. Mong các bác chỉ giáo!!
Mã:
Sub test()
Dim n As Long
n = 8

Dim i As Long
For i = 1 To n

ThisWorkbook.Sheets(1).Range(Cells(i, i), Cells(i, n * 2 - i)).Value = n - i + 1

ThisWorkbook.Sheets(1).Range(Cells(n * 2 - i, i), Cells(n * 2 - i, n * 2 - i)).Value = n - i + 1

ThisWorkbook.Sheets(1).Range(Cells(i, i), Cells(n * 2 - i, i)).Value = n - i + 1

ThisWorkbook.Sheets(1).Range(Cells(i, n * 2 - i), Cells(n * 2 - i, n * 2 - i)).Value = n - i + 1

Next i

End Sub
 
Bài 5
Dựng từng hàng ngang nè. Chú ý là nếu không có điều kiện trái qua phải, trên xuống dưới thì code có thể viết ngắn hơn. Như code bài #18, mảng trong bài này chỉ là để đồng bộ. Chính bản thân giải thuật thì có thể in thẳng ra.

Sub tt()
n = 5
n = n - 1
Dim a(): ReDim a(-n To n, -n To n)
tt2 a, n, n
[a1].Resize(UBound(a) - LBound(a) + 1, UBound(a, 2) - LBound(a, 2) + 1) = a
End Sub

Sub tt2(a(), n, ni)
For i = -n To n
a(-ni, i) = IIf(Abs(i) <= ni, ni, Abs(i)) + 1
Next i
If ni <= 0 Then Exit Sub
tt2 a, n, ni - 1
For i = -n To n
a(ni, i) = a(-ni, i)
Next i
End Sub
Có cách nào chỉ cần 1 For và khó hơn không cần For nào :)
 
Bài 5

Có cách nào chỉ cần 1 For và khó hơn không cần For nào :)
Trên nguyên tắc, tất cả vòng lặp for, while, do,... đều có thể thay thế bằng đệ quy. :p

' Code căn bản là bài #20.
' chỉ dùng đệ quy để thay thế vòng lặp.


Sub tt()
n = 5
n = n - 1
Dim a(): ReDim a(-n To n, -n To n)
tt3 a, n, n
[a1].Resize(UBound(a) - LBound(a) + 1, UBound(a, 2) - LBound(a, 2) + 1) = a
End Sub

Sub tt3(a(), n, ni)
tt3x a, -n, n, ni, True
If ni <= 0 Then Exit Sub
tt3 a, n, ni - 1
tt3x a, -n, n, ni, False
End Sub

Sub tt3x(a(), i, n, ni, firstHalf)
If i > n Then Exit Sub
If firstHalf Then
a(-ni, i) = IIf(Abs(i) <= ni, ni, Abs(i)) + 1
Else
a(ni, i) = a(-ni, i)
End If
tt3x a, i + 1, n, ni, firstHalf
End Sub

Tuy nhiên, đệ quy cũng là một hình thức vòng lặp. Nếu bạn hỏi code mà không phải lặp lại thì tôi chịu thua.
 
Dùng FormulaR1C1 hoặc Formula, dùng công thức ở bài 9, quất 1 phát 1.
Lần này lại "kick the bucket". -.,\;
Quất 1 phát xong nó vẫn còn nằm trong bảng tính. Muốn in phải copy ra lại mảng rồi duyệt.

The wabbit kicked the bucket. The wabbit kicked the bucket.
Elmer Fudd nhảy múa ăn mừng Bugs Bunny (Looney Tunes/Merrie Melodies)
.
 
Lần chỉnh sửa cuối:
Em cũng thử đệ quy dựa vào bài #6 của em.
Mã:
Sub test3()
      Range("A1").Resize(1000, 1000).Value = vbNullString
      Call Dequy(6)
End Sub
Sub Dequy(n As Long)
      Dim Arr
      Dim i As Long, j As Long
      Dim k As Long
      k = n * 2 - 1
      ReDim Arr(1 To k, 1 To k)
      For i = 1 To k
            Arr(i, k) = n
            Arr(k, i) = n
            Arr(1, (k + 1 - i)) = n
            Arr((k + 1 - i), 1) = n
      Next i
      j = Range("A1").Value - n + 1
      If j <= 0 Then j = 1
      Cells(j, j).Resize(k, k).Value = Arr
      If n > 1 Then
            n = n - 1
            Call Dequy(n)
      End If
End Sub
 
Em cũng thử đệ quy dựa vào bài #6 của em.
...

Cái sub tt2 ở bài #20, và cái sub tt3 ở bài #23 đệ quy là có lý do của chúng.
Vì dụ (n =3 cho ngắn)
33333 -> lần chạy thứ nhất
32223 -> lần thứ nhất gọi lần thứ nhì
32123 -> lần thứ nhì gọi lẩn thứ ba : gặp số 1, không gọi tiếp nữa.
32223 -> bước tiếp theo của lần nhứ nhì, sau khi được lộn về từ lần thứ ba
33333 -> bước tiếp theo của lần nhứ nhất, sau khi được lộn về từ lần thứ nhì

Code hơi luộm thuộm là do tôi ép nó dùng mảng 2 chiều cho đồng bộ với các bài khác.
Nếu chỉ cần xuất kết quả ra cửa sổ immediate thì nó dùng mảng 1 chiều. Gọn hơn.
- tạo một mảng 2*n-1 phần tử
- lần chạy thứ nhất copy mảng và ghi n vào các phần tử
- lần chạy thứ nhì copy mảng trên và ghi n-1 vào các phần tử, chừa lại 1 phần tử ở mỗi đầu (đã sẵn ghi n)
- lần chạy thứ ba copy mảng của lần thứ nhì và ghi n-2 vào, chừa lại 2 phần tử ở mỗi đầu.
- bước tiếp theo của lần thứ nhì đã có sẵn mảng, khỏi cần tính thêm gì hết
- tương tự cho bước tiếp theo của lần thứ nhất.

Giải thuật đệ quy này đáp ứng điều kiện tính từ trái sang phải, trên xuống dưới.
 
Lần này lại "kick the bucket". -.,\;
Quất 1 phát xong nó vẫn còn nằm trong bảng tính. Muốn in phải copy ra lại mảng rồi duyệt.
Kick gì mà kick. In gì nữa mà in. Theo các bài giải bằng code phía trên thì xuống sheet là xong rồi.
 
Giải thuật đệ quy này đáp ứng điều kiện tính từ trái sang phải, trên xuống dưới.
Theo yêu cầu đề bài #3 của thầy "lúc vẽ thì từ trái sang phải (hoặc cả dòng như một chuỗi); và xong một dòng mới được sang dòng kế tiếp." thì bài #6 của em không đúng yêu cầu đề bài, #26 em cũng dựa vào #6 nên cũng không đúng yêu cầu đề bài mà thầy đưa ra rồi. Em chỉ làm để các thành viên có thêm 1 cách để tham khảo.
 
Tôi bổ sung thêm một cách dùng công thức tạo ra 1 mảng ảo trong giá trị 1 cell luôn ( không phải trong range nxn)
Ví dụ trong ô A1 điền giá trị:
=IF(ABS(ROW($1:$9)/ROW($1:$9) + TRANSPOSE(ROW($1:$9)-6))+1>TRANSPOSE(ABS(ROW($1:$9)/ROW($1:$9) + TRANSPOSE(ROW($1:$9)-6))+1),ABS(ROW($1:$9)/ROW($1:$9) + TRANSPOSE(ROW($1:$9)-6))+1,TRANSPOSE(ABS(ROW($1:$9)/ROW($1:$9) + TRANSPOSE(ROW($1:$9)-6))+1))
Ví dụ ở đây là ma trận 9x9 ( với 9=5x2-1) và 6=5+1 do vậy thay thế số 9 và 6 tương ứng với số mong muốn.

Nếu áp dụng vào VBA thì như vậy có thể coi là không dùng tí nào vòng For:
Mã:
Function RollMatrix(n As Integer)
Dim tmpMtr, myStr As String
Dim d As Integer, i As Integer
d = n * 2 - 1
i = n + 1
myStr = "IF(ABS(ROW($1:$" & d & ")/ROW($1:$" & d & ") + TRANSPOSE(ROW($1:$" & d & ")-" & i & "))+1>ABS(ROW($1:$" & d & ")-" & i & "+TRANSPOSE(ROW($1:$" & d & ")/ROW($1:$" & d & ")))+1,ABS(ROW($1:$" & d & ")/ROW($1:$" & d & ") + TRANSPOSE(ROW($1:$" & d & ")-" & i & "))+1,ABS(ROW($1:$" & d & ")-" & i & "+TRANSPOSE(ROW($1:$" & d & ")/ROW($1:$" & d & ")))+1)"
If Len(myStr) > 256 Then
MsgBox "UnSuccess"
RollMatrix = False
Exit Function
End If
tmpMtr = Evaluate(myStr)
RollMatrix = tmpMtr
End Function
 
Lần chỉnh sửa cuối:
Kick gì mà kick. In gì nữa mà in. Theo các bài giải bằng code phía trên thì xuống sheet là xong rồi.
Xuống sheet là để test các con số.

Điển hình, code giải theo toán giải tích ở bài #18, không liên quan gì đến worksheet thì như sau:

Sub HinhVuong()
n = 5 ' trị cần vẽ
pW = Space(Len(CStr(n)) + 1) ' độ rộng để in mỗi số
n = n - 1 ' trung điểm là (0,0) nên các toạ độ là từ 0 đến n-1
For i = -n To n
For j = -n To n
Rset pW = Application.Max(Abs(i), Abs(j)) + 1
Debug.Print pW;
Next j
Debug.Print
Next i
End Sub

1647096920383.png
 
Em chỉ biết cộng trừ nhân chia thôi nên mò ra được cái này chắc cũng đáp ứng được yêu cầu bài #3 của thầy.
Mã:
Sub test4()
      Call ThuLapTrinh(150)
End Sub
Sub ThuLapTrinh(n As Long)
      Dim t As Double
      t = Timer
      Dim i As Long, j As Long
      Dim a As Long, b As Long
      Dim x As Long, y As Long
      Dim Dong As String
      a = n * 2 - 1
      b = n
      For i = 1 To a + 2
            GoSub InRaNe
            Dong = ""
            If b = i Then i = i + 2
            n = Abs(b - i)
      Next i
      t = Timer - t
      MsgBox t
      Exit Sub
InRaNe:
      For j = 0 To b - 1
            x = n - j
            y = n + j
            If x = y Then
                  Dong = " " & n & " "
            ElseIf (y - x) / 2 < n Then
                  Dong = " " & n & Dong & n & " "
            ElseIf (y - x) / 2 < b Then
                  Dong = " " & (y - x) / 2 + 1 & Dong & (y - x) / 2 + 1 & " "
            End If
      Next j
      Debug.Print Dong
      Return
End Sub
 
Lần chỉnh sửa cuối:
Tác giả bài #30 giỏi tư duy trừu tượng quá.
 
Em chỉ biết cộng trừ nhân chia thôi nên mò ra được cái này chắc cũng đáp ứng được yêu cầu bài #3 của thầy.
...
Code khá lủng củng.

For i = 1 To a + 2
GoSub InRaNe
Dong = "" ' đặt reset Dong ở đây khá nguy hiểm. Tại sao không đặt nó ngay dòng đầu tiên của vòng lặp?
If b = i Then i = i + 2
n = Abs(b - i)
Next i
-----

' code này chỉ bảo đảm có một dấu cách giữa các số. Nếu n >= 10 thì hình vuông lệch hết.
' ở bài #13 tôi dùng biến pW để bảo đảm mõi số có khoảng in bằng nhau. (pW = print width)

If x = y Then
Dong = Dong & n & " "
ElseIf (y - x) / 2 < n Then
Dong = n & " " & Dong & n & " "
ElseIf (y - x) / 2 < b Then
Dong = (y - x) / 2 + 1 & " " & Dong & (y - x) / 2 + 1 & " "
End If
' nếu tôi viết code này thì tôi dùng quách một mảng. Lúc in ra thì Join thành một chuỗi.
-----

Hình như code này tính chuỗi in ra n*2 - 1 + 2 - 2, tức n*2 -1 lần. Như vậy là không lợi dụng được dạng đối xứng của hình vuông.
Lưu ý tôi giải thích ở bài #27, đệ quy không hẳn là để tránh vòng lặp. Mục đích chính ở đây là để chỉ phải tính chuỗi in ra n lần.
 
Hình như code này tính chuỗi in ra n*2 - 1 + 2 - 2, tức n*2 -1 lần. Như vậy là không lợi dụng được dạng đối xứng của hình vuông.
Lưu ý tôi giải thích ở bài #27, đệ quy không hẳn là để tránh vòng lặp. Mục đích chính ở đây là để chỉ phải tính chuỗi in ra n lần.
Bài này không phải đệ quy và cũng không phải hình vuông đối xứng, nếu đối xứng thì không đáp ứng được yêu cầu bài #3 của thầy. Em mượn pw của thầy và rút gọn một số biểu thức trong vòng lặp để dễ nhìn hơn.
Mã:
Sub test4()
      Call ThuLapTrinh(10)
End Sub
Sub ThuLapTrinh(n As Long)
      Dim t As Double
      t = Timer
      Dim i As Long, j As Long
      Dim a As Long, b As Long
      Dim Dong As String
      Dim pw As String
      pw = Space(Len(CStr(n)) + 1)
      a = n * 2 - 1
      b = n
      For i = 1 To a + 2
            Dong = ""
            GoSub InRaNe
            If b = i Then i = i + 2
            n = Abs(b - i)
      Next i
      t = Timer - t
      MsgBox t
      Exit Sub
InRaNe:
      For j = 0 To b - 1
            If j = 0 Then
                  RSet pw = n
                  Dong = pw
            ElseIf j < n Then
                  RSet pw = n
                  Dong = pw & Dong & pw
            ElseIf j < b Then
                  RSet pw = j + 1
                  Dong = pw & Dong & pw
            End If
      Next j
      Debug.Print Dong
      Return
End Sub
 
Lần chỉnh sửa cuối:
Bài này không phải đệ quy và cũng không phải hình vuông đối xứng, nếu đối xứng thì không đáp ứng được yêu cầu bài #3 của thầy. Em mượn pw của thầy và rút gọn một số biểu thức trong vòng lặp để dễ nhìn hơn.
...

Vậy thì tôi nói thẳng ra. Lúc đầu tôi chỉ gợi ý để bạn tự suy nghĩ.

Bài của bạn, vì bạn dùng nguyên 1 chuỗi để in 1 dòng cho nên điều kiện "trên xuống dưới" thì thoả. Và điều kiện trái qua phải không còn áp dụng. Tương tự vậy, nếu có bạn nào dùng chỉ một chuỗi để chứa hết kết quả - mỗi dòng cách nhau bằng ký tự newline; thì điều kiện trái phải, trên xuống không áp dụng. Đã không thể áp dụng thì không thể gọi là không thoả. Bạn làm một việc mà luật pháp không thể áp dụng thì không thể coi là phạm pháp.

Bây giờ, với code trên, bạn làm thế nào để giảm thiểu số lượt tính?
 
Vậy thì tôi nói thẳng ra. Lúc đầu tôi chỉ gợi ý để bạn tự suy nghĩ.

Bài của bạn, vì bạn dùng nguyên 1 chuỗi để in 1 dòng cho nên điều kiện "trên xuống dưới" thì thoả. Và điều kiện trái qua phải không còn áp dụng. Tương tự vậy, nếu có bạn nào dùng chỉ một chuỗi để chứa hết kết quả - mỗi dòng cách nhau bằng ký tự newline; thì điều kiện trái phải, trên xuống không áp dụng. Đã không thể áp dụng thì không thể gọi là không thoả. Bạn làm một việc mà luật pháp không thể áp dụng thì không thể coi là phạm pháp.

Bây giờ, với code trên, bạn làm thế nào để giảm thiểu số lượt tính?
Theo như thầy Mỹ nói em giỏi cộng trừ nhân chia, cách giải của em chỉ quanh quẩn j<0, j<n, j<b, nên nhìn không chuyên bằng các bạn giỏi toán (em dốt toán). Yêu cầu bài #3 của thầy "lúc vẽ thì từ trái sang phải (hoặc cả dòng như một chuỗi); và xong một dòng mới được sang dòng kế tiếp", thì em nghĩ bài #35 em đã đáp ứng được rồi, nếu đề chỉ yêu cầu vẽ từ trái sang phải thì em sẽ tìm giải pháp khác.
Về hiệu suất thì em đã thử so sánh tốc độ tính toán code của thầy đưa ra ở bài #31 và code của em ở bài #35 thì em thấy code của em kết quả trả về nhanh hơn, do em tạo ra chuỗi rồi mới in, còn thầy in từng chữ từ trái sang phải. Thầy có thể cho em tham khảo dựa vào bài #31, nếu chuyển sang cả dòng như một chuỗi rồi in thì thầy code như thế nào được không?
 
...
Về hiệu suất thì em đã thử so sánh tốc độ tính toán code của thầy đưa ra ở bài #31 và code của em ở bài #35 thì em thấy code của em kết quả trả về nhanh hơn, do em tạo ra chuỗi rồi mới in, còn thầy in từng chữ từ trái sang phải. Thầy có thể cho em tham khảo dựa vào bài #31, nếu chuyển sang cả dòng như một chuỗi rồi in thì thầy code như thế nào được không?
Cái vụ timer là con đẻ của GPE. Tôi sẽ không nói đến nó ở đây.
Nếu bạn không thoát được nó thì có lẽ chủ tâm và mục đích của bạn chỉ là lập trình VBA. Chủ tâm tôi dẫn dắt các bạn muốn học lập trình là loại lập trình tổng quát, ước tính bằng độ phức tạp của thuật toán. Và chủ yếu của bài này là giảm số lần tính.

Hình vuông của đề bài rõ ràng là có tính đối xứng. Đối xứng qua trục, và đối xứng qua tâm. Đề bài bắt trái qua phải, trên xuống dưới là làm khó cho các giải pháp đối qua đường chéo và đối qua tâm. Đối qua tâm thì chỉ cần tính 1/8 hình và dùng phép chiếu copy ra chỗ còn lại; cao tay hơn thì chỉ cần tính nửa đường chéo, chiếu ra nguyên đường chéo, và dùng phép xoay, quét 90 độ để ra chỗ còn lại. Các ngôn ngữ có Lambda chơi cái chiêu này rất đẹp (tôi chưa thử nhưng nhìn lô gic thì thấy vậy).

Thuật toán trái qua phải, trên xuống dưới sẽ như sau:
- Dùng một mảng chuỗi sc( (1 to 2*n-1)
- Cộng thêm một mảng chuỗi sd(2 To n). Tức là nửa hình vuông, dưới đường trung tuyến ngang.
- Vòng lặp i1 = n to 1 step -1, dùng để in dòng và nạp mảng chuỗi
-- Vòng lặp i2 = 1 to n. Tính trị và nạp vào sc(i1) đến sc(n). Cứ mỗi trị thì chuyển thành chuỗi copy qua sc(n+1) đến sc(2*n -i1).
-- Dungn hàm join, chuyển sc thành chuỗi để in. Nếu i1 > 1 thì copy vào sd(i1)
- Vòng lặp i1 = 2 To n) in sd(i1)
Nếu dùng đệ quy thay cho vòng lặp i1 đầu thì không phải làm thêm cái vòng lặp i1 thứ hai, và chỉ cần 1 chuỗi không phải lập mảng chuỗi sd bởi vì chuỗi được chứa trong ngăn xếp (stack), không bị mất.
.
 
Lần chỉnh sửa cuối:
Web KT
Back
Top Bottom