ค้นหาเว็บไซต์

อัลกอริทึม - ฉันจะเปรียบเทียบหมายเลขเวอร์ชันซอฟต์แวร์โดยใช้ JavaScript ได้อย่างไร? (เฉพาะตัวเลข)


นี่คือหมายเลขเวอร์ชันซอฟต์แวร์:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

ฉันจะเปรียบเทียบสิ่งนี้ได้อย่างไร?

สมมติว่าลำดับที่ถูกต้องคือ:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

แนวคิดนั้นง่าย...: อ่านตัวเลขตัวแรก จากนั้น ตัวที่สอง หลังจากนั้นตัวที่สาม... แต่ฉันไม่สามารถแปลงหมายเลขเวอร์ชันเป็นตัวเลขทศนิยมได้... คุณยังสามารถเห็นหมายเลขเวอร์ชันแบบนี้:

"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"

และนี่ทำให้เห็นชัดเจนขึ้นว่าความคิดเบื้องหลังคืออะไร... แต่ฉันจะเปลี่ยนมันเป็นโปรแกรมคอมพิวเตอร์ได้อย่างไร?

คำตอบ #1

semver

ตัวแยกวิเคราะห์เวอร์ชันเชิงความหมายที่ใช้โดย npm

npm install semver
var semver = require('semver');

semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false

semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true

var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')

ลิงก์เวอร์ชันเชิงความหมาย :
https://www.npmjs.com/package/semver#prerelease-identifiers

คำตอบ #2

แนวคิดพื้นฐานในการทำการเปรียบเทียบนี้คือการใช้ Array.split เพื่อให้ได้อาร์เรย์ของส่วนจากสตริงที่ป้อนเข้ามา แล้วเปรียบเทียบคู่ของส่วนจากอาร์เรย์ทั้งสอง; หากส่วนไม่เท่ากัน เราก็จะรู้ว่าเวอร์ชันใดมีค่าน้อยกว่า

มีรายละเอียดสำคัญไม่กี่ข้อที่ต้องจำไว้:

  1. ควรเปรียบเทียบชิ้นส่วนในแต่ละคู่แบบไหน? คำถามต้องการเปรียบเทียบเชิงตัวเลข แต่ถ้าเรามีสตริงเวอร์ชันที่ไม่ได้ประกอบด้วยตัวเลขเพียงอย่างเดียว (เช่น "1.0a") จะทำอย่างไร?
  2. ควรเกิดอะไรขึ้นถ้าสตริงเวอร์ชันหนึ่งมีส่วนมากกว่าอีกส่วน? มีแนวโน้มว่า "1.0" ควรถูกพิจารณาว่าน้อยกว่า "1.0.1" แต่แล้ว "1.0.0" ล่ะ?

นี่คือโค้ดสำหรับการนำไปใช้งานที่คุณสามารถใช้ได้โดยตรง (gist พร้อมเอกสารประกอบ):

function versionCompare(v1, v2, options) {
    var lexicographical = options && options.lexicographical,
        zeroExtend = options && options.zeroExtend,
        v1parts = v1.split('.'),
        v2parts = v2.split('.');

    function isValidPart(x) {
        return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
    }

    if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
        return NaN;
    }

    if (zeroExtend) {
        while (v1parts.length < v2parts.length) v1parts.push("0");
        while (v2parts.length < v1parts.length) v2parts.push("0");
    }

    if (!lexicographical) {
        v1parts = v1parts.map(Number);
        v2parts = v2parts.map(Number);
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) {
            return 1;
        }

        if (v1parts[i] == v2parts[i]) {
            continue;
        }
        else if (v1parts[i] > v2parts[i]) {
            return 1;
        }
        else {
            return -1;
        }
    }

    if (v1parts.length != v2parts.length) {
        return -1;
    }

    return 0;
}

เวอร์ชันนี้เปรียบเทียบส่วนต่าง ๆ อย่างเป็นธรรมชาติ ไม่ยอมรับตัวอักษรต่อท้าย และพิจารณา "1.7" ว่าน้อยกว่า "1.7.0" โหมดการเปรียบเทียบสามารถเปลี่ยนเป็นแบบลำดับอักขระ และสตริงเวอร์ชันที่สั้นกว่าสามารถถูกเติมศูนย์โดยอัตโนมัติด้วยอาร์กิวเมนต์ที่สามแบบเลือกได้

มี JSFiddle ที่รัน "unit tests" อยู่ที่นี่; เป็นเวอร์ชันที่ขยายเล็กน้อยจากผลงานของ ripper234 (ขอบคุณ)

บันทึกสำคัญ: โค้ดนี้ใช้ Array.map และ Array.every ซึ่งหมายความว่าจะไม่สามารถทำงานใน IE เวอร์ชันที่เก่ากว่า 9 หากคุณต้องการรองรับเวอร์ชันเหล่านั้น คุณจะต้องจัดเตรียม polyfills สำหรับเมธอดที่ขาดหายไป

คำตอบ #3

วิธีที่ง่ายที่สุดคือการใช้ localeCompare :

a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })

นี่จะคืนค่า:

  • 0: สตริงเวอร์ชันเท่ากัน
  • 1: เวอร์ชัน a มากกว่า b
  • -1: เวอร์ชัน b มากกว่า a

ข้อจำกัดความรับผิดชอบ: สิ่งนี้จะไม่จัดการกับเวอร์ชันก่อนการเปิดตัวได้อย่างถูกต้อง (เช่น 1.0.1-1)

คำตอบ #4

// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
    if (a === b) {
       return 0;
    }

    var a_components = a.split(".");
    var b_components = b.split(".");

    var len = Math.min(a_components.length, b_components.length);

    // loop while the components are equal
    for (var i = 0; i < len; i++) {
        // A bigger than B
        if (parseInt(a_components[i]) > parseInt(b_components[i])) {
            return 1;
        }

        // B bigger than A
        if (parseInt(a_components[i]) < parseInt(b_components[i])) {
            return -1;
        }
    }

    // If one's a prefix of the other, the longer one is greater.
    if (a_components.length > b_components.length) {
        return 1;
    }

    if (a_components.length < b_components.length) {
        return -1;
    }

    // Otherwise they are the same.
    return 0;
}

console.log(compare("1", "2"));
console.log(compare("2", "1"));

console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));

คำตอบ #5

ฟังก์ชันเปรียบเทียบนี้มีขนาดเล็กมาก แต่ทำงานได้เร็วมาก รับ หมายเลขเวอร์ชันได้ทุกความยาว และ ตัวเลขทุกขนาดในแต่ละส่วน

ค่าที่ส่งกลับ:
- ตัวเลข < 0 ถ้า a < b
- ตัวเลข > 0 ถ้า a > b
- 0 ถ้า a=b

ดังนั้นคุณสามารถใช้มันเป็น ฟังก์ชันเปรียบเทียบสำหรับ Array.sort();

แก้ไข: เวอร์ชันแก้บั๊ก ลบศูนย์ท้ายเพื่อให้สามารถจำแนก "1" และ "1.0.0" ว่าเท่ากัน

function cmpVersions (a, b) {
    var i, diff;
    var regExStrip0 = /(\.0+)+$/;
    var segmentsA = a.replace(regExStrip0, '').split('.');
    var segmentsB = b.replace(regExStrip0, '').split('.');
    var l = Math.min(segmentsA.length, segmentsB.length);

    for (i = 0; i < l; i++) {
        diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
        if (diff) {
            return diff;
        }
    }
    return segmentsA.length - segmentsB.length;
}

// TEST
console.log(
['2.5.10.4159',
 '1.0.0',
 '0.5',
 '0.4.1',
 '1',
 '1.1',
 '0.0.0',
 '2.5.0',
 '2',
 '0.0',
 '2.5.10',
 '10.5',
 '1.25.4',
 '1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]

คำตอบ #6

ฟังก์ชันที่ง่ายและสั้น:

function isNewerVersion (oldVer, newVer) {
  const oldParts = oldVer.split('.')
  const newParts = newVer.split('.')
  for (var i = 0; i < newParts.length; i++) {
    const a = ~~newParts[i] // parse int
    const b = ~~oldParts[i] // parse int
    if (a > b) return true
    if (a < b) return false
  }
  return false
}

การทดสอบ :

isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false

คำตอบ #7

นำมาจาก http://java.com/js/deployJava.js:

    // return true if 'installed' (considered as a JRE version string) is
    // greater than or equal to 'required' (again, a JRE version string).
    compareVersions: function (installed, required) {

        var a = installed.split('.');
        var b = required.split('.');

        for (var i = 0; i < a.length; ++i) {
            a[i] = Number(a[i]);
        }
        for (var i = 0; i < b.length; ++i) {
            b[i] = Number(b[i]);
        }
        if (a.length == 2) {
            a[2] = 0;
        }

        if (a[0] > b[0]) return true;
        if (a[0] < b[0]) return false;

        if (a[1] > b[1]) return true;
        if (a[1] < b[1]) return false;

        if (a[2] > b[2]) return true;
        if (a[2] < b[2]) return false;

        return true;
    }

คำตอบ #8

นี่คือตัวอย่างเวอร์ชันสั้นอีกตัวที่ใช้งานได้กับจำนวนของซับเวอร์ชันใด ๆ มีการเติมศูนย์ และแม้กระทั่งตัวเลขที่มีตัวอักษร (1.0.0b3)

const compareVersions = ((prep, l, i, r) => (a, b) =>
{
    a = prep(a);
    b = prep(b);
    l = Math.max(a.length, b.length);
    i = 0;
    r = i;
    while (!r && i < l)
        //convert into integer, uncluding undefined values
      r = ~~a[i] - ~~b[i++];

    return r < 0 ? -1 : (r ? 1 : 0);
})(t => ("" + t)
    // treat non-numerical characters as lower version
    // replacing them with a negative number based on charcode of first character
  .replace(/[^\d.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
    // remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
  .replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
    // return array
  .split("."));

ฟังก์ชันส่งค่ากลับ:

0 ถ้า a=b

1 ถ้า a > b

-1 ถ้า a < b

1.0         = 1.0.0.0.0.0
1.0         < 1.0.1
1.0b1       < 1.0
1.0b        = 1.0b
1.1         > 1.0.1b
1.1alpha    < 1.1beta
1.1rc1      > 1.1beta
1.1rc1      < 1.1rc2
1.1.0a1     < 1.1a2
1.1.0a10    > 1.1.0a1
1.1.0alpha  = 1.1a
1.1.0alpha2 < 1.1b1
1.0001      > 1.00000.1.0.0.0.01

สิ่งที่มันทำคือแทนที่ตัวอักษรที่ไม่ใช่ตัวเลขด้วยตัวเลขลบตามรหัสตัวอักษรของตัวอักษรตัวแรก แยกสตริงออกเป็นอาร์เรย์ จากนั้นเปรียบเทียบแต่ละรายการระหว่างอาร์เรย์

มันใช้ลูกเล่น ~~ ที่จะแปลงทุกอย่างให้เป็นจำนวนเต็ม ดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับความยาวของอาร์เรย์ที่ไม่เท่ากัน

/*use strict*/
const compareVersions = ((prep, l, i, r) => (a, b) =>
{
    a = prep(a);
    b = prep(b);
    l = Math.max(a.length, b.length);
    i = 0;
    r = i;
    while (!r && i < l)
        //convert into integer, uncluding undefined values
      r = ~~a[i] - ~~b[i++];

    return r < 0 ? -1 : (r ? 1 : 0);
})(t => ("" + t)
    // treat non-numerical characters as lower version
    // replacing them with a negative number based on charcode of first character
  .replace(/[^\d.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
    // remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
  .replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
    // return array
  .split("."));

//examples

let list = [
  ["1.0",         "1.0.0.0.0.0"],
  ["1.0",         "1.0.1"],
  ["1.0b1",       "1.0"],
  ["1.0b",        "1.0b"],
  ["1.1",         "1.0.1b"],
  ["1.1alpha",    "1.1beta"],
  ["1.1rc1",      "1.1beta"],
  ["1.1rc1",      "1.1rc2"],
  ["1.1.0a1",     "1.1a2"],
  ["1.1.0a10",    "1.1.0a1"],
  ["1.1.0alpha",  "1.1a"],
  ["1.1.0alpha2", "1.1b1"],
  ["1.0001",      "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
  console.log( list[i][0] + " " + "<=>"[compareVersions(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}

https://jsfiddle.net/vanowm/p7uvtbor/

คำตอบ #9

ไม่สามารถหาฟังก์ชันที่ทำสิ่งที่ฉันต้องการที่นี่ได้ ดังนั้นฉันจึงเขียนฟังก์ชันของตัวเอง นี่คือผลงานของฉัน หวังว่าใครบางคนจะพบว่ามันมีประโยชน์

ข้อดี:

  • จัดการสตริงเวอร์ชันที่มีความยาวใดก็ได้ '1' หรือ '1.1.1.1.1'

  • ตั้งค่าค่าเริ่มต้นของแต่ละค่าที่ 0 หากไม่ได้ระบุ แค่เพียงเพราะสตริงยาวกว่าไม่ได้หมายความว่ามันเป็นเวอร์ชันที่ใหญ่กว่า ('1' ควรเหมือนกับ '1.0' และ '1.0.0.0')

  • เปรียบเทียบตัวเลขไม่ใช่สตริง ('3'<'21' ควรเป็นจริง ไม่ใช่เท็จ)

  • อย่าเสียเวลาไปกับการเปรียบเทียบที่ไร้ประโยชน์ในลูป (การเปรียบเทียบด้วย ==)

  • คุณสามารถเลือกตัวเปรียบเทียบของคุณเองได้

ข้อเสีย:

  • มันไม่จัดการตัวอักษรในสตริงเวอร์ชัน (ฉันก็ไม่รู้ว่ามันจะทำงานได้ยังไง)

โค้ดของฉัน คล้ายกับคำตอบที่ได้รับการยอมรับโดย Jon:

function compareVersions(v1, comparator, v2) {
    "use strict";
    var comparator = comparator == '=' ? '==' : comparator;
    if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
        throw new Error('Invalid comparator. ' + comparator);
    }
    var v1parts = v1.split('.'), v2parts = v2.split('.');
    var maxLen = Math.max(v1parts.length, v2parts.length);
    var part1, part2;
    var cmp = 0;
    for(var i = 0; i < maxLen && !cmp; i++) {
        part1 = parseInt(v1parts[i], 10) || 0;
        part2 = parseInt(v2parts[i], 10) || 0;
        if(part1 < part2)
            cmp = 1;
        if(part1 > part2)
            cmp = -1;
    }
    return eval('0' + comparator + cmp);
}

ตัวอย่าง:

compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false

บทความที่เกี่ยวข้อง