任意のランダム性(スピード版)


10

与えられたinteger nで、セットの合計が以下に等しくなるようにn、範囲1..n^2(両端を含む)でランダムな一意の整数のセットを計算しますn^2

この場合のランダムとは、有効な出力間で一様にランダムであることを意味します。特定の有効な出力ごとnに、生成される可能性が一定でなければなりません。

例えば、n=33分の1のチャンスに出力のそれぞれを持っていなければならない6, 1, 23, 5, 1または4, 3, 2。これが設定される、順序は、無関係で4, 3, 2同一であります3, 2, 4

得点

勝者はn60秒未満で最高を計算できるプログラムです。
注:部分的なハードコーディングの可能性を防ぐには、すべてのエントリが4000バイト未満である必要があります

テスト中

すべてのコードはローカルのWindows 10マシン(Razer Blade 15、16GB RAM、Intel i7-8750H 6コア、4.1GHz、GPUを悪用したい場合はGTX 1060)で実行されるので、コードを実行するための詳細な手順を提供してください私のマシン。
リクエストに応じて、WSL上のDebianまたはXubuntu仮想マシン(どちらも上記と同じマシン上)でエントリを実行できます。

提出は連続して50回実行され、最終スコアは50件すべての結果の平均になります。



4000バイト未満の場合、ビットのハードコーディングは許可されますか?
Quintec、2018年

@Quintecいいえ、ハードコーディングは標準の抜け穴なので、デフォルトでは禁止されています。注意が必要なのは、ハードコーディングも観察できない基準であると考えられているため、抜け穴が許可しない範囲を超えて「ハードコーディングなし」と公式に言うことはできません。したがって、バイト制限です。つまり、ハードコードしないでください
Skidsdev

1
ほとんどの提出物は拒否方法を使用するため、実行時間はランダムになり、ばらつきが大きくなります。そのため、タイミングが難しくなります
Luis Mendo

2
ああ、忘れました-一部のソリューションでは低品質のRNGを使用して高速化する場合があるため、nを受け取り、(1..n)で乱数を生成し、すべてを強制するブラックボックスルーチンを提供する必要がある場合がありますそれを使用するためのソリューション。
user202729

回答:


6

nは ≈1400

走る方法

でビルドしcargo build --release、で実行しtarget/release/arbitrary-randomness nます。

このプログラムは、大量のメモリを使用すると最も高速に実行されます(もちろん、スワップしない限り)。MAX_BYTES現在8 GiBに設定されている定数を編集して、メモリ使用量を調整できます。

使い方

セットは、一連のバイナリ決定(各番号はセットの内部または外部のいずれか)で構成され、各確率は、動的プログラミングを使用して各選択肢の後に構成可能な可能なセットの数を数えることによって組み合わせて計算されます。

大きなnのメモリ使用量は、この二項分割戦略のバージョンを使用することで削減されます。

Cargo.toml

[package]
name = "arbitrary-randomness"
version = "0.1.0"
authors = ["Anders Kaseorg <andersk@mit.edu>"]

[dependencies]
rand = "0.6"

src/main.rs

extern crate rand;

use rand::prelude::*;
use std::env;
use std::f64;
use std::mem;

const MAX_BYTES: usize = 8 << 30; // 8 gibibytes

fn ln_add_exp(a: f64, b: f64) -> f64 {
    if a > b {
        (b - a).exp().ln_1p() + a
    } else {
        (a - b).exp().ln_1p() + b
    }
}

fn split(steps: usize, memory: usize) -> usize {
    if steps == 1 {
        return 0;
    }
    let mut u0 = 0;
    let mut n0 = f64::INFINITY;
    let mut u1 = steps;
    let mut n1 = -f64::INFINITY;
    while u1 - u0 > 1 {
        let u = (u0 + u1) / 2;
        let k = (memory * steps) as f64 / u as f64;
        let n = (0..memory)
            .map(|i| (k - i as f64) / (i as f64 + 1.))
            .product();
        if n > steps as f64 {
            u0 = u;
            n0 = n;
        } else {
            u1 = u;
            n1 = n;
        }
    }
    if n0 - (steps as f64) <= steps as f64 - n1 {
        u0
    } else {
        u1
    }
}

fn gen(n: usize, rng: &mut impl Rng) -> Vec<usize> {
    let s = n * n.wrapping_sub(1) / 2;
    let width = n.min(MAX_BYTES / ((s + 1) * mem::size_of::<f64>()));
    let ix = |m: usize, k: usize| m + k * (s + 1);
    let mut ln_count = vec![-f64::INFINITY; ix(0, width)];
    let mut checkpoints = Vec::with_capacity(width);
    let mut a = Vec::with_capacity(n);
    let mut m = s;
    let mut x = 1;

    for k in (1..=n).rev() {
        let i = loop {
            let i = checkpoints.len();
            let k0 = *checkpoints.last().unwrap_or(&0);
            if k0 == k {
                checkpoints.pop();
                break i - 1;
            }
            if i == 0 {
                ln_count[ix(0, i)] = 0.;
                for m in 1..=s {
                    ln_count[ix(m, i)] = -f64::INFINITY;
                }
            } else {
                for m in 0..=s {
                    ln_count[ix(m, i)] = ln_count[ix(m, i - 1)];
                }
            }
            let k1 = k - split(k - k0, width - 1 - i);
            for step in k0 + 1..=k1 {
                for m in step..=s {
                    ln_count[ix(m, i)] = ln_add_exp(ln_count[ix(m - step, i)], ln_count[ix(m, i)]);
                }
            }
            if k1 == k {
                break i;
            }
            checkpoints.push(k1);
        };

        while m >= k && rng.gen_bool((ln_count[ix(m - k, i)] - ln_count[ix(m, i)]).exp()) {
            m -= k;
            x += 1;
        }
        a.push(x);
        x += 1;
    }
    a
}

fn main() {
    if let [_, n] = &env::args().collect::<Vec<_>>()[..] {
        let n = n.parse().unwrap();
        let mut rng = StdRng::from_entropy();
        println!("{:?}", gen(n, &mut rng));
    } else {
        panic!("expected one argument");
    }
}

オンラインでお試しください!

(注:TIOバージョンにはいくつかの変更があります。最初に、メモリ制限が1 GiBに減少します。2番目に、TIOはを記述できず、のCargo.tomlような外部クレートに依存しないため、rand代わりdrand48にCライブラリからFFI。私はそれをシードすることを気にしなかったので、TIOバージョンはすべての実行で同じ結果を生成します。公式のベンチマークにTIOバージョンを使用しないでください。)


浮動小数点形式は有限であるためln_add_exp、絶対差が約15より大きいかどうかをチェックすることで最適化できます。そのような追加が多い場合は、高速になる可能性があります。
user202729

@ user202729いいえ、ほぼすべてのln_add_exp通話に同等の入力が含まれます。
Anders Kaseorg

3

Java 7以降、TIOで30秒以内にn = 50

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Random;
class Main{
  public static void main(String[] a){

    int n=50;

    Random randomGenerator = new Random();
    int i = n+1;
    int squaredN = n*n;
    int[]randomIntegers = new int[i];
    randomIntegers[n] = squaredN;
    while(true){
      for(i=n; i-->1; ){
        randomIntegers[i] = randomGenerator.nextInt(squaredN);
      }
      Set<Integer> result = new HashSet<>();
      Arrays.sort(randomIntegers);
      for(i=n; i-->0; ){
        result.add(randomIntegers[i+1] - randomIntegers[i]);
      }
      if(!result.contains(0) && result.size()==n){
        System.out.println(result);
        return;
      }
    }
  }
}

Ungolfedバージョンこの問題のコード・ゴルフバージョンのための私の答え一つだけのマイナーな変更となりましたためには、:java.util.Random#nextInt(limit)の代わりに使用されている(int)(Math.random()*limit)範囲の整数のために[0, n)、それは程度なので、早く二回

オンラインでお試しください。

説明:

使用されるアプローチ:

コードは2つの部分に分かれています。

  1. のリストを生成する n合計がになるランダムな整数量のしn squaredます。
  2. 次に、すべての値が一意でゼロがないかどうかをチェックし、どちらかが誤っている場合は、ステップ1を再試行して、結果が得られるまで洗浄して繰り返します。

ステップ1は、次のサブステップで実行されます。

1)n-1範囲内のランダムな整数の量の配列を生成します[0, n squared)0n squaredをこのリストに追加します。これはO(n+1)パフォーマンスで行われます。
2)次に、組み込みjava.util.Arrays.sort(int[])で配列をソートします。これはO(n*log(n))、ドキュメントに記載されているように、パフォーマンスで行われます。

指定された整数配列を昇順の数値順にソートします。ソートアルゴリズムは、Jon L. BentleyおよびM. Douglas McIlroyの「Engineering a Sort Function」、Software-Practice and Experience、Vol。23(11)P. 1249-1265(1993年11月)。このアルゴリズムは、他のクイックソートを2次のパフォーマンスに低下させる多くのデータセットでn * log(n)のパフォーマンスを提供します。

3)各ペア間の差を計算します。この結果の差のリストにはn、合計がになる整数が含まれますn squared。これはO(n)パフォーマンスで行われます。

ここに例を示します:

// n = 4, nSquared = 16

// n-1 amount of random integers in the range [0, nSquared):
[11, 2, 5]

// Add 0 and nSquared to it, and sort:
[0, 2, 5, 11, 16]

// Calculate differences:
[2, 3, 6, 5]

// The sum of these differences will always be equal to nSquared
sum([2, 3, 6, 5]) = 16

したがって、上記の3つのステップは、ステップ2や基本的なブルートフォースである全体のループとは異なり、パフォーマンスにかなり優れています。ステップ2は、次のサブステップに分かれています。

1)差分リストはすでにに保存されていjava.util.Setます。このセットのサイズがに等しいかどうかをチェックしnます。そうであれば、生成したすべてのランダムな値が一意であることを意味します。
2)そしてそれはまた、それが何が含まれていることを確認していないだろう0課題は範囲内のランダムな値を要求するので、セットでは[1, X]Xあるn squaredのマイナス合計が[1, ..., n-1]で述べたように、@Skidsdev以下のコメントで。

上記の2つのオプションのいずれか(すべての値が一意ではないか、ゼロが存在する場合)は、新しい配列を生成し、手順1にリセットすることによって再度設定します。これは、結果が得られるまで続きます。このため、時間がかなり異なる場合があります。TIOで1回3秒で終了するのを見たn=50が、55秒で1回終了するn=50

均一性の証明:

これが完全に正直であることを証明する方法は完全にはわかりません。java.util.Random#nextIntドキュメントに記載されているように、確かに均一です。

intこの乱数ジェネレータのシーケンスから、次の擬似乱数の均一に分散された値を返します。の一般規約でnextIntは、1つのint値が擬似ランダムに生成されて返されます。2 32のすべての可能なint値は、(ほぼ)等しい確率で生成されます。

これらの(ソートされた)ランダム値自体の違いはもちろん均一ではありませんが、セット全体は均一です。繰り返しになりますが、10,000n=10これを数学的に証明する方法はわかりませんが、生成されたセット(の)をカウンター付きのMapに配置するスクリプトを次に示します。2回繰り返されるものもあります。また、最大反復回数は通常範囲内[4,8]です。

インストール手順:

Javaはかなりよく知られている言語であり、Javaコードの作成方法と実行方法に関する情報が豊富にあるため、ここでは短くしておきます。
私のコードで使用されているすべてのツールはJava 7で使用できます(おそらくすでにJava 5または6でも使用されていますが、念のために7を使用しましょう)。Java 7はすでにアーカイブされていると思いますので、コードを実行するためにJava 8をダウンロードすることをお勧めします。

改善に関する考え:

ゼロのチェックの改善を見つけて、すべての値が一意であることを確認したいと思います。私はのためにチェックすることができ0てください、我々はアレイに追加ランダムな値がそれになっていないが、それは物事のカップルを意味するであろうことで、前に:配列はする必要がありますArrayList我々は組み込みのメソッドを使用することができるように.contains。リストにまだないランダムな値が見つかるまで、whileループを追加する必要があります。ゼロのためにチェックすることは、今で行われているので.contains(0)(一度だけチェックされている)を設定の上、それはパフォーマンスがでループを追加することと比較して、その時点でそれを確認するために、最も可能性の高い方が良いでしょう.contains、少なくともチェックされます一覧にn回、しかしおそらくそれ以上。

一意性チェックについては、プログラムのステップ1の後nで合計されるランダムな整数の量のみがあるn squaredので、それから初めて、すべてが一意であるかどうかをチェックできます。配列の代わりにソート可能なリストを保持し、その違いを確認することは可能かもしれませんが、単にそれらをaに入れてSetそのセットのサイズがn1度かどうかを確認するよりも、パフォーマンスが向上することを真剣に疑っています。


1
それが速度に役立つ場合、セット内のどの数値もn^2 - sum(1..n-1)、たとえばn=5最大の有効な数値より大きくなることはありません5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
Skidsdev

@Skidsdevありがとう、それについて考えていませんでした。私の現在のアプローチでは、ランダムな値の代わりにランダムなペア間の差異を直接取得するため、それを利用することはできません。しかし、それはおそらく他の答えに役立つかもしれません。
Kevin Cruijssen、

1
結果のセットのサイズがを超えることはありませんnか?その場合は0、セットに追加して、サイズが(現在)より大きいことを確認できnます。これは、違いがすべて非ゼロで明確な場合にのみ発生します。
Neil

@ニールああ、それはかなり賢いです、そして私はコードゴルフの答えで間違いなくそれを使って数バイトゴルフをします。ただし、ここでパフォーマンスが向上するかどうかはわかりません。HashSet.containsほとんどの場合はに近くO(1)、最悪の場合O(n)はJava 7とO(log n)Java 8+です(チェーンを衝突検出に置き換えた後で改善されています)。0チェックのために追加されたSetを返すことが許可されている場合、実際にはパフォーマンスが若干向上しますset.remove(0);が、if内で呼び出す必要がある場合、パフォーマンスはある程度同じであると確信できます。
Kevin Cruijssen、

ああ、あなたもセットを返却する必要があるのを忘れていました...
ニール、

1

Mathematica n = 11

(While[Tr@(a=RandomSample[Range[#^2-#(#-1)/2],#])!=#^2];a)&     
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.