8

I've been using a lot Perl hashes due to super flexibility and convenient. for instance, in Perl I can do the following:

$hash{AREA_CODE}->{PHONE}->{STREET_ADDR}

I wondering how can I accomplish the same thing with Java, I guess it has something to do with HashMap?

Thanks,

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
snoofkin
  • 8,725
  • 14
  • 49
  • 86
  • 10
    Now if you described what that Perl code actually does, people who know Java but not Perl could help you directly without having to guess... – Michael Borgwardt Jan 22 '11 at 16:48
  • 2
    Sidenote: $hash{AREA_CODE}->{PHONE}->{STREET_ADDR} is fine as $hash{AREA_CODE}{PHONE}{STREET_ADDR}. – Ashley Jan 22 '11 at 17:02
  • @Ashley - I would argue that the former (using full `->`) is actually more readable/maintainable than the shorthand version omitting the arrows, but I know this opinion is not widely shared. – DVK Jan 22 '11 at 19:32
  • 1
    @Michael - I agree it's a valid point in general when asking such a type of question; so +1 for the comment :). However, there's a sufficient amount of Perl developers who're very proficient in Java that it is not likely to lead to dearth of answers (as evidenced by the answers submitted within 2 hours on a weekend :) ) – DVK Jan 22 '11 at 19:34

8 Answers8

18

I've been using a lot Perl hashes due to super flexibility and convenient. for instance, in Perl I can do the following: $hash{AREA_CODE}->{PHONE}->{STREET_ADDR} I wondering how can I accomplish the same thing with Java, I guess it has something to do with HashMap?

The Java code which approximates the following Perl code:

my %hash;
$hash{AREA_CODE}{PHONE}{STREET_ADDR} = "221B Baker Street";
printf "Street address is %s\n", $hash{AREA_CODE}{PHONE}{STREET_ADDR};

is

HashMap<String, HashMap<String, HashMap<String, String>>> hash =
    new HashMap<String, HashMap<String, HashMap<String, String>>>();

hash.put("AREA_CODE", new HashMap<String, HashMap<String, String>>());
hash.get("AREA_CODE").put("PHONE", new HashMap<String, String>());
hash.get("AREA_CODE").get("PHONE").put("STREET_ADDR", "221B Baker Street");

System.out.printf("Street address is %s\n",
    hash.get("AREA_CODE").get("PHONE").get("STREET_ADDR"));

Isn’t that special? :)

I say ‘approximates’ for many reasons. One of these is that in Java you’ll be frustrated to the point of extreme apoplexy merely for wanting to then do on the next line of Java the equivalent of this perfectly straightforward Perl code:

$hash{AREA_CODE}{PREFIX} = 800;

If you want Perl’s flexibility and convenience in things like this, Java simply isn’t going to give it to you. Even worse, its partisans will often berate you for even expressing such a desire.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
tchrist
  • 78,834
  • 30
  • 123
  • 180
  • 2
    IMHO your Java code is - in general case - somewhat incorrect (though it is 100% OK as far as literally translating the example Perl code). It doesn't check whether AREA_CODE key already maps to a valid hashmap (which is OK if you assume the special case of "just declared" empty container; but breaks if you don't assume that). The same is true for value retrieval - it doesn't check if the value of get("AREA_CODE") is null. It means that - in GENERAL case - the Java equivalent of setting and getting 3-d-level nested hash-of-hash-of-hashes is even longer and uglier than yours. – DVK Jan 22 '11 at 19:46
  • Accepted. I accepted the previous one by mistake. Thanks for that explanation, it really helped me. – snoofkin Jan 22 '11 at 19:49
  • @DVK: I believe all that falls under my ‘approximates’ hedge. :) – tchrist Jan 22 '11 at 19:50
  • excuses, excuses ;) . See my answer for code (admittedly, didn't even try to compile or ensure its correctness) which **precisely** ports Perl's hash-of-hash-of-hashes. – DVK Jan 22 '11 at 20:39
  • 1
    Nice idea, but I doubt that this performs well (too many hashmaps). It's better to do what's described here: http://stackoverflow.com/questions/5122913/java-associative-array – Marcus Feb 05 '13 at 02:10
6

First of all, your specific example ($hash{AREA_CODE}->{PHONE}->{STREET_ADDR}), with hard-coded strings as hash keys, is not really a useful data structure in Java as Michael Carman pointed out - it should be stored as a class with attributes (and to be honest it's a bad data structure in concept - data like this is more likely to be an array of phones, not hash of phones).

Second, assuming you actually meant $hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR}, it looks like everyone's Java code so far was NOT implementing a generic equivalent code - the code all assumed that the Java hash is newly initialized for storing example OR fully populated for retrieval example (in other words, as leonbloy's answer noted, is missing autovivification feature).

The correct code mimiquing autovivification is:

// This method will ensure that hash-of-hash-of-hashes structure exists of a given set of 3 keys.
public HashMap<String, HashMap<String, HashMap<String, Object>>>
 autovivification_3rd_level (
           HashMap<String, HashMap<String, HashMap<String, Object>>> hash
         , String AREA_CODE, String PHONE, String STREET_ADDR) {
    if (hash == null) {
        hash = new HashMap<String, HashMap<String, HashMap<String, Object>>>();
    }
    if (!hash.contains(AREA_CODE) || hash.get(AREA_CODE) == null) {
        hash.put(new HashMap<String, HashMap<String, Object>>());
    }
    HashMap<String, HashMap<String, Object>> AREA_CODE_hash
         = (HashMap<String, HashMap<String, Object>>) hash.get(AREA_CODE);
    if (!AREA_CODE_hash.contains(PHONE) || AREA_CODE_hash.get(PHONE) == null) {
        AREA_CODE_hash.put(new HashMap<String, Object>());
    }
    return hash;
}

////////////////////////////////////////////////////////////////////////////////////    

// Equivalent to Perl's "$hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR} = value;"
public Object put_3d_level_hash(
          HashMap<String, HashMap<String, HashMap<String, Object>>> hash
        , String AREA_CODE, String PHONE, String STREET_ADDR,
        , Object value) {
    hash = autovivification_3rd_level(hash, AREA_CODE, PHONE, STREET_ADDR);
    return hash.get(AREA_CODE).get(PHONE).put(STREET_ADDR, value);
}
put_3d_level_hash(hash, AREA_CODE, PHONE, STREET_ADDR, obj);

////////////////////////////////////////////////////////////////////////////////////    

// Equivalent to Perl's "$var = $hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR}"
public Object get_3d_level_hash(HashMap<String, HashMap<String, HashMap<String, Object>>> hash
                       , String AREA_CODE, String PHONE, String STREET_ADDR) {
    hash = autovivification_3rd_level(hash, AREA_CODE, PHONE, STREET_ADDR);
    return hash.get(AREA_CODE).get(PHONE).get(STREET_ADDR);
}
Object obj = get_3d_level_hash(hash, AREA_CODE, PHONE, STREET_ADDR);
DVK
  • 126,886
  • 32
  • 213
  • 327
  • I’vbe certainly written code like that before. Not my idea of a good time!! – tchrist Jan 22 '11 at 20:43
  • BTW, I'm not a Java expert - feel free to edit and improve this code if it's in some way wrong or can be improved further – DVK Jan 22 '11 at 20:52
5

See the Map interface and its implementations, specially HashMap.

Beware that Java doesn't have Perl's auto-vivification (handy but dangerous feature) so that

hash.get("areaCode").get("phone").get("streetAdr")

will throw an exception if, eg, get(phone) returns null. Beware also that you should not uses hashes for things that have fixed names ("properties"), you should define your own classes with its getters and setters.

Community
  • 1
  • 1
leonbloy
  • 73,180
  • 20
  • 142
  • 190
  • 1
    Think that should be 'Java *doesn't* have Perl's auto-vivication'. – Sdaz MacSkibbons Jan 22 '11 at 17:14
  • Please note that generic Map interface is NOT fully equivalent to Perl's hashes, because Perl hash has `O(1)` access/store time (being a real hash map) whereas a generic Java class implementing Map may not. – DVK Jan 22 '11 at 19:50
  • I added an answer which actually shows how to implement the Java equivalent **with** autovivification – DVK Jan 22 '11 at 20:45
  • You'll get that O(1) rather by calling `ArrayList> data = new ArrayList>(capacity, 1);` and then you just add any key --> value to the hash map. – Marcus Feb 05 '13 at 02:13
2

Java has hashes, but because of strong typing they're not quite as flexible as hashes in Perl. Multidimensional hashes are harder to work with. In Perl, you can just declare a hash and let autovivification create the nested hashes on demand.

my %hash;
$hash{a}{b} = 1;

In Java, you have to declare it to be a hash-of-hashes up-front.

Map<String,Map<String,Integer>> hash = new HashMap<String,HashMap<String,Integer>>();
hash.put("a", new HashMap<String, Integer>());
hash.get("a").put("b", new Integer(1));

For every extra dimension you need to add another nesting of Map<K,V> to the declaration. Aside from being tedious, this isn't very OO.

Michael Carman
  • 30,628
  • 10
  • 74
  • 122
  • 4
    The statement that “this isn’t OO” is bizarre. So what? Insofar as it applies to multisubscripted associative arrays like `$hash{$a}{$b}{$c}`,it must also necessarily apply to multisubscripted regular arrays like `$array[$i][$j][$k]`. Indeed it even applies to singly-subscripted arrays like `$ARGV[$n]`, too! Eschewing subscripted aggregates for cumbersomely over-abstracted OO constructs is a waste of everybody’s time. Java’s inability to treat hashes as first-class citizens like arrays is just another of Java’s innumerable unreasonable annoyances. Even C++ managed (eventually) to get it right. – tchrist Jan 22 '11 at 18:26
  • is it only me, or you meant: hash.get("a").put(new String("fff"),new Integer(1)); ?? – snoofkin Jan 22 '11 at 18:34
  • The last line of Java code there is incorrect: it forgets the subscript on the `put()`. It needs to be `hash.get("a").put("b", new Integer(1));` instead. – tchrist Jan 22 '11 at 18:38
  • Turns out that that first line of Java code there is also wrong. It needs to read `HashMap> hash = new HashMap>();` instead. Boy Java is fun! – tchrist Jan 22 '11 at 18:39
  • Yes, I like java, its nice, although I prefer Perl for many other reasons. anyway, still cant get it to work. – snoofkin Jan 22 '11 at 18:44
  • @soulSurfer2010: The code you accepted doesn’t even compile!! – tchrist Jan 22 '11 at 19:05
1

If the hash keys are constant, why won't hash.getAreaCode().getPhone().getStreetAddr() do? Keep in mind that either your getters or your constructors will need to handle default value generation.

Tassos Bassoukos
  • 16,017
  • 2
  • 36
  • 40
1

You can easily subclass your hash to add a method that'll autovivify for you.

From: $hash{AREA_CODE}->{PHONE}->{STREET_ADDR}

To: hash.vivifyingGet(areaCode).put(phone, streetAddr).

Assuming I've created the hash with:

/**
  * A two-level autovivifying hashmap of X and Y to Z. Provides
  * a new method #vivifyingGet(X) which creates the next level of hash.
  */
Map<AreaCode, Map<Phone, StreetAddr>> hash =
    new HashMap<AreaCode, Map<Phone, StreetAddr>>() {
    /**
      * Convenience method to get or create the next level of hash.
      * @param key the first level key
      * @return the next level map
      */
    public Map<Phone, StreetAddr> vivifyingGet(Phone key) {
        if (containsKey(key)) {
            return get(key);
        } else {
            Map<Phone, StreetAddr> = hash = new HashMap<Phone, StreetAddr>();
            put(key, hash);
            return hash;
        }
    }
};
Josh Jore
  • 191
  • 4
0

I missed the perl hashes a lot in my work and made some ugly workarounds with hash classes.

Last week I had an idea to implement the whole thing in one PerlMap class which use delimiters to access objects and foremost the Lists zu access subsets.

It works fine with map.get(code:street:phone) and map.put(code:street:phone,"123456789"). To get a list of phonenumber you just use map.getList(code:street).

I've just started but use in my project now. It has no limitations of complexity :-) and you can choose the delimiter free. I put the whole stuff under http://www.jdeer.org. Have fun.

wattostudios
  • 8,666
  • 13
  • 43
  • 57
-4

You're probably going to want to go with Groovy if you want this sort of flexibility but still run within the JVM. tchrist likes to ignore the point that Java is strong-typed as opposed to dynamic-typed languages like Perl or PHP - and also likes to ignore that Java is an order of magnitude faster at running, but that's just me being a "partisan", apparently.

Nathan Crause
  • 874
  • 10
  • 7
  • 3
    @Nathan - two points. (1) Do you have an actual cite for a specific well-executed performance study across a wide array of apps written by experts in the respective languages which proves that Java apps outperform Perl apps **by order of magnitude**? Or are you just pulling it out of blue sky because you heard somewhere that "perl is slow"? – DVK Jan 22 '11 at 19:20
  • 2
    (2) Even if Perl is slower in some specific instances (again, possible but please back such assertions up with FACTS), Assembly is probably much (order of magnitude?) faster than Java. Are you planning to switch to Assembly and abandon Java for that reason? Oh, that's right. Programming languages also differ in programmer's efficiency/thoughput/cost of maintenance. – DVK Jan 22 '11 at 19:22
  • 3
    @Nathan - and (3) If you question my "efficiency/throughput/cost of maintenance" item, please see my correct implementation - written in the BEST Java way I know - taking up 35 LOC to emulate 2 lines of Perl code which are very frequently used and useful data structure. And I only emulated a VERY simple example - as tchrist noted, the moment you decide that one of the first or second level keys will contain something OTHER than a 2-level hash, Java equivalent becomes pretty much impossible to write well whereas Perl STILL has 1 line of code. Efficient code, at that :) – DVK Jan 22 '11 at 20:50
  • Every benchmark I have looked at shows Java outpacing Perl BY AN ORDER OF MAGNITUDE. Perhaps you could cite a sort which shows the opposite, because I have never seen any such benchmark. – Nathan Crause Jan 25 '11 at 02:22
  • Efficient code ... huh ... okay, so let's compare what the language's runtime themselves are doing. Perl has to implicitly inspect everything you pass around as a variable because it has absolutely no idea what the variable is at runtime. It has to inspect it - every "dynamic type" language has to do this. Unless I'm very much mistaken, I would guess that this would ... oh, I don't know ... slow things down a bit? I actually get pretty sick and tired with dinky languages always claiming to be "the shits" when anyone with a fraction of intellect can tell it's always going to be slower by nature – Nathan Crause Jan 25 '11 at 02:26
  • So ... you know ... write 1 or 2 lines of code, by all means, and I'll write code that's faster. C'est la vie. – Nathan Crause Jan 25 '11 at 02:26
  • Alas, the really silly thing here is that I actually LIKE Perl (I actually enjoy most language - LISP, Java, Perl, Python, PHP, even Fortran). My ire was only raised by the "its partisans will often berate you" jibe. Completely unwarranted comment. However, to prove my own personal rant, I shall spend the next few days using this precise example to compare the two, and we shall see if I'm full it, or if I was right. – Nathan Crause Jan 25 '11 at 04:47
  • It's probably not exactly authoritative, but here's a citation: http://www.bioinformatics.org/benchmark/results.html Oh, but I still suspect my comments will get negative ratings, regardless of any citations or benchmarks I can provide. – Nathan Crause Jan 25 '11 at 04:54