1 /*
2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package org.openjdk.bench.java.util.stream.tasks.PhoneCode;
25 
26 import org.openjdk.bench.java.util.stream.tasks.DataProviders;
27 
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37 
38 /**
39  * The phone coder problem is trying to find full list of possible
40  * mnemonic combination of numbers.
41  *
42  * The solution is based on Martin Odersky's devoxx 2010 scala talk,
43  * where numbers are not allowed in the result, which is not really
44  * correct, but we don't care.
45  */
46 public class PhoneCodeProblem {
47     // Map Character 'A'-'Z' to digits "2"-"9", key is charCode
48     private static final Map<Integer, String> CHAR_CODE;
49     // Map a string of digits to a collection of dictionary words
50     private static final Map<String, List<String>> WORD_CODES;
51 
52     static {
53         HashMap<String, String> mnemonics = new HashMap<>(8);
54         mnemonics.put("2", "ABC");
55         mnemonics.put("3", "DEF");
56         mnemonics.put("4", "GHI");
57         mnemonics.put("5", "JKL");
58         mnemonics.put("6", "MNO");
59         mnemonics.put("7", "PQRS");
60         mnemonics.put("8", "TUV");
61         mnemonics.put("9", "WXYZ");
62 
63         CHAR_CODE = new ConcurrentHashMap<>();
64         mnemonics.entrySet().stream().forEach(e ->
65                 e.getValue().chars().forEach(c ->
66                     { CHAR_CODE.put(c, e.getKey()); } ));
67 
68         WORD_CODES = loadDictionary();
69         // System.out.println("Dictionary loaded with " + WORD_CODES.size() + " number entries");
70     }
71 
72     // Convert a word to its number form
wordToNumber(String word)73     private static String wordToNumber(String word) {
74         return word.chars().mapToObj(CHAR_CODE::get)
75                            .reduce("", String::concat);
76     }
77 
78     // Prepare number -> word lookup table
loadDictionary()79     private static Map<String, List<String>> loadDictionary() {
80         try (Stream<String> s = DataProviders.dictionary()) {
81             return s.filter(w -> w.length() > 1)
82                     .filter(w -> w.matches("[a-zA-Z]*"))
83                     .map(String::toUpperCase)
84                     .collect(Collectors.groupingBy(PhoneCodeProblem::wordToNumber));
85         } catch (Exception ex) {
86             ex.printStackTrace(System.err);
87             return Collections.emptyMap();
88         }
89     }
90 
wordsForNumber(String number)91     public static Collection<String> wordsForNumber(String number) {
92         Collection<String> rv = WORD_CODES.get(number);
93         return (null == rv) ? Collections.emptySet() : rv;
94     }
95 
get(int length)96     public static Stream<String> get(int length) {
97         String digits[] = { "2", "3", "4", "5", "6", "7", "8", "9" };
98 
99         Stream<String> s = Arrays.stream(digits);
100         for (int i = 1; i < length; i++) {
101             s = s.flatMap(d1 -> Arrays.stream(digits).map(d2 -> d1 + d2));
102         }
103         return s;
104     }
105 }
106