特定の合計または平均を持つ範囲でN個のランダムな整数を生成する効率的な方法はありますか?


14

N個の整数のランダムな組み合わせを生成する効率的な方法はありますか?

  • 各整数は区間[ minmax]にあり、
  • 整数の合計はsum
  • 整数は任意の順序(たとえば、ランダムな順序)で表示できます。
  • 組み合わせは、他の要件を満たすすべての組み合わせの中からランダムに均一に選択されていますか?

ランダムな組み合わせに対して同様のアルゴリズムがあり、整数は値によってソートされた順序で(順序ではなく)表示される必要がありますか?

(平均の適切な組み合わせを選択することmeanは特殊なケースですsum = N * mean。の場合。この問題はsum、間隔[ minmax]にあり、任意の順序または並べ替えられた順序で表示されるN個の部分に均一なランダムパーティションを生成するのと同じです。場合によっては値)。

この問題は、ランダムな順序で現れる組み合わせに対して次の方法で解決できることを認識しています(編集[4月27日]:アルゴリズムが変更されました)。

  1. N * max < sumまたはの場合N * min > sum、解決策はありません。

  2. の場合N * max == sum、すべてのN数値がに等しいソリューションが1つだけ存在しmaxます。の場合N * min == sum、すべてのN数値がに等しいソリューションが1つだけ存在しminます。

  3. SmithとTrombleで指定されたアルゴリズム(「単位シンプレックスからのサンプリング」、2004)を使用して、合計でN個のランダムな非負の整数を生成しsum - N * minます。

  4. minこの方法で生成された各数値に追加します。

  5. いずれかの数値がより大きい場合はmax、手順3に進みます。

ただし、maxがより小さい場合、このアルゴリズムは遅くなりsumます。たとえば、私のテストによれば(上記の特殊なケースの実装を伴うmean)、アルゴリズムは平均して拒否します—

  • の場合N = 7, min = 3, max = 10, sum = 42、約1.6サンプルですが、
  • 約30.6サンプルN = 20, min = 3, max = 10, sum = 120

上記の要件を満たしながら、このアルゴリズムを変更して大きなNに対して効率的にする方法はありますか?

編集:

コメントで提案されている代替案として、有効なランダムな組み合わせを生成する効率的な方法(これは最後の要件を除くすべてを満たします)は次のとおりです。

  1. 計算しX、与えられた可能性の有効な組み合わせの数summinmax
  2. Y統一ランダム整数を選択し[0, X)ます。
  3. Y有効な組み合わせに変換( "ランク解除")します。

しかし、有効な組み合わせ(または順列)の数を計算する式はありますか?整数を有効な組み合わせに変換する方法はありますか?[編集(4月28日):組み合わせではなく順列でも同じ]。

編集(4月27日):

DevroyeのNon-Uniform Random Variate Generation(1986)を読んだ後、これがランダムパーティションの生成の問題であることを確認できます。また、661ページの演習2(特にパートE)はこの質問に関連しています。

編集(4月28日):

結局のところ、私が指定したアルゴリズムは、関連する整数がランダムな順序で与えられる均一なものであり、値によるソート順とは対照的です。どちらの問題も一般的な関心事なので、この問題を修正して、両方の問題の標準的な答えを探しました。

次のRubyコードを使用して、均一性algorithm(...)の候補となるソリューションを検証できます(候補アルゴリズムはです)。

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

編集(4月29日):現在の実装のRubyコードを再度追加。

次のコード例はRubyで提供されていますが、私の質問はプログラミング言語とは無関係です。

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
}

3番目の要件を明確にしていただけますか?すべての可能な組み合わせ(平均が間違っているものを含む)の間、またはすべての有効な組み合わせ(つまり、平均が正しいもの)の間で均一性が必要ですか?
user58697

すべての有効な組み合わせ、つまり、他の要件を満たすすべての組み合わせ。
ピーターO.

[min、max]でN整数に制限された合計のパーティションをカウントしてランク付けを解除する方法があった場合、それらのパーティションの1つをランダムに選択してランク付けを解除すると、均一な分布が表現され、現在の方法よりも効率的ですか?合計とNはどのくらい大きくできますか?
גלעדברקן

「合計のパーティションのランク付けを外す」という意味がわかりません。そうすることで、この質問の意味の範囲内で分布が均一になるという証明は知りません。この質問のために、両方sumとはN(理由以内)を効果的に無制限です。根本的な問題を含む、スタックオーバーフローに尋ねた多くの質問にポップアップするので、私は標準的な答えを求めている。この1、この1。@גלעדברקן
ピーターO.

すべての可能な組み合わせに「ランク」(またはインデックス)を順序付けられた順序で配置すると、「ランク外」とは、そのランク(もちろん、N、最小、最大)を指定して組み合わせを生成することを意味します。考えられるすべての組み合わせから1つを選択しても、一様分布に適合しないのはなぜですか?
גלעדברקן

回答:


5

これがJavaでの私の解決策です。これは完全に機能し、2つのジェネレーター(PermutationPartitionGeneratorソートされていないパーティション用とソートされたパーティション用)が含まれていCombinationPartitionGeneratorます。ジェネレータは、SmithTromblePartitionGenerator比較のためにクラスにも実装されています。このクラスSequentialEnumeratorは、可能なすべてのパーティション(パラメーターに応じて、ソートされていないかソートされている)を順番に列挙します。これらのジェネレーターのすべてに、テスト(テストケースを含む)を追加しました。ほとんどの場合、実装は自明です。ご不明な点がございましたら、数日以内にお答えいたします。

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

イデオネで試してみることができます。


ご回答有難うございます; それはうまくいきます。順列ジェネレータについては、こちらの別の回答で説明しました。あなたの助けを借りて別の質問に答えました。そして、ランダム生成メソッドに関する私の記事のPythonサンプルコードにアルゴリズムをすぐに含めます。
ピーターO.

ただ明確にします。このアルゴリズムは、サンプリングするためにすべての可能なパーティション/構成の生成に依存していますか?
ジョセフウッド

@ジョセフウッドいいえ、それはそれらすべてを数えることに依存しています。これは、ジェネレーターの初期化時に一度だけ実行され、動的プログラミング手法を利用するため、かなり効果的です。
John McClane

どのように動的なプログラミングは、ランダムに選ばれたN個の整数に「合計」の一様乱数パーティション選択の関連問題解決することができる交換にリスト(からの例)または交換せずに)、またはその問題は、そうでない場合はどのように解決することができますか?
Peter O.

@PeterO。私のアルゴリズムと同じ方法ですべての可能なパーティションをカウントする必要がありますが、今回は合計から許容される数のみを減算する必要があります。コメントするには長すぎます。別の質問をすることができます。同じアプローチで4つの異なる問題を解決できると思います。異なる整数のリストから選択するとします(これはこの質問の単なる連続範囲です)。次に、配列を並べ替える/並べ替えず、繰り返しを許可/禁止する場合は、このリストの数値と指定された合計からなる指定された長さのランダム配列を生成できます。
John McClane

3

これは、このページの別の回答にある、John McClaneのPermutationPartitionGeneratorのアルゴリズムです。セットアップフェーズとサンプリングフェーズという2つのフェーズがあり、n[ minmax]に合計を使用して乱数を生成しsumます。ここで、数値はランダムな順序でリストされています。

セットアップ段階:まず、溶液のテーブルは、以下の式を用いて構築されている(t(y, x)ここyでは[0 n]とxにある[0 sum - n * min]):

  • t(0、j)= 1(j == 0の場合)。それ以外の場合は0
  • t(i、j)= t(i-1、j)+ t(i-1、j-1)+ ... + t(i-1、j-(max-min))

ここで、t(y、x)は、y(適切な範囲の)数値の合計がになる相対確率を格納しxます。この確率は、同じを持つすべてのt(y、x)に関連していyます。

サンプリングフェーズ:ここでは、n数値のサンプルを生成します。に設定ssum - n * min、次に各位置についてin - 10 から開始して逆方向に作業します。

  • v[0、t(i + 1、s))でランダムな整数に設定します。
  • に設定rminます。
  • からt(i、s)を引きvます。
  • v0以上になっている間、t(i、s-1)をから減算しv、に1をr加算し、から1を減算しsます。
  • iサンプルの位置の番号はに設定されrます。

編集:

上記のアルゴリズムにささいな変更を加えると、すべての乱数に同じ範囲を使用するのではなく、各乱数に別々の範囲を使用することが可能になるようです:

位置i∈[0、n)の各乱数には、最小値min(i)と最大値max(i)があります。

Let adjsum= sum-Σmin(i)とします。

セットアップ段階:まず、溶液のテーブルは、以下の式を用いて構築されている(t(y, x)ここyでは[0 n]とxにある[0 adjsum]):

  • t(0、j)= 1(j == 0の場合)。それ以外の場合は0
  • t(i、j)= t(i-1、j)+ t(i-1、j-1)+ ... + t(i-1、j- (max(i-1)-min(i -1))

サンプリングフェーズは以前とまったく同じですが、(ではなく)に設定sし、adjsum(ではなくsum - n * minrmin(i)に設定しminます。


編集:

John McClaneのCombinationPartitionGeneratorの場合、セットアップとサンプリングのフェーズは次のとおりです。

セットアップ段階:まず、溶液のテーブルは、以下の式を用いて構築されている(t(z, y, x)ここで、z[0、であるn]、y[0、であるmax - min]とxにある[0 sum - n * min]):

  • k == 0の場合、t(0、j、k)= 1。それ以外の場合は0
  • t(i、0、k)= t(i-1、0、k)
  • t(i、j、k)= t(i、j-1、k)+ t(i-1、j、k-j)

サンプリングフェーズ:ここでは、n数値のサンプルを生成します。sto sum - n * minmrangeto max - minに設定してから、各位置についてin - 10 から開始して逆方向に作業します。

  • v[0、t(i + 1、mrange、s))でランダムな整数に設定します。
  • mrangemin(mranges)に設定
  • mrangeから引くs
  • に設定rmin + mrangeます。
  • 減算トン(imranges)からv
  • ながらv遺体0以上は、に1を加えs、1から減算rから1をmrange減算T(その後、imrangesから)v
  • iサンプルの位置の番号はに設定されrます。

2

私はこれをテストしていないので、それは実際には答えではなく、コメントに収まるには長すぎる試してみるものです。最初の2つの基準を満たす配列から始めて、最初の2つは満たすが、かなりランダムになるように操作します。

平均が整数の場合、初期配列は[4、4、4、... 4]または[3、4、5、3、4、5、... 5、8、0]またはそのような単純なもの。平均4.5の場合は、[4、5、4、5、... 4、5]を試してください。

次の数字の組を選択し、num1そしてnum2、アレイです。おそらく最初の番号を順番に取る必要があります。Fisher-Yatesシャッフルと同様に、2番目の番号はランダムに選択する必要があります。最初の番号を順番に取得することで、すべての番号が少なくとも1回選択されることが保証されます。

計算max-num1してくださいnum2-min。これらは、2つの数値からmaxおよびmin境界までの距離です。limit2つの距離の小さい方に設定します。これは、許可された最大の変更であり、いずれかの数を許可された制限の範囲外にしないことになります。場合はlimitゼロで、このペアをスキップします。

[1、limit]の範囲でランダムな整数を選択しchangeます。これを呼び出します。効果がないので、選択可能な範囲から0を省略します。テストでは、それを含めることにより、ランダム性が向上することが示されます。よく分かりません。

設定num1 <- num1 + changeしてくださいnum2 <- num2 - change。これは平均値には影響せず、配列のすべての要素は必要な境界内にあります。

少なくとも1回はアレイ全体を実行する必要があります。テストでは、十分にランダムなものを取得するために複数回実行する必要があるかどうかを示す必要があります。

ETA:疑似コードを含める

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

私はそれをテストしましたが、残念なことに、あなたのアルゴリズムは、何回繰り返しても、すべての解の均一な分布を形成しません。
Peter O.

しかたがない。とにかく試す価値があった。:(
rossum

1

OPが指摘するように、ランクを効率的に解除する機能は非常に強力です。それが可能な場合は、パーティションの均一な分散を生成することを3つのステップで行うことができます(OPが質問で示したものを再現します)。

  1. パーツが[ 、]の範囲になるように、長さNのパーティションの総数Mを計算します。summinmax
  2. から整数の一様分布を生成し[1, M]ます。
  3. 手順2の各整数のランクを解除して、それぞれのパーティションに入れます。

以下では、n 番目のパーティションの生成にのみ焦点を当てています。これは、所定の範囲で整数の均一な分布を生成するための情報が大量にあるためです。C++これは、他の言語に簡単に翻訳できるシンプルなアンランキングアルゴリズムです(NBでコンポジションケースのランクを解除する方法はまだわかりません(つまり、順序が重要))。

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

主力pCount関数は次のように与えられます。

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

この関数は、制限された数のパーツを使用した整数分割のための効率的なアルゴリズムはありますか?ユーザー@ m69_snarky_and_unwelcoming。上記のものは、単純なアルゴリズム(メモ化されていないもの)を少し変更したものです。これを簡単に変更してメモ化を組み込んで効率を高めることができます。ここではこれをオフにして、ランク付けされていない部分に焦点を当てます。

の説明 unRank

我々最初の音符の長さのパーティションから1対1のマッピングが存在するN数のsum部品が範囲内にあるように、[ minmax]の長さの制限されたパーティションへのN数のsum - N * (min - 1)内の部品と[ 1max - (min - 1)]。

小さな例として、パーティションの検討50長さの4ようにmin = 10及びmax = 15。これは、最大部分がに等しい50 - 4 * (10 - 1) = 14長さの制限付きパーティションと同じ構造に4なり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

これを念頭に置いて、簡単に数えるために、必要に応じて問題を「ユニット」ケースに変換するステップ1aを追加できます。

さて、私たちは単にカウントの問題を抱えています。@ m69が見事に表示されるため、問題を小さな問題に分割することで、パーティションのカウントを簡単に行うことができます。@ m69が提供する関数は、90%の方法を提供します。キャップがあるという追加の制限をどうするかを理解する必要があります。これは私たちが得る場所です:

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

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

また、myMax進むにつれて減少することも覚えておく必要があります。上記の6 番目のパーティションを見ると、これは理にかなっています。

2   2   4   6

これからパーティションの数を数えるには、「ユニット」ケースに翻訳を適用し続ける必要があります。これは次のようになります。

1   1   3   5

前のステップと同じように、最大​​値はでしたが6、ここでは最大値のみを考慮し5ます。

これを念頭に置いて、パーティションのランク付けを解除することは、標準の順列または組み合わせのランク付けを解除することと同じです。特定のセクションのパーティション数をカウントできる必要があります。たとえば、10上記で始まるパーティションの数を数えるには10、最初の列のを削除するだけです。

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

ユニットケースに変換します。

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

そして呼び出すpCount

pCount(13, 3, 6) = 5

ランク付けするランダムな整数を指定すると、インデックスベクトルが満たされるまで、(上記で行ったように)より小さいセクションのパーティション数を計算し続けます。

、、、およびが与えられたmin = 3場合max = 10、20個のランダムパーティションを生成するideoneデモを次に示します。出力は以下のとおりです。n = 7sum = 42

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

辞書編集インデックスは左側にあり、ランク付けされていないパーティションは右側にあります。


1
結局のところ、これは非常に優れた代替手段であり、実際にメモ化によって効率的になります。
Peter O.

0

範囲[l、x-1]のランダム値の0≤a≤1を一様に生成し、範囲[x、h]のランダム値の1-aを一様に生成すると、平均は次のようになります。

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

したがって、特定のmが必要な場合は、aおよびxで遊ぶことができます。

たとえば、x = mを設定した場合:a =(hm)/(h-l + 1)。

多くの可能な組み合わせを取得するには、上記の方程式の有効な解のセットからランダムにaまたはxを選択します。(xは[l、h]の範囲内でなければならず、整数(に近い)でなければなりません。N* aも整数(に近い)でなければなりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.