How to Optimize an Overuse of If Statements in Roblox Studio - optimization

The goal of this code is to spawn a ball "GlowyBall" in 1 of 5 preset locations randomly. This script activates when a player hits a button. The ball also needs to spawn as 1 of 3 colors randomly. The code works for the most part, but I am struggling when it comes to making this code optimized. I don't know which datatype I should or even can use to replace these if statements. I am just trying to learn different avenues that can be taken. The reason this code needs to be optimized is that it could be used thousands of times per minute, and I don't want the game to be held back by the code.
...
-- Says that there will be 3 colors
local ColorRange = 3
-- Says that there will be 5 spawn locations
local range = 5
-- Makes the code run continuously
while true do
local ColorNumber = math.random(1, ColorRange)
local Number = math.random(1, range)
-- Chooses the random color
if ColorNumber == 1 then
game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball1.Color = Color3.new(1, 0, 0)
end
if ColorNumber == 2 then
game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball2.Color = Color3.new(0, 1, 0)
end
if ColorNumber == 3 then
game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball3.Color = Color3.new(0, 0, 1)
end
-- Chooses which ball will get cloned
if Number == 1 then
ClonePart = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball1
end
if Number == 2 then
ClonePart = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball2
end
if Number == 3 then
ClonePart = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball3
end
if Number == 4 then
ClonePart = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball4
end
if Number == 5 then
ClonePart = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1.Glowyball5
end
wait(.6)
local Clone = ClonePart:Clone()
script.Parent.ClickDetector.MouseClick:connect(function()
Clone.Parent = game.Workspace
Clone.Anchored = false
end)
end
...
I am fairly new to programming as a whole so feel free to teach me a few things, thanks.

Instances in Roblox can be accessed in a few different ways; the most common is dot notation (eg. game.Workspace.Part). It's also possible to access instances like items in a table (eg. game["Workspace"]["Part"]). This is useful when accessing an instance with a space in its name, or to add something to the start/end of a string before accessing it.
The if statements for choosing which ball to clone can then be reduced to the following:
-- Chooses which ball will get cloned
ClonePart = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1["Glowyball" .. Number] -- Add Number to the end of "Glowyball" before accessing it
The same could be done with choosing the random color, as well as substituting multiple if statements for one if-elseif statement could make the code more readable.
-- Chooses the random color
local Glowyball = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1["Glowyball" .. ColorNumber]
if ColorNumber == 1 then
Glowyball.Color = Color3.new(1, 0, 0)
elseif ColorNumber == 2 then
Glowyball.Color = Color3.new(0, 1, 0)
elseif ColorNumber == 3 then
Glowyball.Color = Color3.new(0, 0, 1)
end

Heliodex did a good job explaining how to simplify finding the instances, but you could take it a step further and remove the if-statements entirely by replacing them with arrays.
Typically, from a general programming standpoint, when you find that you have a lot of very similar if-statements, the solution is to use a switch-statement instead of if-elseif chains. Switch statements allow you to define a bunch of possible cases and when it executes, it will jump specifically to the case that matches. However, neither lua nor luau support switch statements, but we can get the benefit of switch-statements.
Since you are using random numbers already, you can pre-define your colors into an array and have the random number simply pick an index to use...
-- make some colors to pick from
local colors = {
Color3.new(1, 0, 0),
Color3.new(0, 1, 0),
Color3.new(0, 0, 1),
}
-- Set up the list of spawn locations
local locationGroup = game.ServerStorage.GlowyBallsSideA.GlowyBallGroup1
local spawnLocations = {
locationGroup.Glowyball1,
locationGroup.Glowyball2,
locationGroup.Glowyball3,
locationGroup.Glowyball4,
locationGroup.Glowyball5,
}
-- could this be simplified to ...?
-- local spawnLocations = locationGroup:GetChildren()
-- when someone clicks the button, spawn the part
script.Parent.ClickDetector.MouseClick:Connect(function()
-- choose a color and spawn location
local colorNumber = math.random(1, #colors)
local spawnNumber = math.random(1, #spawnLocations)
local clonePart = spawnLocations[spawnNumber]
local color = colors[colorNumber]
-- spawn it
local clone = clonePart:Clone()
clone.Color = color
clone.Parent = game.Workspace
clone.Anchored = false
end)
The advantage of this approach is that you can simply add more colors and spawn locations to those arrays, and the rest of your code will still work.

Related

How to get same rank for same scores in Redis' ZRANK?

If I have 5 members with scores as follows
a - 1
b - 2
c - 3
d - 3
e - 5
ZRANK of c returns 2, ZRANK of d returns 3
Is there a way to get same rank for same scores?
Example: ZRANK c = 2, d = 2, e = 3
If yes, then how to implement that in spring-data-redis?
Any real solution needs to fit the requirements, which are kind of missing in the original question. My 1st answer had assumed a small dataset, but this approach does not scale as dense ranking is done (e.g. via Lua) in O(N) at least.
So, assuming that there are a lot of users with scores, the direction that for_stack suggested is better, in which multiple data structures are combined. I believe this is the gist of his last remark.
To store users' scores you can use a Hash. While conceptually you can use a single key to store a Hash of all users scores, in practice you'd want to hash the Hash so it will scale. To keep this example simple, I'll ignore Hash scaling.
This is how you'd add (update) a user's score in Lua:
local hscores_key = KEYS[1]
local user = ARGV[1]
local increment = ARGV[2]
local new_score = redis.call('HINCRBY', hscores_key, user, increment)
Next, we want to track the current count of users per discrete score value so we keep another hash for that:
local old_score = new_score - increment
local hcounts_key = KEYS[2]
local old_count = redis.call('HINCRBY', hcounts_key, old_score, -1)
local new_count = redis.call('HINCRBY', hcounts_key, new_score, 1)
Now, the last thing we need to maintain is the per score rank, with a sorted set. Every new score is added as a member in the zset, and scores that have no more users are removed:
local zdranks_key = KEYS[3]
if new_count == 1 then
redis.call('ZADD', zdranks_key, new_score, new_score)
end
if old_count == 0 then
redis.call('ZREM', zdranks_key, old_score)
end
This 3-piece-script's complexity is O(logN) due to the use of the Sorted Set, but note that N is the number of discrete score values, not the users in the system. Getting a user's dense ranking is done via another, shorter and simpler script:
local hscores_key = KEYS[1]
local zdranks_key = KEYS[2]
local user = ARGV[1]
local score = redis.call('HGET', hscores_key, user)
return redis.call('ZRANK', zdranks_key, score)
You can achieve the goal with two Sorted Set: one for member to score mapping, and one for score to rank mapping.
Add
Add items to member to score mapping: ZADD mem_2_score 1 a 2 b 3 c 3 d 5 e
Add the scores to score to rank mapping: ZADD score_2_rank 1 1 2 2 3 3 5 5
Search
Get score first: ZSCORE mem_2_score c, this should return the score, i.e. 3.
Get the rank for the score: ZRANK score_2_rank 3, this should return the dense ranking, i.e. 2.
In order to run it atomically, wrap the Add, and Search operations into 2 Lua scripts.
Then there's this Pull Request - https://github.com/antirez/redis/pull/2011 - which is dead, but appears to make dense rankings on the fly. The original issue/feature request (https://github.com/antirez/redis/issues/943) got some interest so perhaps it is worth reviving it /cc #antirez :)
The rank is unique in a sorted set, and elements with the same score are ordered (ranked) lexically.
There is no Redis command that does this "dense ranking"
You could, however, use a Lua script that fetches a range from a sorted set, and reduces it to your requested form. This could work on small data sets, but you'd have to devise something more complex for to scale.
unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
zskiplistNode *x;
unsigned long rank = 0;
int i;
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
sdscmp(x->level[i].forward->ele,ele) <= 0))) {
rank += x->level[i].span;
x = x->level[i].forward;
}
/* x might be equal to zsl->header, so test if obj is non-NULL */
if (x->ele && x->score == score && sdscmp(x->ele,ele) == 0) {
return rank;
}
}
return 0;
}
https://github.com/redis/redis/blob/b375f5919ea7458ecf453cbe58f05a6085a954f0/src/t_zset.c#L475
This is the piece of code redis uses to compute the rank in sorted sets. Right now ,it just gives rank based on the position in the Skiplist (which is sorted based on scores).
What does the skiplistnode variable "span" mean in redis.h? (what is span ?)

Fortran read() reads last line twice?

So, suppose I'm trying to read in a file the length of which I don't know before hand. We can use iostat and a while loop to break when we need to, but I'm having an issue with this. Namely, the code I've written reads the last line twice. I'm sure there is an obvious solution, but I can't seem to figure it out. I don't really understand how either the read() or iostat functions work entirely (I'm pretty new at fortran), but I can't glean much from documentation so I'm hoping someone here can help.
Here is the (relevant bit of) code I've written:
filename = 'test.txt'
iostat_1 = 0
iostat_2 = 0
open(newunit = lun, file = filename, status = 'old', iostat = iostat_1)
if (iostat_1 == 0) then
do while(iostat_2 == 0)
if(iostat_2 == 0) then
read(lun,*,iostat = iostat_2) dum, real_1,real_2,int_1
print *, dum, real_1,real_2,int_1
endif
enddo
endif
So, supposing my input file is
1 1.0 1.0 1
2 2.0 2.0 1
3 3.0 3.0 1
4 4.0 4.0 4
Then the output to the terminal from the print statement will be
1 1.0 1.0 1
2 2.0 2.0 1
3 3.0 3.0 1
4 4.0 4.0 4
4 4.0 4.0 4
So keep in mind the following: The main purpose here is to be able to read in a file with an arbitrary number of lines. I'm not interested in a solution involving reading the number of lines first.
Thanks for the help!
UPDATE Okay I just solved the problem. That being said, I'm wondering if there is a solution less clumsy than mine. Here is what I did to fix the issue
! Body of ReInsert
filename = 'rpriov3.dat'
iostat_1 = 0
iostat_2 = 0
open(newunit = lun, file = filename, status = 'old', iostat = iostat_1)
if (iostat_1 == 0) then
do while(iostat_2 == 0)
if(iostat_2 == 0) then
read(lun,*,iostat = iostat_2) dum, real_1,real_2,int_1
if(iostat_2 == 0) then !<---- Added this nested if statement
print *, dum, real_1,real_2,int_1
endif
print *, iostat_2
endif
enddo
endif
As you found out, when you set an iostat parameter, the read command doesn't overwrite the variables it asks for.
Your solution is, as you already noticed, somewhat convoluted.
Firstly:
do while (condition)
if (condition) then
...
end if
end do
In this case, the inner if statement is complete surplus. The loop doesn't run unless condition is true, so unless the evaluation of condition itself doesn't change the result 1), the if clause will always be executed.
The second thing I'd look at is: What should happen if the open fails? In most cases, I want to print an error and quit:
open(..., iostat=ios)
if (ios /= 0) then
print*, "Error opening file"
STOP 1
end if
do while (...)
...
end do
Even if you don't want to exit the program in case of an error in open, there are usually ways to make the code more readable than eternal nesting. For example, you could ask the user for a filename again and again (in its own loop) for a file name, until either the file opens, or the user enters some quit message.
ios = 1
do while (ios /= 0)
write(*, *, advance='no') "Enter filename (or 'quit') :"
read(*, *) filename
if ( trim(filename) == "quit" ) STOP
open(newunit=lun, file=filename, ..., iostat=ios)
end do
Finally there's the most inner if block. Since you want to exit the loop anyway when you reach an error, you can use the exit statement inside a loop to exit it immediately without executing the rest of the loop block:
do
read(..., iostat=ios) ...
if (ios /= 0) exit
print *, ....
end do
This is an infinite loop with an explicit exit as soon as it encounters a read error (usually, but not necessarily an EOF). Since the print statement is after the exit, it won't be executed in case of such an error.
1) What I mean by that is something like this C snippet i++ < 10, which both tests i against 10 and increments it.

What is the most elegant way to pick a random value from a set defined in NS_OPTION in objective-c?

I have an NS_OPTION that I'm defining as such :
typedef NS_OPTIONS(NSInteger, PermittedSize) {
SmallSize = 1 << 0,
MediumSize = 1 << 1,
LargeSize = 1 << 2
};
And later I set the values I need :
PermittedSize size = SmallSize | MediumSize;
I'm using it to randomly generate an various objects of small and medium sizes(duh) for a particular level of a game.
What is the best way to go about selecting which size of an object to generate? Meaning, I'd like to choose randomly for each object I'm generating whether it will be one of the 2 options allowed (small and medium in this case). Normally I would use an arc4random function with the range of numbers I need - but in this case, how can it be done with bits? (and then mapped back to the values of the PermittedSize type?
Use the result from arc4random to determine the amount of bit shifting you want to do. Something like this:
int bitShiftAmount = arc4random_uniform(numberOfPermittedSizes);
PermittedSize size = 1 << bitShiftAmount;
You are still working with integers. SmallSize is 1. MediumSize is 2. And LargeSize is 4.
So pick a random number from 1 to 3. 1 is small, 2 is medium, 3 is both.
Once you have a random number, assign it.
NSInteger val = arc4random_uniform(3) + 1; // give 1-3
PermittedSize size = (PermittedSize)val;

Error Handling with Live Data Matlab

I am using a live data API that is returning the next arriving trains. I plan on giving the user the next 5 trains arriving. If there are less than 5 trains arriving, how you handle that? I am having trouble thinking of a way, I was thinking a way with if statements but don't know how I would set them up.
time1Depart = dataReturnedFromLiveAPI{1,1}.orig_departure_time;
time2Depart = dataReturnedFromLiveAPI{1,2}.orig_departure_time;
time3Depart = dataReturnedFromLiveAPI{1,3}.orig_departure_time;
time4Depart = dataReturnedFromLiveAPI{1,4}.orig_departure_time;
time5Depart = dataReturnedFromLiveAPI{1,5}.orig_departure_time;
time1Arrival = dataReturnedFromLiveAPI{1,1}.orig_arrival_time;
time2Arrival = dataReturnedFromLiveAPI{1,2}.orig_arrival_time;
time3Arrival = dataReturnedFromLiveAPI{1,3}.orig_arrival_time;
time4Arrival = dataReturnedFromLiveAPI{1,4}.orig_arrival_time;
time5Arrival = dataReturnedFromLiveAPI{1,5}.orig_arrival_time;
The code right now uses a matrix that goes from 1:numoftrains but I am using just the first five.
It's a bad practice to assign individual value to a separate variable. Better if you pass all related values to a vector or cell array depending on class of orig_departure_time and orig_arrival_time.
It looks like dataReturnedFromLiveAPI is a cell array of structures. Then you can do:
timeDepart = cellfun(#(x), x.orig_departure_time, ...
dataReturnedFromLiveAPI(1,1:min(5,size(dataReturnedFromLiveAPI,2))), ...
'UniformOutput',0 );
timeArrival = cellfun(#(x), x.orig_arrival_time, ...
dataReturnedFromLiveAPI(1,1:min(5,size(dataReturnedFromLiveAPI,2))), ...
'UniformOutput',0 );
Then you how to access the values one by one as
time1Depart = timeDepart{1};
If orig_departure_time and orig_arrival_time are numeric scalars, you can use ...'UniformOutput',1.... You will get output as a vector and can get single values with timeDepart(1).

Name a Table with a Variable in LUA (LÖVE Engine)?

Basically:
I am making a game in the LÖVE Engine where you click to create blocks
Every time you click, a block gets created at your Mouse X and Mouse Y
But, I can only get one block to appear, because I have to name that block (or table) 'object1'
Is there any way to create table after table with increasing values? (like object1{}, object2{}, object3{}, etc... But within the main table, 'created_objects')
But only when clicked, which I suppose rules out the looping part (but if it doesn't please tell me)
Here's my code so far, but it doesn't compile.
function object_create(x, y, id) **--Agruments telling the function where the box should spawn and what the ID of the box is (variable 'obj_count' which increases every time a box is spawned)**
currobj = "obj" .. id **--Gives my currently created object a name**
crob.[currobj] = {} **--Is supposed to create a table for the current object, which holds all of its properties. **
crob.[currobj].body = love.physics.newBody(world, x, y, "dynamic")
crob.[currobj].shape = love.physics.newRectangleShape(30, 30)
crob.[currobj].fixture = love.physics.newFixture(crob.[currobj].body, crob.[currobj].shape, 1) **--The properties**
crob.[currobj].fixture:setRestitution(0.3)
But what should I replace [currobj] with?
Solved
Found what I was looking for. Here's the code if people are wondering:
function block_create(x, y, id) --(the mouse x and y, and the variable that increases)
blocks[id] = {}
blocks[id][1] = love.physics.newBody(world, x, y, "dynamic")
blocks[id][2] = love.physics.newRectangleShape(45, 45)
blocks[id][3] = love.physics.newFixture(blocks[id][1], blocks[id][2])
blocks[id][3]:setRestitution(0.2)
blocks[id][4] = math.random(0, 255) --The Color
blocks[id][5] = math.random(0, 255)
blocks[id][6] = math.random(0, 255)
blockcount = blockcount + 1
i would probably do something like this.
local blocks = {} -- equivalent of your crob table
function create_block(x, y)
local block = funcToCreateBlock() -- whatever code to create the block
table.insert(blocks, block)
return block
end
if you wanted to get a reference to the block you just created with the function, just capture it.
-- gives you the new block, automatically adds it to the list of created blocks
local new_block = create_block(0, 10)
that sticks block objects in your block table and automatically gives each one a numeric index in the table. so if you called create_block() 3 times for 3 different mouse clicks, the blocks table would look like this:
blocks = {
[1] = blockobj1,
[2] = blockobj2,
[3] = blockobj3
}
you could get the second block obj from the blocks table by doing
local block2 = blocks[2]
or you could loop over all the blocks in the table using pairs or ipairs
for idx, block in pairs(blocks) do
-- do something with each block
end
sorry if this doesn't exactly answer your question. but from what you wrote, there didn't seem to be a real reason why you'd need to name the blocks anything specific in the crob table.
If you want those tables to be global, then you can do something like:
sNameOfTable = "NAME"
_G[sNameOfTable] = {1,2}
and then you will have a table variable NAME as depicted here (Codepad).
Otherwise, if you want it to be a child to some other table, something like this would also do:
tTbl = {}
for i = 1, 20 do
local sName = string.format( "NAME%02d", i )
tTbl[sName] = {1,2}
end
for i, v in pairs(tTbl) do
print( i, v )
end
Don't worry about the unsorted output here(Codepad). Lua tables with indexes need not be sorted to be used.