Matrix
Matrix

Reputation: 23

Grouping strings - Java

I have an ArrayList. I want to group similar items, such that the letter.pdf is the first one in each group. For example:

123_Letter.pdf
123_Others.pdf
123_More.pdf
222_Second.pdf
222_Letter.pdf
222_Third.pdf
222_Fourth.pdf
123_File.pdf

The output should be:

**123_Letter.pdf**
123_Others.pdf
123_More.pdf
123_File.pdf
**222_Letter.pdf**
222_Second.pdf
222_Third.pdf
222_Fourth.pdf

The order of other elements in each group doesn't matter. There are more than 3000 elements in the list. As you can see, merely sorting is not of much help.

I tried something like this, but it is missing the last element 123_File.pdf. Any better way of doing this? Please help.

String root = list.get(0).substring(0,4);

        ArrayList<String> al = new ArrayList<>();

        for (int i = 0; i < list.size(); i++) {
            while (list.get(i).substring(0, 4).equals(root)) {
                if (list.get(i).endsWith("etter.pdf")) {
                    al.add(0, list.get(i));
                    i++;
                } else {
                    al.add(list.get(i));
                    i++;
                }
            }
            System.out.println(al);
            al = new ArrayList<>();
            root = list.get(i).substring(0, 4);
        }

Upvotes: 0

Views: 2512

Answers (3)

Bill Horvath
Bill Horvath

Reputation: 1717

It sounds like you want to sort according to two criteria:

  • The first three characters in each list item, and
  • The content of the rest of each list item, such that Letter.pdf appears first.

One reasonable solution that's old-school is to implement a custom java.util.Comparator, and use that as the basis of the sort:

public static List<String> sortOldSchool(List<String> list){

    Comparator<String> comparator = new Comparator<String>(){
        private static final String LETTER = "Letter.pdf";
        public int compare(String orange, String apple){
            String ostart = orange.substring(0,3);
            String astart = apple.substring(0,3);
            if (ostart.equals(astart)){
                if (orange.endsWith(LETTER)){
                    return (apple.endsWith(LETTER)) ? 0 : -1;
                }
                else return (apple.endsWith(LETTER)) ? 1 : orange.compareTo(apple);
            }
            else return ostart.compareTo(astart);
        }
    };
    Collections.sort(list, comparator);
    return list;
}

A more modern approach would be to leverage the new functional paradigms available starting with Java 8:

public static List<String> sortFunctional(List<String> list){

    Comparator<String> firstThree= Comparator.comparing(item -> item.substring(0,3));
    Comparator<String> letter = Comparator.comparing(item -> (!item.endsWith("Letter.pdf")));
    return list.stream()
            .sorted(firstThree.thenComparing(letter))
            .collect(Collectors.toList());
}    

Upvotes: 3

edwardsmatt
edwardsmatt

Reputation: 2044

Based on what you have described - I would suggest breaking the problem into two sub-problems:

  • Step 1: Organise the values into their prefix groups
  • Step 2: Sort the values in each prefix group ensuring the value "_Letter.pdf" is first.

I have provided a code sample demonstrating each of the steps below:

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

public class StackoverflowTest {

    List<String> values = Arrays.asList(
            "123_Letter.pdf",
            "123_Others.pdf",
            "123_More.pdf",
            "222_Second.pdf",
            "222_Letter.pdf",
            "222_Third.pdf",
            "222_Fourth.pdf",
            "123_File.pdf");

    List<String> expected = Arrays.asList(
            "123_Letter.pdf",
            "123_Others.pdf",
            "123_More.pdf",
            "123_File.pdf",
            "222_Letter.pdf",
            "222_Second.pdf",
            "222_Third.pdf",
            "222_Fourth.pdf"
    );

    @Test
    public void sort() {
        // Basic function to group the values by the prefix
        Collector<String, ?, Map<String, List<String>>> groupByPrefix = Collectors.groupingBy(c -> c.substring(0, 3));

        // Basic function to sort the groups with _Letter.pdf first
        Function<List<String>, Stream<? extends String>> sortPrefixGroups = v ->
                v.stream().sorted(Comparator.comparing(i -> !i.endsWith("_Letter.pdf")));

        // Step 1: Organise the values into their prefix groups
        Map<String, List<String>> groupedByPrefix = new TreeMap<>(values.stream().collect(groupByPrefix));


        // Step 2: Sort each of the prefix groups and recombine them back into a list to maintain their internal order
        List<String> collect = groupedByPrefix.entrySet().stream().map(Map.Entry::getValue).flatMap(sortPrefixGroups).collect(toList());

        Assert.assertEquals(expected, collect);
        //Print just for fun
        collect.forEach(System.out::println);
    }
}

Upvotes: 0

Gauravsa
Gauravsa

Reputation: 6528

How about using a HashMap:

HashMap<String, List<String>> hashMap = new HashMap<String, List<String>>();

Now, iterate over the hashmap and add elements:

if (!hashMap.containsKey(listItem.substring(0,2)) {
    List<String> items = new ArrayList<String>();
    items.add(listItem);

    hashMap.put(listItem.substring(0,2), items);
} else {
    hashMap.get(listItem.substring(0,2)).add(items);
}

Upvotes: 0

Related Questions