Trovare correlazioni approssimative


14

Considera una stringa binaria Sdi lunghezza n. Indicizzando da 1, possiamo calcolare le distanze di Hamming tra S[1..i+1]e S[n-i..n]per tutti iin ordine da 0a n-1. La distanza di Hamming tra due stringhe di uguale lunghezza è il numero di posizioni in cui i simboli corrispondenti sono diversi. Per esempio,

S = 01010

[0, 2, 0, 4, 0].

Questo perché le 0partite 0, 01hanno la distanza di Hamming due a 10, le 010partite 010, 0101hanno la distanza di Hamming quattro 1010 e infine si 01010abbina.

Siamo interessati solo alle uscite in cui la distanza di Hamming è al massimo 1, tuttavia. Quindi in questo compito segnaleremo Yse la distanza di Hamming è al massimo una e una Ndiversa. Quindi nel nostro esempio sopra avremmo

[Y, N, Y, N, Y]

Definire f(n)il numero di matrici distinte di Ys che Nsi ottengono quando si scorre su tutte le 2^ndiverse stringhe Sdi bit possibili di lunghezza n.

Compito

Per aumentare a npartire da 1, il tuo codice dovrebbe essere prodotto f(n).

Risposte di esempio

Per n = 1..24, le risposte corrette sono:

1, 1, 2, 4, 6, 8, 14, 18, 27, 36, 52, 65, 93, 113, 150, 188, 241, 279, 377, 427, 540, 632, 768, 870

punteggio

Il codice dovrebbe iterare dal n = 1dare la risposta per ogni na turno. Farò cronometrare l'intera corsa, uccidendola dopo due minuti.

Il tuo punteggio è il più alto che nriesci a raggiungere in quel momento.

In caso di pareggio, vince la prima risposta.

Dove verrà testato il mio codice?

Eseguirò il tuo codice sul mio (leggermente vecchio) laptop Windows 7 con Cygwin. Di conseguenza, ti preghiamo di fornire tutta l'assistenza possibile per aiutarti a renderlo semplice.

Il mio laptop ha 8 GB di RAM e una CPU Intel i7 5600U@2,6 GHz (Broadwell) con 2 core e 4 thread. Il set di istruzioni include SSE4.2, AVX, AVX2, FMA3 e TSX.

Voci principali per lingua

  • n = 40 in Rust usando CryptoMiniSat, di Anders Kaseorg. (Nella VM guest Lubuntu sotto Vbox.)
  • n = 35 in C ++ usando la libreria BuDDy, di Christian Seviers. (Nella VM guest Lubuntu sotto Vbox.)
  • n = 34 in Clingo di Anders Kaseorg. (Nella VM guest Lubuntu sotto Vbox.)
  • n = 31 in Rust di Anders Kaseorg.
  • n = 29 in Clojure di NikoNyrh.
  • n = 29 in C di bartavelle.
  • n = 27 in Haskell da bartavelle
  • n = 24 in Pari / gp di alephalpha.
  • n = 22 in Python 2 + pypy da parte mia.
  • n = 21 in Mathematica da alephalpha. (Segnalato da sé)

Taglie future

Ora darò una taglia di 200 punti per ogni risposta che arriva a n = 80 sulla mia macchina in due minuti.


Conosci qualche trucco che permetterà a qualcuno di trovare un algoritmo più veloce di una forza bruta ingenua? Altrimenti questa sfida è "per favore implementala in x86" (o forse se conosciamo la tua GPU ...).
Jonathan Allan il

@JonathanAllan È certamente possibile accelerare un approccio molto ingenuo. Non sono sicuro di quanto velocemente si possa ottenere. È interessante notare che se abbiamo modificato la domanda in modo da ottenere una Y se la distanza di Hamming è al massimo 0 e una N altrimenti, allora esiste una formula nota della forma chiusa.

@Lembik Misuriamo il tempo della CPU o il tempo reale?
flawr,

@flawr Sto misurando il tempo reale ma eseguendolo alcune volte e prendendo il minimo per eliminare le stranezze.

Risposte:


9

Rust + CryptoMiniSat , n ≈ 41

src/main.rs

extern crate cryptominisat;
extern crate itertools;

use std::iter::once;
use cryptominisat::{Lbool, Lit, Solver};
use itertools::Itertools;

fn make_solver(n: usize) -> (Solver, Vec<Lit>) {
    let mut solver = Solver::new();
    let s: Vec<Lit> = (1..n).map(|_| solver.new_var()).collect();
    let d: Vec<Vec<Lit>> = (1..n - 1)
        .map(|k| {
                 (0..n - k)
                     .map(|i| (if i == 0 { s[k - 1] } else { solver.new_var() }))
                     .collect()
             })
        .collect();
    let a: Vec<Lit> = (1..n - 1).map(|_| solver.new_var()).collect();
    for k in 1..n - 1 {
        for i in 1..n - k {
            solver.add_xor_literal_clause(&[s[i - 1], s[k + i - 1], d[k - 1][i]], true);
        }
        for t in (0..n - k).combinations(2) {
            solver.add_clause(&t.iter()
                                   .map(|&i| d[k - 1][i])
                                   .chain(once(!a[k - 1]))
                                   .collect::<Vec<_>>()
                                   [..]);
        }
        for t in (0..n - k).combinations(n - k - 1) {
            solver.add_clause(&t.iter()
                                   .map(|&i| !d[k - 1][i])
                                   .chain(once(a[k - 1]))
                                   .collect::<Vec<_>>()
                                   [..]);
        }
    }
    (solver, a)
}

fn search(n: usize,
          solver: &mut Solver,
          a: &Vec<Lit>,
          assumptions: &mut Vec<Lit>,
          k: usize)
          -> usize {
    match solver.solve_with_assumptions(assumptions) {
        Lbool::True => search_sat(n, solver, a, assumptions, k),
        Lbool::False => 0,
        Lbool::Undef => panic!(),
    }
}

fn search_sat(n: usize,
              solver: &mut Solver,
              a: &Vec<Lit>,
              assumptions: &mut Vec<Lit>,
              k: usize)
              -> usize {
    if k >= n - 1 {
        1
    } else {
        let s = solver.is_true(a[k - 1]);
        assumptions.push(if s { a[k - 1] } else { !a[k - 1] });
        let c = search_sat(n, solver, a, assumptions, k + 1);
        assumptions.pop();
        assumptions.push(if s { !a[k - 1] } else { a[k - 1] });
        let c1 = search(n, solver, a, assumptions, k + 1);
        assumptions.pop();
        c + c1
    }
}

fn f(n: usize) -> usize {
    let (mut solver, proj) = make_solver(n);
    search(n, &mut solver, &proj, &mut vec![], 1)
}

fn main() {
    for n in 1.. {
        println!("{}: {}", n, f(n));
    }
}

Cargo.toml

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

[dependencies]
cryptominisat = "5.0.1"
itertools = "0.6.0"

Come funziona

Ciò effettua una ricerca ricorsiva attraverso l'albero di tutte le assegnazioni parziali ai prefissi dell'array Y / N, utilizzando un solutore SAT per verificare ad ogni passo se l'assegnazione parziale corrente è coerente e, in caso contrario, eliminare la ricerca. CryptoMiniSat è il risolutore SAT giusto per questo lavoro grazie alle sue speciali ottimizzazioni per le clausole XOR.

Le tre famiglie di vincoli sono

S iS k + iD ki , per 1 ≤ kn - 2, 0 ≤ i ≤ n - k ;
D ki 1D ki 2 ∨ ¬ A k , per 1 ≤ kn - 2, 0 ≤ i 1 < i 2n - k ;
¬ D ki 1 ∨ ⋯ ∨ ¬ D ki n - k - 1A k , per 1 ≤ kn - 2, 0 ≤ i 1 <⋯ < i n - k - 1n - k ;

eccetto che, come ottimizzazione, S 0 è forzato a falso, quindi D k 0 è semplicemente uguale a S k .


2
Woohoooooo! :)

Sto ancora cercando di compilare questo in Windows (usando cygwin + gcc). Ho clonato cryptominisat e l'ho compilato. Ma non so ancora come compilare il codice ruggine. Quando lo cargo buildricevo--- stderr CMake Error: Could not create named generator Visual Studio 14 2015 Win64

2
@ rahnema1 Grazie, ma sembra che il problema sia con il sistema di build CMake della libreria C ++ incorporata nella cassa cryptominisat, non con Rust stesso.
Anders Kaseorg,

1
@Lembik Ricevo un 404 da quella pasta.
Mego,

1
@ChristianSievers Buona domanda. Funziona ma sembra essere un po 'più lento (circa 2 volte). Non sono sicuro del perché non dovrebbe essere altrettanto buono, quindi forse CryptoMiniSat non è stato ben ottimizzato per quel tipo di carico di lavoro incrementale.
Anders Kaseorg,

9

Ruggine, n ≈ 30 o 31 o 32

Sul mio laptop (due core, i5-6200U), questo passa attraverso n = 1,…, 31 in 53 secondi, usando circa 2,5 GiB di memoria, o attraverso n = 1,…, 32 in 105 secondi, usando circa 5 GiB di memoria. Compila cargo build --releaseed esegui target/release/correlations.

src/main.rs

extern crate rayon;

type S = u32;
const S_BITS: u32 = 32;

fn cat(mut a: Vec<S>, mut b: Vec<S>) -> Vec<S> {
    if a.capacity() >= b.capacity() {
        a.append(&mut b);
        a
    } else {
        b.append(&mut a);
        b
    }
}

fn search(n: u32, i: u32, ss: Vec<S>) -> u32 {
    if ss.is_empty() {
        0
    } else if 2 * i + 1 > n {
        search_end(n, i, ss)
    } else if 2 * i + 1 == n {
        search2(n, i, ss.into_iter().flat_map(|s| vec![s, s | 1 << i]))
    } else {
        search2(n,
                i,
                ss.into_iter()
                    .flat_map(|s| {
                                  vec![s,
                                       s | 1 << i,
                                       s | 1 << n - i - 1,
                                       s | 1 << i | 1 << n - i - 1]
                              }))
    }
}

fn search2<SS: Iterator<Item = S>>(n: u32, i: u32, ss: SS) -> u32 {
    let (shift, mask) = (n - i - 1, !(!(0 as S) << i + 1));
    let close = |s: S| {
        let x = (s ^ s >> shift) & mask;
        x & x.wrapping_sub(1) == 0
    };
    let (ssy, ssn) = ss.partition(|&s| close(s));
    let (cy, cn) = rayon::join(|| search(n, i + 1, ssy), || search(n, i + 1, ssn));
    cy + cn
}

fn search_end(n: u32, i: u32, ss: Vec<S>) -> u32 {
    if i >= n - 1 { 1 } else { search_end2(n, i, ss) }
}

fn search_end2(n: u32, i: u32, mut ss: Vec<S>) -> u32 {
    let (shift, mask) = (n - i - 1, !(!(0 as S) << i + 1));
    let close = |s: S| {
        let x = (s ^ s >> shift) & mask;
        x & x.wrapping_sub(1) == 0
    };
    match ss.iter().position(|&s| close(s)) {
        Some(0) => {
            match ss.iter().position(|&s| !close(s)) {
                Some(p) => {
                    let (ssy, ssn) = ss.drain(p..).partition(|&s| close(s));
                    let (cy, cn) = rayon::join(|| search_end(n, i + 1, cat(ss, ssy)),
                                               || search_end(n, i + 1, ssn));
                    cy + cn
                }
                None => search_end(n, i + 1, ss),
            }
        }
        Some(p) => {
            let (ssy, ssn) = ss.drain(p..).partition(|&s| close(s));
            let (cy, cn) = rayon::join(|| search_end(n, i + 1, ssy),
                                       || search_end(n, i + 1, cat(ss, ssn)));
            cy + cn
        }
        None => search_end(n, i + 1, ss),
    }
}

fn main() {
    for n in 1..S_BITS + 1 {
        println!("{}: {}", n, search(n, 1, vec![0, 1]));
    }
}

Cargo.toml

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

[dependencies]
rayon = "0.7.0"

Provalo online!

Ho anche una variante leggermente più lenta che utilizza molta meno memoria.


Quali ottimizzazioni hai usato?

1
@Lembik La più grande ottimizzazione, oltre a fare tutto con l'aritmetica bit per bit in un linguaggio compilato, è usare solo il non determinismo necessario per fissare un prefisso dell'array Y / N. Faccio una ricerca ricorsiva su possibili prefissi dell'array Y / N, portando con sé un vettore di possibili stringhe che raggiungono quel prefisso, ma solo le stringhe il cui centro non esaminato è pieno di zeri. Detto questo, questa è ancora una ricerca esponenziale e queste ottimizzazioni la accelerano solo per fattori polinomiali.
Anders Kaseorg,

È una bella risposta Grazie. Spero che qualcuno possa scavare nella combinatoria per ottenere una notevole velocità.

@Lembik Ho corretto un bug di spreco di memoria, fatto più micro-ottimizzazione e aggiunto parallelismo. Ti preghiamo di ripetere il test quando ne hai la possibilità: spero di aumentare il mio punteggio di 1 o 2. Hai in mente idee combinatorie per accelerazioni più grandi? Non ho trovato niente.
Anders Kaseorg,

1
@Lembik Non esiste una formula fornita alla voce OEIS. (Sembra che anche il codice Mathematica utilizzi la forza bruta.) Se ne conosci uno, potresti parlarne.
Christian Sievers,

6

C ++ utilizzando la libreria BuDDy

Un approccio diverso: avere una formula binaria (come diagramma di decisione binaria ) che accetta i bit di Scome input ed è vera iff che fornisce alcuni valori fissi di Yo Nin determinate posizioni selezionate. Se quella formula non è costantemente falsa, selezionare una posizione libera e ricorrere, provando sia Ye N. Se non c'è posizione libera, abbiamo trovato un possibile valore di uscita. Se la formula è costantemente falsa, tornare indietro.

Funziona in modo relativamente ragionevole perché ci sono così pochi valori possibili in modo che spesso possiamo tornare indietro presto. Ho provato un'idea simile con un solutore SAT, ma ha avuto meno successo.

#include<vector>
#include<iostream>
#include<bdd.h>

// does vars[0..i-1] differ from vars[n-i..n-1] in at least two positions?
bdd cond(int i, int n, const std::vector<bdd>& vars){
  bdd x1 { bddfalse };
  bdd xs { bddfalse };
  for(int k=0; k<i; ++k){
    bdd d { vars[k] ^ vars[n-i+k] };
    xs |= d & x1;
    x1 |= d;
  }
  return xs;
}

void expand(int i, int n, int &c, const std::vector<bdd>& conds, bdd x){
  if (x==bddfalse)
    return;
  if (i==n-2){
    ++c;
    return;
  }

  expand(i+1,n,c,conds, x & conds[2*i]);
  x &= conds[2*i+1];
  expand(i+1,n,c,conds, x);
}

int count(int n){
  if (n==1)   // handle trivial case
    return 1;
  bdd_setvarnum(n-1);
  std::vector<bdd> vars {};
  vars.push_back(bddtrue); // assume first bit is 1
  for(int i=0; i<n-1; ++i)
    if (i%2==0)            // vars in mixed order
      vars.push_back(bdd_ithvar(i/2));
    else
      vars.push_back(bdd_ithvar(n-2-i/2));
  std::vector<bdd> conds {};
  for(int i=n-1; i>1; --i){ // handle long blocks first
    bdd cnd { cond(i,n,vars) };
    conds.push_back( cnd );
    conds.push_back( !cnd );
  }
  int c=0;
  expand(0,n,c,conds,bddtrue);
  return c;
}

int main(void){
  bdd_init(20000000,1000000);
  bdd_gbc_hook(nullptr); // comment out to see GC messages
  for(int n=1; ; ++n){
    std::cout << n << " " << count(n) << "\n" ;
  }
}

Per compilare con debian 8 (jessie), installa libbdd-deve fai g++ -std=c++11 -O3 -o hb hb.cpp -lbdd. Potrebbe essere utile aumentare ulteriormente il primo argomento bdd_init.


Questo sembra interessante. Che cosa ottieni con questo?

@Lembik Ottengo 31 su 100 su hardware molto vecchio che non mi consente di rispondere più velocemente
Christian Sievers,

Qualsiasi aiuto tu possa dare su come compilare questo su Windows (ad es. Usando Cygwin) ricevuto con gratitudine.

@Lembik Non conosco Windws ma github.com/fd00/yacp/tree/master/buddy sembra utile wrt cygwin
Christian Sievers l'

1
Wow, okay, mi hai convinto che ho bisogno di aggiungere questa libreria al mio toolkit. Molto bene!
Anders Kaseorg,

4

Clingo, n ≈ 30 o 31 34

Sono stato un po 'sorpreso di vedere cinque righe di codice Clingo che superano la mia soluzione Rust a forza bruta e si avvicinano molto alla soluzione BuDDy di Christian: sembra che avrebbe battuto anche quello con un limite di tempo più alto.

corr.lp

{s(2..n)}.
d(K,I) :- K=1..n-2, I=1..n-K, s(I), not s(K+I).
d(K,I) :- K=1..n-2, I=1..n-K, not s(I), s(K+I).
a(K) :- K=1..n-2, {d(K,1..n-K)} 1.
#show a/1.

corr.sh

#!/bin/bash
for ((n=1;;n++)); do
    echo "$n $(clingo corr.lp --project -q -n 0 -c n=$n | sed -n 's/Models *: //p')"
done

tracciare


Questo è fantastico! Dal tuo grafico sembra che la soluzione BuDDy peggiori improvvisamente. Qualche idea sul perché?

@Lembik Non ho studiato abbastanza BuDDy per essere sicuro, ma forse a questo punto si esaurisce la cache?
Anders Kaseorg,

Wow! Penso che un primo valore più alto bdd_initpossa aiutare, o consentire di aumentare maggiormente la tabella dei nodi chiamando bdd_setmaxincreasecon un valore molto superiore al valore predefinito di 50000. - Stai usando la versione modificata del mio programma?
Christian Sievers,

2
Adoro il tuo grafico.

1
Ottieni un incredibile aumento delle prestazioni utilizzando l'opzione --configuration=crafty( jumpye ottieni trendyrisultati simili).
Christian Sievers,

2

Pari / GP , 23

Per impostazione predefinita, Pari / GP limita le dimensioni dello stack a 8 MB. La prima riga del codice default(parisize, "4g")imposta questo limite a 4 GB. Se fornisce ancora uno stackoverflow, puoi impostarlo su 8 GB.

default(parisize, "4g")
f(n) = #vecsort([[2 > hammingweight(bitxor(s >> (n-i) , s % 2^i)) | i <- [2..n-1]] | s <- [0..2^(n-1)]], , 8)
for(n = 1, 100, print(n " -> " f(n)))

Raggiunge 22 e quindi fornisce uno stackoverflow.

Ora arriva a 24.

2

Clojure, 29 in 75 38 secondi, 30 in 80 e 31 in 165

Autonomia di Intel i7 6700K , l'utilizzo della memoria è inferiore a 200 MB.

project.clj (utilizza com.climate / claypoole per il multithreading):

(defproject tests "0.0.1-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [com.climate/claypoole "1.1.4"]]
  :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"]
  :aot [tests.core]
  :main tests.core)

Codice sorgente:

(ns tests.core
  (:require [com.climate.claypoole :as cp]
            [clojure.set])
  (:gen-class))

(defn f [N]
  (let [n-threads   (.. Runtime getRuntime availableProcessors)
        mask-offset (- 31 N)
        half-N      (quot N 2)
        mid-idx     (bit-shift-left 1 half-N)
        end-idx     (bit-shift-left 1 (dec N))
        lower-half  (bit-shift-right 0x7FFFFFFF mask-offset)
        step        (bit-shift-left 1 12)
        bitcount
          (fn [n]
            (loop [i 0 result 0]
              (if (= i N)
                result
                (recur
                  (inc i)
                  (-> n
                      (bit-xor (bit-shift-right n i))
                      (bit-and (bit-shift-right 0x7FFFFFFF (+ mask-offset i)))
                      Integer/bitCount
                      (< 2)
                      (if (+ result (bit-shift-left 1 i))
                          result))))))]
    (->>
      (cp/upfor n-threads [start (range 0 end-idx step)]
        (->> (for [i      (range start (min (+ start step) end-idx))
                   :when  (<= (Integer/bitCount (bit-shift-right i mid-idx))
                              (Integer/bitCount (bit-and         i lower-half)))]
               (bitcount i))
             (into #{})))
      (reduce clojure.set/union)
      count)))

(defn -main [n]
  (let [n-iters 5]
    (println "Calculating f(n) from 1 to" n "(inclusive)" n-iters "times")
    (doseq [i (range n-iters)]
      (->> n read-string inc (range 1) (map f) doall println time)))
  (shutdown-agents)
  (System/exit 0))

Una soluzione a forza bruta, ogni thread supera un sottoinsieme dell'intervallo (2 ^ 12 elementi) e crea un insieme di valori interi che indicano i modelli rilevati. Questi vengono quindi "uniti" insieme e quindi viene calcolato il conteggio distinto. Spero che il codice non sia troppo complicato da seguire anche se utilizza molto le macro di threading . Ho maineseguito il test alcune volte per riscaldare JVM.

Aggiornamento: Iterando solo la metà degli interi, ottiene lo stesso risultato a causa della simmetria. Saltare anche i numeri con un numero di bit più elevato nella metà inferiore del numero poiché producono anche duplicati.

Uberjar pre-costruito ( v1 ) (3,7 MB):

$ wget https://s3-eu-west-1.amazonaws.com/nikonyrh-public/misc/so-124424-v2.jar
$ java -jar so-124424-v2.jar 29
Calculating f(n) from 1 to 29 (inclusive) 5 times
(1 1 2 4 6 8 14 18 27 36 52 65 93 113 150 188 241 279 377 427 540 632 768 870 1082 1210 1455 1656 1974)
"Elapsed time: 41341.863703 msecs"
(1 1 2 4 6 8 14 18 27 36 52 65 93 113 150 188 241 279 377 427 540 632 768 870 1082 1210 1455 1656 1974)
"Elapsed time: 37752.118265 msecs"
(1 1 2 4 6 8 14 18 27 36 52 65 93 113 150 188 241 279 377 427 540 632 768 870 1082 1210 1455 1656 1974)
"Elapsed time: 38568.406528 msecs"
[ctrl+c]

Risultati su diversi hardware, il runtime previsto è O(n * 2^n)?

i7-6700K  desktop: 1 to 29 in  38 seconds
i7-6820HQ laptop:  1 to 29 in  43 seconds
i5-3570K  desktop: 1 to 29 in 114 seconds

Puoi facilmente creare questo thread singolo ed evitare quella dipendenza di terze parti usando lo standard per:

(for [start (range 0 end-idx step)]
  ... )

Bene, esiste anche la pmap integrata, ma Claypoole ha più funzionalità e ottimizzazione.


Sì, lo rende banale da distribuire. Avresti tempo di rivalutare la mia soluzione, sono abbastanza sicuro che lo faresti fino a 30 ora. Non ho ulteriori ottimizzazioni in vista.
NikoNyrh,

Purtroppo è un no per 30. Tempo trascorso: 217150.87386 msecs

Ahaa, grazie per averci provato: D Sarebbe stato meglio adattarsi a una curva su questo e interpolare quello a cui viene speso il valore decimale 120 secondi ma anche se è una bella sfida.
NikoNyrh,

1

Mathematica, n = 19

premi alt +. per interrompere e il risultato verrà stampato

k = 0;
For[n = 1, n < 1000, n++,
Z = Table[HammingDistance[#[[;; i]], #[[-i ;;]]], {i, Length@#}] & /@
Tuples[{0, 1}, n];
Table[If[Z[[i, j]] < 2, Z[[i, j]] = 0, Z[[i, j]] = 1], {i, 
Length@Z}, {j, n}];
k = Length@Union@Z]
Print["f(", n, ")=", k]

Non posso farcela, quindi potresti spiegarmi come evitare di perdere tempo esponenziale? 2 ^ 241 è un numero molto grande!

Puoi mostrare l'output del codice?

1
Intendevo f (n) ... risolto
J42161217

1

Mathematica, 21

f [n_]: = Lunghezza @
     DeleteDuplicates @
      Trasporre@
       Tabella [2> Tr @ IntegerDigits [#, 2] e / @ 
         BitXor [BitShiftRight [#, n - i], Mod [#, 2 ^ i]], {i, 1, 
         n - 1}] & @ Range [0, 2 ^ (n - 1)];
Esegui [Stampa [n -> f @ n], {n, Infinito}]

Per confronto, la risposta di Jenny_mathyn = 19sul mio computer.

La parte più lenta è Tr@IntegerDigits[#, 2] &. È un peccato che Mathematica non abbia un peso integrato per il peso di Hamming.


Se vuoi testare il mio codice, puoi scaricare una versione di prova gratuita di Mathematica .


1

Versione AC, utilizzando popcount incorporato

Funziona meglio con clang -O3, ma funziona anche se hai solo gcc.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned long pairs(unsigned int n, unsigned long s) { 
  unsigned long result = 0;

  for(int d=1;d<=n;d++) { 
    unsigned long mx = 1 << d;
    unsigned long mask = mx - 1;

    unsigned long diff = (s >> (n - d)) ^ (s & mask);
    if (__builtin_popcountl(diff) <= 1)
      result |= mx;
  } 
  return result;

}

unsigned long f(unsigned long  n) { 
  unsigned long max = 1 << (n - 1);
#define BLEN (max / 2)
  unsigned char * buf = malloc(BLEN);
  memset(buf, 0, BLEN);
  unsigned long long * bufll = (void *) buf;

  for(unsigned long i=0;i<=max;i++) { 
    unsigned int r = pairs(n, i);
    buf[r / 8] |= 1 << (r % 8);
  } 

  unsigned long result = 0;

  for(unsigned long i=0;i<= max / 2 / sizeof(unsigned long long); i++) { 
    result += __builtin_popcountll(bufll[i]);
  } 

  free(buf);

  return result;
}

int main(int argc, char ** argv) { 
  unsigned int n = 1;

  while(1) { 
    printf("%d %ld\n", n, f(n));
    n++;
  } 
  return 0;
}

Arriva a 24 molto rapidamente e poi finisce. Devi aumentare il limite.

Oddio, ho dimenticato di rimuovere il codice di riferimento! Rimuoverò le due linee offensive: /
bartavelle il

@Lembik ora dovrebbe essere risolto
bartavelle il

1

Haskell, (non ufficiale n = 20)

Questo è solo l'approccio ingenuo, finora senza alcuna ottimizzazione. Mi chiedevo quanto sarebbe andata bene contro altre lingue.

Come usarlo (supponendo che tu abbia installato la piattaforma haskell ):

  • Incolla il codice in un file approx_corr.hs(o qualsiasi altro nome, modifica i seguenti passaggi di conseguenza)
  • Passare al file ed eseguire ghc approx_corr.hs
  • Correre approx_corr.exe
  • Inserisci il massimo n
  • Viene visualizzato il risultato di ciascun calcolo, nonché il tempo reale cumulativo (in ms) fino a quel punto.

Codice:

import Data.List
import Data.Time
import Data.Time.Clock.POSIX

num2bin :: Int -> Int -> [Int]
num2bin 0 _ = []
num2bin n k| k >= 2^(n-1) = 1 : num2bin (n-1)( k-2^(n-1))
           | otherwise  = 0: num2bin (n-1) k

genBinNum :: Int -> [[Int]]
genBinNum n = map (num2bin n) [0..2^n-1]

pairs :: [a] -> [([a],[a])]
pairs xs = zip (prefixes xs) (suffixes xs)
   where prefixes = tail . init . inits 
         suffixes = map reverse . prefixes . reverse 

hammingDist :: (Num b, Eq a) => ([a],[a]) -> b     
hammingDist (a,b) = sum $ zipWith (\u v -> if u /= v then 1 else 0) a b

f :: Int -> Int
f n = length $ nub $ map (map ((<=1).hammingDist) . pairs) $ genBinNum n
--f n = sum [1..n]

--time in milliseconds
getTime = getCurrentTime >>= pure . (1000*) . utcTimeToPOSIXSeconds >>= pure . round


main :: IO()
main = do 
    maxns <- getLine 
    let maxn = (read maxns)::Int
    t0 <- getTime 
    loop 1 maxn t0
     where loop n maxn t0|n==maxn = return ()
           loop n maxn t0
             = do 
                 putStrLn $ "fun eval: " ++ (show n) ++ ", " ++ (show $ (f n)) 
                 t <- getTime
                 putStrLn $ "time: " ++ show (t-t0); 
                 loop (n+1) maxn t0

Il codice sembra non fornire output durante l'esecuzione. Questo rende un po 'difficile il test.

Strano, si compila senza errori? Cosa succede se si tenta di compilare il programma main = putStrLn "Hello World!"?
flawr

Il Data.Bitsmodulo potrebbe essere utile. Per il tuo loop principale, potresti usare qualcosa come main = do maxn <- getmax; t0 <- gettime; loop 1where loop n|n==maxn = return ()e loop n = do printresult n (f n); t <- gettime; printtime (t-t0); loop (n+1). getmaxpotrebbe ad esempio usare getArgsper usare gli argomenti del programma.
Christian Sievers,

@ChristianSievers Grazie mille !!! Ho fatto questa domanda su StackOverflow, penso che sarebbe fantastico se tu potessi aggiungerlo anche lì!
flawr

Non vedo come rispondere lì. Hai già un ciclo simile lì, e non ho detto nulla su come ottenere il tempo: che avevi già qui.
Christian Sievers,

1

Una soluzione Haskell, che utilizza popCount e parallelismo gestito manualmente

Compilare: ghc -rtsopts -threaded -O2 -fllvm -Wall foo.hs

(rilasciare -llvmse non funziona)

Correre : ./foo +RTS -N

module Main (main) where

import Data.Bits
import Data.Word
import Data.List
import qualified Data.IntSet as S 
import System.IO
import Control.Monad
import Control.Concurrent
import Control.Exception.Base (evaluate)

pairs' :: Int -> Word64 -> Int
pairs' n s = fromIntegral $ foldl' (.|.) 0 $ map mk [1..n]
  where mk d = let mask = 1 `shiftL` d - 1 
                   pc = popCount $! xor (s `shiftR` (n - d)) (s .&. mask)
               in  if pc <= 1 
                     then mask + 1 
                     else 0 

mkSet :: Int -> Word64 -> Word64 -> S.IntSet
mkSet n a b = S.fromList $ map (pairs' n) [a .. b]

f :: Int -> IO Int
f n 
   | n < 4 = return $ S.size $ mkSet n 0 mxbound
   | otherwise = do
        mvs <- replicateM 4 newEmptyMVar
        forM_ (zip mvs cpairs) $ \(mv,(mi,ma)) -> forkIO $ do
          evaluate (mkSet n mi ma) >>= putMVar mv
        set <- foldl' S.union S.empty <$> mapM readMVar mvs
        return $! S.size set
   where
     mxbound = 1 `shiftL` (n - 1)
     bounds = [0,1 `shiftL` (n - 3) .. mxbound]
     cpairs = zip bounds (drop 1 bounds)

main :: IO()
main = do
    hSetBuffering stdout LineBuffering
    mapM_ (f >=> print) [1..]

Si è verificato un problema di buffering in quanto non ottengo alcun output se lo eseguo dalla riga di comando di cygwim.

Ho appena aggiornato la mia soluzione, ma non so se sarà di grande aiuto.
bartavelle,

@Lembik Non so se sia ovvio, ma dovrebbe essere compilato -O3e potrebbe essere più veloce con -O3 -fllvm...
bartavelle,

(E tutti i file di compilazione devono essere rimossi prima della ricompilazione, se non si verifica la modifica del codice sorgente)
bartavelle,

@Lembik: ho introdotto il parallelismo. Dovrebbe essere un po 'più veloce.
bartavelle

0

Python 2 + pypy, n = 22

Ecco una soluzione Python davvero semplice come una sorta di benchmark di base.

import itertools
def hamming(A, B):
    n = len(A)
    assert(len(B) == n)
    return n-sum([A[i] == B[i] for i in xrange(n)])

def prefsufflist(P):
    n = len(P)
    return [hamming(P[:i], P[n-i:n]) for i in xrange(1,n+1)]

bound = 1
for n in xrange(1,25):
    booleans = set()
    for P in itertools.product([0,1], repeat = n):
        booleans.add(tuple(int(HD <= bound) for HD in prefsufflist(P)))
    print "n = ", n, len(booleans)
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.