อัลกอริทึม - ฉันจะเปรียบเทียบหมายเลขเวอร์ชันซอฟต์แวร์โดยใช้ 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.0a") จะทำอย่างไร?
- ควรเกิดอะไรขึ้นถ้าสตริงเวอร์ชันหนึ่งมีส่วนมากกว่าอีกส่วน? มีแนวโน้มว่า "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
