I have a sorted list of names in a single column. I would like to transform the names to 3X8 tables before printing them (printing single column would use too much paper). This is Excel. I'll copy names one by one and paste to a blank sheet.
Using numbers as an example, the resulting order should look like this:
1 9 17
2 10 18
3 11 19
4 12 20
5 13 21
6 14 22
7 15 23
8 16 24
25 33 41
26 34 42
27 35 43
........
Possible to get a general answer (n x m table)?
Below is what I have got. It's close but not quite right.
last_row = ThisWorkbook.Sheets(1).Cells(20000,1).End(xlUp).Row
For i = 1 To last_row/24 +1 Step 1
For k = 1 To 3 Step 1
For j = 1 To members_per_column Step 1
ThisWorkbook.Sheets(1).Cells( i + j + (k - 1) * 8 + (i - 1) * 16 + 1, _
name_column).Copy
Worksheets(destination_page).Cells( i + j - 1, (k - 1) +1).PasteSpecial _
Paste:=xlPasteValues
Next j
Next k
Next i
You were already close. I wrapped the code into a function so you can easily re-use it on any matrix size:
Option Explicit
Public Sub TransformIntoBlocks(ByVal MatrixRows As Long, ByVal MatrixColumns As Long, ByVal SourceRange As Range, ByVal OutputStartRange As Range)
Dim BlockStartRow As Long
BlockStartRow = 1
Dim iRowSource As Long
iRowSource = 1
Dim AmountOfBlocks As Long
AmountOfBlocks = WorksheetFunction.RoundUp(SourceRange.Rows.Count / (MatrixRows * MatrixColumns), 0)
Dim iBlock As Long
For iBlock = 1 To AmountOfBlocks
Dim iCol As Long
For iCol = 1 To MatrixColumns
Dim iRow As Long
For iRow = BlockStartRow To BlockStartRow + MatrixRows - 1
OutputStartRange.Offset(iRow - 1, iCol - 1).Value = SourceRange(iRowSource, 1).Value
iRowSource = iRowSource + 1
Next iRow
Next iCol
BlockStartRow = BlockStartRow + MatrixRows
Next iBlock
End Sub
Sub test()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
TransformIntoBlocks MatrixRows:=8, MatrixColumns:=3, SourceRange:=ws.Range("A1", ws.Cells(ws.Rows.Count, "A").End(xlUp)), OutputStartRange:=Tabelle2.Range("C1")
End Sub
Rather than going through three loops, I ended up just using one loop to write in the correct position using mod.
Seems obvious to me as the writer, but please ask questions if it's unclear- it helps the next reader.
Option Explicit
Sub ColumnSplit()
Dim input_rows As Integer
Dim output_columns As Integer
Dim output_rows As Integer
Dim i As Integer
Dim input_sheet As Worksheet
Dim output_sheet As Worksheet
Set input_sheet = Sheet1
Set output_sheet = Sheet2
'output_sheet.Cells.Clear 'optional
output_columns = 3 'Hard coded. Set to whatever you like
input_rows = input_sheet.Cells(Rows.Count, 1).End(xlUp).Row
output_rows = CInt(WorksheetFunction.Ceiling(CDbl(input_rows) / CDbl(output_columns), 1))
For i = 1 To input_rows
output_sheet.Cells( _
((i - 1) Mod output_rows) + 1 _
, (WorksheetFunction.Floor((i - 1) / output_rows, 1) Mod output_columns) + 1 _
) _
= input_sheet.Cells(i, 1) 'cells(calculate output row,calculate output column) = input value
Next i
End Sub
Related
Would appreciate any help here based on a previous question.
The code below was the original ask and finding a way in VBA to turn the original table into the output excel.
Data 4/1/2012 4/2/2012 4/3/2012 4/4/2012 4/5/2012
V 10 20 30 40 50
H 5 10 15 20 25
S 6 12 18 24 30
R 8 16 24 32 40
A 9 18 27 36 45
Output : Excel Table
V 4/1/2012 10
V 4/2/2012 20
V 4/3/2012 30
V 4/4/2012 40
V 4/5/2012 50
H 4/1/2012 5
H 4/2/2012 10
H 4/3/2012 15
H 4/4/2012 20
H 4/5/2012 25
.
.
.
A 4/1/2012 9
A 4/2/2012 18
A 4/3/2012 27
A 4/4/2012 36
A 4/5/2012 45
The answer is below and works, but I was hoping for some help in terms of adapting the code below so the output is created on a new sheet within the same workbook. What code should be added? Thank you.
Option Explicit
Sub colsToRows()
Dim ws1 As Worksheet
Dim a As Long, lr As Long, lc As Long
Dim va As Variant, vd As Variant
Dim LastRow As Long, LastCol As Long
'-- set e.g. sheet name Sheet1, starting column = B, dates starting cell = C2
Set ws1 = Sheets("Sheet1")
LastRow = ws1.Range("B" & ws1.Rows.Count).End(xlUp).Row
LastCol = ws1.Cells(Range("C2").Row, ws1.Columns.Count).End(xlToLeft).Column - 1
'--put dates into this array as it repeats for each item
vd = WorksheetFunction.Transpose(WorksheetFunction.Transpose(ws1.Range("C2").Resize(1, LastCol - 1)))
'-- titles
ws1.Range("B2").Offset(LastRow + 1) = "Item"
ws1.Range("C2").Offset(LastRow + 1) = "Dates"
ws1.Range("D2").Offset(LastRow + 1) = "Data"
'--2 is deducted as the main range is starting from B3. So B3-B1 = 2
For a = 1 To LastRow - 2
'--to get next last row
lr = Cells(Rows.Count, "B").End(xlUp).Row
'--items
va = Array(ws1.Range("B2").Offset(a).Value)
ws1.Range("B1").Offset(lr).Resize(LastCol - 1) = Application.Transpose(va)
'--dates
ws1.Range("C1").Offset(lr).Resize(UBound(vd)) = Application.Transpose(vd)
'--data
va = WorksheetFunction.Transpose(WorksheetFunction.Transpose(ws1.Range("C2").Offset(a).Resize(1, LastCol - 1)))
ws1.Range("D1").Offset(lr).Resize(UBound(va)) = Application.Transpose(va)
Next a
End Sub
This code will put the result on a new sheet.
Note, it assumes the data starts in A1 on Sheet1.
Option Explicit
Sub colsToRowsToNewSheet()
Dim ws1 As Worksheet
Dim wsNew As Worksheet
Dim arrData As Variant
Dim arrOut As Variant
Dim idxRow As Long
Dim idxCol As Long
Dim cnt As Long
'-- set e.g. sheet name Sheet1, starting column = B, dates starting cell = C2
Set ws1 = Sheets("Sheet1")
' Set ws1 = ActiveSheet
arrData = ws1.Range("A1").CurrentRegion
ReDim arrOut(1 To (UBound(arrData, 1) - 1) * (UBound(arrData, 2) - 1), 1 To 3)
For idxRow = LBound(arrData, 1) + 1 To UBound(arrData, 1)
For idxCol = LBound(arrData, 2) + 1 To UBound(arrData, 2)
cnt = cnt + 1
arrOut(cnt, 1) = arrData(idxRow, 1)
arrOut(cnt, 2) = arrData(1, idxCol)
arrOut(cnt, 3) = arrData(idxRow, idxCol)
Next idxCol
Next idxRow
Set wsNew = Sheets.Add
wsNew.Range("A1:C1").Value = Array("Data", "Date", "Item")
wsNew.Range("A2:C2").Resize(cnt).Value = arrOut
End Sub
I'm attempting to print small arrays to an Excel spreadsheet. The bulk of the code to loops n times based on the user's discretion.
The output Sub functions print correctly on the first iteration, but when the array changes on the next iteration and the sub functions move to the next line to output, they also modify the first array values in the spreadsheet from the first iteration.
Example: If I go through five iterations, and they all produce different values in their respective arrays, by the 5th iteration All five columns that have been printed are modified to be the same as the last iteration.
I'm trying to prevent the code from replacing previous values.
I've attempted the Erase function for the array inside of the big for loop, which broke the code.
For loop for iterations
Dim Iter As Integer
For Iter = 1 To number_of_iterations
Randomize
reward_present = Int((1 - 0 + 1) * Rnd + 0)
reward_string = reward_present
reward_present = 1
'Randomize whether there is a reward present or not
If reward_present = 1 Then
Dim door_probabilities() As Variant
ReDim door_probabilities(1 To number_of_doors)
Dim remainder As Double
Dim reward_door As Integer
Dim reward_door_string As String
remainder = 1
For i = 1 To (number_of_doors - 1)
door_probabilities(i) = RndDbl(0, remainder)
remainder = remainder - door_probabilities(i)
Next i
door_probabilities(number_of_doors) = remainder
'randomizing probabilities of each door
Dim max As Variant
max = door_probabilities(1)
reward_door = 0
For i = 1 To number_of_doors
If max <= door_probabilities(i) Then
max = door_probabilities(i)
reward_door = i
End If
Next i
reward_door_string = reward_door
'choosing the reward door based on probability
If number_of_doors = 3 Then
random_player_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
game_doors(random_player_choice) = 1
ArrayFillPlayer1 game_doors, Iter
'choose first player door randomly
'output here
For i = LBound(game_doors) To UBound(game_doors)
msg = msg & game_doors(i) & vbNewLine
Next i
MsgBox "Game doors player choice 1: " + msg
random_host_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
Do While random_host_choice = random_player_choice
random_host_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
Loop
If random_host_choice = reward_door Then
Do While random_host_choice = reward_door
random_host_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
Loop
End If
game_doors(random_host_choice) = 1
ArrayFillHost game_doors, Iter
'choose host door randomly
'output here
For i = LBound(game_doors) To UBound(game_doors)
msg = msg & game_doors(i) & vbNewLine
Next i
MsgBox "Game doors host choice: " + msg
random_player_choice2 = Int((number_of_doors - 1 + 1) * Rnd + 1)
Do While random_player_choice2 = random_host_choice
random_player_choice2 = Int((number_of_doors - 1 + 1) * Rnd + 1)
Loop
game_doors(random_player_choice2) = 1
'choose second player door
ArrayFillPlayer2 game_doors, Iter
For i = LBound(game_doors) To UBound(game_doors)
msg = msg & game_doors(i) & vbNewLine
Next i
ReDim game_doors(1 To number_of_doors)
End If
Sub ArrayFillPlayer1(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 3
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 1), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
Sub ArrayFillHost(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 6
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 4), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
Sub ArrayFillPlayer2(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 9
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 7), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
I expect the output of each consecutive row to be different, but they are all modified retroactively.
Kind of looks like you mean to use Resize() in your subs which fill the array to the sheet?
For example this:
Sub ArrayFillPlayer1(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 3
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 1), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
Here the line:
Set TheRange = Range(Cells(RowToWrite, 1), Cells(CellsDown, CellsAcross))
is the same as:
Set TheRange = Range( Cells(RowToWrite, 1), Range("C1") )
and that "C1" remains constant as RowToWrite increases, so each time you're filling a larger range with TempArray.
This is closer to what you want:
Sub ArrayFillPlayer1(TempArray As Variant, RowToWrite As Integer)
Range(Cells(RowToWrite, 1).Resize(1, 3).Value = TempArray
End Sub
I'm writing a macro public function for finding max of the last N non empty cells for winter months (November, December, January, February).
Here's what I got:
Public Function SuperMax_Winter(rng2 As Range, rng As Range, N As Long) As Double
Dim RngCnt, RngCnt2 As Long, i As Long, Zum As Double, j As Long
Dim ary() As Double
ReDim ary(0)
j = 0
RngCnt = rng.Count
RngCnt2 = rng2.Count
If RngCnt <> RngCnt2 Then SuperMax_Winter = "#ERROR!"
For i = RngCnt To 1 Step -1
If rng(i).Value <> "" Then
If rng2(i).Month = 11 Or rng2(i).Month = 12 Or rng2(i).Month = 1 Or rng2(i).Month = 2 Then
ary(j) = rng(i).Value
If j = N - 1 Then Exit For
ReDim Preserve ary(j + 1)
j = j + 1
End If
End If
Next i
SuperMax_Winter = Application.WorksheetFunction.Max(ary)
End Function
But I get a #VALUE! error.
I think Month should be first:
If Month(rng2(i).Value)= 11 Or Month(rng2(i).Value)= 12 Or Month(rng2(i).Value)= 1 Or Month(rng2(i).Value)= 2 Then
Hope this help.
I am new to Excel VBA and I want to calculate the distance between two atoms and make a loop to calculate it for all wanted cases
with coordinate B(i), C(i), D(i) in the Excel sheet correspond to x,y,z cartesian coordinate..
these atoms are located : One in a row (i) and the other in a row (i+5)
I write this algorithm but I cant transfer it to excel VBA
For i=4 to 1000
For j=9 to 1000
d=SQRT(POWER(B(i)-B(j),2)+ POWER(C(i)-C(j),2)+ POWER(D(i)-D(j),2))
print **d** in (P(i)) #want to print the distance **d** in a case
j=j+4 # **j** is a multiple of 4
i=i+4 # **i** is a multiple of 4
next i
Thanks, this is my first question
I think that the following should work for you:
Sub FindDistances()
Dim i As Long, j As Long
Dim r As Long, c As Long 'row and column indices for output
Dim data As Variant
Application.ScreenUpdating = False 'useful when doing a lot of writing
data = Range("B4:D1000").Value 'data is a 1-based array
c = 5 'column E
For i = 1 To UBound(data) - 5 Step 4
r = 1 'first row printed in -- adjust if need be
For j = i + 5 To UBound(data) Step 4
Cells(r, c).Value = Sqr((data(i, 1) - data(j, 1)) ^ 2 + (data(i, 2) - data(j, 2)) ^ 2 + (data(i, 3) - data(j, 3)) ^ 2)
r = r + 1
Next j
c = c + 1
Next i
Application.ScreenUpdating = True
End Sub
Something like this? In VBA, you refer to cells like Cells(row, column). Data is supposed to be located in a worksheet named Sheet1. I'm calculating each dimension separately (d1, d2, d3) just for reading simplicity. You can merge those four lines in one if you like. EDIT: reading your comments above, I add a nested loop (j).
Sub Distances()
Dim i As Integer
Dim j As Integer
Dim d1 As Double, d2 As Double, d3 As Double, d As Double
For i = 4 To 1000 Step 4 'Can't understand your data, but Step 4 tries to account for your j=j+4 and i=i+4
For j = 9 To 1000 Step 4
d1 = (Worksheets("Sheet1").Cells(i, 2) - Worksheets("Sheet1").Cells(j, 2)) ^ 2
d2 = (Worksheets("Sheet1").Cells(i, 3) - Worksheets("Sheet1").Cells(j, 3)) ^ 2
d3 = (Worksheets("Sheet1").Cells(i, 4) - Worksheets("Sheet1").Cells(j, 4)) ^ 2
d = Sqr(d1 + d2 + d3)
Worksheets("Sheet1").Cells(i, 16).Value = d
Next j
Next i
End Sub
Option Explicit
Sub AtomDistance()
'
' AtomDistance Macro1
'
'
Dim i As Integer
Dim j As Integer
Dim Distance As Double
Dim Column As String
Column = InputBox("Which column you want to print results(put a letter)?")
Dim MyCell11 As String
Dim MyCell12 As String
Dim MyCell13 As String
Dim MyCell21 As String
Dim MyCell22 As String
Dim MyCell23 As String
Dim MyCell3 As String
j = 9
For i = 4 To 12
MyCell3 = Column & i
MyCell11 = "B" & i
MyCell12 = "C" & i
MyCell13 = "D" & i
MyCell21 = "B" & j
MyCell22 = "C" & j
MyCell23 = "D" & j
Distance = (((Range(MyCell11).Value - Range(MyCell21).Value) ^ 2) + ((Range(MyCell12).Value - Range(MyCell22).Value) ^ 2) + ((Range(MyCell13).Value - Range(MyCell23).Value) ^ 2)) ^ 0.5
If i Mod 4 = 0 Or j Mod 4 = 0 Then
Range(MyCell3).Value = Distance
End If
j = j + 1
Next i
After some help on this website I am now looking for more. This was my previous post: stacking and layering boxes in excel
I am now able to make all possible combinations. However my next step would be to set some parameters. By this I mean the height and weight of the boxes. If I were to place on "Sheet2" in Column A by box names (A,B,....) Column B by weight (kg) and Column C by height (millimeters). Then on "Sheet3" I place my maximum height and maximum weight. B2 maximum weight of 30 kg and C3 maximum height of 500 mm.
How can I get my macro to check against these parameters and if they do fit them they are placed in the column as in my previous question and if it goes over my weight or height it does not bother with placing it.
Hope to hear soon :) Starting to enjoy excel!
Edit:
Box name Weight height
A 1 0.12
B 5 0.92
C 3 0.5
D 2 0.34
........etc
This is how I would place my input information. I would like this for many boxes, maybe even up to 100
as a enhancement to the previous solution
input format
(Please implement your own input/output farmat after studying my code)
<num of box> <box name 1> <box name 2> ... <box name N>
<max height> <height 1> <height 2>...
<max weight> <weight 1> <weight 2> ...
<output result 1>
<output result 2>
.
.
.
sample Input & output
3 A B C D E
7.7 3 1 1 1 2
5.5 2 1 2 3 3
A
B
AB
C
AC
BC
ABC
D
AD
BD
CD
E
AE
BE
CE
Not limited to integer, you can use floating numbers
Code:
Function stackBox()
Dim ws As Worksheet
Dim width As Long
Dim height As Long
Dim numOfBox As Long
Dim optionsA() As Variant
Dim results() As Variant
Dim str As String
Dim outputArray As Variant
Dim i As Long, j As Long
Dim currentSymbol As String
'------------------------------------new part----------------------------------------------
Dim maxHeight As Double
Dim maxWeight As Double
Dim heightarray As Variant
Dim weightarray As Variant
Dim totalHeight As Double
Dim totalWeight As Double
'------------------------------------new part----------------------------------------------
Set ws = Worksheets("Sheet1")
With ws
'clear last time's output
height = .Cells(.Rows.Count, 1).End(xlUp).row
If height > 3 Then
.Range(.Cells(4, 1), .Cells(height, 1)).ClearContents
End If
numOfBox = .Cells(1, 1).Value
width = .Cells(1, .Columns.Count).End(xlToLeft).Column
If width < 2 Then
MsgBox "Error: There's no item, please fill your item in Cell B1,C1,..."
Exit Function
End If
'------------------------------------new part----------------------------------------------
maxHeight = .Cells(2, 1).Value
maxWeight = .Cells(3, 1).Value
ReDim heightarray(1 To 1, 1 To width - 1)
ReDim weightarray(1 To 1, 1 To width - 1)
heightarray = .Range(.Cells(2, 2), .Cells(2, width)).Value
weightarray = .Range(.Cells(3, 2), .Cells(3, width)).Value
'------------------------------------new part----------------------------------------------
ReDim optionsA(0 To width - 2)
For i = 0 To width - 2
optionsA(i) = .Cells(1, i + 2).Value
Next i
GenerateCombinations optionsA, results, numOfBox
' copy the result to sheet only once
ReDim outputArray(1 To UBound(results, 1) - LBound(results, 1) + 1, 1 To 1)
Count = 0
For i = LBound(results, 1) To UBound(results, 1)
If Not IsEmpty(results(i)) Then
'rowNum = rowNum + 1
str = ""
totalHeight = 0#
totalWeight = 0#
For j = LBound(results(i), 1) To UBound(results(i), 1)
currentSymbol = results(i)(j)
str = str & currentSymbol 'results(i)(j) is the SYMBOL e.g. A, B, C
'look up box's height and weight , increment the totalHeight/totalWeight
updateParam currentSymbol, optionsA, heightarray, weightarray, totalHeight, totalWeight
Next j
If totalHeight < maxHeight And totalWeight < maxWeight Then
Count = Count + 1
outputArray(Count, 1) = str
End If
'.Cells(rowNum, 1).Value = str
End If
Next i
.Range(.Cells(4, 1), .Cells(UBound(outputArray, 1) + 3, 1)).Value = outputArray
End With
End Function
Sub updateParam(ByRef targetSymbol As String, ByRef symbolArray As Variant, ByRef heightarray As Variant, ByRef weightarray As Variant, ByRef totalHeight As Double, ByRef totalWeight As Double)
Dim i As Long
Dim index As Long
index = -1
For i = LBound(symbolArray, 1) To UBound(symbolArray, 1)
If targetSymbol = symbolArray(i) Then
index = i
Exit For
End If
Next i
If index <> -1 Then
totalHeight = totalHeight + heightarray(1, index + 1)
totalWeight = totalWeight + weightarray(1, index + 1)
End If
End Sub
Sub GenerateCombinations(ByRef AllFields() As Variant, _
ByRef Result() As Variant, ByVal numOfBox As Long)
Dim InxResultCrnt As Integer
Dim InxField As Integer
Dim InxResult As Integer
Dim i As Integer
Dim NumFields As Integer
Dim Powers() As Integer
Dim ResultCrnt() As String
NumFields = UBound(AllFields) - LBound(AllFields) + 1
ReDim Result(0 To 2 ^ NumFields - 2) ' one entry per combination
ReDim Powers(0 To NumFields - 1) ' one entry per field name
' Generate powers used for extracting bits from InxResult
For InxField = 0 To NumFields - 1
Powers(InxField) = 2 ^ InxField
Next
For InxResult = 0 To 2 ^ NumFields - 2
' Size ResultCrnt to the max number of fields per combination
' Build this loop's combination in ResultCrnt
ReDim ResultCrnt(0 To NumFields - 1)
InxResultCrnt = -1
For InxField = 0 To NumFields - 1
If ((InxResult + 1) And Powers(InxField)) <> 0 Then
' This field required in this combination
InxResultCrnt = InxResultCrnt + 1
ResultCrnt(InxResultCrnt) = AllFields(InxField)
End If
Next
If InxResultCrnt = 0 Then
Debug.Print "testing"
End If
'additional logic here
If InxResultCrnt >= numOfBox Then
Result(InxResult) = Empty
Else
' Discard unused trailing entries
ReDim Preserve ResultCrnt(0 To InxResultCrnt)
' Store this loop's combination in return array
Result(InxResult) = ResultCrnt
End If
Next
End Sub