web-dev-qa-db-ja.com

値で自動的にソートされるJava

自動的に値でソートされたマップをJava-に設定して、新しいキーと値のペアを追加している間、いつでもソートされ続けるようにする必要がありますまたは、既存のキーと値のペアの値を更新するか、一部のエントリを削除します。

このマップは非常に大きくなることにも注意してください(数十万、さらには数千万のエントリのサイズ)。

したがって、基本的に次の機能を探しています。

前述の機能を実装するクラス「SortedByValuesMap」があり、次のコードがあるとします。

SortedByValuesMap<String,Long> sorted_map = new SortedByValuesMap<String, Long>();
sorted_map.put("apples", 4);
sorted_map.put("oranges", 2);
sorted_map.put("bananas", 1);
sorted_map.put("lemons", 3);
sorted_map.put("bananas", 6);

for (String key : sorted_map.keySet()) {
  System.out.println(key + ":" + sorted_map.get(key));
}

出力は次のようになります。

bananas:6
apples:4
lemons:3
oranges:2

特に、私にとって本当に重要なことは、次のようなコマンドを使用して、いつでも最小値のエントリを取得できるようにすることです。

smallestItem = sorted_map.lastEntry();

これで「オレンジ」のエントリが表示されます

編集:私はJava初心者なので、答えを少し詳しく説明してください-ありがとう

EDIT2:これは役立つかもしれません:私はこれを巨大なテキストファイルの単語を数えるために使用しています(よく知っている人のために:特にn-grams)。したがって、キーが単語であり、値がそれらの単語の頻度であるマップを作成する必要があります。ただし、制限(RAMなど)のため、X個の最も頻繁な単語のみを保持したいのですが、もちろん、どれが最も頻繁な単語になるかを事前に知ることはできません。したがって、(概算として)機能すると思った方法は、単語のカウントを開始し、マップが上限(100万エントリなど)に達すると、マップのサイズを維持するために最も頻度の低いエントリが削除されることです。常に1ミル。

26
Alexandros

2つのデータ構造を保持します。

  • 単語の辞書->カウント。通常の_HashMap<String, Long>_を使用するだけです。
  • _list[count]_がその数の単語の_Set<String>_を保持するように、順序を追跡するための「配列」。

    これは、表記上の便宜のために配列であるかのように書いています。実際、発生数の上限がわからない可能性があるため、サイズ変更可能なデータ構造が必要です。 _Map<Long, Set<String>>_を使用して実装します。または、メモリの使用量が多すぎる場合は、_ArrayList<Set<String>>_を使用します(count == size() - 1をテストする必要があります。テストする場合は、add()の代わりにset(count + 1)を使用します。 )。

Word(擬似コード)の出現回数を増やすには:

_// assumes data structures are in instance variables dict and arr
public void tally(final String Word)
{
    final long count = this.dict.get(Word) or 0 if absent;
    this.dict.put(Word, count + 1);
    // move Word up one place in arr
    this.arr[count].remove(Word);   // This is why we use a Set: for fast deletion here.
    this.arr[count + 1].add(Word);
}
_

単語を順番に繰り返すには(擬似コード):

_for(int count = 0; count < arr.size; count++)
    for(final String Word : this.arr[count])
        process(Word, count);
_
4

追加のインデックスを使用するか、Long値が異なる場合はTreeMap<Long, TreeSet<String>>またはTreeMap<Long, String>のみを使用するのはどうですか?

ヒープ と書くこともできます。

2

http://paaloliver.wordpress.com/2006/01/24/sorting-maps-in-Java/ に投稿されたソリューションを試してください。昇順または降順で並べ替えを行う柔軟性もあります。

これが彼らの言うことです

import Java.util.Comparator;
import Java.util.HashMap;
import Java.util.Iterator;
import Java.util.Map;
import Java.util.SortedMap;
import Java.util.TreeMap;

public class MapValueSort {

    /** inner class to do soring of the map **/
    private static class ValueComparer implements Comparator<String> {
        private Map<String, String>  _data = null;
        public ValueComparer (Map<String, String> data){
            super();
            _data = data;
        }

         public int compare(String o1, String o2) {
             String e1 = (String) _data.get(o1);
             String e2 = (String) _data.get(o2);
             return e1.compareTo(e2);
         }
    }

    public static void main(String[] args){

        Map<String, String> unsortedData = new HashMap<String, String>();
        unsortedData.put("2", "DEF");
        unsortedData.put("1", "ABC");
        unsortedData.put("4", "ZXY");
        unsortedData.put("3", "BCD");


        SortedMap<String, String> sortedData = new TreeMap<String, String>(new MapValueSort.ValueComparer(unsortedData));

        printMap(unsortedData);

        sortedData.putAll(unsortedData);
        System.out.println();
        printMap(sortedData);
    }

    private static void printMap(Map<String, String> data) {
        for (Iterator<String> iter = data.keySet().iterator(); iter.hasNext();) {
            String key = (String) iter.next();
            System.out.println("Value/key:"+data.get(key)+"/"+key);
        }
    }

}

出力

Value/key:BCD/3
Value/key:DEF/2
Value/key:ABC/1
Value/key:ZXY/4

Value/key:ABC/1
Value/key:BCD/3
Value/key:DEF/2
Value/key:ZXY/4
1
user3656845

Guava BiMap 解決策:

//Prepare original data
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("apples" , 4);
biMap.put("oranges", 2);
biMap.put("bananas", 1);
biMap.put("lemons" , 3);
biMap.put("bananas", 6);

//Create a desc order SortedMap
SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>(new Comparator<Integer>(){
    @Override public int compare(Integer o1, Integer o2) {
      return o2-o1;
}});

//Put inversed map
sortedMap.putAll(biMap.inverse());
for (Map.Entry<Integer, String> e: sortedMap.entrySet()) {
      System.out.println(e);
}
System.out.println(sortedMap.lastKey()); 

関連する値順にオブジェクトのリストを保持するには、同様の構造が必要であることがわかりました。このスレッドのMechanicalカタツムリからの提案に基づいて、このようなマップの基本的な実装をコーディングしました。お気軽にご利用ください。

import Java.util.*;

/**
 * A map where {@link #keySet()} and {@link #entrySet()} return sets ordered
 * with ascending associated values with respect to the the comparator provided
 * at constuction. The order of two or more keys with identical values is not
 * defined.
 * <p>
 * Several contracts of the Map interface are not satisfied by this minimal
 * implementation.
 */
public class ValueSortedMap<K, V> extends HashMap<K, V> {
    protected Map<V, Collection<K>> valueToKeysMap;

    public ValueSortedMap() {
        this((Comparator<? super V>) null);
    }

    public ValueSortedMap(Comparator<? super V> valueComparator) {
        this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator);
    }

    public boolean containsValue(Object o) {
        return valueToKeysMap.containsKey(o);
    }

    public V put(K k, V v) {
        V oldV = null;
        if (containsKey(k)) {
            oldV = get(k);
            valueToKeysMap.get(oldV).remove(k);
        }
        super.put(k, v);
        if (!valueToKeysMap.containsKey(v)) {
            Collection<K> keys = new ArrayList<K>();
            keys.add(k);
            valueToKeysMap.put(v, keys);
        } else {
            valueToKeysMap.get(v).add(k);
        }
        return oldV;
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

    public V remove(Object k) {
        V oldV = null;
        if (containsKey(k)) {
            oldV = get(k);
            super.remove(k);
            valueToKeysMap.get(oldV).remove(k);
        }
        return oldV;
    }

    public void clear() {
        super.clear();
        valueToKeysMap.clear();
    }

    public Set<K> keySet() {
        LinkedHashSet<K> ret = new LinkedHashSet<K>(size());
        for (V v : valueToKeysMap.keySet()) {
            Collection<K> keys = valueToKeysMap.get(v);
            ret.addAll(keys);
        }
        return ret;
    }

    public Set<Map.Entry<K, V>> entrySet() {
        LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size());
        for (Collection<K> keys : valueToKeysMap.values()) {
            for (final K k : keys) {
                final V v = get(k);
                ret.add(new Map.Entry<K,V>() {
                    public K getKey() {
                        return k;
                    }

                    public V getValue() {
                        return v;
                    }

                    public V setValue(V v) {
                        throw new UnsupportedOperationException();
                    }
                });
            }
        }
        return ret;
    }
}

この実装は、返されたキーセットと実際のマップのエントリセットに値の変更と削除を反映するなど、マップインターフェイスのすべてのコントラクトを尊重しませんが、このようなソリューションをこのようなフォーラムに含めるには少し大きくなります。おそらく私は1つに取り組み、githubなどを介して利用できるようにします。

0
David Bleckmann

Java.util.LinkedHashMapの実装を参照できます。基本的な考え方は、内部リンクリストを使用して注文を保存することです。詳細は次のとおりです。

HashMapから拡張します。 HashMapでは、各エントリに基本的なキーと値があります。 nextポインタとprevポインタを追加して、エントリを値順に格納できます。そして、最初と最後のエントリを取得するためのヘッダーとテールポインタ。変更(追加、削除、更新)ごとに、独自のコードを追加してリストの順序を変更できます。これは、線形検索とポインタースイッチにすぎません。

配列ではなくリンクリストであるため、エントリが多すぎると、追加/更新に時間がかかります。しかし、リストがソートされている限り、検索を高速化する方法はたくさんあると思います。

これがあなたが得たものです:キーによってエントリを取得するときにHashMapと同じ速度を持つマップ。エントリを順番に格納するリンクリスト。

このソリューションがお客様の要件を満たしている場合は、これについてさらに話し合うことができます。


to jtahlborn:私が言ったように、最適化なしでは確かに遅いです。今は暗黙ではなくパフォーマンスについて話しているので、多くのことができます。

1つの解決策は、赤黒木などのリンクリストの代わりにツリーを使用することです。次に、マップを反復する代わりに、ツリーを反復します。

最小値については、簡単です。メンバー変数を使用して最小値を格納するだけで、要素を追加または更新するときに、最小値を更新します。削除するときは、ツリーで最小のものを検索します(これは非常に高速です)

ツリーが複雑すぎる場合は、別のリスト/配列を使用してリスト内のいくつかの位置をマークすることもできます。たとえば、それぞれ100個の要素です。次に、検索するときは、最初に位置リストを検索し、次に実際のリストを検索します。このリストも維持する必要があります。特定の変更時(おそらく100回)の位置リストを再カウントすることは合理的です。

0
DeepNightTwo

更新:マップを値で並べ替えることはできません。申し訳ありません。

SortedMapのようなTreeMap実装を使用して、Comparatorで順序を値で定義できます(デフォルトではなく、キーで)。

または、さらに良いことに、値によって事前定義されたコンパレータを使用して、要素を PriorityQueue に配置できます。 TreeMapと比較して、高速でメモリ消費量が少ないはずです。

0
Michał Šrajer