[[20210318215744]] 『4個の数字の和を全通り出したいです。』(TATTA) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『4個の数字の和を全通り出したいです。』(TATTA)

・5個の箱A、B、C、Dがあります。
・箱の中には、30、50、70、90と書かれた玉が1個づつ入ってます。
・5個の箱から1個づつ玉を取り出した時の合計5個の数字の和は、
 小さい方から並べると以下のようになります。

和の最小は、30が5個の時の150
2番目に小さい和は、30が4個と50が1個の時の170
 :
 :
 :
和の最大は、90が5個の時の450

このように、X個の箱に入っている数字を1個づつ取り出して出来る
合計5個の数字の和を全通り表示させたいです。

■例えば以下のような手順です。
1.「箱の数」を(X個)としてA1に数値を入力する。
2.「箱の中に入っている数字の組み合わせ」をA2、A3、A4と全通り入力する。
 ※入力する数字の数は決まってません。15、35、55と3個の時もあれば、
  20、40、60、80、100と5個の時もあります。
 ※どの箱の中の数字も、2.で入力した数字共通で入ります。
  箱ごとに数字が異なることはありません。
 ※10、10、20、20のように同じ数字が重複して入力することはありません。

3.1.2.を入力したら、X個の箱から1個づつ数字を取り出したときの
 5個の数字の和をC列に全通り表示させる。

以上です。
なにとぞよろしくお願いします。

このような和の組み合わせを全て出す処理をさせたいのですが
どのようにすれば良いのでしょうか?

 

< 使用 Excel:Office365、使用 OS:Windows10 >


すみません。質問の修正です。

上記の質問で

3.1.2.を入力したら、X個の箱から1個づつ数字を取り出したときの
 5個の数字の和をC列に全通り表示させる。

で「5個の数字」と書いてますが、

※入力する数字の数は決まってません。15、35、55と3個の時もあれば、
 20、40、60、80、100と5個の時もあります。
と書いてるとおり、必ずしも5個ではありません。

「A2〜Axまで入力されたX個の数字の和」が正確な表記です。
(TATTA) 2021/03/18(木) 22:38


関数でお願いします、ということなんですか?
ご自分ではどこまでトライされていますか?
 
「全ての組み合わせ」をキーワードにして、このサイトの全文検索を実行すると
色々同種の議論がでてくるので、それを参考にしてみたら、どうですか?
例えば、
[[20141223174811]]
 
「箱の数」と箱の中の「玉の数」を明確に分けて正確に書いたらどうでしょう。
 
>・5個の箱A、B、C、Dがあります。
なんで? 4箱じゃない。
>タイトルは、「4個の数字の和を全通り出したいです。」
じゃあ、箱は四つじゃないんですか?
 
実際の「箱の数」「玉の数」はどの程度の大きさなんですか?
それによって解法も変わって来るかも知れないので、
二度手間にならないような配慮が必要でしょう。
(γ) 2021/03/18(木) 23:32

手作業(Power Query)で、トライしてみましたが
わたしには無理、使い物になりませんでした。

課題1:組み合わせ数が多いと、予想通り時間がかかる

 ・5箱、4玉であれば問題ないですが
 ・7箱、5玉だと数秒待たされます。このあたりが限界?
 ・10箱だと30分以上かかりました。
 ・愚直に全ての組み合わせを求めているので、そもそも無茶?

課題2:ステップ数が多く面倒

 ・下記手順の9)10)で、箱数可変に対応する方法がわからない
 ・そのため全工程の自動化ができない
 ・仕方がないので、箱数ごとに、クエリを用意する?

試した手順:

 1)下記のようなテーブルを作成
   テーブル名:玉

      数字
       30
       60
       70
       90

 2)1)のテーブルを選んで「データの取得と変換」-「テーブルまたは範囲から」
 3)「閉じて次に読み込む」-「接続の作成のみ」
 4)「データの取得」-「その他のデータソースから」-「空のクエリ」
 5)数式バーに入力:= 玉 
 6)「列の追加」-「カスタム列」
   カスタム列の式:= 玉
 7)5)で追加した「カスタム列」を右クリックし、「重複する列」
 8)7)を列数=箱数になるまで繰り返し
 9)2列めの見出しの右端にある展開ボタンをクリック
 10)同様に、3列め以降も展開ボタンをクリック
 11)すべての列を選択し、「列の追加」-「統計」-「合計」
 12)11)で追加された「加算」列を右クリックし、「他の列の削除」
 13)「加算」列を右クリックし、「重複の削除」
 14)「閉じて読み込む」

 参考サイト:
 Power Query クロス結合(Cross Join)
 https://qiita.com/hrkasno/items/2b928d2e4713720cf6ce

(マナ) 2021/03/20(土) 12:43


 例えば、箱の数が4個、玉が3個と決まっていれば、
 単純にFor .. Nextのループで書くことができます。

 Sub test0()
     Dim k1 As Long, k2 As Long, k3 As Long, k4 As Long
     Dim iBox    As Long
     Dim iBall   As Long
     Dim v       As Variant
     Dim total   As Long
     Dim p       As Long

     v = Range("A2:A4").Value   '玉に表示された数値
     iBall = 3
     p = 1
     For k1 = 1 To iBall
         For k2 = 1 To iBall
             For k3 = 1 To iBall
                 For k4 = 1 To iBall
                     total = v(k1, 1) + v(k2, 1) + v(k3, 1) + v(k4, 1)
                     Cells(p, "B") = total
                     p = p + 1
                 Next
             Next
         Next
     Next
 End Sub

 しかし、箱の数や、玉の数が可変だと、上記のような書き方だと
 ループ回数が変化するので、どうしてもコードの手入れが必要になってしまいます。

 それを回避するために、なんらかの工夫が必要です。
 参照サイトでの(????)さんのコードをお借りして、作成してみました。

 Sub test()
     Dim LastRow&
     Dim i&, iw&, iBox&, iBall&, j& ' & は型宣言子(As Longと同じです)
     Dim total&
     Dim cBall As Variant
     Dim kosu&

     iBox = Range("A1").Value                '箱の数

     LastRow = Cells(Rows.Count, "A").End(xlUp).Row
     cBall = Range("A2:A" & LastRow).Value   '玉に表示された数値

     iBall = UBound(cBall)                   '玉の数
     kosu = iBall ^ iBox                     '組み合わせの数

     ReDim iDim(iBox - 1)                    '作業配列
     ReDim mat(1 To kosu, 1 To 1) As Long    '結果配列

     For i = 0 To kosu - 1
         'iをiBall進数で表す
         iw = i
         For j = iBox - 1 To 0 Step -1
             iDim(j) = iw Mod iBall
             iw = (iw - iDim(j)) / iBall
         Next

         '合計の計算
         total = 0
         For j = iBox - 1 To 0 Step -1
             total = total + cBall(iDim(j) + 1, 1)
         Next
         mat(i + 1, 1) = total
     Next

     [C1].Resize(kosu, 1) = mat
     MsgBox "終了"
 End Sub
 参考にして下さい。
(γ) 2021/03/20(土) 16:44

 ここ↓のコードをお借りして書いてみましたというよりも編集しただけです。私には到底書けませんm(__)m(^^;
なので質問はお受けできません。Σ( ̄ロ ̄lll)ガーン
https://excel-ubara.com/excelvba5/EXCELVBA264.html

 Sheet2のA列の範囲から取得します。

 Option Explicit
Sub てすと()
Dim MyA As Variant
Dim MyB As Variant
Dim MyC As Variant
Dim MyD As Variant
Dim MyTemp As Variant
Dim n As Long
Dim i As Long
Dim j As Long
Dim k As Long
Dim TempStrA As String
Dim TempStrB As String
Dim MyFlg As Boolean
MyA = Application.Transpose(Sheets("Sheet2").Range("A1").CurrentRegion.Resize(, 1).Value)
n = UBound(MyA)
ReDim MyTemp(LBound(MyA) To UBound(MyA))
For i = LBound(MyA) To UBound(MyA)
    MyTemp(i) = i
Next
並び替え MyTemp, MyC
k = 1
ReDim MyD(1 To n, 1 To k)
For j = LBound(MyC, 2) To UBound(MyC, 2)
    TempStrA = ""
    TempStrB = ""
    MyFlg = True
    If j = 0 Then
        TempStrA = "1"
        TempStrB = "2"
    Else
        For i = 0 To n - 1
            TempStrA = TempStrA & "_" & MyC(i, j - 1)
            TempStrB = TempStrB & "_" & MyC(i, j)
            If i > 0 Then
                If MyC(i - 1, j) > MyC(i, j) Then
                    MyFlg = False
                End If
            End If
        Next
    End If
    If TempStrA <> TempStrB And MyFlg = True Then
        ReDim Preserve MyD(1 To n, 1 To k)
        For i = 1 To n
            MyD(i, k) = MyC(i, j)
        Next
        k = k + 1
    End If
Next
With Sheets("Sheet1")
    .Cells.ClearContents
    k = 1
    For j = LBound(MyD, 2) To UBound(MyD, 2)
        MyB = ""
        ReDim MyTemp(n - 1)
        For i = 1 To n
            MyTemp(i - 1) = MyA(MyD(i, j))
        Next
        並び替え MyTemp, MyB
        .Cells(k, 1).Resize(UBound(MyB, 2) + 1, UBound(MyB, 1) + 1) = Application.Transpose(MyB)
        k = k + UBound(MyB, 2) + 1
    Next
End With
Erase MyA, MyB, MyC, MyD, MyTemp
End Sub
Public Sub 並び替え(ByRef MyX, ByRef MyY, Optional ByVal i As Long = 1)
Dim MyTemp As Variant
Dim MyAry As Variant
Dim j As Long
Dim ii As Long
If i < UBound(MyX) Then
    For j = i To UBound(MyX)
        MyAry = MyX
        MyTemp = MyX(i)
        MyX(i) = MyX(j)
        MyX(j) = MyTemp
        並び替え MyX, MyY, i + 1
        MyX = MyAry
    Next
Else
    If IsEmpty(MyY) Or Not IsArray(MyY) Then
        ii = 0
        ReDim MyY(UBound(MyX), ii)
    Else
        ii = UBound(MyY, 2) + 1
        ReDim Preserve MyY(UBound(MyX), ii)
    End If
    For j = LBound(MyX) To UBound(MyX)
        MyY(j, ii) = MyX(j)
    Next
End If
End Sub
(SoulMan) 2021/03/20(土) 17:07

 Power Queryはなかなか慣れません。
 引き続き、投稿を拝見して学習させていただきます。

 以下、余談です。

 各ステップの処理は、手続き的ではなく、宣言的なもののようですね。

 ヘルプによると、
 >そして、リスト、レコード、テーブル メンバー式、
 >および let 式 (「式、値、および let 式」を参照) は、遅延評価 を使用して評価されます。
 とあり、「遅延評価」いうことなので、
     ・上から順次計算されるわけではない。
     ・したがって、(そんな必要もないが)仮に記載の順序を逆にしても、
       問題なく計算される。つまり、必要となったときに、その処理が行われるわけですね
     ・In の中の処理は、それが Let 内に書かれていれば、その時にはじめてそれが実行される
       ということなんですねえ。

 逆に言うと、For .. Nextなどをつかって、複数のステップを繰り返し処理するといった処理が
 簡単にはできないのかなあ、と思っています。
 こうした場合は、Haskell言語などと同様、
 「繰り返しは、基本的に「再帰」を使う」と言うことになるんでしょうか。
 なかなか難しいものですね。
(γ) 2021/03/20(土) 17:38

 おはようございます。
昨夜お布団の中でよくよく考えていましたら
わちきは大きな勘違いをしていたかもしれません。。。

 Sheet1に↓とあった時に
5
30
50
70
90

 Shtte2に↓の様に書き出します。
30	30	30	30	30	150
30	50	50	50	50	230
30	70	70	70	70	310
30	90	90	90	90	390
50	30	30	30	30	170
50	50	50	50	50	250
50	70	70	70	70	330
50	90	90	90	90	410
70	30	30	30	30	190
70	50	50	50	50	270
70	70	70	70	70	350
70	90	90	90	90	430
90	30	30	30	30	210
90	50	50	50	50	290
90	70	70	70	70	370
30	30	30	30	30	150
30	30	50	50	50	210
30	30	70	70	70	270
30	30	90	90	90	330
50	50	30	30	30	190
50	50	50	50	50	250
50	50	70	70	70	310
50	50	90	90	90	370
70	70	30	30	30	230
70	70	50	50	50	290
70	70	70	70	70	350
70	70	90	90	90	410
90	90	30	30	30	270
90	90	50	50	50	330
90	90	70	70	70	390
30	30	30	30	30	150
30	30	30	50	50	190
30	30	30	70	70	230
30	30	30	90	90	270
50	50	50	30	30	210
50	50	50	50	50	250
50	50	50	70	70	290
50	50	50	90	90	330
70	70	70	30	30	270
70	70	70	50	50	310
70	70	70	70	70	350
70	70	70	90	90	390
90	90	90	30	30	330
90	90	90	50	50	370
90	90	90	70	70	410
30	30	30	30	30	150
30	30	30	30	50	170
30	30	30	30	70	190
30	30	30	30	90	210
50	50	50	50	30	230
50	50	50	50	50	250
50	50	50	50	70	270
50	50	50	50	90	290
70	70	70	70	30	310
70	70	70	70	50	330
70	70	70	70	70	350
70	70	70	70	90	370
90	90	90	90	30	390
90	90	90	90	50	410
90	90	90	90	70	430
90	90	90	90	90	450

 Option Explicit
Sub てすと()
Dim 箱 As Long
Dim MyA As Variant
Dim MyAry As Variant
Dim x As Variant
Dim MyColumn As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim n As Long
Dim ii As Long
With Sheets("Sheet1")
    箱 = .Range("A1").Value
    ReDim x(1 To 箱)
    MyA = .Range("A2", .Range("A" & Rows.Count).End(xlUp)).Value
End With
ReDim MyAry(LBound(MyA, 1) To UBound(MyA, 1) * UBound(MyA, 1), 1 To 箱)
For i = LBound(x) To UBound(x)
    x(i) = MyA
Next
ii = 1
For n = LBound(x) + 1 To UBound(x)
    For i = LBound(MyA, 1) To UBound(MyA, 1)
        k = k + 1
        If k > UBound(MyAry, 1) Then Exit For
        For j = LBound(x) + 1 To UBound(x)
            MyAry(k, j) = x(j)(i, 1)
        Next
        MyAry(k, 1) = x(n)(ii, 1)
        If k Mod UBound(MyA, 1) = 0 Then ii = ii + 1
        If ii > UBound(MyA, 1) Then Exit For
    Next
Next
With Sheets("Sheet2")
    .Cells.Clear
    .Range("A1").Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
    For j = 2 To 箱 - 1
        増殖 MyAry, j
        .Range("A" & Rows.Count).End(xlUp).Offset(1).Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
    Next
    MyAry = .Range("A1").CurrentRegion.Value
    ReDim Preserve MyAry(LBound(MyAry, 1) To UBound(MyAry, 1), LBound(MyAry, 2) To UBound(MyAry, 2) + 1)
    For i = LBound(MyAry, 1) To UBound(MyAry, 1)
        MyAry(i, UBound(MyAry, 2)) = Application.Sum(Application.Index(MyAry, i, 0))
    Next
    .Range("A1").Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
    ReDim MyColumn(UBound(MyAry, 2) - 1)
    For i = LBound(MyAry, 2) To UBound(MyAry, 2)
        MyColumn(i - 1) = i
    Next
'    .Range("A1").CurrentRegion.RemoveDuplicates Columns:=CVar(MyColumn)
End With
Erase MyA, MyAry, x, MyColumn
End Sub
Sub 増殖(ByRef x As Variant, ByVal 箱 As Long)
Dim y As Variant
Dim i As Long
Dim j As Long
For i = LBound(x, 1) + 1 To UBound(x, 1)
    x(i, 箱) = x(i, 1)
Next
End Sub

 Sub 多段配列()
Dim 箱 As Long
Dim MyA As Variant
Dim MyAry As Variant
Dim x As Variant
Dim MyColumn As Variant
Dim i As Long
Dim j As Long
Dim n As Long
Dim ii As Long
Dim xi As Long
With Sheets("Sheet1")
    箱 = .Range("A1").Value
    ReDim x(1 To 箱)
    MyA = .Range("A2", .Range("A" & Rows.Count).End(xlUp)).Value
End With
ReDim MyAry(LBound(MyA, 1) To UBound(MyA, 1) * UBound(MyA, 1) + UBound(MyA, 1), 1 To 箱)
For i = LBound(x) To UBound(x)
    x(i) = MyA
Next
ReDim y(LBound(MyA, 1) To UBound(MyA, 1))
xi = 1
For n = LBound(x) To UBound(x)
    For i = LBound(x) + 1 To UBound(x)
        組み合わせ x(i), y, i - 1
        If xi > UBound(y) Then Exit For
        For ii = LBound(y) To UBound(y)
            MyAry((n - 1) * UBound(MyA, 1) + ii, i) = y(ii)
            MyAry((n - 1) * UBound(MyA, 1) + ii, 1) = y(xi)
        Next
        If i Mod UBound(x) = 0 Then xi = xi + 1
    Next
Next
With Sheets("Sheet2")
    .Cells.Clear
    .Range("A1").Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
    For j = 2 To 箱 - 1
        増殖 MyAry, j
        .Range("A" & Rows.Count).End(xlUp).Offset(1).Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
    Next
    MyAry = .Range("A1").CurrentRegion.Value
    ReDim Preserve MyAry(LBound(MyAry, 1) To UBound(MyAry, 1), LBound(MyAry, 2) To UBound(MyAry, 2) + 1)
    For i = LBound(MyAry, 1) To UBound(MyAry, 1)
        MyAry(i, UBound(MyAry, 2)) = Application.Sum(Application.Index(MyAry, i, 0))
    Next
    .Range("A1").Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
    ReDim MyColumn(UBound(MyAry, 2) - 1)
    For i = LBound(MyAry, 2) To UBound(MyAry, 2)
        MyColumn(i - 1) = i
    Next
'    .Range("A1").CurrentRegion.RemoveDuplicates Columns:=CVar(MyColumn)
End With
Erase MyA, MyAry, x, MyColumn, y
End Sub
Sub 組み合わせ(ByRef x As Variant, ByRef y As Variant, ByRef i As Long, Optional ByVal k As Long)
    k = k + 1
    i = i + 1
    If i > UBound(x) Then i = 1
    If k > UBound(x) Then Exit Sub
    y(k) = x(i, 1)
    組み合わせ x, y, i, k
End Sub

 まだ不完全ですけど参考出展させてください。
てすと と 多段配列 でそれぞれ実行して 縦にならべて重複の削除で削除すればそれらしくなるような(^^;
でも、、TimeUpです。途中でわけがわからなくなっちゃいました。(^^;

 難しいですね。。。Power Query で解決されそうなので何かの参考になれば幸いです。
もう寝ます。。。おやすみなさいzzzzzzzzzzzzzzzz
(SoulMan) 2021/03/21(日) 09:48

 スピードのことはさておき、箱の数を可変にしてみました。
 Power Query の経験が浅いので、思わぬ間違いがあるかと思います。
 データの前提(と、テーブル名を玉とするなど)の前提は提示頂いたものと同じです。

 【参考コード】
 let
    // ■ ここを変更してください。
    箱の数=5,                   
    ソース = Excel.CurrentWorkbook(){[Name="玉"]}[Content],
    変更された型 = Table.TransformColumnTypes(ソース,{{"数字", Int64.Type}}),

    //カスタム列を挿入
    tbl1 = Table.AddColumn(変更された型,"カスタム",each ソース),

    // 再帰関数1
    fn列追加 = (parTable as table,n as number) as table=>
         if n = 0 then 
             parTable
         else
             let
                 wtbl = Table.DuplicateColumn(
                     parTable,    
                     "カスタム", 
                     "カスタム" & Text.From(n-1)),
                 nextTable = @fn列追加(wtbl,n-1)
             in
                 nextTable,
    //列追加の実行
    tbl2 = fn列追加(tbl1, 箱の数-2),

    //再帰関数2
    fn列内テーブルの展開 = (parTbl as table, parColumnNameList as list) as table =>
         // parColumNameList が空なら, 再帰を終了
         if List.IsEmpty(parColumnNameList) then
             parTbl
         else
             let
                 // 実行すべき1列を取得
                 currentColumnName = List.First(parColumnNameList),
                 // parColumnNameListから第一列を削除する
                 nextColumNameList = List.RemoveFirstN(parColumnNameList, 1),
                 // 現在の列を展開する
                 wtbl = Table.ExpandTableColumn(
                     parTbl, 
                     currentColumnName,         //展開すべき対象列 
                     {"数字"},                   
                     { currentColumnName & ".数字"}),
                 // 再帰呼び出し  
                 nextIterationTable = @fn列内テーブルの展開(wtbl, nextColumNameList)
             in
                 nextIterationTable,

    //2列目以降の列名のリスト
    list1 = List.RemoveFirstN(Table.ColumnNames(tbl2), 1),  

    //列内テーブルの展開
    tbl3 = fn列内テーブルの展開(tbl2,list1),

    // 合計の挿入 
    tbl4 = Table.AddColumn(
                 tbl3, 
                 "合計", 
                 each List.Sum(List.Range(Record.ToList(_),0,Table.ColumnCount(tbl3)))),

    // 合計列だけ選択
    tbl5 = Table.SelectColumns(
                 tbl4,{"合計"}),

    // 重複の排除
    tbl6 = Table.Distinct(
                 tbl5)
 in
    tbl6

 # なぜだか、箱が面になっていた。寝ぼけていたのかも。
 #  15:15 コメントを修正
(γ) 2021/03/21(日) 12:08

やはり、いろんな言語に習熟した方だと
あっさり書けてしまうんですね。

「再帰」のサンプルありがとうございます。
一つの大きな壁を超えられそうな予感がします。

わたし個人のPower Queryへの期待は、
プログラミングの知識がなくても
簡単な操作だけで実現できることなので
この領域に踏み込むことに躊躇していました。
簡単にできないならマクロを使えばよいので。

でも、予想外に簡単そうだったので
ちょっと勉強してもよいかなと考え直しました。

 '----
 少し変更させていただいて、動作確認しました。
 期待通りの結果が得られました。

 1)こんなテーブルを別途用意(テーブル名:箱)
       箱数
        10

 2)接続専用クエリを追加(クエリ名:箱の数)
 let
    ソース = Excel.CurrentWorkbook(){[Name="箱"]}[Content],
    変更された型 = Table.TransformColumnTypes(ソース,{{"箱数", Int64.Type}}),
    箱数 = 変更された型{0}[箱数]
 in
    箱数

 3)そのうえで、以下のように変更

 let
     ソース = Excel.CurrentWorkbook(){[Name="玉"]}[Content],
     変更された型 = Table.TransformColumnTypes(ソース,{{"数字", Int64.Type}}),
    フィルターされた行 = Table.SelectRows(変更された型, each [数字] <> null and [数字] <> ""),
     tbl1 = Table.AddColumn(フィルターされた行,"カスタム",each フィルターされた行),

 参考サイト:
 https://tsukaeru-excel.com/parameters

(マナ) 2021/03/21(日) 16:01


 コメントありがとうございました。
 箱数を設定するクエリーを別途作成してそれを利用するわけですね。
 なるほど、なるほど。うまくいきました。

 フィルタも勉強になります。ありがとうございます。

 ■以下、余談です。(お急ぎの方はスキップください。)

 再帰を使うのかなあ、とぼんやり考えていただけです。
 Power Queryは余り使ったことがないので、コード作成は苦労しました。
 マイクロソフト作成のマニュアルだけが手元にありました。

 マナさんのコードの理解から始めました。
 ここが時間がかかりました。
 全く慣れていないので、記事をもとに実行するのに苦労しました。

 次に、当てずっぽうで再帰関数定義をやりましたが、
 型がおかしいとか、そんなものは無い、とか色々叱られました。
 最初の再帰関数はなんとか自力で書けました。フィボナッチ数列と同じ感じですか。

 途中で、英語で検索すればよいかも、と思い、
 power query recursive functionで検索したところ、
https://community.powerbi.com/t5/Community-Blog/Solving-Real-Life-Problems-with-Recursive-Functions-in/ba-p/731137
 が参考になりました。二つ目の再帰関数の骨格はそれです。

 次に苦労したのは全ての列の合計をとるところでした。
 Table.ColumnNamesを使って、名前のリストは取れますが、次に進みません。
 _  => の右辺が書けません。
 これも検索で、
https://www.mrexcel.com/board/threads/power-query-how-to-sum-all-columns-except-a-b-c.1066814/
 が参考になりました。

 each List.Sum(Record.ToList(_))),で良いのに
 each List.Sum(List.Range(Record.ToList(_),0,Table.ColumnCount(tbl3)))),
 となっているのは、その名残です。
 それにしても、Record.ToList(_)とは、気が付かなかった。

 結局、ネットの検索結果の利用ということなのかな。とほほ。
 それでも、勉強にはなりました。

 引き続き、Power Query関係記事拝見させていただきます。

(γ) 2021/03/21(日) 17:25


 こんばんは!
一晩寝てお仕事してよくよく考えましたら二乗なんですね???
3個でも10箱にすると答えが返って来ませんでした。(^^;
でも、4個の5箱ぐらいなら一応返って来ました。
でもこれを右からとか箱の順番もとなると難易度はぐぐん〜〜っとあがるんでしょうね?

 まぁ、、後は応用してください。

 では、、では、、 

 Option Explicit
Sub てすと()
Dim 箱 As Long
Dim MyA As Variant
Dim MyAry As Variant
Dim i As Long
Dim k As Long
Dim n As Long
Dim MyTimer As Single
MyTimer = Timer
With Sheets("Sheet1")
    箱 = .Range("A1").Value
    MyA = .Range("A2", .Range("A" & Rows.Count).End(xlUp)).Value
End With
If UBound(MyA, 1) ^ 箱 > Rows.Count Then
    MsgBox "処理能力を超えています。。。"
    Exit Sub
End If
ReDim MyAry(LBound(MyA, 1) To UBound(MyA, 1) ^ 箱, 1 To 箱)
k = 1
For i = LBound(MyAry, 1) To UBound(MyAry, 1)
    MyAry(i, 1) = MyA(k, 1)
    k = k + 1
    If k > UBound(MyA, 1) Then k = 1
Next
k = 1
For n = 1 To 箱 - 1
    For i = LBound(MyAry, 1) To UBound(MyAry, 1)
        MyAry(i, n + 1) = MyA(k, 1)
        If i Mod UBound(MyA, 1) ^ n = 0 Then k = k + 1
        If k > UBound(MyA, 1) Then k = 1
    Next
Next
With Sheets("Sheet2")
    .Cells.Clear
    With .Range("A1")
        .Resize(UBound(MyAry, 1), UBound(MyAry, 2)).Value = MyAry
        With .CurrentRegion
            .Offset(, .Columns.Count).Resize(, 1).Formula = "=Sum(" & .Rows(1).Address(False, False) & ")"
        End With
    End With
End With
Erase MyA, MyAry
MsgBox Format(Timer - MyTimer, "###0.000秒")

 ご指摘を受けてちょっと訂正です。。
このトピ主さん以外でも後々参考にされる方もいっらしゃるかもしれませんしね
4^10 4個10箱(1048576) で 9秒ほどでした。
あっ、悪い意味ではなくて私はスピード競技には全く興味がありませんので。。。あしからずご了承願います。(^^;
なんでもかんでも配列というかぁ、、メモリー上でするのが早いわけではないのですね。。。
わかっちゃいるけどやめられない。。。ってやつですね。。。病気です(笑)
では、、では、、、
ご指摘ありがとうございました。。。
(SoulMan) 2021/03/22(月) 22:04

 SoulManさん、コメントさせていただきます。

 玉の数が3個、箱が10個で結果が返ってこないとのこと。
 3^10 = 59,049だから2,3秒で済むんじゃないかと思いまして確認したところ、
 たしかに遅いですね。

 MyAry(i, UBound(MyAry, 2)) = Application.Sum(Application.Index(MyAry, i, 0))
 が遅くなっている原因のようです。

 どうやら、配列に対するスライス処理(部分取り出し)は、製品仕様として
 最適化が十分できていない印象です。
 (タスクマネージャを見ると、メモリの取得と開放が頻繁に起きているようです。)

 (1)配列に対して、原始的にループで合計をとったほうが早く確実かも知れません。
 (2)どうしてもIndex処理を使うなら、
    いったんワークシートに書き出してから、
    mat(i, UBound(mat, 2)) = Application.Sum(Application.Index(rng.Rows.Item(i).Value, 0#))
    のような書き方をすると、たぶん今の10倍以上は早くなります。(不思議ではあります)
    数秒で答えが返って来ると思います。

 ■ちなみに、質問者さんから反応が無いのはとても残念です。

 質問内容が、
 (1)重複を除いた和の一覧だけでよいのか、
 (2)重複を厭わず、すべての和の結果が欲しいのか、
 (3)さらに、その時の、取り出した玉の数値も同時に必要なのか、   
 が不明確でした。

 ・私 の 2021/03/20(土) 16:44 の回答の後半部分のコードは、(2)のベースです。
 ・マナさんのPowerQueryを使用したものは、(1)のベースです。
   途中経過に(2)も(3)もあります。
 ・SoulManさんのは、(3)のベースです。

 私のコードも(3)のベースにできますが、反応が無いので躊躇しています。
 また、(1)は、和の結果をdictionaryに突っ込めば良いだけです。   

(γ) 2021/03/23(火) 08:27


 おはようございます
やっぱりその部分ですね
普段の私だったらシートに関数をセットしただろうと思います
でも疲れていたんでしょうね
基本使い回しですから(^^;;
そうですねトピ主さんどうされたんでしょうね
まぁ、私は自分の暇つぶしに書いてますからいいんですけどね
また、よろしくお願いします
(SoulMan) 2021/03/23(火) 08:50

面白そうな題材なのでチャレンジしてみました。

(1)重複を除いた和の一覧だけをシートに出力

という仕様にしました。
(その方が題材として面白い、工夫の余地か大きい、という個人的な理由です。)

「n個の数字を重複を許可してr回取り出す」という命題になると思います。
重複順列ですね。エクセルの関数ですとPERMUTATIONA。

?WorksheetFunction.Permutationa(4,5)
1024
?WorksheetFunction.Permutationa(4,10)
1048576

累乗でも計算できます。
?4^10
1024
?4^10
1048576

4数字、10箱だとちょうどシートの最大行数ですね。

列挙するには再帰を使うか、n進数を使う方法がおもいつきますが、簡単な後者でやってみます。
n進数は BASE関数で変換できますのでそれを利用します。
Dictionaryに合計を格納して重複を排除します。

 Public Sub Test1()
    Dim st As Single: st = Timer()

    Dim dic As New Dictionary
    Dim ary()
    ary = Worksheets(1).Cells(1, 1).CurrentRegion.Value

    Dim n As Long, r As Long
    n = UBound(ary) - 1
    r = ary(1, 1)

    Dim i As Long
    For i = 0 To n ^ r - 1
        Dim s As String, j As Long, sum As Long
        sum = 0
        s = WorksheetFunction.Base(i, n, r)
        For j = 1 To Len(s)
            sum = sum + ary(Mid(s, j, 1) + 2, 1)
        Next
        dic(CStr(sum)) = sum
    Next

    Worksheets(1).Cells(1, 3).Resize(dic.Count) = WorksheetFunction.Transpose(dic.Items)

    Debug.Print Format(Timer() - st, "0.0000000")
 End Sub

4数字10箱で当方の環境では7秒台でした。
(hatena) 2021/03/24(水) 11:09


続きです。

4数字10箱だとループ数が1048576となります。
和をだすだけなら順番は関係ないので無駄が多いですね。
30 30 30 30 50 も 30 30 30 50 30 も和は同じなので。

順番が関係ないなら順列ではなく組み合わせです。
重複組み合わせはエクセル関数なら、COMBINAです。

?WorksheetFunction.Combina(4,10)
286

4数字10箱でも286ループで済むので大幅に高速化できそうです。
しかし、重複組み合わせの列挙は難しそうです。
とりあえず検索してみました。ヒットしたのが下記のページ。

http://www.mathlion.jp/article/ar101.html

A,B,C,Dの4種類の記号から,重複を許して3個選ぶ組合せの総数を
出す場合の考え方として下記のような説明と

重複組合せの総数を知るためのアイデアは,r 個の◯と
n−1本のしきり|を一列に並べる,というものです.

下記のような図がありました。

{A,A,B} ←→ ◯〇|〇||
{B,C,D} ←→|〇|〇|〇

左と右は一対一に対応するということです。

これをみてふと気づきました。
右の順列は2進数ではないかと。

2進数の桁数は、n+r-1桁になるということなので、

4数字10箱なら、4+10-1=13桁の2進数、
2^13 = 8192ループで済むということになります。
COMBINAの286よりは大きいですが、重複順列の1048576に比べたら
桁違いに小さいです。

2進数利用のコードです。
BASE関数で2進数変換、Dictionalyで重複排除の部分は前回とほぼ同じです。
合計計算が複雑になるので別関数にしました。

 Public Sub test2()
    Dim st As Single: st = Timer()

    Dim dic As New Dictionary
    Dim ary()
    ary = Worksheets(1).Cells(1, 1).CurrentRegion.Value

    Dim n As Long, r As Long, s As String
    n = UBound(ary) - 1
    r = ary(1, 1)

    Dim i As Long, res()
    For i = 0 To 2 ^ (n + r - 1)
        s = WorksheetFunction.Base(i, 2, n + r - 1)
        If Len(Replace(s, "1", "")) = r Then
            Dim d As Long
            d = sum2(s, ary)
            dic(CStr(d)) = d
        End If
    Next
    Worksheets(1).Cells(1, 3).Resize(dic.Count).Value = WorksheetFunction.Transpose(dic.Items)

    Debug.Print Format(Timer() - st, "0.0000000")
 End Sub

 Public Function sum2(s As String, ary) As Long
    Dim i As Long, j As Long
    j = 2
    For i = 1 To Len(s)
        If Mid(s, i, 1) = 1 Then
          j = j + 1
        Else
          sum2 = sum2 + ary(j, 1)
        End If
    Next
 End Function

4数字10箱で0.04秒台、
想像以上の速度でした。(@_@;)

(hatena) 2021/03/24(水) 12:22


 こんばんは!
なんだかいい感じになってきましたねぇ(^^;
私には全然わかりませんが、これこそ学校って感じですね。
勉強になります。(わかってませんが_| ̄|○)

 目にも止まらぬ速さなのでケンシロウのこぶしの様なかんじでしょうか?
Power QueryといいExcelの進化についていけてないですね。精進しなければ。。。ですが、、もう無理です。Σ( ̄ロ ̄lll)ガーン
私は、雲の・・・でいきます(^^;

 トピ主さんは、ひょっとして前半部分まではチャレンジされていて hatena さんの様な回答を期待されていたのかもしれませんね
まぁ、、関心を持たれる方の多い題材でしょうから是非ぜひ勉強して習得いたいですね。_〆\(..;) メモメモ
ありがとうございました。
(SoulMan) 2021/03/24(水) 19:41

すみません。
質問させていただいた者です。

最初の質問後に、書き方が分かりづらいとのご指摘を受け、
週末に考えなおしていたのですが、体調を崩してしまい
皆様のご返答を確認させていただくことが出来ませんでした。
さまざまな方からご回答いただきましたのに大変申し訳ありません。

皆様の回答を拝見させていただきましたが、非常に専門的な内容と
なっていて、現在回答いただいていることを一つ一つ理解しようと
しています。

回答いただけた中で解決できる内容が確認できれば
改めて、お礼も含めて発言させていただきます。
皆様まことにありがとうございます。

(TATTA) 2021/03/24(水) 20:00


 質問者さん 復帰おめでとうございます。

 早速ですが、
 質問内容が、
  (1)重複を除いた和の一覧だけでよいのか、
  (2)重複を厭わず、すべての和の結果が欲しいのか、
  (3)さらに、その時の、取り出した玉の数値も同時に必要なのか、   
  が不明確でした。
 は、どうだったのでしょうか?

 hatenaさんの考察素晴らしいです。

 重複を除いて和の一覧でよく、
 しかも、玉に書いてある数字に重複はない(そう書いてある気がしますが)、
 という前提であれば、強引に計算してしまう手もあると思います。

 Option Explicit
 Sub test()
     Dim j&, k&
     Dim hakosu&
     Dim dic As Object

     Dim t
     t = Timer
     worksheets(1).select
     hakosu = Cells(1, 1)
     ReDim dicary(1 To hakosu)

     Set dic = CreateObject("Scripting.Dictionary")

     For j = 2 To Cells(1, 1).End(xlDown).Row
         dic(Cells(j, 1).Value) = Empty
     Next
     Set dicary(1) = dic

     For k = 2 To hakosu
         Set dicary(k) = fsub(dicary(k - 1), dicary(1))
     Next

     [C1].Resize(dicary(hakosu).Count, 1) = Application.Transpose(dicary(hakosu).keys)
     Debug.Print Timer - t; Tab(20); "γ"
 End Sub

 Function fsub(obj1, obj2) As Object
     Dim obj As Object, e1, e2
     Set obj = CreateObject("Scripting.Dictionary")
     For Each e1 In obj1
         For Each e2 In obj2
             obj(e1 + e2) = Empty
         Next
     Next
     Set fsub = obj
 End Function

 玉の数を4個として
 箱数=10ならhatenaさんのとほぼ同程度の速度だと思います。
 箱数=15ならhatenaさんのよりも、速度は大分速いです。
 (比較が同等でなかったらすみません)

(γ) 2021/03/24(水) 21:16


 なるほど、、これは多段ディクショナリー&インクリメントとでもいうのでしょうか???
新手の方法ですね(^^;

 これは早くも今年一番の○○トピになるかもしれませんよぉ(気が早い(^^;)
素晴らしいです。
(SoulMan) 2021/03/24(水) 21:46

おお、盛り上がってきましたね。
質問者さんも戻ってきて、お役に立てるといいですね。
私自身は、自分の好奇心でやってますので大満足です。

γさん、素晴らしい。
そういう発想は私自身にはまったくなかったので刺激になります。
まだ、動作原理が理解できてませんが、ゆっくり精査させていただきます。

実は、重複組み合わせを再帰でやるのにチャレンジしてました。

いろいろ検索しても組み合わせの列挙のコード例は見つかりますが、
重複組み合わせのは見つからない。

そこで重複なし組み合わせのコードで一番シンプルなものを参考に
いろいろいじっていたら、できてしまいました。
参考にしたコード
https://teratail.com/questions/281147#reply-400587

上記のnCr関数が再帰関数なんですが、恐ろしくシンプルで、
これでいいのか不安になるぐらいです。
ix - 1 の部分の - 1 をとっただけで重複組み合わせになりました。

これをもとに、再帰の中で合計していって、
Dictionaryに格納するようにしました。

 Option Explicit
 Dim ary() As Variant
 Dim dic As Dictionary

 Private Sub Test3()
    SWStart
    Set dic = New Dictionary

    ary = Worksheets(1).Cells(1, 1).CurrentRegion.Value

    Dim n As Long, r As Long, s As String
    n = UBound(ary) - 1
    r = ary(1, 1)

    Call CombinRSum(n, r, 0)

    Worksheets(1).Cells(1, 4).Resize(dic.Count).Value = WorksheetFunction.Transpose(dic.Keys)

    Set dic = Nothing

    SWStop
    SWShow "hatena"
 End Sub

 Private Function CombinRSum(ByVal n As Long, ByVal r As Long, ByVal Num As Long)
    If (r = 0) Then
        dic(Num) = Empty
    Else
        Dim ix As Long
        For ix = n To 1 Step -1
            Call CombinRSum(ix, r - 1, ary(ix + 1, 1) + Num)
        Next
    End If
 End Function

※下記の部分は、

    SWStart

    SWStop
    SWShow "hatena"

https://hatenachips.blog.fc2.com/blog-entry-377.html
で紹介している高精度処理時間計測関数です。
マイクロ秒単位の精度がでます。

γさんのも含めてここまでくると Timer関数の分解能では不足している
と思いますので。

出力結果は Test1、 Test2とγさんのと同じになったので正しいと思います。
処理時間は γさんのより若干速いようです。(こちらの環境では)

(hatena) 2021/03/24(水) 23:12


(γ)様
ありがとうございます。

ご質問について
望んでいた出力結果は(SoulMan)様が 3/21(日) 09:48に
[Sheet1]と[Sheet2]として記されている形が近いです。

箱A(30、50、70、90)
箱B(30、50、70、90)
箱C(30、50、70、90)
箱D(30、50、70、90)
箱E(30、50、70、90)

この状態で、
・箱A〜Eから1個ずつ取り出した数字5個の和を
 構成する5個の数字とともに全通り出す。

30 30 30 30 30  150
30 50 50 50 50  230
30 70 70 70 70  310
30 90 90 90 90  390
50 30 30 30 30  170

※5個の数字の組み合わせはユニークのみ

30、30、50、50、50=210
90、30、30、30、30=210

和は同じでも構成する数字が異なるので両方出す。

30、30、30、30、50=170
50、30、30、30、30=170

和は同じで構成する数字も順番が異なるだけなので一方のみを出す。

という感じを希望していました。

また、
「強引に計算してしまう手もあると思います。」

こちらはまさにおっしゃる通りで、
最初はそれこそ原始的に、全ての数字の組み合わせを出して計算したのですが
この計算を、質問時に出した数字の組み合わせだけでなく、また、
組み合わせる数字の種類も5個だけでなく、3個や6個となった時にも
応用できる計算方法や処理方法が無いかと思って質問させていただきました。
ただ、思った以上に専門的な領域のようで、勉強不足な状態で質問をしてしまい
申し訳無いです。

(TATTA) 2021/03/24(水) 23:27


重複組み合わせそのものの出力ということですね。

ならば前回の回答のコードの簡単な修正で可能です。

とりあえずSheet1に出力
C列にカンマ区切りで数値の組み合わせ
D列に合計値という仕様になってます。
大きい数値からの出力になります。

 A	B            	C
 5	90,90,90,90,90	450
 30	70,90,90,90,90	430
 50	50,90,90,90,90	410
 70	30,90,90,90,90	390
 90	70,70,90,90,90	410
	50,70,90,90,90	390
        ・・・・・・

1つの数値は1セルでということなら、出力後に「区切り位置」機能で分割してください。

 Option Explicit
 Dim ary() As Variant '入力データ配列
 Dim res() As Variant '結果配列
 Dim rx As Long '出力位置

 Private Sub Test4()

    ary = Worksheets(1).Cells(1, 1).CurrentRegion.Value

    Dim n As Long, r As Long, c As Long
    n = UBound(ary) - 1 'データ数
    r = ary(1, 1)       '取り出し数
    c = WorksheetFunction.Combina(n, r) '重複組み合わせ数

    ReDim res(1 To c, 1 To 2)
    rx = 0
    Call CombinRSum(n, r, "", 0)

    Worksheets(1).Cells(1, 2).Resize(c, 2).Value = res

 End Sub

 Private Function CombinRSum(ByVal n As Long, ByVal r As Long, ByVal txt As String, ByVal Num As Long)
    If (r = 0) Then
        rx = rx + 1
        res(rx, 1) = Mid$(txt, 2)
        res(rx, 2) = Num
    Else
        Dim ix As Long
        For ix = n To 1 Step -1
            Call CombinRSum(ix, r - 1, "," & ary(ix + 1, 1) & txt, ary(ix + 1, 1) + Num)
        Next
    End If
 End Function

(hatena) 2021/03/25(木) 00:53


hatenaさん、ありがとうございます。
高精度処理時間計測関数は知りませんでしたので、活用させていただきます。
 
質問者さんの仕様にあった最終版ができましたですね、
すばらしいです。
nHr用の再帰ロジックもよく勉強させていただきます。(概ね理解したつもりですが)
 
今回のテーマは、PowerQueryや、配列中のスライス処理の予想外の遅さなども含めて、
私には大変得るところが多かったですね。
皆様、ありがとうございました。また、よろしくお願いします。

(γ) 2021/03/25(木) 08:21


(hatena)様(γ)様(SoulMan)様(マナ)様

素晴らしい回答をありがとうございました。
私一人では絶対にたどりつけなかったです。
本当に助かりました。

教えていただいたやり方を一つ一つ勉強して、
自分でも考えられる知識を身につけていきたいと思います。

本当にありがとうございました。

(TATTA) 2021/03/27(土) 22:05


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.