Reputation: 435
I have a spreadsheet with many numbers, and I want the cells with the same numbers to be moved to the same row. Currently, my spreadsheet looks something like this:
* May Jun Jul Aug Sep Oct
* 10584 10589 10584 10584 10589 10589
* 10589 11202 10589 10589 11202 11202
* 11202 9799 11202 11202 11677 11677
* 11677
I would like to have some vba code to organize the data so that cells with the same value are on the same row, so it should look like this:
* May Jun Jul Aug Sep Oct
* 9799
* 10584 10584 10584
* 10589 10589 10589 10589 10589 10589
* 11202 11202 11202 11202 11202 11202
* 11677 11677 11677
With empty cells in the places with no numbers. I tried searching through the forum but I wasn't able to find anything similiar enough. I would really apreciate any help on this. Thanks for your time.
Upvotes: 1
Views: 117
Reputation: 60174
Here is another approach that, if you have a large amount of data, should run more quickly, as it only reads and writes to/from the worksheet once -- all the "work" is done within VBA.
You will probably want to move the results to a different worksheet -- all you need to do is change what you Set wsRes
and possible rRes
Create a User defined object which consists of the "Number" and a collection of the columns in which that number appears.
Read the source data into an array
You must RENAME the Class Module cNumCols
Option Explicit
Private pNum As Long
Private pCOL As Long
Private pCOLs As Collection
Private Sub Class_Initialize()
Set pCOLs = New Collection
End Sub
Public Property Get Num() As Long
Num = pNum
End Property
Public Property Let Num(Value As Long)
pNum = Value
End Property
Public Property Get COL() As Long
COL = pCOL
End Property
Public Property Let COL(Value As Long)
pCOL = Value
End Property
Public Property Get COLs() As Collection
Set COLs = pCOLs
End Property
Public Sub ADD(COLval As Long)
pCOLs.ADD COLval
End Sub
Option Explicit
Sub SortNumbers()
Dim cNC As cNumCols, colNC As Collection
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes() As Variant
Dim i As Long, J As Long
'Set source and destination sheets and ranges
Set wsSrc = Worksheets("sheet4")
Set wsRes = Worksheets("sheet4")
Set rRes = wsRes.Range("L1")
With wsSrc
vSrc = .Range("a1").CurrentRegion
End With
'collect list of unique numbers, along with their columns
Set colNC = New Collection
On Error Resume Next
For i = 2 To UBound(vSrc, 1)
For J = 1 To UBound(vSrc, 2)
If vSrc(i, J) <> "" Then
Set cNC = New cNumCols
With cNC
.Num = vSrc(i, J)
.COL = J
.ADD .COL
colNC.ADD cNC, CStr(.Num)
If Err.Number = 457 Then
Err.Clear
colNC(CStr(.Num)).ADD .COL
End If
If Err.Number <> 0 Then 'stop to debug error
Debug.Print Err.Source, Err.Number, Err.Description
Stop
End If
End With
End If
Next J
Next i
On Error GoTo 0
'Sort collection by number
CollectionBubbleSort colNC, "Num"
'Populate results array
ReDim vRes(0 To colNC.Count, 1 To UBound(vSrc, 2))
'header row
For J = 1 To UBound(vSrc, 2)
vRes(0, J) = vSrc(1, J)
Next J
'data
For i = 1 To colNC.Count
With colNC(i)
For J = 1 To .COLs.Count
vRes(i, .COLs(J)) = .Num
Next J
End With
Next i
'Clear results area and write results
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
With .Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
End Sub
'Could use faster sort routine if necessary
Sub CollectionBubbleSort(TempCol As Collection, Optional Prop As String = "")
Dim i As Long
Dim NoExchanges As Boolean
' Loop until no more "exchanges" are made.
Do
NoExchanges = True
' Loop through each element in the array.
For i = 1 To TempCol.Count - 1
If Prop = "" Then
' If the element is greater than the element
' following it, exchange the two elements.
If TempCol(i) > TempCol(i + 1) Then
NoExchanges = False
TempCol.ADD TempCol(i), after:=i + 1
TempCol.Remove i
End If
Else
If CallByName(TempCol(i), Prop, VbGet) > CallByName(TempCol(i + 1), Prop, VbGet) Then
NoExchanges = False
TempCol.ADD TempCol(i), after:=i + 1
TempCol.Remove i
End If
End If
Next i
Loop While Not (NoExchanges)
End Sub
Upvotes: 0
Reputation: 4010
Here is an approach that works on a block of data of arbitrary size. It works by sorting the columns and then shifting the cells down if they are not equal to the smallest value in the row.
The only real parameter here to adjust is the starting cell: rng_start
which is initially set to the ActiveCell
. This code also uses CurrentRegion
so the data needs to be a block... or you can redefine those couple of lines.
Code
Sub SortAndPutSameValuesInSameRow()
'get data ranges
Dim rng_start As Range
Set rng_start = ActiveCell
Dim rng_data As Range
Set rng_data = rng_start.CurrentRegion
Set rng_data = Intersect(rng_data, rng_data.Offset(1))
'sort by column
Dim rng_col As Range
For Each rng_col In rng_data.Columns
rng_col.Sort Key1:=rng_col
Next
'iterate through rows and arrange
Dim rng_row As Range
For Each rng_row In rng_data.Rows
Dim rng_cell As Range
For Each rng_cell In rng_row.Cells
If rng_cell.Value <> Application.WorksheetFunction.min(rng_row) Then
rng_cell.Insert xlShiftDown
End If
Next
'break out if cell goes past data
If Intersect(rng_row, rng_start.CurrentRegion) Is Nothing Then
Exit For
End If
Next
End Sub
How it works
The main idea here is that once the columns are sorted, you just need to move values down so that only the smallest value is kept in each row. This logic also ensures that all of the same values are in the same row. Note that if there are duplicate values, you will get a row of matching values and then duplicate values (which would also match if repeated in multiple columns). Specific comments:
For Each
loop but is required because the size of the range is changing as it iterates (because of Insert
).Since I am using Rows
and Columns
, this code will work for data anywhere on the sheet and for as many columns as you want.
Pictures of before/after show results with your data
before
after
Upvotes: 1
Reputation: 368
This should work:
Sub t()
Dim i As Integer, min As Long, rowCurrent As Integer
Dim j As String
For i = 1 To 6
'sort all the columns first
Columns(i).Sort key1:=Cells(2, i), _
order1:=xlAscending, Header:=xlYes
Next i
rowCurrent = 2
While Not Application.WorksheetFunction.Sum(Range("A" & rowCurrent & ":F" & rowCurrent)) = 0
min = Application.WorksheetFunction.min(Rows(rowCurrent))
For i = 1 To 6
If Cells(rowCurrent, i) <> min Then
Range(Cells(rowCurrent, i).Offset(1, 0).Address & ":" & Cells(Rows.Count, i).End(xlUp).Offset(1, 0).Address).Value = _
Range(Cells(rowCurrent, i).Address & ":" & Cells(Rows.Count, i).End(xlUp).Address).Value
Cells(rowCurrent, i).Value = ""
End If
Next i
rowCurrent = rowCurrent + 1
Wend
End Sub
Upvotes: 1