如何通过比较值来对HashMap的条目进行排序,其中每个值都是int []?

时间:2017-02-11 06:01:37

标签: java sorting dictionary java-8 comparator

我将HashMap定义为HashMap<String, int[]>。值为int[],其中只有2个数字。我想要做的是通过这两个数字的总和对这个HashMap的条目进行排序。

这就是我所拥有的。我正在使用Java 8.我只需要添加我在ints中对2 int[]求和的部分,并将其作为一个数字处理,然后按照下面的方式排序,但我不知道如何添加该部分。

hm.entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))

5 个答案:

答案 0 :(得分:3)

以下是Java 8 Comparator lambdas的解决方案:

map.entrySet().stream()
              .sorted(Map.Entry.comparingByValue(
                      (v1, v2) -> v2[0] + v2[1] - v1[0] - v1[1]));

请注意,此解决方案存在溢出/下溢的风险,请参阅leeyuiwah answer以获得更好的解决方案。我们的想法是使用comparingLong方法。

答案 1 :(得分:1)

我很确定您无法使用HashMap创建排序元素。 我的建议是使用另外两张地图:

listInner::contains

首先,您需要将hm地图复制到tempMap中,TreeMap中的自然顺序将按升序排序整数键。

在tempMap中获取排序结果后,可以复制到resultMap中以获得最终结果。

&#34;复印&#34;意味着您迭代旧地图,然后将(键,值)放在新地图中。

这种方法会花费你双倍内存,但它会以复杂度O(n lg n)运行。

如果您想使用Comparator,可以使用以下方法:

public class Combiner {

    public static void main(String[] args) {
        List<List<Integer>> mainList = new ArrayList<>();
        mainList.add(Arrays.asList(1, 2));
        mainList.add(Arrays.asList(4, 5));
        mainList.add(Arrays.asList(7, 8));
        mainList.add(Arrays.asList(6, 19));

        mainList.add(Arrays.asList(2, 3, 5, 7));

        System.out.println(combineList(new ArrayList<>(mainList)));
        List<List<Integer>> result = mergeCollections(new ArrayList<>(mainList));
        System.out.println(result);

    }

    public static <T> List<List<T>> combineList(List<List<T>> argList) {
        List<List<T>> result = new ArrayList<>();
        for (List<T> list : argList) {
            //Copy the given list
            List<T> addedList = new ArrayList<>(list);
            result.add(addedList);
            for (List<T> otherList : argList) {
                if (list.equals(otherList)) continue;
                //If at least one element is shared between the two lists
                if (list.stream().anyMatch(otherList::contains)) {
                    //Add all elements that are exclusive to the second list
                    addedList.addAll(otherList.stream().map(t -> addedList.contains(t) ? null : t)
                            .filter(t -> t != null).collect(Collectors.toList()));
                }
            }
        }
        List<List<T>> del = new ArrayList<>();
        for (int i = 0; i < result.size(); i++) {
            for (int j = i + 1; j < result.size(); j++) {
                List<T> list = result.get(j);
                if (listEqualsUnOrdered(list, result.get(i))) {
                    //Modified this
                    del.add(result.get(i));
                }
            }
            //Can't use listIterator here because of iterating starting at j + 1
            result.removeAll(del);
        }
        //Recursion
        if (!result.equals(argList)) {
            result = combineList(result);
        }
        return result;
    }

    private static <T> boolean listEqualsUnOrdered(List<T> list1, List<T> list2) {
        if (list1.size() != list2.size()) return false;
        List<T> testOne = new ArrayList<>(list1);
        testOne.removeAll(list2);
        boolean testOnePassed = (testOne.size() == 0);
        List<T> testTwo = new ArrayList<>(list2);
        testTwo.removeAll(list1);
        if (testTwo.size() == 0 && testOnePassed) return true;
        return false;
    }
}

答案 2 :(得分:1)

这个答案实际上是受到Hesham Attia的另一个答案的启发,可以作为问题的替代解决方案。

但是,我也借此机会讨论溢出int数据类型的潜在问题(详见下文)。

此解决方案使用带有Comparator.comparingLong()的界面keyExtractor,而不是带Map.Entry.comparingByValue()的界面comparator

(如果我们不够谨慎,两者都可能遭遇数据溢出 - 请参阅下面的测试2和3。)

hm.entrySet()
    .stream()
    .sorted(Comparator.comparingLong(
                e -> ((long) e.getValue()[0])+e.getValue()[1]
           ))

这是一个完整的测试程序,它演示了中间失败的测试2和3。排序的正确答案应该是e, b, c, a, d(显示在测试4中)

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class StreamSortedIntArray {

    public static void main(String[] args) {
        Map<String, int[]> hm = new HashMap<>();
        hm.put("a", new int[]{3, 1});
        hm.put("b", new int[]{1, 1});
        hm.put("c", new int[]{2, 1});
        hm.put("d", new int[]{Integer.MAX_VALUE, 1});
        hm.put("e", new int[]{Integer.MIN_VALUE, 1});

        // Test 1: 
        System.out.println("Test 1: hm before sorting: ");

        hm.entrySet()
            .stream()
            .forEach(StreamSortedIntArray::printEntry);

        // Test 2: 
        System.out.println("Test 2: hm after sort -- using Map.Entry.comparingByValue()"); 
        hm.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue(
                        (v1, v2) -> v2[0] + v2[1] - v1[0] - v1[1]))
            .forEach(StreamSortedIntArray::printEntry);

        // Test 3: After sorting -- using Comparator.comparingLong()
        // WITHOUT protection against data overflowing the int type
        System.out.println("Test 3: hm after sorting: (using Comparator.comparingLong())"); 
        System.out.println("WITHOUT protection against data overflowing the int type"); 
        hm.entrySet()
            .stream()
            .sorted(Comparator.comparingLong(
                        e -> e.getValue()[0]+e.getValue()[1]
                   ))
            .forEach(StreamSortedIntArray::printEntry);

        // Test 4: After sorting -- using Comparator.comparingLong()
        // WITH protection against data overflowing the int type
        System.out.println("Test 4: hm after sorting: (using Comparator.comparingLong())"); 
        System.out.println("WITH protection against data overflowing the int type"); 
        hm.entrySet()
            .stream()
            .sorted(Comparator.comparingLong(
                        // protection against overflowing the int type
                        // cast to long before the sum operation
                        e -> ((long) e.getValue()[0])+e.getValue()[1]
                   ))
            .forEach(StreamSortedIntArray::printEntry);


    }

    public static void printEntry(Map.Entry<String, int[]> e) {
        String message
            = String.format("%s: %20s; sum=%d"
                            , e.getKey()
                            , Arrays.toString(e.getValue())
                            , ((long)(e.getValue()[0])+e.getValue()[1]));
        System.out.println(message);
    }

}

此程序的输出 - 测试4显示正确答案,但测试2和3不显示:

Test 1: hm before sorting: 
a:               [3, 1]; sum=4
b:               [1, 1]; sum=2
c:               [2, 1]; sum=3
d:      [2147483647, 1]; sum=2147483648
e:     [-2147483648, 1]; sum=-2147483647
Test 2: hm after sort -- using Map.Entry.comparingByValue()
e:     [-2147483648, 1]; sum=-2147483647
d:      [2147483647, 1]; sum=2147483648
a:               [3, 1]; sum=4
c:               [2, 1]; sum=3
b:               [1, 1]; sum=2
Test 3: hm after sorting: (using Comparator.comparingLong())
WITHOUT protection against data overflowing the int type
d:      [2147483647, 1]; sum=2147483648
e:     [-2147483648, 1]; sum=-2147483647
b:               [1, 1]; sum=2
c:               [2, 1]; sum=3
a:               [3, 1]; sum=4
Test 4: hm after sorting: (using Comparator.comparingLong())
WITH protection against data overflowing the int type
e:     [-2147483648, 1]; sum=-2147483647
b:               [1, 1]; sum=2
c:               [2, 1]; sum=3
a:               [3, 1]; sum=4
d:      [2147483647, 1]; sum=2147483648

通过简单减法实现比较器的危险

这个问题在上面的测试程序(测试2)中显示,但也是如此 在Oracle / Sun对象排序教程中警告

https://docs.oracle.com/javase/tutorial/collections/interfaces/order.html

  

最后一点:您可能想要替换最终的回报   比较器中的声明更简单:

     

return e1.number() - e2.number();

     

除非你是绝对的,否则不要这样做   确定没有人会有负面的员工编号!这个伎俩呢   通常不起作用,因为有符号整数类型不够大   表示两个任意有符号整数的差异。如果我是   一个大的正整数,j是一个大的负整数,i - j会   溢出并返回一个负整数。结果比较器   违反了我们一直在谈论的四项技术限制之一   (传递性)并产生可怕的,微妙的错误。 这不是   纯粹的理论关注;人们被它焚烧。

答案 3 :(得分:1)

更一般的解决方案,使用适当的比较器(它不会影响可能的溢出问题),这将适用于任何长度的数组:

1)因为您无法对不保留密钥顺序的HashMap进行排序,我们需要创建新地图 - LinkedHashMap

    Map<String, int[]> result = new LinkedHashMap<>();

2)自我排序:

    map.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(
                            (o1, o2) -> Integer.compare(Arrays.stream(o1).sum(), Arrays.stream(o2).sum()))
            )
    .forEach(se -> result.put(se.getKey(), se.getValue()));

UPD:亲爱的@Holger建议使用Comparator.comparingInt(o -> Arrays.stream(o).sum())看起来更紧凑,但做同样的工作。对我个人而言,我的版本看起来更容易理解,但Holger的版本更像是lambda-styler。

答案 4 :(得分:1)

最简单的恕我直言最好的方法是不使用Map.Entry比较方法,因为您不是通过键或值进行比较,而是使用派生值进行比较:

map.entrySet().stream()
    .sorted(Comparator.comparing(e -> 0 - e.getValue()[0] - e.getValue[1]))
    .forEach(<whatever>);

负值会产生代码建议你想要的颠倒顺序。