Map <k,v =“”>的前N个值按值</k,>排序

时间:2013-04-30 09:38:05

标签: java sorting hashmap treemap

我有一个字符串列表。我想基于返回double的函数来评估每个字符串。然后我想要前5个字符串,基于他们的计算值。如果少于5,我想要所有这些(按顺序)。假设字符串是化学化合物,函数计算质量。该功能在计算上很昂贵;我需要每串评估一次。 (不过我只是在这里编写数据。)

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

程序应返回按其各自值排列的前五个字符串。对于此示例数据:H20, HCl, HeNe, H2SO4, 4FeO3。实际上,我并不关心订单;我只需要任何顺序的五个最低点。

我想过如何在Perl中做到这一点。这只是几行:

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

但我需要用Java来做。它让我发疯了。

首先,我尝试填充HashMap<String, Double>,然后使用Collections.sort使用自定义比较器,就像Perl版本一样。但是比较器的范围使它无法引用HashMap来查找值。

然后我尝试了一个TreeMap<String, Double>,但它只按键进行排序,没有任何强制可以让它按值排序。

所以我尝试了TreeMap<Double, String>。它将丢弃具有相同Double的条目。但是,将字符串映射到同一个Double的可能性很低,所以我向前推进。将条目添加到TreeMap没有问题,但是我遇到了试图从中提取值的问题。

TreeMap提供了一个名为subMap的方法,但其参数是用于分隔子集的键。我不知道它们是什么;我只想要前五个。所以我尝试使用values方法从TreeMap中获取所有值,希望它们按顺序排列。然后我就可以得到前十名。

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

不。运行时错误:无法将TreeMap $ Values转换为ArrayList。

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

相同。尝试执行强制转换的运行时错误。好的,我们只是分配给一个集合......

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

抱歉,subList不是收集方法。

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

最后,有效的东西!但两个额外的数据结构只是为了获得前五个元素?而且我并不太喜欢使用Double作为TreeMap的关键。

有更好的解决方案吗?

3 个答案:

答案 0 :(得分:3)

我认为你不会比上面的三行更紧凑,而不是Java。

除此之外,我的印象是首先将Map作为数据结构是错误的选择,因为您似乎不需要字符串查找(除非您想以某种方式处理)多次出现的字符串,但你没有这么说)。另一种方法是声明您自己的可比数据记录类:

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;

现在这比上面的三行(毕竟这是Java)要冗长得多,但也包括将键/值对插入地图所需的代码。

答案 1 :(得分:1)

以下内容适用于您?

请注意,我假设您不需要除了对数据进行排序之外的双倍值。

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
}

此示例打印:

a
a
d
e
h
i
i
s
s
s

答案 2 :(得分:0)

为什么不创建一个类来组合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)
  {
    ...
  }
}

然后您需要的只是List<Thing>Collections.sortList.subList