1

I am doing the following coding challenge in java:

/**
     * 4. Given a word, compute the scrabble score for that word.
     * 
     * --Letter Values-- Letter Value A, E, I, O, U, L, N, R, S, T = 1; D, G = 2; B,
     * C, M, P = 3; F, H, V, W, Y = 4; K = 5; J, X = 8; Q, Z = 10; Examples
     * "cabbage" should be scored as worth 14 points:
     * 
     * 3 points for C, 1 point for A, twice 3 points for B, twice 2 points for G, 1
     * point for E And to total:
     * 
     * 3 + 2*1 + 2*3 + 2 + 1 = 3 + 2 + 6 + 3 = 5 + 9 = 14
     * 
     * @param string
     * @return
     */

My idea is to insert all these letters in a hash map by doing something like this:

map.add({A,,E,I,O,U,L,N,R,S,T}, 1);

Is there any way to do this in java?

patzi
  • 353
  • 4
  • 13
  • Sure. `map.put('A', 1); map.put('E', 1);` etc. Or just use a loop over the array of keys. – JB Nizet Mar 03 '18 at 16:43
  • so I can't do this in one statement. – patzi Mar 03 '18 at 16:43
  • Yes, you can, using the stream api, or List.forEach for example. – JB Nizet Mar 03 '18 at 16:45
  • 1
    Note that putting collections as keys is very inefficient since hash computation needs to be done often which requires to iterate the whole collection of the key over and over. Of course you can counter this with a custom collection that caches its hash value or with IdentityHashMaps that ignore the hash values of the collection and instead only compare the identity. – Zabuzard Mar 03 '18 at 16:56
  • "What I am actually looking for is an statement in which I can store all these letters with the same value in a hash_map." So you should mention that in the question. If you want a single statement, say so. Although that may not be a great thing to ask for. – Patrick Parker Mar 03 '18 at 16:59

5 Answers5

4

You said in your comments that you would like to be able to add all these entries in a single statement. While Java is not a great language for doing things like this in a single statement, it can be done if you are really determined to do so. For example:

Map<Character, Integer> scores =
    Stream.of("AEIOULNRST=1","DG=2","BCMP=3","FHVWY=4" /* etc */ )
        .flatMap(line -> line.split("=")[0].chars().mapToObj(c -> new Pair<>((char)c, Integer.parseInt(line.split("=")[1]))))
        .collect(Collectors.toMap(Pair::getKey, Pair::getValue));

System.out.println("C = " + scores.get('C'));

Output:

C = 3

In the code above, I first build a stream of all the entries (as Pairs), and collect them into a map.

Note:

The Pair class I have used above is from javafx.util.Pair. However you could just as easily use AbstractMap.SimpleEntry, your own Pair class, or any collection data type capable of holding two Objects.


A Better Approach

Another idea would be to write your own helper method. This method could be put into a class which contains similar helper methods. This approach would be more idiomatic, easier to read, and thus easier to maintain.

public enum MapHelper {
; // Utility class for working with maps
public static <K,V> void multiKeyPut(Map<? super K,? super V> map, K[] keys, V value) {
for(K key : keys) {
    map.put(key, value);
}}}

Then you would use it like this:

Map<Character, Integer> scores = new HashMap<>();
MapHelper.multiKeyPut(scores, new Character[]{'A','E','I','O','U','L','N','R','S','T'}, 1);
MapHelper.multiKeyPut(scores, new Character[]{'D','G'}, 2);
MapHelper.multiKeyPut(scores, new Character[]{'B','C','M','P'}, 3);
/* etc */
Patrick Parker
  • 4,863
  • 4
  • 19
  • 51
1

Take an array of length 26, each element representing an alphabet's score. So, we will have an array like this:-

alphabetScore = [1,3,3,2,.....................];

Now, iterate over the word, and keep adding the score of the current alphabet in the total score.

ncoder
  • 151
  • 1
  • 8
  • I don't see how this answer has anything to do with the question, how to insert same value to a map multiple times with different keys...? – hyde Mar 03 '18 at 16:48
  • Because this question might be an X/Y problem. I would personally favor this approach over a `HashMap`. – M. le Rutte Mar 03 '18 at 16:54
  • This approach is more efficient but more prone to error I think. I may misplace some number; – patzi Mar 03 '18 at 17:02
  • it’s not necessarily error-prone: just use the char literal, both when initializing and when accessing. – Patrick Parker Mar 04 '18 at 04:15
1

I think it's not a good idea to store list of more characters as keys (take a look at this question) and single value corresponding to this key, but if you really need that, you might want to give a try to this:

Map<ArrayList<Character>, Integer> map = new HashMap<>();
map.put(new ArrayList<Character>(Arrays.asList('A', 'E',...)), 1);
map.put(new ArrayList<Character>(Arrays.asList('D', 'G',...)), 2);

Personally, I would suggest using HashMap<Integer, ArrayList<Character>> - keys are "values" of a set of letters (e. g. key would be 1 for ArrayList containg letters: A, E, etc.), as a value corresponding to that Integer key could be ArrayList storing characters (A, E,...). You can achieve that result with:

Map<Integer, ArrayList<Character>> map = new HashMap<>();
map.put(1, new ArrayList<Character>(Arrays.asList('A', 'E',...)));
map.put(2, new ArrayList<Character>(Arrays.asList('D', 'G',...)));
Przemysław Moskal
  • 3,551
  • 2
  • 12
  • 21
  • In your example, how would one retrieve the letter score for a single letter? – M. le Rutte Mar 03 '18 at 16:53
  • What I am actually looking for is an statement in which I can store all these letters with the same value in a hash_map. – patzi Mar 03 '18 at 16:56
  • for (Map.Entry, Integer> entry : map.entrySet()) { if (entry.getKey().contains('a')) { int value = entry.getValue(); break;}} – Przemysław Moskal Mar 03 '18 at 16:57
  • Note that putting collections as keys is very inefficient since hash computation needs to be done often which requires to iterate the whole collection of the key over and over. Of course you can counter this with a custom collection that caches its hash value or with IdentityHashMaps that ignore the hash values of the collection and instead only compare the identity. – Zabuzard Mar 03 '18 at 16:57
  • @Zabuza I understand it's a bad idea, I would suggest OP to use "value" of a letter as a key, and list of letters could be stored as values to the corresonding Integer key. I was giving him a possibility how he could achieve that. – Przemysław Moskal Mar 03 '18 at 16:59
  • Yeah, absolutely. That's why I only add it as a note. – Zabuzard Mar 03 '18 at 17:03
  • @Zabuza Thank you for giving this explanation. I edited my answer to make it a bit more clear it's not the best idea to store ArrayList as key. – Przemysław Moskal Mar 03 '18 at 17:06
  • @patzi So store value as key, and set of letters as values corresponding to that Integer key. – Przemysław Moskal Mar 03 '18 at 17:08
  • this doesn't answer the question – Patrick Parker Mar 03 '18 at 17:18
  • @PatrickParker Why do you think first part of my answer doesn't answer the question? – Przemysław Moskal Mar 03 '18 at 17:22
  • you are misreading his intent from his pseudocode. he wants multiple entries – Patrick Parker Mar 03 '18 at 17:25
  • @PatrickParker I'm reading it on and on and can't figure out what's wrong with my solution :) I added two additional lines in my code to make it clear it allows multiple entries. – Przemysław Moskal Mar 03 '18 at 17:29
  • he wants multiple entries for 1, multiple entries for 2... each letter (key) should have its own entry. the asker's intent is clear from the question title and his followup comments. – Patrick Parker Mar 03 '18 at 17:46
0

Map has no methods that operate on multiple keys, but you could stream a list of these characters and call forEach:

Map<Character, Integer> scores = new HashMap<>();
Stream.of('A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T')
      .forEach(c -> scores.put(c, 1));
Stream.of('D', 'G').forEach(c -> scores.put(c, 2));
// etc...
Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • forEach(scores::put) could, in theory, result in concurrency issues. In general you should be wary of streams with side-effects. Instead, build a stream of all the entries and collect them into a map. – Patrick Parker Mar 03 '18 at 17:13
0

One line:

Map<String, Integer> map = Stream.of( "A", "E", "I", "O", "U", "L", "N", "R", "S", "T" ).collect( Collectors.toMap( Function.identity(), o -> 1 ) );

Or if you already have a list of strings

Collection<String> chars = new ArrayList<>();
// Add to collection
Map<String, Integer> map = chars.stream().collect( Collectors.toMap( Function.identity(), o -> 1 ) );

You can use the same method to add other keys with the same value

map.addAll( Stream.of( "F", "H", "V", "W", "Y" ).collect( Collectors.toMap( FUnction.identity(), o -> 4 );

Ultimately, it would be best to use a helper function for readability

private Map<String, Integer> mapScores( int score, String... letter ) {
    return Stream.of( letter ).collect( Collectors.toMap( Function.identity(), o -> score ) );
}

Map<String, Integer> map = new ConcurrentHashMap<>();
map.putAll( mapScores( 1, "A", "E", "I", "O", "U", "L", "N", "R", "S", "T" ) );
map.putAll( mapScores( 2, "D", "G" ) );
map.putAll( mapScores( 3, "B", "C", "M", "P" ) );
map.putAll( mapScores( 4, "F", "H", "V", "W", "Y" ) );
map.put( "K", 5 );
map.putAll( mapScores( 8, "J", "X" ) );
map.putAll( mapScores( 10, "Q", "Z" ) );