- 締切済み
Excelで合計値を基にデータを均等に分ける
どなたかご存知でしたら教えてください。 Excel で次のようなデータがあるとします。 .....A 1...10 2...15 3.....8 4...20 5...17 合計は 70 になります。これを 2 で割った 35 になるように、この 5 つのデータを振り分ける方法に悩んでいます。 この例の場合、2 と 4、1 と 3 と 5 で、それぞれ 35 になりますが、 結果として .....A 1...10 2....8 3...17 -------- 4...15 5...20 などのように表示されるようにしたいのですが、 どのような方法であれば実現できるでしょうか? 宜しくお願いいたします。
- みんなの回答 (9)
- 専門家の回答
みんなの回答
- _Kyle
- ベストアンサー率78% (109/139)
#6=7=8 です。 何度もスミマセン。 仕様自体は#8(#7修正版)と変わらないのですが、 私自身の練習や実験を兼ねていろいろいじってたらかなり速くなったので。 「速くなった」のではなく「元が遅すぎた」というウワサもありますが…。 所要時間比でいうと、 #6 : 750 #7 : × #8 : 700 #9 : 350 ぐらいです。 「最適解を求める」という観点からは焼け石に水ですが、 (↑10日かかるところが5日で済んだとしてもあまり嬉しくない) 単位時間あたりの探索量が多くなれば、 5分であれ10分であれ一定時間内に「よりマシな解が見つかる」可能性が高くなります。 '--------------------------↓ ココカラ ↓-------------------------- Dim ogAry() As Long Dim ixAry As Variant Dim elCnt As Long Dim gpCnt As Long Dim tpAry() As Long Dim alSum As Long Dim tpSum() As Long Dim btDif As Long Dim btMax As Long Dim WSF As WorksheetFunction Dim t As Variant '-------------------------- Sub Sample() Dim i As Long t = Timer Set WSF = Application.WorksheetFunction Range("C:G").Clear elCnt = Range("A1").End(xlDown).Row gpCnt = Val(InputBox("いくつのグループに分けますか?")) With Range(Range("C1"), Cells(elCnt, "E")) .Value = .Offset(0, -2).Value .Sort Key1:=Range("C1"), Order1:=xlDescending, Header:=xlNo ReDim ogAry(1 To elCnt) For i = 1 To elCnt ogAry(i) = .Cells(i, 1).Value Next i ixAry = .Columns(2).Value End With Range("F1:F4").Value = WSF.Transpose(Array("最大", "最小", "差", "比")) alSum = WSF.Sum(ogAry) ReDim tpAry(1 To elCnt) ReDim tpSum(1 To gpCnt) For i = 1 To elCnt tpAry(i) = WSF.Match(WSF.Min(tpSum), tpSum, 0) tpSum(tpAry(i)) = tpSum(tpAry(i)) + ogAry(i) Next i btDif = WSF.Max(tpSum) - WSF.Min(tpSum) btMax = -Int(-(alSum + (gpCnt - 1) * btDif) / gpCnt) Call SubDsp If btDif < 2 Then Call SubMsg: End ReDim tpSum(1 To gpCnt) Call SubRef(1) Call SubMsg End Sub '-------------------------- Private Sub SubRef(ByVal elIdx As Long) Dim i As Long Dim bfSum As Long Dim bfDif As Long For i = 1 To gpCnt bfSum = tpSum(i) tpSum(i) = tpSum(i) + ogAry(elIdx) If tpSum(i) < btMax Then tpAry(elIdx) = i If elIdx = elCnt Then bfDif = WSF.Max(tpSum) - WSF.Min(tpSum) If bfDif < btDif Then btDif = bfDif btMax = -Int(-(alSum + (gpCnt - 1) * btDif) / gpCnt) Call SubDsp If btDif < 2 Then Call SubMsg: End End If Else Call SubRef(elIdx + 1) End If End If tpSum(i) = bfSum If bfSum = 0 Then Exit For Next i End Sub '-------------------------- Private Sub SubDsp() Dim i As Long Dim j As Long Dim k As Long Range("C:E").Clear k = 1 For i = 1 To gpCnt For j = 1 To elCnt If tpAry(j) = i Then Cells(k, 3).Value = ogAry(j) Cells(k, 4).Value = ixAry(j, 1) Cells(k, 5).Value = i k = k + 1 End If Next j Cells(k - 1, 3).Resize(, 3).Borders(xlEdgeBottom).Weight = xlMedium Next i Cells(1, 7).Value = WSF.Max(tpSum) Cells(2, 7).Value = WSF.Min(tpSum) Cells(3, 7).Value = btDif Cells(4, 7).Value = Format(WSF.Min(tpSum) / WSF.Max(tpSum), "#0.00%") End Sub '-------------------------- Private Sub SubMsg() MsgBox "これが最適解です" & vbCr & vbCr & _ "所要時間 : " & Int(Timer - t) & " sec." End Sub '--------------------------↑ ココマデ ↑--------------------------
- _Kyle
- ベストアンサー率78% (109/139)
#6=#7です。 スミマセン。#7のコードにミスがありました。 btMax = Int((alSum + (gpCnt - 1) * btDif) / gpCnt + 0.5) という部分が【 2ヶ所 】ありますが、いずれも正しくは btMax = -Int(-(alSum + (gpCnt - 1) * btDif) / gpCnt) です。修正してください。 「ココは切り上げなきゃ」と考えつつ四捨五入を書く私って…。 要る枝まで切ればそりゃ速くなりますわね。
- _Kyle
- ベストアンサー率78% (109/139)
#6です。 「正常終了を待たずブレイクするのが標準の運用」という邪道なマクロですが、 とりあえず当座の役には立てたようで何よりです。 少し直してみました。 0.コードを(気持ちだけ)整理した。 微妙に速くなりました。(#6に較べて1割くらい) 1.データ列/結果列の構成を変更・追加した。 罫線で分けただけだと並べ替えや数式参照する際に不便なので。 「色情報」の件の代替仕様も兼ねています。 A列:元データ-値 B列:元データ-備考とか連番とか C列:結果-値 D列:結果-備考とか連番とか E列:結果-グループ番号 なお、最適解は一つとは限らないので、 「すべての最適解を探せるように」というのも考えたのですが、 「最後まで調べて、結局解Aが最適解であることが判った」 ↓ 「解A以降に見つけてスルーした解Bや解Cも最適解だった」 というケースで困る…というか覚えておくのが面倒なのと、 そもそも最適解を見つけること自体困難な場合の方が多いのでやめました。 '--------------------------↓ ココカラ ↓-------------------------- Dim ogAry As Variant Dim ixAry As Variant Dim elCnt As Long Dim gpCnt As Long Dim tpAry() As Long Dim alSum As Long Dim tpSum() As Long Dim btDif As Long Dim btMax As Long Dim WSF As WorksheetFunction Dim t As Variant '-------------------------- Sub Sample() Dim i As Long t = Timer Set WSF = Application.WorksheetFunction Range("C:G").Clear elCnt = Range("A1").End(xlDown).Row gpCnt = Val(InputBox("いくつのグループに分けますか?")) With Range(Range("C1"), Cells(elCnt, "E")) .Value = .Offset(0, -2).Value .Sort Key1:=Range("C1"), Order1:=xlDescending, Header:=xlNo ogAry = .Columns(1).Value ixAry = .Columns(2).Value End With Range("F1:F4").Value = WSF.Transpose(Array("最大", "最小", "差", "比")) alSum = WSF.Sum(ogAry) ReDim tpAry(1 To elCnt) ReDim tpSum(1 To gpCnt) For i = 1 To elCnt tpAry(i) = WSF.Match(WSF.Min(tpSum), tpSum, 0) tpSum(tpAry(i)) = tpSum(tpAry(i)) + ogAry(i, 1) Next i btDif = WSF.Max(tpSum) - WSF.Min(tpSum) btMax = Int((alSum + (gpCnt - 1) * btDif) / gpCnt + 0.5) Call SubDsp If btDif <= 1 Then Call SubMsg: End ReDim tpSum(1 To gpCnt) Call SubRef(0) Call SubMsg End Sub '-------------------------- Private Sub SubRef(ByVal elIdx As Long) Dim i As Long Dim f As Boolean Dim bfSum As Long Dim bfDif As Long If elIdx = elCnt Then bfDif = WSF.Max(tpSum) - WSF.Min(tpSum) If bfDif < btDif Then btDif = bfDif btMax = Int((alSum + (gpCnt - 1) * btDif) / gpCnt + 0.5) Call SubDsp If btDif <= 1 Then Call SubMsg: End End If Else elIdx = elIdx + 1 For i = 1 To gpCnt If i = 1 Then f = True Else f = tpSum(i - 1) > 0 End If If f Then bfSum = tpSum(i) tpSum(i) = tpSum(i) + ogAry(elIdx, 1) If tpSum(i) < btMax Then tpAry(elIdx) = i Call SubRef(elIdx) End If tpSum(i) = bfSum End If Next i End If End Sub '-------------------------- Private Sub SubDsp() Dim i As Long Dim j As Long Dim k As Long Range("C:E").Clear k = 1 For i = 1 To gpCnt For j = 1 To elCnt If tpAry(j) = i Then Cells(k, 3).Value = ogAry(j, 1) Cells(k, 4).Value = ixAry(j, 1) Cells(k, 5).Value = i k = k + 1 End If Next j Cells(k - 1, 3).Resize(, 3).Borders(xlEdgeBottom).Weight = xlMedium Next i Cells(1, 7).Value = WSF.Max(tpSum) Cells(2, 7).Value = WSF.Min(tpSum) Cells(3, 7).Value = btDif Cells(4, 7).Value = Format(WSF.Min(tpSum) / WSF.Max(tpSum), "#0.00%") End Sub '-------------------------- Private Sub SubMsg() MsgBox "これが最適解です" & vbCr & vbCr & _ "所要時間 : " & Int(Timer - t) & " sec." End Sub '--------------------------↑ ココマデ ↑--------------------------
お礼
再び・・・お・おぉぉ・・・っです。 チラッと改良を思いついたはいいものの、ほぼ満足していたのですが、 こんなに早くバージョンアップしていただけるなんて、 ほんとに感謝です。早速コピらせて頂きました(^0^) ありがとうございました!!
- _Kyle
- ベストアンサー率78% (109/139)
横から失礼します。 もし、近似解で良い(必ずしも最適解でなくともよい)のであれば、 マクロ(VBA)を使えばある程度近い解を見つけることはできます。 ●動作の概要 A1セル以下にある任意の数の整数を、 【和が最大となる組と最小となる組との差がなるべく小さくなるように】 指定した数の組に振り分け、B1セル以下に表示する。 【途中であきらめること】を前提にしたマクロです。 適当なタイミングでEscキーを押して中断してください。 時間をかければいつかは最適解が見つかりますが、 総当りではないとはいえ可能性のある部分はすべて舐めるので、 条件次第ではマクロが終わるより先に世界が滅びてしまいます^^;; また、早い段階で最適解が見つかった場合でも、 「最後まで調べ尽くしてそれが最適解であることを確かめる」のに時間がかかる場合があります。 一晩放置して調べたとしても、より良い解が見つかるとは限りません。 なお、上述の通り、ここでいう「最適解」は、 【和が最大となる組と最小となる組の差が最も小さくなる分け方】としています。 分散だの標準偏差だのといった種類のハナシではないようなので…。 参考画像は、1000以下のランダムな整数25個を6組に分けた事例です。 (最適解を見つけるのに15秒、それが最適解だということを確認するのに12分) Excel2003で動作確認。 以上ご参考まで。長乱文長乱コード陳謝。 '--------------------------↓ ココカラ ↓-------------------------- Dim ogAry As Variant Dim elCnt As Long Dim gpCnt As Long Dim tpAry() As Long Dim alSum As Long Dim tpSum() As Long Dim btDif As Long Dim btMax As Long Dim WSF As WorksheetFunction Dim t As Variant '-------------------------- Sub Sample() Dim i As Long Set WSF = Application.WorksheetFunction With Range(Range("B1"), Cells(Range("A1").End(xlDown).Row, 2)) .Clear .Value = .Offset(0, -1).Value .Sort Key1:=Range("B1"), Order1:=xlDescending, Header:=xlNo ogAry = .Value End With Range("C1:C4").Value = WSF.Transpose(Array("最大", "最小", "差", "比")) elCnt = UBound(ogAry) gpCnt = Val(InputBox("いくつのグループに分けますか?")) alSum = WSF.Sum(ogAry) t = Timer ReDim tpAry(1 To elCnt) ReDim tpSum(1 To gpCnt) For i = 1 To elCnt tpAry(i) = WSF.Match(WSF.Min(tpSum), tpSum, 0) tpSum(tpAry(i)) = tpSum(tpAry(i)) + ogAry(i, 1) Next i btDif = WSF.Max(tpSum) - WSF.Min(tpSum) btMax = alSum + (gpCnt - 1) * btDif Call SubDsp t = Timer ReDim tpAry(1 To elCnt) ReDim tpSum(1 To gpCnt) tpSum(1) = ogAry(1, 1) tpAry(1) = 1 Call SubRef(1) MsgBox "これが最適解です" & vbCr & vbCr & _ "所要時間 : " & Int(Timer - t) & " sec." End Sub '-------------------------- Private Sub SubRef(ByVal elIdx As Long) Dim i As Long Dim f As Boolean Dim bfSum As Long If btDif <= 1 Then Exit Sub If elIdx = elCnt Then If WSF.Max(tpSum) - WSF.Min(tpSum) < btDif Then btDif = WSF.Max(tpSum) - WSF.Min(tpSum) btMax = alSum + (gpCnt - 1) * btDif Call SubDsp End If Else elIdx = elIdx + 1 For i = 1 To gpCnt If i = 1 Then f = True Else f = tpSum(i - 1) > 0 End If If f Then bfSum = tpSum(i) tpSum(i) = tpSum(i) + ogAry(elIdx, 1) If tpSum(i) * gpCnt < btMax Then tpAry(elIdx) = i Call SubRef(elIdx) End If tpSum(i) = bfSum End If Next i End If End Sub '-------------------------- Private Sub SubDsp() Dim i As Long Dim j As Long Dim k As Long Columns(2).Clear k = 1 For i = 1 To gpCnt For j = 1 To elCnt If tpAry(j) = i Then Cells(k, 2).Value = ogAry(j, 1) k = k + 1 End If Next j Cells(k - 1, 2).Borders(xlEdgeBottom).Weight = xlMedium Next i Cells(1, 4).Value = WSF.Max(tpSum) Cells(2, 4).Value = WSF.Min(tpSum) Cells(3, 4).Value = btDif Cells(4, 4).Value = Format(WSF.Min(tpSum) / WSF.Max(tpSum), "#0.00%") End Sub '--------------------------↑ ココマデ ↑--------------------------
お礼
お・・・おぉぉ・・・!すごいです。 早速試してみましたが、期待通りの結果を得ることができました。 本当に感謝です。助かりました! ふと、最初にAでいくつかのセルに色を付けておいて その色情報ともにBにもっていければ 結果を見たとき、Aのどれとどれがグループになったのかが よりわかりやすいかも、と思いました。 頑張ってみます。ありがとうございました!
- web2525
- ベストアンサー率42% (1219/2850)
数が20で5等分は非常に難しいですよ 数が5個で2等分だから計算式で求められる 2等分する場合の割合が 1:4か2:3の2通りしか存在しないため 最大値+xを求める事だけで解が得られるからです。 数値が20あり5等分の組み合わせを総当たりで計算する事は非常に難解極まる計算式が必要となるでしょう。
お礼
・・・で、ですよね・・・はぁ(>_<) やはり今までやっていたように .....A 1...10 2...15 3.....8 4...20 5...17 の合計とその2等分(35)を出し、上から10+15=25, 25+8=33、で区切り、 また20+17=37というようにするしかないようですね。 この問題点は、昇順に並べて上から順に足していったのでは 大幅に35と異なる場合がよくあるからで、 数字の大きいのと小さいのをうまく組み合わせることができないか、 と思ったのです(いまは手動です・・・) また何か名案が浮かんだら、是非教えてください。 私ももう少し考えてみます。ありがとうございました。
- web2525
- ベストアンサー率42% (1219/2850)
考え方はNo2と一緒です 5の整数の最大値に他の数を足した結果の中から合計の1/2に一番近い組み合わせを求める 例:5つの数が20,18,14,13,5の場合 20 18+20= 38 14+20= 34 13+20= 33 5+20= 25 合計の23に一番近い組み合わせは14+20となるので {20,14}と{18,13,5}の組み合わせに分ける 最大値が合計の1/2を超える場合は無条件で{最大値}と{その他}の組み合わせとなる。 ただし数字が6以上ある場合や3等分する場合などはもっと複雑になってしまいます。 Excelの計算式だけで結果を導き出すのであれば A1:A5に数値が有り降順に並んでいると仮定し =LARGE(A2:A5,MATCH(MIN(IF(INDEX(A2:A5+A1,0)>SUM(A1:A5)/2,INDEX(A2:A5+A1,0)-SUM(A1:A5)/2,SUM(A1:A5)/2-INDEX(A2:A5+A1,0))),IF(INDEX(A2:A5+A1,0)>SUM(A1:A5)/2,INDEX(A2:A5+A1,0)-SUM(A1:A5)/2,SUM(A1:A5)/2-INDEX(A2:A5+A1,0)),0)) 配列計算になるので[Sift]+[Ctrl]+[Enter]で確定 この計算式で求められる数値と最大値の組み合わせとその他の組み合わせの2グループに分けることが出来ます。 計算式自体はもっとシンプルにする方法はあるかも知れません。
お礼
詳しくお答えいただき、本当にありがとうございました。 大体の考え方は理解できたように思います。 実際には数字が 20 以上あることが多く、 等分も少なくても 5 等分するので、 あまりのややこしさに諦めそうになりますが、 この計算式を基に考えてみたいと思います。
- web2525
- ベストアンサー率42% (1219/2850)
>必ずしも割り切れないのですが、その場合はできないのでしょうか。 答えのない問題自体は解くことができません。 例題1. 5つの整数の合計が71の場合、合計が1/2になるように2つのグループに分けろ 整数の組み合わせで合計35.5にする事は出来ません 例題2. 5つの整数の合計が70の場合、合計が1/2になるように2つのグループに分けろ、5つの整数{36、20、10、3、1} 最大値が既に1/2の35を超えているので答えは出ません。 共に解が無い問題になっていますので、どんなに計算しても回答は出てきません。 出題の形式を変更するしかないでしょう。 例題3. 5つの整数の合計が70の場合、それぞれの合計が1/2に一番近くなる2つのグループに分けた場合の組み合わせを求めろ とか
補足
なるほど。ありがとうございます。 最後の例題 3 のように、合計が 1/2 に一番近くなる 2 つのグループに分けた組み合わせを求める、というのは、具体的にはどのようにするのでしょうか? 何度も申し訳ありませんが、宜しくお願いいたします。
- web2525
- ベストアンサー率42% (1219/2850)
考え方としては 最大値を基準に大きい方から足し算を行い1/2以上の場合は、次の数・・・・ と繰り返し1/2になった時点で終了 足した数が1/2以下の場合は最小値を足して1/2を超える場合は次の数を足す。 総当たり的な計算をする事になると思います。 必ず解が有ると言う事が条件 数値 A B C D Eが降順に並んでいるとし A+B>合計/2なら A+C<合計/2→A+C+E>合計/2なら A+D+E=合計/2 のような感じ
補足
ありがとうございます。 必ず解がある、というのは割り切れる、という意味でしょうか。 必ずしも割り切れないのですが、その場合はできないのでしょうか。
- fujillin
- ベストアンサー率61% (1594/2576)
データが5つとして、 1)3,3,3,3,3の場合 2)1,1,1,1,50の場合 などの単純に1/2にできない場合(上はほんの一例)の処置をどうするのか決めておかないと、必ず出来る保証が無い問題は解決できないのでは? あるいは、解が複数存在する場合とか…
お礼
再度バージョンアップ、ありがとうございます。 試行錯誤していただき、感激です!!(^o^)