Looping through cells in Excel in two dimensions VBA - excel-2007

I'm trying to loop through a set of cells using VBA in a worksheet and check if they contain any data.
My spreadsheet is like:
__A_____B_____C_____D____E_____F____G
1| 3122 -1 -1 3243
2| -1 -1 -1 3243 1 1 1
3| -1 -1 -1 3255 1
5| 4232 2132 -1 3259
6| 7544 1333 324 3259
7| -1 -1 -1 3259 1 2 1
8| -1 -1 -1 3259 1 1 1
9| -1 -1 -1 3267
10| 2121 222 -1 3267
I want to get rid of the rows that don't have any data in columns E F and G but I'm not sure how to loop through the rows and columns. I have seen many instructions for looping down a column but I can't find anything on how to loop in two dimensions checking the cells for data.
Thanks

The basic idea of looping over rows and columns is that you need two for loops.
The first loops over rows, the second over columns.
I don't use VBA enough to remember how rows get deleted, but if you loop backwards (as in the code below) you should never lose track of which row you're deleting.
The following code should work for your purposes (although it begs for refactoring!):
Edit: thanks to barrowc for the correction.
Sub remove_some_rows()
Dim i As Long
Dim j As Long
Dim current_cell As Object
Dim beg_row As Long
Dim end_row As Long
Dim beg_col As Long
Dim end_col As Long
beg_row = 1
end_row = 10
beg_col = 1
end_col = 7
Dim empty_col_counter As Integer
For i = end_row To beg_row Step -1
empty_col_counter = 0
For j = end_col To beg_col Step -1
Set current_cell = ThisWorkbook.ActiveSheet.Cells(i, j)
If j > 4 And current_cell.Value = "" Then
empty_col_counter = empty_col_counter + 1
End If
Next j
If empty_col_counter = 3 Then
current_cell.EntireRow.Select
Selection.Delete
End If
Next i
End Sub

This should work:
Sub main()
Dim maxRow As Integer
Dim currentRow As Integer
With Worksheets("Sheet1")
maxRow = .Range("A1").CurrentRegion.Rows.Count
Dim i As Integer
' Start at the bottom and work upwards
For i = maxRow To 1 Step -1
' 5 represents column E, 6 is column F and 7 is column G
If (.Cells(i, 5).Value = "" And .Cells(i, 6).Value = "" And _
.Cells(i, 7).Value = "") Then
.Rows(i).Delete
End If
Next i
End With
End Sub
Because there are only three columns to check, it's easy to just use And to join the three checks together. In a more complex situation, a nested For loop as in Adam Bernier's answer would be better

Related

Generate combinations of 0s and 1s

I have two rows of cells: A1, B1, C1 and A2, B2, C2. In each of these 2 rows there can only be a single 1, with the two other cells set as 0. Here is one example occurrence:
A B C
+---+---+---+
1 | 1 | 0 | 0 |
+---+---+---+
2 | 0 | 1 | 0 |
+---+---+---+
I'm trying to iterate over all 9 possible combinations: 3 possibilities for the first row times 3 possibilities for the second. How would I go about doing this?
Here is one way:
Function delta(i As Long, n As Long) As Variant
'returns an array of length n
'consisting of all 0 with one 1
'at index i
Dim A As Variant
ReDim A(1 To n) As Long
A(i) = 1
delta = A
End Function
Sub IterateOver()
Dim i As Long, j As Long
For i = 1 To 3
Range("A1:C1").Value = delta(i, 3)
For j = 1 To 3
Range("A2:C2").Value = delta(j, 3)
'stub for real code:
MsgBox "Continue?", vbQuestion, "Test"
Next j
Next i
End Sub
This will generate all 8 combinations of 0 and 1 for 3 columns
Dim i As Integer
For i = 1 To 2^3
Range("A" & i) = (i-1) % 2
Range("B" & i) = Int((Range("A" & i))/2) % 2
Range("C" & i) = Int((Range("B" & i))/2) % 2
Next i
Try to extend it yourself

How to copy every row except every nth

In excel I would like to copy the date from one sheet to another one using macro in a way that it will copy everything until row 9, then it will skip row 10 and copy row 11 and 12, and then skip one again.
So it should not copy row 10,13,16,19, etc..
I have the following code
Dim i As Integer
i = 9
J = 1
K = 9
Do While i < 5000
If J = 3 Then
J = 0
Sheets("sheet1").Select
Rows(i).Select
Selection.Copy
Sheets("sheet2").Select
Cells(K, 1).Select
ActiveSheet.Paste
K = K + 1
End If
J = J + 1
i = i + 1
Loop
This code is copying everything till the 8th row and then every 3rd, can somebody help me how to modify that code?
Fastest way will be to Copy >> Paste the entire rows once, according to your criteria.
You can achieve it by merging all rows that needs to be copies to a Range object, in my code it's CopyRng, and you do that by using Application.Union.
Code
Option Explicit
Sub CopyCertailRows()
Dim i As Long
Dim CopyRng As Range
Application.ScreenUpdating = False
With Sheets("sheet1")
' first add the first 8 rows to the copied range
Set CopyRng = .Rows("1:8")
For i = 9 To 5000
If (i / 3) - Int(i / 3) <> 0 Then ' don't add to copied range the rows that divide by 3 without a remainder
Set CopyRng = Application.Union(CopyRng, .Rows(i))
End If
Next i
End With
' copy >> paste in 1- line
CopyRng.Copy Destination:=Sheets("sheet2").Range("A9")
Application.ScreenUpdating = True
End Sub
You could simplify this massively by using If i < 10 Or (i - 1) Mod 3 <> 0 Then... which will select the rows you're interested in. Like so:
Dim i As Integer, j As Integer
j = 0
Dim sourceSht As Worksheet
Dim destSht As Worksheet
Set sourceSht = Sheets("Sheet1")
Set destSht = Sheets("Sheet2")
For i = 1 To 5000
If i < 10 Or (i - 1) Mod 3 <> 0 Then
j = j + 1
sourceSht.Rows(i).Copy destSht.Rows(j)
End If
Next
Personally, I'd turn screen updating and calculations off before running this and enable them again after to reduce the time needed to perform the loop.
Also, as MichaƂ suggests, unless your dataset happens to be exactly 5,000 rows, you might want to 'find' the last row of data before starting to further reduce the time needed.
All necessary comments in code:
'declare all variables, be consistent with lower/uppercases, use Long instead of Integeer (its stored as long anyway)
'use meaningful variable names
Dim i As Long, copyUntil As Long, currentRow As Long
copyUntil = 9
currentRow = 1
'copy all rows until we reach 9th row
For i = 1 To copyUntil
Sheets("sheet1").Rows(i).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Next
'now we will takes steps by 3, on every loop we will copy i-th row and next one, third will be omitted
'we also use currentRow variable to avoid empty rows in sheet2
'also, 5000 seems wrong, I'd recommend to determine last row, until which we will loop
'last row is often determined like Cells(Rows.Count, 1).End(xlUp).Row
For i = copyUntil + 2 To 5000 Step 3
Sheets("sheet1").Rows(i).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Sheets("sheet1").Rows(i + 1).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Next
This code will only paste values. Let me know if any questions or if you really, really need the formatting I can tweak it.
Sub DoCopy()
'This code is pretty much specifit to your request/question, it will copy 1-9, skip 10, 13, 16....
'i for the loop, x for the row that will not be added, y to paste on the second sheet
Dim i, x, y As Long, divn As Integer
For i = 1 To 5000
If i < 10 Then
y = y + 1
Sheets("Sheet1").Rows(i).Copy
Sheets("Sheet2").Range("A" & y).PasteSpecial ''Paste values only
ElseIf i >= 10 Then
x = i - 10
If x Mod 3 <> 0 Then
y = y + 1
Sheets("Sheet1").Rows(i).Copy
Sheets("Sheet2").Range("A" & y).PasteSpecial ''Paste values only
Else
'Do nothing
End If
End If
Next i
End Sub

Excel count occurrences of a value

I have been given data which is associated by postal code.
I need to count the number of occurrences of certain values in certain columns. Sample data is below.
Survey# PostCode
1 2136
1 2136
2 136
2 2136
1 2137
2 2137
I would like to take this data and produce this type of excel workbook:
Postal Code Survey #1 Survey #2 Survey #3 Survey #4
2136 2 2 0 0
2137 1 1 0 0
I have tried to write code to produce this by using a while loop and the following code. The code works without the while loop.
If you comment out the while look it works, however, I have to select the postal codes I want and then run the macro... it works well when I do this, it adds up all the occurrences of the 1,2,3, or 4 and then places these count values in the appropriate columns.
What I want is for it to run through the column add up all of the occurrences with one postal code (2136), stop and add in the count to the columns. Then begin again counting the occurrences of the next postal code until it gets to the next one and then stop - add in counts.
Sub sort_PC_CPE()
Dim cell As Object
Dim count As Integer
Dim count1 As Integer
Dim count2 As Integer
Dim SV1Count As Integer
Dim SV2Count As Integer
Dim SV3Count As Integer
Dim SV4Count As Integer
Dim ofset As Integer
Dim newVal As Object
'Dim origvalue As Object
count = 0
count1 = 0
count2 = 0
SV1Count = 0
SV2Count = 0
SV3Count = 0
SV4Count = 0
ofset = -1
' if you use the for each cell... and then highlight the cells with the same postal code, this works (also commenting out the while loop)
'For Each cell In Selection
While cell.Offset(ofset, 0).Value = cell.Value
cell.Offset(ofset, 0).Select
ofset = ofset - 1
Wend
'the following works well so long as you manually highlight the cells you want.
If cell.Offset(0, -3).Value = 1 Then
SV1Count = SV1Count + 1
cell.Offset(-count, 1).Value = SV1Count
count = count + 1
ElseIf cell.Offset(0, -3).Value = 2 Then
SV2Count = SV2Count + 1
cell.Offset(-count, 2).Value = SV2Count
count = count + 1
ElseIf cell.Offset(0, -3).Value = 3 Then
SV3Count = SV3Count + 1
cell.Offset(-count, 3).Value = SV3Count
count = count + 1
ElseIf cell.Offset(0, -3).Value = 4 Then
SV4Count = SV4Count + 1
cell.Offset(-count, 4).Value = SV4Count
count = count + 1
End If
End Sub
Why not just use a countifs?
I dropped your sample into a new sheet in column A and B, in column D I put 2136 in row 1 and 2137 in row 2. If you have a large number of postcodes, copy the whole column from the source and paste into column D then click Data and remove Duplicates to get your distinct list.
E1 is this formula:
=COUNTIFS($A:$A,COLUMN(E1)-4,$B:$B,$D1)
Drag across 4 columns and then down 2 rows
results:
2136 2 2 0 0
2137 1 1 0 0

Creating a unique entry for each line item in Excel

I need help in creating a macro in Excel wherein it grabs a certain cell and copies the entire row x number of times depending on the cell's contents.
To make it clear, let's say I have 2 rows:
| Order # | Item | Qty |
| 30001 | bag | 3 |
| 30002 | pen | 1 |
What I want the macro to do is grab the number under the Qty column and copy the entire row and insert a new line with the exact same contents under it. The number of times it does this depends on the number in the Qty cell. Also, it appends a three digit number in the Order # cell to make it a unique reference point. What the end-result should be:
| Order # | Item | Qty |
| 30001-001 | bag | 1 |
| 30001-002 | bag | 1 |
| 30001-003 | bag | 1 |
| 30002-001 | pen | 1 |
It's hard to explain it here but I hope you get the point. Thanks in advance, gurus!
The following code supports blank lines in the middle of the data.
If Qty = 0, it won't write the Item in the output table.
Please insert at least 1 row of data, because it won't work if there is no data :)
Option Explicit
Sub caller()
' Header at Row 1:
' "A1" = Order
' "B1" = Item
' "C1" = Qty
'
' Input Data starts at Row 2, in "Sheet1"
'
' Output Data starts at Row 2, in "Sheet2"
'
' Sheets must be manually created prior to running this program
Call makeTheThing(2, "Sheet1", "Sheet2")
End Sub
Sub makeTheThing(lStartRow As Long, sSheetSource As String, sSheetDestination As String)
Dim c As Range
Dim rOrder As Range
Dim sOrder() As String
Dim sItem() As String
Dim vQty As Variant
Dim sResult() As String
Dim i As Long
' Reads
With ThisWorkbook.Sheets(sSheetSource)
Set rOrder = .Range(.Cells(lStartRow, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 1)) ' It will work if there are blank lines in the middle!
i = rOrder.Rows.Count
ReDim sOrder(1 To i)
ReDim sItem(1 To i)
ReDim vQty(1 To i)
i = 1
For Each c In rOrder
sOrder(i) = Trim(c.Text)
sItem(i) = Trim(c.Offset(0, 1).Text)
vQty(i) = c.Offset(0, 2).Value
i = i + 1
Next c
End With
' Processes
sResult = processData(sOrder, sItem, vQty)
' Writes
ThisWorkbook.Sheets(sSheetDestination).Range("A" & lStartRow).Resize(UBound(sResult, 1), UBound(sResult, 2)).Value = sResult
End Sub
Function processData(sOrder() As String, sItem() As String, vQty As Variant) As String()
Dim i As Long
Dim j As Long
Dim k As Long
Dim sResult() As String
j = WorksheetFunction.Sum(vQty) ' That's why vQty had to be Variant!
ReDim sResult(0 To j, 1 To 3)
k = 0
For i = 1 To UBound(sOrder)
For j = 1 To vQty(i)
sResult(k, 1) = sOrder(i) & "-" & Format(j, "000")
sResult(k, 2) = sItem(i)
sResult(k, 3) = "1"
k = k + 1
Next j
Next i
processData = sResult
End Function
I hope it helps you. I had fun making it!
One way: Walk down the qty column inserting as needed then jumping to the next original row;
Sub unwind()
Dim rowCount As Long, cell As Range, order As String, i As Long, r As Long
Set cell = Range("C1")
rowCount = Range("C" & rows.Count).End(xlUp).Row
For i = 1 To rowCount
order = cell.Offset(0, -2).Value
For r = 0 To cell.Value - 1
If (r > 0) Then cell.Offset(r).EntireRow.Insert
cell.Offset(r, 0).Value = 1
cell.Offset(r, -1).Value = cell.Offset(0, -1).Value
cell.Offset(r, -2).Value = order & "-" & Format$(r + 1, "000")
Next
Set cell = cell.Offset(r, 0)
Next
End Sub

Excel Loop through list,transpose and create a matrix based on cell content

I am receiving a large file 500k+ lines but all the content is in column A. I need to run a macro that will transpose the data into matrix form but will only create a new row when it finds "KEY*" in the ActiveCell. For example:
| KEY 4759839 | asljhk | 35049 | | sklahksdjf|
| KEY 359 | skj | 487 |y| 2985789 |
The above data in my file would originally look like this in column A:
KEY 4759839
asljhk
35049
sklahksdjf
KEY 359
skj
487
y
2985789
Considerations:
Blank cells need to be transposed as well, so the macro cant stop based on emptyCell
The number of cells between KEY's is not constant so it actually needs to read the cell to know if it should create a new row
It can either stop based on say 20 empty cells in a row or prompt for a max row number
(Optional) It would be nice if there was some sort of visual indicator for the last item in a row so that its possible to tell if the last item(s) were blank cells
I searched around and found a macro that had the same general theme but it went based on every 6 lines and I did not know enough to try to modify it for my case. But in case it helps here it is:
Sub kTest()
Dim a, w(), i As Long, j As Long, c As Integer
a = Range([a1], [a500000].End(xlUp))
ReDim w(1 To UBound(a, 1), 1 To 6)
j = 1
For i = 1 To UBound(a, 1)
c = 1 + (i - 1) Mod 6: w(j, c) = a(i, 1)
If c = 6 Then j = j + 1
Next i
[c1].Resize(j, 6) = w
End Sub
I would greatly appreciate any help you can give me!
This works with the sample data you provided in your question - it outputs the result in a table starting in B1. It runs in less than one second for 500k rows on my machine.
Sub kTest()
Dim originalData As Variant
Dim result As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim countKeys As Long
Dim countColumns As Long
Dim maxColumns As Long
originalData = Range([a1], [a500000].End(xlUp))
countKeys = 0
maxColumns = 0
'Calculate the number of lines and columns that will be required
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
countKeys = countKeys + 1
maxColumns = IIf(countColumns > maxColumns, countColumns, maxColumns)
countColumns = 1
Else
countColumns = countColumns + 1
End If
Next i
'Create the resulting array
ReDim result(1 To countKeys, 1 To maxColumns) As Variant
j = 0
k = 1
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
j = j + 1
k = 1
Else
k = k + 1
End If
result(j, k) = originalData(i, 1)
Next i
With ActiveSheet
.Cells(1, 2).Resize(UBound(result, 1), UBound(result, 2)) = result
End With
End Sub
Tested and works:
Sub test()
Row = 0
col = 1
'Find the last not empty cell by selecting the bottom cell and moving up
Max = Range("A650000").End(xlUp).Row 'Or whatever the last allowed row number is
'loop through the data
For i = 1 To Max
'Check if the left 3 characters of the cell are "KEY" and start a new row if they are
If (Left(Range("A" & i).Value, 3) = "KEY") Then
Row = Row + 1
col = 1
End If
Cells(Row, col).Value = Range("A" & i).Value
If (i > Row) Then
Range("A" & i).Value = ""
End If
col = col + 1
Next i
End Sub