Please see the image attached -
My requirement is -
"If status null and Ref No. not unique then
check value2. If value2 not present, check value1 and take average
Example: For ref number = 1, calculated value is (50+10)/2 = 30 "
"if status is selected or Ref no is unique then
copy from value2, if not present then copy from value1
Example: For Ref No 3, value is 100 and for Ref No 4, value is 20
Total value= 100+30+20 = 150
My attempt
For I = 2 To lrow 'sheets all have headers that are 2 rows
'unique
If Application.WorksheetFunction.CountIf(ws.Range("A" & fRow, "A" & lrow), ws.Range("A" & I)) = 1 Then
If (ws.Range("AW" & I) <> "") Then 'AW has value2
calc = calc + ws.Range("AW" & I).Value
Else: calc = calc + ws.Range("AV" & I).Value 'AV has value1
End If
'not unique
Else
'selected
If ws.Range("AY" & I) = "Selected" Then 'AY has status (Selected/Null)
If (ws.Range("AW" & I) <> "") Then
calc = calc + ws.Range("AW" & I).Value
Else: calc = calc + ws.Range("AV" & I).Value
End If
'not selected
Else
If (ws.Range("AW" & I) <> "") Then
calc1 = calc1 + ws.Range("AW" & I).Value
Else: calc1 = calc1 + ws.Range("AV" & I).Value
End If
calc1 = calc1/Application.WorksheetFunction.CountIf(ws.Range("A" & fRow, "A" & lrow), ws.Range("A" & I))
End If
End If
My problem is -
Getting the Ref No 3 twice in my logic.
Not able to calculate the correct average.
How can I get the correct output? Thanks.
Using a SQL statement against the worksheet
If I understand your requirements, they are as follows:
For each Ref no, you want
the average of
value2 if it exists, otherwise value1
where the status is selected, or
there is no status = selected for this Ref no
I would open an ADODB Recordset against the data, with the following SQL:
SELECT [Ref no], Avg(Iif(value2 IS NOT NULL, value2, value1)) AS Result
FROM Sheet1
LEFT JOIN (
SELECT DISTINCT [Ref No]
FROM Sheet1
WHERE status = "selected"
) t1 ON Sheet1.[Ref no] = t1.[Ref no]
WHERE Sheet1.status="selected" OR t1.[Ref no] IS NULL
GROUP BY [Ref no]
Using nested Scripting.Dictionary
If SQL is not your thing, then you could something like the following:
'Define names for the columns; much easier to read row(RefNo) then arr(0)
Const refNo = 1
Const status = 3
Const value1 = 5
Const value2 = 6
'For each RefNo, we have to store 3 pieces of information:
' whether any of the rows are selected
' the sum of the values
' the count of the values
Dim aggregates As New Scripting.Dictionary
Dim arr() As Variant
arr = Sheet1.UsedRange.Value
Dim maxRow As Long
maxRow = UBound(arr, 1)
Dim i As Long
For i = 2 To maxRow 'exclude the column headers in the first row
Dim row() As Variant
row = GetRow(arr, i)
'Get the current value of the row
Dim currentValue As Integer
currentValue = row(value1)
If row(value2) <> Empty Then currentValue = row(value2)
'Ensures the dictionary always has a record corresponding to the RefNo
If Not aggregates.Exists(row(refNo)) Then Set aggregates(row(refNo)) = InitDictionary
Dim hasPreviousSelected As Boolean
hasPreviousSelected = aggregates(row(refNo))("selected")
If row(status) = "selected" Then
If Not hasPreviousSelected Then
'throw away any previous sum and count; they are from unselected rows
Set aggregates(row(refNo)) = InitDictionary(True)
End If
End If
'only include currently seleced refNos, or refNos which weren't previously selected,
If row(status) = "selected" Or Not hasPreviousSelected Then
aggregates(row(refNo))("sum") = aggregates(row(refNo))("sum") + currentValue
aggregates(row(refNo))("count") = aggregates(row(refNo))("count") + 1
End If
Next
Dim key As Variant
For Each key In aggregates
Debug.Print key, aggregates(key)("sum") / aggregates(key)("count")
Next
with the following two helper functions:
Function GetRow(arr() As Variant, rowIndex As Long) As Variant()
Dim ret() As Variant
Dim lowerbound As Long, upperbound As Long
lowerbound = LBound(arr, 2)
upperbound = UBound(arr, 2)
ReDim ret(1 To UBound(arr, 2))
Dim i As Long
For i = lowerbound To upperbound
ret(i) = arr(rowIndex, i)
Next
GetRow = ret
End Function
Function InitDictionary(Optional selected As Boolean = False) As Scripting.Dictionary
Set InitDictionary = New Scripting.Dictionary
InitDictionary.Add "selected", selected
InitDictionary.Add "sum", 0
InitDictionary.Add "count", 0
End Function
Explanation of SQL
For each Ref no, you want
Group the records by Ref no, using the GROUP BY clause
the average of
We'll return both the Ref no and the average -- SELECT [Ref no], Avg(...)
value2 if it exists, otherwise value1
Iif(value2 IS NOT NULL, value2, value1)
where the status is selected, or
WHERE Sheet1.status="selected" OR
there is no status = selected for this Ref no
We get a list of (unique -- DISTINCT) Ref nos that have status = "selected":
SELECT DISTINCT [Ref No]
FROM Sheet1
WHERE status = "selected"
and give it a name (AS t1) so we can refer to it separately from the main list (Sheet1)
Then we connect, or join (JOIN) that sublist to the main list, where the [Ref no] is the same in both (ON Sheet1.[Ref no] = t1.[Ref no]).
A simple JOIN is an INNER JOIN, where the records on both sides of the connection have to match. What we want in this case, is the records on the main list which do not match the records in the sublist. In order to see such records, we can use a LEFT JOIN, which displays all the records on the left side, and only those records on the right side that match.
We can then filter out the records that do match, using OR t1.[Ref no] IS NULL.
There must be a more concise way, but I think this does what you want. It is based on your example, so data in A1:F6 so will need amending.
Sub x()
Dim v2() As Variant, v1, i As Long, n As Long, d As Double
v1 = Sheet1.Range("A1:F6").Value
ReDim v2(1 To UBound(v1, 1), 1 To 5) 'ref/count/null/value null/value selected
With CreateObject("Scripting.Dictionary")
For i = 2 To UBound(v1, 1)
If Not .Exists(v1(i, 1)) Then
n = n + 1
v2(n, 1) = v1(i, 1)
v2(n, 2) = v2(n, 2) + 1
If v1(i, 3) = "" Then
v2(n, 3) = v2(n, 3) + 1
v2(n, 4) = IIf(v1(i, 6) = "", v1(i, 5), v1(i, 6))
ElseIf v1(i, 3) = "selected" Then
v2(n, 5) = IIf(v1(i, 6) = "", v1(i, 5), v1(i, 6))
End If
.Add v1(i, 1), n
ElseIf .Exists(v1(i, 1)) Then
v2(.Item(v1(i, 1)), 2) = v2(.Item(v1(i, 1)), 2) + 1
If v1(i, 3) = "" Then
v2(.Item(v1(i, 1)), 3) = v2(.Item(v1(i, 1)), 3) + 1
If v1(i, 6) = "" Then
v2(.Item(v1(i, 1)), 4) = v2(.Item(v1(i, 1)), 4) + v1(i, 5)
Else
v2(.Item(v1(i, 1)), 4) = v2(.Item(v1(i, 1)), 4) + v1(i, 6)
End If
Else
If v1(i, 6) = "" Then
v2(.Item(v1(i, 1)), 5) = v2(.Item(v1(i, 1)), 5) + v1(i, 5)
Else
v2(.Item(v1(i, 1)), 5) = v2(.Item(v1(i, 1)), 5) + v1(i, 6)
End If
End If
End If
Next i
End With
For i = LBound(v2, 1) To UBound(v2, 1)
If v2(i, 2) > 1 And v2(i, 3) = v2(i, 2) Then
d = d + v2(i, 4) / v2(i, 2)
End If
If v2(i, 2) > 1 And v2(i, 3) < v2(i, 2) Then
d = d + v2(i, 5) / (v2(i, 2) - v2(i, 3))
End If
If v2(i, 2) = 1 And v2(i, 3) = v2(i, 2) Then
d = d + v2(i, 4) / v2(i, 2)
End If
Next i
MsgBox "Total = " & d
End Sub
Related
i would like to split the cells into TextA, TextB and TextC after "." and sort by the Text genre.
I also tried this:
Sub split_By_Text()
Set sh1 = ThisWorkbook.Sheets(1)
Set sh2 = ThisWorkbook.Sheets(2)
lrow1 = sh1.Range("A65356").End(xlUp).Row
For j = 2 To lrow1
splitVals = Split(sh1.Cells(j, 2), ".")
totalVals = UBound(splitVals)
For i = LBound(splitVals) To UBound(splitVals)
lrow2 = sh2.Range("B65356").End(xlUp).Row
lrow3 = sh2.Range("A65356").End(xlUp).Row
sh2.Cells(lrow3 + 1, 1) = sh1.Cells(j, 1)
'Debug.Print sh1.Cells(j, 1)
sh2.Cells(lrow2 + 1, 2) = splitVals(i)
'Debug.Print splitVals(i)
Next i
Next j
sh2.Activate
sh2.Range("A1") = "Drink ID"
sh2.Range("B1") = "Recipe_data"
sh2.Range("C1") = "Volume"
End Sub
But when i have only one sentence excel also add a line.
THX
Input:
Output:
Rearrange data to be split due to genre
Demonstrate an approach via array assignment and using the advanced restructuring features of the Application.Index() function:
Sub ReArrange()
Const GENRE& = 1, ID& = 2, TXT& = 5, TXTA& = 6, TXTB& = 7, TXTC& = 8 ' columns in variant array v2
With Sheet1 ' source sheet's CodeName (!)
' [0] define data range
Dim v, rng As Range, lastRow&
lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set rng = .Range("A1:F" & lastRow)
' [1] get data
v = rng
' [2] rearrange array rows & columns (inserting 2 new columns)
v = Application.Index(v, _
Application.Transpose(getRows(v)), _
Array(0, 1, 2, 3, 0, 4, 5, 6))
v(1, GENRE) = "Genre": v(1, TXT) = "Text" ' renew headers
' [3] Fill in genre & tokens
Dim i&, ii&, cnt& ' item counters
Dim a&, b&, c& ' split item boundaries
For i = 2 To UBound(v) ' loop through v2
If v(i, ID) <> v(i - 1, ID) Then
cnt = 0: ii = 0
a = UBound(Split(v(i, TXTA), ".")) ' items TextA
b = UBound(Split(v(i, TXTB), ".")) ' items TextB
c = UBound(Split(v(i, TXTC), ".")) ' items TextC
End If
cnt = cnt + 1: ii = ii + 1 ' increment id and genre counters
Select Case cnt
Case Is <= a: v(i, GENRE) = "A"
v(i, GENRE) = "A": v(i, TXT) = Split(v(i, TXTA), ".")(ii - 1): If ii = a Then ii = 0
Case Is <= a + b
v(i, GENRE) = "B": v(i, TXT) = Split(v(i, TXTB), ".")(ii - 1): If ii = b Then ii = 0
Case Is <= a + b + c
v(i, GENRE) = "C": v(i, TXT) = Split(v(i, TXTC), ".")(ii - 1): If ii = c Then ii = 0
End Select
Next i
End With
' [4] write results back whereever you want (reducing array by 3 temporary columns)
Sheet2.Range("A1").Resize(UBound(v), UBound(v, 2) - 3) = v
End Sub
Helper function getRows()
Function getRows(arr) As Variant()
' Purpose: return an array of n-times repeated row numbers (based on number of splits)
Dim i&, ii&, j&, cnt&
Dim tmp(), tokens
ReDim tmp(0 To UBound(arr) * 10)
tmp(cnt) = 1: cnt = cnt + 1 ' one title row equals row no 1; increment new rows counter
For i = 2 To UBound(arr)
For j = 4 To 6 ' D:F
tokens = Split(arr(i, j), ".") ' upper boundary minus one because of right side point
For ii = LBound(tokens) To UBound(tokens) - 1
tmp(cnt) = i ' input row number as often as necessary
cnt = cnt + 1 ' increment counter
Next ii
Next
Next i
ReDim Preserve tmp(0 To cnt - 1) ' resize array to actual item size
getRows = tmp ' return function result array
'Debug.Print Join(tmp, ",") ' Array(1,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,5,5,5,5,5,5,6,6,6,6,6,6)
End Function
I have 3 workers.
I need to make assembly line balancing.
There are 10 operations of model.
You can see the time of operations for all workers in the chart below. They have different abilities.
So I need to share all operations between 3 workers.
so what I need:
Worker and operations of model is changeable.
20 worker-25 operations
18 worker-40 operations
19 worker-75 operations
...
So I need to define parameters for all i. Maybe need to use a function?
Sub rapor_calistir()
Range("q1") = Now()
Sheets("Rapor").Range("A2:Z1048576").ClearContents
a = 2: worker1 = 0: worker2 = 0: worker3 = 0
For i1 = 1 To 3
For i2 = 1 To 3
For i3 = 1 To 3
For i4 = 1 To 3
For i5 = 1 To 3
For i6 = 1 To 3
For i7 = 1 To 3
For i8 = 1 To 3
For i9 = 1 To 3
Sheets("Rapor").Cells(a, 1) = a - 1
Sheets("Rapor").Cells(a, 2) = i1
Sheets("Rapor").Cells(a, 3) = i2
Sheets("Rapor").Cells(a, 4) = i3
Sheets("Rapor").Cells(a, 5) = i4
Sheets("Rapor").Cells(a, 6) = i5
Sheets("Rapor").Cells(a, 7) = i6
Sheets("Rapor").Cells(a, 8) = i7
Sheets("Rapor").Cells(a, 9) = i8
Sheets("Rapor").Cells(a, 10) = i9
Sheets("Rapor").Cells(a, 11) = i10
For i = 1 To 10
ara_toplam = ara_toplam + WorksheetFunction.VLookup(i, Sheets("Data").Columns("A:D"), Sheets("Rapor").Cells(a, i + 1) + 1, False)
If Sheets("Rapor").Cells(a, i + 1) = 1 Then
worker1 = worker1 + WorksheetFunction.VLookup(i, Sheets("Data").Columns("A:D"), Sheets("Rapor").Cells(a, i + 1) + 1, False)
ElseIf Sheets("Rapor").Cells(a, i + 1) = 2 Then
worker2 = worker2 + WorksheetFunction.VLookup(i, Sheets("Data").Columns("A:D"), Sheets("Rapor").Cells(a, i + 1) + 1, False)
ElseIf Sheets("Rapor").Cells(a, i + 1) = 3 Then
worker3 = worker3 + WorksheetFunction.VLookup(i, Sheets("Data").Columns("A:D"), Sheets("Rapor").Cells(a, i + 1) + 1, False)
End If
Next i
Sheets("Rapor").Cells(a, 12) = ara_toplam
Sheets("Rapor").Cells(a, 13) = worker1
Sheets("Rapor").Cells(a, 14) = worker2
Sheets("Rapor").Cells(a, 15) = worker3
ara_toplam = 0: worker1 = 0: worker2 = 0: worker3 = 0
a = a + 1
Next i10
Next i9
Next i8
Next i7
Next i6
Next i5
Next i4
Next i3
Next i2
Next i1
End Sub
This sounds like a combination problem (order doesn't matter).
Option Explicit
Sub main()
Call for_each_in_others(rDATA:=Worksheets("Sheet1").Range("A2"), bHDR:=True)
End Sub
Sub for_each_in_others(rDATA As Range, Optional bHDR As Boolean = False)
Dim v As Long, w As Long
Dim iINCROWS As Long, iMAXROWS As Long, sErrorRng As String
Dim vVALs As Variant, vTMPs As Variant, vCOLs As Variant
On Error GoTo bm_Safe_Exit
appTGGL bTGGL:=False
With rDATA.Parent
With rDATA(1).CurrentRegion
'Debug.Print rDATA(1).Row - .Cells(1).Row
With .Resize(.Rows.Count - (rDATA(1).Row - .Cells(1).Row), .Columns.Count).Offset(2, 0)
sErrorRng = .Address(0, 0)
vTMPs = .Value2
ReDim vCOLs(LBound(vTMPs, 2) To UBound(vTMPs, 2))
iMAXROWS = 1
'On Error GoTo bm_Output_Exceeded
For w = LBound(vTMPs, 2) To UBound(vTMPs, 2)
vCOLs(w) = Application.CountA(.Columns(w))
iMAXROWS = iMAXROWS * vCOLs(w)
Next w
'control excessive or no rows of output
If iMAXROWS > Rows.Count Then
GoTo bm_Output_Exceeded
ElseIf .Columns.Count = 1 Or iMAXROWS = 0 Then
GoTo bm_Nothing_To_Do
End If
On Error GoTo bm_Safe_Exit
ReDim vVALs(LBound(vTMPs, 1) To iMAXROWS, LBound(vTMPs, 2) To UBound(vTMPs, 2))
iINCROWS = 1
For w = LBound(vVALs, 2) To UBound(vVALs, 2)
iINCROWS = iINCROWS * vCOLs(w)
For v = LBound(vVALs, 1) To UBound(vVALs, 1)
vVALs(v, w) = vTMPs((Int(iINCROWS * ((v - 1) / UBound(vVALs, 1))) Mod vCOLs(w)) + 1, w)
Next v
Next w
End With
End With
.Cells(2, UBound(vVALs, 2) + 2).Resize(1, UBound(vVALs, 2) + 2).EntireColumn.Delete
If bHDR Then
rDATA.Cells(1, 1).Offset(-1, 0).Resize(1, UBound(vVALs, 2)).Copy _
Destination:=rDATA.Cells(1, UBound(vVALs, 2) + 2).Offset(-1, 0)
End If
rDATA.Cells(1, UBound(vVALs, 2) + 2).Resize(UBound(vVALs, 1), UBound(vVALs, 2)) = vVALs
End With
GoTo bm_Safe_Exit
bm_Nothing_To_Do:
MsgBox "There is not enough data in " & sErrorRng & " to perform expansion." & Chr(10) & _
"This could be due to a single column of values or one or more blank column(s) of values." & _
Chr(10) & Chr(10) & "There is nothing to expand.", vbInformation, _
"Single or No Column of Raw Data"
GoTo bm_Safe_Exit
bm_Output_Exceeded:
MsgBox "The number of expanded values created from " & sErrorRng & _
" (" & Format(iMAXROWS, "\> #, ##0") & " rows × " & UBound(vTMPs, 2) & _
" columns) exceeds the rows available (" & Format(Rows.Count, "#, ##0") & ") on this worksheet.", vbCritical, _
"Too Many Entries"
bm_Safe_Exit:
appTGGL
End Sub
Sub appTGGL(Optional bTGGL As Boolean = True)
Application.EnableEvents = bTGGL
Application.ScreenUpdating = bTGGL
End Sub
Before:
After:
Expanding column cells for each column cell
I have the follow script to put a list of people with there know skills in an array and then match the first match with a customer with the same skill. Every time it runs the results are the same. I would like to have it be a random order of the array, but keeping the two columns in the array together. How can I shuffle(rearrange) the array that keeps the rows in the array the same? Or would it be better to erase the array, randomly sort the columns and set the array back up?
Sub Assign()
Dim arOne()
ReDim arOne(1000, 15)
Dim o As Integer
Dim p As Integer
Dim StartTime As Double
Dim MinutesElapsed As String
p = 0
o = 0
For i = 2 To 920
If Cells(i, 12).Value <> Cells(i - 1, 12) Then
p = p + 1
arOne(p, 0) = Cells(i, 12).Value
arOne(p, 1) = Cells(i, 13).Value
o = 2
Else
arOne(p, o) = Cells(i, 13).Value
o = o + 1
End If
Next
For i = 2 To 612
For o = LBound(arOne, 1) + 1 To UBound(arOne, 1)
If arOne(o, 0) <> "" Then
iUsed = Application.WorksheetFunction.CountIf(Range("C2:C" & i), "=" & arOne(o, 0))
If iUsed < Application.WorksheetFunction.VLookup(arOne(o, 0), Range("Q2:R62"), 2, False) Then
For j = LBound(arOne, 2) + 1 To UBound(arOne, 2)
If arOne(o, j) = Cells(i, 2).Value Then
Cells(i, 3).Value = arOne(o, 0)
ActiveSheet.Calculate
GoTo NextIR
End If
Next j
End If
End If
Next o
NextIR:
Next i
End Sub
Multiple loops and multiple access to range objects makes your code very, very slow (I don't know if performance is important).
I would read all necessary data to arrays and use filter and rnd to get a random person with the relevant skill:
Option Explicit
Sub PeopleBusiness()
Dim People, Customers, FilterArray
Dim I As Long, Idx As Long
People = Application.Transpose([L2:L920 & "|" & M2:M8])
Customers = Range("A2:C612").Value2
For I = 1 To UBound(Customers, 1)
FilterArray = Filter(People, Customers(I, 2))
If UBound(FilterArray) > -1 Then
Idx = Round(Rnd() * UBound(FilterArray), 0)
Customers(I, 3) = Left(FilterArray(Idx), InStr(1, FilterArray(Idx), "|") - 1)
End If
Next I
Range("A2:C612").Value = Customers
End Sub
I was able to get done what I needed by erasing the array and redimming it after sorting the data based on a rand() number in the table. It takes about 15 minutes to run 7000 assignment but it is a lot better than 7+ hours it takes to do manually.
Sub Assign()
Dim arOne()
ReDim arOne(1000, 15)
Dim o As Integer
Dim p As Integer
Dim StartTime As Double
Dim MinutesElapsed As String
Application.Calculation = xlAutomatic
StartTime = Timer
NextIR:
ReDim arOne(1000, 15)
p = 0
o = 0
QAlr = Sheets("Sheet1").Range("L" & Rows.Count).End(xlUp).Row
For I = 2 To QAlr
If Cells(I, 12).Value <> Cells(I - 1, 12) Then
p = p + 1
arOne(p, 0) = Cells(I, 12).Value
arOne(p, 1) = Cells(I, 13).Value
o = 2
Else
arOne(p, o) = Cells(I, 13).Value
o = o + 1
End If
Next
AQAlr = Sheets("Sheet1").Range("C" & Rows.Count).End(xlUp).Row
AgtLr = Sheets("Sheet1").Range("A" & Rows.Count).End(xlUp).Row
For I = AQAlr + 1 To AgtLr
For o = LBound(arOne, 1) + 1 To UBound(arOne, 1)
If arOne(o, 0) <> "" Then
iUsed = Application.WorksheetFunction.CountIf(Range("C2:C" & I), "=" & arOne(o, 0))
If iUsed < Application.WorksheetFunction.VLookup(arOne(o, 0), Range("Q2:R62"), 2, False) Then
For j = LBound(arOne, 2) + 1 To UBound(arOne, 2)
If arOne(o, j) = Cells(I, 2).Value Then
Cells(I, 3).Value = arOne(o, 0)
ActiveSheet.Calculate
Erase arOne()
ActiveWorkbook.Worksheets("Sheet1").ListObjects("Table1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").ListObjects("Table1").Sort.SortFields.Add _
Key:=Range("Table1[[#All],[Random '#]]"), SortOn:=xlSortOnValues, Order:= _
xlDescending, DataOption:=xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("Sheet1").ListObjects("Table1").Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
GoTo NextIR
End If
Next j
End If
End If
Next o
Next I
MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
MsgBox "Assignments completed in " & MinutesElapsed & " minutes", vbInformation
End Sub
Not entirely sure I got your set-up right but you can try this:
Option Explicit
Sub Assign()
Randomize
Range("C2", Range("C" & Rows.Count).End(xlUp)).ClearContents
Dim R1 As Range: Set R1 = Range("L2:M920") 'People skill table
Dim R2 As Range: Set R2 = Range("A2:B612") 'Cusotmers skill talbe
Dim D0 As Object: Set D0 = CreateObject("scripting.dictionary")
Dim i As Integer, j As Integer, Rand as Integer
For i = 1 To R2.Rows.Count
Rand = Int(R1.Rows.Count * Rnd + 1)
For j = 1 To R1.Rows.Count
If R1.Cells(Rand, 2) = R2(i, 2) And Not D0.exists(Rand) Then
R2.Cells(i, 2).Offset(0, 1) = R1(Rand, 1)
D0.Add Rand, Rand
Exit For
End If
Rand = (Rand Mod R1.Rows.Count) + 1
Next j
Next i
End Sub
The idea is to check the people skill list starting from a random point and making sure a key is not used twice.
EDIT:
According to your comment I assume a "people / skill" can then be assigned more than once as there are 7000+ customers ?
Code below randomly assign with a fairly good distribution 1500 peoples to 7000 customers in +/- 1 second.
Have a try and see if you can adapt it to your project.
Option Explicit
Sub Assign()
Application.ScreenUpdating = False
Dim Start: Start = Timer
Randomize
Range("C2:C99999").ClearContents
Dim D1 As Object
Dim R1 As Range: Set R1 = Range("L2", Range("M" & Rows.Count).End(xlUp))
Dim R2 As Range: Set R2 = Range("A2", Range("B" & Rows.Count).End(xlUp))
Dim T1: T1 = R1
Dim T2: T2 = R2
Dim T3()
Dim a As Integer: a = 1
Dim i As Integer, j As Integer, k As Integer, Rnd_Val As Integer, j_loop As Integer
For i = 1 To (Int(R2.Rows.Count / R1.Rows.Count) + 1)
Set D1 = CreateObject("scripting.dictionary")
For j = (R1.Rows.Count * i - R1.Rows.Count + 1) To R1.Rows.Count * i
ReDim Preserve T3(1 To j)
Rnd_Val = Int(Rnd * R1.Rows.Count + 1)
For k = 1 To R1.Rows.Count
If T1(Rnd_Val, 2) = T2(j, 2) And Not D1.exists(Rnd_Val) And T3(j) = "" Then
T3(j) = T1(Rnd_Val, 1)
D1.Add Rnd_Val, Rnd_Val
Exit For
End If
Rnd_Val = (Rnd_Val Mod R1.Rows.Count) + 1
Next k
If T3(j) = "" Then
For k = 1 To R1.Rows.Count
If T1(Rnd_Val, 2) = T2(j, 2) Then
T3(j) = T1(Rnd_Val, 1)
Exit For
End If
Rnd_Val = (Rnd_Val Mod R1.Rows.Count) + 1
Next k
End If
a = a + 1
If a > R2.Rows.Count Then GoTo EndLoop
Next j
Set D1 = Nothing
Next i
EndLoop:
Range("C2").Resize(UBound(T3), 1) = Application.Transpose(T3)
Debug.Print Timer - Start
Application.ScreenUpdating = True
End Sub
I assume that formatting time and date in an array isn't possible? If yes how would you change the format of a column in a ListBox? This is the part that I'm working with.
Private Sub TextBox_Search_Change()
Select Case True
Case OptionButton_User_Name.Value
temp = UCase(Me.TextBox_Search.Value)
Dim a()
Dim rngValues As Variant
With Sheets("ToolData")
rngValues = .Range("B2", .Range("B" & .Rows.Count).End(xlUp)).Resize(, 11).Value
End With
For i = 1 To UBound(rngValues, 1)
'Check columns B & F for matching values
If UCase(rngValues(i, 1)) Like "*" & temp & "*" Then
'(i, Colunm being searched)
'Store columns B, F & G for displaying in the ListBox
n = n + 1
ReDim Preserve a(1 To 8, 1 To n)
'ListBox (Colunms 1-...)
a(1, n) = rngValues(i, 1)
a(2, n) = rngValues(i, 2)
a(3, n) = rngValues(i, 5)
a(4, n) = rngValues(i, 6)
a(5, n) = rngValues(i, 7)
a(6, n) = rngValues(i, 8)
a(7, n) = rngValues(i, 9)
a(8, n) = rngValues(i, 10)
'ListBox = rngValues(B,+Colunms on sheet)
End If
Next
'If anything found, replace the ListBox contents. Otherwise leave it as it was.
If n > 0 Then
Me.ListBox_History.Column = a
Me.ListBox_History.Column(3, Me.ListBox_History.ListCount - 1) = Format("hh:mm")
End If
Case Else
At the bottom I added this bit as a test Me.ListBox_History.Column(3, Me.ListBox_History.ListCount - 1) = Format("hh:mm") But it doesn't format and instead displays hh:mm in row 1 of the ListBox.
So it should work to format values in an array if they are not numbers.
Try:
a(1, n) = Format(rngValues(i, 1), "hh:mm")
If this is not working you could cylce trough the column like that:
With ListBox1
For i = 0 To .ListCount - 1
.List(i, 0) = (Format(.List(i, 0), "hh:mm"))
Next i
End With
I need to track a person in a data sheet to determine from which location to which location the person moved.
If a person appears more then one time in Column J that means the person has changed the location and the location value is in Column L. For this I have the following code:
=IF(J18=J19;IF(COUNTIF(J:J;J18)>1; "From "&L18 &" to "& IF(J18=J19;L19;"");"");"")
The problem is if the person changes the location more than two times. In Column O to Column AA I have the months of the year which determines the location of the person.
How can I modify this code to do the above:
=IF(J18=J19;IF(COUNTIF(J:J;J18)>1; "From "&L18 &" to "& IF(J18=J19;L19;"");"");"")
Here is a User Defined Function (aka UDF) to accomplish the task.
Function my_Travels(nm As Range, loc As Range, cal As Range)
Dim n As Long, cnt As Long, v As Long, vLOCs As Variant, vTMPs As Variant
Dim iLOC As Long, sTMP As String
my_Travels = vbNullString '"no travels"
cnt = Application.CountIf(nm.EntireColumn, nm(1))
If Application.CountIf(nm, nm(1)) = cnt And cnt > 1 Then
Set loc = loc.Rows(1).Resize(nm.Rows.Count, loc.Columns.Count)
Set cal = cal.Rows(1).Resize(nm.Rows.Count, cal.Columns.Count)
'seed the array
ReDim vLOCs(1 To cnt, 1 To cnt)
For v = LBound(vLOCs, 1) To UBound(vLOCs, 1)
vLOCs(v, 1) = cal.Columns.Count + 1
vLOCs(v, 2) = cal.Columns.Count + 1
Next v
'collect the values into the array
For n = 1 To nm.Rows.Count
If nm.Cells(n, 1).Value2 = nm.Cells(1, 1).Value2 Then
iLOC = Application.Match(1, Application.Index(cal, n, 0), 0)
For v = LBound(vLOCs, 1) To UBound(vLOCs, 1)
If vLOCs(v, 1) = cal.Columns.Count + 1 Then
vLOCs(v, 1) = iLOC
vLOCs(v, 2) = n
Exit For
End If
Next v
End If
Next n
'sort the values in the array
For v = LBound(vLOCs, 1) To (UBound(vLOCs, 1) - 1)
For n = (v + 1) To UBound(vLOCs, 1)
If vLOCs(v, 1) > vLOCs(n, 1) Then
vTMPs = Array(vLOCs(v, 1), vLOCs(v, 2))
vLOCs(v, 1) = vLOCs(n, 1)
vLOCs(v, 2) = vLOCs(n, 2)
vLOCs(n, 1) = vTMPs(0)
vLOCs(n, 2) = vTMPs(1)
Exit For
End If
Next n
Next v
'concatenate the locations from the array
For v = LBound(vLOCs) To (UBound(vLOCs) - 1)
sTMP = sTMP & "From " & loc.Cells(vLOCs(v, 2), 1) & " to " & loc.Cells(vLOCs(v + 1, 2), 1) & "; "
Next v
'truncate the string and return it
sTMP = Left(sTMP, Len(sTMP) - 2)
my_Travels = sTMP
End If
End Function
The Locations and the Calendar cells only need to be defined by the first row. Each has its height (i.e. rows) redefined to maintain consistency with the list of names.
In AB2 (as above) the formula is,
=my_Travels(J2:J$8, L2, O2:AA2)
Fill down as necessary.