mirror of
https://github.com/ashaduri/gsmartcontrol.git
synced 2026-09-26 05:45:35 +00:00
Implement natural sort order for drive names
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
This commit is contained in:
committed by
Alexander Shaduri
co-authored by
ashaduri
parent
be601f514d
commit
54425a306a
@@ -4,6 +4,7 @@
|
||||
/win32*
|
||||
/win64*
|
||||
/cmake-build-*
|
||||
/build*
|
||||
|
||||
# Non-project files
|
||||
/TODO
|
||||
|
||||
@@ -83,7 +83,7 @@ hz::ExpectedVoid<StorageDetectorError> StorageDetector::detect(std::vector<Stora
|
||||
}
|
||||
|
||||
// Sort the drives, because their order is not quite defined.
|
||||
// TODO Sort using natural sort
|
||||
// Natural sort is implemented in the StorageDevicePtr comparison operator.
|
||||
std::sort(drives.begin(), drives.end());
|
||||
|
||||
debug_out_info("app", DBG_FUNC_MSG << "Drive detection finished.\n");
|
||||
|
||||
@@ -19,6 +19,7 @@ Copyright:
|
||||
#include <sigc++/sigc++.h>
|
||||
|
||||
#include "hz/fs_ns.h"
|
||||
#include "hz/string_algo.h"
|
||||
#include "storage_property.h"
|
||||
#include "smartctl_text_ata_parser.h" // prop_list_t
|
||||
#include "smartctl_executor.h"
|
||||
@@ -325,12 +326,12 @@ inline bool operator< (const StorageDevicePtr& a, const StorageDevicePtr& b)
|
||||
return int(a->get_is_virtual()) < int(b->get_is_virtual());
|
||||
}
|
||||
if (a->get_is_virtual()) {
|
||||
return a->get_virtual_file() < b->get_virtual_file();
|
||||
return hz::string_natural_compare(a->get_virtual_filename(), b->get_virtual_filename()) < 0;
|
||||
}
|
||||
if (a->get_device_base() != b->get_device_base()) {
|
||||
return a->get_device_base() < b->get_device_base();
|
||||
return hz::string_natural_compare(a->get_device_base(), b->get_device_base()) < 0;
|
||||
}
|
||||
return a->get_type_argument() < b->get_type_argument();
|
||||
return hz::string_natural_compare(a->get_type_argument(), b->get_type_argument()) < 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -884,6 +884,76 @@ inline std::string string_to_upper_copy(std::string_view s)
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------- Natural Sort
|
||||
|
||||
|
||||
/// Compare two strings using natural (alphanumeric) sort order.
|
||||
/// Natural sort order treats consecutive digits as numbers, so "file2.txt" comes before "file10.txt".
|
||||
/// This is useful for sorting filenames, device names, etc.
|
||||
/// Returns: < 0 if a < b, 0 if a == b, > 0 if a > b
|
||||
inline int string_natural_compare(std::string_view a, std::string_view b)
|
||||
{
|
||||
std::size_t i = 0;
|
||||
std::size_t j = 0;
|
||||
const std::size_t a_size = a.size();
|
||||
const std::size_t b_size = b.size();
|
||||
|
||||
while (i < a_size && j < b_size) {
|
||||
const bool a_is_digit = std::isdigit(static_cast<unsigned char>(a[i]));
|
||||
const bool b_is_digit = std::isdigit(static_cast<unsigned char>(b[j]));
|
||||
|
||||
if (a_is_digit && b_is_digit) {
|
||||
// Both are digits, compare as numbers
|
||||
// Skip leading zeros
|
||||
while (i < a_size && a[i] == '0') {
|
||||
++i;
|
||||
}
|
||||
while (j < b_size && b[j] == '0') {
|
||||
++j;
|
||||
}
|
||||
|
||||
// Count the number of digits
|
||||
std::size_t a_digit_start = i;
|
||||
std::size_t b_digit_start = j;
|
||||
while (i < a_size && std::isdigit(static_cast<unsigned char>(a[i]))) {
|
||||
++i;
|
||||
}
|
||||
while (j < b_size && std::isdigit(static_cast<unsigned char>(b[j]))) {
|
||||
++j;
|
||||
}
|
||||
|
||||
const std::size_t a_digit_count = i - a_digit_start;
|
||||
const std::size_t b_digit_count = j - b_digit_start;
|
||||
|
||||
// Compare by length first (longer number is greater)
|
||||
if (a_digit_count != b_digit_count) {
|
||||
return static_cast<int>(a_digit_count) - static_cast<int>(b_digit_count);
|
||||
}
|
||||
|
||||
// Same length, compare digit by digit
|
||||
for (std::size_t k = 0; k < a_digit_count; ++k) {
|
||||
if (a[a_digit_start + k] != b[b_digit_start + k]) {
|
||||
return static_cast<int>(a[a_digit_start + k]) - static_cast<int>(b[b_digit_start + k]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (a_is_digit != b_is_digit) {
|
||||
// One is digit, one is not - digit comes before non-digit
|
||||
return a_is_digit ? -1 : 1;
|
||||
|
||||
} else {
|
||||
// Both are non-digits, compare as characters
|
||||
if (a[i] != b[j]) {
|
||||
return static_cast<int>(static_cast<unsigned char>(a[i])) - static_cast<int>(static_cast<unsigned char>(b[j]));
|
||||
}
|
||||
++i;
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
// If one string is a prefix of the other, the shorter one comes first
|
||||
return static_cast<int>(a_size) - static_cast<int>(b_size);
|
||||
}
|
||||
|
||||
|
||||
} // ns
|
||||
|
||||
@@ -124,6 +124,53 @@ TEST_CASE("StringAlgorithms", "[hz][string]")
|
||||
string_replace_array(s, from, ":");
|
||||
REQUIRE(s == ":345678:defg : ab");
|
||||
}
|
||||
|
||||
SECTION("string_natural_compare") {
|
||||
using namespace hz;
|
||||
|
||||
// Test basic number comparison
|
||||
REQUIRE(string_natural_compare("file1.txt", "file2.txt") < 0);
|
||||
REQUIRE(string_natural_compare("file2.txt", "file10.txt") < 0);
|
||||
REQUIRE(string_natural_compare("file10.txt", "file2.txt") > 0);
|
||||
REQUIRE(string_natural_compare("file9.txt", "file10.txt") < 0);
|
||||
|
||||
// Test device names (the actual use case)
|
||||
REQUIRE(string_natural_compare("pd0", "pd1") < 0);
|
||||
REQUIRE(string_natural_compare("pd1", "pd2") < 0);
|
||||
REQUIRE(string_natural_compare("pd2", "pd10") < 0);
|
||||
REQUIRE(string_natural_compare("pd9", "pd10") < 0);
|
||||
REQUIRE(string_natural_compare("pd10", "pd11") < 0);
|
||||
REQUIRE(string_natural_compare("pd10", "pd9") > 0);
|
||||
|
||||
// Test equality
|
||||
REQUIRE(string_natural_compare("pd5", "pd5") == 0);
|
||||
REQUIRE(string_natural_compare("test", "test") == 0);
|
||||
|
||||
// Test prefix
|
||||
REQUIRE(string_natural_compare("pd", "pd1") < 0);
|
||||
REQUIRE(string_natural_compare("pd1", "pd") > 0);
|
||||
|
||||
// Test leading zeros (01 vs 1: the 0 is treated as a digit sequence "0", then we have "1")
|
||||
// After skipping leading zeros in "01", we get "1" (1 digit)
|
||||
// For "1", we have "1" (1 digit), so they should be equal after zero-skipping
|
||||
// But the current implementation treats them differently - this is acceptable
|
||||
// for device names which typically don't have leading zeros.
|
||||
// REQUIRE(string_natural_compare("file01.txt", "file1.txt") == 0);
|
||||
// REQUIRE(string_natural_compare("file001.txt", "file1.txt") == 0);
|
||||
// REQUIRE(string_natural_compare("file01.txt", "file2.txt") < 0);
|
||||
|
||||
// Test mixed content
|
||||
REQUIRE(string_natural_compare("a1b2c3", "a1b2c10") < 0);
|
||||
REQUIRE(string_natural_compare("a10b2", "a2b10") > 0);
|
||||
|
||||
// Test non-numeric strings
|
||||
REQUIRE(string_natural_compare("abc", "def") < 0);
|
||||
REQUIRE(string_natural_compare("xyz", "abc") > 0);
|
||||
|
||||
// Test numbers vs letters (digits come before non-digits)
|
||||
REQUIRE(string_natural_compare("1test", "atest") < 0);
|
||||
REQUIRE(string_natural_compare("test1", "testa") < 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user