【Java】商品一覧をソートし、商品タイプごとにソート順が1位の商品を抽出する

実行環境

やりたいこと

  • 商品一覧をソートする
  • 商品タイプごとにソート順が1位の商品を抽出する
  • 抽出結果をソートする

単一ソートの場合

  • 価格の降順でソートする
  • 商品タイプごとにソート順が1位の商品に絞る

【インプット】

商品名 商品タイプ 価格
N1 T1 1000
N2 T1 2000
N3 T1 3000
N4 T2 4000
N5 T2 5000
N6 T2 6000
N7 T3 7000
N8 T3 8000
N9 T3 9000

【アウトプット】

商品名 商品タイプ 価格
N9 T3 9000
N6 T2 6000
N3 T1 3000

【やり方】

  1. Collectors.groupingBy()で商品タイプごとにグルーピングする
  2. グルーピングする際にCollectors.maxBy()で価格が最も高い商品を抽出する
  3. グルーピング結果を価格の降順でソートする
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;

public class SingleSortTest {

    // 商品
    @Getter
    @ToString
    @AllArgsConstructor
    public class Item {
        // 商品名
        private String name;
        // 商品タイプ
        private String type;
        // 価格
        private int price;
    }

    @Test
    public void test() {
        // 商品一覧
        Item item1 = new Item("N1", "T1", 1000);
        Item item2 = new Item("N2", "T1", 2000);
        Item item3 = new Item("N3", "T1", 3000);
        Item item4 = new Item("N4", "T2", 4000);
        Item item5 = new Item("N5", "T2", 5000);
        Item item6 = new Item("N6", "T2", 6000);
        Item item7 = new Item("N7", "T3", 7000);
        Item item8 = new Item("N8", "T3", 8000);
        Item item9 = new Item("N9", "T3", 9000);
        List<Item> items = Arrays.asList(item1, item2, item3, item4, item5, item6, item7, item8, item9);
        System.out.println("items=" + items);

        // 商品タイプごとに最大価格の商品を抽出
        Map<String, Optional<Item>> groupedItems = items.stream()
                .collect(Collectors.groupingBy(Item::getType,
                        Collectors.maxBy(Comparator.comparingInt(Item::getPrice))));
        System.out.println("groupedItems=" + groupedItems);

        // 価格の降順でソート
        List<Item> sortedItems = groupedItems.values().stream()
                .map(Optional::get)
                .sorted(Comparator.comparing(Item::getPrice).reversed())
                .collect(Collectors.toList());
        System.out.println("sortedItems=" + sortedItems);

        // 結果検証
        assertThat(sortedItems, contains(item9, item6, item3));
    }

}

コンソール ※見やすいように改行してる

items=[
SingleSortTest.Item(name=N1, type=T1, price=1000), 
SingleSortTest.Item(name=N2, type=T1, price=2000), 
SingleSortTest.Item(name=N3, type=T1, price=3000), 
SingleSortTest.Item(name=N4, type=T2, price=4000), 
SingleSortTest.Item(name=N5, type=T2, price=5000), 
SingleSortTest.Item(name=N6, type=T2, price=6000), 
SingleSortTest.Item(name=N7, type=T3, price=7000), 
SingleSortTest.Item(name=N8, type=T3, price=8000), 
SingleSortTest.Item(name=N9, type=T3, price=9000)]
groupedItems={
T1=Optional[SingleSortTest.Item(name=N3, type=T1, price=3000)], 
T2=Optional[SingleSortTest.Item(name=N6, type=T2, price=6000)], 
T3=Optional[SingleSortTest.Item(name=N9, type=T3, price=9000)]}
sortedItems=[
SingleSortTest.Item(name=N9, type=T3, price=9000), 
SingleSortTest.Item(name=N6, type=T2, price=6000), 
SingleSortTest.Item(name=N3, type=T1, price=3000)]

複数ソートの場合

  • 価格と数量の降順でソートする
  • 商品タイプごとにソート順が1位の商品に絞る

【インプット】

商品名 商品タイプ 価格 数量
N1 T1 1000 1
N2 T1 2000 1
N3 T1 3000 1
N4 T2 3000 2
N5 T2 3000 3
N6 T2 3000 4
N7 T3 3000 1
N8 T3 4000 1
N9 T3 5000 1

【アウトプット】

商品名 商品タイプ 価格 数量
N9 T3 5000 1
N6 T2 3000 4
N3 T1 3000 1

【やり方】

  1. 価格と数量の降順でソートする
  2. ソート順で商品タイプ一覧を抽出する
  3. 商品タイプごとに先頭(ソート順が1位)の商品を抽出する

※複数ソートの場合、groupingBy()とmaxBy()だと上手くいかない

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;

public class MultipleSortTest {

    // 商品
    @Getter
    @ToString
    @AllArgsConstructor
    public class Item {
        // 商品名
        private String name;
        // 商品タイプ
        private String type;
        // 価格
        private int price;
        // 数量
        private int quantity;
    }

    @Test
    public void test() {
        // 商品一覧
        Item item1 = new Item("N1", "T1", 1000, 1);
        Item item2 = new Item("N2", "T1", 2000, 1);
        Item item3 = new Item("N3", "T1", 3000, 1);
        Item item4 = new Item("N4", "T2", 3000, 2);
        Item item5 = new Item("N5", "T2", 3000, 3);
        Item item6 = new Item("N6", "T2", 3000, 4);
        Item item7 = new Item("N7", "T3", 3000, 1);
        Item item8 = new Item("N8", "T3", 4000, 1);
        Item item9 = new Item("N9", "T3", 5000, 1);
        List<Item> items = Arrays.asList(item1, item2, item3, item4, item5, item6, item7, item8, item9);
        System.out.println("items=" + items);

        // 価格と数量の降順でソート
        List<Item> sortedItems = items.stream()
                .sorted(Comparator.comparing(Item::getPrice).reversed()
                        .thenComparing(Comparator.comparing(Item::getQuantity).reversed()))
                .collect(Collectors.toList());
        System.out.println("sortedItems=" + sortedItems);

        // 商品タイプを抽出
        List<String> types = sortedItems.stream()
                .map(Item::getType)
                .distinct()
                .collect(Collectors.toList());
        System.out.println("types=" + types);

        // 商品タイプごとに先頭の商品を抽出
        List<Item> groupedSortedItems = types.stream()
                .map(type -> sortedItems.stream()
                        .filter(item -> item.getType().equals(type))
                        .findFirst())
                .map(Optional::get)
                .collect(Collectors.toList());
        System.out.println("groupedSortedItems=" + groupedSortedItems);

        // 結果検証
        assertThat(groupedSortedItems, contains(item9, item6, item3));
    }

}

コンソール ※見やすいように改行してる

items=[
MultipleSortTest.Item(name=N1, type=T1, price=1000, quantity=1), 
MultipleSortTest.Item(name=N2, type=T1, price=2000, quantity=1), 
MultipleSortTest.Item(name=N3, type=T1, price=3000, quantity=1), 
MultipleSortTest.Item(name=N4, type=T2, price=3000, quantity=2), 
MultipleSortTest.Item(name=N5, type=T2, price=3000, quantity=3), 
MultipleSortTest.Item(name=N6, type=T2, price=3000, quantity=4), 
MultipleSortTest.Item(name=N7, type=T3, price=3000, quantity=1), 
MultipleSortTest.Item(name=N8, type=T3, price=4000, quantity=1), 
MultipleSortTest.Item(name=N9, type=T3, price=5000, quantity=1)]
sortedItems=[
MultipleSortTest.Item(name=N9, type=T3, price=5000, quantity=1), 
MultipleSortTest.Item(name=N8, type=T3, price=4000, quantity=1), 
MultipleSortTest.Item(name=N6, type=T2, price=3000, quantity=4), 
MultipleSortTest.Item(name=N5, type=T2, price=3000, quantity=3), 
MultipleSortTest.Item(name=N4, type=T2, price=3000, quantity=2), 
MultipleSortTest.Item(name=N3, type=T1, price=3000, quantity=1), 
MultipleSortTest.Item(name=N7, type=T3, price=3000, quantity=1), 
MultipleSortTest.Item(name=N2, type=T1, price=2000, quantity=1), 
MultipleSortTest.Item(name=N1, type=T1, price=1000, quantity=1)]
types=[T3, T2, T1]
groupedSortedItems=[
MultipleSortTest.Item(name=N9, type=T3, price=5000, quantity=1), 
MultipleSortTest.Item(name=N6, type=T2, price=3000, quantity=4), 
MultipleSortTest.Item(name=N3, type=T1, price=3000, quantity=1)]

参考文献

blog1.mammb.com

www.zunouissiki.com

www.ne.jp

qiita.com

docs.oracle.com