web-dev-qa-db-ja.com

Javaでバージョン文字列を比較する効率的な方法

可能性のある複製:
Javaの2つのバージョン文字列をどのように比較しますか?

以下に示すバージョン情報を含む2つの文字列があります。

str1 = "1.2"
str2 = "1.1.2"

ここで、文字列内のこれらのバージョンをJava&が等しい場合は0を返し、str1 <str2の場合は-1、str1> str2の場合は1を返します。

56
Mike

文字列操作にはcommons-lang3-3.8.1.jarが必要です。

/**
 * Compares two version strings. 
 * 
 * Use this instead of String.compareTo() for a non-lexicographical 
 * comparison that works for version strings. e.g. "1.10".compareTo("1.6").
 * 
 * @param v1 a string of alpha numerals separated by decimal points. 
 * @param v2 a string of alpha numerals separated by decimal points.
 * @return The result is 1 if v1 is greater than v2. 
 *         The result is 2 if v2 is greater than v1. 
 *         The result is -1 if the version format is unrecognized. 
 *         The result is zero if the strings are equal.
 */

public int VersionCompare(String v1,String v2)
{
    int v1Len=StringUtils.countMatches(v1,".");
    int v2Len=StringUtils.countMatches(v2,".");

    if(v1Len!=v2Len)
    {
        int count=Math.abs(v1Len-v2Len);
        if(v1Len>v2Len)
            for(int i=1;i<=count;i++)
                v2+=".0";
        else
            for(int i=1;i<=count;i++)
                v1+=".0";
    }

    if(v1.equals(v2))
        return 0;

    String[] v1Str=StringUtils.split(v1, ".");
    String[] v2Str=StringUtils.split(v2, ".");
    for(int i=0;i<v1Str.length;i++)
    {
        String str1="",str2="";
        for (char c : v1Str[i].toCharArray()) {
            if(Character.isLetter(c))
            {
                int u=c-'a'+1;
                if(u<10)
                    str1+=String.valueOf("0"+u);
                else
                    str1+=String.valueOf(u);
            }
            else
                str1+=String.valueOf(c);
        }            
        for (char c : v2Str[i].toCharArray()) {
            if(Character.isLetter(c))
            {
                int u=c-'a'+1;
                if(u<10)
                    str2+=String.valueOf("0"+u);
                else
                    str2+=String.valueOf(u);
            }
            else
                str2+=String.valueOf(c);
        }
        v1Str[i]="1"+str1;
        v2Str[i]="1"+str2;

            int num1=Integer.parseInt(v1Str[i]);
            int num2=Integer.parseInt(v2Str[i]);

            if(num1!=num2)
            {
                if(num1>num2)
                    return 1;
                else
                    return 2;
            }
    }
    return -1;
}    
146
Alex Gitelman

他の人が指摘しているように、String.split()はあなたが望む比較を行う非常に簡単な方法であり、マイク・デッキはそのような(おそらく)短い文字列ではおそらくあまり重要ではないが、何がおい!文字列を手動で解析せずに比較を行い、早期に終了するオプションがある場合は、 Java.util.Scanner クラスを試すことができます。

public static int versionCompare(String str1, String str2) {
    try ( Scanner s1 = new Scanner(str1);
          Scanner s2 = new Scanner(str2);) {
        s1.useDelimiter("\\.");
        s2.useDelimiter("\\.");

        while (s1.hasNextInt() && s2.hasNextInt()) {
            int v1 = s1.nextInt();
            int v2 = s2.nextInt();
            if (v1 < v2) {
                return -1;
            } else if (v1 > v2) {
                return 1;
            }
        }

        if (s1.hasNextInt() && s1.nextInt() != 0)
            return 1; //str1 has an additional lower-level version number
        if (s2.hasNextInt() && s2.nextInt() != 0)
            return -1; //str2 has an additional lower-level version 

        return 0;
    } // end of try-with-resources
}
14
Eric

私は自分でこれを実行しようとしていましたが、これを実行するための3つの異なるアプローチがあります。私はそれを効率的であるとは考えていませんが、コードサイズに関してはよく読み、見栄えは良いです。

アプローチ:

  1. バージョン文字列のセクション(序数)の数の上限と、そこに表される値の制限を想定します。多くの場合、最大4ドット、任意の序数で最大999です。これがどこに行くのかを見ることができ、バージョンを変換して、次のような文字列に収まるようにします: "1.0" => "001000000000"文字列形式または各序数を埋め込む他の方法。次に、文字列比較を行います。
  2. 順序区切り文字( '。')で文字列を分割し、それらを反復処理して、解析されたバージョンを比較します。これは、Alex Gitelmanが実証したアプローチです。
  3. 問題のバージョン文字列から解析するときに序数を比較します。すべての文字列が実際にCのように文字の配列への単なるポインターである場合、これは明確なアプローチです(見つかったときに '。'をヌルターミネーターに置き換え、2つまたは4つのポインターを移動します).

3つのアプローチに関する考え:

  1. ブログの投稿リンク があり、1を使用する方法を示しました。制限は、バージョン文字列の長さ、セクションの数、およびセクションの最大値にあります。ある時点で10,000を超えるような文字列があるのは、おかしいとは思いません。さらに、ほとんどの実装では、依然として文字列が分割されます。
  2. 事前に文字列を分割することは、読んで考えることは明らかですが、これを行うために各文字列を約2回実行しています。次のアプローチと時間を比較したいと思います。
  3. 分割時に文字列を比較すると、「2.1001.100101.9999998」と「1.0.0.0.0.0.1.0.0.0.1」の比較で分割を非常に早く停止できるという利点があります。これがCであり、Javaではない場合、各バージョンの各セクションの新しい文字列に割り当てられるメモリ量を制限する利点がありますが、そうではありません。

この3番目のアプローチの例を挙げている人は誰もいなかったので、効率を上げるための答えとしてここに追加したいと思います。

public class VersionHelper {

    /**
     * Compares one version string to another version string by dotted ordinals.
     * eg. "1.0" > "0.09" ; "0.9.5" < "0.10",
     * also "1.0" < "1.0.0" but "1.0" == "01.00"
     *
     * @param left  the left hand version string
     * @param right the right hand version string
     * @return 0 if equal, -1 if thisVersion &lt; comparedVersion and 1 otherwise.
     */
    public static int compare(@NotNull String left, @NotNull String right) {
        if (left.equals(right)) {
            return 0;
        }
        int leftStart = 0, rightStart = 0, result;
        do {
            int leftEnd = left.indexOf('.', leftStart);
            int rightEnd = right.indexOf('.', rightStart);
            Integer leftValue = Integer.parseInt(leftEnd < 0
                    ? left.substring(leftStart)
                    : left.substring(leftStart, leftEnd));
            Integer rightValue = Integer.parseInt(rightEnd < 0
                    ? right.substring(rightStart)
                    : right.substring(rightStart, rightEnd));
            result = leftValue.compareTo(rightValue);
            leftStart = leftEnd + 1;
            rightStart = rightEnd + 1;
        } while (result == 0 && leftStart > 0 && rightStart > 0);
        if (result == 0) {
            if (leftStart > rightStart) {
                return containsNonZeroValue(left, leftStart) ? 1 : 0;
            }
            if (leftStart < rightStart) {
                return containsNonZeroValue(right, rightStart) ? -1 : 0;
            }
        }
        return result;
    }

    private static boolean containsNonZeroValue(String str, int beginIndex) {
        for (int i = beginIndex; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c != '0' && c != '.') {
                return true;
            }
        }
        return false;
    }
}

期待される出力を示す単体テスト。

public class VersionHelperTest {

    @Test
    public void testCompare() throws Exception {
        assertEquals(1, VersionHelper.compare("1", "0.9"));
        assertEquals(1, VersionHelper.compare("0.0.0.2", "0.0.0.1"));
        assertEquals(1, VersionHelper.compare("1.0", "0.9"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2.0.0"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2.0"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2"));
        assertEquals(1, VersionHelper.compare("0.9.1", "0.9.0"));
        assertEquals(1, VersionHelper.compare("0.9.2", "0.9.1"));
        assertEquals(1, VersionHelper.compare("0.9.11", "0.9.2"));
        assertEquals(1, VersionHelper.compare("0.9.12", "0.9.11"));
        assertEquals(1, VersionHelper.compare("0.10", "0.9"));
        assertEquals(0, VersionHelper.compare("0.10", "0.10"));
        assertEquals(-1, VersionHelper.compare("2.10", "2.10.1"));
        assertEquals(-1, VersionHelper.compare("0.0.0.2", "0.1"));
        assertEquals(1, VersionHelper.compare("1.0", "0.9.2"));
        assertEquals(1, VersionHelper.compare("1.10", "1.6"));
        assertEquals(0, VersionHelper.compare("1.10", "1.10.0.0.0.0"));
        assertEquals(1, VersionHelper.compare("1.10.0.0.0.1", "1.10"));
        assertEquals(0, VersionHelper.compare("1.10.0.0.0.0", "1.10"));
        assertEquals(1, VersionHelper.compare("1.10.0.0.0.1", "1.10"));
    }
}
8
dlamblin

これはほぼ間違いなくnotmost効率的な方法ですが、バージョン番号の文字列はほとんど常に数文字の長さであるため、最適化する価値はないと思いますさらに:

public static int compareVersions(String v1, String v2) {
    String[] components1 = v1.split("\\.");
    String[] components2 = v2.split("\\.");
    int length = Math.min(components1.length, components2.length);
    for(int i = 0; i < length; i++) {
        int result = new Integer(components1[i]).compareTo(Integer.parseInt(components2[i]));
        if(result != 0) {
            return result;
        }
    }
    return Integer.compare(components1.length, components2.length);
}
6
Mike Deck

それらを配列に分割してから比較します。

// check if two strings are equal. If they are return 0;
String[] a1;

String[] a2;

int i = 0;

while (true) {
    if (i == a1.length && i < a2.length) return -1;
    else if (i < a1.length && i == a2.length) return 1;

    if (a1[i].equals(a2[i]) {
       i++;
       continue;
    }
     return a1[i].compareTo(a2[i];
}
return 0;
0
fastcodejava

Alex Gitelmanの回答から改編。

int compareVersions( String str1, String str2 ){

    if( str1.equals(str2) ) return 0; // Short circuit when you shoot for efficiency

    String[] vals1 = str1.split("\\.");
    String[] vals2 = str2.split("\\.");

    int i=0;

    // Most efficient way to skip past equal version subparts
    while( i<vals1.length && i<val2.length && vals[i].equals(vals[i]) ) i++;

    // If we didn't reach the end,

    if( i<vals1.length && i<val2.length )
        // have to use integer comparison to avoid the "10"<"1" problem
        return Integer.valueOf(vals1[i]).compareTo( Integer.valueOf(vals2[i]) );

    if( i<vals1.length ){ // end of str2, check if str1 is all 0's
        boolean allZeros = true;
        for( int j = i; allZeros & (j < vals1.length); j++ )
            allZeros &= ( Integer.parseInt( vals1[j] ) == 0 );
        return allZeros ? 0 : -1;
    }

    if( i<vals2.length ){ // end of str1, check if str2 is all 0's
        boolean allZeros = true;
        for( int j = i; allZeros & (j < vals2.length); j++ )
            allZeros &= ( Integer.parseInt( vals2[j] ) == 0 );
        return allZeros ? 0 : 1;
    }

    return 0; // Should never happen (identical strings.)
}

ご覧のとおり、それほど些細なことではありません。また、先頭の0を許可するとこれは失敗しますが、バージョン「1.04.5」またはw/eを見たことはありません。それを修正するには、whileループで整数比較を使用する必要があります。バージョン文字列に文字と数字を混在させると、これはさらに複雑になります。

0
trutheality

Step1: Java区切り文字としてドットを使用してStringTokenizerを使用

StringTokenizer(String str, String delimiters)または

String.split()およびPattern.split()を使用し、ドットで分割してから、Integer.parseInt(String str)を使用して各文字列を整数に変換できます。

ステップ2:整数を左から右に比較します。

0
Farm

「。」で文字列を分割しますまたは、デリミタが何であろうと、それらのトークンのそれぞれを整数値に解析して比較します。

int compareStringIntegerValue(String s1, String s2, String delimeter)  
{  
   String[] s1Tokens = s1.split(delimeter);  
   String[] s2Tokens = s2.split(delimeter);  

   int returnValue = 0;
   if(s1Tokens.length > s2Tokens.length)  
   {  
       for(int i = 0; i<s1Tokens.length; i++)  
       {  
          int s1Value = Integer.parseString(s1Tokens[i]);  
          int s2Value = Integer.parseString(s2Tokens[i]);  
          Integer s1Integer = new Integer(s1Value);  
          Integer s2Integer = new Integer(s2Value);  
          returnValue = s1Integer.compareTo(s2Value);
          if( 0 == isEqual)  
           {  
              continue; 
           }  
           return returnValue;  //end execution
        }
           return returnValue;  //values are equal
 } 

他のifステートメントは演習として残します。

0
Woot4Moo

バージョン文字列を比較するのは面倒です。この作業を行う唯一の方法は、注文規則が何であるかを非常に具体的にすることだからです。 ブログ投稿 で比較的短く完全なバージョン比較関数を1つ見てきました。コードはパブリックドメインに配置されています。Javaでなく、これをどのように適応させるかを見るのは簡単です。

0
Prodicus

問題をフォーマットと比較の2つに分けます。形式が正しいと仮定できる場合、数字バージョンのみを比較するのは非常に簡単です。

final int versionA = Integer.parseInt( "01.02.00".replaceAll( "\\.", "" ) );
final int versionB = Integer.parseInt( "01.12.00".replaceAll( "\\.", "" ) );

その後、両方のバージョンを整数として比較できます。したがって、「大きな問題」は形式ですが、それには多くのルールがあります。私の場合、最低2桁の数字を入力するだけなので、フォーマットは常に "99.99.99"になり、上記の変換を行います。私の場合、プログラムロジックはフォーマット比較であり、バージョン比較ではありません。今、あなたが非常に具体的なことをしていて、バージョン文字列の起源を信頼できる場合は、バージョン文字列の長さをチェックしてからint変換を行うことができます...しかし、それはベストプラクティスだと思います形式が期待どおりであることを確認してください。

0