デイ3のランダムゴルフ:整数分割


19

シリーズについて

まず、これを他のコードゴルフチャレンジと同様に扱い、シリーズについてまったく心配することなく答えることができます。ただし、すべての課題にリーダーボードがあります。最初の投稿では、リーダーボードとシリーズに関する詳細情報を見つけることができます。

このシリーズにはたくさんのアイデアが並んでいますが、将来の課題はまだはっきりしていません。何か提案があれば、関連するサンドボックスの投稿でお知らせください。

穴3:整数分割

難易度を少し上げる時間です。

パーティション正の整数のは、nその和に正の整数のマルチセットとして定義されますn。例として、n = 5次のパーティションが存在します。

{1,1,1,1,1}
{2,1,1,1}
{2,2,1}
{3,1,1}
{3,2}
{4,1}
{5}

これらはマルチセットであるため、順序はなく、{3,1,1}{1,3,1}あり{1,1,3}、すべて同一であると見なされます。

あなたのタスクは、nのランダムなパーティションを生成することですn。詳細なルールは次のとおりです。

  • 生成されるパーティションの分布は均一でなければなりません。つまり、上記の例では、各パーティションは1/7の確率で返される必要があります。

    もちろん、PRNGの技術的な制限により、完全な均一性は不可能です。提出物の均一性を評価するために、次の操作は完全に均一な分布をもたらすと見なされます。

    • (ほぼ)均一であることが文書化されているPRNGから(任意の範囲で)数値を取得する。
    • モジュロまたは乗算(または値を均等に分散する他の演算)を介して、大きな数のセットにわたる均一な分布を小さなセットにマッピングします。大きいセットには、小さいセットの少なくとも1024倍の値が含まれている必要があります。
  • パーティションはマルチセットであるため、任意の順序でパーティションを返すことができ、この順序は一貫している必要はありません。ただし、ランダム分布の目的では、順序は無視されます。つまり、上記の例では{3,1,1}{1,3,1}とが{1,1,3} 一緒に返される確率は1/7でなければなりません。

  • アルゴリズムには確定的なランタイムが必要です。特に、ランダムなマルチセットを生成し、合計がでない場合は拒否できませんn
  • アルゴリズムの時間計算量はの多項式でなければなりませんn。特に、すべてのパーティションを単純に生成してランダムに選択することはできません(パーティションの数は指数関数的に増加するためn)。使用しているPRNGは、値ごとにO(1)で均一に分布した値を返すことができると仮定できます。
  • このタスクを解決する組み込み関数を使用しないでください。

完全なプログラムまたは関数を作成し、STDINまたは最も近い代替、コマンドライン引数または関数引数を介して入力を取得し、戻り値またはSTDOUT(または最も近い代替)に出力して出力を生成できます。

n ≤ 65(パーティションの数が2 21未満であると)仮定することができます。出力は、便利で明確なリスト形式または文字列形式です。

関数を送信する場合は、関数を何度も呼び出して結果を出力する小さなテストプログラムの提供も検討してください。コード内でパラメーターを調整する必要がある場合でも問題ありません。これは、ソリューションが少なくともほぼ均一であることを人々が確認できるようにするためです。

これはコードゴルフであるため、最短の提出(バイト単位)が優先されます。そしてもちろん、ユーザーごとの最短投稿もシリーズの総合リーダーボードに入ります。

リーダーボード

シリーズの最初の投稿はリーダーボードを生成します。

回答が表示されるようにするには、次のマークダウンテンプレートを使用して、すべての回答を見出しで開始してください。

# Language Name, N bytes

N提出物のサイズはどこですか。スコアを改善する場合、古いスコアを打つことで見出しに残すことができます。例えば:

# Ruby, <s>104</s> <s>101</s> 96 bytes

(言語は現在表示されていませんが、スニペットはそれを必要とし、解析します。将来、言語ごとのリーダーボードを追加するかもしれません。)

回答:


8

Python 2、179バイト

from random import*
m=r=input();i=q=r+1;h=[1]+[0]*q*q;exec"h[i]=h[i+~q]+h[i-i%q*q];i+=1;"*r*q
while r:
 x=random()*sum(h[r*q:r*q-~m]);m=0
 while x>0:m+=1;x-=h[r*q+m]
 print m;r-=m

このKnuth抽出の式(39)を使用しました。これは、n正確にmパーツを持つパーティションの数を示します。これはたまたま最大の要素としてn持っているパーティションの数に等しくなりmます。これは私が使用している解釈です。パーティションの要素は、最大から最小まで生成されます。各段階で、式は現在の残りのn要素と最大許容要素で再利用されます。


5

Dyalog APL、67 59 51バイト

p←{⍵,⊂1,⍨+/¨⌽⍵↑¨⍨⌽⍳⍴⍵}⍣⎕⊢⍬⋄f←{⍵=0:⍬⋄a,a∇⍵-a←{1++/(?+/⍵)>+\⍵}⍺↑⍵⊃p}⍨ (67バイト)

pは、被加数へp[n][k]のパーティションの数、または同等に最大の被加数のパーティションの数であるベクトルのベクトルです。私たち構築空ベクターを開始することにより、読み(入力を読み込み)を繰り返し、以下を適用します:nkkpn

{⍵,⊂1,⍨+/¨⌽⍵↑¨⍨⌽⍳⍴⍵}
                 ⍴⍵   ⍝ the current length, initially 0
                ⍳⍴⍵   ⍝ 1 2 ... length
               ⌽⍳⍴⍵   ⍝ length ... 2 1
           ⍵↑¨⍨       ⍝ take length elements from p[1], length-1 from p[2], etc
                      ⍝ padded with 0-s, e.g. if p was (,1)(1 1)(1 1 1)(1 2 1 1)(1 2 2 1 1):
                      ⍝ we get:     (1 0 0 0 0)(1 1 0 0)(1 1 1)(1 2)(,1)
          ⌽           ⍝ reverse it: (,1)(1 2)(1 1 1)(1 1 0 0)(1 0 0 0 0)
       +/¨            ⍝ sum each:   1 3 3 2 1
    1,⍨               ⍝ append 1:   1 3 3 2 1 1
 ⍵,⊂                  ⍝ append the above to the vector of vectors

nのアプリケーション(⍣⎕)、我々は構築していますp

fランダムパーティションを選択します。 最大で被加数のn f kランダムパーティションです。 です。 kf nn f n

{⍵=0:⍬⋄a,a∇⍵-a←{1++/(?+/⍵)>+\⍵}⍺↑⍵⊃p}⍨
                                     ⍨ ⍝ "selfie" -- use n as k if no k is provided
 ⍵=0:⍬                                 ⍝ if n=0 return empty
                                 ⍵⊃p   ⍝ pick the n-th element of p
                               ⍺↑      ⍝ take k elements from that
               {1++/(?+/⍵)>+\⍵}        ⍝ use them as weights to pick a random number 1...k
               {           +\⍵}        ⍝   partial sums of weights
               {    (?+/⍵)    }        ⍝   a random number 1...sum of weights
               {    (?+/⍵)>+\⍵}        ⍝   which partial sums is it greater than?
               {  +/          }        ⍝   count how many "greater than"-s
               {1+            }        ⍝   we're off by one
             a←                        ⍝ this will be the greatest number in our partition
         a∇⍵-a                         ⍝ recur with n1=n-a and k1=a
       a,                              ⍝ prepend a

いくつかの改善:

  • インラインpでは、パフォーマンスはわずかに低下します(ただし、それでも十分です)

  • 計算でのp再配置1,文字を保存します

  • ターン{1++/(?+/⍵)>+\⍵}で電車に1+前に:1+(+/(?+/)>+\)

  • 完全なプログラムを取得するためにf、匿名関数を作成し、引数として(評価済みの入力)を提供します

{⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨⎕ (59バイト)

n = 5でテスト

n = 65でテスト

また、次のリンクはn = 5千回実行され、各パーティションの頻度に関する統計を収集します。 ⎕rl←0 ⋄ {⍺,⍴⍵}⌸ {⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨ ¨10000⍴5


Roger Huiの助けを借りて、さらに改善されました。

  • 置き換える{⍵=0:A⋄B}{×⍵:B⋄A}。Signum(×⍵)は、に対してtrueを返し、に対して⍵>0falseを返します⍵=0

  • 交換する(+/(?+/)>+\)+/b<?⊃⌽b←+\、それは文字を保存し、

  • 計算する代わりに、ベクトルのベクトルの行列を使用してp置き換えます⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1

{×⍵:a,a∇⍵-a←1++/b<?⊃⌽b←+\⍺↑⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1⋄⍬}⍨ (51バイト)

テストn = 5 ; テストn = 65 ; 周波数統計


2
ロジャー・ホイからどのように助けを得るのですか?
FUZxxl

5
おもちゃのAPLインタープリターを作成して、彼と同じ会社に就職してください。挑戦として上記の表現をポーズし、彼が取り出すすべてのキャラクターにビールのパイントを約束します。それから利益:彼がビールを飲まないのでより少ない特性およびより多くの酒。
ngn

1
そうですか。Dyalog APLは、Jのような何かを得るために起こっている場合には、あなたが彼に尋ねることができます...私はそれを再現することができるかどうかを見てみましょうきちんと戦略だu/\. yいつでもすぐに?
-FUZxxl


彼に聞いてくれてありがとう。今では、線形時間でもそれが可能かどうか疑問に思います。
FUZxxl

4

GolfScript、90バイト

~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`

オンラインデモ

これは、(単純な)パーティションカウントコードの適応であり、単にカウントを追跡するのではなく、カウントとカウントされた要素の一様に選択された1つを追跡します。

2つの比較比較:

~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`
 [[ 1  ]]\({..[[{          1$,1$,-=}%  0 @0=+     ]zip{{+}*                }:^%]\+}*0=^

違い:

  • 初期には、~このプログラムではなく、スニペットがあるためです。
  • [1.]交換1追跡何の変化に対応しています。
  • 追加{(\{)}%+}%はそのパーティション内の各要素をインクリメントし、パーティションに{1+}%追加1します。
  • 0は追跡対象の変更の一部として[0](にゴルフ1,)されますが、別の配列の前に追加するときに配列を維持する必要があるため、extraが必要[ ]です。
  • 単純な合計{+}*は、パーティションからの重み付けされた選択になり、それらのカウントの合計と組み合わされます。
  • (;`出力からのカウントを削除し、素敵なフォーマットにパーティションを置きます。

テストフレームワーク

;7000,{;
  '5'

  ~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`

}%
:RESULTS
.&${
  RESULTS.[2$]--,' '\n
}/

別の試行回数を実行する場合は、最初の7000を微調整します。これはオンラインデモには遅すぎることに注意してください。


3

Java、285 267バイト

int[][]p;void p(int n){p=new int[n+1][n+1];int a=n,b=k(n,a),c,d;for(b*=Math.random();n>0;System.out.print(c+" "),n-=a=c)for(c=0;c++<(a<n?a:n)&b>=(d=k(n-c,c));b-=d);}int k(int n,int k){if(p[n][k]<1)for(int a=0,b=0;b<k&b++<n;p[n][k]=a)a+=k(n-b,b);return n>0?p[n][k]:1;}

これはTheBestOneの答えと同じ方法ですが、マップの代わりに単純な配列を使用します。また、ランダムパーティションをaとして返す代わりに、Listそれらをコンソールに出力します。

以下は、それを100000回実行するテストプログラムです。例ではn=5、すべてのセットは、最後の実行での完全な1/7の0.64%以内でした。

public class Partition {
    public static void main(String[] args) {
        Partition p = new Partition();
        for(int i=0;i<100000;i++){
            p.p(5);
            System.out.println();
        }
    }

    int[][]p;

    void p(int n){
        p=new int[n+1][n+1];
        int a=n,b=k(n,a),c,d;
        for(b*=Math.random();n>0;System.out.print(c+" "),n-=a=c)
            for(c=0;c++<(a<n?a:n)&b>=(d=k(n-c,c));b-=d);
    }

    int k(int n,int k){
        if(p[n][k]<1)
            for(int a=0,b=0;b<k&b++<n;p[n][k]=a)
                a+=k(n-b,b);
        return n>0?p[n][k]:1;
    }

}

3
あなたがgolfedてきたもののMath.minまでの呼び出しを(k<n?k:n)、あなたはそれを完全に捨て、ちょうど2つのチェックを行うことによって、さらに行くことができますb<k&b++<n。またn>0、ループの一部を条件付きで簡単に捨てることができます(非負であることが保証n>0&b<nされるのはb<nいつになるかbです)。
ピーターテイラー

@PeterTaylorありがとう。別の表情を取ることは、私が余分なreturn文と別々の解消を取得しましょうintも宣言。
ジオビット

3

CJam、64 56バイト

ri_L{_0>{\,f{)_@1$-j+}{)@)2$+:Umr@<@@?U+}*}{!a\;}?}2j);p

このスクリプトでテストできます:

ria100*{_L{_0>{\,f{)_@1$-j+}{)@)2$+:Umr@<@@?U+}*}{!a\;}?}2j);}%__|\f{_,\2$a-,-}2/p

説明

ri_                  " Read an integer and duplicate. ";
L{                   " Create a memoized function of the maximum and the sum, which returns
                       a random partition, and the total number of partitions as the last item. ";
    _0>              " If sum > 0: ";
    {
        \,f{         " For I in 0..max-1: ";
            )_@1$-   " Stack: I+1 I+1 sum-I-1 ";
            j+       " Recursively call with the two parameters, and prepend I+1. ";
        }
        {            " Reduce on the results: ";
            )@)2$+   " Stack: partition1 total1 partition2 total1+total2 ";
            :Umr     " U = total1+total2, then generate a random number smaller than that. ";
            @<@@?    " If it is <total1, choose partition1, else choose partition2. ";
            U+       " Append the total back to the array. ";
        }*
    }
    {!a\;}?          " Else return [0] if negative, or [1] if zero. ";
}2j
);p                  " Discard the total and print. ";

2
あなたはあなたの答えの間違った「非常にうまくゴルフされていない」部分を削除する必要があります;)
アナトリグ

@anatolygが削除されました。しかし、いくつかのバイトを削除することはまだ可能であると信じています。私はそれをするのが面倒です。
jimmy23013

3

Pyth、64バイト

/programming//a/2163753/4230423を使用します。ただし、a)Pythが自動的にメモするためキャッシュがなく、b)リストに追加する代わりにそれぞれを印刷し、c)Pythに変換されます。

M?smg-Gddr1hhS,GHG1Akd,QOgQQWQFNr1hhS,QkKg-QNNI<dKB-=dK)N=kN-=QN

時間があればこの説明を投稿しますが、対応するpythonコードは次のとおりです。

g=lambda G,H: sum(map(lambda d:g(G-d, d), range(1, (H if H<G else G) + 1))) if G else 1
Q=input()
k,d = Q,random.randrange(g(Q, Q))
while Q:
    for N in range(1, min(k, Q) + 1):
        K = g(Q-N, N)
        if d < K:
            break
        d -= K
    print N
    k=N
    Q -= N

編集:私はついに説明をするようになりました:

M                Lambda g(G,H)
 ?         G     If G truthy
  s              Sum
   m             Map
    g            Recursive call
     -Gdd        G-d,d
    r            Range
     1           1 to
     h           +1
      hS         First element of sorted (does min)
       ,GH       From G and H
   1             Else 1
A                Double assign
 kd              Vars k and d
 ,               To vals
  Q              Q (evaled input)
  O              Randrange 0 till val
   gQQ           Call g(Q, Q)
WQ               While Q is truthy
 FN              For N in
  r              Range
   1             From one
   h             Till +1
    hS,QK        Min(Q,K)
  Kg             K=g(
   -QN           Q-N
   N             N
  I<dK           If d<k
   B             Break (implicit close paren)
  -=dk           Subtracts d-=k
 )               Close out for loop
 N               Prints N
 =kN             Set k=N
 -=QN            Subtracts Q-=N

2

オクターブ、200

function r=c(m)r=[];a=eye(m);a(:,1)=1;for(i=3:m)for(j=2:i-1)a(i,j)=a(i-1,j-1)+a(i-j,j);end;end;p=randi(sum(a(m,:)));while(m>0)b=a(m,:);c=cumsum(b);x=min(find(c>=p));r=[r x];p=p-c(x)+b(x);m=m-x;end;end

ゴルフをしていない:

function r=c(m)
  r=[];
  a=eye(m);
  a(:,1)=1;
  for(i=3:m)
    for(j=2:i-1)
      a(i,j)=a(i-1,j-1)+a(i-j,j);
    end;
  end;
  p=randi(sum(a(m,:)));
  while(m>0)
    b=a(m,:);
    c=cumsum(b);
    x=min(find(cumsum(b)>=p));
    r=[r x];
    p=p-c(x)+b(x);
    m=m-x;
  end
end

各セル(m、n)のパーティション数を反映する正方行列を作成します mn親切に引用されたクヌース抽出@feersumに従ってが最大数のます。たとえば、5,22つの有効なパーティション2,2,1とがあるため、2が得られ2,1,1,1ます。との6,3ため3,1,1,1に3を与えます。3,2,13,3

これで、確定的にp番目のパーティションを見つけることができます。ここではp、乱数として生成していますpが、パラメーターを変更するためにスクリプトをわずかに変更できます。

function r=c(m,p)
  r=[];
  a=eye(m);
  a(:,1)=1;
  for(i=3:m)
    for(j=2:i-1)
      a(i,j)=a(i-1,j-1)+a(i-j,j);
    end;
  end;
  while(m>0)
    b=a(m,1:m);
    c=cumsum(b);
    x=min(find(c>=p));
    r=[r x];
    p=p-c(x)+b(x);
    m=m-x;
  end
end

これで、各結果がpのみに依存していることを決定論的に示すことができます。

octave:99> for(i=1:7)
> c(5,i)
> end
ans =

   1   1   1   1   1

ans =

   2   1   1   1

ans =

   2   2   1

ans =

   3   1   1

ans =

   3   2

ans =

   4   1

ans =  5

したがって、pがランダムに生成される元に戻ると、各結果が等しく発生する可能性が保証されます。


あなたの5,2の例についてはわかりません。2つのパーティションが(2,2,1)andであっ(2,1,1,1,1)てはなりません(リストした2つのパーティションの数はより大きいため2)。
マーティンエンダー

あなたは正しい、私は物事がねじれた。2つのコンポーネントを持つ2つのパーティションと、で始まる2つのパーティションがあり2ます。私は後者を意味しました。
dcsohl

2

R、198バイト

function(m){r=c();a=diag(m);a[,1]=1;for(i in 3:m)for(j in 2:(i-1))a[i,j]=a[i-1,j-1]+a[i-j,j];p=sample(sum(a[m,]),1);while(m>0){b=a[m,];c=cumsum(b);x=min(which(c>=p));r=c(r,x);p=p-c[x]+b[x];m=m-x};r}

ゴルフをしていない:

f <- function(m) {
    r <- c()
    a <- diag(m)
    a[, 1] <- 1
    for (i in 3:m)
        for (j in 2:(i-1))
            a[i, j] <- a[i-1, j-1] + a[i-j, j]
    p <- sample(sum(a[m, ]), 1)
    while (m > 0) {
        b <- a[m, ]
        c <- cumsum(b)
        x <- min(which(c >= p))
        r <- c(r, x)
        p <- p - c[x] + b[x]
        m <- m - x
    }
    return(r)
}

これは、Octaveの @dcsohlの優れたソリューションと同じ構造に従います。によって投稿されたKnuth抽出にも基づいています。

Rでより創造的なソリューションを思いつくことができれば、これを後で編集します。それまでの間、もちろんどんな入力でも歓迎します。


1

Java、392バイト

import java.util.*;Map a=new HashMap();List a(int b){List c=new ArrayList();int d=b,e=b(b,d),f=(int)(Math.random()*e),g,i;while(b>0){for(g=0;g++<Math.min(d, b);f-=i){i=b(b-g,g);if(f<i)break;}c.add(g);d=g;b-=g;}return c;}int b(int b,int c){if(b<1)return 1;List d=Arrays.asList(b,c);if(a.containsKey(d))return(int)a.get(d);int e,f;for(e=f=0;f++<Math.min(c, b);)e+=b(b-f,f);a.put(d,e);return e;}

で呼び出す a(n)ます。戻り値ListInteger S

インデント:

import java.util.*;

Map a=new HashMap();

List a(int b){
    List c=new ArrayList();
    int d=b,e=b(b,d),f=(int)(Math.random()*e),g,i;
    while(b>0){
        for(g=0;g++<Math.min(d, b);f-=i){
            i=b(b-g,g);
            if(f<i)
                break;
        }
        c.add(g);
        d=g;
        b-=g;
    }
    return c;
}

int b(int b,int c){
    if(b<1)
        return 1;
    List d=Arrays.asList(b,c);
    if(a.containsKey(d))
        return(int)a.get(d);
    int e,f;
    for(e=f=0;f++<Math.min(c, b);)
        e+=b(b-f,f);
    a.put(d,e);
    return e;
}

から適応 /programming//a/2163753/4230423とゴルフ

仕組み: O(n 2)時間内に整数nのパーティションがいくつあるかを計算できます。副作用として、これはサイズO(n 2)のテーブルを生成します。このテーブルを使用して、任意の整数kに対してnのk番目のパーティションを生成できますてO(nの)時間で生成できます。

だから、聞かせて合計 =パーティションの数。0からtotalまでの乱数kを選択します -1ます。k番目のパーティションを生成します。

いつものように、提案は大歓迎です:)


1

Python 2、173バイト

from random import*
N,M=input__
R=67;d=[(0,[])]*R*R
for k in range(R*R):p,P=d[k+~R];q,Q=d[k-k%R*R];d[k]=p+q+0**k,[[x+1 for x in Q],[1]+P][random()*(p+q)<p]
print d[N*R+M][1]

再帰的には、辞書を行うdキーで、kペアを表現(n,m)することによってk=67*n+m(保証を使用してn<=65)。エントリは、パーティションの数のタプルnmパーツあり、そのようなパーティションはランダムです。カウントは再帰式によって計算されます(指摘してくれたfeersumに感謝します)

f(n,m) = f(n-1,m-1) + f(n,n-m)

ランダムパーティションは、そのカウントに比例する確率でブランチの2つのうちの1つを選択することによって更新されます。更新は追加によって行われ、追加は1、最初のブランチにをし、2番目のブランチに対してすべての要素をインクリメントます。

私はトラブルの多くは、の範囲外の値を取得していたmし、nゼロのカウントを与えます。最初は、デフォルトでカウントが0で空のリストが使用される辞書を使用しました。ここでは、リストを使用し、代わりにこのデフォルトエントリでパディングしています。負のインデックスを使用すると、リストが最後から読み込まれます。これにより、デフォルトのエントリは、これまでにないほど終わり近くになり、ラップアラウンドはの領域にのみ接触しますm>n


1

80386マシンコード、105バイト

コードのHexdump:

60 8b fa 81 ec 00 41 00 00 33 c0 8b f4 33 d2 42
89 14 06 42 33 ed 8b d8 03 2c 1e 2a fa 73 f9 83
c6 04 89 2c 06 42 3b d1 76 ea fe c4 3a e1 76 db
33 d2 0f c7 f0 f7 f5 86 e9 85 d2 74 1b 33 c0 8d
34 0c 39 14 86 77 03 40 eb f8 2b 54 86 fc 40 89
07 83 c7 04 2a e8 77 e1 42 89 17 83 c7 04 fe cd
7f f7 4a b6 41 03 e2 61 c3

C関数として:void random_partition(int n, int result[]);。指定されたバッファ内の数値のリストとして結果を返します。リストの終わりを示すものではありませんが、ユーザーは数字を累積することで終わりを見つけることができます-合計がnます。

使用方法(Visual Studioで):

#include <stdio.h>

__declspec(naked) void __fastcall random_partiton(int n, int result[])
{
#define a(byte) __asm _emit 0x ## byte
a(60) a(8b) a(fa) a(81) a(ec) a(00) a(41) a(00) a(00) a(33) a(c0) a(8b) a(f4) a(33) a(d2) a(42)
a(89) a(14) a(06) a(42) a(33) a(ed) a(8b) a(d8) a(03) a(2c) a(1e) a(2a) a(fa) a(73) a(f9) a(83)
a(c6) a(04) a(89) a(2c) a(06) a(42) a(3b) a(d1) a(76) a(ea) a(fe) a(c4) a(3a) a(e1) a(76) a(db)
a(33) a(d2) a(0f) a(c7) a(f0) a(f7) a(f5) a(86) a(e9) a(85) a(d2) a(74) a(1b) a(33) a(c0) a(8d)
a(34) a(0c) a(39) a(14) a(86) a(77) a(03) a(40) a(eb) a(f8) a(2b) a(54) a(86) a(fc) a(40) a(89)
a(07) a(83) a(c7) a(04) a(2a) a(e8) a(77) a(e1) a(42) a(89) a(17) a(83) a(c7) a(04) a(fe) a(cd)
a(7f) a(f7) a(4a) a(b6) a(41) a(03) a(e2) a(61) a(c3)
}

void make_stack() // see explanations about stack below
{
    volatile int temp[65 * 64];
    temp[0] = 999;
}

int main()
{
    int result[100], j = 0, n = 64, counter = n;
    make_stack(); // see explanations about stack below

    random_partiton(n, result);

    while (counter > 0)
    {
        printf("%d ", result[j]);
        counter -= result[j];
        ++j;
    }
    putchar('\n');
}

出力例(n = 64の場合):

21 7 4 4 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1

これには多くの説明が必要です...

もちろん、私は他の誰もが使用したアルゴリズムも使用しました。複雑さに関する要件には選択肢がありませんでした。したがって、アルゴリズムについてあまり説明する必要はありません。とにかく:

以下の部分を使用してf(n, m)n要素の分割数で示しmます。それらを2次元配列(Cで宣言f[65][64])に格納します。最初のインデックスはnで、2番目のインデックスはm-1です。私はサポートすることを決めましたn=65があまりにも面倒ので、それを放棄しました...

このテーブルを計算するCコードは次のとおりです。

#define MAX_M 64
int f[(MAX_M + 1) * MAX_M];
int* f2;
int c; // accumulates the numbers needed to calculate f(n, m)
int m;
int k; // f(k, m), for various values of k, are accumulated
int n1;

for (n1 = 0; n1 <= n; ++n1)
{
    f2 = f;
    f2[n1 * MAX_M] = 1;
    for (m = 2; m <= n; ++m)
    {
        c = 0;
        k = n1;
        while (k >= 0)
        {
            c += f2[k * MAX_M];
            k -= m;
        }
        ++f2;
        f2[n1 * MAX_M] = c;
    }
}

このコードには難読化されたスタイルがあるため、アセンブリ言語に簡単に変換できます。要素f(n, n)の分割数であるまで要素を計算しnます。このコードが終了すると、一時変数cに必要な番号が含まれます。この番号を使用して、ランダムパーティションを選択できます。

int index = rand() % c;

後で、これindexは生成されたテーブルを使用して必要な形式(数字のリスト)に変換されます。

do {
    if (index == 0)
        break;

    m = 0;
    f2 = &f[n * MAX_M];
    while (f2[m] <= index)
    {
        ++m;
    }

    index -= f2[m-1];
    ++m;
    *result++ = m;
    n -= m;
} while (n > 0);

do {
    *result++ = 1;
    --n;
} while (n > 0);

このコードは、アセンブリ言語への変換用にも最適化されています。小さな「バグ」があります:パーティショニング1の最後に数字が含まれていない場合、最後のループはに遭遇しn = 0、不要な1要素を出力します。ただし、印刷コードは数値の合計を追跡し、この余分な数値を印刷しないため、問題はありません。

インラインアセンブリに変換すると、このコードは次のようになります。

__declspec(naked) void _fastcall random_partition_asm(int n, int result[])
{
    _asm {
        pushad;

        // ecx = n
        // edx = m
        // bh = k; ebx = k * MAX_M * sizeof(int)
        // ah = n1; eax = n1 * MAX_M * sizeof(int)
        // esp = f
        // ebp = c
        // esi = f2
        // edi = result

        mov edi, edx;
        sub esp, (MAX_M + 1) * MAX_M * 4; // allocate space for table
        xor eax, eax;
    row_loop:
        mov esi, esp;
        xor edx, edx;
        inc edx;
        mov dword ptr [esi + eax], edx;
        inc edx;

    col_loop:
        xor ebp, ebp;
        mov ebx, eax;

    sum_loop:
        add ebp, [esi + ebx];
        sub bh, dl;
        jae sum_loop;

        add esi, 4;
        mov [esi + eax], ebp;
        inc edx;
        cmp edx, ecx;
        jbe col_loop;

        inc ah;
        cmp ah, cl;
        jbe row_loop;

        // Done calculating the table

        // ch = n; ecx = n * MAX_M * sizeof(int)
        // eax = m
        // ebx = 
        // edx = index
        // esp = f
        // esi = f2
        // ebp = c
        // edi = result

        xor edx, edx;
        rdrand eax; // generate a random number
        div ebp; // generate a random index in the needed range
        xchg ch, cl; // multiply by 256

    n_loop:
        test edx, edx;
        jz out_trailing;
        xor eax, eax;
        lea esi, [esp + ecx];

    m_loop:
        cmp [esi + eax * 4], edx;
        ja m_loop_done;
        inc eax;
        jmp m_loop;
    m_loop_done:

        sub edx, [esi + eax * 4 - 4];
        inc eax;
        mov [edi], eax;
        add edi, 4;
        sub ch, al;
        ja n_loop;

    out_trailing:
        inc edx;
    out_trailing_loop:
        mov dword ptr [edi], edx;
        add edi, 4;
        dec ch;
        jg out_trailing_loop;

        dec edx;
        mov dh, (MAX_M + 1) * MAX_M * 4 / 256;
        add esp, edx;
        popad;
        ret;
    }
}

注意すべきいくつかの楽しいこと:

  • 乱数の生成には、わずか3バイトのマシンコード(rdrand命令)が必要です
  • 偶然にも、テーブルのサイズは64であるため、1行のサイズは256バイトです。これを使用して、行インデックスをのような「高バイト」レジスタに保持します。これahにより、256倍の自動乗算が可能になります。これを利用するために、n = 65ます。。私はこの罪を許されることを願っています...
  • スタック上のスペースの割り当ては、スタックポインタレジスタから0x4100を引くことによって実行されespます。これは6バイトの命令です!この番号を追加して戻すと、5バイトで管理できました。

        dec edx; // here edx = 1 from earlier calculations
        mov dh, (MAX_M + 1) * MAX_M * 4 / 256; // now edx = 0x4100
        add esp, edx; // this deallocates space on stack
    
  • MS Visual Studioでこの関数をデバッグすると、スタックに割り当てたスペースにデータを書き込むとクラッシュすることがわかりました。いくつか掘り下げた後、ある種のスタックオーバーラン保護を発見しました。OSは、スタックに非常に限られた範囲の仮想アドレスのみを割り当てているようです。関数が遠く離れたアドレスにアクセスすると、OSはそれがオーバーランであると見なし、プログラムを強制終了します。ただし、関数に多くのローカル変数がある場合、OSはそれを機能させるために追加の「魔法」を実行します。そのため、スタックに割り当てられた大きな配列を持つ空の関数を呼び出す必要があります。この関数が戻ると、追加のスタックVMページが割り当てられ、使用できるようになります。

        void make_stack()
        {
            volatile int temp[65 * 64];
            temp[0] = 999; // have to "use" the array to prevent optimizing it out
        }
    
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.