Thắc mắc về code có liên quan đến xử lý mãng (Array)

Liên hệ QC

ndu96081631

Huyền thoại GPE
Thành viên BQT
Super Moderator
Tham gia
5/6/08
Bài viết
30,703
Được thích
53,952
Tôi có đoạn code thế này:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
  For ith = 1 To 5
    ReDim Preserve Arr(1, ith)
    Arr(1, ith) = Cells(ith, 1)
  Next ith
  Range("C1:G1").Value = Arr
End Sub
Code chạy không có vấn đề... nhưng nếu tôi sửa lại thành:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
  For ith = 1 To 5
    ReDim Preserve Arr(ith, 1)
    Arr(ith, 1) = Cells(ith, 1)
  Next ith
  Range("C1:C5").Value = Arr
End Sub
thì báo lổi Subscript out of range
Ý tôi muốn thêm các phần tử vào 1 mãng dọc ---> Tại sao nó lại không làm việc được nhỉ, trong khi với mãng ngang nó làm việc bình thường
 
(Múa rìu qua mắt thợ) Thầy thử sửa lại thế này xem:
For ith = 1 To 5
ReDim Preserve Arr(ith, 1)
Arr(ith, 1) = Cells(1, ith)
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
  For ith = 1 To 5
    ReDim Preserve Arr(ith, 1)
    Arr(ith, 1) = Cells(1,  ith)
  Next ith
  Range("C1:C5").Value = Arr
End Sub
 
Upvote 0
(Múa rìu qua mắt thợ) Thầy thử sửa lại thế này xem:
For ith = 1 To 5
ReDim Preserve Arr(ith, 1)
Arr(ith, 1) = Cells(1, ith)
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
  For ith = 1 To 5
    ReDim Preserve Arr(ith, 1)
    Arr(ith, 1) = Cells(1,  ith)
  Next ith
  Range("C1:C5").Value = Arr
End Sub
Vẫn báo lổi y chang mà
Vã lại code này có tác dụng như sau:
- Cho từng cell tại vùng A1:A5 vào mãng
- Gán nguyên mãng vào vùng C1:C5
Nói chung, nếu đã làm được với mãng ngang thì tôi có thể dùng hàm TRANSPOSE để biến nó thành mãng dọc... có điều tôi không muốn vậy ---> Muốn trực tiếp luôn!
Thắc mắc tại sao 2 code giống y chang về thuật toán, chỉ khác về chiều của mãng ---> Vậy mà nó báo lổi!!!
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Thầy ui, em đã thử lại rồi, nếu để y chang như code 1 và chỉ chỉnh
Range("C1:G1").Value = Arr
thành
Range("C1:C5").Value = Arr
Em thấy nó bình thường và không báo lỗi! Không biết có đúng ý Thầy không?
Nhưng nó lại chi cho ra 1 giá trị ban đầu Hic hic
 
Upvote 0
Thầy ui, em đã thử lại rồi, nếu để y chang như code 1 và chỉ chỉnh
Range("C1:G1").Value = Arr
thành
Range("C1:C5").Value = Arr
Em thấy nó bình thường và không báo lỗi! Không biết có đúng ý Thầy không?
Thì đúng vậy! Nhưng kết quả không như ý muốn (C1 = A1... C5=A5)
Mục đích cuối cùng của tôi là:
- Từ 1 vùng dử liệu cho trước, làm sao đưa nó vào 1 Vertical Array để từ array này ta có thể gán nó vào 1 vùng khác
(thực tế có thể phức tạp hơn, nhưng cái vướng mắc của tôi chính là chổ này)
 
Lần chỉnh sửa cuối:
Upvote 0
If you use the Preserve keyword, you can resize only the last array dimension and you can't change the number of dimensions at all. For example, if your array has only one dimension, you can resize that dimension because it is the last and only dimension. However, if your array has two or more dimensions, you can change the size of only the last dimension and still preserve the contents of the array.
Khi dùng từ khóa Preserve, bạn chỉ có thể sửa kích thước chiều cuối cùng chứ không thêm chiều cho mảng được. Thí dụ, nếu mảng chỉ có 1 chiều, bạn có thể thay đổi kích thước của chiều đó vì là chiều duy nhất. Nhưng nếu bạn có 1 mảng 2 hay nhiều chiều, bạn chỉ có thể thay 9ổi kích thước chiều cuối cùng mà vẫn giữ nguyên giá trị.

Nói vậy, chứ mà vẫn đổi được nếu Redim bằng giá trị chứ không phải biến ith:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
    ReDim Preserve Arr(5, 1)
    For ith = 1 To 5
    Arr(ith, 1) = Cells(ith, 1)
  Next ith
  Range("C1:C5").Value = Arr
End Sub

Mà nếu vậy thì đưa Redim ra ngoài vòng For và không cần Preserve nữa.
 
Lần chỉnh sửa cuối:
Upvote 0
Nói vậy, chứ mà vẫn đổi được nếu Redim bằng giá trị chứ không phải biến ith:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
    ReDim Preserve Arr(5, 1)
    For ith = 1 To 5
    Arr(ith, 1) = Cells(1,  ith)
  Next ith
  Range("C1:C5").Value = Arr
End Sub

Mà nếu vậy thì đưa Redim ra ngoài vòng For và không cần Preserve nữa.

Code này cho ra kết quả sai vị trí và mất vài dữ liệu Thầy ui!
 
Upvote 0
Khi dùng từ khóa Preserve, bạn chỉ có thể sửa kích thước chiều cuối cùng chứ không thêm chiều cho mảng được. Thí dụ, nếu mảng chỉ có 1 chiều, bạn có thể thay đổi kích thước của chiều đó vì là chiều duy nhất. Nhưng nếu bạn có 1 mảng 2 hay nhiều chiều, bạn chỉ có thể thay 9ổi kích thước chiều cuối cùng mà vẫn giữ nguyên giá trị.

Nói vậy, chứ mà vẫn đổi được nếu Redim bằng giá trị chứ không phải biến ith:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long
    ReDim Preserve Arr(5, 1)
    For ith = 1 To 5
    Arr(ith, 1) = Cells(1,  ith)
  Next ith
  Range("C1:C5").Value = Arr
End Sub
Mà nếu vậy thì đưa Redim ra ngoài vòng For và không cần Preserve nữa.
Vâng! Cái vụ đó em đã có làm qua và thành công rồi
Vấn đề ở đây là số phần tử của mãng ta chưa biết trước, dạng vầy:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long, j As Long
  For j = 1 To 5
    If Cells(j, 1) > 2 Then '<--- Nếu thỏa mãn điều kiện này mới cho vào mãng
      ith = ith + 1
      ReDim Preserve Arr(ith, 1)
      Arr(ith, 1) = Cells(j, 1).Value
    End If
  Next j
  Range("C1").Resize(ith).Value = Arr
End Sub
Tạm thời em làm vầy:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long, j As Long
  ReDim Arr(5, 1)
  For j = 1 To 5
    If Cells(j, 1) > 2 Then
      ith = ith + 1
      Arr(ith, 1) = Cells(j, 1).Value
    End If
  Next j
  Range("C1").Resize(ith).Value = Arr
End Sub
Định size cho mãng trước, với số phần tử lớn nhất có thể xảy ra
Như vậy thì mãng bị thừa! Không hay!
Hic...
 
Upvote 0
Em nghĩ vấn đề ở đây là như vầy:
Code Test1 nó chuyển từ Cột sang Hàng --> OK
Nếu bắt nó làm ngược lại ở Code Test2 thì phải lấy giá trị từ Hàng sang Cột mới đúng chứ! Đằng này ta lấy Cột bắt nó chuyển thành Cột cho nên nó lấy giá trị đầu tiên là phải rồi!!!
Vậy thì lặp lại ở Test1, lấy giá trị của Test1 tạo Test2 được không?
 
Upvote 0
Em nghĩ vấn đề ở đây là như vầy:
Code Test1 nó chuyển từ Cột sang Hàng --> OK
Nếu bắt nó làm ngược lại ở Code Test2 thì phải lấy giá trị từ Hàng sang Cột mới đúng chứ! Đằng này ta lấy Cột bắt nó chuyển thành Cột cho nên nó lấy giá trị đầu tiên là phải rồi!!!
Vậy thì lặp lại ở Test1, lấy giá trị của Test1 tạo Test2 được không?
Không phải vậy đâu bạn à! Nguyên nhân sâu xa đúng như sư phụ pmt0412 vừa nói ấy!
--------------------------
Với sư phụ:
Em xin nói thêm, trước giờ em vẫn xài theo kiểu này:
PHP:
Option Base 1
Sub Test()
  Dim Arr(), ith As Long, j As Long
  For j = 1 To 5
    If Cells(j, 1) > 2 Then
      ith = ith + 1
      ReDim Preserve Arr(ith)
      Arr(ith) = Cells(j, 1).Value
    End If
  Next j
  Range("C1").Resize(ith).Value = WorksheetFunction.Transpose(Arr)
End Sub
Hoặc
PHP:
Sub Test()
  Dim Arr(), ith As Long, j As Long
  For j = 1 To 5
    If Cells(j, 1) > 2 Then
      ith = ith + 1
      ReDim Preserve Arr(ith)
      Arr(ith) = Cells(j, 1).Value
    End If
  Next j
  Range("C1").Resize(ith).Value = Evaluate("{" & Join(Arr, ";") & "}")
End Sub
Hoặc:
PHP:
Sub Test()
  Dim Arr, ith As Long, j As Long
  Set Arr = CreateObject("Scripting.Dictionary")
  For j = 1 To 5
    If Cells(j, 1) > 2 Then
      Arr.Add j, Cells(j, 1).Value
    End If
  Next j
  Range("C1").Resize(Arr.Count).Value = WorksheetFunction.Transpose(Arr.Items)
End Sub
Từ 1 mãng ngang, biến nó thành mãng dọc (phải qua trung gian Evaluate hoặc Transpose)
Nói chung là tốt... nhưng ý định của em là loại bỏ bớt những công đoạn trung gian nào có thể, nhăm mục đích tăng tốc độ tính toán
(Em áp dụng code với vài chục ngàn dòng)
 
Lần chỉnh sửa cuối:
Upvote 0
Trời ơi, em nghĩ kỹ lại rồi, Thầy đâu cần chuyển đi chuyển lại (bởi cột với cột thì chuyển làm gì), cứ Copy rồi dán thôi, em đã chọn thử 65536 dòng rồi, chớp mắt là OK
PHP:
Sub Test2()
Range("A:A").Copy
    Range("C1").Select
    ActiveSheet.Paste
    Application.CutCopyMode = False
    Range("c1").Select
End Sub
 
Upvote 0
Trời ơi, em nghĩ kỹ lại rồi, Thầy đâu cần chuyển đi chuyển lại (bởi cột với cột thì chuyển làm gì), cứ Copy rồi dán thôi, em đã chọn thử 65536 dòng rồi, chớp mắt là OK
PHP:
Sub Test2()
Range("A:A").Copy
    Range("C1").Select
    ActiveSheet.Paste
    Application.CutCopyMode = False
    Range("c1").Select
End Sub
Ah... đây chỉ là ví dụ minh họa thôi, bài toán thực tế nó hỏng phải vậy (tôi dùng Range("A1:A5")Range("C1:C5") để minh họa ý tưởng)
Mục đích cuối cùng tôi cần là CHO CÁC PHẦN TỬ THỎA ĐIỀU KIỆN NÀO ĐÓ VÀO 1 MÃNG DỌC
Vấn đề nằm ở chổ: Cho vào mãng ngang không có vấn đề gì... thế mà với mãng dọc thì nó "cằn nhằn" ---> Thế mới tức
------------
Bạn có thể xem bài #6 tại topic này:
http://www.giaiphapexcel.com/forum/showthread.php?t=26890
Tôi đã làm xong, có điều không hài lòng lắm vì phải dùng TRANSPOSE ở cuối code (thêm công đoạn trung gian là mất thêm thời gian xử lý)
 
Lần chỉnh sửa cuối:
Upvote 0
Sorry Minh Thien 1 cái, code minh họa ở trên bị nhầm chiều nên kết quả chỉ có 1 ô:
Sai:
PHP:
Arr(ith, 1) = Cells(1,  ith)
Sửa lại:
PHP:
Arr(ith, 1) = Cells(ith, 1)

Nhưng cái ý chính vẫn là Redim (5, 1), cái này thì ndu đã hiểu (Phước đức cho ai có ndu làm đệ tử, nói ít hiểu nhiều, nói sai cũng hiểu!)

Nói rõ hơn thì là vầy:
Khi khai báo mảng ngang (1 dòng nhiều cột), chỉ cần khai báo mảng 1 chiều thế này:
PHP:
Dim Arr
ReDim Arr(i)
Vì mặc định thế.

Nhưng mảng dọc (1 cột nhiều dòng) thì phải khai báo thành mảng 2 chiều dù cho kích thước chiều thứ 2 chỉ là 1:
PHP:
Dim Arr
ReDim Arr(i, 1)

Mà mảng 2 chiều trở lên thì bị cái Preserve như đoạn trích dịch Help ở trên. Có cách là ReDim (5, 1) nhưng do điều kiện nên chưa biết trước cái con số 5. nếu cứ ReDim (5, 1) thì mảng bị dư không cần thiết.

Vậy không biết cái điều kiện thực tế có phức tạp không, nếu chỉ là >2 thì đề xuất 2 cách:

1. Dùng Application.CountIf() tính ra con số đúng để ReDim vừa đủ
2. Dùng Advanced Filter thay cho vòng lặp và mảng.

Ghi chú: nếu 60.000 dòng e rằng Filter bị lỗi, chỉ còn cách 1. CountIf của WorksheetFunction cũng nhanh lắm, không đến nỗi chậm như transpose.
 
Upvote 0
PHP:
Arr(ith, 1) = Cells(1,  ith)
Sửa lại:
PHP:
Arr(ith, 1) = Cells(ith, 1)

Yeah, Giờ này em đã hiểu, cám ơn Thầy của Thầy, Chắc bây giơ Thầy NDU cũng đã có giải pháp!
PHP:
Sub Test2()
  Dim Arr(), ith As Long
    ReDim Preserve Arr(5, 1)
    For ith = 1 To 5
    Arr(ith, 1) = Cells(ith, 1)
  Next ith
  Range("C1:C5").Value = Arr
End Sub
Nhưng em nghĩ cái cần đổi của Thầy NDU là cái này Arr(5, 1). Nhưng em tin NDU có cách giải quyết cụ thể!
 
Upvote 0
Minh họa cho việc dùng CountIf:

PHP:
Sub Test2()
Application.ScreenUpdating = False
  Dim Arr(), i As Long
  [e1] = Timer
  CCount = Application.CountIf(Range("A1:A50000"), ">20")
    ReDim Arr(CCount, 1)
    For j = 1 To 50000
        If Cells(j, 1) > 20 Then
      i = i + 1
    Arr(i, 1) = Cells(j, 1)
    End If
  Next j
  Range("d1:d" & CCount).Value = Arr
  [e2] = Timer
Application.ScreenUpdating = True
End Sub

So sánh với Transpose thì nhanh hơn (thử nghiệm với 50.000 ô).
So với ReDim(50000,1) cho dư mảng, thì "sem sem"
 
Lần chỉnh sửa cuối:
Upvote 0
Thầy PTM ơi, điều gì đã xảy ra trên máy em mà nó báo lỗi cũng ở ngay cái File ban đầu:

"Compile error: Variable not defined"

Và tô đen tại CCount= Application.CountIf(Range("A1:A50000"), ">20")

Em cũng thử đặt E1 một giá trị bất kỳ???
 
Upvote 0
Lỗi này:
"Compile error: Variable not defined"
Chưa khai báo biến, khai thêm CCount As Long là được.
Còn không thì tải file dưới đây, có bổ sung dùng Advanced Filter, bằng có 1/5 thời gian. Dùng Advanced Filter còn có cái lợi là có thể dùng nhiều điều kiện khác nhau kể cả cho nhiều field khác nhau, cái mà CountIf bó tay.

E1, E2, là dùng để tính thời gian chạy code mà? Kết quả thời gian chạy là C3, E3, G3, J3 cho những phương pháp khác nhau.
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Lỗi này:
"Compile error: Variable not defined"
Chưa khai báo biến, khai thêm CCount As Long là được.
Còn không thì tải file dưới đây, có bổ sung dùng Advanced Filter, bằng có 1/5 thời gian. Dùng Advanced Filter còn có cái lợi là có thể dùng nhiều điều kiện khác nhau kể cả cho nhiều field khác nhau, cái mà CountIf bó tay.

E1, E2, là dùng để tính thời gian chạy code mà? Kết quả thời gian chạy là C3, E3, G3, J3 cho những phương pháp khác nhau.
Vâng! Em biết Advanced Filter rất nhanh, nhưng trong trường hợp vùng điều kiện thuộc dạng công thức thì chưa chắc ---> Khi ấy quá trình lọc vẫn sẽ duyệt qua từng cell và so sánh với công thức điều kiện ---> Dẩn đến tốc độ giảm xuống đáng kể
Chắc sư phụ còn nhớ bài của viendo tại đây:
http://www.giaiphapexcel.com/forum/showthread.php?t=8770
Sư phụ thử dùng Advanced Filter xem ---> Em thấy tốc độ chậm hơn so với dùng Array
 
Upvote 0
Hiện tại có 4 cách để chọn lựa:
- Transpose
- Khai báo mảng dư
- Dùng công thức (bất kỳ công thức nào) tính ra con số để ReDim vừa đủ
- Advanced Filter

Vậy tuỳ theo điều kiện lọc thế nào, ndu chọn cách thích hợp nhất.

Riêng vấn đề so sánh giữa Advanced Filter và For:

Nếu nói rằng với điều kiện là công thức (phức tạp) thì Advanced Filter cũng so sánh từng cell giống như For, thì hãy nhớ rằng cái điều kiện (phức tạp y như vậy) cũng phải đưa vào For trong mệnh đề If để gán vào Array
 
Upvote 0
Hiện tại có 4 cách để chọn lựa:
- Transpose
- Khai báo mảng dư
- Dùng công thức (bất kỳ công thức nào) tính ra con số để ReDim vừa đủ
- Advanced Filter

Vậy tuỳ theo điều kiện lọc thế nào, ndu chọn cách thích hợp nhất.

Riêng vấn đề so sánh giữa Advanced Filter và For:

Nếu nói rằng với điều kiện là công thức (phức tạp) thì Advanced Filter cũng so sánh từng cell giống như For, thì hãy nhớ rằng cái điều kiện (phức tạp y như vậy) cũng phải đưa vào For trong mệnh đề If để gán vào Array
Em nghĩ có khác nhau đấy ---> Mấu chốt nằm ở chổ Advanced Filter làm đến đâu ra đến nấy (em chắc chắn 100% điều này)... Còn dùng Array lại khác, lúc nào xong chuyện, em gán Array vào cell luôn ---> Vì thế mà nhanh hơn
Ở topic mà viendo mở ở trên, em đã thử bằng Advanced Filter và thấy nó chậm hơn so với dùng For ---> Mặc khác khi theo dỏi quá trình, em thấy khi dùng Advanced Filter, dử liệu nó cứ "trải" ra từng cell một (chứng tỏ làm đến đâu nó ra kết quả đến nấy)
Khi dùng For thì em thấy Array nhanh hơn so với cách làm đến đâu ra kết quả đến nấy
Sau khi làm đủ hết các kiểu, em quyết định dùng Array và gặp trở ngại khi Resize số phần tử như em đã nói ở bài đầu ---> Chắc phải dùng công thức tính số phần tử vừa đủ hoặc ReDim dư ra như sư phụ đã nói, em nghĩ là giải pháp tương đối ngon lành nhất
 
Upvote 0
Web KT

Bài viết mới nhất

Back
Top Bottom