0

I am working on 'Grouping Anagrams'. Problem statement: Given an array of strings, group anagrams together.

I could group the anagrams but I am not able to avoid the ones which are already grouped. I want to avoid duplicates. An element can only belong to one group. In my code, an element belongs to multiple groups.

Here is my code:

       public class GroupAnagrams1 {

           public static void main(String[] args) {
                 String[] input = {"eat", "tea", "tan", "ate", "nat", "bat"};
                 List<List<String>> result = groupAnagrams(input);
                 for(List<String> s: result) {
                      System.out.println(" group: ");
                            for(String x:s) {
                                System.out.println(x);
                            }
                   }
      }

      public static List<List<String>> groupAnagrams(String[] strs) {

            List<List<String>> result = new ArrayList<List<String>>();

            for(int i =0; i < strs.length; i++) {
                Set<String> group = new HashSet<String>();
                   for(int j= i+1; j < strs.length; j++) {
                       if(areAnagrams(strs[i], strs[j])) {
                            group.add(strs[i]);
                            group.add(strs[j]);
                     }
            }

                 if(group.size() > 0) {
                      List<String> aList = new ArrayList<String>(group); 
                      result.add(aList);
                 }
           }
      return result;


    }

Here comes the method to check if two string are anagrams.

 private static boolean areAnagrams(String str1, String str2) {
         char[] a = str1.toCharArray();
         char[] b = str2.toCharArray();
        int[] count1 = new int[256];
        Arrays.fill(count1, 0);
        int[] count2 = new int[256];
        Arrays.fill(count2, 0);
        for(int i = 0; i < a.length && i < b.length; i++) {
           count1[a[i]]++;
           count2[b[i]]++;
         }
        if(str1.length() != str2.length())
              return false;
        for(int k=0; k < 256; k++) {
              if(count1[k] != count2[k])
                    return false;
        }
        return true;
      }
     }

expected output:

 group: 
    tea
    ate
    eat
 group: 
    bat
 group: 
    tan
    nat

actual output:

  group: 
     tea
     ate
     eat
  group: 
     tea
     ate
  group: 
     tan
     nat

The order in which the groups are displayed does not matter. The way it is displayed does not matter.

Preference: Please feel free to submit solutions using HashMaps but I prefer to see solutions without using HashMaps and using Java8

ilakk
  • 69
  • 1
  • 8
  • Look at your `if(areAnagrams(strs[i], strs[j]))` statement. You're only adding to a group if there are two matching. You'll never get "bat" in a group alone. – yhyrcanus May 02 '19 at 22:28

2 Answers2

1

I would have taken a slightly different approach using streams:

public class Scratch {
    public static void main(String[] args) {
        String[] input = { "eat", "tea", "tan", "ate", "nat", "bat" };

        List<List<String>> result = groupAnagrams(input);

        System.out.println(result);

    }

    private static List<List<String>> groupAnagrams(String[] input) {
        return Arrays.asList(input)
                     // create a list that wraps the array

                     .stream()
                     // stream that list

                     .map(Scratch::sortedToOriginalEntryFor)
                     // map each string we encounter to an entry containing
                     // its sorted characters to the original string

                     .collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList())))
                     // create a map whose key is the sorted characters and whose
                     // value is a list of original strings that share the sorted
                     // characters: Map<String, List<String>>

                     .values()
                     // get all the values (the lists of grouped strings)

                     .stream()
                     // stream them

                     .collect(Collectors.toList());
                     // convert to a List<List<String>> per your req
    }

    // create an Entry whose key is a string of the sorted characters of original
    // and whose value is original
    private static Entry<String, String> sortedToOriginalEntryFor(String original) {
        char c[] = original.toCharArray();
        Arrays.sort(c);
        String sorted = new String(c);

        return new SimpleEntry<>(sorted, original);
    }
}

This yields:

[[eat, tea, ate], [bat], [tan, nat]]

If you want to eliminate repeated strings (e.g. if "bat" appears twice in your input) then you can call toSet() instead of toList() in your Collectors.groupingBy call, and change the return type as appropriate.

Not a JD
  • 1,864
  • 6
  • 14
1

I also would recommend using java Streams for that. Because you don't want that here is another solution:

public static List<List<String>> groupAnagrams(String[] strs) {
    List<List<String>> result = new ArrayList<>();
    for (String str : strs) {
        boolean added = false;
        for (List<String> r : result) {
            if (areAnagrams(str, r.get(0))) {
                r.add(str);
                added = true;
                break;
            }
        }

        if (!added) {
            List<String> aList = new ArrayList<>();
            aList.add(str);
            result.add(aList);
        }
    }
    return result;
}

The problem in your solution is that you are moving each iteration one step ahead, so you just generate the not full complete group ["tea", "ate"] instead of ["bat"].

My solution uses a different approach to check if you have a group where the first word is an anagram for the searched word. if not create a new group and move on.

Because I would use Java Streams as I said at the beginning here is my initial solution using a stream:

List<List<String>> result = new ArrayList<>(Arrays.stream(words)
        .collect(Collectors.groupingBy(w -> Stream.of(w.split("")).sorted().collect(Collectors.joining()))).values());

To generate the sorted string keys to group the anagrams you can look here for more solutions.

The result is both my provided solutions will be this:

[[eat, tea, ate], [bat], [tan, nat]]
Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56