Design ideas needed for bunch of conditional if-else - java

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.

Related

How to code these conditional statements in more elegant & scalable manner

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.

Listing all the Games

Following a question here OP is interested in listing all unique 2x2 games. Games here are game theory games in which there with two players and two strategies each. Hence, there are four possible outcomes (see diagram). These outcomes comes with 'payoffs' for each players. Payoff 'pairs' are two payoffs for each player from some combinations of strategies. Payoffs are given in integers and cannot exceed 4.
For instance, consider the following example of a 2x2 game (with a payoff pair is written in the brackets, and P1 and P2 denote player 1 and 2 respectively):
P2
Right Left
Up (2,2) (3,4)
P1
Down (1,1) (4,3)
The payoffs here take the values [ (2,2),(3,4) | (1,1),(4,3) ].
Now, clearly many other games (i.e. unique payoff matrices) are possible. If payoffs for each players is given by 1,2,3,4 (which we can permute in 4!=24 ways) then 24*24 games are possible. OP was interested with listing all these games.
Here comes the subtle part: two unique payoff matrices may nevertheless represent games if one can be obtained from the other by
i) exchanging columns (i.e. relabel Player A's strategies)
ii) exchanging rows (i.e. relabel Player B's strategies)
iii) Exchange the players (i.e. exchanging the payoff pairs and
mirroring the matrix along the first diagonal)
OP posted the following code that correctly lists all 78 possible games in which the payoffs for each can be (1,2,3,4).
I am interested in changing the code so that the program lists all unique games where the possible payoffs are different: i.e. (1,2,3,3) for player 1 and (1,2,3,4) for player 2. Here, there would be 4!/2! ways of permuting (1,2,3,3) and therefore fewer games.
#!/usr/bin/groovy
// Payoff Tuple (a,b) found in game matrix position.
// The Tuple is immutable, if we need to change it, we create a new one.
// "equals()" checks for equality against another Tuple instance.
// "hashCode()" is needed for insertion/retrievel of a Tuple instance into/from
// a "Map" (in this case, the hashCode() actually a one-to-one mapping to the integers.)
class Tuple {
final int a,b
Tuple(int a,int b) {
assert 1 <= a && a <= 4
assert 1 <= b && b <= 4
this.a = a
this.b = b
}
#!/usr/bin/groovy
// Payoff Tuple (a,b) found in game matrix position.
// The Tuple is immutable, if we need to change it, we create a new one.
// "equals()" checks for equality against another Tuple instance.
// "hashCode()" is needed for insertion/retrievel of a Tuple instance into/from
// a "Map" (in this case, the hashCode() actually a one-to-one mapping to the integers.)
class Tuple {
final int a,b
Tuple(int a,int b) {
assert 1 <= a && a <= 4
assert 1 <= b && b <= 4
this.a = a
this.b = b
}
boolean equals(def o) {
if (!(o && o instanceof Tuple)) {
return false
}
return a == o.a && b == o.b
}
int hashCode() {
return (a-1) * 4 + (b-1)
}
String toString() {
return "($a,$b)"
}
Tuple flip() {
return new Tuple(b,a)
}
}
// "GameMatrix" is an immutable structure of 2 x 2 Tuples:
// top left, top right, bottom left, bottom right
// "equals()" checks for equality against another GameMatrix instance.
// "hashCode()" is needed for insertion/retrievel of a GameMatrix instance into/from
// a "Map" (in this case, the hashCode() actually a one-to-one mapping to the integers)
class GameMatrix {
final Tuple tl, tr, bl, br
GameMatrix(Tuple tl,tr,bl,br) {
assert tl && tr && bl && br
this.tl = tl; this.tr = tr
this.bl = bl; this.br = br
}
GameMatrix colExchange() {
return new GameMatrix(tr,tl,br,bl)
}
GameMatrix rowExchange() {
return new GameMatrix(bl,br,tl,tr)
}
GameMatrix playerExchange() {
return new GameMatrix(tl.flip(),bl.flip(),tr.flip(),br.flip())
}
GameMatrix mirror() {
// columnEchange followed by rowExchange
return new GameMatrix(br,bl,tr,tl)
}
String toString() {
return "[ ${tl},${tr} | ${bl},${br} ]"
}
boolean equals(def o) {
if (!(o && o instanceof GameMatrix)) {
return false
}
return tl == o.tl && tr == o.tr && bl == o.bl && br == o.br
}
int hashCode() {
return (( tl.hashCode() * 16 + tr.hashCode() ) * 16 + bl.hashCode() ) * 16 + br.hashCode()
}
}
// Check whether a GameMatrix can be mapped to a member of the "canonicals", the set of
// equivalence class representatives, using a reduced set of transformations. Technically,
// "canonicals" is a "Map" because we want to not only ask the membership question, but
// also obtain the canonical member, which is easily done using a Map.
// The method returns the array [ canonical member, string describing the operation chain ]
// if found, [ null, null ] otherwise.
static dupCheck(GameMatrix gm, Map canonicals) {
// Applying only one of rowExchange, colExchange, mirror will
// never generate a member of "canonicals" as all of these have player A payoff 4
// at topleft, and so does gm
def q = gm.playerExchange()
def chain = "player"
if (q.tl.a == 4) {
}
else if (q.tr.a == 4) {
q = q.colExchange(); chain = "column ∘ ${chain}"
}
else if (q.bl.a == 4) {
q = q.rowExchange(); chain = "row ∘ ${chain}"
}
else if (q.br.a == 4) {
q = q.mirror(); chain = "mirror ∘ ${chain}"
}
else {
assert false : "Can't happen"
}
assert q.tl.a == 4
return (canonicals[q]) ? [ canonicals[q], chain ] : [ null, null ]
}
// Main enumerates all the possible Game Matrixes and builds the
// set of equivalence class representatives, "canonicals".
// We only bother to generate Game Matrixes of the form
// [ (4,_) , (_,_) | (_,_) , (_,_) ]
// as any other Game Matrix can be trivially transformed into the
// above form using row, column and player exchange.
static main(String[] argv) {
def canonicals = [:]
def i = 1
[3,2,1].permutations { payoffs_playerA ->
[4,3,2,1].permutations { payoffs_playerB ->
def gm = new GameMatrix(
new Tuple(4, payoffs_playerB[0]),
new Tuple(payoffs_playerA[0], payoffs_playerB[1]),
new Tuple(payoffs_playerA[1], payoffs_playerB[2]),
new Tuple(payoffs_playerA[2], payoffs_playerB[3])
)
def ( c, chain ) = dupCheck(gm,canonicals)
if (c) {
System.out << "${gm} equivalent to ${c} via ${chain}\n"
}
else {
System.out << "${gm} accepted as canonical entry ${i}\n"
canonicals[gm] = gm
i++
}
}
}
}
I have attempted changing the "assert 1 <= a && a <= 4" to "assert 1 <= a && a <= 3" and then changing the 4's to a 3 further down in the code. This does not seem to work.
I am not sure however what the "int hashCode() {return (a-1) * 4 + (b-1)" or if "(q.tl.a == 4) {
}
else if (q.tr.a == 4) {" does and therefore not sure how to change this.
Apart from this, I suspect that the flips and exchanges can remain the way they are, since this should produce a procedure for identifying unique games no matter what the specific payoff set is (i.e. whether it's 1,2,3,4 or 1,2,3,3).
I have calculated the number of unique games for different payoff sets by hand which may be useful for reference.
I had a similar situation making an AI for Othello/Reversi, and wanting the state-space to be as small as possible to remove redundant processing.
The technique I used was to represent the game as a set of meta-states, or in your case, meta-outcomes, where each meta consists of all the permutations which are equivalent. Listing and identifying equivalent permutations involved coming up with a normalization scheme which determines which orientation or reflection is the key for the meta instance. Then all new permutations are transformed to normalize them before comparing to see if they represented a new instance.
In your case, if swapping rows and columns are both considered equivalent, you might consider the case where the orientation of sorted order puts the smallest value in the top-left corner and the next smallest adjacent value in the top-right corner. This normalizes all 4 flip positions (identity, h-flip, v-vlip, hv-flip) into a single representation.

Find relationship between gps data

10 million user gps data, structure like:
userId
startGps
endGps
one user has two gps, start point and end point. if a distance of two points from different user larger than 1km. we'll defined there users are potentially close relation.
userA startGpsA endGpsA
userB startGpsB endGpsB
function relation(userGpsA A, userGpsB B)
if(distance(A.startGps , B.startGps) > 1km || distance(A.startGps , B.endGps) > 1km || distance(A.endGps , B.endGps) > 1km)
return A,B
return null
how could i find these relation fast?
One possible algorithm use spacial 'buckets' to reduce computation time.
It will not do a special threading tricks, but will reduce a lot the amount of User to compare (depending of the size of the bucket).
The idea is to put in the same 'buckets' every user that are already not so far from each other, and create an index on 'buckets' that allow to get adjacent 'buckets' at low cost.
Let's assume we have
class User{
long userId;
GPS start;
GPS stop;
}
class GPS{
long x;
long y;
}
First we create a class for indexed User :
class BucketEntity implements Comparable<BucketEntity>{
User origin;
long x;
long y
}
class Bucket extends Set<BucketEntity {
}
For each User we will create two BucketEntity, one for 'start' and one for 'end'. We will store thoses BucketEntity into a specialy indexed data structure that allow easy retrival of nearest other BucketEntity.
class Index extends ConcurrentHashMap<BucketEntity,Bucket> {
// Overload the 'put' implementation to correctly manage the Bucket (null initialy, etc...)
}
All we need is to implements 'hash' (and 'equals' method in the BucketEntity class. The specification for 'hash' and 'equals' is to be the same if for two BucketEntity if they are not so far from each other. We also want to be able to compute the 'hash' function of Bucket that are spacially adjacent to another Bucket, for a given BucketEntity.
To get the correct behavior for 'hash' and 'equals' a nice/fast solution is to do 'precision reduction'. In short if you have 'x = 1248813' you replace it by 'x=124' (divide by 1000) it like changing your gps-meter precision to a gps-kilometer precision.
public static long scall = 1000;
boolean equals(BucketEntity that)
{
if (this == that) return true;
if (this.x / scall == that.x / scall &&
this.y / scall == that.y / scall)
return true;
return false;
}
// Maybe an 'int' is not enough to correctly hash your data
// if so you have to create you own implementation of Map
// with a special "long hashCode()" support.
int hashCode()
{
// We put the 'x' bits in the high level, and the 'y' bits in the low level.
// So the 'x' and 'y' don't conflict.
// Take extra-care of the value of 'scall' relatively to your data and the max value of 'int'. scall == 10000 should be a maximum.
return (this.x / scall) * scall + (this.y / scall);
}
As you can see in the hashCode() method, Bucket that are close to each other have really near hashCode(), if I give you a Bucket, you can also compute the spacially adjacents Bucket hashCode().
Now you can get BucketEntity(ies) that are in the same Bucket as your given BucketEntity. To get the adjacent bucket you need to create 9 virtual BucketEntity to 'get()' Bucket/null that are around the Bucket of your BucketEntity.
List<BucketEntity> shortListToCheck = // A List not a Set !
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)+1 , (y/scall)+1 )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)+1 , (y/scall) )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)+1 , (y/scall)-1 )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)+1 , (y/scall)+1 )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall) , (y/scall) )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)-1 , (y/scall)-1 )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)-1 , (y/scall)+1 )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)-1 , (y/scall) )));
shortListToCheck.addindex.get(new BucketEntity(user, (x / scall)-1 , (y/scall)-1 )));
get() all Buckets that match the 9 virtual BucketEntry(can be null).
For each of the User of the given 9 Buckets, really compute the distance by the way you provide in your question.
Then play with the 'scall'. Has you can see, there is no real constraint on multi-threading here. Maybe the next level of algorithm optimization is the adaptative/recursive bucket-size based on an adaptative scaling-size.

Best choice for in memory data structure for IP address filter in Java

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.

Solving a simple maximization game

I've got a very simple question about a game I created (this is not homework): what should the following method contain to maximize payoff:
private static boolean goForBiggerResource() {
return ... // I must fill this
};
Once again I stress that this is not homework: I'm trying to understand what is at work here.
The "strategy" is trivial: there can only be two choices: true or false.
The "game" itself is very simple:
P1 R1 R2 P2
R5
P3 R3 R4 P4
there are four players (P1, P2, P3 and P4) and five resources (R1, R2, R3, R4 all worth 1 and R5, worth 2)
each player has exactly two options: either go for a resource close to its starting location that gives 1 and that the player is sure to get (no other player can get to that resource first) OR the player can try to go for a resource that is worth 2... But other players may go for it too.
if two or more players go for the bigger resource (the one worth 2), then they'll arrive at the bigger resource at the same time and only one player, at random, will get it and the other player(s) going for that resource will get 0 (they cannot go back to a resource worth 1).
each player play the same strategy (the one defined in the method goForBiggerResource())
players cannot "talk" to each other to agree on a strategy
the game is run 1 million times
So basically I want to fill the method goForBiggerResource(), which returns either true or false, in a way to maximize the payoff.
Here's the code allowing to test the solution:
private static final int NB_PLAYERS = 4;
private static final int NB_ITERATIONS = 1000000;
public static void main(String[] args) {
double totalProfit = 0.0d;
for (int i = 0; i < NB_ITERATIONS; i++) {
int nbGoingForExpensive = 0;
for (int j = 0; j < NB_PLAYERS; j++) {
if ( goForBiggerResource() ) {
nbGoingForExpensive++;
} else {
totalProfit++;
}
}
totalProfit += nbGoingForExpensive > 0 ? 2 : 0;
}
double payoff = totalProfit / (NB_ITERATIONS * NB_PLAYERS);
System.out.println( "Payoff per player: " + payoff );
}
For example if I suggest the following solution:
private static boolean goForBiggerResource() {
return true;
};
Then all four players will go for the bigger resource. Only one of them will get it, at random. Over one million iteration the average payoff per player will be 2/4 which gives 0.5 and the program shall output:
Payoff per player: 0.5
My question is very simple: what should go into the method goForBiggerResource() (which returns either true or false) to maximize the average payoff and why?
Since each player uses the same strategy described in your goForBiggerResource method, and you try to maximize the overall payoff, the best strategy would be three players sticking with the local resource and one player going for the big game. Unfortunately since they can not agree on a strategy, and I assume no player can not be distinguished as a Big Game Hunter, things get tricky.
We need to randomize whether a player goes for the big game or not. Suppose p is the probability that he goes for it. Then separating the cases according to how many Big Game Hunters there are, we can calculate the number of cases, probabilities, payoffs, and based on this, expected payoffs.
0 BGH: (4 choose 0) cases, (1-p)^4 prob, 4 payoff, expected 4(p^4-4p^3+6p^2-4p+1)
1 BGH: (4 choose 1) cases, (1-p)^3*p prob, 5 payoff, expected 20(-p^4+3p^3-3p^2+p)
2 BGH: (4 choose 2) cases, (1-p)^2*p^2 prob, 4 payoff, expected 24(p^4-2p^3+p^2)
3 BGH: (4 choose 3) cases, (1-p)*p^3 prob, 3 payoff, expected 12(-p^4+p^3)
4 BGH: (4 choose 4) cases, p^4 prob, 2 payoff, expected 2(p^4)
Then we need to maximize the sum of the expected payoffs. Which is -2p^4+8p^3-12p^2+4p+4 if I calculated correctly. Since the first term is -2 < 0, it is a concave function, and hopefully one of the roots to its derivative, -8p^3+24p^2-24p+4, will maximize the expected payoffs. Plugging it into an online polynomial solver, it returns three roots, two of them complex, the third being p ~ 0.2062994740159. The second derivate is -24p^2+48p-24 = 24(-p^2+2p-1) = -24(p-1)^2, which is < 0 for all p != 1, so we indeed found a maximum. The (overall) expected payoff is the polynomial evaluated at this maximum, around 4.3811015779523, which is a 1.095275394488075 payoff per player.
Thus the winning method is something like this
private static boolean goForBiggerResource ()
{
return Math.random() < 0.2062994740159;
}
Of course if players can use different strategies and/or play against each other, it's an entirely different matter.
Edit: Also, you can cheat ;)
private static int cheat = 0;
private static boolean goForBiggerResource ()
{
cheat = (cheat + 1) % 4;
return cheat == 0;
}
I take it you tried the following:
private static boolean goForBiggerResource() {
return false;
};
where none of the player try to go for the resource that is worth 2. They are hence guaranteed to each get a resource worth 1 every time hence:
Payoff per player: 1.0
I suppose also that if you ask this nice question is because you guess there's a better answer.
The trick is that you need what is called a "mixed strategy".
EDIT: ok here I come with a mixed-strategy... I don't get how Patrick found the 20% that fast (when he commented, only minutes after you posted your question) but, yup, I found out basically that same value too:
private static final Random r = new Random( System.nanoTime() );
private static boolean goForBiggerResource() {
return r.nextInt(100) < 21;
}
Which gives, for example:
Payoff per player: 1.0951035
Basically if I'm not mistaken you want to read the Wikipedia page on the "Nash equilibrium" and particularly this:
"Nash Equilibrium is defined in terms of mixed strategies, where players choose a probability distribution over possible actions"
Your question/simple example if I'm not mistaken also can be used to show why colluding players can do better average payoffs: if players could colude, they'd get 1.25 on average, which beats the 1.095 I got.
Also note that my answers contains approximation errors (I only check random numbers from 0 to 99) and depends a bit on the Random PRNG but you should get the idea.
if the players cannot cooperate and have no memory there is only one possible way to implement goForBiggerResource: choose a value randomly. Now the question is what is the best rate to use.
Now simple mathematics (not really programming related):
assume the rate x represents the probability to stay with the small resource;
therefore the chance for no player going for the big one is x^4;
so the chance for at least one player going to the big one is 1-x^4;
total profit is x + ( 1 - x^4 ) / 2
find the maximum of that formula for 0% <= x <= 100%
the result is about 79.4% (for returning false)
Mmm, I think your basic problem is that the game as described is trivial. In all cases, the optimal strategy is to stick with the local resource, because the expected payoff for going for R5 is only 0.5 (1/4 * 2). Raise the reward for R5 to 4, and it becomes even; there's no better strategy. reward(R5)>4 and it always pays to take R5.

Categories

Resources