1
$\begingroup$

I want to generate a set of X int numbers, were 4 <= X <= 100, both positive and negative that add up to target value. The numbers are between -6 and 4 and there needs to be at least 2 negative numbers, and up to half of the numbers can be negative.

I have seen posts about this being done for all positive numbers but I haven't figured out how adapt it to my case were there need to be negatives.

I have thought of an algorithm but was wondering if a better solution is already available/known or possible improvements to my algorithm.

Psudo Code with Example

PositiveNumbers = [0, 1, 2, 3, 4]
OriginalTarget = 4
numOfTerms = 5

numberOfNegatives = rand(2, numOfTerms/2)
numberOfPositives = numOfTerms - numberOfNegatives

// Restriction only 10% of positives can be 4s
maxNumberOfFours = numberOfPositives * .10

//Generate and save negatives numbers between -6 and -1
int[] negatives = generateNegatives(numberOfNegatives)
negativeSum = calculateNegativeSum(negatives)

// Calculate new Target
Target = OriginalTarget - negativeSum

// Makes sure we can reach the target with the amount of numbers we have left
// Calculate max positive sum possible >= Target
targetRechable = CalculateMaxPossibleSum(numberOfPositives, maxNumberOfFours, target) 

If targetReachable --> use algorith to generate positive numbers that reach target

else {
    while (!targetRechable) {
        int[] negatives = removeLastNegative()
        negativeSum = calculateNegativeSum(negatives)

        Target = OriginalTarget - negativeSum

        targetRechable = CalculateMaxPossibleSum(numberOfPositives, maxNumberOfFours, target)

    }
    use algorithm to generate positive numbers that reach target
}
 ```
$\endgroup$
5
  • $\begingroup$ What probability distribution do you want the numbers to have? Do you care? $\endgroup$
    – D.W.
    Mar 15, 2023 at 2:11
  • $\begingroup$ At the moment I do not have a probability distribution in mind, it does not affect much I want to do, so we could ignore that for now, if I had one it would slightly favor smaller numbers [ -3, . . . , 3] with -6, -5, -4 and 4 being a rarer occurrence. $\endgroup$ Mar 15, 2023 at 13:21
  • $\begingroup$ What is a typical value for $X$? How large will $X$ be? I'm trying to figure out whether to suggest a simple-to-code algorithm that will work for fairly small values of $X$ or something that will work for all values of $X$ but might be more complex to code. $\endgroup$
    – D.W.
    Mar 15, 2023 at 17:15
  • $\begingroup$ Sorry I forgot to mention it on my post, I will edit it and add it, but to answer your question 4 <= X <= 100. On average the vast majority of X will be between 4 and 50 $\endgroup$ Mar 15, 2023 at 20:29
  • $\begingroup$ I think you mean a list of numbers, not a set of numbers. You can't have a set of more than 11 numbers with all numbers in the range -4,..,6, since each number can only appear in a set once (and the set doesn't take into account ordering). $\endgroup$
    – D.W.
    Mar 16, 2023 at 5:59

3 Answers 3

1
$\begingroup$

Quick and dirty hack

Here is a simple, easy-to-implement approach that meets your requirements and might be good enough when $X \le 100$. It does not guarantee a uniform distribution.

We will randomly and independently select $X$ numbers, from some distribution. Then, we'll test whether that list of $X$ numbers meets all the requirements (sums to the target $T$, has at least 2 negative numbers and at most $X/2$ negative numbers). If it does, we'll use that list of numbers. If it doesn't, we'll go back to the beginning and try again with new random choices. This is known as rejection sampling.

I propose a distribution on each number, to make this more efficient. In particular, let $T$ be the target number, and define $t=T/X$. Then there are two cases:

  • If $t \le 1.8$, define $p=(t+3.5)/5.5$, and with probability $p$, choose a random number from 0..4; otherwise choose a random number from -6..-1.

  • If $t > 1.8$, define $p = \min(1-2/X,(t - 1.5 + 10/X)/2.5)$, and with probability $2/X$, choose a random number from -6..-1, with probability $p$, choose 4, and with probability $1-p-2/X$, choose a random number from 0..3.

Use this distribution for each number in the list. These distributions are chosen to have expected value equal to $t$ (so that rejection sampling will work well, and the number of iterations needed will be manageable), and so that it's likely we'll have an acceptable number of negative numbers in the list.

Principled solution

Alternatively, here is a solution that gives the best possible randomness, but unfortunately will be more complicated to implement. Let's say that a list of numbers is a solution if it satisfies all of your conditions. Then, it is possible to sample uniformly at random from the set of solutions.

The core technique is that we'll construct an algorithm count the number of solutions. In fact, we'll define an ordering on all possible solutions, call it $\prec$, so that $x \prec y$ means that the solution $x$ comes before $y$ in the ordering. Then, we'll design an algorithm that given an index $r$, lets us find the $r$th solution in this ordering. With these building blocks, it becomes simple to generate a random solution: we count the number $n$ of solutions, we randomly pick a number $r$ in the range $1,\dots,n$, and then we find the $r$th solution and output it. By construction, this will sample uniformly at random from the set of all solutions.

How do we count the number of solutions? Using dynamic programming. Let $A[i,s,k]$ count the number of solutions such that the first $i$ numbers sum to $s$, and there are $k$ negative numbers among the first $i$ numbers. We can use dynamic programming to compute $A[i,s,k]$ for every value of $i,s,k$, using a recurrence:

$$A[i,s,k] = \sum_{x=-6}^{-1} A[i-1,s-x,k-1] + \sum_{x=0}^4 A[i-1,s-x,k].$$

Finally, if $t$ is the target value, then $\sum_{k=2}^{X/2} A[X,t,k]$ counts the number of solutions.

Now, given an index $r$, how do we find the $r$th solution in an appropriate ordering? This is called "unranking". The procedure itself gets messy, but conceptually it is simple: we are traversing backwards through the recurrence, using $A$ to figure out how to fill in the last element so that the number of elements that precede the solution we'll pick will be less than $r$.

Basically, we can put solutions to your problem into one-to-one correspondence (isomorphism) with paths through a DAG. Each vertex in the graph corresponds to a tuple $(i,s,k)$. The DAG has a single starting node, with an edge from the start node to $(X,t,k)$ for each $k=2,3,\dots,X/2$. The DAG also has a single ending node, $(0,0,0)$. Using the recurrence above, we can see that each path through the graph from the start node to the end node can be mapped to a solution to your problem. The dynamic programming above is basically counting the number of such paths in this graph.

Now our unranking algorithm can be viewed as finding the $r$th path in this graph, and mapping it to a solution to your problem. For how to do that, see Efficiently sampling shortest $s$-$t$ paths uniformly and independently at random and Algorithm that finds the number of simple paths from $s$ to $t$ in $G$ to understand the core concepts here.

If we expand this out in the specific case of your particular problem, I think it looks something like the following (warning: it gets incredibly messy). First, we find $k^*$ such that

$$\sum_{k=2}^{k^*-1} A[X,t,k] \le r < \sum_{k=2}^{k^*} A[X,t,k].$$

Next, we let $r' = r - \sum_{k=2}^{k^*-1} A[X,t,k]$. Next, defining

$$f(x^*) = \sum_{x=-6}^{\min(-1,x^*)} A[X-1,t-x,k^*-1] + \sum_{x=0}^{x^*} A[X-1,t-x,k^*],$$

we find $x^*$ such that

$$f(x^*-1) \le r' < f(x^*),$$

we define $r'' = r - f(x^*-1)$, and $k'=k^*-1$ if $x^*<1$ or $k'=k^*-1$ if $x^*\ge 1$. Then we decide that the last element in the list of numbers will be $x^*$, and the list will contain $k^*$ negative numbers. We can recurse to fill in the entire list, going backwards to front.

I haven't checked the math above carefully, so please try to understand the concept, and then work out the solution for yourself, to make sure I haven't made mistakes along the way.

$\endgroup$
0
$\begingroup$

For example, if X = 100 then obviously the smallest number is -300 (50 times -6 and 50 times 0) and the largest is 390 (2 times -1 and 98 times 4). An algorithm to pick the numbers for each target is quite trivial.

$\endgroup$
0
$\begingroup$

The case with negatives is not different.

Consider $X$ numbers in the range $1$ to $11$ and add $7X$ to the target. Then you can apply the known algorithm. (I assume that this algorithm can also handle the constraint "between $2$ and $X/2$ numbers are below $7$".)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.