In my software, I need to decide the version of a feature based on 2 parameters. Eg.
Render version 1 -> if (param1 && param2) == true;
Render version 2 -> if (!param1 && !param2) == true;
Render version 3 -> if only param1 == true;
Render version 4 -> if only param2 == true;
So, to meet this requirement, I wrote a code which looks like this -
if(param1 && param2) //both are true {
version = 1;
}
else if(!param1 && !param2) //both are false {
version = 2;
}
else if(!param2) //Means param1 is true {
version = 3;
}
else { //Means param2 is true
version = 4;
}
There are definitely multiple ways to code this but I finalised this approach after trying out different approaches because this is the most readable code I could come up with.
But this piece of code is definitely not scalable because -
Let say tomorrow we want to introduce new param called param3. Then
the no. of checks will increase because of multiple possible
combinations.
For this software, I am pretty much sure that we
will have to accommodate new parameters in future.
Can there be any scalable & readable way to code these requirements?
EDIT:
For a scalable solution define the versions for each parameter combination through a Map:
Map<List<Boolean>, Integer> paramsToVersion = Map.of(
List.of(true, true), 1,
List.of(false, false), 2,
List.of(true, false), 3,
List.of(false, true), 4);
Now finding the right version is a simple map lookup:
version = paramsToVersion.get(List.of(param1, param2));
The way I initialized the map works since Java 9. In older Java versions it’s a little more wordy, but probably still worth doing. Even in Java 9 you need to use Map.ofEntries if you have 4 or more parameters (for 16 combinations), which is a little more wordy too.
Original answer:
My taste would be for nested if/else statements and only testing each parameter once:
if (param1) {
if (param2) {
version = 1;
} else {
version = 3;
}
} else {
if (param2) {
version = 4;
} else {
version = 2;
}
}
But it scales poorly to many parameters.
If you have to enumerate all the possible combinations of Booleans, it's often simplest to convert them into a number:
// param1: F T F T
// param2; F F T T
static final int[] VERSIONS = new int[]{2, 3, 4, 1};
...
version = VERSIONS[(param1 ? 1:0) + (param2 ? 2:0)];
I doubt that there is a way that would be more compact, readable and scalable at the same time.
You express the conditions as minimized expressions, which are compact and may have meaning (in particular, the irrelevant variables don't clutter them). But there is no systematism that you could exploit.
A quite systematic alternative could be truth tables, i.e. the explicit expansion of all combinations and the associated truth value (or version number), which can be very efficient in terms of running-time. But these have a size exponential in the number of variables and are not especially readable.
I am afraid there is no free lunch. Your current solution is excellent.
If you are after efficiency (i.e. avoiding the need to evaluate all expressions sequentially), then you can think of the truth table approach, but in the following way:
declare an array of version numbers, with 2^n entries;
use the code just like you wrote to initialize all table entries; to achieve that, enumerate all integers in [0, 2^n) and use their binary representation;
now for a query, form an integer index from the n input booleans and lookup the array.
Using the answer by Olevv, the table would be [2, 4, 3, 1]. A lookup would be like (false, true) => T[01b] = 4.
What matters is that the original set of expressions is still there in the code, for human reading. You can use it in an initialization function that will fill the array at run-time, and you can also use it to hard-code the table (and leave the code in comments; even better, leave the code that generates the hard-coded table).
Your combinations of parameters is nothing more than a binary number (like 01100) where the 0 indicates a false and the 1 a true.
So your version can be easily calculated by using all the combinations of ones and zeroes. Possible combinations with 2 input parameters are:
11 -> both are true
10 -> first is true, second is false
01 -> first is false, second is true
00 -> both are false
So with this knowledge I've come up with a quite scalable solution using a "bit mask" (nothing more than a number) and "bit operations":
public static int getVersion(boolean... params) {
int length = params.length;
int mask = (1 << length) - 1;
for(int i = 0; i < length; i++) {
if(!params[i]) {
mask &= ~(1 << length - i - 1);
}
}
return mask + 1;
}
The most interesting line is probably this:
mask &= ~(1 << length - i - 1);
It does many things at once, I split it up. The part length - i - 1 calculates the position of the "bit" inside the bit mask from the right (0 based, like in arrays).
The next part: 1 << (length - i - 1) shifts the number 1 the amount of positions to the left. So lets say we have a position of 3, then the result of the operation 1 << 2 (2 is the third position) would be a binary number of the value 100.
The ~ sign is a binary inverse, so all the bits are inverted, all 0 are turned to 1 and all 1 are turned to 0. With the previous example the inverse of 100 would be 011.
The last part: mask &= n is the same as mask = mask & n where n is the previously computed value 011. This is nothing more than a binary AND, so all the same bits which are in mask and in n are kept, where as all others are discarded.
All in all, does this single line nothing more than remove the "bit" at a given position of the mask if the input parameter is false.
If the version numbers are not sequential from 1 to 4 then a version lookup table, like this one may help you.
The whole code would need just a single adjustment in the last line:
return VERSIONS[mask];
Where your VERSIONS array consists of all the versions in order, but reversed. (index 0 of VERSIONS is where both parameters are false)
I would have just gone with:
if (param1) {
if (param2) {
} else {
}
} else {
if (param2) {
} else {
}
}
Kind of repetitive, but each condition is evaluated only once, and you can easily find the code that executes for any particular combination. Adding a 3rd parameter will, of course, double the code. But if there are any invalid combinations, you can leave those out which shortens the code. Or, if you want to throw an exception for them, it becomes fairly easy to see which combination you have missed. When the IF's become too long, you can bring the actual code out in methods:
if (param1) {
if (param2) {
method_12();
} else {
method_1();
}
} else {
if (param2) {
method_2();
} else {
method_none();
}
}
Thus your whole switching logic takes up a function of itself and the actual code for any combination is in another method. When you need to work with the code for a particular combination, you just look up the appropriate method. The big IF maze is then rarely looked at, and when it is, it contains only the IFs themselves and nothing else potentially distracting.
Related
I have a simple logic to implement. But not sure if there is a better way to design it, other than simple if-else or switch statements.
There are 4 permissions (consider them boolean variable), which can be true or false. Based on various conditions (permutations of those permissions), i need to return list of String values that need to be displayed on UI for a dropdown field.
So its like this for now -
if(!permission1 && !permission2){return list_of_strings_1;}
else if (permission1 && permission2 && !permission3){return list_of_strings_2;}
and so on. Some of them are just if statements. So multiple conditions maybe true and we have to collect all the list of strings and display them.
Those if elses go on for quite some time (about 100 lines). Each will return different list of strings. Most of it is NOT likely to change in future. So maybe too deep of a design maybe an overkill.
But just wondering how experts would refactor this code (or if they will even refactor it or not). Maybe sticking to switch/if-else is ok?
I don't understand how four flags gives you 100 lines of code. This can be done with a map of 16 entries (or less, if some combinations are invalid and can be mapped to a default). If the string representation is truly a list of strings, one for each possible permission, the solution is even more compact.
The key is an object representing the combination of permissions, and the value is the string representation for that combination. You could create a custom type for the key, but in this example, I'm just using four bits of an integer, where each bit indicates whether the permission is granted or not:
private static final int P1 = 1 << 0, P2 = 1 << 1, P3 = 1 << 2, P4 = 1 << 3;
private static final Map<Integer, String> permissionsToString = Map.ofEntries(
Map.entry( 0, "No permissions granted."),
Map.entry( 1, "Permissions 2-3 revoked."),
Map.entry( 2, "Permission 2 granted."),
...
Map.entry(14, "Permission 1 revoked"),
Map.entry(15, "Superuser"));
public static String toString(boolean p1, boolean p2, boolean p3, boolean p4) {
int key = (p1 ? 0 : 1) << 0
| (p2 ? 0 : 1) << 1
| (p3 ? 0 : 1) << 2
| (p4 ? 0 : 1) << 3;
return permissionsToString.get(key);
}
If you don't understand bits, you can use an EnumSet or define your own value object to represent the key at a higher level. The idea is the same: map all possible combinations (24 = 16) to their corresponding label.
I have an if statement with many conditions (have to check for 10 or 15 constants to see if any of them are present.)
Instead of writing something like:
if (x == 12 || x == 16 || x == 19 || ...)
is there any way to format it like
if x is [12, 16, 19]?
Just wondering if there is an easier way to code this, any help appreciated.
The answers have been very helpful, but I was asked to add more detail by a few people, so I will do that to satiate their curiosity. I was making a date validation class that needed to make sure days were not > 30 in the months that have only 30 days (of which there are 4, I think) and I was writing an if statement to check things like this:
if (day > 30 && (month == 4 || month == 6 || month == 9 || month == 11))
I was just wondering if there was a faster way to code things like that - many of the answers below have helped.
I use this kind of pattern often. It's very compact:
// Define a constant in your class. Use a HashSet for performance
private static final Set<Integer> values = new HashSet<Integer>(Arrays.asList(12, 16, 19));
// In your method:
if (values.contains(x)) {
...
}
A HashSet is used here to give good look-up performance - even very large hash sets are able to execute contains() extremely quickly.
If performance is not important, you can code the gist of it into one line:
if (Arrays.asList(12, 16, 19).contains(x))
but know that it will create a new ArrayList every time it executes.
Do you want to switch to this??
switch(x) {
case 12:
case 16:
case 19:
//Do something
break;
default:
//Do nothing or something else..
break;
}
If the set of possibilities is "compact" (i.e. largest-value - smallest-value is, say, less than 200) you might consider a lookup table. This would be especially useful if you had a structure like
if (x == 12 || x == 16 || x == 19 || ...)
else if (x==34 || x == 55 || ...)
else if (...)
Set up an array with values identifying the branch to be taken (1, 2, 3 in the example above) and then your tests become
switch(dispatchTable[x])
{
case 1:
...
break;
case 2:
...
break;
case 3:
...
break;
}
Whether or not this is appropriate depends on the semantics of the problem.
If an array isn't appropriate, you could use a Map<Integer,Integer>, or if you just want to test membership for a single statement, a Set<Integer> would do. That's a lot of firepower for a simple if statement, however, so without more context it's kind of hard to guide you in the right direction.
Use a collection of some sort - this will make the code more readable and hide away all those constants. A simple way would be with a list:
// Declared with constants
private static List<Integer> myConstants = new ArrayList<Integer>(){{
add(12);
add(16);
add(19);
}};
// Wherever you are checking for presence of the constant
if(myConstants.contains(x)){
// ETC
}
As Bohemian points out the list of constants can be static so it's accessible in more than one place.
For anyone interested, the list in my example is using double brace initialization. Since I ran into it recently I've found it nice for writing quick & dirty list initializations.
You could look for the presence of a map key or see if it's in a set.
Depending on what you're actually doing, though, you might be trying to solve the problem wrong :)
No you cannot do that in Java. you can however write a method as follows:
boolean isContains(int i, int ... numbers) {
// code to check if i is one of the numbers
for (int n : numbers) {
if (i == n) return true;
}
return false;
}
With Java 8, you could use a primitive stream:
if (IntStream.of(12, 16, 19).anyMatch(i -> i == x))
but this may have a slight overhead (or not), depending on the number of comparisons.
Here is another answer based on a comment above, but simpler:
List numbers= Arrays.asList(1,2,3,4,5);
if(numbers.contains(x)){
//
}
I have an if statement with many conditions (have to check for 10 or 15 constants to see if any of them are present.)
Instead of writing something like:
if (x == 12 || x == 16 || x == 19 || ...)
is there any way to format it like
if x is [12, 16, 19]?
Just wondering if there is an easier way to code this, any help appreciated.
The answers have been very helpful, but I was asked to add more detail by a few people, so I will do that to satiate their curiosity. I was making a date validation class that needed to make sure days were not > 30 in the months that have only 30 days (of which there are 4, I think) and I was writing an if statement to check things like this:
if (day > 30 && (month == 4 || month == 6 || month == 9 || month == 11))
I was just wondering if there was a faster way to code things like that - many of the answers below have helped.
I use this kind of pattern often. It's very compact:
Define a constant in your class:
private static final Set<Integer> VALUES = Set.of(12, 16, 19);
// Pre Java 9 use: VALUES = new HashSet<Integer>(Arrays.asList(12, 16, 19));
In your method:
if (VALUES.contains(x)) {
...
}
Set.of() returns a HashSet, which performs very well even for very large sets.
If performance is not important, you can code the gist of it into one line for less code footprint:
if (Set.of(12, 16, 19).contains(x))
but know that it will create a new Set every time it executes.
Do you want to switch to this??
switch(x) {
case 12:
case 16:
case 19:
//Do something
break;
default:
//Do nothing or something else..
break;
}
If the set of possibilities is "compact" (i.e. largest-value - smallest-value is, say, less than 200) you might consider a lookup table. This would be especially useful if you had a structure like
if (x == 12 || x == 16 || x == 19 || ...)
else if (x==34 || x == 55 || ...)
else if (...)
Set up an array with values identifying the branch to be taken (1, 2, 3 in the example above) and then your tests become
switch(dispatchTable[x])
{
case 1:
...
break;
case 2:
...
break;
case 3:
...
break;
}
Whether or not this is appropriate depends on the semantics of the problem.
If an array isn't appropriate, you could use a Map<Integer,Integer>, or if you just want to test membership for a single statement, a Set<Integer> would do. That's a lot of firepower for a simple if statement, however, so without more context it's kind of hard to guide you in the right direction.
Use a collection of some sort - this will make the code more readable and hide away all those constants. A simple way would be with a list:
// Declared with constants
private static List<Integer> myConstants = new ArrayList<Integer>(){{
add(12);
add(16);
add(19);
}};
// Wherever you are checking for presence of the constant
if(myConstants.contains(x)){
// ETC
}
As Bohemian points out the list of constants can be static so it's accessible in more than one place.
For anyone interested, the list in my example is using double brace initialization. Since I ran into it recently I've found it nice for writing quick & dirty list initializations.
You could look for the presence of a map key or see if it's in a set.
Depending on what you're actually doing, though, you might be trying to solve the problem wrong :)
No you cannot do that in Java. you can however write a method as follows:
boolean isContains(int i, int ... numbers) {
// code to check if i is one of the numbers
for (int n : numbers) {
if (i == n) return true;
}
return false;
}
With Java 8, you could use a primitive stream:
if (IntStream.of(12, 16, 19).anyMatch(i -> i == x))
but this may have a slight overhead (or not), depending on the number of comparisons.
Here is another answer based on a comment above, but simpler:
List numbers= Arrays.asList(1,2,3,4,5);
if(numbers.contains(x)){
//
}
I have file that is CIDR format like this 192.168.1.0/24 and it is converted into this two column strucutre
3232236030 3232235777
Each string IP address convertion happens with this code:
String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);
Inet4Address a = (Inet4Address) InetAddress.getByName(utils.getInfo().getHighAddress());
long high = bytesToLong(a.getAddress());
Inet4Address b = (Inet4Address) InetAddress.getByName(utils.getInfo().getLowAddress());
long low = bytesToLong(b.getAddress());
private static long bytesToLong(byte[] address) {
long ipnum = 0;
for (int i = 0; i < 4; ++i) {
long y = address[i];
if (y < 0) {
y += 256;
}
ipnum += y << ((3 - i) * 8);
}
return ipnum;
}
Consider that there are over 5 million entries of (low high : 3232236030 3232235777).
Also there will be intersects so the IP can originate from multiple ranges. Just the first one is more than OK.
The data is read only.
What would be the fastest way to find the range the ipToBefiltered belongs to? The structure will be entirely in memory so no database lookups.
UPDATE:
I found this Peerblock project (it has over million download so I'm thinking it must have some fast algorithms):
http://code.google.com/p/peerblock/source/browse/trunk/src/pbfilter/filter_wfp.c
Does anyone know what technique is the project using for creating the list of ranges and than searching them?
When it comes down to it I just need to know if the IP is present in any of the 5M ranges.
I would consider an n-ary tree, where n=256, and work from the dotted address rather than the converted integer.
The top level would be an array of 256 objects. A null entry means "No" there is no range that contains the address, so given your example 192.168.1.0/24 array[192] would contain an object, but array[100] might be null because no range was defined for any 100.x.x.x/n
The stored object contains a (reference to) another array[256] and a range specifier, only one of the two would be set, so 192.0.0.0/8 would end up with a range specifier indicating all addresses within that range are to be filtered. This would allow for things like 192.255.0.0/10 where the first 10 bits of the address are significant 1100 0000 11xx xxxx -- otherwise you need to check the next octet in the 2nd level array.
Initially coalescing overlapping ranges, if any, into larger ranges... e.g. 3 .. 10 and 7 .. 16 becomes 3 .. 16 ... allows this, since you don't need to associate a given IP with which range defined it.
This should require no more than 8 comparisons. Each octet is initially used directly as an index, followed by a compare for null, a compare for terminal-node (is it a range or a pointer to the next tree level)
Worst case memory consumption is theoretically 4 GB (256 ^ 4) if every IP address was in a filtering range, but of course that would coalesce into a single range so actually would be only 1 range object. A more realistic worst-case would probably be more like (256 ^ 3) or 16.7 MB. Real world usage would probably have the majority of array[256] nodes at each level empty.
This is essentially similar to Huffman / prefix coding. The shortest distinct prefix can terminate as soon as an answer (a range) is found, so often you would have averages of < 4 compares.
I would use a sorted array of int (the base address) and another array the same size (the end address). This would use 5M * 8 = 40 MB. The first IP is the base and the second IP is the last address in range. You would need to remove intersections.
To find if an address is filtered to a binary search O(log N) and if not an exact match, check it is less than (or equal to) the upper bound.
I found this binary chop algorithm in Vuze (aka azureus) project:
public IpRange isInRange(long address_long) {
checkRebuild();
if (mergedRanges.length == 0) {
return (null);
}
// assisted binary chop
int bottom = 0;
int top = mergedRanges.length - 1;
int current = -1;
while (top >= 0 && bottom < mergedRanges.length && bottom <= top) {
current = (bottom + top) / 2;
IpRange e = mergedRanges[current];
long this_start = e.getStartIpLong();
long this_end = e.getMergedEndLong();
if (address_long == this_start) {
break;
} else if (address_long > this_start) {
if (address_long <= this_end) {
break;
}
// lies to the right of this entry
bottom = current + 1;
} else if (address_long == this_end) {
break;
} else {
// < this_end
if (address_long >= this_start) {
break;
}
top = current - 1;
}
}
if (top >= 0 && bottom < mergedRanges.length && bottom <= top) {
IpRange e = mergedRanges[current];
if (address_long <= e.getEndIpLong()) {
return (e);
}
IpRange[] merged = e.getMergedEntries();
if (merged == null) {
//inconsistent merged details - no entries
return (null);
}
for (IpRange me : merged) {
if (me.getStartIpLong() <= address_long && me.getEndIpLong() >= address_long) {
return (me);
}
}
}
return (null);
}
Seems to be performing pretty well. If you know about something faster please let me know.
If you just have a CIDR address (or a list of them) and you want to check if some ipAddress is in the range of that CIDR (or list of CIDR's), just define a Set of SubnetUtils objects.
Unless you are filtering a very large N addresses, this is all String comparison and will execute extremely fast. You dont need to build a binary tree based on the higher/lower order bits and all of that complicated Jazz.
String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);
//...
//for each subnet, create a SubnetUtils object
Set<SubnetUtils> subnets = getAllSubnets();
//...
Use a Guava Predicate to filter the ipAddresses that are not in the range of your set of subnets:
Set<String> ipAddresses = getIpAddressesToFilter();
Set<String> ipAddressesInRange =
Sets.filter(ipAddresses, filterIpsBySubnet(subnets))
Predicate<String> filterIpsBySubnet(final Set<SubnetUtils> subnets){
return new Predicate<String>() {
#Override
public boolean apply(String ipAddress) {
for (SubnetUtils subnet : subnets) {
if (subnet.getInfo().isInRange(ipAddress)) {
return true;
}
}
return false;
}
};
}
Now if the IP is in any of the Subnets, you have a nice simple filter and you dont have to build a data structure that you will have to unit test. If this is not performant enough, then go to optimization. Don't prematurely optimize :)
Here is the beginning of an answer, I'll come back when I get more freetime
Setup:
Sort the ranges by the starting number.
Since these are IP Addresses, I assume that none of the ranges overlap. If there are overlaps, you should probably run the list merging ranges and trimming unnecessary ranges (ex. if you have a range 1 - 10, you can trim the range 5 - 7).
To merge or trim do this (assume range a immediately precedes range b):
If b.end < a.end then range b is a subset of range a and you can remove range b.
If b.start < b.end and b.end > a.end then you can merge range a and b. Set a.end = b.end then remove range b.
I am converting a some C++ to java and have a small bit that I am unsure about
first question is what is tested for in the line
if (ampconst[i][0] || ampconst[i][1])
this in an example to the data in the array.
static short ampconst[NUT_SERIES][2] = {
{0,0},
{0,0},
{46,-24}
};
and my second question is that the ampsecul array is far shorter than NUT_SERIES
so I am getting array out of bounds exceptions, the array terminates like so
static long ampsecul[][5] = {
{0 ,-171996 ,-1742 ,92025 ,89},
{1 ,2062 ,2 ,-895 ,5},
{8 ,-13187 ,-16 ,5736 ,-31},
{9 ,1426 ,-34 ,54 ,-1},
{10 ,-517 ,12 ,224 ,-6},
{11 ,217 ,-5 ,-95 ,3},
{12 ,129 ,1 ,-70 ,0},
{15 ,17 ,-1 ,0 ,0},
{17 ,-16 ,1 ,7 ,0},
{30 ,-2274 ,-2 ,977 ,-5},
{31 ,712 ,1 ,-7 ,0},
{32 ,-386 ,-4 ,200 ,0},
{33 ,-301 ,0 ,129 ,-1},
{37 ,63 ,1 ,-33 ,0},
{38 ,-58 ,-1 ,32 ,0},
/* termination */ { -1, }
};
so how could this be handled in java and what would the values be at these lines
when the array is out of bounds or how would C++ handle this.
ampsin = ampsecul[isecul][1] + ampsecul[isecul][2] * T10;
ampcos = ampsecul[isecul][3] + ampsecul[isecul][4] * T10;
thanks in advance for any advice.
This is the whole for loop too see the code in context.
for (i = isecul = 0; i < NUT_SERIES ; ++i) {
double arg = 0., ampsin, ampcos;
short j;
if (ampconst[i][0] || ampconst[i][1]) {
/* take non-secular terms from simple array */
ampsin = ampconst[i][0];
ampcos = ampconst[i][1];
} else {
/* secular terms from different array */
ampsin = ampsecul[isecul][1] + ampsecul[isecul][2] * T10;
ampcos = ampsecul[isecul][3] + ampsecul[isecul][4] * T10;
++isecul;
}
for (j = 0; j < 5; ++j)
arg += delcache[j][NUT_MAXMUL + multarg[i][j]];
if (fabs(ampsin) >= prec)
lastdpsi += ampsin * sin(arg);
if (fabs(ampcos) >= prec)
lastdeps += ampcos * cos(arg);
}
if (ampconst[i][0] || ampconst[i][1])
tests whether the first/second column in ampconst[i] contain non-zero (it is an early-out optimization: if both the constants are 0 then the calculation can be skipped)
Edit I just found (google!) that this is a nutation calculation that has been adopted in quite a few places, but seems to be originally from a libastro.
hg clone https://bitbucket.org/brandon/pyephem
As far as the isecul index is concerned: apparently isecul should never grow to >= 15 (note that i is the loop variable, not isecul, isecul is incremented conditionally).
However, seeing the 'terminator' (-1) value, I'd really expect a check some like
if (ampsecul[isecul][0] == -1)
isecul = 0; // ? just guessing :)
or
if (ampsecul[isecul][0] == -1)
break;
Also, I get the impression that the first column of ampsecul is a range-based division, so somehow, there would be a binarysearch for the matching slot into ampsecul, not direct indexing (i.e. isecul=4 would select index 2 (2..8) not 4)
Are you sure you are getting the source code correctly? I looks very much like there are some custom indexers (operators[](...)) that you misssed out on? This would probably be about the same class/function that contains the terminator check like shown above.
Edit from the linked source I get the impression that the code is very much intended as is, and hence isecul should simply not be growing >= 15
That first if statement is testing the array entries for zero/non-zero. In C/C++ a boolean is simply an int that is used in a special way such that zero is false and non-zero is true.
As for your second array question I haven't grocked it yet. But understand that C/C++ does no array bounds checking (other than what may accidentally occur if you touch an undefined storage page), so unless there's an egregious error in the C++ code there must be something that limits references to the valid bounds of the array.