มีวิธีที่มีประสิทธิภาพในการสร้างจำนวนเต็มสุ่ม N ในช่วงที่มีผลรวมหรือค่าเฉลี่ยที่กำหนด?


11

มีวิธีที่มีประสิทธิภาพในการสร้างการรวมกันแบบสุ่มของจำนวนเต็ม N เช่นนั้น -

  • จำนวนเต็มแต่ละค่าอยู่ในช่วง [ min, max],
  • จำนวนเต็มมียอดรวมของsum,
  • จำนวนเต็มสามารถปรากฏในลำดับใดก็ได้ (เช่นลำดับแบบสุ่ม) และ
  • ชุดค่าผสมจะถูกสุ่มเลือกจากชุดค่าผสมทั้งหมดที่ตรงตามข้อกำหนดอื่น ๆ

มีอัลกอริทึมที่คล้ายกันสำหรับชุดค่าผสมแบบสุ่มซึ่งจำนวนเต็มจะต้องปรากฏในลำดับที่เรียงตามค่าของพวกเขา (แทนที่จะเรียงตามลำดับใด ๆ ) หรือไม่?

(การเลือกชุดค่าผสมที่เหมาะสมกับค่าเฉลี่ยของmeanเป็นกรณีพิเศษถ้าsum = N * meanปัญหานี้เทียบเท่ากับการสร้างพาร์ติชันแบบสุ่มที่สม่ำเสมอของsumเป็นส่วน N ซึ่งแต่ละส่วนในช่วง [ min, max] และปรากฏในลำดับใด ๆ หรือเรียงตามลำดับของพวกเขา ค่าตาม แต่กรณี)

ฉันทราบว่าปัญหานี้สามารถแก้ไขได้ด้วยวิธีต่อไปนี้สำหรับชุดค่าผสมที่ปรากฏตามลำดับแบบสุ่ม (แก้ไข [27 เมษายน]: การแก้ไขอัลกอริทึม):

  1. หากN * max < sumหรือN * min > sumไม่มีวิธีแก้ปัญหา

  2. ถ้าN * max == sumมีเพียงหนึ่งในวิธีการแก้ปัญหาที่ทุกตัวเลขจะเท่ากับN maxถ้าN * min == sumมีเพียงหนึ่งในวิธีการแก้ปัญหาที่ทุกตัวเลขจะเท่ากับNmin

  3. ใช้อัลกอริทึมที่กำหนดในสมิ ธ และ Tromble ( "การเก็บตัวอย่างจากหน่วย Simplex", 2004) เพื่อสร้าง N sum - N * minจำนวนเต็มสุ่มไม่ใช่เชิงลบที่มีผลรวม

  4. เพิ่มminไปยังแต่ละหมายเลขที่สร้างด้วยวิธีนี้

  5. หากตัวเลขใด ๆ มากกว่าmaxไปที่ขั้นตอนที่ 3

อย่างไรก็ตามอัลกอริทึมนี้ช้าถ้าmaxน้อยกว่าsumมาก ตัวอย่างเช่นตามการทดสอบของฉัน (ด้วยการใช้งานกรณีพิเศษด้านบนที่เกี่ยวข้องmean) อัลกอริทึมปฏิเสธโดยเฉลี่ย -

  • ประมาณ 1.6 ตัวอย่างถ้าN = 7, min = 3, max = 10, sum = 42แต่
  • เกี่ยวกับ 30.6 N = 20, min = 3, max = 10, sum = 120ตัวอย่างถ้า

มีวิธีแก้ไขอัลกอริทึมนี้ให้มีประสิทธิภาพสำหรับ N ขนาดใหญ่ในขณะที่ยังคงปฏิบัติตามข้อกำหนดข้างต้นหรือไม่

แก้ไข:

เป็นทางเลือกที่แนะนำในการแสดงความคิดเห็นวิธีที่มีประสิทธิภาพในการสร้างชุดค่าผสมที่ถูกต้อง (ซึ่งตรงตามทั้งหมด แต่ข้อกำหนดสุดท้าย) คือ:

  1. คำนวณXจำนวนรวมกันที่ถูกต้องเป็นไปได้ที่กำหนดsum, และminmax
  2. เลือกYจำนวนเต็มแบบสุ่มใน[0, X)ชุด
  3. แปลง ("unrank") Yเป็นชุดค่าผสมที่ถูกต้อง

อย่างไรก็ตามมีสูตรสำหรับการคำนวณจำนวนชุดค่าผสมที่ถูกต้อง (หรือวิธีเรียงสับเปลี่ยน) และมีวิธีแปลงจำนวนเต็มเป็นชุดค่าผสมที่ถูกต้องหรือไม่ [แก้ไข (28 เม.ย. ): เหมือนกันสำหรับการเรียงสับเปลี่ยนมากกว่าชุดค่าผสม]

แก้ไข (27 เมษายน):

หลังจากอ่านการสร้างชุดความแปรปรวนแบบไม่สม่ำเสมอของ Devroye (1986) ฉันสามารถยืนยันได้ว่านี่เป็นปัญหาของการสร้างพาร์ติชันแบบสุ่ม นอกจากนี้แบบฝึกหัด 2 (โดยเฉพาะส่วน E) ในหน้า 661 นั้นเกี่ยวข้องกับคำถามนี้

แก้ไข (28 เมษายน):

ที่จะเปิดออกอัลกอริทึมฉันให้เป็นชุดที่จำนวนเต็มที่เกี่ยวข้องจะได้รับในการสุ่มเมื่อเทียบกับการเรียงลำดับโดยค่าของพวกเขา เนื่องจากปัญหาทั้งสองเป็นที่สนใจทั่วไปฉันได้แก้ไขคำถามนี้เพื่อค้นหาคำตอบที่ยอมรับได้สำหรับปัญหาทั้งสอง

รหัสทับทิมต่อไปนี้สามารถนำมาใช้ในการตรวจสอบการแก้ปัญหาที่มีศักยภาพสำหรับความสม่ำเสมอ (ที่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

แก้ไข (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
}

คุณช่วยชี้แจงข้อกำหนดที่สามของคุณได้ไหม? คุณต้องการความเท่าเทียมกันในชุดค่าผสมที่เป็นไปได้ทั้งหมด (รวมถึงชุดค่าผสมที่ผิด) หรือชุดค่าผสมที่ใช้ได้ทั้งหมด (เช่นชุดค่าผสมที่ถูกต้อง) หรือไม่
user58697

ชุดค่าผสมที่ถูกต้องทั้งหมดคือชุดค่าผสมทั้งหมดที่ตรงตามข้อกำหนดอื่น ๆ
Peter O.

หากเรามีวิธีนับและยกเลิกพาร์ติชันของผลรวมที่ จำกัด จำนวนเต็ม N ตัวใน [min, max] จะเลือกหนึ่งในพาร์ติชั่นเหล่านั้นแบบสุ่มและไม่จัดเรียงเป็นการแทนการแจกแจงแบบเดียวกันและจะมีประสิทธิภาพมากกว่าวิธีปัจจุบันของคุณหรือไม่ ผลรวมและ N มีขนาดใหญ่แค่ไหน?
גלעדברקן

ฉันไม่รู้ว่าคุณหมายถึงอะไรโดย "พาร์ทิชันที่ไม่ได้จัดเรียงของผลรวม" และฉันไม่ได้รับการพิสูจน์ว่าการทำเช่นนั้นส่งผลให้มีการแจกแจงแบบเดียวกันภายใต้ความหมายของคำถามนี้ สำหรับคำถามนี้ทั้งสองsumและNไม่ จำกัด อย่างมีประสิทธิภาพ (ภายในเหตุผล) ฉันกำลังหาคำตอบที่ยอมรับได้เนื่องจากปัญหาพื้นฐานปรากฏขึ้นในหลายคำถามที่ถามใน Stack Overflow รวมถึงคนนี้และคนนี้ @ גלעדברקן
Peter O.

หากเราให้ชุดค่าผสมที่เป็นไปได้ "อันดับ" (หรือดัชนี) ในการจัดเรียงที่เรียงลำดับของพวกเขาทั้งหมด "unranking" จะหมายถึงการสร้างชุดค่าผสมที่ได้รับอันดับ (และ N, min และ max ของหลักสูตร) เหตุใดจึงไม่เลือกตัวเลือกหนึ่งในชุดค่าผสมที่เป็นไปได้ทั้งหมดไม่สอดคล้องกับการแจกแจงแบบเดียวกัน
גלעדברקן

คำตอบ:


3

นี่คือทางออกของฉันใน Java มันทำงานได้อย่างสมบูรณ์และมีสองเครื่องกำเนิดไฟฟ้า: 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();
        }
    }
}

คุณสามารถลองนี้ใน Ideone


ขอบคุณสำหรับคำตอบ; มันทำงานได้ดี ฉันได้อธิบายกำเนิดการเปลี่ยนแปลงในคำตอบอื่นที่นี่; ตอบคำถามอื่นด้วยความช่วยเหลือของคุณ; และจะรวมอัลกอริทึมของคุณในโค้ดตัวอย่างของ Python สำหรับบทความของฉันเกี่ยวกับวิธีการสร้างแบบสุ่ม
Peter O.

เพียงเพื่อให้ชัดเจน อัลกอริทึมนี้ขึ้นอยู่กับการสร้างพาร์ทิชัน / องค์ประกอบทั้งหมดที่เป็นไปได้เพื่อที่จะสุ่มตัวอย่างหรือไม่?
Joseph Wood

@JosephWood ไม่ขึ้นอยู่กับการนับทั้งหมด สิ่งนี้ทำเพียงครั้งเดียวที่การกำหนดค่าเริ่มต้นของตัวสร้างและค่อนข้างมีประสิทธิภาพเพราะใช้วิธีการเขียนโปรแกรมแบบไดนามิก
John McClane

การเขียนโปรแกรมแบบไดนามิกสามารถแก้ปัญหาที่เกี่ยวข้องกับการเลือกพาร์ติชันแบบสุ่มของ 'ผลรวม' เป็นจำนวนเต็ม N ที่เลือกโดยการสุ่มด้วยการแทนที่จากรายการ ( ตัวอย่าง ) หรือไม่มีการแทนที่ ( ตัวอย่าง ) หรือวิธีแก้ปัญหานั้นได้อย่างไร?
Peter O.

@PeterO คุณต้องนับจำนวนพาร์ติชันที่เป็นไปได้ทั้งหมดด้วยวิธีเดียวกันกับอัลกอริทึมของฉัน แต่เวลานี้คุณต้องลบเฉพาะจำนวนที่อนุญาตจากผลรวม นี่ยาวเกินกว่าจะแสดงความคิดเห็นคุณสามารถถามคำถามแยกต่างหาก ฉันสงสัยว่าจะสามารถแก้ปัญหาที่แตกต่างกันสี่ปัญหาด้วยวิธีการเดียวกัน สมมติว่าคุณมีรายการจำนวนเต็มที่แตกต่างกันให้เลือก (นี่เป็นเพียงช่วงต่อเนื่องในคำถามนี้) จากนั้นคุณสามารถสร้างอาร์เรย์สุ่มของความยาวที่กำหนดซึ่งประกอบด้วยตัวเลขจากรายการนี้ด้วยผลรวมที่กำหนดหากควรเรียงลำดับ / ไม่เรียงลำดับและอนุญาต / ไม่อนุญาตให้ทำซ้ำ
John McClane

1

นี่คืออัลกอริทึมจาก PermutationPartitionGenerator ของจอห์นแม็คเคลนในคำตอบอื่นในหน้านี้ มันมีสองขั้นตอนคือขั้นตอนการติดตั้งและขั้นตอนการสุ่มตัวอย่างและสร้างnตัวเลขสุ่มใน [ min, max] ด้วยผลรวม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- (สูงสุด - นาที))

ที่นี่ t (y, x) เก็บความน่าจะเป็นสัมพัทธ์ที่ผลรวมของyตัวเลข (ในช่วงที่เหมาะสม) จะเท่ากันxจะเท่ากับ นี้น่าจะเป็นญาติทุก T (y, x) yด้วยเหมือนกัน

ขั้นตอนการสุ่มตัวอย่าง: ที่นี่เราสร้างตัวอย่างของnตัวเลข ตั้งค่าsเป็นsum - n * minจากนั้นสำหรับแต่ละตำแหน่งiเริ่มต้นด้วยn - 1และทำงานย้อนกลับเป็น 0:

  • ชุด vเป็นจำนวนเต็มแบบสุ่มใน [0, t (i + 1, s))
  • ตั้งค่าrเป็นminการ
  • ลบ t (i, s) จาก vจาก
  • ในขณะที่vยังคงเป็น 0 หรือมากกว่าให้ลบ t (i, s-1) จากv, เพิ่ม 1 ถึงrและลบ 1 จากsจาก
  • จำนวนที่ตำแหน่งในกลุ่มตัวอย่างมีการตั้งค่าir

แก้ไข:

ดูเหมือนว่าด้วยการเปลี่ยนแปลงเล็กน้อยของอัลกอริทึมข้างต้นคุณสามารถมีตัวเลขสุ่มแต่ละตัวที่ใช้ช่วงแยกกันแทนที่จะใช้ช่วงเดียวกันสำหรับทุกคน:

แต่ละหมายเลขสุ่มที่ตำแหน่งi∈ [0,n ) มีค่าต่ำสุดขั้นต่ำ (i) และค่าสูงสุดสูงสุด (i)

อนุญาต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- (สูงสุด (i-1) - นาที (i -1)) )

ขั้นตอนการสุ่มตัวอย่างนั้นเหมือนกันทุกประการยกเว้นเราตั้งค่าsเป็นadjsum(มากกว่าsum - n * min) และตั้งค่าrเป็นนาที (i) (มากกว่าmin)


แก้ไข:

สำหรับ CombinationPartitionGenerator ของ John McClane ขั้นตอนการตั้งค่าและการสุ่มตัวอย่างมีดังนี้

ขั้นตอนการตั้งค่า: อันดับแรกตารางโซลูชันสร้างขึ้นโดยใช้สูตรต่อไปนี้ ( t(z, y, x)โดยที่zอยู่ใน [0, n], yอยู่ใน [0, max - min] และxอยู่ใน [0, sum - n * min]):

  • t (0, j, k) = 1 ถ้า k == 0; 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ตัวเลข ตั้งsไปsum - n * minและmrangeไปmax - minแล้วสำหรับแต่ละตำแหน่งiที่เริ่มต้นด้วยn - 1และการทำงานย้อนกลับไปที่ 0:

  • ตั้งค่าvเป็นจำนวนเต็มแบบสุ่มใน [0, t (i + 1, mrange, s))
  • ตั้งค่าmrangeเป็น min ( mrange, s)
  • ลบจากmranges
  • ตั้งค่าการrmin + mrange
  • เสื้อลบ ( i, mrange, s) vจาก
  • ในขณะที่vยังคงเป็น 0 หรือมากกว่าเพิ่ม 1 ถึงsลบ 1 จากr1 และจากmrangeนั้นเสื้อลบ ( i, mrange, s) vจาก
  • จำนวนที่ตำแหน่งในกลุ่มตัวอย่างมีการตั้งค่าir

0

ฉันยังไม่ได้ทดสอบสิ่งนี้ดังนั้นจึงไม่ใช่คำตอบจริงๆสิ่งที่ต้องลองซึ่งยาวเกินกว่าจะแสดงความคิดเห็นได้ เริ่มต้นด้วยอาร์เรย์ที่ตรงกับเกณฑ์สองข้อแรกและเล่นกับมันดังนั้นจึงยังคงตรงกับสองตัวแรก แต่จะสุ่มขึ้นมาเยอะกว่า

ถ้าค่าเฉลี่ยเป็นจำนวนเต็มอาร์เรย์เริ่มต้นของคุณอาจเป็น [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 ตัวเลขที่สองควรถูกสุ่มเลือก การจดหมายเลขแรกเพื่อให้มั่นใจว่าทุกหมายเลขจะถูกเลือกอย่างน้อยหนึ่งครั้ง

ตอนนี้คำนวณและmax-num1 num2-minสิ่งเหล่านี้คือระยะทางจากตัวเลขสองตัวไปจนถึงmaxและminขอบเขต ตั้งค่าlimitให้เล็กลงของสองระยะทาง นั่นคือการเปลี่ยนแปลงสูงสุดที่อนุญาตซึ่งจะไม่ใส่หนึ่งหรือตัวเลขอื่น ๆ นอกขีด จำกัด ที่อนุญาต ถ้าlimitเป็นศูนย์ให้ข้ามคู่นี้

เลือกจำนวนเต็มแบบสุ่มในช่วง [1 limit]: changeเรียกมันว่า ฉันข้าม 0 จากช่วงที่เลือกได้เนื่องจากไม่มีผล การทดสอบอาจแสดงว่าคุณได้รับการสุ่มที่ดีขึ้นด้วยการรวมไว้ ฉันไม่แน่ใจ.

ชุดนี้และnum1 <- num1 + change num2 <- num2 - changeที่จะไม่ส่งผลกระทบต่อค่าเฉลี่ยและองค์ประกอบทั้งหมดของอาร์เรย์ยังคงอยู่ภายในขอบเขตที่ต้องการ

คุณจะต้องวิ่งผ่านทั้งแถวอย่างน้อยหนึ่งครั้ง การทดสอบควรแสดงว่าคุณจำเป็นต้องใช้มันมากกว่าหนึ่งครั้งเพื่อให้ได้สิ่งที่สุ่มมาอย่างเพียงพอ

ETA: รวมถึง 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

ฉันได้ทดสอบแล้วและน่าเสียดายที่อัลกอริทึมของคุณไม่ได้มีการกระจายตัวของโซลูชั่นทั้งหมดอย่างสม่ำเสมอไม่ว่าฉันจะทำซ้ำกี่ครั้งก็ตาม
Peter O.

โอ้ดี มันก็คุ้มค่าที่จะลอง :(
rossum

0

ตามที่ OP ชี้ให้เห็นความสามารถในการ unrank อย่างมีประสิทธิภาพนั้นทรงพลังมาก หากเราสามารถทำเช่นนั้นได้การสร้างพาร์ติชั่นการกระจายพาร์ติชั่นอย่างสม่ำเสมอสามารถทำได้ในสามขั้นตอน

  1. คำนวณจำนวนรวมMของพาร์ติชันที่มีความยาวNของจำนวนsumที่ชิ้นส่วนนั้นอยู่ในช่วง [ min, max]
  2. [1, M]สร้างการกระจายชุดของจำนวนเต็มจาก
  3. ถอนการติดตั้งจำนวนเต็มแต่ละค่าจากขั้นตอนที่ 2 ไปยังพาร์ติชันที่เกี่ยวข้อง

ด้านล่างนี้เราจะมุ่งเน้นไปที่การสร้างn THพาร์ทิชันที่มีเป็นจำนวนมากของข้อมูลเกี่ยวกับการสร้างการกระจายชุดของจำนวนเต็มในช่วงที่กำหนด ต่อไปนี้เป็นC++อัลกอริทึมการยกเลิกการจัดเรียงแบบง่ายซึ่งควรแปลเป็นภาษาอื่นได้ง่าย

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

เราทราบก่อนว่ามีการทำแผนที่แบบหนึ่งต่อหนึ่งจากพาร์ทิชันที่มีความยาวNของจำนวนsumที่ชิ้นส่วนนั้นอยู่ในช่วง [ min, max] ไปยังพาร์ทิชันที่ จำกัด ความยาวNของจำนวนที่sum - m * (min - 1)มีชิ้นส่วนใน [ 1, max - (min - 1)]

เป็นตัวอย่างเล็ก ๆ ให้พิจารณาพาร์ทิชันของ 50ความยาว4เช่นนั้นและmin = 10 max = 15นี้จะมีโครงสร้างเช่นเดียวกับพาร์ทิชัน จำกัด50 - 4 * (10 - 1) = 14ของความยาวกับส่วนสูงสุดเท่ากับ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

เมื่อคำนึงถึงสิ่งนี้เพื่อให้ง่ายต่อการนับเราสามารถเพิ่มขั้นตอนที่ 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 THพาร์ทิชันบน:

2   2   4   6

ในการนับจำนวนพาร์ติชั่นจากที่นี่เราต้องทำการแปลต่อไปในกรณี "ยูนิต" ดูเหมือนว่า:

1   1   3   5

ในกรณีที่เป็นขั้นตอนก่อนที่เรามีสูงสุดของตอนนี้เราจะพิจารณาสูงสุดของ65

เมื่อคำนึงถึงเรื่องนี้แล้วการถอนการติดตั้งพาร์ติชั่นก็ไม่ต่างไปจากการยกเลิกการเปลี่ยนแปลงมาตรฐานหรือการรวมกัน เราจะต้องสามารถนับจำนวนพาร์ติชันในส่วนที่กำหนด ตัวอย่างเช่นหากต้องการนับจำนวนพาร์ติชันที่ขึ้นต้นด้วย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

กำหนดจำนวนเต็มแบบสุ่มเพื่อ unrank เรายังคงคำนวณจำนวนพาร์ติชันในส่วนที่เล็กกว่าและเล็กกว่า (อย่างที่เราทำด้านบน) จนกว่าเราจะเติมเวกเตอร์ดัชนีของเรา

ตัวอย่าง

ป.ร. ให้ไว้min = 3, max = 10, n = 7และsum = 42นี่เป็นideoneสาธิตที่สร้าง 20 พาร์ทิชันแบบสุ่ม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.