Belirli bir toplam veya ortalamaya sahip bir aralıkta N rastgele tamsayı oluşturmanın etkili bir yolu var mı?


12

N tamsayıların rastgele bir kombinasyonunu oluşturmak için etkili bir yol var mı?

  • her tamsayı [ min, max] aralığında ,
  • tamsayıların toplamı sum,
  • tamsayılar herhangi bir sırada görünebilir (örneğin, rastgele sıra) ve
  • kombinasyon, diğer gereksinimleri karşılayan tüm kombinasyonlar arasından rastgele olarak muntazam bir şekilde seçilir?

Tamsayıların değerlerine göre (herhangi bir sırada değil) sıralı sırada görünmesi gereken rasgele kombinasyonlar için benzer bir algoritma var mı?

(Bir ortalama ile uygun bir kombinasyon seçmek mean, eğer özel bir durumdur, eğer sum = N * meanbu sorun, sumher biri [ min, max] aralığında olan ve herhangi bir sırada veya sıralı olarak sıralanmış olarak N parçasına üniform bir rastgele bölüm oluşturmaya eşdeğerdir. değerleri olabilir.)

Bu sorunun rasgele sırada görünen kombinasyonlar için şu şekilde çözülebileceğinin farkındayım (EDIT [27 Nisan]: Algoritma değiştirildi.):

  1. Eğer N * max < sumyoksa N * min > sum, hiçbir çözüm yoktur.

  2. Eğer N * max == sum, tüm Nsayıların eşit olduğu tek bir çözüm varsa max. Eğer N * min == sum, tüm Nsayıların eşit olduğu tek bir çözüm varsa min.

  3. Toplamla N rasgele negatif olmayan tamsayı üretmek için Smith ve Tromble'da ("Unit Simplex'ten Örnekleme", 2004) verilen algoritmayı kullanınsum - N * min .

  4. minBu şekilde oluşturulan her numaraya ekleyin .

  5. Herhangi bir sayı daha büyükse max, 3. adıma gidin.

Ancak, bu algoritma maxçok daha azsa yavaştır sum. Örneğin, testlerime göre (yukarıdaki özel durumun uygulanmasıyla mean), algoritma ortalama olarak reddediyor—

  • yaklaşık 1.6 örnek N = 7, min = 3, max = 10, sum = 42, ancak
  • yaklaşık 30.6 örnekleri N = 20, min = 3, max = 10, sum = 120.

Yukarıdaki gereksinimleri karşılarken bu algoritmayı büyük N için verimli olacak şekilde değiştirmenin bir yolu var mı?

DÜZENLE:

Yorumlarda önerilen bir alternatif olarak, geçerli bir rastgele kombinasyon üretmenin etkili bir yolu (son gereklilik dışında hepsini karşılayan):

  1. Hesapla X, geçerli kombinasyonların sayısı mümkün gibi verilen sum, minve max.
  2. İçinde Y, düzgün bir rasgele tamsayı seçin [0, X).
  3. YGeçerli bir kombinasyona dönüştürün ("açma") .

Bununla birlikte, geçerli kombinasyonların (veya permütasyonların) sayısını hesaplamak için bir formül var mı ve bir tamsayıyı geçerli bir kombinasyona dönüştürmenin bir yolu var mı? [EDIT (28 Nisan): Kombinasyonlar yerine permütasyonlar için aynıdır].

DÜZENLE (27 Nisan):

Devroye'nin Düzgün Olmayan Rastgele Değişken Üretimi (1986) okuduktan sonra , bunun rastgele bir bölüm oluşturma sorunu olduğunu doğrulayabilirim. Ayrıca sayfa 661'deki Alıştırma 2 (özellikle Bölüm E) bu soru ile ilgilidir.

EDIT (28 Nisan):

Ortaya çıktığı gibi, verdiğim algoritma , değerlerine göre sıralı sıralamanın aksine, ilgili tamsayıların rasgele sırayla verildiği yerde eşittir . Her iki sorunun da genel ilgisi olduğundan, bu soruyu her iki soruna da kanonik bir cevap aramak için değiştirdim.

Aşağıdaki Ruby kodu, tekdüzelik için potansiyel çözümleri doğrulamak için kullanılabilir ( algorithm(...)aday algoritması nerede ):

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 Nisan): Geçerli uygulamanın Ruby kodunu yeniden ekledi.

Aşağıdaki kod örneği Ruby'de verilmiştir, ancak sorum programlama dilinden bağımsızdır:

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
}

Üçüncü gereksiniminizi açıklığa kavuşturabilir misiniz? Tüm olası kombinasyonlar (yanlış ortalamaya sahip olanlar dahil) veya tüm geçerli kombinasyonlar (yani doğru ortalamaya sahip olanlar ) arasında bir homojenliğe mi ihtiyacınız var ?
user58697

Tüm geçerli kombinasyonlar, yani diğer gereksinimleri karşılayan tüm kombinasyonlar.
Peter O.

Eğer [min, max] cinsinden N tamsayı ile sınırlı bir toplamın bölümlerini saymak ve çözmek için bir yolumuz olsaydı, bu bölümlerden birini rastgele ve unranking olarak seçmek tekdüze bir dağılımı temsil eder ve bu mevcut yönteminizden daha verimli olur muydu? Toplam ve N ne kadar büyük olabilir?
גלעד ברקן

"Bir meblağın belirsiz bölümleri" ile ne demek istediğinizi bilmiyorum ve bunu yapmanın bu sorunun anlamı içinde tekdüze bir dağılımla sonuçlandığına dair bir kanıtın farkında değilim. Bu soru için, her iki sumve N(mâkul) etkin bir şekilde sınırsızdır. Ben kanonik bir cevap arıyorum çünkü altta yatan sorun bu ve bu da dahil olmak üzere Stack Overflow üzerinde sorulan birçok soru ortaya çıkıyor . @ גלעדברקן
Peter O.

Eğer her bir kombinasyona hepsinin sıralı bir düzenlemesinde bir "sıralama" (veya indeks) verirsek, "unranking", sırası (ve elbette N, min ve maks) verildiğinde kombinasyonun üretilmesi anlamına gelir. Neden tüm olası kombinasyonlardan birinin böyle bir seçimi tekdüze bir dağılıma uymuyor?
גלעד ברקן

Yanıtlar:


3

İşte benim Java çözümüm. Tamamen işlevseldir ve iki jeneratör içerir: PermutationPartitionGeneratorayrılmamış bölümler ve CombinationPartitionGeneratorsıralı bölümler için. Jeneratörünüz de SmithTromblePartitionGeneratorkarşılaştırma için sınıfta uygulandı . Sınıf, SequentialEnumeratorolası tüm bölümleri (parametreye bağlı olarak sıralanmamış veya sıralanmış) sırayla sıralar. Tüm bu jeneratörler için kapsamlı testler (test senaryolarınız dahil) ekledim. Uygulama çoğunlukla kendi kendini açıklayabilir. Herhangi bir sorunuz varsa, onlara birkaç gün içinde cevap vereceğim.

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

Bunu Ideone'da deneyebilirsiniz .


Cevabınız için teşekkürler; iyi çalışıyor. Permütasyon üreticisini burada başka bir cevapta tanımladım; yardımınızla başka bir soruyu yanıtladı ; ve yakında algoritmanızı rastgele üretim yöntemleri hakkındaki makalem için Python örnek koduna ekleyeceğim.
Peter O.30

Sadece net olmak için. Bu algoritma örnekleme için tüm olası bölümleri / kompozisyonları oluşturmaya dayanıyor mu?
Joseph Wood

@JosephWood Hayır, hepsini saymaya dayanıyor . Bu, jeneratörün başlatılmasında sadece bir kez yapılır ve oldukça etkilidir çünkü dinamik programlama yaklaşımını kullanır.
John McClane

Nasıl dinamik programlama rastgele seçilmiş N tamsayılar içine 'toplamı' tek tip rastgele bölüm seçme ilgili sorunu çözebilir değiştirme ile bir liste (dan örneğin ) ya da yedek olmadan ( örneğin ) ya da bu sorun başka türlü nasıl çözülebilir?
Peter O.

@PeterO. Tüm olası bölümleri algoritmamdakiyle aynı yöntemle saymanız gerekir, ancak bu sefer toplamdan yalnızca izin verilen sayıları çıkarmanız gerekir . Bu yorum yapmak için çok uzun, ayrı bir soru sorabilirsiniz. Aynı yaklaşımla dört farklı sorunun çözülebileceğinden şüpheleniyorum. Seçebileceğiniz farklı tamsayıların bir listeniz olduğunu varsayalım (bu, bu soruda yalnızca sürekli bir aralıktır). Ardından, dizilerin sıralanması / ayrıştırılması ve bir tekrarlamaya izin verilmesi / izin verilmemesi durumunda, bu listeden sayılardan oluşan belirli bir uzunlukta rastgele diziler oluşturabilirsiniz.
John McClane

1

Bu sayfadaki başka bir cevapta John McClane'nin PermutationPartitionGenerator algoritması. Bir kurulum aşaması ve bir örnekleme aşaması olmak üzere iki aşamadan oluşur ve sayıların rasgele sırada listelendiği toplamla birlikte n[ min, max] içinde rasgele sayılar üretir sum.

Kurulum aşaması: İlk olarak, aşağıdaki formüller kullanılarak bir çözüm tablosu oluşturulur ( t(y, x)burada y[0, n] ve x[0, sum - n * min] 'da bulunur):

  • j == 0 ise t (0, j) = 1; 0 aksi takdirde
  • t (i, j) = t (i-1, j) + t (i-1, j-1) + ... + t (i-1, j- (maks-min))

Burada, t (y, x) ysayıların toplamının (uygun aralıkta) eşit olma olasılığını saklar x. Bu olasılık tüm t (y, x) ile aynıdır y.

Örnekleme aşaması: Burada bir nsayı örneği üretiyoruz . Set siçin sum - n * minher bir pozisyon için, daha sonra ibaşlayarak, n - 1ve 0 geriye doğru çalışma:

  • v[0, t (i + 1, s)) içinde rastgele bir tam sayıya ayarlayın .
  • Set riçin min.
  • 'Dan t (i, s) çıkarın v.
  • Birlikte vkalıntıları 0 veya daha büyük, çıkarma t (i, s-1) den v, 1 eklenir rve 1 çıkarma s.
  • iNumunedeki konumdaki sayı olarak ayarlanır r.

DÜZENLE:

Yukarıdaki algoritmada yapılan önemsiz değişikliklerle, her rastgele sayının hepsi için aynı aralığı kullanmak yerine ayrı bir aralık kullanması mümkündür:

i∈ [0, n) konumlarındaki her rastgele sayının minimum değeri min (i) ve maksimum değeri maks (i) vardır.

Let adjsum= sum- Σmin (i).

Kurulum aşaması: İlk olarak, aşağıdaki formüller kullanılarak bir çözüm tablosu oluşturulur ( t(y, x)burada y[0, n] ve x[0, adjsum] 'da bulunur):

  • j == 0 ise t (0, j) = 1; 0 aksi takdirde
  • t (i, j) = t (i-1, j) + t (i-1, j-1) + ... + t (i-1, j- (maks. (i-1) -min (i -1)) )

Biz belirtilen durumlar hariç örnekleme fazı, daha sonra önce olduğu gibi tam olarak aynıdır siçin adjsum(yerine sum - n * min) ve ayar rdakika (i) (yerine min).


DÜZENLE:

John McClane'nin CombinationPartitionGenerator için kurulum ve örnekleme aşamaları aşağıdaki gibidir.

Kurulum aşaması: İlk olarak, aşağıdaki formüller kullanılarak bir çözüm tablosu oluşturulur ( t(z, y, x)burada z[0, n], y[0, max - min] ve x[0, sum - n * min] 'dadır):

  • k == 0 ise t (0, j, k) = 1; 0 aksi takdirde
  • t (i, 0, k) = t (i - 1, 0, k)
  • t (i, j, k) = t (i, j-1, k) + t (i - 1, j, k - j)

Örnekleme aşaması: Burada bir nsayı örneği üretiyoruz . Set siçin sum - n * minve mrangeüzere max - minher bir pozisyon için, daha sonra i, ile başlayarak n - 1ve 0 geriye doğru çalışma:

  • v[0, t (i + 1, mrange, s)) içinde rastgele bir tam sayıya ayarlayın .
  • mrangeMin. ( mrange, s) Olarak ayarla
  • Çıkar mrangedan s.
  • Set riçin min + mrange.
  • Çıkar t ( i, mrange, s) den v.
  • Birlikte vkalıntıları 0 veya daha büyük, 1 eklenir s, 1 çıkarma rgelen ve 1 mrangeçıkarma t (o, i, mrange, sdan) v.
  • iNumunedeki konumdaki sayı olarak ayarlanır r.

0

Bunu test etmedim, bu yüzden gerçekten bir cevap değil, sadece bir yoruma uymak için çok uzun olan denemek için bir şey. İlk iki kriteri karşılayan bir dizi ile başlayın ve onunla oynayın, böylece yine de ilk ikisini karşılar, ancak çok daha rastgele olur.

Ortalama bir tamsayı ise, ilk diziniz [4, 4, 4, ... 4] veya belki [3, 4, 5, 3, 4, 5, ... 5, 8, 0] veya böyle basit bir şey. Ortalama 4,5 için [4, 5, 4, 5, ... 4, 5] 'i deneyin.

Sonraki numaralarının bir çift çekme, num1ve num2dizi içinde,. Muhtemelen ilk sayı sırayla alınmalıdır, Fisher-Yates shuffle'da olduğu gibi, ikinci sayı rasgele seçilmelidir. İlk sayıyı sırayla almak, her sayının en az bir kez seçilmesini sağlar.

Şimdi hesaplayın max-num1ve num2-min. Bunlar için iki sayı arasındaki mesafelerdir maxve minsınırları. limitİki mesafeden daha küçük olarak ayarlayın . Bir veya daha fazla sayıyı izin verilen sınırların dışına koymayacak olan izin verilen maksimum değişikliktir. Eğer limitsıfır olduğu zaman bu çifti atlayın.

[1, limit] aralığında rastgele bir tamsayı seçin : diyelim change. Etkisi olmadığından 0'ı seçilebilir aralıktan çıkarıyorum. Testler, dahil ederek daha iyi bir rastgelelik elde ettiğinizi gösterebilir; Emin değilim.

Şimdi ayarlayın num1 <- num1 + changeve num2 <- num2 - change. Bu, ortalama değeri etkilemez ve dizinin tüm öğeleri hala gerekli sınırlar dahilindedir.

Tüm diziyi en az bir kez çalıştırmanız gerekir. Test, yeterince rastgele bir şey elde etmek için birden fazla kez geçmeniz gerekip gerekmediğini göstermelidir.

Tahmini varış süresi: sözde kodu dahil et

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

Test ettim ve ne yazık ki, algoritma, kaç yineleme yaptığımdan bağımsız olarak, tüm çözümlerin düzgün bir dağılımını oluşturmuyor.
Peter O.

Oh iyi. Yine de denemeye değerdi. :(
rossum

0

OP'nin işaret ettiği gibi, verimli bir şekilde krank açma yeteneği çok güçlüdür. Bunu yapabiliyorsak, bölümlerin düzgün bir şekilde dağıtılması üç adımda yapılabilir (OP'nin soruda ortaya koyduğu şeyin yeniden düzenlenmesi):

  1. N sayısı uzunluktaki bölümlerin toplam sayısını ( M) , parçalar aralıkta olacak şekilde hesaplayın [ , ].summinmax
  2. Den tamsayıların düzgün bir dağılımını üretir [1, M].
  3. Adım 2'deki her tamsayıyı ilgili bölüme ayırın.

Aşağıda, sadece üreten odaklanmak n inci belirli bir aralıkta tamsayı düzgün bir şekilde dağılmasını üreten bir bilgi bol miktarda olduğundan bölümü. İşte C++diğer dillere çevirmesi kolay olan basit bir unranking algoritması.

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

İşgücü pCountişlevi şu şekilde verilir:

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

Bu fonksiyon, sınırlı sayıda parça ile tamsayı bölümleme için etkili bir algoritma var mı? kullanıcı tarafından @ m69_snarky_and_unwelcoming. Yukarıda verilen, basit algoritmanın küçük bir modifikasyonudur (notsuz olan). Bu, daha fazla verimlilik için notlamayı içerecek şekilde kolayca değiştirilebilir. Bunu şimdilik kapalı bırakacağız ve kararsız kısma odaklanacağız.

Açıklaması unRank

İlk not uzunluğu bölümleri bir bire-bir eşleme vardır N sayıda sumparça aralığında olacak şekilde [ min, max] uzunluğu sınırlı bölümleri N sayıda sum - m * (min - 1)parçaları ile birlikte [ 1, max - (min - 1)].

Küçük bir örnek olarak, bir bölümleri dikkate 50uzunluğunun 4öyle ki min = 10ve max = 15. Bu , maksimum kısmı eşit 50 - 4 * (10 - 1) = 14olan uzunluktaki kısıtlanmış bölümlerle aynı yapıya sahip olacaktır .415 - (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

Bunu akılda tutarak, kolayca saymak için, sorunu "birim" durumuna çevirmek için bir adım 1a ekleyebiliriz.

Şimdi sayma problemimiz var. @ M69 mükemmel bir şekilde görüntülendiğinden, sayım bölümleri sorunu daha küçük sorunlara bölerek kolayca elde edilebilir. @ M69 işlevi bize% 90 oranında yol gösterir, sadece bir sınır olduğunu ek kısıtlama ile ne yapacağını bulmak zorundayız. İşte burada:

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

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

Ayrıca myMaxilerledikçe azalacağını da aklımızda tutmalıyız . Baktığımız zaman anlam taşır 6 inci bölüm yukarıda:

2   2   4   6

Buradan itibaren bölüm sayısını saymak için çeviriyi "birim" vakasına uygulamaya devam etmeliyiz. Bu şuna benzer:

1   1   3   5

Bir önceki adım olarak, bir maksimum değere sahiptik 6, şimdi sadece bir maks 5.

Bunu göz önünde bulundurarak, bölümün çözülmesinin standart bir permütasyon veya kombinasyonun çözülmesinden farklı değildir. Belirli bir bölümdeki bölüm sayısını sayabilmeliyiz. Örneğin, 10yukarıdan başlayan bölüm sayısını saymak için tek yaptığımız 10ilk sütundaki bölümü kaldırmaktır :

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

Ünite kasasına tercüme et:

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

ve arayın pCount:

pCount(13, 3, 6) = 5

Çözülecek rastgele bir tamsayı verildiğinde, dizin vektörümüzü doldurana kadar daha küçük ve daha küçük bölümlerdeki (yukarıda yaptığımız gibi) bölüm sayısını hesaplamaya devam ediyoruz.

Örnekler

Verilen min = 3, max = 10, n = 7, ve sum = 42, burada bir olduğunu ideone 20 rastgele bölümleri oluşturur demo.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.