Cách tạo sự khác biệt giữa hai mảng đối tượng trong JavaScript


100

Tôi có hai bộ kết quả như thế này:

// Result 1
[
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" },
    { value: "4", display: "Ryan" }
]

// Result 2
[
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" },
]

Kết quả cuối cùng tôi cần là sự khác biệt giữa các mảng này - kết quả cuối cùng sẽ như thế này:

[{ value: "4", display: "Ryan" }]

Có thể làm điều gì đó như thế này trong JavaScript không?


Vì vậy, bạn muốn một mảng gồm tất cả các phần tử không xuất hiện trong cả hai mảng, được lọc theo giá trị & hiển thị?
Cerbrus

Tôi muốn sự khác biệt giữa hai mảng. Giá trị sẽ có trong bất kỳ một trong các mảng.
BKM


2
Có vẻ như mảng như được hiển thị khi đăng nhập vào bảng điều khiển firebug ...
Mike C

2
Xin lỗi, nhưng các đối tượng json là sai ... bạn cần phải thay đổi = cho:
Walter Zalazar

Câu trả lời:


136

Chỉ sử dụng JS gốc, một cái gì đó như thế này sẽ hoạt động:

a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}]
b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}]

function comparer(otherArray){
  return function(current){
    return otherArray.filter(function(other){
      return other.value == current.value && other.display == current.display
    }).length == 0;
  }
}

var onlyInA = a.filter(comparer(b));
var onlyInB = b.filter(comparer(a));

result = onlyInA.concat(onlyInB);

console.log(result);


1
Cách này hoạt động và có thể là câu trả lời trực tiếp tốt nhất, nhưng sẽ rất tuyệt nếu biến nó thành một thứ chấp nhận một vị ngữ và hai danh sách và trả về sự khác biệt đối xứng của hai danh sách bằng cách áp dụng vị từ một cách thích hợp. (Thêm điểm nếu nó là hiệu quả hơn vì phải chạy qua tất cả m * n so sánh hai lần!)
Scott Sauyet

@ScottSauyet: Tôi không quen với thuật ngữ "vị ngữ" (Không phải là người nói tiếng Anh bản ngữ). Đó có phải là return a.value ===...câu trả lời của bạn? (Nhân tiện, giải pháp tuyệt vời, +1) Ngoài việc sử dụng Array.prototype.some(), tôi thực sự không thể tìm thấy cách hiệu quả hơn / ngắn hơn để làm điều này.
Cerbrus

2
@Cerbrus: Có. Vị từ là một hàm trả về boolean ( truehoặc falsegiá trị.) Trong trường hợp này, nếu chúng ta tách khái niệm kiểm tra tính bình đẳng với phần còn lại của mã bằng cách yêu cầu người dùng vượt qua kiểm tra bình đẳng như một hàm, chúng ta có thể thực hiện một thuật toán chung đơn giản.
Scott Sauyet

@ScottSauyet: Tôi phải mất một lúc để tìm lại câu trả lời này, nhưng bây giờ nó đã khá hơn một chút: D
Cerbrus

ES6 const so sánhr = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Shnigi

68

Bạn có thể sử dụng Array.prototype.filter()kết hợp với Array.prototype.some().

Đây là một ví dụ (giả sử mảng của bạn được lưu trữ trong các biến result1result2):

//Find values that are in result1 but not in result2
var uniqueResultOne = result1.filter(function(obj) {
    return !result2.some(function(obj2) {
        return obj.value == obj2.value;
    });
});

//Find values that are in result2 but not in result1
var uniqueResultTwo = result2.filter(function(obj) {
    return !result1.some(function(obj2) {
        return obj.value == obj2.value;
    });
});

//Combine the two arrays of unique entries
var result = uniqueResultOne.concat(uniqueResultTwo);

40

Đối với những người thích các giải pháp một lớp trong ES6, một cái gì đó như sau:

const arrayOne = [ 
  { value: "4a55eff3-1e0d-4a81-9105-3ddd7521d642", display: "Jamsheer" },
  { value: "644838b3-604d-4899-8b78-09e4799f586f", display: "Muhammed" },
  { value: "b6ee537a-375c-45bd-b9d4-4dd84a75041d", display: "Ravi" },
  { value: "e97339e1-939d-47ab-974c-1b68c9cfb536", display: "Ajmal" },
  { value: "a63a6f77-c637-454e-abf2-dfb9b543af6c", display: "Ryan" },
];
          
const arrayTwo = [
  { value: "4a55eff3-1e0d-4a81-9105-3ddd7521d642", display: "Jamsheer"},
  { value: "644838b3-604d-4899-8b78-09e4799f586f", display: "Muhammed"},
  { value: "b6ee537a-375c-45bd-b9d4-4dd84a75041d", display: "Ravi"},
  { value: "e97339e1-939d-47ab-974c-1b68c9cfb536", display: "Ajmal"},
];

const results = arrayOne.filter(({ value: id1 }) => !arrayTwo.some(({ value: id2 }) => id2 === id1));

console.log(results);


3
Hãy giải thích nó!
Bruno Brito

15

Tôi thực hiện một cách tiếp cận có mục đích chung hơn một chút, mặc dù ý tưởng tương tự như các cách tiếp cận của cả @Cerbrus@Kasper Moerch . Tôi tạo một hàm chấp nhận một vị từ để xác định xem hai đối tượng có bằng nhau hay không (ở đây chúng tôi bỏ qua thuộc $$hashKeytính, nhưng nó có thể là bất kỳ thứ gì) và trả về một hàm tính toán sự khác biệt đối xứng của hai danh sách dựa trên vị từ đó:

a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}]
b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}]

var makeSymmDiffFunc = (function() {
    var contains = function(pred, a, list) {
        var idx = -1, len = list.length;
        while (++idx < len) {if (pred(a, list[idx])) {return true;}}
        return false;
    };
    var complement = function(pred, a, b) {
        return a.filter(function(elem) {return !contains(pred, elem, b);});
    };
    return function(pred) {
        return function(a, b) {
            return complement(pred, a, b).concat(complement(pred, b, a));
        };
    };
}());

var myDiff = makeSymmDiffFunc(function(x, y) {
    return x.value === y.value && x.display === y.display;
});

var result = myDiff(a, b); //=>  {value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan"}

Nó có một lợi thế nhỏ so với cách tiếp cận của Cerebrus (cũng như cách tiếp cận của Kasper Moerch) ở chỗ nó thoát sớm; nếu nó tìm thấy một kết quả phù hợp, nó sẽ không bận tâm kiểm tra phần còn lại của danh sách. Nếu tôi có một currychức năng hữu ích, tôi sẽ làm điều này hơi khác một chút, nhưng điều này hoạt động tốt.

Giải trình

Một bình luận yêu cầu giải thích chi tiết hơn cho người mới bắt đầu. Đây là một nỗ lực.

Chúng tôi chuyển hàm sau cho makeSymmDiffFunc:

function(x, y) {
    return x.value === y.value && x.display === y.display;
}

Hàm này là cách chúng ta quyết định rằng hai đối tượng bằng nhau. Giống như tất cả các hàm trả về truehoặc false, nó có thể được gọi là "hàm vị từ", nhưng đó chỉ là thuật ngữ. Điểm chính là nó makeSymmDiffFuncđược cấu hình với một hàm chấp nhận hai đối tượng và trả về truenếu chúng ta coi chúng bằng nhau, falsenếu chúng ta không.

Sử dụng hàm đó, makeSymmDiffFunc(đọc "tạo hàm khác biệt đối xứng") trả về cho chúng ta một hàm mới:

        return function(a, b) {
            return complement(pred, a, b).concat(complement(pred, b, a));
        };

Đây là chức năng chúng tôi thực sự sẽ sử dụng. Chúng tôi chuyển cho nó hai danh sách và nó tìm các phần tử trong danh sách đầu tiên không có trong danh sách thứ hai, sau đó tìm các phần tử trong danh sách thứ hai không có trong danh sách đầu tiên và kết hợp hai danh sách này.

Tuy nhiên, nhìn lại nó một lần nữa, tôi chắc chắn có thể đã lấy một gợi ý từ mã của bạn và đơn giản hóa hàm chính một chút bằng cách sử dụng some:

var makeSymmDiffFunc = (function() {
    var complement = function(pred, a, b) {
        return a.filter(function(x) {
            return !b.some(function(y) {return pred(x, y);});
        });
    };
    return function(pred) {
        return function(a, b) {
            return complement(pred, a, b).concat(complement(pred, b, a));
        };
    };
}());

complementsử dụng vị từ và trả về các phần tử của danh sách đầu tiên không có trong danh sách thứ hai. Điều này đơn giản hơn lần vượt qua đầu tiên của tôi với một containshàm riêng biệt .

Cuối cùng, hàm chính được bao bọc trong một biểu thức hàm được gọi ngay lập tức ( IIFE ) để giữ cho complementhàm bên trong nằm ngoài phạm vi toàn cục.


Cập nhật, một vài năm sau

Bây giờ ES2015 đã trở nên khá phổ biến, tôi sẽ đề xuất kỹ thuật tương tự, với ít bản ghi âm hơn:

const diffBy = (pred) => (a, b) => a.filter(x => !b.some(y => pred(x, y)))
const makeSymmDiffFunc = (pred) => (a, b) => diffBy(pred)(a, b).concat(diffBy(pred)(b, a))

const myDiff = makeSymmDiffFunc((x, y) => x.value === y.value && x.display === y.display)

const result = myDiff(a, b)
//=>  {value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan"}

1
Bạn có thể thêm một số giải thích cho mã của bạn? Tôi không chắc một người mới bắt đầu học JavaScript sẽ hiểu cách thức hoạt động của phương pháp tiếp cận vị từ.
kaspermoerch

1
@KasperMoerch: Đã thêm một lời giải thích dài dòng. Tôi hy vọng rằng sẽ giúp. (Nó cũng khiến tôi nhận ra một sự dọn dẹp nghiêm túc mà mã này nên có.)
Scott Sauyet

8
import differenceBy from 'lodash/differenceBy'

const myDifferences = differenceBy(Result1, Result2, 'value')

Thao tác này sẽ trả về sự khác biệt giữa hai mảng đối tượng, sử dụng phím valueđể so sánh chúng. Lưu ý rằng hai thứ có cùng giá trị sẽ không được trả lại vì các khóa khác bị bỏ qua.

Đây là một phần của lodash .


Đối tượng json ở trên là sai. Khi thử cách thay đổi này = cho:
Walter Zalazar

6

Bạn có thể tạo một đối tượng với các khóa là giá trị duy nhất tương ứng cho từng đối tượng trong mảng và sau đó lọc từng mảng dựa trên sự tồn tại của khóa trong đối tượng khác. Nó làm giảm sự phức tạp của hoạt động.

ES6

let a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}];
let b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}];

let valuesA = a.reduce((a,{value}) => Object.assign(a, {[value]:value}), {});
let valuesB = b.reduce((a,{value}) => Object.assign(a, {[value]:value}), {});
let result = [...a.filter(({value}) => !valuesB[value]), ...b.filter(({value}) => !valuesA[value])];
console.log(result);

ES5

var a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}];
var b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}];

var valuesA = a.reduce(function(a,c){a[c.value] = c.value; return a; }, {});
var valuesB = b.reduce(function(a,c){a[c.value] = c.value; return a; }, {});
var result = a.filter(function(c){ return !valuesB[c.value]}).concat(b.filter(function(c){ return !valuesA[c.value]}));
console.log(result);


5

Tôi nghĩ rằng giải pháp @Cerbrus là đúng. Tôi đã thực hiện cùng một giải pháp nhưng đã trích xuất mã lặp lại thành hàm riêng của nó (DRY).

 function filterByDifference(array1, array2, compareField) {
  var onlyInA = differenceInFirstArray(array1, array2, compareField);
  var onlyInb = differenceInFirstArray(array2, array1, compareField);
  return onlyInA.concat(onlyInb);
}

function differenceInFirstArray(array1, array2, compareField) {
  return array1.filter(function (current) {
    return array2.filter(function (current_b) {
        return current_b[compareField] === current[compareField];
      }).length == 0;
  });
}

2

Tôi tìm thấy giải pháp này bằng cách sử dụng bộ lọc và một số.

resultFilter = (firstArray, secondArray) => {
  return firstArray.filter(firstArrayItem =>
    !secondArray.some(
      secondArrayItem => firstArrayItem._user === secondArrayItem._user
    )
  );
};


1

Hầu hết các câu trả lời ở đây đều khá phức tạp, nhưng không phải logic đằng sau điều này khá đơn giản sao?

  1. kiểm tra mảng nào dài hơn và cung cấp nó làm tham số đầu tiên (nếu độ dài bằng nhau, thứ tự tham số không quan trọng)
  2. Lặp lại trên mảng 1.
  3. Đối với phần tử lặp hiện tại của array1, hãy kiểm tra xem nó có hiện diện trong array2 hay không
  4. Nếu nó KHÔNG hiện diện, hơn
  5. Đẩy nó vào mảng 'khác biệt'
const getArraysDifference = (longerArray, array2) => {
  const difference = [];

  longerArray.forEach(el1 => {      /*1*/
    el1IsPresentInArr2 = array2.some(el2 => el2.value === el1.value); /*2*/

    if (!el1IsPresentInArr2) { /*3*/
      difference.push(el1);    /*4*/
    }
  });

  return difference;
}

Độ phức tạp O (n ^ 2).


cộng 1 để bao gồm độ phức tạp
delavago1999

1

bạn có thể làm khác a trên b và khác b trên a, sau đó hợp nhất cả hai kết quả

let a = [
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" },
    { value: "4", display: "Ryan" }
]

let b = [
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" }
]

// b diff a
let resultA = b.filter(elm => !a.map(elm => JSON.stringify(elm)).includes(JSON.stringify(elm)));

// a diff b
let resultB = a.filter(elm => !b.map(elm => JSON.stringify(elm)).includes(JSON.stringify(elm)));  

// show merge 
console.log([...resultA, ...resultB]);


0

Tôi đã tạo một khác biệt tổng quát để so sánh 2 đối tượng thuộc bất kỳ loại nào và có thể chạy trình xử lý sửa đổi gist.github.com/bortunac "diff.js" một cách sử dụng:

old_obj={a:1,b:2,c:[1,2]}
now_obj={a:2 , c:[1,3,5],d:55}

do đó thuộc tính a được sửa đổi, b bị xóa, c được sửa đổi, d được thêm vào

var handler=function(type,pointer){
console.log(type,pointer,this.old.point(pointer)," | ",this.now.point(pointer)); 

}

bây giờ sử dụng như

df=new diff();
df.analize(now_obj,old_obj);
df.react(handler);

bảng điều khiển sẽ hiển thị

mdf ["a"]  1 | 2 
mdf ["c", "1"]  2 | 3 
add ["c", "2"]  undefined | 5 
add ["d"]  undefined | 55 
del ["b"]  2 | undefined 

0

Cách chung chung và đơn giản nhất:

findObject(listOfObjects, objectToSearch) {
    let found = false, matchingKeys = 0;
    for(let object of listOfObjects) {
        found = false;
        matchingKeys = 0;
        for(let key of Object.keys(object)) {
            if(object[key]==objectToSearch[key]) matchingKeys++;
        }
        if(matchingKeys==Object.keys(object).length) {
            found = true;
            break;
        }
    }
    return found;
}

get_removed_list_of_objects(old_array, new_array) {
    // console.log('old:',old_array);
    // console.log('new:',new_array);
    let foundList = [];
    for(let object of old_array) {
        if(!this.findObject(new_array, object)) foundList.push(object);
    }
    return foundList;
}

get_added_list_of_objects(old_array, new_array) {
    let foundList = [];
    for(let object of new_array) {
        if(!this.findObject(old_array, object)) foundList.push(object);
    }
    return foundList;
}

0

Tôi thích đối tượng bản đồ hơn khi nói đến các mảng lớn.

// create tow arrays
array1 = Array.from({length: 400},() => ({value:Math.floor(Math.random() * 4000)}))
array2 = Array.from({length: 400},() => ({value:Math.floor(Math.random() * 4000)}))

// calc diff with some function
console.time('diff with some');
results = array2.filter(({ value: id1 }) => array1.some(({ value: id2 }) => id2 === id1));
console.log('diff results ',results.length)
console.timeEnd('diff with some');

// calc diff with map object
console.time('diff with map');
array1Map = {};
for(const item1 of array1){
    array1Map[item1.value] = true;
}
results = array2.filter(({ value: id2 }) => array1Map[id2]);
console.log('map results ',results.length)
console.timeEnd('diff with map');


0

JavaScript có Bản đồ, cung cấp thời gian chèn và tra cứu O (1). Do đó, điều này có thể được giải quyết bằng O (n) (chứ không phải O (n²) như tất cả các câu trả lời khác). Vì vậy, cần phải tạo một khóa nguyên thủy (chuỗi / số) duy nhất cho mỗi đối tượng. Người ta có thể JSON.stringify, nhưng điều đó khá dễ xảy ra lỗi vì thứ tự của các phần tử có thể ảnh hưởng đến sự bình đẳng:

 JSON.stringify({ a: 1, b: 2 }) !== JSON.stringify({ b: 2, a: 1 })

Do đó, tôi sẽ lấy một dấu phân tách không xuất hiện trong bất kỳ giá trị nào và soạn một chuỗi theo cách thủ công:

const toHash = value => value.value + "@" + value.display;

Sau đó, một Bản đồ được tạo. Khi một phần tử đã tồn tại trong Bản đồ, phần tử đó sẽ bị xóa, nếu không, phần tử đó sẽ được thêm vào. Do đó chỉ còn lại các phần tử được bao gồm lần lẻ (nghĩa là chỉ một lần). Điều này sẽ chỉ hoạt động nếu các phần tử là duy nhất trong mỗi mảng:

const entries = new Map();

for(const el of [...firstArray, ...secondArray]) {
  const key = toHash(el);
  if(entries.has(key)) {
    entries.delete(key);
  } else {
    entries.set(key, el);
  }
}

const result = [...entries.values()];


0

Tôi đã gặp câu hỏi này khi đang tìm cách chọn ra mục đầu tiên trong một mảng không khớp với bất kỳ giá trị nào trong một mảng khác và cuối cùng đã quản lý để sắp xếp nó với array.find () và array.filter () như điều này

var carList= ['mercedes', 'lamborghini', 'bmw', 'honda', 'chrysler'];
var declinedOptions = ['mercedes', 'lamborghini'];

const nextOption = carList.find(car=>{
    const duplicate = declinedOptions.filter(declined=> {
      return declined === car
    })
    console.log('duplicate:',duplicate) //should list out each declined option
    if(duplicate.length === 0){//if theres no duplicate, thats the nextOption
      return car
    }
})

console.log('nextOption:', nextOption);
//expected outputs
//duplicate: mercedes
//duplicate: lamborghini
//duplicate: []
//nextOption: bmw

nếu bạn cần tiếp tục tìm nạp danh sách cập nhật trước khi kiểm tra chéo để tìm tùy chọn tốt nhất tiếp theo, điều này sẽ hoạt động đủ tốt :)


-2

Nếu bạn sẵn sàng sử dụng các thư viện bên ngoài, Bạn có thể sử dụng _.difference trong underscore.js để đạt được điều này. _.difference trả về các giá trị từ mảng không có trong các mảng khác.

_.difference([1,2,3,4,5][1,4,10])

==>[2,3,5]

11
Điều này chỉ hoạt động trên các mảng có giá trị nguyên thủy. Nếu mảng chứa danh sách các đối tượng, giống như câu hỏi này, nó sẽ không hoạt động vì nó cố gắng so sánh các tham chiếu thay vì chính các đối tượng, điều này hầu như luôn có nghĩa là mọi thứ đều khác.
Ross Pe People

1
cảm ơn Ross bạn đã cứu tôi khỏi một cơn đau đầu. Tôi đã chuẩn bị báo cáo một lỗi để gạch dưới.
Etienne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.