souki-paranoiastのブログ

地方都市でプログラマーをやっている人のブログ。技術ネタ以外も少し書く。メインの言語はJava。https://paranoiastudio-japan.jimdo.com/ に所属

【Java】AオブジェクトとBオブジェクトの紐づけ

本文

型自体が違うオブジェクトのリストが2つ存在し、その中身は一つのキーでペアになることができる前提の時、これをどうするか少し悩む時があるのでメモをしておく。

補足として、以下の考慮や前提がある

  • リストのサイズは一致しない可能性があること
  • 数が少なくなる可能性のある方のオブジェクトは空要素の概念が存在すること
  • キーとなるものはequalsが実装されていること(ないならPredicateを引数に追加する)
  • 紐づけたあと、片方のオブジェクトの値でソートができること

量が増える場合はMapなんかにして探索のコストを減らすべきな気がするが、50件以内程度ならこれでも十分でしょう。 下記例ではKEYは<KEY extends Comparable<KEY>>としてcompare() == 0みたいに比較しても良かったかもしれない

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) throws Exception {
        List<SortInfo> sortInfoList = new ArrayList<>();
        {
            sortInfoList.add(new SortInfo(1, 1, "cd"));
            sortInfoList.add(new SortInfo(1, 2, "name"));
            sortInfoList.add(new SortInfo(2, 1, "quantity"));
            sortInfoList.add(new SortInfo(3, 1, "note"));
        }

        List<DataElement> elementList = new ArrayList<>();
        {
            elementList.add(new DataElement("name", 100, "foo"));
            elementList.add(new DataElement("quantity", 120, "bar"));
            elementList.add(new DataElement("cd", 140, "hoge"));
            elementList.add(new DataElement("other1", 160, "not_exists_key1"));
            elementList.add(new DataElement("note", 180, "foga"));
            elementList.add(new DataElement("other2", 200, "not_exists_key2"));
        }


        List<Tuple2<DataElement, SortInfo>> zipList = associateTwoObject(
                elementList,
                element -> element.elementKey,
                sortInfoList,
                sortInfo -> sortInfo.elementKey,
                () -> new SortInfo(null, null, ""),
                Comparator.comparing((Tuple2<DataElement, SortInfo> tuple) -> tuple.t2.rowIndex, Comparator.nullsLast(Comparator.naturalOrder()))
                        .thenComparing((Tuple2<DataElement, SortInfo> tuple) -> tuple.t2.colIndex, Comparator.nullsLast(Comparator.naturalOrder()))
        );

        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(zipList));
    }

    static <ORIGINAL, SORT_SRC, KEY> List<Tuple2<ORIGINAL, SORT_SRC>> associateTwoObject(
            List<ORIGINAL> originalList,
            Function<ORIGINAL, KEY> originalKeyExtractor,
            List<SORT_SRC> sortSrcList,
            Function<SORT_SRC, KEY> sortSrcKeyExtractor,
            Supplier<SORT_SRC> sortSrcEmptySupplier,
            Comparator<Tuple2<ORIGINAL, SORT_SRC>> comparator
    ) {

        List<Tuple2<ORIGINAL, SORT_SRC>> bindList = originalList.stream()
                .map(original -> {
                    SORT_SRC sortSrc = sortSrcList.stream()
                            .filter(e -> {
                                KEY sortKey = sortSrcKeyExtractor.apply(e);
                                KEY originalKey = originalKeyExtractor.apply(original);
                                return Objects.equals(sortKey, originalKey);
                            })
                            .findFirst()
                            .orElseGet(sortSrcEmptySupplier);
                    return new Tuple2<>(original, sortSrc);
                })
                .collect(Collectors.toList());


        return bindList.stream()
                .sorted(comparator)
                .collect(Collectors.toList());
    }

    public static class SortInfo {
        public final Integer rowIndex;
        public final Integer colIndex;
        public final String elementKey;

        public SortInfo(Integer rowIndex, Integer colIndex, String elementKey) {
            this.rowIndex = rowIndex;
            this.colIndex = colIndex;
            this.elementKey = elementKey;
        }
    }

    public static class DataElement {
        public final String elementKey;
        public final int attr1;
        public final String attr2;

        public DataElement(String elementKey, int attr1, String attr2) {
            this.elementKey = elementKey;
            this.attr1 = attr1;
            this.attr2 = attr2;
        }

    }

    public static class Tuple2<T1, T2> {
        public final T1 t1;
        public final T2 t2;

        public Tuple2(T1 t1, T2 t2) {
            this.t1 = t1;
            this.t2 = t2;
        }

    }
}

結果

[ {
  "t1" : {
    "elementKey" : "cd",
    "attr1" : 140,
    "attr2" : "hoge"
  },
  "t2" : {
    "rowIndex" : 1,
    "colIndex" : 1,
    "elementKey" : "cd"
  }
}, {
  "t1" : {
    "elementKey" : "name",
    "attr1" : 100,
    "attr2" : "foo"
  },
  "t2" : {
    "rowIndex" : 1,
    "colIndex" : 2,
    "elementKey" : "name"
  }
}, {
  "t1" : {
    "elementKey" : "quantity",
    "attr1" : 120,
    "attr2" : "bar"
  },
  "t2" : {
    "rowIndex" : 2,
    "colIndex" : 1,
    "elementKey" : "quantity"
  }
}, {
  "t1" : {
    "elementKey" : "note",
    "attr1" : 180,
    "attr2" : "foga"
  },
  "t2" : {
    "rowIndex" : 3,
    "colIndex" : 1,
    "elementKey" : "note"
  }
}, {
  "t1" : {
    "elementKey" : "other1",
    "attr1" : 160,
    "attr2" : "not_exists_key1"
  },
  "t2" : {
    "rowIndex" : null,
    "colIndex" : null,
    "elementKey" : ""
  }
}, {
  "t1" : {
    "elementKey" : "other2",
    "attr1" : 200,
    "attr2" : "not_exists_key2"
  },
  "t2" : {
    "rowIndex" : null,
    "colIndex" : null,
    "elementKey" : ""
  }
} ]

雑感

多分もっと計算量とかメモリなんかを考慮して書けるはず…。

素直に書いている分理解はすぐにできるけど、数が増えると微妙だよなー。