Home » Java » Refactoring Java code to make it reusable

Refactoring Java code to make it reusable

Posted by: admin October 26, 2017 Leave a comment

Questions:

I recently created a simple method that takes a HashMap and LinkedList as arguments. It iterates through the HashMap, find any two entries that follows these rules:

  1. Sum of keys of this entries must be divisible by 100
  2. Sum of keys must be less than 1000
  3. If the entry with the same value appears more than once the case should be skipped

Pairs that follows these rules are added to the LinkedList. It looks like this:

private static void compare2(HashMap<Integer,String> values,List<String>results){
    if (values.size()>1) {
        for(HashMap.Entry<Integer,String> entry1:values.entrySet()){
            for (HashMap.Entry<Integer,String> entry2:values.entrySet()){
                if (entry1.getValue().equals(entry2.getValue()))continue;
                if ((entry1.getKey() + entry2.getKey())%100 == 0 && (entry1.getKey() + entry2.getKey())<1000){
                    results.add(entry1.getKey() + "+" + entry2.getKey() + "=" + entry1.getKey() + entry2.getKey());
                    results.add(entry1.getValue());
                    results.add(entry2.getValue());
                }
            }
        }
    }
}

Now I wanted to create similar method that finds 3 entries that follows the same rules. Problem is that I would like to reuse existing code instead of copy/pasting this and modyfiyng, and I can’t seem to find a way to do that. I don’t mind if I need to change my method as long as the result is the same.

Answers:

You could make the amount of numbers a parameter: N.

You could stay using for loops, but an alternative example approach could be to refactor your method with lambda’s and streams as follows:

List<List<Map.Entry<Integer, String>>> compareN(HashMap<Integer, String> map, int n) {
    return map.entrySet().stream()
              .map(entry -> listOfNAccompanyingEntriesThatSatisfyTheConditions(entry, emptyList(), map, n - 1))
              .filter(list -> !list.isEmpty())
              .collect(toList());
}

Where the method listOfNAccompanyingEntriesThatSatisfyTheConditions is a recursive method:

private List<Map.Entry<Integer, String>>
                listOfNAccompanyingEntriesThatSatisfyTheConditions(Map.Entry<Integer, String> newEntry,
                                                                   List<Map.Entry<Integer, String>> selectedEntries,
                                                                   HashMap<Integer, String> originalMap,
                                                                   int n) {

        List<Map.Entry<Integer, String>> newSelectedEntries = join(newEntry, selectedEntries);

        if (n == 0) return satisifiesCondition(newSelectedEntries) ? selectedEntries : emptyList();

        return originalMap.entrySet().stream()
                          .filter(entry -> !selectedEntries.contains(entry) && !entry.equals(newEntry))
                          .map(entry -> listOfNAccompanyingEntriesThatSatisfyTheConditions(entry, newSelectedEntries, originalMap, n - 1))
                          .flatMap(Collection::stream)
                          .collect(toList());
    }

For every n, the method takes another shot at the original full list to accumulate a sublist that might fulfil the requirements. If the amount of numbers is reached (n==0), the recursion stops and the stop condition is verified:

private static boolean satisifiesCondition(List<Map.Entry<Integer, String>> entries) {
    int sum = sumOfTheKeysFrom(entries);
    return sum % 100 == 0 && sum < 1000;
}

However, this approach is really an exact translation of your implementation and has still the same couple of issues. For example, if you run it for

HashMap<Integer, String> map = new HashMap<Integer, String>() {{
        put(1, "fred");
        put(2, "anja");
        put(24, "tom");
        put(45, "eddy");
        put(22, "lenny");
        put(77, "tommy");
        put(55, "henry");
        put(43, "alfred");
    }};

You will yield double results and results with the same entry twice, such as:

[1=fred, 22=lenny, 1=fred, 77=tommy] [2=anja, 55=henry, 2=anja,
43=alfred]

These are however easily solved by some small tweaks.

If you are not familiar with streams, lambda’s or recursion, I’d suggest you implement the same with the imperative approach. Also be aware I did not care about performance in my example code, as I perceived this question as some kind of exercise.

Questions:
Answers:

Try something like this:

@SafeVarargs
private static void check( final List<String> results,
                           final Map.Entry<Integer, String>... vals )
{
    int sum = 0;
    for ( final Map.Entry<Integer, String> val : vals )
    {
        final Integer key = val.getKey();
        sum += null == key ? 0 : key;
    }
    if ( sum < 1000 && 0 == ( sum % 100 ) )
    {
        final StringBuilder result = new StringBuilder( 200 );
        for ( final Map.Entry<Integer, String> val : vals )
        {
            result.append( " + " ).append( val.getKey() );
        }
        results.add( result.append( " = " ).append( sum ).substring( 3 ) );
        for ( final Map.Entry<Integer, String> val : vals )
        {
            results.add( val.getValue() );
        }
    }
}

private static void compare2( final HashMap<Integer, String> values,
                              final List<String> results )
{
    if ( values.size() > 1 )
    {
        for ( final HashMap.Entry<Integer, String> entry1 : values.entrySet() )
        {
            for ( final HashMap.Entry<Integer, String> entry2 : values.entrySet() )
            {
                if ( entry1 == entry2 )
                    continue;
                check( results, entry1, entry2 );
            }
        }
    }
}

private static void compare3( final HashMap<Integer, String> values,
                              final List<String> results )
{
    if ( values.size() > 2 )
    {
        for ( final HashMap.Entry<Integer, String> entry1 : values.entrySet() )
        {
            for ( final HashMap.Entry<Integer, String> entry2 : values.entrySet() )
            {
                for ( final HashMap.Entry<Integer, String> entry3 : values.entrySet() )
                {
                    if ( entry1 == entry2 || entry1 == entry3 || entry2 == entry3 )
                        continue;
                    check( results, entry1, entry2, entry3 );
                }
            }
        }
    }
}