Difficulty understanding nested collections - java

I am working on dynamically generating classes in java using byte-code and then loading the class. I found this code on a tutorial how to do it.
private int stringConstant(String s) {
return constant(CONSTANT_Utf8, s);
}
private int classConstant(String s) {
int classNameIndex = stringConstant(s.replace('.', '/'));
return constant(CONSTANT_Class, classNameIndex);
}
private int constant(Object... data) {
List<?> dataList = Arrays.asList(data);
if (poolMap.containsKey(dataList))
return poolMap.get(dataList);
poolMap.put(dataList, poolIndex);
return poolIndex++;
}
private void writeConstantPool(DataOutputStream dout) throws IOException {
dout.writeShort(poolIndex);
int i = 1;
for (List<?> data : poolMap.keySet()) {
assert(poolMap.get(data).equals(i++));
int tag = (Integer) data.get(0);
dout.writeByte(tag); // u1 tag
switch (tag) {
case CONSTANT_Utf8:
dout.writeUTF((String) data.get(1));
break; // u2 length + u1 bytes[length]
case CONSTANT_Class:
dout.writeShort((Integer) data.get(1));
break; // u2 name_index
default:
throw new AssertionError();
}
}
}
private final Map<List<?>, Integer> poolMap =
new LinkedHashMap<List<?>, Integer>();
private int poolIndex = 1;
I don't understand how the map containing the list is being used it doesn't seem like a traditional oop way of doing this sort of thing.

In the fragment you've given us, the map is a means to assign a unique index (increasing from 1) to every List you put into it. This index is apparently only used in the assert to check that the keys are retrieved in the same order they are inserted - which should indeed be the case given that the map is a LinkedHashMap.
As far as the given code is concerned, a LinkedHashSet would have done as well. Of course we don't know where constant etc. are invoked, maybe the indices are used by the invoker.
I see no objections from an OO point of view - there is little OO about this code fragment in the first place.

Related

What is better to use just one method with switch or 4 different methods?

I have a object and i get some fields from it, i made a method with a switch statement, the idea was make it generic and just call this method through parameters but now I´m not sure.
The options are
private String getCode(Row row, String code) {
String result;
switch (code) {
case code1:
result = row.getString("constant1");
break;
case code2:
result = row.getString(constant2);
break;
case code3:
result = row.getString(constant3);
break;
case code4:
result = row.getString(constant4);
break;
default:
result = null;
}
return result;
}
or
private String getcode1(Row row){
return row.getString("constant1")
}
private String getcode1(Row row){
return row.getString("constant2")
}
private String getcode1(Row row){
return row.getString("constant3")
}
private String getcode1(Row row){
return row.getString("constant4")
}
I wand to use the better way, I´m a little confuse
The answer is neither of the them.
What you have here is a value conversion process. How this works? Well, as far I understand, you need to store the data in form of key-value pair where key must be unique. So this is the definition of a HashMap in java.
Also, because you are using Switch I'm assuming that you can identify your data with some unique key. This meas for each code there is only one constant. But how we can apply this to my issue? Well, I think you have a collection of data from where you extracted a single row. Now, from this row you want to access to a value (lets call it codeValue) using a constant, but to get this constant you need a code. Like this: code->constant->codeValue
How can I implement this??
Well, we gonna need a HasMap() called constants, which defines its keys a codes and the values as constants. Now you easily get each constant if you know its code. Obviously, if you get the constant you can also get the codeValue of each `row, like this:
public class TestClass {
public static void main(String[] args) {
// Create a HashMap object called constants
Map<String, String> constants = new HashMap<String, String>();
// Add keys and values (code, constant)
constants.put("code1", "constant1");
constants.put("code2", "constant2");
constants.put("code3", "constant3");
constants.put("code4", "constant4");
System.out.println(constants);
Row row = loadRow();
String code = loadCode();
//How to use it
String result = getCodeValue(row, code);
System.out.println(result);
}
// Now you get the code from the Map
private String getCodeValue(Row row, String code) {
return row.getString(constants.get(code));
}
}
In case your input code is different than constant, I will suggest the following approach. Create an Enum mapping code and constant.
public enum Mapping {
MAPPING_FIRST("code1", "constant1"),
MAPPING_SECOND("code2", "constant2");
private String code;
private String constant;
// constructor and getters
public static Mapping getMappingFromCode(String code){
return Arrays.stream(Mapping.values())
.filter(mapping -> mapping.getCode().equals(code))
.findFirst()
.orElse(null);
}
}
Now, create a method to access value from row.
private String getValue(Row row, String code) {
Mapping mapping = Mapping.getMappingFromCode(code);
if(mapping == null){
return null;
}
return row.getString(mapping.getConstant());
}
This question is little bit to much, but I will try to explain best I could. For me second option is no no at all. Why? You are making methods that you will have to sort out with some if/else statements anyway, for example:
if (code.equals(code1))
someString = getcode1(row);
else if (code.equals(code2))
somestring = getcode2(row);
else if (code.equals(code3))
someString = getcode3(row);
else
someString = getcode4(row);
Why not use this:
if (code.equals(code1))
someString = row.getString("content1");
else if (code.equals(code2))
somestring = row.getString("content2");
else if (code.equals(code3))
someString = row.getString("content3");
else
someString = row.getString("content4");
The first one I can see being used, but there is alternative there. Give us entire minimal requirement code with entire class and methods and we could help you far more than using these snippets of code.
Create an enum for mapping of code and constant. In that enum, create a generic method where you can get code just passing through parameter.
public enum MyEnum {
CODE1("constant1"), CODE2("constant2");
private String constant;
public String getConstant() {
return constant;
}
private MyEnum(String constant) {
this.constant = constant;
}
private static String getConstant(String code) {
return Arrays.stream(MyEnum.values()).filter(mapping -> mapping.name().equalsIgnoreCase(code))
.map(e -> e.getConstant()).findAny().orElse(null);
}
public static String getCode(Row row, String code) {
String constant = getConstant(code);
return constant != null ? row.get(constant) : null;
}
}
You can get code from row object by calling genric method getCode().
MyEnum.getCode(row, "code1")

Comparing Using 2 Properties in Ascending and Descending Orders

I have an object that has multiple properties, two of which the user can choose to order by, both can be Ascending, Descending, or neither (Normal), and they are independent of each other. So my cases are:
Case 1
propA - Normal
propB - Normal
Case 2
propA - Asc
propB - Normal
Case 3
propA - Desc
propB - Normal
And you get the idea. I'm using a Comparator to do this, and so far I have been able to get it to sort when one or both values are set to Normal. The part that I'm unsure of is what to do when I have chosen to sort by both methods. For example, if I want to order by propA ascending and propB descending, it should look a little like this
propA propB
A Z
A D
B M
B A
R Q
Z Z
Z A
Here is how I'm sorting now
#Override
public int compare(Field lhs, Field rhs) {
switch (growerSort) {
case NORMAL:
switch (fieldSort) {
case NORMAL:
return ((Integer) lhs.getID()).compareTo(rhs.getID());
case ASC:
return lhs.getPropB().toLowerCase().compareTo(rhs.getPropB().toLowerCase());
default:
return rhs.getPropB().toLowerCase().compareTo(lhs.getPropB().toLowerCase());
}
case ASC:
switch (fieldSort) {
case NORMAL:
return lhs.getPropA().toLowerCase().compareTo(rhs.getPropA().toLowerCase());;
case ASC:
return 0; // 0 used as placeholder
default:
return 0; // 0 used as placeholder
}
default:
switch (fieldSort) {
case NORMAL:
return rhs.getPropA().toLowerCase().compareTo(lhs.getPropA().toLowerCase());
case ASC:
return 0; // 0 used as placeholder
default:
return 0; // 0 used as placeholder
}
}
}
How can I sort with two different fields, each with their own order of sorting?
I'm a little bit confused of your Comparator. It's not easy to understand what switch triggers what event.
However I'll describe the standard procedure.
You'll need a priority order over your fields you want to compare. In your example above, I assume it first must be sorted by propA, then by propB.
Then you first sort by propA. If it returns "equals" (zero), then you want to sort by the next field, propB, and so on.
Let me show you an example:
#Override
public int compare(final Field lhs, final Field rhs) {
int firstCompareValue = lhs.getPropA().compareTo(rhs.getPropA());
if (firstCompareValue == 0) {
// lhs and rhs are equals in propA, use propB
int secondCompareValue = lhs.getPropB().compareTo(rhs.getPropB());
return secondCompareValue;
} else {
return firstCompareValue;
}
}
Of course you can also do this iterative if you have multiple fields, as long as you have specified an order (e.g. by using an ordered list over your property fields).
Now you need to add your switches to this showcase :) I'll recommend doing a PropertyComparator for that.
public final class PropertyComparator extends Comparator<Comparable<?>> {
private final boolean mUseDscOrder = false;
public void setUseDscOrder(final boolean useDscOrder) {
mUseDscOrder = useDscOrder;
}
public int compare(final Comparable<?> o1, final Comparable<?> o2) {
if (!mUseDscOrder) {
return o1.compareTo(o2);
} else {
// Reverses the logic, results in DscOrder
return o2.compareTo(o1)
}
}
}
And now use it in the above Comparator.
#Override
public int compare(final Field lhs, final Field rhs, final boolean firstUseDscOrder, final boolean secondUseDcsOrder) {
PropertyComparator firstComparator = new PropertyComparator();
firstComparator.setUseDscOrder(firstUseDscOrder);
int firstCompareValue = firstComparator.compare(lhs.getPropA(), rhs.getPropA());
if (firstCompareValue == 0) {
// lhs and rhs are equals in propA, use propB
PropertyComparator secondComparator = new PropertyComparator();
secondComparator.setUseDscOrder(secondUseDscOrder);
int secondCompareValue = secondComparator.compare(lhs.getPropB(), rhs.getPropB());
return secondCompareValue;
} else {
return firstCompareValue;
}
}
I've not tested it but I think you get the idea :)
Create 1 comparator that sorts on the "first field". If the values of the first field are equal, sort on the "second field".
if(object1.f1.equals(object2.f1)){
object1.f2.compareTo(object2.f2);
} else {
object1.f1.compareTo(object2.f1);
}

Minimise if else conditions in method

I have the following class.
public enum EnumService
{
ONE, TWO, THREE, FOUR,.............HUNDRED;
//Values till HUNDRED
public static EnumService returnMockService(String request)
{
//some string match
if(request.matches("/abc*")){
return ONE;
}
//some other string is match
else if(request.matches("/bcd*"))
return TWO;
else if(request.matches("/decf*"))
return THREE;
//many else if conditions
else if(request.matches("/wxyz*"))
return HUNDRED;
return null;
}
}
The code is not standard with more if else statements.
I want to minimize the number of if calls in above method yet maintaining the return type as EnumService
Any better option to do this.
It would be great if some can help me in making it clean.
First: no need to else if you return.
Second: you can optimize it a LOT if you use this string as a parameter in the enum:
public enum EnumService
{
ONE("abc*"),
// etc
private static final Map<Pattern, EnumService> MAPPING
= new HashMap<>();
static {
for (final EnumService svc: values())
MAPPING.put(svc.pattern, svc);
}
private final Pattern pattern;
EnumService(final String s)
{
pattern = Pattern.compile(s);
}
public static EnumService returnMockService(String request)
{
for (final Map.Entry<Pattern, EnumService> entry: MAPPING.entrySet())
if (entry.getKey().matcher(request).matches())
return entry.getValue();
return null;
}
}
I would put the strings that you're matching along with the EnumService value they should map to into an array of simple objects, then loop through the array.
E.g.:
ArrayEntry[] entries = new ArrayEntry[] {
new ArrayEntry("/abc*", EnumService.ONE),
// ...and so on...
};
and then:
for (ArrayEntry entry : entries) {
if (request.matches(entry.str)) {
return entry.value;
}
}
return null;
...where ArrayEntry is just a simple class with those two properties (str and value).
If you don't want to have ArrayEntry, you can use a Map:
Map<String,EnumService> entries = new HashMap<String,EnumService>();
entries.put("/abc*", EnumService.ONE);
//...and so on...
and then
for (Map.Entry<String,EnumService> entry : entries.entrySet()) {
if (request.matches(entry.getKey())) {
return entry.getValue();
}
}
return null;
Or you can do it with parallel arrays instead:
String[] strings = new String[] { "/abc*", /*...and so on...*/ };
EnumService[] values = new EnumService[] { EnumService.ONE, /*...and so on...*/ };
and then
int n;
for (n = 0; n < strings.length; ++n) {
if (request.matches(strings[n])) {
return values[n];
}
}
return null;
But parallel arrays tend to be a bit of a maintenance issue.
You can go for design patterns, best for these kind of things are state pattern. State Pattern is to solve this kind of issues and make the code more compatible & flexible. Look over this http://www.javacodegeeks.com/2013/08/state-design-pattern-in-java-example-tutorial.html
Have a look at the below link.
Why can't I switch on a String?
As stated there, if you are using jdk 7, just use switch on the strings. If not, create an enum of /abc*,/bcd*,etc. and use them in switch.
Hope this helps.
Or you could always, store them in an array and loop through them. That would be much easier but costs an extra array.
If there is no logic to map /abc to ONE and /bcd to TWO etc, then maybe you need to load these into an array or map, then simply get the index of the array where it matches.
myMap.put("abc", ONE);
myMap.put("bcd", TWO);

Is it possible to return more than one value from a method in Java? [duplicate]

This question already has answers here:
How to return multiple objects from a Java method?
(25 answers)
Closed 7 years ago.
I am using a simulator to play craps and I am trying to return two values from the same method (or rather I would like to).
When I wrote my return statement I simply tried putting "&" which compiled and runs properly; but I have no way of accessing the second returned value.
public static int crapsGame(){
int myPoint;
int gameStatus = rollagain;
int d1,d2;
int rolls=1;
d1 = rollDice();
d2 = rollDice();
switch ( d1+d2 ) {
case 7:
case 11:
gameStatus = win;
break;
case 2:
case 3:
case 12:
gameStatus = loss;
break;
default:
myPoint = d1+d2;
do {
d1=rollDice();
d2=rollDice();
rolls++;
if ( d1+d2 == myPoint )
gameStatus = win;
else if ( d1+d2 == 7 )
gameStatus = loss;
} while (gameStatus == rollagain);
} // end of switch
return gameStatus & rolls;
}
When I return the value as:
gameStatus=crapsGame();
It appropriately sets the varaible to win or lose but if I try something as simple as following that statement with:
rolls=crapsGame();
It is assigned the same value as gamestatus...a 0 or a 1 (win or lose).
Any way that I can access the second returned value? Or is there a completely different way to go about it?
Create your own value holder object to hold both values, then return it.
return new ValueHolder(gameStatus, rolls);
It's possible to return an array with multiple values, but that's cryptic and it does nothing for readability. It's much easier to understand what this means...
valueHolder.getGameStatus()
than what this means.
intArray[0]
returning gameStatus & rolls means "return the bitwise and of gameStatus and rolls" which probably is not what you want
you have some options here:
return an array
create a class that represents the response with a property for each value and return an instance
use one of the many java collections to return the values (probably lists or maps)
You can return an array of values or a Collection of values.
Is it possible to return more than one value from a method in Java?
No it is not. Java allows only one value to be returned. This restriction is hard-wired into the language.
However, there are a few approaches to deal with this restriction:
Write a light-weight "holder" class with fields for the multiple values you want to return, and create and return an instance of that class.
Return a Map containing the values. The problem with this (and the next) approach is that you are straying into an area that requires runtime type checking ... and that can lead to fragility.
Return an array containing the values. The array has to have a base type that will accommodate the types of all of the values.
If this is a method on an object, then add some fields on the same object and methods that allow the caller to pick up "auxiliary results" from the last call. (For example, the JDBC ResultSet class does this to allow a client to determine if the value just retrieved was a NULL.) The problem is that this makes the class non-reentrant at the instance level.
(You could even return extra results in statics, but it is a really bad idea. It makes the class non-reentrant across all instances, not to mention all of the other badnesses associated with misused statics.)
Of these, the first option is the cleanest. If you are worried about the overhead of creating holder instances, etc, you could consider reusing the instances; e.g. have the caller pass an existing "holder" to the called method into which the results should be placed.
The best practice for an OOP approach is to return an Object. An object that contains all the values you want.
Example:
class Main {
public static void main(String[] args) {
MyResponse response = requestResponse();
System.out.println( response.toString() );
}
private static MyResponse requestResponse() {
return new MyResponse( "this is first arg", "this is second arg" );
}
}
class MyResponse {
private String x, y;
public MyResponse( String x, String y ) {
this.x = x;
this.y = y;
}
#Override
public String toString() {
return "x: " + x + "\t y: " + y;
}
}
If you want an even more scalable approach then you have to use JSON responses. (let me know if you want an example with JSON too)
You can following ways to do this:
Use a Container class, for example
public class GameStatusAndRolls {
String gameStatus;
String rolls;
... // constructor and getter/setter
}
public static GameStatusAndRolls crapsGame(String gameStatus, String rolls) {
return new GameStatusAndRolls(gameStatus, rolls);
}
public static void main(String[] args) {
...
GameStatusAndRolls gameStatusAndRolls = crapsGame(gameStatus, rolls);
gameStatusAndRolls.getGameStatus();
Use List or an array, for example
public static List<Integer> crapsGame(String gameStatus, String rolls) {
return Arrays.asList(gameStatus, rolls);
}
private static final int GAME_STATUS = 0;
private static final int ROOLS = 0;
public static void main(String[] args) {
...
List<Integer> list = crapsGame(gameStatus, rolls);
... list.get(0)...list.get(GAME_STATUS);
... list.get(1)...list.get(ROOLS);
or
public static String[] crapsGame(String gameStatus, String rolls) {
return new String[] {gameStatus, rolls};
}
private static final int GAME_STATUS = 0;
private static final int ROOLS = 0;
public static void main(String[] args) {
...
String[] array = crapsGame(gameStatus, rolls);
... array[0]...array[GAME_STATUS];
... array[1]...array[ROOLS];
Use Map, for example
public static Map<String, String> crapsGame(String gameStatus, String rolls) {
Map<String, String> result = new HashMap<>(2);
result.put("gameStatus", gameStatus);
result.put("rolls", rolls);
return result;
}
public static void main(String[] args) {
...
Map map = crapsGame(gameStatus, rolls);
... map.get("gameStatus")...map.get("rolls");

Null-free "maps": Is a callback solution slower than tryGet()?

In comments to "How to implement List, Set, and Map in null free design?", Steven Sudit and I got into a discussion about using a callback, with handlers for "found" and "not found" situations, vs. a tryGet() method, taking an out parameter and returning a boolean indicating whether the out parameter had been populated. Steven maintained that the callback approach was more complex and almost certain to be slower; I maintained that the complexity was no greater and the performance at worst the same.
But code speaks louder than words, so I thought I'd implement both and see what I got. The original question was fairly theoretical with regard to language ("And for argument sake, let's say this language don't even have null") -- I've used Java here because that's what I've got handy. Java doesn't have out parameters, but it doesn't have first-class functions either, so style-wise, it should suck equally for both approaches.
(Digression: As far as complexity goes: I like the callback design because it inherently forces the user of the API to handle both cases, whereas the tryGet() design requires callers to perform their own boilerplate conditional check, which they could forget or get wrong. But having now implemented both, I can see why the tryGet() design looks simpler, at least in the short term.)
First, the callback example:
class CallbackMap<K, V> {
private final Map<K, V> backingMap;
public CallbackMap(Map<K, V> backingMap) {
this.backingMap = backingMap;
}
void lookup(K key, Callback<K, V> handler) {
V val = backingMap.get(key);
if (val == null) {
handler.handleMissing(key);
} else {
handler.handleFound(key, val);
}
}
}
interface Callback<K, V> {
void handleFound(K key, V value);
void handleMissing(K key);
}
class CallbackExample {
private final Map<String, String> map;
private final List<String> found;
private final List<String> missing;
private Callback<String, String> handler;
public CallbackExample(Map<String, String> map) {
this.map = map;
found = new ArrayList<String>(map.size());
missing = new ArrayList<String>(map.size());
handler = new Callback<String, String>() {
public void handleFound(String key, String value) {
found.add(key + ": " + value);
}
public void handleMissing(String key) {
missing.add(key);
}
};
}
void test() {
CallbackMap<String, String> cbMap = new CallbackMap<String, String>(map);
for (int i = 0, count = map.size(); i < count; i++) {
String key = "key" + i;
cbMap.lookup(key, handler);
}
System.out.println(found.size() + " found");
System.out.println(missing.size() + " missing");
}
}
Now, the tryGet() example -- as best I understand the pattern (and I might well be wrong):
class TryGetMap<K, V> {
private final Map<K, V> backingMap;
public TryGetMap(Map<K, V> backingMap) {
this.backingMap = backingMap;
}
boolean tryGet(K key, OutParameter<V> valueParam) {
V val = backingMap.get(key);
if (val == null) {
return false;
}
valueParam.value = val;
return true;
}
}
class OutParameter<V> {
V value;
}
class TryGetExample {
private final Map<String, String> map;
private final List<String> found;
private final List<String> missing;
private final OutParameter<String> out = new OutParameter<String>();
public TryGetExample(Map<String, String> map) {
this.map = map;
found = new ArrayList<String>(map.size());
missing = new ArrayList<String>(map.size());
}
void test() {
TryGetMap<String, String> tgMap = new TryGetMap<String, String>(map);
for (int i = 0, count = map.size(); i < count; i++) {
String key = "key" + i;
if (tgMap.tryGet(key, out)) {
found.add(key + ": " + out.value);
} else {
missing.add(key);
}
}
System.out.println(found.size() + " found");
System.out.println(missing.size() + " missing");
}
}
And finally, the performance test code:
public static void main(String[] args) {
int size = 200000;
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < size; i++) {
String val = (i % 5 == 0) ? null : "value" + i;
map.put("key" + i, val);
}
long totalCallback = 0;
long totalTryGet = 0;
int iterations = 20;
for (int i = 0; i < iterations; i++) {
{
TryGetExample tryGet = new TryGetExample(map);
long tryGetStart = System.currentTimeMillis();
tryGet.test();
totalTryGet += (System.currentTimeMillis() - tryGetStart);
}
System.gc();
{
CallbackExample callback = new CallbackExample(map);
long callbackStart = System.currentTimeMillis();
callback.test();
totalCallback += (System.currentTimeMillis() - callbackStart);
}
System.gc();
}
System.out.println("Avg. callback: " + (totalCallback / iterations));
System.out.println("Avg. tryGet(): " + (totalTryGet / iterations));
}
On my first attempt, I got 50% worse performance for callback than for tryGet(), which really surprised me. But, on a hunch, I added some garbage collection, and the performance penalty vanished.
This fits with my instinct, which is that we're basically talking about taking the same number of method calls, conditional checks, etc. and rearranging them. But then, I wrote the code, so I might well have written a suboptimal or subconsicously penalized tryGet() implementation. Thoughts?
Updated: Per comment from Michael Aaron Safyan, fixed TryGetExample to reuse OutParameter.
I would say that neither design makes sense in practice, regardless of the performance. I would argue that both mechanisms are overly complicated and, more importantly, don't take into account actual usage.
Actual Usage
If a user looks up a value in a map and it isn't there, most likely the user wants one of the following:
To insert some value with that key into the map
To get back some default value
To be informed that the value isn't there
Thus I would argue that a better, null-free API would be:
has(key) which indicates if the key is present (if one only wishes to check for the key's existence).
get(key) which reports the value if the key is present; otherwise, throws NoSuchElementException.
get(key,defaultval) which reports the value for the key, or defaultval if the key isn't present.
setdefault(key,defaultval) which inserts (key,defaultval) if key isn't present, and returns the value associated with key (which is defaultval if there is no previous mapping, otherwise prev mapping).
The only way to get back null is if you explicity ask for it as in get(key,null). This API is incredibly simple, and yet is able to handle the most common map-related tasks (in most use cases that I have encountered).
I should also add that in Java, has() would be called containsKey() while setdefault() would be called putIfAbsent(). Because get() signals an object's absence via a NoSuchElementException, it is then possible to associate a key with null and treat it as a legitimate association.... if get() returns null, it means the key has been associated with the value null, not that the key is absent (although you can define your API to disallow a value of null if you so choose, in which case you would throw an IllegalArgumentException from the functions that are used to add associations if the value given is null). Another advantage to this API, is that setdefault() only needs to perform the lookup procedure once instead of twice, which would be the case if you used if( ! dict.has(key) ){ dict.set(key,val); }. Another advantage is that you do not surprise developers who write something like dict.get(key).doSomething() who assume that get() will always return a non-null object (because they have never inserted a null value into the dictionary)... instead, they get a NoSuchElementException if there is no value for that key, which is more consistent with the rest of the error checking in Java and which is also a much easier to understand and debug than NullPointerException.
Answer To Question
To answer original question, yes, you are unfairly penalizing the tryGet version.... in your callback based mechanism you construct the callback object only once and use it in all subsequent calls; whereas in your tryGet example, you construct your out parameter object in every single iteration. Try taking the line:
OutParameter out = new OutParameter();
Take the line above out of the for-loop and see if that improves the performance of the tryGet example. In other words, place the line above the for-loop, and re-use the out parameter in each iteration.
David, thanks for taking the time to write this up. I'm a C# programmer, so my Java skills are a bit vague these days. Because of this, I decided to port your code over and test it myself. I found some interesting differences and similarities, which are pretty much worth the price of admission as far as I'm concerned. Among the major differences are:
I didn't have to implement TryGet because it's built into Dictionary.
In order to use the native TryGet, instead of inserting nulls to simulate misses, I simply omitted those values. This still means that v = map[k] would have set v to null, so I think it's a proper porting. In hindsight, I could have inserted the nulls and changed (_map.TryGetValue(key, out value)) to (_map.TryGetValue(key, out value) && value != null)), but I'm glad I didn't.
I want to be exceedingly fair. So, to keep the code as compact and maintainable as possible, I used lambda calculus notation, which let me define the callbacks painlessly. This hides much of the complexity of setting up anonymous delegates, and allows me to use closures seamlessly. Ironically, the implementation of Lookup uses TryGet internally.
Instead of declaring a new type of Dictionary, I used an extension method to graft Lookup onto the standard dictionary, much simplifying the code.
With apologies for the less-than-professional quality of the code, here it is:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
static class CallbackDictionary
{
public static void Lookup<K, V>(this Dictionary<K, V> map, K key, Action<K, V> found, Action<K> missed)
{
V v;
if (map.TryGetValue(key, out v))
found(key, v);
else
missed(key);
}
}
class TryGetExample
{
private Dictionary<string, string> _map;
private List<string> _found;
private List<string> _missing;
public TryGetExample(Dictionary<string, string> map)
{
_map = map;
_found = new List<string>(_map.Count);
_missing = new List<string>(_map.Count);
}
public void TestTryGet()
{
for (int i = 0; i < _map.Count; i++)
{
string key = "key" + i;
string value;
if (_map.TryGetValue(key, out value))
_found.Add(key + ": " + value);
else
_missing.Add(key);
}
Console.WriteLine(_found.Count() + " found");
Console.WriteLine(_missing.Count() + " missing");
}
public void TestCallback()
{
for (int i = 0; i < _map.Count; i++)
_map.Lookup("key" + i, (k, v) => _found.Add(k + ": " + v), k => _missing.Add(k));
Console.WriteLine(_found.Count() + " found");
Console.WriteLine(_missing.Count() + " missing");
}
}
class Program
{
static void Main(string[] args)
{
int size = 2000000;
var map = new Dictionary<string, string>(size);
for (int i = 0; i < size; i++)
if (i % 5 != 0)
map.Add("key" + i, "value" + i);
long totalCallback = 0;
long totalTryGet = 0;
int iterations = 20;
TryGetExample tryGet;
for (int i = 0; i < iterations; i++)
{
tryGet = new TryGetExample(map);
long tryGetStart = DateTime.UtcNow.Ticks;
tryGet.TestTryGet();
totalTryGet += (DateTime.UtcNow.Ticks - tryGetStart);
GC.Collect();
tryGet = new TryGetExample(map);
long callbackStart = DateTime.UtcNow.Ticks;
tryGet.TestCallback();
totalCallback += (DateTime.UtcNow.Ticks - callbackStart);
GC.Collect();
}
Console.WriteLine("Avg. callback: " + (totalCallback / iterations));
Console.WriteLine("Avg. tryGet(): " + (totalTryGet / iterations));
}
}
}
My performance expectations, as I said in the article that inspired this one, would be that neither one is much faster or slower than the other. After all, most of the work is in the searching and adding, not in the simple logic that structures it. In fact, it varied a bit among runs, but I was unable to detect any consistent advantage.
Part of the problem is that I used a low-precision timer and the test was short, so I increased the count by 10x to 2000000 and that helped. Now callbacks are about 3% slower, which I do not consider significant. On my fairly slow machine, callbacks took 17773437 while tryget took 17234375.
Now, as for code complexity, it's a bit unfair because TryGet is native, so let's just ignore the fact that I had to add a callback interface. At the calling spot, lambda notation did a great job of hiding the complexity. If anything, it's actually shorter than the if/then/else used in the TryGet version, although I suppose I could have used a ternary operator to make it equally compact.
On the whole, I found the C# to be more elegant, and only some of that is due to my bias as a C# programmer. Mainly, I didn't have to define and implement interfaces, which cut down on the plumbing overhead. I also used pretty standard .NET conventions, which seem to be a bit more streamlined than the sort of style favored in Java.

Categories

Resources