web-dev-qa-db-ja.com

複数の配列を同時に「インプレース」で並べ替える

私は次の3つの配列を持っています:

int[] indexes = new int[]{0,2,8,5};
String[] sources = new String[]{"how", "are", "today", "you"};
String[] targets = new String[]{"I", "am", "thanks", "fine"};

インデックスに基づいて3つの配列を並べ替えたいと思います。

indexes -> {0,2,5,8}
sources -> {"how", "are", "you", "today"}
targets -> {"I", "am",  "fine",  "thanks"}

3つの要素すべてを使用して新しいクラスmyClassを作成できます。

class myClass {
    int x;
    String source;
    String target;
}

すべてをmyClassに再割り当てし、myClassを使用してxを並べ替えます。ただし、これには追加のスペースが必要になります。 in placeソートを実行できるかどうか疑問に思っていますか?ありがとう!

19
Edamame

これを行う3つの方法

1。コンパレータの使用(Need Java 8 plus)

import Java.io.*;
import Java.util.*;

class Test {

public static String[] sortWithIndex (String[] strArr, int[] intIndex )
    {
     if (! isSorted(intIndex)){
        final List<String> stringList = Arrays.asList(strArr);
        Collections.sort(stringList, Comparator.comparing(s -> intIndex[stringList.indexOf(s)]));
        return stringList.toArray(new String[stringList.size()]);
       }
     else
        return strArr;
    }

public static boolean isSorted(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        if (arr[i + 1] < arr[i]) {
            return false;
        };
    }
    return true;
}       


// Driver program to test function.
    public static void main(String args[])
    {
        int[] indexes = new int[]{0,2,8,5};
        String[] sources = new String[]{"how", "are", "today", "you"};
        String[] targets = new String[]{"I", "am", "thanks", "fine"};   
        String[] sortedSources = sortWithIndex(sources,indexes);
        String[] sortedTargets = sortWithIndex(targets,indexes);
        Arrays.sort(indexes);
        System.out.println("Sorted Sources " + Arrays.toString(sortedSources) + " Sorted Targets " + Arrays.toString(sortedTargets)  + " Sorted Indexes " + Arrays.toString(indexes));
    }
}

出力

Sorted Sources [how, are, you, today] Sorted Targets [I, am, fine, thanks] Sorted Indexes [0, 2, 5, 8]

2。Lambdaの使用(Need Java 8 plus)

import Java.io.*;
import Java.util.*;

public class Test {

public static String[] sortWithIndex (String[] strArr, int[] intIndex )
    {

  if (! isSorted(intIndex)) {
        final List<String> stringList = Arrays.asList(strArr);
        Collections.sort(stringList, (left, right) -> intIndex[stringList.indexOf(left)] - intIndex[stringList.indexOf(right)]);
        return stringList.toArray(new String[stringList.size()]);
  }
  else 
    return strArr;
    }

public static boolean isSorted(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        if (arr[i + 1] < arr[i]) {
            return false;
        };
    }
    return true;
}  

// Driver program to test function.
public static void main(String args[])
{
    int[] indexes = new int[]{0,2,5,8};
    String[] sources = new String[]{"how", "are", "today", "you"};
    String[] targets = new String[]{"I", "am", "thanks", "fine"};   
    String[] sortedSources = sortWithIndex(sources,indexes);
    String[] sortedTargets = sortWithIndex(targets,indexes);
    Arrays.sort(indexes);
    System.out.println("Sorted Sources " + Arrays.toString(sortedSources) + " Sorted Targets " + Arrays.toString(sortedTargets)  + " Sorted Indexes " + Arrays.toString(indexes));
}

}

。リストとマップを使用し、個々の配列を並べ替えるメソッドへの複数の呼び出しを回避する(上記の2番目のソリューションのように)

import Java.util.*;
import Java.lang.*;
import Java.io.*;

public class Test{

    public static <T extends Comparable<T>> void sortWithIndex( final List<T> key, List<?>... lists){
        // input validation
        if(key == null || lists == null)
            throw new NullPointerException("Key cannot be null.");

        for(List<?> list : lists)
            if(list.size() != key.size())
                throw new IllegalArgumentException("All lists should be of the same size");

        // Lists are size 0 or 1, nothing to sort
        if(key.size() < 2)
            return;

        // Create a List of indices
        List<Integer> indices = new ArrayList<Integer>();
        for(int i = 0; i < key.size(); i++)
            indices.add(i);

        // Sort the indices list based on the key
        Collections.sort(indices, new Comparator<Integer>(){
            @Override public int compare(Integer i, Integer j) {
                return key.get(i).compareTo(key.get(j));
            }
        });

        Map<Integer, Integer> swapMap = new HashMap<Integer, Integer>(indices.size());
        List<Integer> swapFrom = new ArrayList<Integer>(indices.size()),
                      swapTo   = new ArrayList<Integer>(indices.size());

        // create a mapping that allows sorting of the List by N swaps.
        for(int i = 0; i < key.size(); i++){
            int k = indices.get(i);
            while(i != k && swapMap.containsKey(k))
                k = swapMap.get(k);

            swapFrom.add(i);
            swapTo.add(k);
            swapMap.put(i, k);
        }

        // use the swap order to sort each list by swapping elements
        for(List<?> list : lists)
            for(int i = 0; i < list.size(); i++)
                Collections.swap(list, swapFrom.get(i), swapTo.get(i));
    }

    public static void main (String[] args) throws Java.lang.Exception{

      List<Integer> index = Arrays.asList(0,2,8,5);
      List<String> sources = Arrays.asList("how", "are", "today", "you");
      // List Types do not need to be the same
      List<String> targets  = Arrays.asList("I", "am", "thanks", "fine");

      sortWithIndex(index, index, sources, targets);

      System.out.println("Sorted Sources " + sources + " Sorted Targets " + targets  + " Sorted Indexes " + index);


    }
}

出力

Sorted Sources [how, are, you, today] Sorted Targets [I, am, fine, thanks] Sorted Indexes [0, 2, 5, 8]
16
Eby Jacob

見た目ほど簡単ではありませんが、可能です。 2つのオプションがあります:

  1. 独自のソートアルゴリズムを記述してくださいここで、2つの要素のスワップ関数は、他の配列の要素もスワップします。

    AFAIKは、追加の配列を交換する方法で標準_Array.sort_を拡張する方法はありません。

  2. ソート順でヘルパー配列を使用します。

    • まず、ヘルパー配列を範囲_{0, 1 ... indexes.Length-1}_で初期化する必要があります。

    • ここで、ヘルパー配列を並べ替えるComparatoraではなく_indexes[a]_と_indexes[b]_を比較するbを使用します。結果はヘルパー配列であり、各要素には、そのコンテンツが由来するソース配列の要素のインデックス、つまりソートシーケンスがあります。

    • 最後のステップは最もトリッキーなものです。 上記の並べ替え順序に従ってソース配列の要素を交換する必要があります。
      操作するには厳密に配置現在のインデックスcurを_0_に設定します。
      次に、ヘルパー配列からcur番目の要素を取得します。それをfromと呼びましょう。これは、完了後にインデックスcurに配置する必要がある要素インデックスです。
      ここで、インデックスcurの要素を配置するために、インデックスfromにスペースを作成する必要があります。それらを一時的な場所tmpにコピーします。
      次に、要素をインデックスfromからインデックスcurに移動します。インデックスfromは自由に上書きできるようになりました。
      インデックスcurのヘルパー配列の要素を無効な値に設定します。例: _-1_。
      現在のインデックスcurfromに設定します。ヘルパー配列内の、すでに無効なインデックス値を持つ要素、つまり開始点に到達するまで、上から続行します。この場合、tmpの内容を最後のインデックスに保存します。これで、回転したインデックスの閉ループが見つかりました。
      残念ながら、それぞれが任意のサイズのループが任意の数存在する可能性があります。したがって、ヘルパー配列で次の無効でないインデックス値を探し、ヘルパー配列のすべての要素が処理されるまで上から続行する必要があります。各ループの後に開始点で終了するため、無効でないエントリが見つからない限り、curをインクリメントするだけで十分です。したがって、ヘルパー配列の処理中、アルゴリズムはまだO(n)です。curより前のすべてのエントリは、ループが完了した後は必ず無効になります。
      ヘルパー配列のサイズを超えてcurがインクリメントされた場合は完了です。

  3. 新しいターゲット配列を作成できる場合は、オプション2のバリエーションが簡単になります。
    この場合、新しいターゲット配列を割り当て、ヘルパー配列のインデックスに従ってそれらのコンテンツを入力するだけです。
    欠点は、配列が非常に大きい場合、割り当てにかなりの費用がかかる可能性があることです。そしてもちろん、それはもはやインプレースではありません。


いくつかの追加の注意事項。

  • 通常、カスタムソートアルゴリズムは、一時配列の割り当てを回避するため、パフォーマンスが向上します。しかし、場合によっては状況が変わります。循環要素回転ループの処理では、最小移動操作を使用します。これは、一般的な並べ替えアルゴリズムのO(n log n)ではなくO(n)です。したがって、並べ替える配列の数や配列のサイズが大きくなると、メソッド#2にはスワップ操作の使用量が少ないため、利点があります。

  • このようなソートアルゴリズムを必要とするデータモデルは、ほとんどの場合設計上壊れています。もちろん、いつものように、これを避けられない場合がいくつかあります。

7
Marcel

整数をキーとして使用して、TreeMapなどを使用することをお勧めします。

static Map<Integer, myClass> map = new TreeMap<>();

したがって、順序付きを取得する場合は、forループまたは任意の操作を実行するだけで済みます。

for (int i : map.keyset()){
    System.out.println("x: "+map.get(i).x+"\nsource: "+map.get(i).source+"\ntarget: "+map.get(i).target);
}
3
Luke

この例では、インデックスの整数配列を作成する必要がありますが、並べ替える配列はarray1に従って所定の位置に並べ替えられ、配列はインデックスを作成できる任意のタイプ(プリミティブまたはオブジェクト)にすることができます。

public static void main(String[] args) {
    int array1[]={5,1,9,3,8}; 
    int array2[]={2,0,3,6,1};
    int array3[]={3,1,4,5,9};
    // generate array of indices
    Integer[] I = new Integer [array1.length];
    for(int i = 0; i < I.length; i++)
        I[i] = i;
    // sort array of indices according to array1
    Arrays.sort(I, (i, j) -> array1[i]-array1[j]);
    // reorder array1 ... array3 in place using sorted indices
    // also reorder indices back to 0 to length-1
    // time complexity is O(n)
    for(int i = 0; i < I.length; i++){
        if(i != I[i]){
            int t1 = array1[i];
            int t2 = array2[i];
            int t3 = array3[i];
            int j;
            int k = i;
            while(i != (j = I[k])){
                array1[k] = array1[j];
                array2[k] = array2[j];
                array3[k] = array3[j];
                I[k] = k;
                k = j;
            }
            array1[k] = t1;
            array2[k] = t2;
            array3[k] = t3;
            I[k] = k;
        }
    }
    // display result
    for (int i = 0; i < array1.length; i++) {
        System.out.println("array1 " + array1[i] +
           " array2 " + array2[i] +
           " array3 " + array3[i]);
    }
}
3
rcgldr

Collectionを使用する別の解決策(メモリ使用量を増やす):

ソートされたマップを作成して、正しいインデックスと元の位置の間のマッピングにしましょう。

public static TreeMap<Integer, Integer> sortIndex(int[] array){
    TreeMap<Integer, Integer> tree = new TreeMap<>();
    for(int i=0; i < array.length; ++i) {
        tree.put(array[i], i);
    }   
    return tree;
}

テスト:

int[] indexes = new int[] { 0, 1, 3, 2, 4, 5 };
TreeMap<Integer, Integer> map = sortIndex(indexes);

map.keySet().stream().forEach(System.out::print); //012345
map.values().stream().forEach(System.out::print); //013245

インデックスは(キー上で)ソートされ、元のインデックスの順序が値として使用されます。

いいえ、これを使用して配列を簡単に注文することはできません。思い切ってStreamを使用して、Listにマップして収集します。

public static List<String> sortInPlace(String[] array, TreeMap<Integer, Integer> map) {
    return map.values().stream().map(i -> array[i]).collect(Collectors.toList());
}

テスト:

String[] sources = "to be not or to be".split(" ");

int[] indexes = new int[] { 0, 1, 3, 2, 4, 5 };
TreeMap<Integer, Integer> map = sortIndex(indexes);

List<String> result = sortInPlace(sources, map);
System.out.println(result);

[to、be、or、not、to、be]

なぜ私はListを使用したのですか。主に再注文を簡素化するために、元の配列を注文しようとすると、反対側のキー/ペアを削除する必要があるため、複雑になります

2 -> 3
3 -> 2

クリーニングを行わずに、セルを2回交換するだけなので、変更はありません。

メモリ使用量を少し減らしたい場合は、ストリームを使用する代わりに別の配列を作成し、マップを反復する値ごとに値をコピーできます。これは、複数のアレイを並列に使用する場合にも可能です。

2
AxelH

私のアプローチは有効かどうか疑問に思います。

    public class rakesh{

    public static void sort_myClass(myClass myClasses[]){
        for(int i=0; i<myClasses.length; i++){
            for(int j=0; j<myClasses.length-i-1; j++){
                if(myClasses[j].x >myClasses[j+1].x){
                    myClass temp_myClass = new myClass(myClasses[j+1]);
                    myClasses[j+1] = new myClass(myClasses[j]);
                    myClasses[j] = new myClass(temp_myClass);
                }
            }
        }
    }

    public static class myClass{
        int x;
        String source;
        String target;
        myClass(int x,String source,String target){
          this.x = x;
          this.source = source;
          this.target = target;
        }
        myClass(myClass super_myClass){
            this.x = super_myClass.x;
            this.source = super_myClass.source;
            this.target = super_myClass.target;
        }
    }

    public static void main(String args[]) {
        myClass myClass1 = new myClass(0,"how","I");
        myClass myClass2 = new myClass(2,"are","am");
        myClass myClass3 = new myClass(8,"today","thanks");
        myClass myClass4 = new myClass(5,"you","fine");
        myClass[] myClasses = {myClass1, myClass2, myClass3, myClass4};

        sort_myClass(myClasses);

        for(myClass myClass_dummy : myClasses){
            System.out.print(myClass_dummy.x + " ");
        }
        System.out.print("\n");
        for(myClass myClass_dummy : myClasses){
            System.out.print(myClass_dummy.source + " ");
        }
        System.out.print("\n");
        for(myClass myClass_dummy : myClasses){
            System.out.print(myClass_dummy.target + " ");
        }
    }


}

エラーを見つけたり、提案がある場合は、コメントを残してください。必要な編集を行うことができます。

出力

0 2 5 8
今日は元気ですか
元気ですありがとう
プロセスは終了コード0で終了しました

1
user7909000

クラスに値を割り当てなくても、次のコードでそれを実現できます。

    Integer[] indexes = new Integer[]{0,2,8,5};
    String[] sources = new String[]{"how", "are", "today", "you"};
    String[] targets = new String[]{"I", "am", "thanks", "fine"};
    Integer[] sortedArrya = Arrays.copyOf(indexes, indexes.length);
    Arrays.sort(sortedArrya);
    String[] sortedSourses = new String[sources.length];
    String[] sortedTargets = new String[targets.length];
    for (int i = 0; i < sortedArrya.length; i++) {
        int intValus = sortedArrya[i];
        int inx = Arrays.asList(indexes).indexOf(intValus);
        sortedSourses[i] = sources[+inx];
        sortedTargets[i] = targets[+inx];
    }
    System.out.println(sortedArrya);
    System.out.println(sortedSourses);
    System.out.println(sortedTargets);
1
Devratna

それはすべてあなたの配列のサイズに依存します。このソリューションでは、最初の配列を使用して並べ替えを実行しますが、複数の配列で順列を実行します。
したがって、使用されるソートアルゴリズムで多くの順列が必要になる場合、これにはパフォーマンスの問題が発生する可能性があります。

ここでは、2つのセルの交換中に実行できるいくつかのアクションを追加した、基本的な並べ替えアルゴリズムを採用しました。これにより、ラムダを定義して、1つの配列に基づいて複数の配列を同時にスワップすることができます。

public static void sortArray( int[] array, BiConsumer<Integer, Integer>... actions ) {
    int tmp;
    for ( int i = 0, length = array.length; i < length; ++i ) {
        tmp = array[i];
        for ( int j = i + 1; j < length; ++j ) {
            if ( tmp > array[j] ) {
                array[i] = array[j];
                array[j] = tmp;
                tmp = array[i];

                // Swap the other arrays
                for ( BiConsumer<Integer, Integer> cons : actions ){
                    cons.accept( i,  j);
                }
            }
        }
    }
}

BiConsumerラムダとして渡すことができるセルを交換するための汎用メソッドを作成しましょう(非プリミティブ配列でのみ機能します)。

public static <T> void swapCell( T[] array, int from, int to ) {
    T tmp = array[from];
    array[from] = array[to];
    array[to] = tmp;
}

これにより、次のように配列を並べ替えることができます。

public static void main( String[] args ) throws ParseException {
    int[] indexes = new int[] { 0, 2, 8, 5 };
    String[] sources = new String[] { "how", "are", "today", "you" };
    String[] targets = new String[] { "I", "am", "thanks", "fine" };

    sortArray( indexes,
            ( i, j ) -> swapCell( sources, i, j ),
            ( i, j ) -> swapCell( targets, i, j ) );

    System.out.println( Arrays.toString( indexes ) );
    System.out.println( Arrays.toString( sources ) );
    System.out.println( Arrays.toString( targets ) );
}

[0、2、5、8]
[今日は元気ですか]
[私、元気です、ありがとう]

このソリューションでは、追加の配列やCollectionが必要ないため、すでに使用されているものよりも(はるかに)多くのメモリは必要ありません。

BiConsumer<>...を使用すると、一般的なソリューションが提供されます。これはObject[]...も受け入れることができますが、プリミティブ配列では機能しなくなります。もちろん、これはわずかなパフォーマンスの低下があるため、必要に応じて削除できます。


完全なソリューションの作成では、最初に、ファクトリとしても使用されるインターフェイスを定義しましょう。

interface Sorter {
    void sort(int[] array, BiConsumer<Integer, Integer>... actions);

    static void sortArrays(int[] array, BiConsumer<Integer, Integer>... actions){
        // call the implemented Sorter
    }
}

次に、前と同じロジックで単純な選択ソーターを実装します。元の配列の順列ごとに、BiConsumerを実行します。

class SelectionSorter implements Sorter {
    public void sort(int[] array, BiConsumer<Integer, Integer>... actions) {
        int index;
        int value;
        int tmp;
        for (int i = 0, length = array.length; i < length; ++i) {
            index = i;
            value = array[i];
            for (int j = i + 1; j < length; ++j) {
                if (value > array[j]) {
                    index = j;
                    value = array[j];
                }
            }

            if (index != i) {
                tmp = array[i];
                array[i] = array[index];
                array[index] = tmp;

                // Swap the other arrays
                for (BiConsumer<Integer, Integer> cons : actions) {
                    cons.accept(i, index);
                }
            }
        }
    }
}

バブルソートも作成しましょう:

class BubbleSorter implements Sorter {
    public void sort(int[] array, BiConsumer<Integer, Integer>... actions) {
        int tmp;

        boolean swapped;
        do {
            swapped = false;
            for (int i = 1, length = array.length; i < length; ++i) {
                if (array[i - 1] > array[i]) {
                    tmp = array[i];
                    array[i] = array[i - 1];
                    array[i - 1] = tmp;

                    // Swap the other arrays
                    for (BiConsumer<Integer, Integer> cons : actions) {
                        cons.accept(i, i - 1);
                    }

                    swapped = true;
                }
            }
        } while (swapped);
    }
}

これで、長さという単純な条件に基づいて、どちらか一方を簡単に呼び出すことができます。

static void sortArrays(int[] array, BiConsumer<Integer, Integer>... actions){
    if(array.length < 1000){
        new BubbleSorter().sort(array, actions);
    } else {
        new SelectionSorter().sort(array, actions);
    }
}

そうすれば、ソーターを簡単に呼び出すことができます

Sorter.sortArrays(indexes, 
    (i, j) -> swapCell(sources, i, j), 
    (i, j) -> swapCell(targets, i, j)
);

ideone の完全なテストケース(タイムアウトによるサイズの制限)

1
AxelH

私はあなたの質問に対する他の解決策を持っています:

private void reOrder(int[] indexes, String[] sources, String[] targets){
        int[] reIndexs = new int[indexes.length]; // contain index of item from MIN to MAX
        String[] reSources = new String[indexes.length]; // array sources after re-order follow reIndexs
        String[] reTargets = new String[indexes.length]; // array targets after re-order follow reIndexs
        for (int i=0; i < (indexes.length - 1); i++){
            if (i == (indexes.length - 2)){
                if (indexes[i] > indexes[i+1]){
                    reIndexs[i] = i+1;
                    reIndexs[i+1] = i;
                }else
                {
                    reIndexs[i] = i;
                    reIndexs[i+1] = i+1;
                }
            }else
            {
                for (int j=(i+1); j < indexes.length; j++){
                    if (indexes[i] > indexes[j]){
                        reIndexs[i] = j;
                    }else {
                        reIndexs[i] = i;
                    }
                }
            }
        }

        // Re-order sources array and targets array
        for (int index = 0; index < reIndexs.length; index++){
            reSources[index] = sources[reIndexs[index]];
            reTargets[index] = targets[reIndexs[index]];
        }

        // Print to view result
        System.out.println( Arrays.toString(reIndexs));
        System.out.println( Arrays.toString(reSources));
        System.out.println( Arrays.toString(reTargets));
    }
1
chaunv

あなたもあなたの方法で達成することができます。

ここでは、ArrayList myArrを作成し、インデックス値に基づいて並べ替えてから、ArrayListに満足している場合は、変換を削除するか、配列を使用したい場合は、配列に変換し直しました。

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Collections;
import Java.util.Comparator;

public class StackOverflow {
    public static void main(String[] args) {
        int[] indexes = new int[]{0,2,8,5};
        String[] sources = new String[]{"how", "are", "today", "you"};
        String[] targets = new String[]{"I", "am", "thanks", "fine"};
        ArrayList<myClass> myArr=new ArrayList<>();
        for(int i=0;i<indexes.length;i++) {
            myArr.add(new myClass(indexes[i], sources[i], targets[i]));
        }
        //Collections.sort(myArr,new compareIndex()); 
        // Just for readability of code 
        Collections.sort(myArr, (mC1, mC2) -> mC1.getX() - mC2.getX());

       //Conversion Part
       for (int i=0;i<myArr.size();i++){
           indexes[i]=myArr.get(i).getX();
           sources[i]=myArr.get(i).getSource();
           targets[i]=myArr.get(i).getTarget();
       }

        System.out.println(Arrays.toString(indexes));
        System.out.println(Arrays.toString(sources));
        System.out.println(Arrays.toString(targets));

    }
}
class myClass {
    private Integer x;
    private String source;
    private String target;
    public myClass(Integer x,String source,String target){
        this.x=x;
        this.source=source;
        this.target=target;
    }

    public Integer getX() {
        return x;
    }

    public String getSource() {
        return source;
    }

    public String getTarget() {
        return target;
    }
}
1