短いコードなので詳しい説明は省きます。
基本的な動作
選択範囲内の行方向に対して相互にセルの値を入れ替えます。
入れ替えた後の最小値が、前の最小値より小さかったら、その最小値を記憶
小さくなかったら、入れ替えを元に戻す
これを繰り返します。
ただし、入れ替えた後に、条件セルの値が0で無い場合は無条件に元に戻す。
以上の処理を収束値に達するか、または、50回に達するまで繰り返します。
ソースコード
標準モジュール Optimize
シフト範囲/条件を入力するフォームを起動します。
Sub 均等再配置() frm均等再配置.Show '(vbModeless) End Sub
フォーム frm均等再配置
Option Explicit Const 収束判定値 = 0.01 Dim flg中止 As Boolean 'flg中止判定用の変数 Private Sub ref最小化_BeforeDragOver(Cancel As Boolean, ByVal Data As MSForms.DataObject, ByVal x As stdole.OLE_XPOS_CONTAINER, ByVal y As stdole.OLE_YPOS_CONTAINER, ByVal DragState As MSForms.fmDragState, Effect As MSForms.fmDropEffect, ByVal Shift As Integer) End Sub Private Sub UserForm_Activate() If IsRange("変化させるセル") Then ref範囲 = "変化させるセル" If IsRange("最小化セル") Then ref最小化 = "最小化セル" If IsRange("条件セル") Then ref条件 = "条件セル" ref範囲.SetFocus End Sub Private Sub cmd実行_Click() '入力チェック If Not IsRange(ref範囲) Then MsgBox "範囲セルを選択して下さい!", vbOKOnly ref範囲.SetFocus Exit Sub End If If Not IsRange(ref最小化) Then MsgBox "最小化セルを選択して下さい!", vbOKOnly ref最小化.SetFocus Exit Sub End If flg中止 = False '「flg中止」フラグをオフ cmd中止.Enabled = True '「flg中止」ボタンを有効 cmd実行.Enabled = False '「実行」ボタンを無効 Call 分散最少化(ref範囲, ref条件, ref最小化) cmd実行.Enabled = True '「実行」ボタンを有効 cmd中止.Enabled = False '「flg中止」ボタンを有効 End Sub Private Sub cmd中止_Click() '「中止」ボタンを押された時の処理 flg中止 = True End Sub Sub 分散最少化(範囲セル As String, 条件セル As String, 最小化セル As String) '---------------------- '変数 '---------------------- Dim rngShift As Range '選択範囲 Dim SigmaSum As Double '分散計 Set rngShift = Range(範囲セル) '---------------------- '開始 '---------------------- SigmaSum = 99999 '分散初期値 '---------------------- '作業変数 '---------------------- Dim r, c, r2 As Long Dim n As Long Dim tmpSigmaSum As Double tmpSigmaSum = SigmaSum For n = 1 To 10 '繰り返し回数 For c = 1 To rngShift.Columns.Count '選択範囲の列数 For r = 1 To rngShift.Rows.Count '選択範囲の行数 For r2 = r + 1 To rngShift.Rows.Count '----------------------------- DoEvents If flg中止 Then GoTo MyAbort End If '----------------------------- Call MySwap(rngShift(r, c), rngShift(r2, c)) 'セル入れ替え If Range(条件セル).Value <> 0 Then '条件を満たすか? Call MySwap(rngShift(r, c), rngShift(r2, c)) '満たさない→戻す Else Dim tmp tmp = Range(最小化セル).Value '最小値取り出し If tmp < SigmaSum Then '値更新? SigmaSum = tmp '新たな最小値とする Else Call MySwap(rngShift(r, c), rngShift(r2, c)) '戻す End If End If Next r2 Next r Next c If Abs(tmpSigmaSum - SigmaSum) < 収束判定値 Then Exit For '収束 tmpSigmaSum = SigmaSum Next n '-------------------------------------- '完了 '-------------------------------------- GoTo MyEnd MyAbort: MyEnd: End Sub Private Sub MySwap(Rng1 As Range, Rng2 As Range) '----------------- 'セルデータ入替 '----------------- Dim tmp As Variant tmp = Rng1.Value Rng1.Value = Rng2.Value Rng2.Value = tmp End Sub Private Function IsRange(RangeName As String) As Boolean ' 範囲名の正当性チェック On Error GoTo MyEnd IsRange = False If Range(RangeName).Address <> "" Then IsRange = True MyEnd: On Error GoTo 0 End Function
課題
この処理は簡素化しているため、あまり性能が良いとは言えません。
以下のような改善を加えると、もう少し良くなるかもしれません。
・列方向に順に入れ替えを進めているのを、ランダムな列にする。
・列方向も考慮して、同時に3つ、または、4つのセルを入れ替え試行する。