#delphi #random #probability
Вопрос:
Я хочу рисовать случайным образом пиксели на экране, при этом вероятность не должна быть распределена поровну.
Пример: Допустим, экран имеет разрешение 1920 x 1080 пикселей. В случае розыгрыша вероятность того, что будут нарисованы пиксели, расположенные в прямоугольнике размером 100 х 100 в позиции (500 500), должна быть в 10 раз выше, чем для пикселей за пределами прямоугольника.
Для достижения этой цели я сначала создаю массив, содержащий вероятность. Позиции внутри прямоугольника получают значение 10, все остальные позиции получают значение 1.
for i := 1 to 1920 do
begin
for j := 1 to 1080 do
begin
FProbability[i, j]:=1;
if InRange(i, 500, 600) and InRange(j, 500, 600) then
begin
FProbability[i, j]:=10;
end;
end;
end;
Затем я составляю список всех пикселей:
FPixelList:=TList<TPoint>.Create;
for i := 1 to 1920 do
begin
for j := 1 to 1080 do
begin
for k := 1 to FProbabilty[i, j] do
begin
FPixelList.Add(TPoint.Create(i,j))
end;
end;
end;
Список пикселей теперь содержит 10 записей для каждого пикселя внутри прямоугольника и 1 запись для всех остальных позиций пикселей.
В случае розыгрыша я получаю положение пикселя, которое будет нарисовано
FPixelList[RandomRange(0, FPixelList.Count-1)]
Это прекрасно работает.
Однако мне было интересно, есть ли другие решения этой проблемы. Мое решение использует много памяти, если размеры экрана становятся больше, и я могу использовать только целочисленные значения для вероятности.
Комментарии:
1. Обратите внимание, что это, по сути, вопрос, не зависящий от языка; ответ будет (по сути) одинаковым в сборке C , C#, Rust или x86. На самом деле, это едва ли вопрос программирования: по сути, это вопрос чистой математики.
2. Для этого вам не нужна никакая память. Предположим, проблема в том, что вам нужны орел и решка, но вероятность решки в два раза выше. Затем используйте Случайное(3), и 0 означает орел, 1 или 2 означает решку. Расширьте эту концепцию, и вы закончите.
Ответ №1:
Как отметил Дэвид Хеффернан в комментариях, для этого нет необходимости использовать дополнительное хранилище.
Вместо этого мы можем расширить диапазон случайного выбора за пределы размера изображения, чтобы включить (regionOptions-1)*regionSize
дополнительные значения, где regionOptions
представлена повышенная вероятность выбора пикселя, в вашем примере это было 10, в области размера regionSize
. Если случайно выбранное значение меньше размера изображения, то мы используем его непосредственно в качестве позиции на изображении. Если он больше, то мы выполняем расчет, чтобы определить соответствующую позицию в интересующей области.
Боюсь, я не знаком с Delphi, поэтому для иллюстрации приведу несколько примеров Java-кода.
int imWidth = 1920;
int imHeight = 1000;
int[][] image = new int[imWidth][imHeight];
int regionOptions = 10;
Rectangle region = new Rectangle(500, 500, 100, 100);
int imSize = imWidth * imHeight;
int randomRange = imSize (regionOptions-1)*region.width*region.height;
int max = 0;
Random rand = new Random();
for(int i=0; i<region.width*region.height*1000; i )
{
int randPos = rand.nextInt(randomRange);
int x, y;
if(randPos < imSize)
{
x = randPos % imWidth;
y = randPos / imWidth;
}
else
{
randPos = (randPos - imSize) / (regionOptions - 1);
x = region.x randPos % region.width;
y = region.x randPos / region.height;
}
image[x][y] = 1;
max = Math.max(max, image[x][y]);
}
И я использовал следующий код для создания нормализованного изображения с целью демонстрации:
BufferedImage im = new BufferedImage(imWidth, imHeight, BufferedImage.TYPE_BYTE_GRAY);
for(int i=0; i<imWidth; i )
{
for(int j=0; j<imHeight; j )
{
int level = (int)(255.0*image[i][j]/max);
im.setRGB(i, j, (level << 16) | (level << 8) | level);
}
}
try
{
ImageIO.write(im, "png", new File("probIm.png"));
}
catch(Exception e)
{
e.printStackTrace();
}
Это вырезанная область из изображения с использованием regionOptions = 2
. При значении 10 нормализованные пиксели слишком сгруппированы в интересующей области, чтобы отображать какой-либо фон.
Ответ №2:
Почему бы не использовать другой подход: сначала нарисуйте определенное количество случайно расположенных пикселей по всей области, подсчитав, сколько из них было нарисовано в сфокусированной области и сколько в нормальной области.
Затем вы можете просто умножить количество пикселей из нормальной области на 10 и вычесть количество пикселей, которые уже были нарисованы в фокусе, чтобы получить количество пикселей, которые все еще нужно нарисовать в фокусе, чтобы удовлетворить желаемому соотношению. Как только у вас появится это число, вы просто дополнительно визуализируете необходимое количество случайных пикселей, расположенных внутри области фокусировки.
Код для этого будет выглядеть примерно так:
procedure TForm2.Button1Click(Sender: TObject);
var X,Y,N: Integer;
Rect: Trect;
Pt: Tpoint;
NormalAreaCount, FocusedAreaCount: Integer;
begin
//Set the rectagle boundaries where pixels needs to be more focused
Rect.Left := 500;
Rect.Top := 500;
Rect.Width := 100;
Rect.Height := 100;
//Initialize pixels count variables to 0. Othervise they might end up bein unitialized.
NormalAreaCount := 0;
FocusedAreaCount := 0;
//Use first loop do draw certain number of random pixels across the whole area
for N := 0 to 200 do
begin
X := Random(PaintBox1.Width);
Y := Random(PaintBox1.Height);
PaintBox1.Canvas.Pixels[X,Y] := clBlack;
Pt.X := X;
Pt.Y := Y;
if PtInRect(Rect,Pt) then
begin
//if the drawn pixel was in are of focus rectangle increase FocusedAreaCount
Inc(FocusedAreaCount);
end
//else increase NormalAreaCount
else Inc(NormalAreaCount);
end;
//In scond loop draw more random pixels only in focused rectangle area
//You get the number of pixels taht needs to be drawn by multiplying the number of pixels
//taht were drawn outsite focused area by 10 and then substract the number of pixels that
//were already drawn within the focused area
for N := 0 to (NormalAreaCount*10)-FocusedAreaCount-1 do
begin
X := RandomRange(Rect.Left,Rect.Right);
Y := RandomRange(Rect.Top,Rect.Bottom);
PaintBox1.Canvas.Pixels[X,Y] := clBlack;
//Here I increase the FocusedArea count so I can verify the corect number of pixels drawn
Inc(FocusedAreaCount);
end;
//Simply display the number of pixels drawn in focused and normal area for visual verificatiuon
Form2.Caption := IntToStr(NormalAreaCount) ':' IntToStr(FocusedAreaCount);
end;
Конечно, этот подход не гарантирует вам фиксированное общее количество пикселей, так как начальное количество пикселей, нарисованных в нормальной области, является немного случайным. Также этот код не гарантирует, что пиксель в сфокусированном виде не будет прорисован во втором цикле.