0

As my tile says that I want to get random number for origin (X-Axis & y-Axis) so in my whole screen in iPad landscape I have 1 rectangle, I want to get random number for origin which out of this rectangle, so obiously I want to get random number for X-Axis between max and min and same as for Y-Axis.

I tried with following answers but not helpful for me.

Generate Random Numbers Between Two Numbers in Objective-C
Generate a random float between 0 and 1
Generate random number in range in iOS?

For more clear see below image

enter image description here

In above image I just want to find random number (for origin) of GREEN screen. How can I achieve it ?

Edited

I had tried.

int randNum = rand() % ([max intValue] - [min intValue]) + [min intValue]; 

Same for both X-Axis & y-Axis.

Community
  • 1
  • 1
  • It might help if you post some code of what you've tried? Are you saying that the blue rectangle is in a random position and now you want to create another random spot that is inside the green area but not in the blue area? – Flexicoder Mar 21 '14 at 13:36

6 Answers6

0

If the blue exclusion rectangle is not "too large" compared to the green screen rectangle then the easiest solution is to

  • create a random point inside the green rectangle,
  • check if the point lies inside the blue rectangle, and
  • repeat the process if necessary.

That would look like:

CGRect greenRect = ...;
CGRect blueRect = ...;
CGPoint p;
do {
    p = CGPointMake(greenRect.origin.x + arc4random_uniform(greenRect.size.width),
                    greenRect.origin.y + arc4random_uniform(greenRect.size.height));
} while (CGRectContainsPoint(blueRect, p));

If I remember correctly, the expected number of iterations is G/(G - B), where G is the area of the green rectangle and B is the area of the blue rectangle.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Love the do-while, but if this were a mid-term, teacher would take off half credit for having a loop at all. – danh Mar 21 '14 at 14:12
  • @danh: Of course you could represent the admissible area as 4 rectangles, choose one of these randomly first, and then choose a point inside the selected rectangle. But then the tricky part is to select one of the 4 rectangles with the correct probabilities (proportional to their area). - Or is there another solution that I did not think of? – Martin R Mar 21 '14 at 14:19
  • I think there is a way, considering the dimensions independently. I expressed here in an answer. – danh Mar 21 '14 at 14:36
  • Oh gosh, reading your challenge, I just realised that my answer is inadequate, since the dimensions are not fully independent. I'll have to fix my answer (it's still very doable without search). Need to run out, so I must withdraw it for now and fix later. – danh Mar 21 '14 at 14:42
  • Would be curious to hear your reaction to the solution I just posted. – danh Mar 21 '14 at 16:34
0

[Apologies I did this with OS X, translation is straightforward]

A non-iterative solution:

- (NSPoint) randomPointIn:(NSRect)greenRect excluding:(NSRect)blueRect
{
    // random point on green x-axis
    int x = arc4random_uniform(NSWidth(greenRect)) + NSMinX(greenRect);
    if (x < NSMinX(blueRect) || x > NSMaxX(blueRect))
    {
        // to the left or right of the blue, full height available
        int y = arc4random_uniform(NSHeight(greenRect)) + NSMinY(greenRect);
        return NSMakePoint(x, y);
    }
    else
    {
        // within the x-range of the blue, avoid it
        int y = arc4random_uniform(NSHeight(greenRect) - NSHeight(blueRect)) + NSMinY(greenRect);
        if (y >= NSMinY(blueRect))
        {
            // not below the blue, step over it
            y += NSHeight(blueRect);
        }
        return NSMakePoint(x, y);
    }
}

This picks a random x-coord in the range of green. If that point is outside the range of blue it picks a random y-coord in the range of green; otherwise it reduces the y range by the height of blue, produces a random point, and then increases it if required to avoid blue.

There are other solutions based on picking a uniform random point in the available area (green - blue) and then adjusting, but the complexity isn't worth it I think (I haven't done the stats).

Addendum

OK folk seem concerned over uniformity, so here is the algorithm mentioned in my last paragraph. We're picking an "point" with integer coords so the number of points to pick from is the green area minus the blue area. Pick a point randomly in this range. Now place it into one of the rectangles below, left, right or above the blue:

// convenience
int RectArea(NSRect r) { return (int)NSWidth(r) * (int)NSHeight(r); }

- (NSPoint) randomPointIn:(NSRect)greenRect excluding:(NSRect)blueRect
{
    // not we are using "points" with integer coords so the
    // bottom left point is 0,0 and the top right (width-1, height-1)
    // you can adjust this to suit

    // the number of points to pick from is the diff of the areas
    int availableArea = RectArea(greenRect) - RectArea(blueRect);
    int pointNumber = arc4random_uniform(availableArea);

    // now "just" locate pointNumber into the available space
    // we consider four rectangles, one each full width above and below the blue
    // and one each to the left and right of the blue

    int belowArea = NSWidth(greenRect) * (NSMinY(blueRect) - NSMinY(greenRect));
    if (pointNumber < belowArea)
    {
        return NSMakePoint(pointNumber % (int)NSWidth(greenRect) + NSMinX(greenRect),
                           pointNumber / (int)NSWidth(greenRect) + NSMinY(greenRect));
    }

    // not below - consider to left
    pointNumber -= belowArea;
    int leftWidth = NSMinX(blueRect) - NSMinX(greenRect);
    int leftArea = NSHeight(blueRect) * leftWidth;
    if (pointNumber < leftArea)
    {
        return NSMakePoint(pointNumber % leftWidth + NSMinX(greenRect),
                           pointNumber / leftWidth + NSMinY(blueRect));
    }

    // not left - consider to right
    pointNumber -= leftArea;
    int rightWidth = NSMaxX(greenRect) - NSMaxX(blueRect);
    int rightArea = NSHeight(blueRect) * rightWidth;
    if (pointNumber < rightArea)
    {
        return NSMakePoint(pointNumber % rightWidth + NSMaxX(blueRect),
                           pointNumber / rightWidth + NSMinY(blueRect));
    }

    // it must be above
    pointNumber -= rightArea;
    return NSMakePoint(pointNumber % (int)NSWidth(greenRect) + NSMinX(greenRect),
                       pointNumber / (int)NSWidth(greenRect) + NSMaxY(blueRect));
}

This is uniform, but whether it is worth it you'll have to decide.

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Shouldn't it be "-" instead of "+" in the first arc4random_uniform() call? - One disadvantage might be that it does not choose all points uniformly in the admissible area with equal probability. E.g. if blueRect = (0,0,1,1) and greenRect = (0,0,2,2), where the notation is (origin.x, origin.y, size.width, size.height) then your method chooses a point in r1=(1,0,1,2) or in r2=(0,1,1,1) with equal probility 0.5, even if the r1 has twice the area of r2. – Martin R Mar 21 '14 at 15:42
  • No, + is correct (random in width + min). Stats - any column is equally likely, and within any column any permissible row is equally likely; but viewed as areas as you suggest... With a few more conditionals it can be made uniform for sure (number of possible points is the diff of the areas, pick one, adjust), but is it worth it? I don't think it will make that much difference, but I haven't done the stats of either this or the iterative version you give. – CRD Mar 21 '14 at 16:17
  • I think I've got the probability right, albeit with too many source lines. Would be curious to hear your reaction. – danh Mar 21 '14 at 16:35
  • @danh - It's easier than that, no probabilities needed at all, just the solution I mentioned in my last para. I've added code of it as an addendum. – CRD Mar 21 '14 at 17:46
  • @CRD I've read it a couple times, but I'm not seeing it. Wouldn't you need to consider all eight regions surrounding the inner rect? And I'm having a tough time seeing how it's okay to modify pointNumber by subtracting areas. I'm sure you've given a good answer, but my mind is either too small, or two warped by my own solution to see it. (Fun problem, anyway). Cheers. – danh Mar 21 '14 at 21:05
  • @danh Each valid outcome has the exact same probability. First, compute the total number of possible outcomes (area of bigger minus area of smaller). Now, randomly pick one valid outcome. The trick is in the mapping between a randomly selected outcome and its meaning. If you look at the solution space from the x-axis, there are only 4 possible areas... left of the excluded box, above the excluded box, below the excluded box, and right of the excluded box. Every solution you get fits in one of them, and it does not matter which mapping you pick as long as it is consistent. – Jody Hagins Mar 21 '14 at 21:19
0

What if you first determined x within the green rectangle like this:

int randomX = arc4random()%greenRectangle.frame.size.width;
int randomY; // we'll do y later

Then check if this is inside the blue rectangle:

if(randomX < blueRectangle.frame.origin.x && randomX > (blueRectangle.frame.origin.x + blueRectangle.frame.size.width))
{
    //in this case we are outside the rectangle with the x component
    //so can randomly generate any y like this:
    randomY = arc4random()%greenRectangle.frame.size.height;
}
//And if randomX is in the blue rectangle then we can use the space either before or after it:
else
{
      //randomly decide if you are going to use the range to the left of blue rectangle or to the right
     BOOL shouldPickTopRange = arc4random()%1;

     if(shouldPickTopRange)
     {
         //in this case y can be any point before the start of blue rectangle
         randomY = arc4random()%blueRectangle.frame.origin.y;
     }
     else
     {
         //in this case y can be any point after the blue rectangle
         int minY = blueRectangle.frame.origin.y + blueRectangle.frame.size.height;
         int maxY = greenRectangle.frame.size.height;

         randomY = arc4random()%(maxY - minY + 1) + minY;
     }
}

Then your random point would be:

 CGPoint randomPoint = CGPointMake(randomX, randomY);

The only thing missing above is to check if your blue rectangle sits at y = 0 or at the very bottom of green rectangle.

jancakes
  • 456
  • 4
  • 10
  • See my comment to CRD's answer, it applies here as well. The admissible points are not chosen with a equal probability. – Martin R Mar 21 '14 at 15:46
  • To make the top and the bottom parts equally weighted, then the ratio of topHeight and bottomHeight should be determined, so for instance if topHeight = 2 and bottomHeight = 7, then could do int randomFactor = arc4random()%(2+7). Then shouldPickTopRange would be determined based on where in that range randomFactor lies – jancakes Mar 21 '14 at 15:56
0

Okay. This was bothering me, so I did the work. It's a lot of source code, but computationally lightweight and probabilistically correct (haven't tested).

With all due respect to @MartinR, I think this is superior insofar as it doesn't loop (consider the case where the contained rect covers a very large portion of the outer rect). And with all due respect to @CRD, it's a pain, but not impossible to get the desired probabilities. Here goes:

// Find a random position in rect, excluding a contained rect called exclude
//
// It looks terrible, but it's just a lot of bookkeeping.
// Divide rect into 8 regions, like a tic-tac-toe board, excluding the center square
// Reading left to right, top to bottom, call these: A,B,C,D, (no E, it's the center) F,G,H,I

// The random point must be in one of these regions, choose by throwing a random dart, using
// cumulative probabilities to choose.  The likelihood that the dart will be in regions A-I is
// the ratio of each's area to the total (less the center)

// With a target rect, correctly selected, we can easily pick a random point within it.

+ (CGPoint)pointInRect:(CGRect)rect excluding:(CGRect)exclude {

    // find important points in the grid
    CGFloat xLeft = CGRectGetMinX(rect);
    CGFloat xCenter = CGRectGetMinX(exclude);
    CGFloat xRight = CGRectGetMaxX(exclude);

    CGFloat widthLeft = exclude.origin.x-CGRectGetMinX(rect);
    CGFloat widthCenter = exclude.size.width;
    CGFloat widthRight = CGRectGetMaxY(rect)-CGRectGetMaxX(exclude);

    CGFloat yTop = CGRectGetMinY(rect);
    CGFloat yCenter = exclude.origin.y;
    CGFloat yBottom = CGRectGetMaxY(exclude);

    CGFloat heightTop = exclude.origin.y-CGRectGetMinY(rect);
    CGFloat heightCenter = exclude.size.height;
    CGFloat heightBottom = CGRectGetMaxY(rect)-CGRectGetMaxY(exclude);

    // compute the eight regions
    CGFloat areaA = widthLeft * heightTop;
    CGFloat areaB = widthCenter * heightTop;
    CGFloat areaC = widthRight * heightTop;

    CGFloat areaD = widthLeft * heightCenter;
    CGFloat areaF = widthRight * heightCenter;

    CGFloat areaG = widthLeft * heightBottom;
    CGFloat areaH = widthCenter * heightBottom;
    CGFloat areaI = widthRight * heightBottom;

    CGFloat areaSum = areaA+areaB+areaC+areaD+areaF+areaG+areaH+areaI;

    // compute the normalized probabilities
    CGFloat pA = areaA/areaSum;
    CGFloat pB = areaB/areaSum;
    CGFloat pC = areaC/areaSum;
    CGFloat pD = areaD/areaSum;
    CGFloat pF = areaF/areaSum;
    CGFloat pG = areaG/areaSum;
    CGFloat pH = areaH/areaSum;

    // compute cumulative probabilities
    CGFloat cumB = pA+pB;
    CGFloat cumC = cumB+pC;
    CGFloat cumD = cumC+pD;
    CGFloat cumF = cumD+pF;
    CGFloat cumG = cumF+pG;
    CGFloat cumH = cumG+pH;

    // now pick which region we're in, using cumulatvie probabilities
    // whew, maybe we should just use MartinR's loop.  No No, we've come too far!

    CGFloat dart = uniformRandomUpTo(1.0);
    CGRect targetRect;

    // top row
    if (dart < pA) {
        targetRect = CGRectMake(xLeft, yTop, widthLeft, heightTop);
    } else if (dart >= pA && dart < cumB) {
        targetRect = CGRectMake(xCenter, yTop, widthCenter, heightTop);
    } else if (dart >= cumB && dart < cumC) {
        targetRect = CGRectMake(xRight, yTop, widthRight, heightTop);
    }

    // middle row
    else if (dart >= cumC && dart < cumD) {
        targetRect = CGRectMake(xRight, yCenter, widthRight, heightCenter);
    } else if (dart >= cumD && dart < cumF) {
        targetRect = CGRectMake(xLeft, yCenter, widthLeft, heightCenter);
    }

    // bottom row
    else if (dart >= cumF && dart < cumG) {
        targetRect = CGRectMake(xLeft, yBottom, widthLeft, heightBottom);
    } else if (dart >= cumG && dart < cumH) {
        targetRect = CGRectMake(xCenter, yBottom, widthCenter, heightBottom);
    } else {
        targetRect = CGRectMake(xRight, yBottom, widthRight, heightBottom);
    }

    // yay.  pick a point in the target rect
    CGFloat x = uniformRandomUpTo(targetRect.size.width) + CGRectGetMinX(targetRect);
    CGFloat y = uniformRandomUpTo(targetRect.size.height)+ CGRectGetMinY(targetRect);

    return CGPointMake(x, y);
}

float uniformRandomUpTo(float max) {
    return max * arc4random_uniform(RAND_MAX) / RAND_MAX; 
}
danh
  • 62,181
  • 10
  • 95
  • 136
  • At first use I think you meant `arc4random() / RAND_MAX`. – CRD Mar 21 '14 at 20:22
  • Oh yeah, but I want uniform distribution, so I guess that's arc4random_uniform(RAND_MAX) / RAND_MAX; right? – danh Mar 21 '14 at 20:30
0

Try this code, Worked for me.

-(CGPoint)randomPointInRect:(CGRect)r
{
    CGPoint p = r.origin;

    p.x += arc4random_uniform((u_int32_t) CGRectGetWidth(r));
    p.y += arc4random_uniform((u_int32_t) CGRectGetHeight(r));

    return p;
}
Hasya
  • 9,792
  • 4
  • 31
  • 46
Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
-1

I don't like piling onto answers. However, the provided solutions do not work, so I feel obliged to chime in.

Martin's is fine, and simple... which may be all you need. It does have one major problem though... finding the answer when the inner rectangle dominates the containing rectangle could take quite a long time. If it fits your domain, then always choose the simplest solution that works.

jancakes solution is not uniform, and contains a fair amount of bias.

The second solution provided by dang just plain does not work... because arc4_random takes and returns uint32_t and not a floating point value. Thus, all generated numbers should fall into the first box.

You can address that by using drand48(), but it's not a great number generator, and has bias of its own. Furthermore, if you look at the distribution generated by that method, it has heavy bias that favors the box just to the left of the "inner box."

You can easily test the generation... toss a couple of UIViews in a controller, add a button handler that plots 100000 "random" points and you can see the bias clearly.

So, I hacked up something that is not elegant, but does provide a uniform distribution of random numbers in the larger rectangle that are not in the contained rectangle.

You can surely optimize the code and make it a bit easier to read...

Caveat: Will not work if you have more than 4,294,967,296 total points. There are multiple solutions to this, but this should get you moving in the right direction.

- (CGPoint)randomPointInRect:(CGRect)rect 
               excludingRect:(CGRect)excludeRect
{
    excludeRect = CGRectIntersection(rect, excludeRect);
    if (CGRectEqualToRect(excludeRect, CGRectNull)) {
        return CGPointZero;
    }

    CGPoint result;
    uint32_t rectWidth = rect.size.width;
    uint32_t rectHeight = rect.size.height;
    uint32_t rectTotal = rectHeight * rectWidth;
    uint32_t excludeWidth = excludeRect.size.width;
    uint32_t excludeHeight = excludeRect.size.height;
    uint32_t excludeTotal = excludeHeight * excludeWidth;

    if (rectTotal == 0) {
        return CGPointZero;
    }
    if (excludeTotal == 0) {
        uint32_t r = arc4random_uniform(rectHeight * rectWidth);
        result.x = r % rectWidth;
        result.y = r /rectWidth;
        return result;
    }

    uint32_t numValidPoints = rectTotal - excludeTotal;
    uint32_t r = arc4random_uniform(numValidPoints);

    uint32_t numPointsAboveOrBelowExcludedRect =
        (rectHeight * excludeWidth) - excludeTotal;

    if (r < numPointsAboveOrBelowExcludedRect) {
        result.x = (r % excludeWidth) + excludeRect.origin.x;
        result.y = r / excludeWidth;
        if (result.y >= excludeRect.origin.y) {
            result.y += excludeHeight;
        }
    } else {
        r -= numPointsAboveOrBelowExcludedRect;
        uint32_t numPointsLeftOfExcludeRect =
            rectHeight * excludeRect.origin.x;
        if (r < numPointsLeftOfExcludeRect) {
            uint32_t rowWidth = excludeRect.origin.x;
            result.x = r % rowWidth;
            result.y = r / rowWidth;
        } else {
            r -= numPointsLeftOfExcludeRect;
            CGFloat startX =
                excludeRect.origin.x + excludeRect.size.width;
            uint32_t rowWidth = rectWidth - startX;
            result.x = (r % rowWidth) + startX;
            result.y = r / rowWidth;
        }
    }

    return result;
}
Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
  • 1
    Apart from the checking for intersection which the other solutions omit (for brevity or taken as read) how does this materially differ from my second solution offered in my original last paragraph, with code later added as an addendum? – CRD Mar 21 '14 at 20:08
  • Your original post provided a solution which had bias in the random number generation. I guess I missed your addendum. If I had seen it, I would have kept moving. Looking at it briefly, it does not look much different from what I posted. Then again, what we did is a quite common method. – Jody Hagins Mar 21 '14 at 20:44