[[20230912033324]] 『再帰処理について』(とと) ページの最後に飛ぶ

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

 

『再帰処理について』(とと)

functionの再帰処理で困っています。
マクロのコードでどこを修正したらいいかアドバイスいただけたらと思います。

まずどのような処理をするか説明しますと、2桁の整数(10〜99)をランダムに二つ作って、大きい数から小さい数を引き算するひっ算をエクセルシート上にいくつも作り出すことです。これだけのことなら実現できたのですが、例えば53−23とか、49−43のように二つの整数の同じ桁の数値が同じだと引き算として簡単になってしまうので、これを回避するためにランダムに作り出した二つの整数の各桁の数値が同じ場合は、再帰処理で整数を作り直すことを考えました。
以下はそのコードです。

'=================================================================
' 10〜99の整数を二つ生成する(引き算用)
'=================================================================
Public Function 整数2桁を生成_引き算() As Long()
Dim tmp(1 To 2) As Long, tmp0 As Long
Dim cnt As Long

    For cnt = 1 To 2
        Randomize
        tmp(cnt) = Int(90 * Rnd + 10)
    Next cnt

    '大きい数値をtmp(1)のほうにする
    If tmp(1) < tmp(2) Then
        tmp0 = tmp(1)
        tmp(1) = tmp(2)
        tmp(2) = tmp0
    End If

    '二つの整数の同じ桁の値が同じ場合は再生成する
    If tmp(1) Mod 10 = tmp(2) Mod 10 _
        Or tmp(1) \ 10 = tmp(2) \ 10 Then
⇒      Call 整数2桁を生成_引き算
        Exit Function
    End If

    整数2桁を生成_引き算 = tmp()

End Function
'=================================================================

コード中の⇒の部分で再帰処理に入り、tmp(1),tmp(2)の整数が改めて作られるのに、その値は戻り値として採用されず、呼び出し元のプロシージャで「インデックスが有効範囲にありません」とエラーになってしまいます。

どうしたら再帰処理がうまくいくのでしょうか?
ほかの方法でもいいので解決のアドバイスいただけたら幸いです。

< 使用 Excel:Excel2021、使用 OS:Windows11 >


 もう1つ、戻り値としてメモリ上に残る変数を用意してみては、どうでしょう?

 Public Function 整数2桁を生成_引き算() As Long()
    Static ret(1 To 2) As Long '戻り値用の変数
    Dim tmp(1 To 2) As Long, tmp0 As Long
    Dim cnt As Long
    For cnt = 1 To 2
        Randomize
        tmp(cnt) = Int(90 * Rnd + 10)
    Next cnt
    '大きい数値をtmp(1)のほうにする
    If tmp(1) < tmp(2) Then
        tmp0 = tmp(1)
        tmp(1) = tmp(2)
        tmp(2) = tmp0
    End If
    '二つの整数の同じ桁の値が同じ場合は再生成する
    If tmp(1) Mod 10 = tmp(2) Mod 10 _
       Or tmp(1) \ 10 = tmp(2) \ 10 Then
        Call 整数2桁を生成_引き算
    Else
        ret(1) = tmp(1): ret(2) = tmp(2)
    End If
    整数2桁を生成_引き算 = ret
End Function
(まる2021) 2023/09/12(火) 07:25:41

 再帰を使わず、希望結果が出るまで無限ループするのでも

 Public Function 整数2桁を生成_引き算() As Long()
    Dim tmp(1 To 2) As Long, tmp0 As Long
    Dim cnt As Long
    Do
        For cnt = 1 To 2
            Randomize
            tmp(cnt) = Int(90 * Rnd + 10)
        Next cnt
        '大きい数値をtmp(1)のほうにする
        If tmp(1) < tmp(2) Then
            tmp0 = tmp(1)
            tmp(1) = tmp(2)
            tmp(2) = tmp0
        End If
        '二つの整数の同じ桁の値が同じ場合以外はループを抜ける
        If Not (tmp(1) Mod 10 = tmp(2) Mod 10 _
           Or tmp(1) \ 10 = tmp(2) \ 10) Then
            Exit Do
        End If
    Loop
    整数2桁を生成_引き算 = tmp
End Function
(まる2021) 2023/09/12(火) 07:36:34

 関数の結果をどうやって受けるかっていうのはいくらかやり方がありますが、

    Function sampleFunc() As String
        sampleFunc = "文字列を返します"
    End Function

 変数で受け取るのがよくやるやり方です

    Sub test1()
      Dim ret
      ret = sampleFunc '← 関数の返値を変数に代入
      Debug.Print ret
    End Sub

  Callしただけでは、結果を受け取れません

    Sub test2()
      Dim ret
      Call sampleFunc  '← 何も起こらない
      Debug.Print ret
    End Sub
 
 モジュールレベルの変数に代入するという方法もありますが。
(´・ω・`) 2023/09/12(火) 08:59:11

 上記を踏まえて、こうなります。
    Sub test()
      Dim ret() As Long
      ret = 整数2桁を生成_引き算
      Debug.Print ret(1); ret(2)
    End Sub

    Public Function 整数2桁を生成_引き算() As Long()
        ReDim tmp(1 To 2) As Long
        Dim tmp0 As Long
        Dim cnt As Long
        Randomize                    'Randomize は1回でいいです
        For cnt = 1 To 2
            tmp(cnt) = Int(90 * Rnd + 10)
        Next cnt
        '大きい数値をtmp(1)のほうにする
        If tmp(1) < tmp(2) Then
            tmp0 = tmp(1)
            tmp(1) = tmp(2)
            tmp(2) = tmp0
        End If
        '二つの整数の同じ桁の値が同じ場合は再生成する
        If tmp(1) Mod 10 = tmp(2) Mod 10 _
            Or tmp(1) \ 10 = tmp(2) \ 10 Then
            tmp() = 整数2桁を生成_引き算()
        End If
        整数2桁を生成_引き算 = tmp()
    End Function

 まる2021さんの提案のとおり、再帰はループで書き換え可能な場合が多いです
 再帰を使って何がうれしいかというと... まぁうれしいこともあるのですが、
 今回の場合は、再帰を使わない方が簡単な気がします
(´・ω・`) 2023/09/12(火) 09:10:59

 書き忘れました
 >呼び出し元のプロシージャで「インデックスが有効範囲にありません」とエラー
 この理由は、

 最後に
  整数2桁を生成_引き算 = tmp()
 とする前に Exit Funtion しているからです。返値が空になります。
(´・ω・`) 2023/09/12(火) 09:36:42

 こんな風でした。重なっているので、無駄かもしれませんん。折角書いてしまったので。
 Sub test()
     '二つの整数の同じ桁の値が同じ場合は再生成する
     Dim tmp
     Dim check As Boolean
     Randomize
     Do
         tmp = 整数2桁を生成_引き算
         check = tmp(1) Mod 10 = tmp(2) Mod 10 _
                 Or tmp(1) \ 10 = tmp(2) \ 10
     Loop While check
     Debug.Print tmp(1); tmp(2)
 End Sub

 Public Function 整数2桁を生成_引き算() As Long()
     Dim tmp(1 To 2) As Long, tmp0 As Long
     Dim cnt As Long
     For cnt = 1 To 2
         tmp(cnt) = Int(90 * Rnd + 10)
     Next cnt
     '大きい数値をtmp(1)のほうにする
     If tmp(1) < tmp(2) Then
         tmp0 = tmp(1)
         tmp(1) = tmp(2)
         tmp(2) = tmp0
     End If
     整数2桁を生成_引き算 = tmp
 End Function

(xyz) 2023/09/12(火) 10:01:15


朝からみなさんありがとうございます。
自分的にまる2021さんの再帰を使わない方法が一番しっくり来たので
採用させていただきました。
ちょっと急を要していたので助かりました。
返事をしていただいた皆様、本当にありがとうございました。
(とと) 2023/09/12(火) 14:20:14

解決しているようなので感想になりますが、すでに指摘があるように条件を満たすまでループして再抽選するのが手っ取り早いかなと思いました。
コードにすると↓のような感じになると思います。
    Sub test()
        Dim tmp(1 To 2) As Long, i As Long

        For i = 1 To 100
            Randomize
            tmp(1) = Int(90 * Rnd + 10)
            Do
                Randomize
                tmp(2) = Int(90 * Rnd + 10)
                If tmp(1) Mod 10 <> tmp(2) Mod 10 And tmp(1) \ 10 <> tmp(2) \ 10 Then Exit Do
            Loop

            ActiveSheet.Cells(i, "A").Value = WorksheetFunction.Max(tmp) & " − " & WorksheetFunction.Min(tmp) & "= ?"
        Next i
    End Sub

(もこな2 ) 2023/09/12(火) 15:00:20


 蛇足ですが一応書いておきます。

 VBAの乱数は疑似乱数です。乱数を漸化式で求めています。乱数系列と呼びます。
 乱数系列はシード値と呼ばれる値を元に計算しています。同じシード値であれば同じ順序で値が出てきます。

  Rnd関数は、1回呼び出される毎に、この乱数系列の次の値を返します
 厳密な乱数が必要な計算ではよい乱数ではないとの指摘もありますが、
 実用上十分にランダムな数値の並びになっています。

 Randmoize関数は、シード値を変更=関数系列 を変更するものです。

 1つの乱数系列を順に見ていけば、乱数として成立しています。
 乱数系列を都度変えて、系列の最初の値を次々見ていくという方法は、乱数と成立しているか不明です。

 というわけで、Randomize はRndを呼ぶループの前に一回だけ実行するのがよいです。
(´・ω・`) 2023/09/12(火) 17:15:47

(´・ω・`)さん、Randomizeの解説ありがとうございます。
難しい言葉が並んでいますが、内容は理解できました。
ありがとうございます。
(とと) 2023/09/13(水) 00:40:47

コメント返信:

[ 一覧(最新更新順) ]


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