tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトル効率的なパンデジタル数作成方法が分かりません
記事No11823
投稿日: 2017/04/18(Tue) 11:39
投稿者nis9036
時々参考にさせて貰っています。nis9036と云います。
仕事でVB.net(VS2010)を使っていますが、特殊な分野のライブラリーの使い方に通じているだけで
プログラミングは初心者です。
パンデジタル数(同じ数字が出て来ない数)の効率的な作成方法で相談させて下さい。(仕事とは関係ない事です)
パンデジタル数を作成する為に下記の繰り返し処理をやってみたのですが、3桁の場合は良いのですが、
10桁のパンデジタル数を作るとなると処理時間が膨大です。
 
        '3桁パンデジタル数作成方法1
        Dim CH() As Char = {"1", "2", "3"}
        Dim N1, N2, N3 As Integer
        ListBox1.Items.Clear()
        For N1 = 0 To 2
            For N2 = 0 To 2
                For N3 = 0 To 2
                    If N1 <> N2 And N1 <> N3 And N2 <> N3 Then          'パンデジタル条件
                        ListBox1.Items.Add(CH(N1) & CH(N2) & CH(N3))    'パンデジタル数をリストアップ
                    End If
                Next
            Next
        Next
  

効率的な作成方法をいろいろ探しているのですが、1件Javaのものを見ましたが
他言語のものは理解できませんでした。
何か参考になる様なものでもあれば教えて下さい。
宜しくお願いします。

[ツリー表示へ]
タイトルRe: 効率的なパンデジタル数作成方法が分かりません
記事No11824
投稿日: 2017/04/18(Tue) 18:11
投稿者魔界の仮面弁士
> 10桁のパンデジタル数を作るとなると処理時間が膨大です。

ListBox を使う以上、実用に耐える速度にはならないでしょう。
BeginUpdate/EndUpdate を併用したとしても、
せいぜい 32K 件程度が許容限界かと。


とりあえず、仮想リストビューに表示させてみました。
ListView、Button、CheckBox を貼っておいてください、


======= Form =======
Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        'Dim CH As Char() = "1".ToCharArray()
        'Dim CH As Char() = "12".ToCharArray()
        'Dim CH As Char() = "123".ToCharArray()
        'Dim CH As Char() = "1234".ToCharArray()
        'Dim CH As Char() = "12345".ToCharArray()
        Dim CH As Char() = "123456".ToCharArray()
        'Dim CH As Char() = "1234567".ToCharArray()
        'Dim CH As Char() = "12345678".ToCharArray()
        'Dim CH As Char() = "123456789".ToCharArray()
        'Dim CH As Char() = "1234567890".ToCharArray()

        Cursor.Current = Cursors.WaitCursor
        Dim q = CH.GetPermutation()

        '先頭が0で始まる数字を取り除くか?
        If CheckBox1.Checked Then
            q = q.Where(Function(c) c(0) <> "0"c)
        End If

        pandigital = q.Select(Function(c) New String(c)).ToArray()

        ListView1.VirtualListSize = pandigital.Length
        Cursor.Current = Cursors.Default

        MsgBox("検出件数:" & pandigital.Length.ToString("#,0"), MsgBoxStyle.Information)
    End Sub

    Private pandigital(-1) As String

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ListView1.VirtualMode = True
        ListView1.Columns.Add("Text")
        ListView1.View = View.Details
        ListView1.VirtualListSize = 0
    End Sub

    Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem
        If e.ItemIndex < pandigital.Length Then
            If e.Item Is Nothing Then
                e.Item = New ListViewItem()
            End If
            e.Item.Text = pandigital(e.ItemIndex)
        End If
    End Sub
End Class



======= Module =======
Module PermutationExtensions
    <System.Runtime.CompilerServices.Extension()> _
    Public Function GetPermutation(Of T)(this As T()) As IEnumerable(Of T())
        If this.Length = 1 Then
            Return New T()() {this}
        Else
            Return this.SelectMany( _
                Function(v) GetPermutation(this.Except(New T() {v}).ToArray()), _
                Function(v, p) New T() {v}.Concat(p).ToArray())
        End If
    End Function
End Module




なお、1133 のように、同じ文字が含まれるようなパターンについては考慮していません。
同じ文字を含むケース(11 桁以上の 10 進数など)にも対応させるのであれば、
下記が参考になるかもしれません。VB ではなく C# ですけれども。
http://d.hatena.ne.jp/taguo/20080722/1216745650

[ツリー表示へ]
タイトルRe^2: 効率的なパンデジタル数作成方法が分かりません
記事No11825
投稿日: 2017/04/19(Wed) 05:14
投稿者nis9036
魔界の仮面弁士 様
ありがとうございました。
早速試してみます。
ListBoxを使うと遅いと分かっていたので実際には配列を使っていました。
因みに18時間ぐらい廻していますが120万件ほど集まっていました。

> > 10桁のパンデジタル数を作るとなると処理時間が膨大です。
>
> ListBox を使う以上、実用に耐える速度にはならないでしょう。
> BeginUpdate/EndUpdate を併用したとしても、
> せいぜい 32K 件程度が許容限界かと。
>
>
> とりあえず、仮想リストビューに表示させてみました。
> ListView、Button、CheckBox を貼っておいてください、
>
>
> ======= Form =======
> Public Class Form1
>
>     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
>         'Dim CH As Char() = "1".ToCharArray()
>         'Dim CH As Char() = "12".ToCharArray()
>         'Dim CH As Char() = "123".ToCharArray()
>         'Dim CH As Char() = "1234".ToCharArray()
>         'Dim CH As Char() = "12345".ToCharArray()
>         Dim CH As Char() = "123456".ToCharArray()
>         'Dim CH As Char() = "1234567".ToCharArray()
>         'Dim CH As Char() = "12345678".ToCharArray()
>         'Dim CH As Char() = "123456789".ToCharArray()
>         'Dim CH As Char() = "1234567890".ToCharArray()
>
>         Cursor.Current = Cursors.WaitCursor
>         Dim q = CH.GetPermutation()
>
>         '先頭が0で始まる数字を取り除くか?
>         If CheckBox1.Checked Then
>             q = q.Where(Function(c) c(0) <> "0"c)
>         End If
>
>         pandigital = q.Select(Function(c) New String(c)).ToArray()
>
>         ListView1.VirtualListSize = pandigital.Length
>         Cursor.Current = Cursors.Default
>
>         MsgBox("検出件数:" & pandigital.Length.ToString("#,0"), MsgBoxStyle.Information)
>     End Sub
>
>     Private pandigital(-1) As String
>
>     Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
>         ListView1.VirtualMode = True
>         ListView1.Columns.Add("Text")
>         ListView1.View = View.Details
>         ListView1.VirtualListSize = 0
>     End Sub
>
>     Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem
>         If e.ItemIndex < pandigital.Length Then
>             If e.Item Is Nothing Then
>                 e.Item = New ListViewItem()
>             End If
>             e.Item.Text = pandigital(e.ItemIndex)
>         End If
>     End Sub
> End Class
>
>
>
> ======= Module =======
> Module PermutationExtensions
>     <System.Runtime.CompilerServices.Extension()> _
>     Public Function GetPermutation(Of T)(this As T()) As IEnumerable(Of T())
>         If this.Length = 1 Then
>             Return New T()() {this}
>         Else
>             Return this.SelectMany( _
>                 Function(v) GetPermutation(this.Except(New T() {v}).ToArray()), _
>                 Function(v, p) New T() {v}.Concat(p).ToArray())
>         End If
>     End Function
> End Module
>
>
>
>
> なお、1133 のように、同じ文字が含まれるようなパターンについては考慮していません。
> 同じ文字を含むケース(11 桁以上の 10 進数など)にも対応させるのであれば、
> 下記が参考になるかもしれません。VB ではなく C# ですけれども。
> http://d.hatena.ne.jp/taguo/20080722/1216745650

[ツリー表示へ]
タイトルRe^3: 効率的なパンデジタル数作成方法が分かりません
記事No11826
投稿日: 2017/04/19(Wed) 09:55
投稿者魔界の仮面弁士
> ListBoxを使うと遅いと分かっていたので実際には配列を使っていました。

ReDim Preserve を使ってはいないですよね?


> 因みに18時間ぐらい廻していますが120万件ほど集まっていました。

その計算ペースですと、終了までには 2〜3 日かかりそうですね。
PC 性能にもよりますが、先の私の方法なら 15 秒前後だと思います。


>>> 下記の繰り返し処理をやってみたのですが、3桁の場合は良いのですが、

1,2,3 の 3 桁に対して、N1 = 0〜2、N2 = 0〜2、N3 = 0〜2 のループを行っていますよね。

これはすなわち、CH の個数を x としたときに、
『x^x 回』のループが行われるということを意味します。

x = 3 ならば「27 回」のループが行われることになりますが、
それによって求まる組み合わせは、下記の「6 通り」しか
ありませんので、かなりの回数が無駄になります。
  123
  132
  213
  231
  312
  312



改めて、1,2,3 の組み合わせについて考えてみましょう。

最初の数字は、1/2/3 のいずれかですよね。
最初の数字が 1 だった場合、次の文字は 2/3 のいずれかですよね。
最初が 1 で二番目が 3 だったら、次の文字は必ず 2 ですよね。


このように、後ろの桁になるほど選択肢が狭まることがわかります。
これはすなわち、組み合わせ数が『x! 通り』であるということです。
(先の私のコードでは、Except 拡張メソッドを再帰的に呼び出すことで対処しています)


計算してみると分かりますが、『x!』と『x^x』とでは、文字通り桁違いの数となります。

n = 2 なら「2」と「4」
n = 3 なら「6」と「27」
n = 4 なら「24」と「256」
n = 5 なら「120」と「3,125」
n = 6 なら「720」と「46,656」
n = 7 なら「5,040」と「823,543」
n = 8 なら「40,320」と「16,777,216」
n = 9 なら「362,880」と「387,420,489」
n =10 なら「3,628,800」と「10,000,000,000」
n =11 なら「39,916,800」と「285,311,670,611」
n =12 なら「479,001,600」と「8,916,100,448,256」

n =16 なら「20,922,789,888,000」と「18,446,744,073,709,551,616」

n =32 なら「263,130,836,933,693,530,167,218,012,160,000,000」と
「1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976」


※パンデジタル数では、先頭の数字が 0 になることがありませんので、
 10! = 3,628,800 通りではなく、10! - 9! = 3,265,920 通りです。

[ツリー表示へ]
タイトルRe^4: 効率的なパンデジタル数作成方法が分かりません
記事No11827
投稿日: 2017/04/19(Wed) 10:19
投稿者nis9036
魔界の仮面弁士様
さてやってみようかともう一度本ツリーを覗いてみたら
もう返事が来ているので驚きました。
御手数掛けます。
Redim Preserve 使っています。と云うのもどれくらいの配列量になるのか分からなかったので一つ見付ける度に
カウントアップして配列量を増やしてやろう。と安直に考えていました。
教えて貰った方法で最初から配列数を指定できます。
頭にゼロがくる事についてはValで数値化した時に省けば良かろう。とこれまた安易に考えていました。
後ろの桁に行くほど選択肢が狭くなる事には気が付いていましたが、幾つか試して
「こんなん リストアップの所で and <> やっとるのとかわらんやんけ」と努力を放棄していました。
再帰については ”世の中にそんな方法があるらしく、どうやら不定ループをかなり簡潔に処理できるらしい”
と云う事だけ知っていました。
早速プリントアウトして試してみます。
重ね重ねありがとうございました。

> > ListBoxを使うと遅いと分かっていたので実際には配列を使っていました。
>
> ReDim Preserve を使ってはいないですよね?
>
>
> > 因みに18時間ぐらい廻していますが120万件ほど集まっていました。
>
> その計算ペースですと、終了までには 2〜3 日かかりそうですね。
> PC 性能にもよりますが、先の私の方法なら 15 秒前後だと思います。
>
>
> >>> 下記の繰り返し処理をやってみたのですが、3桁の場合は良いのですが、
>
> 1,2,3 の 3 桁に対して、N1 = 0〜2、N2 = 0〜2、N3 = 0〜2 のループを行っていますよね。
>
> これはすなわち、CH の個数を x としたときに、
> 『x^x 回』のループが行われるということを意味します。
>
> x = 3 ならば「27 回」のループが行われることになりますが、
> それによって求まる組み合わせは、下記の「6 通り」しか
> ありませんので、かなりの回数が無駄になります。
>   123
>   132
>   213
>   231
>   312
>   312
>
>
>
> 改めて、1,2,3 の組み合わせについて考えてみましょう。
>
> 最初の数字は、1/2/3 のいずれかですよね。
> 最初の数字が 1 だった場合、次の文字は 2/3 のいずれかですよね。
> 最初が 1 で二番目が 3 だったら、次の文字は必ず 2 ですよね。
>
>
> このように、後ろの桁になるほど選択肢が狭まることがわかります。
> これはすなわち、組み合わせ数が『x! 通り』であるということです。
> (先の私のコードでは、Except 拡張メソッドを再帰的に呼び出すことで対処しています)
>
>
> 計算してみると分かりますが、『x!』と『x^x』とでは、文字通り桁違いの数となります。
>
> n = 2 なら「2」と「4」
> n = 3 なら「6」と「27」
> n = 4 なら「24」と「256」
> n = 5 なら「120」と「3,125」
> n = 6 なら「720」と「46,656」
> n = 7 なら「5,040」と「823,543」
> n = 8 なら「40,320」と「16,777,216」
> n = 9 なら「362,880」と「387,420,489」
> n =10 なら「3,628,800」と「10,000,000,000」
> n =11 なら「39,916,800」と「285,311,670,611」
> n =12 なら「479,001,600」と「8,916,100,448,256」
>
> n =16 なら「20,922,789,888,000」と「18,446,744,073,709,551,616」
>
> n =32 なら「263,130,836,933,693,530,167,218,012,160,000,000」と
> 「1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976」
>
>
> ※パンデジタル数では、先頭の数字が 0 になることがありませんので、
>  10! = 3,628,800 通りではなく、10! - 9! = 3,265,920 通りです。

[ツリー表示へ]
タイトルRe^5: 効率的なパンデジタル数作成方法が分かりません
記事No11828
投稿日: 2017/04/19(Wed) 11:57
投稿者nis9036
魔界の仮面弁士様
ありがとうございました。
動きました。10桁パンデジタル数は約350万件ほど。と回答が出ました。
話しの通り十数秒で回答されたので”魔術だ!”と思いました。
プログラムの内容は初心者にはかなり厳しいので取り敢えず 使い方を理解する事を優先し
内容の理解は後回しにするつもりです。
ありがとうございました。

> 魔界の仮面弁士様
> さてやってみようかともう一度本ツリーを覗いてみたら
> もう返事が来ているので驚きました。
> 御手数掛けます。
> Redim Preserve 使っています。と云うのもどれくらいの配列量になるのか分からなかったので一つ見付ける度に
> カウントアップして配列量を増やしてやろう。と安直に考えていました。
> 教えて貰った方法で最初から配列数を指定できます。
> 頭にゼロがくる事についてはValで数値化した時に省けば良かろう。とこれまた安易に考えていました。
> 後ろの桁に行くほど選択肢が狭くなる事には気が付いていましたが、幾つか試して
> 「こんなん リストアップの所で and <> やっとるのとかわらんやんけ」と努力を放棄していました。
> 再帰については ”世の中にそんな方法があるらしく、どうやら不定ループをかなり簡潔に処理できるらしい”
> と云う事だけ知っていました。
> 早速プリントアウトして試してみます。
> 重ね重ねありがとうございました。
>
> > > ListBoxを使うと遅いと分かっていたので実際には配列を使っていました。
> >
> > ReDim Preserve を使ってはいないですよね?
> >
> >
> > > 因みに18時間ぐらい廻していますが120万件ほど集まっていました。
> >
> > その計算ペースですと、終了までには 2〜3 日かかりそうですね。
> > PC 性能にもよりますが、先の私の方法なら 15 秒前後だと思います。
> >
> >
> > >>> 下記の繰り返し処理をやってみたのですが、3桁の場合は良いのですが、
> >
> > 1,2,3 の 3 桁に対して、N1 = 0〜2、N2 = 0〜2、N3 = 0〜2 のループを行っていますよね。
> >
> > これはすなわち、CH の個数を x としたときに、
> > 『x^x 回』のループが行われるということを意味します。
> >
> > x = 3 ならば「27 回」のループが行われることになりますが、
> > それによって求まる組み合わせは、下記の「6 通り」しか
> > ありませんので、かなりの回数が無駄になります。
> >   123
> >   132
> >   213
> >   231
> >   312
> >   312
> >
> >
> >
> > 改めて、1,2,3 の組み合わせについて考えてみましょう。
> >
> > 最初の数字は、1/2/3 のいずれかですよね。
> > 最初の数字が 1 だった場合、次の文字は 2/3 のいずれかですよね。
> > 最初が 1 で二番目が 3 だったら、次の文字は必ず 2 ですよね。
> >
> >
> > このように、後ろの桁になるほど選択肢が狭まることがわかります。
> > これはすなわち、組み合わせ数が『x! 通り』であるということです。
> > (先の私のコードでは、Except 拡張メソッドを再帰的に呼び出すことで対処しています)
> >
> >
> > 計算してみると分かりますが、『x!』と『x^x』とでは、文字通り桁違いの数となります。
> >
> > n = 2 なら「2」と「4」
> > n = 3 なら「6」と「27」
> > n = 4 なら「24」と「256」
> > n = 5 なら「120」と「3,125」
> > n = 6 なら「720」と「46,656」
> > n = 7 なら「5,040」と「823,543」
> > n = 8 なら「40,320」と「16,777,216」
> > n = 9 なら「362,880」と「387,420,489」
> > n =10 なら「3,628,800」と「10,000,000,000」
> > n =11 なら「39,916,800」と「285,311,670,611」
> > n =12 なら「479,001,600」と「8,916,100,448,256」
> >
> > n =16 なら「20,922,789,888,000」と「18,446,744,073,709,551,616」
> >
> > n =32 なら「263,130,836,933,693,530,167,218,012,160,000,000」と
> > 「1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976」
> >
> >
> > ※パンデジタル数では、先頭の数字が 0 になることがありませんので、
> >  10! = 3,628,800 通りではなく、10! - 9! = 3,265,920 通りです。

[ツリー表示へ]
タイトルRe^6: 効率的なパンデジタル数作成方法が分かりません
記事No11830
投稿日: 2017/04/19(Wed) 12:13
投稿者魔界の仮面弁士
> 動きました。10桁パンデジタル数は約350万件ほど。と回答が出ました。

9! × 9 = 3,265,920 が本来の件数です。

No11826 でも述べたとおり、10桁パンデジタル数は
3,628,800 件ではありませんのでご注意ください。

※対処方法については回答済み。

[ツリー表示へ]
タイトルRe^5: 効率的なパンデジタル数作成方法が分かりません
記事No11829
投稿日: 2017/04/19(Wed) 12:02
投稿者魔界の仮面弁士
全文引用はお控えください。
引用は適切に。


> Redim Preserve 使っています。と云うのもどれくらいの配列量になるのか分からなかったので一つ見付ける度に
> カウントアップして配列量を増やしてやろう。と安直に考えていました。

通常は「配列」ではなく、ジェネリックコレクションなどを利用するようにします。

Redim Preserve は比較的低速な処理なので、仮に使うとしても、
ReDim Preserve の呼びだし回数をできるだけ減らすように心がけてください。


たとえば、足りなくなった時に 1 件ずつちまちま増やしていくのではなく、
もっと大きな単位(数百件〜数万件、あるいは現データの数パーセントなど)を
一括して確保するようにします。そして、ループ処理が終わったところで
多すぎた分を減らして調整するような作りにすると、かなり高速化するでしょう。


というのも、Redim Preserve というのは単なるサイズ変更ではなく、内部的には
 (1) 現在の配列とは別に、新しく別の配列を作成
 (2) 古い配列の内容を、新しい配列にコピー
 (3) 古い配列を解放
という 3 段階の処理が行われているからです。

要素数が数百件程度であればさほど問題になりませんが、要素数が増えるほど、
「メモリ確保」「コピー」「解放」の負荷も増加するため、
件数に応じて処理速度が加速度的に増加してしまいます。


たとえば、下記のような実験コードを用意してみました。
ReDim Preserve を 1 万回呼び出して、その処理時間を Label に表示するサンプルです。


Dim s() As String
Dim sw As Stopwatch '経過時間測定用
Dim f As Integer

ReDim s(-1) '0個から増やし始めた場合
f = s.GetUpperBound(0)
sw = Stopwatch.StartNew()
For n = 1 To 10000
    '1万個増やす
    ReDim Preserve s(f + n)
    s(f + n) = n.ToString()
Next
sw.Stop()
Label1.Text = sw.Elapsed.ToString()

ReDim s(99999) '10万個から増やし始めた場合
f = s.GetUpperBound(0)
sw = Stopwatch.StartNew()
For n = 1 To 10000
    '1万個増やす
    ReDim Preserve s(f + n)
    s(f + n) = n.ToString()
Next
sw.Stop()
Label2.Text = sw.Elapsed.ToString()


ReDim Preserve の回数は、どちらも同じ「1 万回」であるにも関わらず、
それぞの処理時間は圧倒的に異なります。

1 回目のループは、要素数が少ないので 0.1 秒程度で完了しましたが、
2 回目のループは、要素数が多いために 2.0 秒程度を要しました。


今回は 300 万件のデータを扱う必要があるので、さらに差は広がるでしょう。

100万個から始めた場合、ReDim Preserve を 1 件ずつ、1 万回増やすのに
25 秒もの時間がかかりました。

しかし最初に 101 万個の配列を確保しておき、ループ内では ReDim を
呼ばないようにすれば、僅か 0.02 秒足らずで同じ処理結果が得られます。


これは配列に限らず、文字列連結などにも言えることです。
 strData = strData & "新しい文字列"
のような処理をループ内で繰り返す場合も、
文字列が長くなるにつれて、処理速度が加速度的に低下します。

(連結処理のたびに、メモリーの再確保・複写・解放が発生するため)

[ツリー表示へ]
タイトルRe^6: 効率的なパンデジタル数作成方法が分かりません
記事No11831
投稿日: 2017/04/19(Wed) 16:23
投稿者nis9036
魔界の仮面弁士様
これは重ね重ねありがとうございました。又、申し訳ありませんでした。

> 全文引用はお控えください。
> 引用は適切に。
>


リストのあちこちを弄って反応を見たりステップインで動きを見ながら仕組みを見ています。
ハッキリ言って理解の入り口にも達していませんが、大変助かっています。
ありがとうございました。

[ツリー表示へ]