Can We Use Comparator For Arrays.sort? Yes, you can use a comparator function with the arrays.sort
method to define a custom sorting order for elements within the array, enhancing sorting efficiency. This comprehensive guide on COMPARE.EDU.VN explores the usage of comparators for array sorting, covering syntax, implementation, and best practices, ultimately empowering you with the knowledge to choose the optimal comparison strategy for enhanced performance. Discover more by exploring comparison functions, custom sorting, and sorting algorithms.
1. What is the Role of a Comparator in Arrays.Sort?
The primary role of a comparator in the arrays.sort
method is to define a custom comparison logic that dictates how elements within an array should be ordered during the sorting process. It provides a way to go beyond the default lexicographical sorting and implement more complex or domain-specific sorting rules.
1.1 Defining Custom Sorting Logic
- Definition: A comparator function is a callback function that takes two elements from the array as input and returns a value indicating their relative order.
- Syntax: The comparator function has the form
compareFn(a, b)
, wherea
andb
are the elements being compared. - Return Values:
- If
compareFn(a, b)
returns a value less than 0,a
is sorted beforeb
. - If
compareFn(a, b)
returns a value greater than 0,a
is sorted afterb
. - If
compareFn(a, b)
returns 0, the original order ofa
andb
is preserved.
- If
1.2 Benefits of Using Comparators
- Flexibility: Comparators enable you to sort arrays based on any criteria, including numerical value, string length, object properties, or custom business rules.
- Control: You have complete control over the sorting process, ensuring that the elements are arranged according to your specific requirements.
- Customization: Comparators allow you to tailor the sorting behavior to match the unique characteristics of your data.
1.3 Illustrative Examples
- Sorting Numbers in Ascending Order:
function compareNumbers(a, b) { return a - b; }
let numbers = [5, 2, 8, 1, 9];
numbers.sort(compareNumbers); // [1, 2, 5, 8, 9]
- **Sorting Strings by Length**:
```javascript
function compareStringLength(a, b) {
return a.length - b.length;
}
let strings = ["apple", "banana", "kiwi", "orange"];
strings.sort(compareStringLength); // ["kiwi", "apple", "banana", "orange"]
- Sorting Objects by Property:
function compareObjectsByProperty(propertyName) { return function(a, b) { return a[propertyName] - b[propertyName]; }; }
let objects = [{age: 30}, {age: 20}, {age: 40}];
objects.sort(compareObjectsByProperty(“age”)); // [{age: 20}, {age: 30}, {age: 40}]
**2. How Do You Implement a Comparator for Numerical Sorting?**
Implementing a comparator for numerical sorting involves creating a function that takes two numbers as input and returns a value indicating their relative order. The function should return a negative value if the first number is less than the second, a positive value if the first number is greater than the second, and zero if they are equal.
**2.1 Basic Numerical Comparator**
- **Implementation**:
```javascript
function compareNumbers(a, b) {
return a - b;
}
- Usage:
let numbers = [5, 2, 8, 1, 9]; numbers.sort(compareNumbers); // [1, 2, 5, 8, 9]
- Explanation:
- The
compareNumbers
function subtractsb
froma
. - If
a
is less thanb
, the result is negative, anda
is sorted beforeb
. - If
a
is greater thanb
, the result is positive, anda
is sorted afterb
. - If
a
is equal tob
, the result is zero, and their original order is preserved.
- The
2.2 Handling NaN Values
- Challenge:
NaN
values can cause unexpected behavior when using the basic numerical comparator. - Solution: Add a check for
NaN
values and handle them appropriately. - Implementation:
function compareNumbersWithNaN(a, b) { if (isNaN(a)) return -1; // NaN values go to the end if (isNaN(b)) return 1; return a - b; }
- Usage:
let numbersWithNaN = [5, NaN, 2, 8, 1, NaN, 9]; numbersWithNaN.sort(compareNumbersWithNaN); // [1, 2, 5, 8, 9, NaN, NaN]
- Explanation:
- The
isNaN
function checks if a value isNaN
. - If
a
isNaN
, it is sorted afterb
. - If
b
isNaN
, it is sorted aftera
.
- The
2.3 Sorting in Descending Order
- Modification: To sort numbers in descending order, simply reverse the subtraction.
- Implementation:
function compareNumbersDescending(a, b) { return b - a; }
- Usage:
let numbers = [5, 2, 8, 1, 9]; numbers.sort(compareNumbersDescending); // [9, 8, 5, 2, 1]
- Explanation:
- The
compareNumbersDescending
function subtractsa
fromb
. - If
a
is less thanb
, the result is positive, anda
is sorted afterb
. - If
a
is greater thanb
, the result is negative, anda
is sorted beforeb
.
- The
3. How Can You Sort an Array of Objects Using a Comparator?
Sorting an array of objects using a comparator involves creating a function that compares specific properties of the objects to determine their relative order. This allows you to sort objects based on any criteria, such as name, age, or custom business logic.
3.1 Basic Object Comparator
- Implementation:
function compareObjectsByProperty(propertyName) { return function(a, b) { if (a[propertyName] < b[propertyName]) return -1; if (a[propertyName] > b[propertyName]) return 1; return 0; }; }
- Usage:
let objects = [{name: "Alice", age: 30}, {name: "Bob", age: 20}, {name: "Charlie", age: 40}]; objects.sort(compareObjectsByProperty("age")); // [{name: "Bob", age: 20}, {name: "Alice", age: 30}, {name: "Charlie", age: 40}]
- Explanation:
- The
compareObjectsByProperty
function takes apropertyName
as input and returns a comparator function. - The comparator function compares the values of the specified property for two objects
a
andb
. - If
a[propertyName]
is less thanb[propertyName]
, it returns -1, sortinga
beforeb
. - If
a[propertyName]
is greater thanb[propertyName]
, it returns 1, sortinga
afterb
. - If they are equal, it returns 0, preserving their original order.
- The
3.2 Sorting by Multiple Properties
- Challenge: Sometimes, you need to sort objects by multiple properties, such as sorting by last name and then first name.
- Solution: Create a comparator that checks multiple properties in order.
- Implementation:
function compareObjectsByMultipleProperties(properties) { return function(a, b) { for (let i = 0; i < properties.length; i++) { let propertyName = properties[i]; if (a[propertyName] < b[propertyName]) return -1; if (a[propertyName] > b[propertyName]) return 1; } return 0; }; }
- Usage:
let objects = [ {firstName: "Alice", lastName: "Smith"}, {firstName: "Bob", lastName: "Jones"}, {firstName: "Charlie", lastName: "Smith"} ]; objects.sort(compareObjectsByMultipleProperties(["lastName", "firstName"])); // [{firstName: "Bob", lastName: "Jones"}, {firstName: "Alice", lastName: "Smith"}, {firstName: "Charlie", lastName: "Smith"}]
- Explanation:
- The
compareObjectsByMultipleProperties
function takes an array ofproperties
as input. - It iterates through the properties and compares the values in order.
- If a difference is found, it returns -1 or 1 based on the comparison.
- If all properties are equal, it returns 0.
- The
3.3 Handling Different Data Types
- Challenge: Object properties can have different data types, such as numbers, strings, or dates.
- Solution: Use appropriate comparison logic for each data type.
- Implementation:
function compareObjectsByPropertyWithType(propertyName, type) { return function(a, b) { if (type === "number") { return a[propertyName] - b[propertyName]; } else if (type === "string") { return a[propertyName].localeCompare(b[propertyName]); } else if (type === "date") { return a[propertyName].getTime() - b[propertyName].getTime(); } else { // Default comparison if (a[propertyName] < b[propertyName]) return -1; if (a[propertyName] > b[propertyName]) return 1; return 0; } }; }
- Usage:
let objects = [ {name: "Alice", date: new Date("2024-01-01")}, {name: "Bob", date: new Date("2023-01-01")}, {name: "Charlie", date: new Date("2025-01-01")} ]; objects.sort(compareObjectsByPropertyWithType("date", "date")); // [{name: "Bob", date: new Date("2023-01-01")}, {name: "Alice", date: new Date("2024-01-01")}, {name: "Charlie", date: new Date("2025-01-01")}]
- Explanation:
- The
compareObjectsByPropertyWithType
function takes apropertyName
and atype
as input. - It uses different comparison logic based on the
type
. - For numbers, it subtracts the values.
- For strings, it uses
localeCompare
. - For dates, it uses
getTime
to compare the date values.
- The
4. What Are Some Best Practices for Writing Comparators?
Writing effective comparators requires careful consideration of various factors, including data types, comparison logic, and potential edge cases. Adhering to best practices ensures that your comparators are reliable, efficient, and maintainable.
4.1 Ensuring Comparator Stability
- Definition: A stable comparator maintains the relative order of elements that are considered equal by the comparison logic.
- Importance: Stability is crucial when sorting arrays with elements that have the same value for the sorting criteria.
- Implementation:
function compareObjectsByPropertyStable(propertyName) { return function(a, b) { if (a[propertyName] < b[propertyName]) return -1; if (a[propertyName] > b[propertyName]) return 1; return 0; // Maintain original order for equal elements }; }
- Explanation:
- The comparator returns 0 when the elements are equal, preserving their original order.
4.2 Handling Null or Undefined Values
- Challenge: Null or undefined values can cause errors or unexpected behavior if not handled properly.
- Solution: Add checks for null or undefined values and define how they should be sorted.
- Implementation:
function compareObjectsByPropertyWithNullCheck(propertyName) { return function(a, b) { if (a[propertyName] == null && b[propertyName] == null) return 0; if (a[propertyName] == null) return -1; // Null values go to the end if (b[propertyName] == null) return 1; if (a[propertyName] < b[propertyName]) return -1; if (a[propertyName] > b[propertyName]) return 1; return 0; }; }
- Explanation:
- The comparator checks if either value is null or undefined.
- If both are null or undefined, it returns 0.
- If only one is null or undefined, it sorts the non-null value before the null value.
4.3 Optimizing Comparator Performance
- Minimize Computations: Avoid complex or time-consuming calculations within the comparator, as it will be called repeatedly during the sorting process.
- Use Efficient Data Structures: If necessary, preprocess the data into a more efficient data structure for comparison.
- Cache Results: If the comparison logic involves expensive operations, consider caching the results to avoid redundant calculations.
4.4 Avoiding Side Effects
- Pure Functions: Comparators should be pure functions, meaning they should not modify the input elements or any external state.
- Immutability: Avoid mutating the objects being compared or any external variables within the comparator.
- Consistency: Ensure that the comparator always returns the same result for the same pair of inputs.
5. What is the Difference Between LocaleCompare and a Custom Comparator for Strings?
When sorting strings, you have the option of using the localeCompare
method or creating a custom comparator. Understanding the differences between these approaches is crucial for choosing the most appropriate method for your specific use case.
5.1 LocaleCompare Method
- Definition: The
localeCompare
method compares two strings in the current locale, taking into account language-specific sorting rules. - Syntax:
string1.localeCompare(string2)
- Return Values:
- Returns a negative value if
string1
comes beforestring2
in the locale. - Returns a positive value if
string1
comes afterstring2
in the locale. - Returns 0 if the strings are equal in the locale.
- Returns a negative value if
5.2 Custom Comparator for Strings
- Implementation:
function compareStringsCustom(a, b) { return a.localeCompare(b); }
- Usage:
let strings = ["apple", "banana", "cherry"]; strings.sort(compareStringsCustom); // ["apple", "banana", "cherry"]
5.3 Key Differences
Feature | localeCompare |
Custom Comparator |
---|---|---|
Localization | Supports language-specific sorting rules | Requires manual implementation of localization |
Case Sensitivity | Can be case-sensitive or case-insensitive, depending on locale | Can be customized for case sensitivity |
Performance | Generally optimized for performance | Performance depends on the implementation |
Flexibility | Limited customization options | Provides full control over the comparison logic |
5.4 When to Use LocaleCompare
- Localized Sorting: When you need to sort strings according to the rules of a specific locale.
- Simple String Comparisons: For basic string comparisons that do not require complex logic.
- Performance: When performance is a concern, as
localeCompare
is generally optimized for speed.
5.5 When to Use a Custom Comparator
- Complex Sorting Logic: When you need to implement custom sorting rules, such as sorting by string length or ignoring certain characters.
- Case-Insensitive Sorting: When you need to perform case-insensitive sorting without relying on locale-specific settings.
- Fine-Grained Control: When you need full control over the comparison process.
6. How Do You Handle Case-Insensitive Sorting with a Comparator?
Case-insensitive sorting involves comparing strings without regard to the case of the letters. This can be achieved by converting the strings to lowercase or uppercase before comparing them.
6.1 Using toLowerCase() or toUpperCase()
- Implementation:
function compareStringsCaseInsensitive(a, b) { let aLower = a.toLowerCase(); let bLower = b.toLowerCase(); return aLower.localeCompare(bLower); }
- Usage:
let strings = ["Apple", "banana", "Cherry"]; strings.sort(compareStringsCaseInsensitive); // ["Apple", "banana", "Cherry"]
- Explanation:
- The
compareStringsCaseInsensitive
function converts both strings to lowercase before comparing them usinglocaleCompare
. - This ensures that the sorting is case-insensitive.
- The
6.2 Using localeCompare with Sensitivity Options
- Implementation:
function compareStringsCaseInsensitiveLocale(a, b) { return a.localeCompare(b, undefined, { sensitivity: 'base' }); }
- Usage:
let strings = ["Apple", "banana", "Cherry"]; strings.sort(compareStringsCaseInsensitiveLocale); // ["Apple", "banana", "Cherry"]
- Explanation:
- The
localeCompare
method is used with thesensitivity
option set to'base'
. - The
base
sensitivity means that only the base characters are compared, ignoring case and diacritics.
- The
6.3 Considerations
- Performance: Converting strings to lowercase or uppercase can impact performance, especially for large arrays.
- Locale: The
localeCompare
method with sensitivity options provides a more robust solution for case-insensitive sorting that takes into account language-specific rules. - Consistency: Ensure that you use the same case-insensitive comparison method throughout your application to maintain consistency.
7. Can You Provide Examples of Sorting Complex Data Structures?
Sorting complex data structures, such as arrays of objects with nested properties or arrays of arrays, requires more sophisticated comparators that can navigate the structure and compare the relevant values.
7.1 Sorting an Array of Objects with Nested Properties
- Data Structure:
let data = [ {name: "Alice", address: {city: "New York", country: "USA"}}, {name: "Bob", address: {city: "London", country: "UK"}}, {name: "Charlie", address: {city: "Paris", country: "France"}} ];
- Implementation:
function compareObjectsByNestedProperty(propertyNames) { return function(a, b) { let aValue = a; let bValue = b; for (let propertyName of propertyNames) { aValue = aValue[propertyName]; bValue = bValue[propertyName]; } if (aValue < bValue) return -1; if (aValue > bValue) return 1; return 0; }; }
- Usage:
data.sort(compareObjectsByNestedProperty(["address", "city"])); // Sort by city
- Explanation:
- The
compareObjectsByNestedProperty
function takes an array ofpropertyNames
as input, representing the path to the nested property. - It iterates through the
propertyNames
to access the nested property values. - It compares the nested property values and returns -1, 1, or 0 based on the comparison.
- The
7.2 Sorting an Array of Arrays
- Data Structure:
let data = [ [1, "Alice"], [2, "Bob"], [3, "Charlie"] ];
- Implementation:
function compareArraysByIndex(index) { return function(a, b) { if (a[index] < b[index]) return -1; if (a[index] > b[index]) return 1; return 0; }; }
- Usage:
data.sort(compareArraysByIndex(1)); // Sort by the second element in each array (name)
- Explanation:
- The
compareArraysByIndex
function takes anindex
as input, representing the index of the element to compare. - It compares the elements at the specified index in each array.
- It returns -1, 1, or 0 based on the comparison.
- The
7.3 Sorting an Array of Mixed Data Types
- Data Structure:
let data = [1, "Alice", 2, "Bob", 3, "Charlie"];
- Implementation:
function compareMixedDataTypes(a, b) { if (typeof a === "number" && typeof b === "number") { return a - b; } else if (typeof a === "string" && typeof b === "string") { return a.localeCompare(b); } else if (typeof a === "number" && typeof b === "string") { return -1; // Numbers before strings } else { return 1; // Strings before numbers } }
- Usage:
data.sort(compareMixedDataTypes); // Sort numbers before strings
- Explanation:
- The
compareMixedDataTypes
function checks the data types of the elements being compared. - It uses different comparison logic based on the data types.
- It sorts numbers before strings.
- The
Alt Text: Comparison of array sorting methods, highlighting the use of comparators for custom sorting logic and increased flexibility.
8. What Are the Performance Implications of Using a Comparator?
Using a comparator function with the arrays.sort
method provides flexibility in defining custom sorting orders, but it also introduces performance implications that should be considered, especially when dealing with large arrays.
8.1 Overhead of Comparator Functions
- Function Call Overhead: Each comparison performed by the
sort
method involves calling the comparator function, which introduces overhead due to function invocation. - Complexity: The complexity of the comparator function itself can significantly impact performance. Complex comparators with expensive calculations will slow down the sorting process.
- Impact on Large Arrays: The overhead of comparator functions becomes more pronounced with larger arrays, as the
sort
method needs to perform more comparisons.
8.2 Performance Benchmarks
- Native Sort vs. Comparator: In general, using a native sort (without a comparator) is faster than using a custom comparator for simple data types like numbers or strings.
- Complex Objects: For complex objects, the performance difference may be less significant, as the overhead of accessing object properties can outweigh the function call overhead.
- Testing: It’s recommended to benchmark the performance of your comparator function with different array sizes to understand its impact on your specific use case.
8.3 Optimizing Comparator Performance
- Simple Logic: Keep the comparator function as simple and efficient as possible.
- Caching: If the comparator function involves expensive calculations, consider caching the results to avoid redundant computations.
- Pre-processing: If possible, pre-process the data into a more efficient format for comparison.
function compareObjectsByPropertyOptimized(propertyName) { return function(a, b) { let aValue = a[propertyName]; let bValue = b[propertyName]; if (aValue < bValue) return -1; if (aValue > bValue) return 1; return 0; }; }
- Explanation:
- The
compareObjectsByPropertyOptimized
function stores the property values in local variables before comparing them. - This avoids accessing the object properties multiple times, which can improve performance.
- The
8.4 Alternative Sorting Algorithms
- When to Consider Alternatives: If the performance of the
sort
method with a comparator is not satisfactory, consider using alternative sorting algorithms like merge sort or quicksort, which may offer better performance for certain types of data. - Libraries: Libraries like Lodash and Underscore.js provide optimized sorting functions that can be used as alternatives to the native
sort
method.
9. How Does the Sort Method Handle Undefined Elements?
The sort
method in JavaScript handles undefined elements in a specific way: it sorts them to the end of the array, regardless of the comparator function.
9.1 Default Behavior
- Undefined Elements at the End: When the
sort
method encounters undefined elements, it moves them to the end of the array. - No Comparator Call: The comparator function is not called for undefined elements.
- Order Preservation: The relative order of undefined elements is preserved.
9.2 Example
let array = [5, undefined, 2, undefined, 8];
array.sort(); // [2, 5, 8, undefined, undefined]
- Explanation:
- The
sort
method moves the undefined elements to the end of the array. - The numbers are sorted in ascending order.
- The
9.3 Handling Undefined Elements in a Comparator
- Explicitly Check for Undefined: If you want to handle undefined elements differently, you need to explicitly check for them in your comparator function.
- Custom Sorting Logic: You can define custom sorting logic for undefined elements, such as sorting them to the beginning of the array or treating them as a specific value.
- Implementation:
function compareWithUndefined(a, b) { if (a === undefined && b === undefined) return 0; if (a === undefined) return -1; // Undefined elements go to the beginning if (b === undefined) return 1; return a - b; }
let array = [5, undefined, 2, undefined, 8];
array.sort(compareWithUndefined); // [undefined, undefined, 2, 5, 8]
- **Explanation**:
- The `compareWithUndefined` function checks if either element is undefined.
- If both are undefined, it returns 0.
- If only one is undefined, it sorts the undefined element to the beginning of the array.
**9.4 Considerations**
- **Consistency**: Ensure that your handling of undefined elements is consistent throughout your application.
- **Data Integrity**: Be aware of the potential impact of undefined elements on your sorting results.
**10. What Are Common Mistakes to Avoid When Using Comparators?**
Using comparators effectively requires avoiding common mistakes that can lead to unexpected behavior, performance issues, or incorrect sorting results.
**10.1 Not Returning Consistent Values**
- **Inconsistency**: Ensure that your comparator function always returns the same result for the same pair of inputs.
- **Side Effects**: Avoid side effects, such as modifying the input elements or external state, as this can lead to unpredictable behavior.
- **Example**:
```javascript
function compareInconsistent(a, b) {
if (Math.random() > 0.5) {
return a - b;
} else {
return b - a;
}
}
- Explanation:
- The
compareInconsistent
function returns different results for the same pair of inputs due to the use ofMath.random()
. - This can lead to incorrect sorting results.
- The
10.2 Incorrectly Handling Null or Undefined Values
- Null/Undefined Checks: Always check for null or undefined values and handle them appropriately.
- Sorting Order: Define a clear sorting order for null or undefined values, such as sorting them to the beginning or end of the array.
- Example:
function compareWithoutNullCheck(a, b) { return a.property - b.property; // Error if a.property or b.property is null or undefined }
- Explanation:
- The
compareWithoutNullCheck
function does not check for null or undefined values. - This can lead to errors if
a.property
orb.property
is null or undefined.
- The
10.3 Using Complex Logic Unnecessarily
- Simplicity: Keep your comparator function as simple and efficient as possible.
- Performance: Avoid complex or time-consuming calculations within the comparator, as this can impact performance.
- Example:
function compareComplex(a, b) { // Avoid complex calculations here let expensiveCalculationA = performExpensiveCalculation(a); let expensiveCalculationB = performExpensiveCalculation(b); return expensiveCalculationA - expensiveCalculationB; }
- Explanation:
- The
compareComplex
function performs expensive calculations within the comparator. - This can slow down the sorting process.
- The
10.4 Not Ensuring Comparator Stability
- Stability: Ensure that your comparator is stable, meaning it maintains the relative order of elements that are considered equal by the comparison logic.
- Preservation: Preserve the original order of equal elements to avoid unexpected behavior.
- Example:
function compareUnstable(a, b) { return 1; // Always returns 1, not a stable comparator }
- Explanation:
- The
compareUnstable
function always returns 1, which means it does not maintain the relative order of equal elements. - This can lead to unexpected behavior.
- The
FAQ Section
Q1: Can I use a comparator for arrays.sort to sort numbers in descending order?
A1: Yes, you can use a comparator to sort numbers in descending order by reversing the subtraction in the comparator function. For example: function compareNumbersDescending(a, b) { return b - a; }
Q2: How do I sort an array of objects by multiple properties using a comparator?
A2: You can sort an array of objects by multiple properties by creating a comparator that checks multiple properties in order. If the first property is equal, it checks the second property, and so on.
Q3: What is the difference between localeCompare and a custom comparator for strings?
A3: localeCompare
supports language-specific sorting rules, while a custom comparator provides full control over the comparison logic. localeCompare
is generally optimized for performance, while the performance of a custom comparator depends on the implementation.
Q4: How does the sort method handle undefined elements in an array?
A4: The sort
method moves undefined elements to the end of the array, regardless of the comparator function. The comparator function is not called for undefined elements.
Q5: What are some common mistakes to avoid when using comparators?
A5: Common mistakes include not returning consistent values, incorrectly handling null or undefined values, using complex logic unnecessarily, and not ensuring comparator stability.
Q6: Can I use a comparator to sort an array of mixed data types?
A6: Yes, you can use a comparator to sort an array of mixed data types by checking the data types of the elements being compared and using different comparison logic based on the data types.
Q7: How do I handle case-insensitive sorting with a comparator?
A7: You can handle case-insensitive sorting by converting the strings to lowercase or uppercase before comparing them, or by using localeCompare
with sensitivity options.
Q8: What are the performance implications of using a comparator for arrays.sort?
A8: Using a comparator introduces overhead due to function invocation. The complexity of the comparator function can significantly impact performance. It’s recommended to benchmark the performance of your comparator function with different array sizes.
Q9: How do I sort an array of objects with nested properties using a comparator?
A9: You can sort an array of objects with nested properties by creating a comparator that takes an array of property names as input, representing the path to the nested property. It iterates through the property names to access the nested property values.
Q10: Is it possible to reverse the order of an already sorted array using a comparator?
A10: While you can use a comparator, it’s generally more efficient to use the reverse()
method to reverse the order of an already sorted array.
Ready to make smarter choices? Visit COMPARE.EDU.VN now to explore comprehensive comparisons and make informed decisions. Our expert analysis and user reviews will help you find the perfect fit for your needs. Don’t just compare, decide with confidence.
Contact us:
Address: 333 Comparison Plaza, Choice City, CA 90210, United States
Whatsapp: +1 (626) 555-9090
Website: compare.edu.vn