简体   繁体   English

按值排序的Map <K,V>的前N个值

[英]First N values of a Map<K, V> sorted by value

I have a list of Strings. 我有一个字符串列表。 I want to evaluate each string based on a function that returns a double. 我想基于返回double的函数来评估每个字符串。 Then I want the first 5 strings, based on their calculated values. 然后我想要前5个字符串,基于他们的计算值。 If there are fewer than 5, I want all of them (in order). 如果少于5,我想要所有这些(按顺序)。 Let's say the strings are chemical compounds and the function computes the mass. 假设字符串是化学化合物,函数计算质量。 The function is computationally expensive; 该功能在计算上很昂贵; I need to evaluate it once per string. 我需要每串评估一次。 (I'm just making up data here, though.) (不过我只是在这里编制数据。)

H2O => 18.5
C12H11O22 => 109.1
HeNe => 32.0
H2SO4 => 54.37
HCl => 19.11
4FeO3 => 82.39
Xe6 => 281.9

The program should return the first five strings arranged in order by their respective values. 程序应返回按其各自值排列的前五个字符串。 For this sample data: H20, HCl, HeNe, H2SO4, 4FeO3 . 对于该样品数据: H20, HCl, HeNe, H2SO4, 4FeO3 Actually, I don't really care about the order; 实际上,我并不关心订单; I just need the five lowest in any order. 我只需要任何顺序的五个最低点。

I thought about how I'd do this in Perl. 我想过如何在Perl中做到这一点。 It's just a few lines: 这只是几行:

foreach $s (@str) {
    $strmap{$s} = f($s);
}
@sorted = sort { $strmap{$a} <=> $strmap{$b} } keys %strmap;
return @sorted[0, 4]

But I need to do it in Java. 但我需要用Java来做。 And it's driving me crazy. 这让我发疯了。

First I tried populating a HashMap<String, Double> , then using Collections.sort with a custom comparator, just like the Perl version. 首先,我尝试填充HashMap<String, Double> ,然后使用Collections.sort和自定义比较器,就像Perl版本一样。 But scoping on the Comparator prevented it from referring to the HashMap to look up the values. 但是比较器的范围使它无法引用HashMap来查找值。

Then I tried a TreeMap<String, Double> , but it only sorts by key and no amount of coercing could get it to order the entries by value. 然后我尝试了一个TreeMap<String, Double> ,但它只按键进行排序,并且没有多少强制可以让它按值排序。

So I tried a TreeMap<Double, String> . 所以我尝试了一个TreeMap<Double, String> It will discard entries with the same Double. 它将丢弃具有相同Double的条目。 However, the likelihood of having Strings that map to the same Double is low, so I pressed forward. 但是,将字符串映射到同一个Double的可能性很低,所以我向前推进。 Adding the entries to the TreeMap is no problem, but I ran into issues trying to extract the values from it. 将条目添加到TreeMap没有问题,但是我试图从中提取值时遇到了问题。

TreeMap supplies a method called subMap , but its parameters are the keys that delimit the subset. TreeMap提供了一个名为subMap的方法,但其参数是分隔子集的键。 I don't know what they are; 我不知道它们是什么; I just want the first five of them. 我只想要前五个。 So I tried using the values method to get all the values out of the TreeMap, hoping they'd be in order. 所以我尝试使用values方法从TreeMap中获取所有值,希望它们按顺序排列。 Then I can just get the first ten. 然后我就可以获得前十名。

ArrayList<String> strs = (ArrayList<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));

Nope. 不。 Runtime error: cannot cast TreeMap$Values to ArrayList. 运行时错误:无法将TreeMap $ Values转换为ArrayList。

List<String> strs = (List<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));

Same. 相同。 Runtime error trying to do the cast. 尝试执行强制转换的运行时错误。 OK, let's just assign to a Collection... 好的,我们只是分配给一个集合......

Collection<String> strs = treemap.values();
return new ArrayList<String>(strs.subList(0, 5));

Sorry, subList isn't a method of Collection. 抱歉, subList不是Collection的方法。

Collection<String> strs = treemap.values();
ArrayList<String> a = new ArrayList<String>(strs);
return new ArrayList<String>(a.subList(0,  5));

Finally, something that works! 最后,有用的东西! But two extra data structures just to get the first five elements? 但两个额外的数据结构只是为了获得前五个元素? And I'm not too wild about using Double as the key for TreeMap. 而且我并不太喜欢使用Double作为TreeMap的关键。

Is there a better solution? 有更好的解决方案吗?

I don't think you'll get more compact than the three lines above, not in Java. 我不认为你会比上面的三行更紧凑,而不是Java。

Apart from that, I have the impression that a Map as a data structure is the wrong choice in the first place, since you do not seem to need by-string lookups (UNLESS you want in some way deal with multiple occurences of strings, but you didn't say so). 除此之外,我认为Map作为数据结构首先是错误的选择,因为你似乎不需要字符串查找(除非你想以某种方式处理多个字符串的出现,但是你没有这么说)。 An alternative approach would be to declare your own comparable data record class: 另一种方法是声明您自己的可比数据记录类:

private static class Record implements Comparable<Record> {
    // public final fields ok for this small example
    public final String string;
    public final double value;

    public Record(String string, double value) {
        this.string = string;
        this.value = value;
    }

    @Override
    public int compareTo(Record other) {
        // define sorting according to double fields
        return Double.compare(value, other.value); 
    }
}

// provide size to avoid reallocations
List<Record> records = new ArrayList<Record>(stringList.size());
for(String s : stringList)
    records.add(new Record(s, calculateFitness(s));
Collections.sort(records); // sort according to compareTo method
int max = Math.min(10, records.size()); // maximum index
List<String> result = new ArrayList<String>(max);
for(int i = 0; i < max; i++)
    result.add(records.get(i).string);
return result;

This is now much more verbose than the three lines above (this is Java, after all), but also includes the code that would be required to insert the key/value pairs into the map. 现在这比上面的三行(毕竟这是Java)要冗长得多,但也包括将键/值对插入到映射中所需的代码。

Would something like the following work for you? 以下是适合您的工作吗?

Note that I've assumed you don't require the double value other than to sort the data. 请注意,我假设除了对数据进行排序之外,您不需要double值。

public static void main(String[] args) throws Exception {
  List<String> data = new ArrayList<>(Arrays.asList("t", "h", "i", "s", "i", "s", "t", "e", "s", "t", "d", "a", "t", "a"));

  Collections.sort(data, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
      double o1Value = evaluate(o1);
      double o2Value = evaluate(o2);
      return Double.compare(o1Value, o2Value);
    }
  });

  List<String> result = data.subList(0, 10); // Note the end point is exclusive

  for (String s : result) {
    System.out.println(s);
  }
}

private static double evaluate(String s) {
  return s.codePointAt(0); // Nonsense, I know
}

This example prints: 此示例打印:

a
a
d
e
h
i
i
s
s
s

Why don't you just create a class to combine the String , Double and function that does the calculation - something like: 你为什么不创建一个类来组合StringDouble和进行计算的函数 - 类似于:

public Thing implements Comparable<Thing>
{
  private String s;
  private Double d;

  public Thing(String s)
  {
    this.s = s;
    this.d = calculateDouble(s); 
  }

  public String getString()
  {
    return this.s;
  }

  public Double getDouble()
  {
    return this.d;
  }

  public int compareTo(Thing other)
  {
    return getDouble().compareTo(other.getDouble());
  }

  public Double calculateDouble(String s)
  {
    ...
  }
}

Then all you need is a List<Thing> , Collections.sort and List.subList . 然后你需要的只是List<Thing>Collections.sortList.subList

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM