0

I have mobile numbers in database table column, in a format of country_code followed by mobile_number So Mobile Number format is like this,

+91123456789 // country code of India is +91 followed by mobile number
+97188888888 // Country code of UAE +971

I have one HashMap containing CountryCodes of 5 countries like this,

map.put("+91","India")
map.put("+94","Sri Lanka")
map.put("+881","Bangladesh")
map.put("+971","UAE")
map.put("+977","Nepal")

My Bean Structure is something like this

class UserDetails {

  // other fields
   String countryCode;
   String mobileNumber;
}

Now my task is to take the mobile number from Database table column and split it in two parts and set countryCode and mobileNumber, but country code length(in map's key) varies between 3 and 4. This checking can be done by using subString() and equals() but I don't think it's correct way, So what would be the elegant(may be checking in map key) way to solve this issue?

Govinda Sakhare
  • 5,009
  • 6
  • 33
  • 74
  • Do you want to store the split numbers back in the database? And is this is going to be a one-off operation? – rohitvats Aug 21 '16 at 13:09
  • I don't see any other solutions. You cannot guess a information you have not. – davidxxx Aug 21 '16 at 13:10
  • @rohitvats No, numbers are stored in merged format, but I have two two html controls one for country code(select list) and other is mobile number(textbox), so to show saved values I need this. – Govinda Sakhare Aug 21 '16 at 13:16
  • Just FYI, there are single-digit country codes. US (`+1`) and Russia (`+7`) come to mind. – Sergei Tachenov Aug 21 '16 at 13:17
  • @SergeyTachenov I know that, my app works only in these five countries, if have solution that handles this scenario, then that's cool too. – Govinda Sakhare Aug 21 '16 at 13:21
  • @piechuckerr OK, although you can split these in Java, can I ask why they are not simply being stored as two different fields in the DB? Are you getting these full numbers from some other source or are they being entered by the users in the first place? – rohitvats Aug 21 '16 at 13:31

3 Answers3

2

Although there is a library which seems to already do the trick, I think I'd go for an easy self-written solution:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class CountryExtractor {
    private static final Map<String, String> COUNTRY_MAPPING = new HashMap<>();

    static {
        COUNTRY_MAPPING.put("+91", "India");
        COUNTRY_MAPPING.put("+94", "Sri Lanka");
        COUNTRY_MAPPING.put("+881", "Bangladesh");
        COUNTRY_MAPPING.put("+971", "UAE");
        COUNTRY_MAPPING.put("+977", "Nepal");
    }

    public static void main(String[] args) {
        String[] inputs = new String[] { "+91123456789", "+97188888888" };

        for (String input : inputs) {
            System.out.println(Arrays.toString(parseNumber(input)));
        }
    }

    private static String[] parseNumber(String number) {
        for (String countryCode : COUNTRY_MAPPING.keySet()) {
            if (number.startsWith(countryCode)) {
                return new String[] { countryCode, number.replace(countryCode, "") };
            }
        }
        return new String[0];
    }
}

Output:

[+91, 123456789]
[+971, 88888888]

Note that this may not work correctly when a mobile prefix is a substring of another, but according to Wikipedia country calling codes are prefix codes and therefore guarantee that "there is no whole code word in the system that is a prefix (initial segment) of any other code word in the system".

Community
  • 1
  • 1
Marvin
  • 13,325
  • 3
  • 51
  • 57
  • 1
    This is bit inefficient approach as you need to iterate over all country codes in worst case. – Ashwinee K Jha Aug 21 '16 at 13:32
  • @AshwineeKJha +1. This is slow, although there should not be hundreds of countries you are kind of defeating the purpose of a map – Dici Aug 21 '16 at 13:37
  • Maybe. On the other hand this requires no extra logic for 1-digit, 2-digit, 3-digit or n-digit prefix handling. And unless it is proven to be too inefficient I'd argue that clarity is at least as important as efficiency. – Marvin Aug 21 '16 at 13:37
  • @Marvin Well you have a linear complexity rather than a constant execution time, and your code still does not protect you against three-letters codes being prefixes of four-letters codes. So yeah, it is inefficient and has the same flaws – Dici Aug 21 '16 at 13:39
  • First: See my Wikipedia links, there is no three-letter code being prefix of a four-letter code by definition. Second: There are not only three- or four-letter codes and my solution is the only one that would correctly handle those. – Marvin Aug 21 '16 at 13:41
  • @Marvin again, relying on such assumption is risky unless it is explicitely specified by an authoritative entity. If you had a link to the website of the *Supreme Council Of International Phone Numbers* stating that a 3-letters code will never be prefix of a 4-letters codes, then your assumption would be justified. Otherwise, it is flaky and fragile. Look the comments on the main thread, apparenty there are 1-letter codes, which breaks your assumption – Dici Aug 21 '16 at 13:46
  • @Marvin I would use your library. After diving a bit into this, each country has different formats for national phone numbers and the international codes can be anything between 1 letter to 6 letters (maybe even more). It's better to let this to someone who put a lot of thought into it, if the library is lightweigh enough – Dici Aug 21 '16 at 14:02
1

IMHO a single map is better. An example;

public static void main(String args[]) throws Exception {
    Map<String, String> map = new HashMap<>();
    put(map, "+91", "India");
    put(map, "+94", "Sri Lanka");
    put(map, "+881", "Bangladesh");
    put(map, "+971", "UAE");
    put(map, "+977", "Nepal");
    map = Collections.unmodifiableMap(map);

    String mobileNumber = "+91123456789";
    System.out.println(countryCode(map.keySet(), mobileNumber));
}

private static void put(Map<String, String> map, String key, String value) {
    if (countryCode(map.keySet(), key) != null) {
        throw new IllegalArgumentException("...");
    }
    map.put(key, value);
}

public static String countryCode(Set<String> countryCodes, String number) {
    if (number == null || number.length() < 3) {
        throw new IllegalArgumentException("...");
    }
    String code = number.substring(0, 3);
    if (!countryCodes.contains(code)) {
        if (number.length() > 3) {
            code = number.substring(0, 4);
            if (!countryCodes.contains(code)) {
                code = null;
            }
        } else {
            code = null;
        }
    }
    return code;
}
David Pérez Cabrera
  • 4,960
  • 2
  • 23
  • 37
  • `map` is not the best name for a set. And even for a map this does not really help to understand what the map contains – Dici Aug 21 '16 at 13:18
  • 2
    Plus, same question as for the other answer: how do you know a three letters code can never be a prefix of any four letters code ? I would much rather have logic using the total length of the phone numbr to determine the length of the code – Dici Aug 21 '16 at 13:20
  • @Dici I know it, I have used proper names in my real code. – Govinda Sakhare Aug 21 '16 at 13:22
  • 1
    @Dici Edited again. – David Pérez Cabrera Aug 21 '16 at 13:26
  • @DavidPérezCabrera that's a good check to add, although it complicates things a bit. What would happen if this exception was thrown ? What action should be taken ? But again, it's also a good thing that you added this – Dici Aug 21 '16 at 13:35
  • That's not what I meant. If I first add `+91` and then `+911`, your code will throw an exception. Actually your code will even throw a `StringOutOfBoundsException` for any code of length 3 or less which would not be in the map – Dici Aug 21 '16 at 13:42
  • 1
    @Dici sorry I did not understand you, Edited again. – David Pérez Cabrera Aug 21 '16 at 14:02
  • 1
    @DavidPérezCabrera no prob. I deleted my answer as it was relying on the wrong assumption that all national (without country code) mobile numbers are 8-digits. Diving a bit deeper into this, I can see that there are lots of different formats for a mobile number and lots of different lengths for a country code. I think the best is to not do it yourself, there are too many edge cases. Better take it from a library or not try to support all use-cases and be very explicit about the limitations of the implementation – Dici Aug 21 '16 at 14:05
0

You could use two maps for country code of different lengths and then search first for a match with 3 letters, and then with 4 letters.

    HashMap<String, String > threeLetterCodes = new HashMap<String, String>();
    threeLetterCodes.put("+91","India");
    threeLetterCodes.put("+94","Sri Lanka");


    HashMap<String, String > fourLetterCodes = new HashMap<String, String>();        
    fourLetterCodes.put("+881","Bangladesh");
    fourLetterCodes.put("+971","UAE");
    fourLetterCodes.put("+977","Nepal");

    String test = "+97188888888";

    String prefix = test.substring(0, 3);
    String country = threeLetterCodes.get(prefix);
    if (country == null) {
        prefix = test.substring(0, 4);
        country = fourLetterCodes.get(prefix);
    }

    System.out.println(country);

Output:

UAE
Ashwinee K Jha
  • 9,187
  • 2
  • 25
  • 19
  • 2
    Don't think this is any simpler than having one single map. Plus, is it guaranteed that no 3 letters code is prefix of at least one 4 letters code ? – Dici Aug 21 '16 at 13:17
  • Hmm, yes single map could be used here. Looking at https://en.wikipedia.org/wiki/List_of_country_calling_codes it appears that the assumption that 3 letter prefix is never prefix of a 4 letter code is true. – Ashwinee K Jha Aug 21 '16 at 13:30
  • It may be the case that this is part of the specification of country codes, but it may also not be. If I could be certain that a never code would never be introduced with such property, then this could would be safer but it's still better if we can avoid relying on assumptions – Dici Aug 21 '16 at 13:34