Apakah ada cara yang efisien untuk menghasilkan bilangan bulat acak N dalam rentang yang memiliki jumlah atau rata-rata tertentu?

14

Apakah ada cara efisien untuk menghasilkan kombinasi acak bilangan bulat N sehingga—

  • setiap bilangan bulat berada dalam interval [ min, max],
  • bilangan bulat memiliki jumlah sum,
  • bilangan bulat dapat muncul dalam urutan apa pun (mis., urutan acak), dan
  • kombinasi dipilih secara seragam secara acak dari semua kombinasi yang memenuhi persyaratan lain?

Apakah ada algoritma yang sama untuk kombinasi acak di mana bilangan bulat harus muncul dalam urutan diurutkan berdasarkan nilainya (daripada dalam urutan apa pun)?

(Memilih kombinasi yang sesuai dengan rata-rata meanadalah kasus khusus, jika sum = N * mean. Masalah ini setara dengan menghasilkan partisi acak yang seragam sumke dalam N bagian yang masing-masing dalam interval [ min, max] dan muncul dalam urutan apa pun atau dalam urutan diurutkan menurut urutannya. nilai-nilai, sesuai kasusnya.)

Saya menyadari bahwa masalah ini dapat diselesaikan dengan cara berikut untuk kombinasi yang muncul dalam urutan acak (EDIT [27 Apr]: Algoritma dimodifikasi.):

  1. Jika N * max < sumatau N * min > sum, tidak ada solusi.

  2. Jika N * max == sum, hanya ada satu solusi, di mana semua Nangka sama dengan max. Jika N * min == sum, hanya ada satu solusi, di mana semua Nangka sama dengan min.

  3. Gunakan algoritma yang diberikan dalam Smith dan Tromble ("Pengambilan Sampel dari Unit Simplex", 2004) untuk menghasilkan bilangan bulat acak non-negatif N dengan penjumlahan sum - N * min.

  4. Tambahkan minke setiap nomor yang dihasilkan dengan cara ini.

  5. Jika ada nomor yang lebih besar dari max, lanjutkan ke langkah 3.

Namun, algoritma ini lambat jika maxjauh lebih kecil sum. Sebagai contoh, menurut pengujian saya (dengan implementasi kasus khusus yang melibatkan di atas mean), algoritma menolak, rata-rata—

  • sekitar 1,6 sampel jika N = 7, min = 3, max = 10, sum = 42, tetapi
  • sekitar 30,6 sampel jika N = 20, min = 3, max = 10, sum = 120.

Apakah ada cara untuk memodifikasi algoritma ini agar efisien untuk N besar sambil tetap memenuhi persyaratan di atas?

EDIT:

Sebagai alternatif yang disarankan dalam komentar, cara efisien untuk menghasilkan kombinasi acak yang valid (yang memenuhi semua kecuali persyaratan terakhir) adalah:

  1. Hitung X, jumlah kombinasi yang valid mungkin diberikan sum, mindan max.
  2. Pilih Y, bilangan bulat acak seragam [0, X).
  3. Ubah ("unrank") Ymenjadi kombinasi yang valid.

Namun, apakah ada rumus untuk menghitung jumlah kombinasi yang valid (atau permutasi), dan adakah cara untuk mengkonversi integer ke kombinasi yang valid? [EDIT (28 April): Sama untuk permutasi daripada kombinasi].

EDIT (27 April):

Setelah membaca Devroye's Non-Uniform Random Variate Generation (1986), saya dapat mengkonfirmasi bahwa ini adalah masalah menghasilkan partisi acak. Juga, Latihan 2 (terutama bagian E) pada halaman 661 relevan dengan pertanyaan ini.

EDIT (28 April):

Ternyata algoritma yang saya berikan seragam di mana bilangan bulat yang terlibat diberikan dalam urutan acak , sebagai lawan urutan diurutkan berdasarkan nilai-nilai mereka . Karena kedua masalah tersebut merupakan kepentingan umum, saya telah memodifikasi pertanyaan ini untuk mencari jawaban kanonik untuk kedua masalah tersebut.

Kode Ruby berikut dapat digunakan untuk memverifikasi solusi potensial untuk keseragaman (di mana algorithm(...)kandidat algoritma):

combos={}
permus={}
mn=0
mx=6
sum=12
for x in mn..mx
  for y in mn..mx
    for z in mn..mx
      if x+y+z==sum
        permus[[x,y,z]]=0
      end
      if x+y+z==sum and x<=y and y<=z
        combos[[x,y,z]]=0
      end
    end
  end
end

3000.times {|x|
 f=algorithm(3,sum,mn,mx)
 combos[f.sort]+=1
 permus[f]+=1
}
p combos
p permus

EDIT (29 April): Menambahkan kembali kode Ruby dari implementasi saat ini.

Contoh kode berikut diberikan dalam Ruby, tetapi pertanyaan saya tidak tergantung pada bahasa pemrograman:

def posintwithsum(n, total)
    raise if n <= 0 or total <=0
    ls = [0]
    ret = []
    while ls.length < n
      c = 1+rand(total-1)
      found = false
      for j in 1...ls.length
        if ls[j] == c
          found = true
          break
        end
      end
      if found == false;ls.push(c);end
    end
    ls.sort!
    ls.push(total)
    for i in 1...ls.length
       ret.push(ls[i] - ls[i - 1])
    end
    return ret
end

def integersWithSum(n, total)
 raise if n <= 0 or total <=0
 ret = posintwithsum(n, total + n)
 for i in 0...ret.length
    ret[i] = ret[i] - 1
 end
 return ret
end

# Generate 100 valid samples
mn=3
mx=10
sum=42
n=7
100.times {
 while true
    pp=integersWithSum(n,sum-n*mn).map{|x| x+mn }
    if !pp.find{|x| x>mx }
      p pp; break # Output the sample and break
    end
 end
}
Peter O.
sumber
Bisakah Anda memperjelas persyaratan ketiga Anda? Apakah Anda memerlukan keseragaman di antara semua kombinasi yang mungkin (termasuk yang dengan mean yang salah), atau di antara semua kombinasi yang valid (yaitu yang dengan mean yang benar)?
user58697
Semua kombinasi yang valid, yaitu semua kombinasi yang memenuhi persyaratan lainnya.
Peter O.
Jika kita memiliki cara untuk menghitung dan melepaskan partisi dari jumlah yang dibatasi untuk integer N dalam [min, maks], akankah memilih salah satu dari partisi tersebut secara acak dan unranking mewakili distribusi yang seragam, dan apakah itu lebih efisien daripada metode Anda saat ini? Seberapa besar jumlah dan N?
גלעד ברקן
Saya tidak tahu apa yang Anda maksud dengan "unranking partisi jumlah", dan saya tidak menyadari bukti bahwa hal itu menghasilkan distribusi yang seragam dalam arti pertanyaan ini. Untuk pertanyaan ini, keduanya sumdan Nsecara efektif tidak terbatas (sesuai alasan). Saya mencari jawaban kanonik karena masalah mendasar muncul dalam banyak pertanyaan yang diajukan pada Stack Overflow, termasuk yang ini dan yang ini . @ גלעדברקן
Peter O.
Jika kita memberi setiap kemungkinan kombinasi "peringkat" (atau indeks) dalam pengaturan yang terurut dari semuanya, "unranking," akan berarti menghasilkan kombinasi, mengingat peringkatnya (dan N, min, dan maks, tentu saja). Mengapa pilihan satu dari semua kombinasi yang mungkin tidak sesuai dengan distribusi yang seragam?
גלעד ברקן

Jawaban:

5

Inilah solusi saya di Jawa. Ini berfungsi penuh dan berisi dua generator: PermutationPartitionGeneratoruntuk partisi yang tidak disortir dan CombinationPartitionGeneratoruntuk partisi yang diurutkan. Generator Anda juga diterapkan di kelas SmithTromblePartitionGeneratoruntuk perbandingan. Kelas SequentialEnumeratormenghitung semua kemungkinan partisi (tidak disortir atau disortir, tergantung pada parameter) dalam urutan berurutan. Saya telah menambahkan tes menyeluruh (termasuk kasus pengujian Anda) untuk semua generator ini. Implementasinya dapat dijelaskan sendiri untuk sebagian besar. Jika Anda memiliki pertanyaan, saya akan menjawabnya dalam beberapa hari.

import java.util.Random;
import java.util.function.Supplier;

public abstract class PartitionGenerator implements Supplier<int[]>{
    public static final Random rand = new Random();
    protected final int numberCount;
    protected final int min;
    protected final int range;
    protected final int sum; // shifted sum
    protected final boolean sorted;

    protected PartitionGenerator(int numberCount, int min, int max, int sum, boolean sorted) {
        if (numberCount <= 0)
            throw new IllegalArgumentException("Number count should be positive");
        this.numberCount = numberCount;
        this.min = min;
        range = max - min;
        if (range < 0)
            throw new IllegalArgumentException("min > max");
        sum -= numberCount * min;
        if (sum < 0)
            throw new IllegalArgumentException("Sum is too small");
        if (numberCount * range < sum)
            throw new IllegalArgumentException("Sum is too large");
        this.sum = sum;
        this.sorted = sorted;
    }

    // Whether this generator returns sorted arrays (i.e. combinations)
    public final boolean isSorted() {
        return sorted;
    }

    public interface GeneratorFactory {
        PartitionGenerator create(int numberCount, int min, int max, int sum);
    }
}

import java.math.BigInteger;

// Permutations with repetition (i.e. unsorted vectors) with given sum
public class PermutationPartitionGenerator extends PartitionGenerator {
    private final double[][] distributionTable;

    public PermutationPartitionGenerator(int numberCount, int min, int max, int sum) {
        super(numberCount, min, max, sum, false);
        distributionTable = calculateSolutionCountTable();
    }

    private double[][] calculateSolutionCountTable() {
        double[][] table = new double[numberCount + 1][sum + 1];
        BigInteger[] a = new BigInteger[sum + 1];
        BigInteger[] b = new BigInteger[sum + 1];
        for (int i = 1; i <= sum; i++)
            a[i] = BigInteger.ZERO;
        a[0] = BigInteger.ONE;
        table[0][0] = 1.0;
        for (int n = 1; n <= numberCount; n++) {
            double[] t = table[n];
            for (int s = 0; s <= sum; s++) {
                BigInteger z = BigInteger.ZERO;
                for (int i = Math.max(0, s - range); i <= s; i++)
                    z = z.add(a[i]);
                b[s] = z;
                t[s] = z.doubleValue();
            }
            // swap a and b
            BigInteger[] c = b;
            b = a;
            a = c;
        }
        return table;
    }

    @Override
    public int[] get() {
        int[] p = new int[numberCount];
        int s = sum; // current sum
        for (int i = numberCount - 1; i >= 0; i--) {
            double t = rand.nextDouble() * distributionTable[i + 1][s];
            double[] tableRow = distributionTable[i];
            int oldSum = s;
            // lowerBound is introduced only for safety, it shouldn't be crossed 
            int lowerBound = s - range;
            if (lowerBound < 0)
                lowerBound = 0;
            s++;
            do
                t -= tableRow[--s];
            // s can be equal to lowerBound here with t > 0 only due to imprecise subtraction
            while (t > 0 && s > lowerBound);
            p[i] = min + (oldSum - s);
        }
        assert s == 0;
        return p;
    }

    public static final GeneratorFactory factory = (numberCount, min, max,sum) ->
        new PermutationPartitionGenerator(numberCount, min, max, sum);
}

import java.math.BigInteger;

// Combinations with repetition (i.e. sorted vectors) with given sum 
public class CombinationPartitionGenerator extends PartitionGenerator {
    private final double[][][] distributionTable;

    public CombinationPartitionGenerator(int numberCount, int min, int max, int sum) {
        super(numberCount, min, max, sum, true);
        distributionTable = calculateSolutionCountTable();
    }

    private double[][][] calculateSolutionCountTable() {
        double[][][] table = new double[numberCount + 1][range + 1][sum + 1];
        BigInteger[][] a = new BigInteger[range + 1][sum + 1];
        BigInteger[][] b = new BigInteger[range + 1][sum + 1];
        double[][] t = table[0];
        for (int m = 0; m <= range; m++) {
            a[m][0] = BigInteger.ONE;
            t[m][0] = 1.0;
            for (int s = 1; s <= sum; s++) {
                a[m][s] = BigInteger.ZERO;
                t[m][s] = 0.0;
            }
        }
        for (int n = 1; n <= numberCount; n++) {
            t = table[n];
            for (int m = 0; m <= range; m++)
                for (int s = 0; s <= sum; s++) {
                    BigInteger z;
                    if (m == 0)
                        z = a[0][s];
                    else {
                        z = b[m - 1][s];
                        if (m <= s)
                            z = z.add(a[m][s - m]);
                    }
                    b[m][s] = z;
                    t[m][s] = z.doubleValue();
                }
            // swap a and b
            BigInteger[][] c = b;
            b = a;
            a = c;
        }
        return table;
    }

    @Override
    public int[] get() {
        int[] p = new int[numberCount];
        int m = range; // current max
        int s = sum; // current sum
        for (int i = numberCount - 1; i >= 0; i--) {
            double t = rand.nextDouble() * distributionTable[i + 1][m][s];
            double[][] tableCut = distributionTable[i];
            if (s < m)
                m = s;
            s -= m;
            while (true) {
                t -= tableCut[m][s];
                // m can be 0 here with t > 0 only due to imprecise subtraction
                if (t <= 0 || m == 0)
                    break;
                m--;
                s++;
            }
            p[i] = min + m;
        }
        assert s == 0;
        return p;
    }

    public static final GeneratorFactory factory = (numberCount, min, max, sum) ->
        new CombinationPartitionGenerator(numberCount, min, max, sum);
}

import java.util.*;

public class SmithTromblePartitionGenerator extends PartitionGenerator {
    public SmithTromblePartitionGenerator(int numberCount, int min, int max, int sum) {
        super(numberCount, min, max, sum, false);
    }

    @Override
    public int[] get() {
        List<Integer> ls = new ArrayList<>(numberCount + 1);
        int[] ret = new int[numberCount];
        int increasedSum = sum + numberCount;
        while (true) {
            ls.add(0);
            while (ls.size() < numberCount) {
                int c = 1 + rand.nextInt(increasedSum - 1);
                if (!ls.contains(c))
                    ls.add(c);
            }
            Collections.sort(ls);
            ls.add(increasedSum);
            boolean good = true;
            for (int i = 0; i < numberCount; i++) {
                int x = ls.get(i + 1) - ls.get(i) - 1;
                if (x > range) {
                    good = false;
                    break;
                }
                ret[i] = x;
            }
            if (good) {
                for (int i = 0; i < numberCount; i++)
                    ret[i] += min;
                return ret;
            }
            ls.clear();
        }
    }

    public static final GeneratorFactory factory = (numberCount, min, max, sum) ->
        new SmithTromblePartitionGenerator(numberCount, min, max, sum);
}

import java.util.Arrays;

// Enumerates all partitions with given parameters
public class SequentialEnumerator extends PartitionGenerator {
    private final int max;
    private final int[] p;
    private boolean finished;

    public SequentialEnumerator(int numberCount, int min, int max, int sum, boolean sorted) {
        super(numberCount, min, max, sum, sorted);
        this.max = max;
        p = new int[numberCount];
        startOver();
    }

    private void startOver() {
        finished = false;
        int unshiftedSum = sum + numberCount * min;
        fillMinimal(0, Math.max(min, unshiftedSum - (numberCount - 1) * max), unshiftedSum);
    }

    private void fillMinimal(int beginIndex, int minValue, int fillSum) {
        int fillRange = max - minValue;
        if (fillRange == 0)
            Arrays.fill(p, beginIndex, numberCount, max);
        else {
            int fillCount = numberCount - beginIndex;
            fillSum -= fillCount * minValue;
            int maxCount = fillSum / fillRange;
            int maxStartIndex = numberCount - maxCount;
            Arrays.fill(p, maxStartIndex, numberCount, max);
            fillSum -= maxCount * fillRange;
            Arrays.fill(p, beginIndex, maxStartIndex, minValue);
            if (fillSum != 0)
                p[maxStartIndex - 1] = minValue + fillSum;
        }
    }

    @Override
    public int[] get() { // returns null when there is no more partition, then starts over
        if (finished) {
            startOver();
            return null;
        }
        int[] pCopy = p.clone();
        if (numberCount > 1) {
            int i = numberCount;
            int s = p[--i];
            while (i > 0) {
                int x = p[--i];
                if (x == max) {
                    s += x;
                    continue;
                }
                x++;
                s--;
                int minRest = sorted ? x : min;
                if (s < minRest * (numberCount - i - 1)) {
                    s += x;
                    continue;
                }
                p[i++]++;
                fillMinimal(i, minRest, s);
                return pCopy;
            }
        }
        finished = true;
        return pCopy;
    }

    public static final GeneratorFactory permutationFactory = (numberCount, min, max, sum) ->
        new SequentialEnumerator(numberCount, min, max, sum, false);
    public static final GeneratorFactory combinationFactory = (numberCount, min, max, sum) ->
        new SequentialEnumerator(numberCount, min, max, sum, true);
}

import java.util.*;
import java.util.function.BiConsumer;
import PartitionGenerator.GeneratorFactory;

public class Test {
    private final int numberCount;
    private final int min;
    private final int max;
    private final int sum;
    private final int repeatCount;
    private final BiConsumer<PartitionGenerator, Test> procedure;

    public Test(int numberCount, int min, int max, int sum, int repeatCount,
            BiConsumer<PartitionGenerator, Test> procedure) {
        this.numberCount = numberCount;
        this.min = min;
        this.max = max;
        this.sum = sum;
        this.repeatCount = repeatCount;
        this.procedure = procedure;
    }

    @Override
    public String toString() {
        return String.format("=== %d numbers from [%d, %d] with sum %d, %d iterations ===",
                numberCount, min, max, sum, repeatCount);
    }

    private static class GeneratedVector {
        final int[] v;

        GeneratedVector(int[] vect) {
            v = vect;
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(v);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            return Arrays.equals(v, ((GeneratedVector)obj).v);
        }

        @Override
        public String toString() {
            return Arrays.toString(v);
        }
    }

    private static final Comparator<Map.Entry<GeneratedVector, Integer>> lexicographical = (e1, e2) -> {
        int[] v1 = e1.getKey().v;
        int[] v2 = e2.getKey().v;
        int len = v1.length;
        int d = len - v2.length;
        if (d != 0)
            return d;
        for (int i = 0; i < len; i++) {
            d = v1[i] - v2[i];
            if (d != 0)
                return d;
        }
        return 0;
    };

    private static final Comparator<Map.Entry<GeneratedVector, Integer>> byCount =
            Comparator.<Map.Entry<GeneratedVector, Integer>>comparingInt(Map.Entry::getValue)
            .thenComparing(lexicographical);

    public static int SHOW_MISSING_LIMIT = 10;

    private static void checkMissingPartitions(Map<GeneratedVector, Integer> map, PartitionGenerator reference) {
        int missingCount = 0;
        while (true) {
            int[] v = reference.get();
            if (v == null)
                break;
            GeneratedVector gv = new GeneratedVector(v);
            if (!map.containsKey(gv)) {
                if (missingCount == 0)
                    System.out.println(" Missing:");
                if (++missingCount > SHOW_MISSING_LIMIT) {
                    System.out.println("  . . .");
                    break;
                }
                System.out.println(gv);
            }
        }
    }

    public static final BiConsumer<PartitionGenerator, Test> distributionTest(boolean sortByCount) {
        return (PartitionGenerator gen, Test test) -> {
            System.out.print("\n" + getName(gen) + "\n\n");
            Map<GeneratedVector, Integer> combos = new HashMap<>();
            // There's no point of checking permus for sorted generators
            // because they are the same as combos for them
            Map<GeneratedVector, Integer> permus = gen.isSorted() ? null : new HashMap<>();
            for (int i = 0; i < test.repeatCount; i++) {
                int[] v = gen.get();
                if (v == null && gen instanceof SequentialEnumerator)
                    break;
                if (permus != null) {
                    permus.merge(new GeneratedVector(v), 1, Integer::sum);
                    v = v.clone();
                    Arrays.sort(v);
                }
                combos.merge(new GeneratedVector(v), 1, Integer::sum);
            }
            Set<Map.Entry<GeneratedVector, Integer>> sortedEntries = new TreeSet<>(
                    sortByCount ? byCount : lexicographical);
            System.out.println("Combos" + (gen.isSorted() ? ":" : " (don't have to be uniform):"));
            sortedEntries.addAll(combos.entrySet());
            for (Map.Entry<GeneratedVector, Integer> e : sortedEntries)
                System.out.println(e);
            checkMissingPartitions(combos, test.getGenerator(SequentialEnumerator.combinationFactory));
            if (permus != null) {
                System.out.println("\nPermus:");
                sortedEntries.clear();
                sortedEntries.addAll(permus.entrySet());
                for (Map.Entry<GeneratedVector, Integer> e : sortedEntries)
                    System.out.println(e);
                checkMissingPartitions(permus, test.getGenerator(SequentialEnumerator.permutationFactory));
            }
        };
    }

    public static final BiConsumer<PartitionGenerator, Test> correctnessTest =
        (PartitionGenerator gen, Test test) -> {
        String genName = getName(gen);
        for (int i = 0; i < test.repeatCount; i++) {
            int[] v = gen.get();
            if (v == null && gen instanceof SequentialEnumerator)
                v = gen.get();
            if (v.length != test.numberCount)
                throw new RuntimeException(genName + ": array of wrong length");
            int s = 0;
            if (gen.isSorted()) {
                if (v[0] < test.min || v[v.length - 1] > test.max)
                    throw new RuntimeException(genName + ": generated number is out of range");
                int prev = test.min;
                for (int x : v) {
                    if (x < prev)
                        throw new RuntimeException(genName + ": unsorted array");
                    s += x;
                    prev = x;
                }
            } else
                for (int x : v) {
                    if (x < test.min || x > test.max)
                        throw new RuntimeException(genName + ": generated number is out of range");
                    s += x;
                }
            if (s != test.sum)
                throw new RuntimeException(genName + ": wrong sum");
        }
        System.out.format("%30s :   correctness test passed%n", genName);
    };

    public static final BiConsumer<PartitionGenerator, Test> performanceTest =
        (PartitionGenerator gen, Test test) -> {
        long time = System.nanoTime();
        for (int i = 0; i < test.repeatCount; i++)
            gen.get();
        time = System.nanoTime() - time;
        System.out.format("%30s : %8.3f s %10.0f ns/test%n", getName(gen), time * 1e-9, time * 1.0 / test.repeatCount);
    };

    public PartitionGenerator getGenerator(GeneratorFactory factory) {
        return factory.create(numberCount, min, max, sum);
    }

    public static String getName(PartitionGenerator gen) {
        String name = gen.getClass().getSimpleName();
        if (gen instanceof SequentialEnumerator)
            return (gen.isSorted() ? "Sorted " : "Unsorted ") + name;
        else
            return name;
    }

    public static GeneratorFactory[] factories = { SmithTromblePartitionGenerator.factory,
            PermutationPartitionGenerator.factory, CombinationPartitionGenerator.factory,
            SequentialEnumerator.permutationFactory, SequentialEnumerator.combinationFactory };

    public static void main(String[] args) {
        Test[] tests = {
                            new Test(3, 0, 3, 5, 3_000, distributionTest(false)),
                            new Test(3, 0, 6, 12, 3_000, distributionTest(true)),
                            new Test(50, -10, 20, 70, 2_000, correctnessTest),
                            new Test(7, 3, 10, 42, 1_000_000, performanceTest),
                            new Test(20, 3, 10, 120, 100_000, performanceTest)
                       };
        for (Test t : tests) {
            System.out.println(t);
            for (GeneratorFactory factory : factories) {
                PartitionGenerator candidate = t.getGenerator(factory);
                t.procedure.accept(candidate, t);
            }
            System.out.println();
        }
    }
}

Anda dapat mencoba ini di Ideone .

John McClane
sumber
Terima kasih atas jawaban anda; ini bekerja dengan baik. Saya telah menggambarkan generator permutasi dalam jawaban lain di sini; jawab pertanyaan lain dengan bantuan Anda; dan akan segera menyertakan algoritma Anda dalam kode sampel Python untuk artikel saya tentang metode generasi acak.
Peter O.
Untuk lebih jelasnya. Apakah algoritma ini bergantung pada menghasilkan semua kemungkinan partisi / komposisi untuk sampel?
Joseph Wood
@JosephWood Tidak, ini bergantung pada penghitungan mereka semua. Ini dilakukan hanya satu kali pada inisialisasi generator dan agak efektif karena menggunakan pendekatan pemrograman dinamis.
John McClane
Bagaimana pemrograman dinamis dapat memecahkan masalah terkait dalam memilih partisi acak 'sum' menjadi N bilangan bulat yang dipilih secara acak dengan penggantian dari daftar ( contoh ) atau tanpa penggantian ( contoh ), atau bagaimana masalah itu dapat diselesaikan?
Peter O.
@PeterO. Anda perlu menghitung semua partisi yang mungkin melalui metode yang sama seperti pada algoritma saya, tetapi kali ini Anda perlu mengurangi hanya angka yang diizinkan dari jumlah tersebut. Ini terlalu panjang untuk dikomentari, Anda dapat mengajukan pertanyaan terpisah. Saya menduga bahwa seseorang dapat memecahkan empat masalah yang berbeda melalui pendekatan yang sama. Misalkan Anda memiliki daftar bilangan bulat yang berbeda untuk dipilih (ini hanya rentang berkelanjutan dalam pertanyaan ini). Kemudian Anda dapat menghasilkan array acak dengan panjang tertentu yang terdiri dari angka-angka dari daftar ini dengan jumlah yang diberikan jika array harus diurutkan / tidak disortir dan memungkinkan / tidak mengizinkan pengulangan.
John McClane
3

Berikut adalah algoritme dari PermutationPartitionGenerator John McClane, dalam jawaban lain di halaman ini. Ini memiliki dua fase, yaitu fase pengaturan dan fase pengambilan sampel, dan menghasilkan nangka acak dalam [ min, max] dengan jumlah sum, di mana angka-angka tersebut tercantum dalam urutan acak.

Fase pengaturan: Pertama, tabel solusi dibangun menggunakan rumus berikut (di t(y, x)mana yberada di [0, n] dan xdi [0, sum - n * min]):

  • t (0, j) = 1 jika j == 0; 0 sebaliknya
  • t (i, j) = t (i-1, j) + t (i-1, j-1) + ... + t (i-1, j- (maks-mnt))

Di sini, t (y, x) menyimpan probabilitas relatif bahwa jumlah yangka (dalam kisaran yang sesuai) akan sama x. Probabilitas ini relatif terhadap semua t (y, x) dengan yang sama y.

Fase pengambilan sampel: Di sini kami menghasilkan sampel nangka. Setel ske sum - n * min, lalu untuk setiap posisi i, mulai dengan n - 1dan mundur ke 0:

  • Setel vke integer acak dalam [0, t (i + 1, s)).
  • Setel rke min.
  • Kurangi t (i, s) dari v.
  • Sementara vtetap 0 atau lebih besar, kurangi t (i, s-1) dari v, tambahkan 1 ke r, dan kurangi 1 dari s.
  • Angka pada posisi idalam sampel diatur ke r.

EDIT:

Tampaknya dengan perubahan sepele untuk algoritme di atas, dimungkinkan untuk membuat setiap nomor acak menggunakan rentang yang terpisah daripada menggunakan rentang yang sama untuk semuanya:

Setiap angka acak pada posisi i∈ [0, n) memiliki nilai minimum min (i) dan nilai maksimum maks (i).

Biarkan adjsum= sum- Σmin (i).

Fase pengaturan: Pertama, tabel solusi dibangun menggunakan rumus berikut (di t(y, x)mana yberada di [0, n] dan xdi [0, adjsum]):

  • t (0, j) = 1 jika j == 0; 0 sebaliknya
  • t (i, j) = t (i-1, j) + t (i-1, j-1) + ... + t (i-1, j- (maks (i-1) -min (i -1)) )

Fase pengambilan sampel sama persis seperti sebelumnya, kecuali kita mengatur ske adjsum(daripada sum - n * min) dan diatur rke min (i) (bukan min).


EDIT:

Untuk CombinationPartitionGenerator John McClane, fase pengaturan dan pengambilan sampel adalah sebagai berikut.

Fase pengaturan: Pertama, tabel solusi dibangun menggunakan rumus berikut (di t(z, y, x)mana zberada di [0, n], ydi [0, max - min], dan xdi [0, sum - n * min]):

  • t (0, j, k) = 1 jika k == 0; 0 sebaliknya
  • t (i, 0, k) = t (i - 1, 0, k)
  • t (i, j, k) = t (i, j-1, k) + t (i - 1, j, k - j)

Fase pengambilan sampel: Di sini kami menghasilkan sampel nangka. Atur ske sum - n * mindan mrangeke max - min, lalu untuk setiap posisi i, mulai dengan n - 1dan mundur ke 0:

  • Setel vke integer acak dalam [0, t (i + 1, susun, s)).
  • Setel mrangeke min ( mrange, s)
  • Kurangi mrangedari s.
  • Setel rke min + mrange.
  • Kurangi t ( i, mrange, s) dari v.
  • Sementara vsisa-sisa 0 atau lebih besar, tambahkan 1 ke s, kurangi 1 dari rdan 1 dari mrange, maka t kurangi ( i, mrange, s) dari v.
  • Angka pada posisi idalam sampel diatur ke r.
Peter O.
sumber
2

Saya belum menguji ini, jadi itu bukan jawaban, hanya sesuatu untuk dicoba yang terlalu panjang untuk dimasukkan ke dalam komentar. Mulailah dengan sebuah array yang memenuhi dua kriteria pertama dan bermain dengannya sehingga masih memenuhi dua kriteria pertama, tetapi jauh lebih acak.

Jika mean adalah bilangan bulat, maka array awal Anda dapat [4, 4, 4, ... 4] atau mungkin [3, 4, 5, 3, 4, 5, ... 5, 8, 0] atau sesuatu yang sederhana seperti itu. Untuk rata-rata 4,5, coba [4, 5, 4, 5, ... 4, 5].

Selanjutnya pilih sepasang angka, num1dan num2, dalam array. Mungkin nomor pertama harus diambil secara berurutan, seperti halnya kocokan Fisher-Yates, angka kedua harus dipilih secara acak. Mengambil nomor pertama secara berurutan memastikan bahwa setiap nomor dipetik setidaknya satu kali.

Sekarang hitung max-num1dan num2-min. Itu adalah jarak dari dua angka ke batas maxdan min. Atur limitke yang lebih kecil dari dua jarak. Itu adalah perubahan maksimum yang diizinkan yang tidak akan menempatkan salah satu dari angka-angka di luar batas yang diizinkan. Jika limitnol maka lewati pasangan ini.

Pilih bilangan bulat acak dalam rentang [1, limit]: panggil saja change. Saya menghilangkan 0 dari rentang yang dapat dipilih karena tidak memiliki efek. Pengujian mungkin menunjukkan bahwa Anda mendapatkan keacakan yang lebih baik dengan memasukkannya; Saya tidak yakin.

Sekarang atur num1 <- num1 + changedan num2 <- num2 - change. Itu tidak akan mempengaruhi nilai rata-rata dan semua elemen array masih dalam batas yang diperlukan.

Anda harus menjalankan seluruh array setidaknya satu kali. Pengujian harus menunjukkan jika Anda perlu menjalankannya lebih dari satu kali untuk mendapatkan sesuatu yang cukup acak.

ETA: sertakan pseudocode

// Set up the array.
resultAry <- new array size N
for (i <- 0 to N-1)
  // More complex initial setup schemes are possible here.
  resultAry[i] <- mean
rof

// Munge the array entries.
for (ix1 <- 0 to N-1)  // ix1 steps through the array in order.

  // Pick second entry different from first.
  repeat
    ix2 <- random(0, N-1)
  until (ix2 != ix1)

  // Calculate size of allowed change.
  hiLimit <- max - resultAry[ix1]
  loLimit <- resultAry[ix2] - min
  limit <- minimum(hiLimit, loLimit)
  if (limit == 0)
    // No change possible so skip.
    continue loop with next ix1
  fi

  // Change the two entries keeping same mean.
  change <- random(1, limit)  // Or (0, limit) possibly.
  resultAry[ix1] <- resultAry[ix1] + change
  resultAry[ix2] <- resultAry[ix2] - change

rof

// Check array has been sufficiently munged.
if (resultAry not random enough)
  munge the array again
fi
rossum
sumber
Saya telah mengujinya dan sayangnya, algoritma Anda tidak membentuk distribusi yang seragam dari semua solusi, tidak peduli berapa banyak iterasi yang saya lakukan.
Peter O.
Baiklah. Itu pantas untuk dicoba. :(
rossum
1

Seperti yang ditunjukkan OP, kemampuan unrank efisien sangat kuat. Jika kita dapat melakukannya, menghasilkan distribusi partisi yang seragam dapat dilakukan dalam tiga langkah (menyatakan kembali apa yang telah OP sebutkan dalam pertanyaan):

  1. Hitung jumlah total, M , dari partisi dengan panjang N dari jumlah sumsedemikian rupa sehingga bagian-bagiannya berada dalam kisaran [ min, max].
  2. Hasilkan distribusi bilangan bulat yang seragam dari [1, M].
  3. Lepaskan setiap integer dari langkah 2 ke dalam partisi masing-masing.

Di bawah ini, kami hanya fokus pada menghasilkan partisi ke- n karena ada sejumlah besar informasi tentang menghasilkan distribusi integer yang seragam dalam rentang yang diberikan. Berikut ini adalah C++algoritma unranking sederhana yang seharusnya mudah diterjemahkan ke bahasa lain (NB saya belum menemukan cara untuk membuka tutup case komposisi (mis. Ketertiban)).

std::vector<int> unRank(int n, int m, int myMax, int nth) {

    std::vector<int> z(m, 0);
    int count = 0;
    int j = 0;

    for (int i = 0; i < z.size(); ++i) {
        int temp = pCount(n - 1, m - 1, myMax);

        for (int r = n - m, k = myMax - 1;
             (count + temp) < nth && r > 0 && k; r -= m, --k) {

            count += temp;
            n = r;
            myMax = k;
            ++j;
            temp = pCount(n - 1, m - 1, myMax);
        }

        --m;
        --n;
        z[i] = j;
    }

    return z;
}

Fungsi pekerja keras pCountdiberikan oleh:

int pCount(int n, int m, int myMax) {

    if (myMax * m < n) return 0;
    if (myMax * m == n) return 1;

    if (m < 2) return m;
    if (n < m) return 0;
    if (n <= m + 1) return 1;

    int niter = n / m;
    int count = 0;

    for (; niter--; n -= m, --myMax) {
        count += pCount(n - 1, m - 1, myMax);
    }

    return count;
}

Fungsi ini didasarkan pada jawaban yang sangat baik untuk Apakah ada algoritma yang efisien untuk partisi integer dengan jumlah bagian yang terbatas? oleh pengguna @ m69_snarky_and_unwelcoming. Yang diberikan di atas adalah sedikit modifikasi dari algoritma sederhana (yang tanpa memoisasi). Ini dapat dengan mudah dimodifikasi untuk memasukkan memoisasi untuk efisiensi yang lebih besar. Kami akan mengabaikan ini untuk saat ini dan fokus pada bagian unranking.

Penjelasan dari unRank

Kami pertama-tama mencatat ada pemetaan satu-ke-satu dari partisi panjang N dari nomor sumsedemikian sehingga bagian-bagian berada dalam kisaran [ min, max] ke partisi terbatas panjang N dari angka sum - N * (min - 1)dengan bagian-bagian dalam [ 1, max - (min - 1)].

Sebagai contoh kecil, pertimbangkan partisi dari 50panjang 4sehingga min = 10dan max = 15. Ini akan memiliki struktur yang sama dengan partisi dengan 50 - 4 * (10 - 1) = 14panjang terbatas 4dengan bagian maksimum sama dengan 15 - (10 - 1) = 6.

10   10   15   15   --->>    1    1    6    6
10   11   14   15   --->>    1    2    5    6
10   12   13   15   --->>    1    3    4    6
10   12   14   14   --->>    1    3    5    5
10   13   13   14   --->>    1    4    4    5
11   11   13   15   --->>    2    2    4    6
11   11   14   14   --->>    2    2    5    5
11   12   12   15   --->>    2    3    3    6
11   12   13   14   --->>    2    3    4    5
11   13   13   13   --->>    2    4    4    4
12   12   12   14   --->>    3    3    3    5
12   12   13   13   --->>    3    3    4    4

Dengan mengingat hal ini, agar mudah dihitung, kita dapat menambahkan langkah 1a untuk menerjemahkan masalah ke kasing "unit" jika Anda mau.

Sekarang, kami hanya memiliki masalah penghitungan. Seperti @ m69 ditampilkan dengan cemerlang, menghitung partisi dapat dengan mudah dicapai dengan memecah masalah menjadi masalah yang lebih kecil. Fungsi @ m69 memberi Anda 90% dari cara, kita hanya harus mencari tahu apa yang harus dilakukan dengan pembatasan tambahan yang ada batasnya. Di sinilah kita mendapatkan:

int pCount(int n, int m, int myMax) {

    if (myMax * m < n) return 0;
    if (myMax * m == n) return 1;

Kita juga harus ingat bahwa hal itu myMaxakan berkurang ketika kita terus berjalan. Ini masuk akal jika kita melihat partisi ke- 6 di atas:

2   2   4   6

Untuk menghitung jumlah partisi dari sini, kita harus terus menerapkan terjemahan ke kasus "unit". Ini terlihat seperti:

1   1   3   5

Dimana sebagai langkah sebelumnya, kami memiliki maks 6, sekarang kami hanya mempertimbangkan maks 5.

Dengan mengingat hal ini, unranking partisi tidak ada bedanya dengan unranking permutasi atau kombinasi standar. Kita harus dapat menghitung jumlah partisi di bagian yang diberikan. Misalnya, untuk menghitung jumlah partisi yang dimulai dengan di 10atas, semua yang kami lakukan adalah menghapus 10di kolom pertama:

10   10   15   15
10   11   14   15
10   12   13   15
10   12   14   14
10   13   13   14

10   15   15
11   14   15
12   13   15
12   14   14
13   13   14

Menerjemahkan ke unit kasus:

1   6   6
2   5   6
3   4   6
3   5   5
4   4   5

dan telepon pCount:

pCount(13, 3, 6) = 5

Diberikan bilangan bulat acak untuk dibuka, kami terus menghitung jumlah partisi di bagian yang lebih kecil dan lebih kecil (seperti yang kami lakukan di atas) sampai kami telah mengisi vektor indeks kami.

Contohnya

Mengingat min = 3, max = 10, n = 7, dan sum = 42, di sini adalah ideone demo yang menghasilkan 20 partisi acak. Outputnya di bawah ini:

42: 3 3 6 7 7 8 8 
123: 4 4 6 6 6 7 9 
2: 3 3 3 4 9 10 10 
125: 4 4 6 6 7 7 8 
104: 4 4 4 6 6 8 10 
74: 3 4 6 7 7 7 8 
47: 3 4 4 5 6 10 10 
146: 5 5 5 5 6 7 9 
70: 3 4 6 6 6 7 10 
134: 4 5 5 6 6 7 9 
136: 4 5 5 6 7 7 8 
81: 3 5 5 5 8 8 8 
122: 4 4 6 6 6 6 10 
112: 4 4 5 5 6 8 10 
147: 5 5 5 5 6 8 8 
142: 4 6 6 6 6 7 7 
37: 3 3 6 6 6 9 9 
67: 3 4 5 6 8 8 8 
45: 3 4 4 4 8 9 10 
44: 3 4 4 4 7 10 10

Indeks leksikografis ada di sebelah kiri dan partisi yang tidak dirank di sebelah kanan.

Joseph Wood
sumber
1
Ternyata, ini adalah alternatif yang sangat bagus, dan memang menjadi efisien dengan memoisasi.
Peter O.
0

Jika Anda menghasilkan 0≤a≤1 dari nilai acak dalam rentang [l, x-1] secara seragam, dan 1-dari nilai acak dalam rentang [x, h] secara seragam, rata-rata yang diharapkan adalah:

m = ((l+x-1)/2)*a + ((x+h)/2)*(1-a)

Jadi, jika Anda menginginkan m tertentu, Anda bisa bermain dengan a dan x.

Misalnya, jika Anda mengatur x = m: a = (hm) / (h-l + 1).

Untuk memastikan probabilitas lebih dekat ke seragam untuk kombinasi yang berbeda, pilih a atau x secara acak dari serangkaian solusi yang valid untuk persamaan di atas. (x harus dalam kisaran [l, h] dan harus (dekat dengan) bilangan bulat; N * a juga harus (dekat dengan) bilangan bulat.

Lior Kogan
sumber