4

I'm trying to sort a stream by an specific order by one of its fields.

Now I'm achieving this by converting streams to list and using a switch and then rejoining them into a list in the desired order.


    fruits.forEach(fruit -> {
                switch (fruit.getName()) {
                    case "Orange":
                        orangesList.add(fruit);
                        break;
                    case "Apple":
                        applesList.add(fruit);
                        break;
                    case "WaterMelon":
                        watermelonList.add(fruit);
                        break;
                    default:
                        otherFruits.add(fruit);
                        break;
                }
    });

    genericFruitList.addAll(0, orangeList);
    genericFruitList.addAll(1, applesList);
    genericFruitList.addAll(2, watermelonList);
    genericFruitList.addAll(3, otherFruits);

I wonder if there's any change to achieve this using stream sorted method and using a custom comparator or something like that.

Thanks in advance.

Antonio682
  • 363
  • 1
  • 4
  • 18
  • 4
    `Collectors.groupingBy(Fruit::getName)` would group the fruits by name, as in the switch. You could then do the `addAll`s like this. – Andy Turner Sep 08 '17 at 11:52
  • Creating a helper class may give you appropriate list:fruitHelper.getList(fruit.getName) – ihsan kocak Sep 08 '17 at 11:53
  • 3
    Note that your current logic is strange. Let's say you've got two of each type of fruit: your resulting `genericFruitList` would be `orange, apple, watermelon, other, other, watermelon, apple, orange`. Do you mean to specify the insertion point, or do you just intend to add them all at the end? – Andy Turner Sep 08 '17 at 12:00

2 Answers2

6

You can create a comparator using an explicit order like

List<String> order = Arrays.asList("Orange", "Apple", "WaterMelon");
Comparator<String> comp
    = Comparator.comparingInt(name -> order.indexOf(name)-Integer.MIN_VALUE);

which can be used like

List<Fruit> genericFruitList = fruits
    .sorted(Comparator.comparing(fruit -> fruit.getName(), comp))
    .collect(Collectors.toList());

however, sorting the entire list, especially with an List.indexOf based comparator, can be quiet inefficient. An alternative would be

List<Fruit> genericFruitList = fruits
    .collect(Collectors.groupingBy(fruit -> fruit.getName()))
    .entrySet().stream()
    .sorted(Map.Entry.comparingByKey(comp))
    .flatMap(e -> e.getValue().stream())
    .collect(Collectors.toList());

which just performs a hash lookup per Fruit and only sort the distinct mappings.

This can be seen as a variant of Bucket Sort.

Holger
  • 285,553
  • 42
  • 434
  • 765
5

If you want to sort the fruit into a particular order (oranges first, then apples, then watermelon, then "other"), you could define a comparator thus:

List<String> order = Arrays.asList("Orange", "Apple", "Watermelon");
Comparator<Fruit> comparator = Comparator.comparing(f -> {
  int i = order.indexOf(f.getName());
  return (i >= 0) ? i : order.size();
});

and then sort:

List<Fruit> genericFruitList = fruits.stream().sorted(comparator).collect(Collectors.toList());
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • btw guava has `Ordering.explicit(List)` that could used here too. – Eugene Sep 08 '17 at 12:04
  • 1
    @Eugene ["If you are using Java 8, this class is now obsolete"](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/Ordering.html). Additionally, that doesn't handle "other" values. – Andy Turner Sep 08 '17 at 12:16