IT TIP

Java에서 버전 문자열을 비교하는 효율적인 방법

itqueen 2020. 12. 13. 11:33
반응형

Java에서 버전 문자열을 비교하는 효율적인 방법


중복 가능성 :
Java에서 두 버전 문자열을 어떻게 비교합니까?

아래와 같이 버전 정보가 포함 된 2 개의 문자열이 있습니다.

str1 = "1.2"
str2 = "1.1.2"

이제 누구든지 Java의 문자열 내부에서 이러한 버전을 비교하는 효율적인 방법을 알려줄 수 있으며 같으면 0을 반환하고 str1 <str2 & 1 if str1> str2이면 -1을 반환합니다.


문자열 작업에는 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;
}    

다른 사람들이 지적했듯이 String.split ()은 원하는 비교를 수행하는 매우 쉬운 방법이며 Mike Deck은 그러한 (아마도) 짧은 문자열을 사용하면 아마도별로 중요하지 않을 것입니다. 야! 문자열을 수동으로 구문 분석하지 않고 비교를 수행하고 조기 종료 옵션이있는 경우 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
}

이 거의 확실 하지 대부분의 버전 번호 문자열은 거의 항상 몇 자 할 것이다 나는 그것의 가치가 더 최적화 생각하지 않는다 주어진 그것을 할 수있는 효율적인 방법 만 :

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);
}

제가 직접이 작업을하려고했는데이 작업을 수행하는 세 가지 다른 접근 방식을 보았고 지금까지 거의 모든 사람들이 버전 문자열을 분할하고 있습니다. 코드 크기가 현명하고 잘 읽고 좋아 보이지만 효율적으로 수행하는 것은 아닙니다.

구혼:

  1. 버전 문자열의 섹션 수 (서 디널)에 대한 상한과 여기에 표시된 값에 대한 한계를 가정합니다. 종종 최대 4 도트, 모든 서수에 대해 최대 999. 이것이 어디로 가는지 볼 수 있으며, "1.0"=> "001000000000"과 같은 문자열에 맞게 버전을 변환하거나 각 서수를 채우는 다른 방법을 사용할 수 있습니다. 그런 다음 문자열 비교를 수행하십시오.
  2. 서수 구분 기호 ( '.')에서 문자열을 분할하고이를 반복하고 구문 분석 된 버전을 비교합니다. 이것은 Alex Gitelman이 잘 보여준 접근 방식입니다.
  3. 문제의 버전 문자열에서 구문 분석 할 때 서수를 비교합니다. 모든 문자열이 실제로 C에서와 같이 문자 배열에 대한 포인터 였다면 이것은 명확한 접근 방식이 될 것입니다.

세 가지 접근 방식에 대한 생각 :

  1. 있었다 링크 된 블로그 게시물 한계는 섹션의 수와 섹션의 최대 값, 버전 문자열 길이에 1로 이동하는 방법을 보여 주었다. 나는 한 지점에서 10,000 개를 끊는 그런 끈을 갖는 것이 미친 짓이라고 생각하지 않는다. 또한 대부분의 구현은 여전히 ​​문자열을 분할합니다.
  2. 미리 문자열을 분할하는 것은 읽고 생각하는 것이 분명하지만이를 수행하기 위해 각 문자열을 약 두 번 살펴 보겠습니다. 다음 접근 방식과 시간을 비교하고 싶습니다.
  3. 분할 할 때 문자열을 비교하면 "2.1001.100101.9999998"과 "1.0.0.0.0.0.1.0.0.0.1"의 비교에서 매우 일찍 분할을 중지 할 수있는 이점이 있습니다. 이것이 C이고 Java가 아니라면 각 버전의 각 섹션에 대한 새 문자열에 할당되는 메모리 양을 제한 할 수 있지만 그렇지 않습니다.

이 세 번째 접근 방식의 예를 제시하는 사람을 보지 못했기 때문에 여기에 효율성을위한 답으로 추가하고 싶습니다.

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"));
    }
}

"."에서 문자열 분할 또는 구분자가 무엇이든간에 각 토큰을 Integer 값으로 구문 분석하고 비교하십시오.

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
 } 

I will leave the other if statement as an exercise.


Comparing version strings can be a mess; you're getting unhelpful answers because the only way to make this work is to be very specific about what your ordering convention is. I've seen one relatively short and complete version comparison function on a blog post, with the code placed in the public domain- it isn't in Java but it should be simple to see how to adapt this.


Adapted from Alex Gitelman's answer.

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.)
}

So as you can see, not so trivial. Also this fails when you allow leading 0's, but I've never seen a version "1.04.5" or w/e. You would need to use integer comparison in the while loop to fix that. This gets even more complex when you mix letters with numbers in the version strings.


Split them into arrays and then compare.

// 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;

I would divide the problem in two, formating and comparing. If you can assume that the format is correct, then comparing only numbers version is very simple:

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

Then both versions can be compared as integers. So the "big problem" is the format, but that can have many rules. In my case i just complete a minimum of two pair of digits, so the format is "99.99.99" always, and then i do the above conversion; so in my case the program logic is in the formatting, and not in the version comparison. Now, if you are doing something very specific and maybe you can trust the origin of the version string, maybe you just can check the length of the version string and then just do the int conversion... but i think it's a best practice to make sure the format is as expected.


Step1 : Use StringTokenizer in java with dot as delimiter

StringTokenizer(String str, String delimiters) or

You can use String.split() and Pattern.split(), split on dot and then convert each String to Integer using Integer.parseInt(String str)

Step 2: Compare integer from left to right.

참고URL : https://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java

반응형