Arbitrary Randomness (Speed ​​edition)


10

Dato intero n, calcola un insieme di ninteri univoci casuali nell'intervallo 1..n^2(incluso) in modo tale che la somma dell'insieme sia uguale an^2

Casuale, in questo caso, significa uniformemente casuale tra uscite valide. Ogni output valido per un dato ndeve avere una probabilità uniforme di essere generato.

Per esempio, n=3dovrebbe avere la possibilità di 1/3 ciascuno di output 6, 1, 2, 3, 5, 1o 4, 3, 2. Poiché questo è un insieme, l'ordine è irrilevante, 4, 3, 2è identico a3, 2, 4

punteggio

Il vincitore è il programma che può calcolare il massimo nin meno di 60 secondi.
Nota: per evitare possibili hardcoding parziali, tutte le voci devono essere inferiori a 4000 byte

analisi

Tutto il codice verrà eseguito sul mio computer Windows 10 locale (Razer Blade 15, 16 GB di RAM, Intel i7-8750H 6 core, 4,1 GHz, GTX 1060 nel caso in cui si desideri abusare della GPU), quindi si prega di fornire istruzioni dettagliate per eseguire il codice su la mia macchina.
Su richiesta, le voci possono essere eseguite tramite Debian su WSL o su una macchina virtuale Xubuntu (entrambe sulla stessa macchina di cui sopra)

Le iscrizioni verranno eseguite 50 volte consecutive, il punteggio finale sarà una media di tutti e 50 i risultati.



La codifica hardware è un po 'consentita, se è inferiore a 4000 byte?
Quintec,

@Quintec No, la codifica hardware è una scappatoia standard, quindi vietata per impostazione predefinita. La cosa difficile è che l'hardcoding è anche considerato un criterio inosservabile, quindi non posso dire ufficialmente "No hardcoding" al di là di ciò che la scappatoia non consente. Da qui il limite di byte. In altre parole: per favore , non
faticare il codice

1
La maggior parte degli invii utilizzerà un metodo di rifiuto e pertanto il tempo di esecuzione sarà casuale e presenterà una grande variabilità. Ciò rende difficile il cronometraggio
Luis Mendo,

2
Oh, ho dimenticato - poiché alcune soluzioni potrebbero decidere di utilizzare RNG di bassa qualità per essere veloci, potrebbe essere necessario fornire una routine black box che prende n e produce un numero casuale in (1..n), e forza tutti soluzioni per usarlo.
user202729

Risposte:


6

Ruggine , n ≈ 1400

Come correre

Costruisci cargo build --releasee corri con target/release/arbitrary-randomness n.

Questo programma funziona più velocemente con molta memoria (purché non si scambi, ovviamente). È possibile regolare l'utilizzo della memoria modificando la MAX_BYTEScostante, attualmente impostata su 8 GiB.

Come funziona

L'insieme è costruito da una sequenza di decisioni binarie (ogni numero è all'interno o all'esterno dell'insieme), ciascuna delle cui probabilità sono calcolate in modo combinatorio contando il numero di insiemi possibili costruibili dopo ogni scelta usando la programmazione dinamica.

L'utilizzo della memoria per n di grandi dimensioni viene ridotto utilizzando una versione di questa strategia di partizionamento binomiale .

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");
    }
}

Provalo online!

(Nota: la versione TIO ha alcune modifiche. In primo luogo, il limite di memoria è ridotto a 1 GiB. In secondo luogo, poiché TIO non consente di scrivere un Cargo.tomle dipende da cassette esterne come rand, ho invece estratto drand48dalla libreria C usando il FFI. Non mi sono preoccupato di seminarlo, quindi la versione TIO produrrà lo stesso risultato ad ogni corsa. Non usare la versione TIO per benchmark ufficiali.)


Poiché il formato in virgola mobile è finito, è possibile ottimizzare ln_add_expverificando se la differenza assoluta è maggiore di ~ 15 o giù di lì, che può essere più veloce se ci sono molte aggiunte.
user202729

@ user202729 No, quasi tutte le ln_add_expchiamate comportano input comparabili.
Anders Kaseorg,

3

Java 7+, n = 50 in ~ 30 sec su TIO

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;
      }
    }
  }
}

La versione non golfata della mia risposta per la versione code-golf di questa sfida per ora, con solo una piccola modifica: java.util.Random#nextInt(limit)viene utilizzata al posto di (int)(Math.random()*limit)un numero intero nell'intervallo [0, n), poiché è circa il doppio più veloce .

Provalo online.

Spiegazione:

Approccio utilizzato:

Il codice è diviso in due parti:

  1. Genera un elenco di nquantità di numeri interi casuali da sommare a n squared.
  2. Quindi controlla se tutti i valori sono univoci e nessuno è zero e se uno dei due è falso, proverà nuovamente a eseguire il passaggio 1, sciacquando e ripetendo fino a quando non si ottiene un risultato.

Il passaggio 1 viene eseguito con i seguenti passaggi secondari:

1) Genera un array di n-1quantità di numeri interi casuali nell'intervallo [0, n squared). E aggiungi 0e n squareda questo elenco. Questo è fatto in termini di O(n+1)prestazioni.
2) Quindi ordinerà l'array con l'integrato java.util.Arrays.sort(int[]), questo viene fatto in termini di O(n*log(n))prestazioni, come indicato nei documenti:

Ordina la matrice di in specificata in ordine numerico crescente. L'algoritmo di ordinamento è un quicksort sintonizzato, adattato da Jon L. Bentley e M. Douglas McIlroy "Engineering a Sort Function", Software-Practice and Experience, Vol. 23 (11) P. 1249-1265 (novembre 1993). Questo algoritmo offre n * log (n) prestazioni su molti set di dati che causano il degrado di altri quicksorts in prestazioni quadratiche.

3) Calcola la differenza tra ogni coppia. Questo elenco risultante di differenze conterrà nnumeri interi che sommano n squared. Questo è fatto in termini di O(n)prestazioni.

Ecco un esempio:

// 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

Quindi questi tre passaggi sopra sono abbastanza buoni per le prestazioni, a differenza del passaggio 2 e del ciclo attorno a tutto, che è una forza bruta di base. Il passaggio 2 è suddiviso in questi passaggi secondari:

1) L'elenco delle differenze è già stato salvato in a java.util.Set. Verificherà se la dimensione di questo set è uguale a n. Se lo è, significa che tutti i valori casuali che abbiamo generato sono unici.
2) E verificherà anche che non contenga alcun valore 0nel Set, poiché la sfida richiede valori casuali nell'intervallo [1, X], dove Xè n squaredmeno la somma di [1, ..., n-1], come affermato da @Skidsdev nel commento qui sotto.

Se una delle due opzioni sopra (non tutti i valori sono univoci o è presente uno zero), verrà generato un nuovo array e impostato nuovamente ripristinando il passaggio 1. Questo continua fino a quando non si ottiene un risultato. Per questo motivo, il tempo può variare abbastanza. L'ho visto finire in 3 secondi una volta su TIO per n=50, ma anche in 55 secondi una volta per n=50.

Prova di uniformità:

Non sono del tutto sicuro di come dimostrarlo completamente onesto. L' java.util.Random#nextIntuniforme è sicura, come è descritto nei documenti:

Restituisce il successivo valore pseudocasuale, distribuito uniformemente intdalla sequenza di questo generatore di numeri casuali. Il contratto generale di nextIntè che un intvalore viene generato e restituito in modo pseudocasuale. Tutti i 2 32 possibili intvalori sono prodotti con (approssimativamente) uguale probabilità.

Le differenze tra questi valori casuali (ordinati) non sono ovviamente uniformi, ma gli insiemi nel loro insieme sono uniformi. Ancora una volta, non sono sicuro di come dimostrarlo matematicamente, ma ecco uno script che metterà i 10,000set generati (per n=10) in una mappa con un contatore , dove la maggior parte dei set sono unici; alcuni ripetuti due volte; e la ricorrenza massima ripetuta è generalmente nell'intervallo [4,8].

Istruzioni per l'installazione:

Poiché Java è un linguaggio piuttosto noto con molte informazioni disponibili su come creare ed eseguire il codice Java, lo terrò breve.
Tutti gli strumenti utilizzati nel mio codice sono disponibili in Java 7 (forse anche già in Java 5 o 6, ma usiamo 7 per ogni evenienza). Sono abbastanza sicuro che Java 7 sia già archiviato, quindi suggerirei di scaricare Java 8 per eseguire il mio codice.

Considerazioni sui miglioramenti:

Vorrei trovare un miglioramento per il controllo degli zeri e verificare che tutti i valori siano univoci. Potrei verificare 0prima, assicurandomi che il valore casuale che aggiungiamo all'array non sia già presente, ma significherebbe un paio di cose: l'array dovrebbe essere un ArrayListcosì da poter usare il metodo incorporato .contains; un ciclo while dovrebbe essere aggiunto fino a quando non abbiamo trovato un valore casuale che non è ancora nella lista. Poiché il controllo dello zero è ora eseguito con .contains(0)sul set (che viene verificato solo una volta), è molto meglio che le prestazioni lo controllino in quel punto, rispetto all'aggiunta del loop con .containssull'elenco, che verrà verificato almeno nvolte , ma molto probabilmente di più.

Per quanto riguarda il controllo di unicità, abbiamo solo la nostra nquantità di numeri interi casuali che si sommano n squareddopo il passaggio 1 del programma, quindi solo così possiamo verificare se tutti sono unici o no. Potrebbe essere possibile mantenere un elenco ordinabile anziché un array e verificare le differenze tra loro, ma dubito seriamente che migliorerà le prestazioni piuttosto che inserirle in un Sete verificare se la dimensione di quel set è nuna volta.


1
se aiuta la velocità, nessun numero nel set può essere maggiore di quanto n^2 - sum(1..n-1)ad esempio n=5il numero più grande valido sia5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
Skidsdev

@Skidsdev Grazie, non ci avevo pensato. Sebbene con il mio approccio attuale non riesco a utilizzarlo, poiché ottengo le differenze tra coppie casuali, anziché i valori casuali direttamente. Ma potrebbe essere utile per altre risposte forse.
Kevin Cruijssen,

1
La dimensione del set risultante non può mai essere superiore a n, vero? In tal caso, è possibile aggiungere 0al set e quindi verificare che la dimensione sia (ora) maggiore di n. Questo può accadere solo se le differenze sono tutte diverse da zero e distinte.
Neil,

@Neil Oh, è piuttosto intelligente, e sicuramente userò quella risposta nel mio codice-golf al golf a pochi byte di distanza. Non sono sicuro se qui migliorerà le prestazioni. HashSet.containsè nella maggior parte dei casi vicino O(1)e, nel peggiore dei casi, è O(n)in Java 7 e O(log n)in Java 8+ (è stato migliorato dopo aver sostituito il concatenamento con il rilevamento delle collisioni). Se mi è permesso restituire il Set con l'aggiunta 0per il controllo, allora è leggermente migliore per le prestazioni, ma se devo chiamare set.remove(0);all'interno dell'if, sono abbastanza sicuro che le prestazioni siano un po 'le stesse.
Kevin Cruijssen,

Oh, ho dimenticato che devi restituire anche il set ... non importa.
Neil,

1

Mathematica n = 11

(While[Tr@(a=RandomSample[Range[#^2-#(#-1)/2],#])!=#^2];a)&     
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.