so i have an excel file with multiple columns and rows. At the moment it looks like this:
| A | B | C | D
---------------------
1 | 1a | 1b | 1c | 1d
---------------------
2 | 2a | 2b | 2c | 2d
---------------------
3 | 3a | 3b | 3c | 3d
----------------------
How can i randomise it with VBA so that it becomes:
| A | B | C | D
---------------------
1 | 3a | 3b | 3c | 3d
---------------------
2 | 1a | 1b | 1c | 1d
---------------------
3 | 2a | 2b | 2c | 2d
----------------------
It's true that this question has many possible answers. This is probably the most lame one, but it works quite ok actually:
Add an additional column;
Then put random value in this column;
Sort by this column - that's exactly what you want!
Delete the additional column, so the trick is no visible!
Voila!
Just to give you some idea how this should look like:
Option Explicit
Public Sub Randomize()
Dim lCounter As Long
Application.ScreenUpdating = False
Columns("A:A").Insert Shift:=xlToRight
For lCounter = 1 To 5
Cells(lCounter, 1) = Rnd()
Next lCounter
With ActiveSheet.Sort
.SortFields.Add Key:=Range("A1:A5")
.SetRange Range("A1:E5")
.Apply
End With
Columns("A:A").Delete
Application.ScreenUpdating = False
End Sub
It would work on data like this one:
You can further update the code, by removing the magic numbers and improving the ranges.
I'd go like follows:
Sub ShuffleRows()
Dim vals As Variant, val As Variant
Dim iRow As Long
With Range("A1").CurrentRegion '<--| reference your contiguous range
vals = .Value '<--| store its content in an array
For Each val In GetRandomNumbers(.Rows.count) '<--| loop through referenced range shuffled rows indexes
iRow = iRow + 1 '<--| update current row to write in counter
.Rows(iRow).Value = Application.Index(vals, val, 0) '<--| write in current rows to write the random row from corresponding shuffled rows indexes
Next
End With
End Sub
Function GetRandomNumbers(ByVal n As Long) As Variant
Dim i As Long, rndN As Long, tempN As Long
ReDim randomNumbers(1 To n) As Long '<--| resize the array to the number of rows
For i = 1 To n '<--| fill it with integer numbers from 1 to nr of rows
randomNumbers(i) = i
Next
'shuffle array
Do While i > 2
i = i - 1
Randomize
rndN = Int(i * Rnd + 1)
tempN = randomNumbers(i)
randomNumbers(i) = randomNumbers(rndN)
randomNumbers(rndN) = tempN
Loop
GetRandomNumbers = randomNumbers
End Function
This is my solution:
First I have created a function to generate random numbers between a and b without repeated values:
jlqmoreno#gmail.com
Julio Jesus Luna Moreno
Option Base 1
Public Function u(a As Variant, b As Variant) As Variant
Application.Volatile
Dim k%, p As Double, flag As Boolean, x() As Variant
k = 1
flag = False
ReDim x(1)
x(1) = Application.RandBetween(a, b)
Do Until k = b - a + 1
Do While flag = False
Randomize
p = Application.RandBetween(a, b)
'Debug.Assert p = 2
resultado = Application.Match(p, x, False)
If IsError(resultado) Then
k = k + 1
ReDim Preserve x(k)
x(k) = p
flag = True
Else
flag = False
End If
Loop
flag = False
Loop
u = x
End Function
this is nessesary since i needed a funtion to create random indices with no duplicates (This was the rough part)
Then i used this function using the logic i applied here
with this function:
Public Function RNDORDER(rango As Range) As Variant
Dim z() As Variant, n%, m%, i%, j%, y() As Variant, k%
n = rango.Rows.count
m = rango.Columns.count
k = 1
ReDim x(n, m)
ReDim y(n)
y = u(1, n)
For i = 1 To n
For j = 1 To m
x(i, j) = rango(y(i), j)
Next j
Next i
RNDORDER = x
Just run this function as an array function.
Thanks!
Related
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
I have to produce a list of combinations which are indicators to fields as I am trying to produce some conditions to interrogate data.I have some code for the combinations from here.
In the example of Power/Torque/Cylinders with a combination of 1,2,4:
I need to work out the combinations of those 3 fields, so the output would be:
So essentially all combinations but not from the same 'bucket' if that makes sense?
Edit:
The number of combinations (ie 3 in the example) will change as per the link I provided. The combinations from the link determine which field to look at or use. Eg combination 123 would be the first 3 fields in the image. Combination 1,2 would be tge first 2 and 1,3 would be first and last. I have the code for that.
Now we have the combination buckets, need to work through the combinations across thise fields.
Also I am looking for guidance on how to approach the algo and not necessarily someone to do it for me 😊
As another example, if 1,2,3 were the column combination, the expected output would be:
20-0.5-200
20-0.5-300
20-0.5-400
etc
Nested Loop:
Sub allCombo(set1 As Range, set2 As Range, set3 As Range)
Dim c1, c2, c3, n
For Each c1 In set1
For Each c2 In set2
For Each c3 In set3
n = n + 1
Debug.Print "#" & n, c1, c2, c3
Next c3
Next c2
Next c1
End Sub
Example Usage:
Sub test()
allCombo [I2:I4], [J2:J3], [L2:L3]
End Sub
Result:
#1 20 0.5 4
#2 20 0.5 8
#3 20 0.8 4
#4 20 0.8 8
#5 30 0.5 4
#6 30 0.5 8
#7 30 0.8 4
#8 30 0.8 8
#9 40 0.5 4
#10 40 0.5 8
#11 40 0.8 4
#12 40 0.8 8
Here is an option that is completely dynamic:
Option Explicit
Sub MakeCombos()
Dim myCols As Variant, i As Long, j As Long, myCombos() As Variant
Dim temp() As Variant, LastRow As Long, lngCol As Long, myLens() As Long
Dim index() As Long, totalCombs As Long, count As Long
'' Prompt user for columns N.B. there is no
'' data validation, so enter with caution
myCols = Split(InputBox("Enter the columns as a comma separated list: ", "Column Combos 3000"), ",")
ReDim myCombos(0 To UBound(myCols))
ReDim index(0 To UBound(myCols))
ReDim myLens(0 To UBound(myCols))
totalCombs = 1
'' This loop is simply populating myCombos
'' with the chosen columns. We are also populating
'' myLens with the maximum length of each column
For i = 0 To UBound(myCols)
lngCol = CLng(myCols(i))
With ActiveSheet
LastRow = .Cells(.Rows.count, lngCol).End(xlUp).Row
End With
ReDim temp(0 To LastRow - 2)
For j = 2 To LastRow
temp(j - 2) = Cells(j, lngCol)
Next j
myCombos(i) = temp
myLens(i) = LastRow - 2
'' Get the total number of combinations
totalCombs = totalCombs * (LastRow - 1)
Next i
'' This is where the magic happens. Note, we
'' don't have nested for loops. Rather, we are keeping
'' up with the correct index with the appropriately
'' named array "index". When one of the indices exceeds
'' the maximum length, we reset that index and increment
'' the next index until we have enumerated every combo
While (count < totalCombs)
For j = 0 To UBound(myCols)
Cells(count + 20, j + 1) = myCombos(j)(index(j))
Next j
j = UBound(index)
index(j) = index(j) + 1
Do While index(j) > myLens(j)
index(j) = 0
j = j - 1
If j < 0 Then Exit Do
index(j) = index(j) + 1
Loop
count = count + 1
Wend
End Sub
Here is the example input:
And here is the top of the output for entering 1,2,4 at the prompt:
And here is the top of the output for entering 2,3 at the prompt:
Here is a sub that first determines the number of items in columns I, J, L and adjust the loops accordingly:
Sub SteveP()
Dim N1 As Long, N2 As Long, N3 As Long, K As Long
Dim m1 As Long, m2 As Long, m3 As Long
Dim a As Variant, b As Variant, c As Variant
N1 = Cells(Rows.Count, "I").End(xlUp).Row
N2 = Cells(Rows.Count, "J").End(xlUp).Row
N3 = Cells(Rows.Count, "L").End(xlUp).Row
K = 1
For m1 = 2 To N1
a = Cells(m1, "I")
For m2 = 2 To N2
b = Cells(m2, "J")
For m3 = 2 To N3
c = Cells(m3, "L")
Cells(K, "M") = a
Cells(K, "N") = b
Cells(K, "O") = c
K = K + 1
Next m3
Next m2
Next m1
End Sub
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
sheet1 sheet2 sheet3
---------
| |
V V * V-----
123 | A 123 | 456 C | |
* | B 123 | 789 D | |
| C 123 | 345 E | |
^ |
|-----------------
Can I look up 123 from sheet 1 to sheet 2 to return a letter (but that letter must appear in sheet 3 (C), look up the letter that is in sheet 3 and return 456? the problem is there are multiple 123's in sheet 2; I'm only used to dealing with unique numbers. Can it go A is not in sheet 3 so go to next letter until hits C. then lookup value to the left which is 456.
Thanks
Using VBA, inside a Module, write this new function:
Public Function LookFx(Sh1 As Range, Sh2 As Range, Sh3 As Range) As String
Dim BaseVal As String
Dim FoundV As Boolean
Dim SecVal As String
Application.Volatile
BaseVal = Sh1.Value
FoundV = False
For Each xx In Sh2
If xx.Value = BaseVal Then
SecVal = xx.Offset(0, -1).Value
For Each yy In Sh3
If yy.Value = SecVal Then
LookFx = yy.Offset(0, -1).Value
End If
Next
End If
Next
End Function
the value to be add in the function are:
Lets this is your data:
Sheet1:
Sheet2 :
Sheet 3:
The code below will loop through the values in sheet2 if a match is found it will loop through the values in sheet3. If a match is found it will be displayed, else it will c continue its loop in sheet.
Sub main()
Dim intValue As Integer
Dim i As Integer
Dim j As Integer
Dim strChar As String
intValue = Sheet1.Cells(1, 1)
For i = 1 To 3
If intValue = Sheet2.Cells(i, 2) Then
strChar = Sheet2.Cells(i, 1)
For j = 1 To 3
If strChar = Sheet3.Cells(j, 2) Then
MsgBox (Sheet3.Cells(j, 1))
Exit Sub
End If
Next j
End If
Next i
End Sub
I have created this sample below to illustrate and explain my need. I want to complete the number range between A and B (including A and B) with the result in column A, and keep the data from C for each new row created.
Current Table:
A | B | C
-------------------------------
0010 | 0015 | 0312
0020 | | 3500
0029 | 0031 | 4000
Desired result:
A | B | C
-------------------------------
0010 | | 0312
0011 | | 0312
0012 | | 0312
0013 | | 0312
0014 | | 0312
0015 | | 0312
0020 | | 3500
0029 | | 4000
0030 | | 4000
0031 | | 4000
Note: The result does not need to be rendered in the same sheet.
Any suggestions?
EDIT:
Someone almost solved it but removed their post and I managed to mess it up in the meantime. Can anyone spot the error for me?
Sub Macro1()
Dim num, i, j, x, lastRow
Dim sh1 As Worksheet
Dim sh2 As Worksheet
Set sh1 = ThisWorkbook.Sheets("Sheet1")
Set sh2 = ThisWorkbook.Sheets("Sheet2")
lastRow = sh1.Cells(Rows.Count, "A").End(xlUp).Row
x = 1
For i = 2 To lastRow
num = (sh1.Cells(2, 2) - sh1.Cells(2, 1))
For j = 0 To num - 1
x = x + 1
sh2.Cells(x, 1) = sh1.Cells(i, 1) + j
sh2.Cells(x, 3) = sh1.Cells(i, 3)
Next j
Next i
End Sub
When the above is fixed, the only thing that's missing the last value in column A.
When running this on my example the result in A is:
0010
0011
0012
0013
0014
It should be:
0010
0011
0012
0013
0014
0015
This is assuming your data is on a sheet called "Sheet1" in Columns A, B and C, with your data starting from row 2 downward. It will generate your output on "Sheet2":
Sub Macro1()
Dim num, i, j, x, lastRow
Dim sh1 As Worksheet
Dim sh2 As Worksheet
Set sh1 = ThisWorkbook.Sheets("Sheet1")
Set sh2 = ThisWorkbook.Sheets("Sheet2")
lastRow = sh1.Cells(Rows.Count, "A").End(xlUp).Row
x = 1
For i = 2 To lastRow
If sh1.Cells(i, 2) = "" Then
num = 0
Else
num = (sh1.Cells(i, 2) - sh1.Cells(i, 1))
End If
For j = 0 To num
x = x + 1
sh2.Cells(x, 1) = sh1.Cells(i, 1) + j
sh2.Cells(x, 3) = sh1.Cells(i, 3)
Next j
Next i
End Sub
First you cells format change to the text and you use this code
Sub test()
Dim a, rw, b As Long
rw = 1
For a = 1 To Sheet1.Cells(1048576, 1).End(xlUp).Row
If Sheet1.Cells(a, 2) = "" Then
Sheet2.Cells(rw, 1) = Sheet1.Cells(a, 1)
Sheet2.Cells(rw, 3) = Sheet1.Cells(a, 3)
rw = rw + 1
ElseIf Sheet1.Cells(a, 2) <> "" Then
For b = Val(Sheet1.Cells(a, 1)) To Val(Sheet1.Cells(a, 2))
Sheet2.Cells(rw, 1) = Format(b, "0000")
Sheet2.Cells(rw, 3) = Sheet1.Cells(a, 3)
rw = rw + 1
Next
End If
Next
End Sub