[Closed] Random Select
[left]I would like to select certain number of objects I want randomly among 5000 objects.[/left]
[left]Here is the script I wrote. If you select 500 objects, it does not select exactly 500. It selects somewhat around 460 to 470.[/left]
[left]The problem is selecting it randomly gives you repetition of same number of selection. How can I fix this problem?[/left]
[left]
[/left]
[left][font=Arial]rollout RndSelecter “Random Selecter” width:256 height:48[/left]
(
spinner HowManySpN “How many?” pos:[48,16] width:120 height:16 range:[1,10000,1] type:#integer
button SelRndBtn “Select” pos:[176,13] width:72 height:21
[left]on SelRndBtn pressed do[/left]
(
local TempGeomArray = #()
TempGeomArray = geometry as array
GeomArray = for obj in TempGeomArray where obj.ishidden == false collect obj
[left]local TempSelArray = #()[/left]
clearSelection()
for i=1 to DDRndSelecter.HowManySpN.value do
(
GeomI =GeomArray[(random 1 GeomArray.count)]
append TempSelArray GeomI
)
select TempSelArray
)
)
createdialog RndSelecter 256 48[left]
[/font][/left]
Hi,
indeed, when you create 500 random numbers between 1 and 5000 you're bound to create some duplicates. So to avoid collecting the same object twice you've got to remove the object you just selected from the array you're selecting from.
Replace your main selecting loop with this one and try it out. Didn't test it but it should work as an approach.
for i=1 to RndSelecter.HowManySpN.value do
(
theRandomIndex = random 1 GeomArray.count --make a random index
append TempSelArray GeomArray[theRandomIndex] --append the object to your temp array
deleteItem GeomArray theRandomIndex --remove the object form the array you're selecting from
)
Klaas
Another approach would be to create a bitarray and use the loop to set a random bit to true until the number of set bits is equal to the number of objects to select. Setting bits in a bitarray is generally faster than appending and deleting in a regular array due to the nature of these operations and the way the values are copied in memory to perform the operation.
Once the desired number of bits is set to true, one would have to collect the objects whose index has the corresponding bit set to true and select the resulting array.
EDIT:
Here is the code.
rollout RndSelecter "Random Selecter" width:256 height:48
(
spinner HowManySpN "How many?" pos:[48,16] width:120 height:16 range:[1,10000,500] type:#integer
button SelRndBtn "Select" pos:[176,13] width:72 height:21
on SelRndBtn pressed do
(
local st = timestamp() --get the start time
--collect all geometry objects visible in the viewport
local GeomArray = for obj in geometry where not obj.isHiddenInVpt collect obj
local theBitArray = #{} --define a bitarray
theBitArray[GeomArray.count] = false --set the last element to false to preinit. in memory
while theBitArray.numberset < HowManySpN.value do --while not enough set to true,
theBitArray[random 1 GeomArray.count] = true --set a random index to true
select (for i in theBitArray collect GeomArray[i]) --collect all flagged objects and select
format "% ms
" (timestamp()-st) --print the time
)
)
createdialog RndSelecter 256 48
I must admit that in most cases, the 3 variations of the code run in similar time – around 32 ms on my old home PC selecting 500 out of 5000 objects. Running the script 100 times in a row shows that there is a slight advantage to my method – the time for 100 runs was between 3062 ms and 4015 ms for my code and 4125 ms to 4250 ms for the original script modified to select all 500 objects usind deleteItem(). This is of course just for profiling – the end user would barely see any difference in actual execution time.
My version gave me a couple of faster single runs (15-16 ms), but it looks like the majority of time is spent collecting the geometry in the first loop anyway, so the rest of the code is rather fast. Still, I would prefer to stay away from excessive append() and deleteItem() calls and try to avoid intermediate variables.
In the original script, the geometry was collected once, then filtered again into a second array by visibility. This can be done in one line as shown above. Same for selecting – just collect the results inline and pass the array to select() without an intermediate variable. Creating and destroying local variables takes a tiny bit of time, so it is a good practice to optimize your code, esp. if it is going to be run multiple times. (This is more relevant to optimizing, say, Particle Flow Script Operators where the same code is run for each particle and intermediate variables memory management time can really add up).
Another thing to keep in mind: If the user would enter a number that is higher than the available number of objects, my script will never finish and lock up!
It would be a good idea to add either a test for the total number of objects and adjust the entered value if it is higher, or have an IF test that stops the script from executing, or just selecting all geometry objects at once:
rollout RndSelecter "Random Selecter" width:256 height:48
(
spinner HowManySpN "How many?" pos:[48,16] width:120 height:16 range:[1,10000,500] type:#integer
button SelRndBtn "Select" pos:[176,13] width:72 height:21
on SelRndBtn pressed do
(
local st = timestamp()
local GeomArray = for obj in geometry where not obj.isHiddenInVpt collect obj
if GeomArray.count <= HowManySpN.value then
select GeomArray
else
(
local theBitArray = #{}
theBitArray[GeomArray.count] = false
while theBitArray.numberset < HowManySpN.value do
theBitArray[random 1 GeomArray.count] = true
select (for i in theBitArray collect GeomArray[i])
)
format "% ms
" (timestamp()-st)
)
)
createdialog RndSelecter 256 48