Diskuze: Porovnání řetězců s diakritikou _wcsicmp_l
V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.


Michal Hadraba:31.10.2023 17:42
No tak jsem tu našel starší post, dokonce můj - kde jsme řešil malinko něco jiného. Nicméně takto to funguje. Ale proč nefunguje to předchozí, stále nevím.
struct LessStruct
{
bool operator()(const CString& str1, const CString &str2) const {
setlocale(LC_COLLATE, "cs_CZ.utf8");
return wcscoll(str1.GetString(), str2.GetString())<0;
}
};
DarkCoder:31.10.2023 18:04
Zkus popsat slovně to čeho chceš dosáhnout, nikoli imolementačně. Ať se neomezuje na jeden způsob řešení.
Co se týká řazení stránkování, má tedy posloupnost od menšího k většímu vypadat takto?
str 1, str 2, str 100, ...
Tedy řazeno alfanumericky
Nebo
str 1, str 100, str 2, ...
Tedy řazeno kombinované str a pak podle číselné hodnoty?
Bude se řetězec skládat jen z kombinace str a čísla nebo jiných subřetězců?
PS: před pár měsíci jsme tu řešili společně řazení diakritickych
řetězců. Tuším že tam byla "žirafa"
Michal Hadraba:31.10.2023 18:18
Jasně, já jsem chtěl původně řadit jenom písmena, ale postupem vývoje
jsem pojal potřebu řešit také řazení čísel numericky. Pro numerické
řazení čísel jsme našel nějaký vzorový kód, ten jsme použil - šoupnu
ho sem zítra - ale ten zas neřešil diakritiku.
Takže problém je - seřadit, přesně jak píšeš:
stoka 1 - levá
stoka 15 - něco
stoka 2 - pravá
stoka 25 .....
řad
větev
V zásadě jsme to už vyřešil, když jsme si vzpomněl a našel
předchozí post.
Nicméně stále nechápu, kdy je třeba použít jaké locale - jedno funguje
(nebo nefunguje) pro funkce wstring, string, druhé pro obecné c funkce, jednou
se tam přidává "_locale_t loc", podruhé ne....
DarkCoder:31.10.2023 18:46
S locale jsou občas problémy, Také jsem se setkal s tím že jeho použití nevykazovalo to co jsme chtěli.
Pokud chceme variantu řazení:
stoka 1 - levá
stoka 15 - něco
stoka 2 - pravá
stoka 25 .....
Pak je to jednoduché. Řetězce jsou řazeny alfanumericky, tedy že 15 bude v abecedě dříve než 2. Pak můžeš použít funkci wcscmp() pro case sensitive řazení nebo _wscicmp() pro non case sensitive.
Pokud chces řadit hodnotově, tedy že 15 bude více než 2, pak nastavis ukazatel na počátek číselné hodnoty a aplikuješ funkci atoi() pro převod na číselnou hodnotu.
Abys pro řazení neprováděl opakovaně tyto konverze, vytvoříš pole celých čísel o délce počtu záznamu, konverzi zápisem do tohoto pole a řazení budeš provádět na tomto temp poli.
Michal Hadraba:31.10.2023 18:53
Já chtěl řadit numerických, napsal jsem to blbě, už nevím co jsem.
Mělo být:
Stoka 1
Stoka 2
Stoka 15
Logicky. No, tak jak píšeš na konci.
Děkuji.
Ještě zkusím to wscipm.
DarkCoder:31.10.2023 21:03
A nebo úplně jednoduše. Zjisti počet záznamů a vytvoř 2 pole o počtu záznamů. Jedno pro hodnoty na dane stoce, druhé pro serazene indexy stok. Načti záznam ze souboru, vyextrahuj z něho ID stoky, na index ID stoky ulož hodnotu pro danou stoku. To dělej pro všechny záznamy. Nyní hledej minima z hodnot v prvnim poli a zapisuj index nejnizsihomprvku postupně do indexu druhého pole. Tím získáš ID stok od nejnižší hodnoty. Nakonec projdeš celé druhé pole a vygeneruješ seřazené stoky podle hodnot na indexech. Budeš-li potřebovat hodnotu pro dáno stoky, jednoduše si sáhneš do prvního pole na index odpovídající hodnotě na indexundruheho pole.
Michal Hadraba:31.10.2023 21:10
Sorry. Příklad byl pouze ilustrativní a příliš zjednodušený. Ty názvy si volí uživatel. Může tam mít 11 Stok, 22 přípojek, 15 větví... a bůhví co ještě lze vymyslet..... Už se mi ten algoritmus začíná rýsovat. Zítra nebo pozítří sem kdyztak picnu nějaké nápady, ať ten problém uzavřeme.
Přesné zadání je "natural sorting s respektem k diakritice, no case
sensitive".
Ale hlavně mě zajímá, jak funguje "locale".
Zjistil jsem, že funguje _wscicmp_l()
, ale do locale se musí
zadat czech_cz.utf8
(to utf8 je tam nutný - to se člověk nikde
nedoví)
_locale_t locale;
locale = _create_locale(LC_ALL, "cs-CZ.utf8");
return _wcsicoll_l(&lhs, &rhs, locale) < 0;
_free_locale(locale);
to mi přijde o hodně jednodušší (a také asi méně náročné na prostředky), než:
char* old_locale = setlocale(typ, NULL);
lcSavedLocale = strdup(old_locale);
if (!lcSavedLocale) throw CMAJEX("Out of memory");
setlocale(LS_COLLATE, "Czech_cz.utf8");
bool res = _wcsicoll_l(&lhs, &rhs, locale) < 0;
setlocale(LS_COLLATE, lcSavedLocale);
free(lcSavedLocale);
Razeni cisel se da resit podle delky a pak podle cislic
115 = delka 3 znaky
10 = delka 2 znaky
Problem by nastal s desetinnym cislem nebo cisly s jinym delenim, treba
cislovane odrazky, tam bys to musel udelat asi spesl pro danou situaci
1.2.3
1.1.15
Pokud nezalezi na rychlosti, pouzival jsem pro takove spesl situace regularni
vyrazy.
DarkCoder:1.11.2023 12:07
To je dosti neefektivní. Důvodem je to, že řadící rozsah je extrémně malý. Např. pro čísla v rozsahu 1-99 je 9 délek jeden znak a 90 délek dva znaky.
Michal Hadraba:2.11.2023 8:09
Takhle to funguje myslím, že efektivně. Jen ale pro celá čísla, žádné desetinné čárky atd. Nebyl by asi problém to tam doplnit.
#include <locale>
#include <string>
using namespace std;
namespace MajCompare {
struct MajLoc
{
private:
_locale_t m_loc;
public:
MajLoc(){ m_loc = _create_locale(LC_COLLATE, "cs-CZ.utf8"); }
~MajLoc() { _free_locale(m_loc);}
_locale_t& GetLocale() { return m_loc; }
};
static MajLoc s_cLoc;
//Vrací -1, 0 1
int CompareNatural(const wchar_t* lhsBegin, const wchar_t* lhsEnd
, const wchar_t* rhsBegin, const wchar_t* rhsEnd)
{
wstring wsPom1, wsPom2;
const wchar_t* itCurLhs = lhsBegin, *itCurRhs = rhsBegin;
while (!iswdigit(*itCurLhs) && !iswdigit(*itCurRhs) && itCurLhs != lhsEnd && itCurRhs != rhsEnd)
{
wsPom1.push_back(*itCurLhs);
wsPom2.push_back(*itCurRhs);
if (*itCurLhs != *itCurRhs)
return _wcsicoll_l(lhsBegin, rhsBegin, s_cLoc.GetLocale());
itCurLhs++, itCurRhs++;
}
if (itCurLhs == lhsEnd || itCurRhs == rhsEnd || iswdigit(*itCurLhs) != iswdigit(*itCurRhs)) //jeden je delší nebo kombinace čísla a písmena, porovnáme původní řetězce bez ohledu na čísla porovnáme standardně
return _wcsicoll_l(lhsBegin, rhsBegin, s_cLoc.GetLocale());
else //oba řetězce mají čísla a nejsou na konci
{
wstring wsNum1, wsNum2;
while (iswdigit(*itCurLhs))
wsNum1.push_back(*itCurLhs++);
while (iswdigit(*itCurRhs))
wsNum2.push_back(*itCurRhs++);
int iNum1 = _wtoi(wsNum1.data());
int iNum2 = _wtoi(wsNum2.data());
if (iNum1 != iNum2)
return iNum1 > iNum2 ? 1 : -1;
return CompareNatural(itCurLhs, lhsEnd, itCurRhs, rhsEnd);
}
}
int CompareNatural(const wchar_t* lsFirst, const wchar_t* lsSecond)
{
const wchar_t* it1 = lsFirst;
while (*it1 != '\0')it1++;
const wchar_t* it2 = lsSecond;
while (*it2 != '\0')it2++;
return CompareNatural(lsFirst, it1, lsSecond, it2);
}
};
DarkCoder:3.11.2023 21:32
Michale, kód, který si uvedl, není efektivní.
Tři body:
1.
Zbytečně generuješ nový řetezec pro textovou i numerickou část. Proč?
Ten subřetězec už tam máš. Zbytečně to zpomaluje aplikaci.
2. Způsob, jakým generuješ subřetězce je neefektivní. Pokud v cyklu provádíš pus.back na prázdném dynamickém poli, s každým vkládaným znakem provádíš realokaci řetězce, což velmi spomaluje aplikaci. Zejména jsou-li řetězce dlouhé.
3. S každým voláním funkce provádíš činnost kterou už si dříve dělal. Zejména u velkého množství záznamů se toto negativně projeví na zpomalení aplikace.
+20 Zkušeností
+2,50 Kč

Michal Hadraba:4.11.2023 7:06
Ahoj díky. První dva body chápu. Ten první řetězec wsPom nakonec ani nepoužívám. Ovšem wsNum je nutné, jak to generovat efektivněji? Použít wstring::resize nebo wstring::reserve?
A ten třetí bod úplně nechápu, co tím myslíš. Myslíš volání _wscicoll znova pro celý řetězec? Šlo by to od aktuálního znaku, to jo.
DarkCoder:4.11.2023 12:52
Když už máš nějaký řetězec o kterém víš že jej nebudeš měnit, můžeš jej používat na více místech. To první místo je celý samotný řetězec a to druhé je jeho podřetězec, ať už jeho alfa část nebo druhá část numerická. Není třeba vytvářet nějaký další prostor pro kopírování subřetězce z řetězce někam jinam. To platí pochopitelně za přepokladu, že subřetězec nebudeš měnit. A to je tento případ.
Funkce dodané s překladačem pracují pro řetězce, tedy data ukončená nulovým znakem. Ty musíš pro alfa část vytvořit vlastní funkci, protože řetězec měnit nechceš a subřetězce nekončí nulovým znakem. Stačí Ti ukazatel a délka alfa subřetězce. Pro numerickou část subřetězce je situace maličko jiná. Pro ten musíš najít adresu kde číselná část začíná, přečíst jej funkcí pro konverzi ze znakové podoby do číselné a tuto hodnotu někam uložit.
Ještě jednou, pro textový subřetězec v řetězci potřebuješ adresu počátku a jeho délku. Pro číselný subřetězec proměnnou pro uložení čísla a adresu na které se první číselný znak nachází a provést konverzi.
Krátká ukázka:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(void) {
const char* input = "zak100 180cm";
int number = -1;
unsigned int nonDigitCount = 0;
const char* ptr = input;
while (*ptr) {
if (isdigit(*ptr)) {
number = atoi(ptr);
break;
}
else {
nonDigitCount++;
}
ptr++;
}
printf("Delka textove casti: %u\n", nonDigitCount);
printf("Extrahovane cislo: %d\n", number);
return 0;
}
V cyklu while provádím vše co potřebuji. Posouvám ukazatel postupně celým řetězcem dokud nenarazím na číslici, od této číslice zavolám konverzi a ukončím cyklus. Zároveň počítám počet znaků textové části. Kdykoli se mohu dostat k textové části, neboť znám ukazatel a počet znaků subřetězce. Číselnou část mám uloženou v paměti a s tou poté pracuji. Již nemusím v každém volání provádět konverzi.
K třetímu bodu.
Když se třídí nějaká data pole či jiné kolekce, dochází k opakovanému braní prvku. S tím co bys měl bys opakovaně prováděl úkony které si už jednou nad daným prvkem dělal. Aplikaci je třeba zefektivnit tak že ten krok provedeš pouze jednou. To docílíš tak, že před samotným porovnáváním a tříděním provedeš analýzu všech řetězců, zjistíš délku textové části a získáš číselnou hodnotu z řetězce. Tak jak jsme si ukázali výše. Když pak budeš mít řetězce, provedeš na nich jen porovnání. a na základě výsledku prohodíš indexy.
Ještě jednou, když porovnáš dva řetězce, používáš pro ně to co jsi předpočítal, tedy číselnou hodnotu a délku textové části. A na základě porovnání se prohazují indexy nikoli data! Řetězcová data nikdy nebudeš měnit. Na to budeš mít pole ID řetězců, která budeš měnit podle výsledku porovnání.
Třetí bod ve stručnosti znamená to, že provedeš předzpracování dat, které si někam uložíš a ta pak využíváš pro porovnávání a třídění, aniž bys musel opětovně provádět rozsekání řetězce a vytahování dat, protože už si to dříve dělal. Tím celá aplikace poběží mnohem rychleji, než kdybys tomu tak neudělal.
Darku díky.
S tím cashováním to nedám, neb používám std::map a ten si třídí sám.
Nicméně, zas takových položek nemám.
Třídící algoritmus jsem upravil:
inline int CompareNatural(const wchar_t* lhsBegin, const wchar_t* lhsEnd
, const wchar_t* rhsBegin, const wchar_t* rhsEnd)
{
wstring wsPom1, wsPom2;
const wchar_t* itCurLhs = lhsBegin, *itCurRhs = rhsBegin;
while (!iswdigit(*itCurLhs) && !iswdigit(*itCurRhs) && itCurLhs != lhsEnd && itCurRhs != rhsEnd)
{
if (*itCurLhs != *itCurRhs)
return _wcsicoll_l(itCurLhs, itCurRhs, s_cLoc.GetLocale());
itCurLhs++, itCurRhs++;
}
if (itCurLhs == lhsEnd || itCurRhs == rhsEnd || iswdigit(*itCurLhs) != iswdigit(*itCurRhs)) //jeden je delší nebo kombinace čísla a písmena, porovnáme původní řetězce bez ohledu na čísla porovnáme standardně
return _wcsicoll_l(lhsBegin, rhsBegin, s_cLoc.GetLocale());
else //oba řetězce mají čísla a nejsou na konci
{
int iNum1 = _wtoi(itCurLhs);
int iNum2 = _wtoi(itCurRhs);
if (iNum1 != iNum2)
return iNum1 > iNum2 ? 1 : -1;
else
{
while (iswdigit(*itCurLhs)) ++itCurLhs; //přeskočíme čísla na další písmeno
while (iswdigit(*itCurRhs)) ++itCurRhs;
}
return CompareNatural(itCurLhs, lhsEnd, itCurRhs, rhsEnd);
}
}
DarkCoder:4.11.2023 23:05
Nemáš zač Michale. Abys viděl blíže, co jsem Ti popisoval v
předchozích příspěvcích, zpracoval jsem pro tebe jeden krátký ukázkový
příklad. Smyslem příkladu je poukázat na způsob efektivního
porovnávání řetězců jak abecedně tak číselně. Podívej se zejména, co
předchází samotnému porovnání. V programu není zahrnuta diakritika a
třídění, pouze porovnání a analýza vstupu. Pokud bude cokoli nejasného,
ptej se..
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define DEBUG 0
void analyze_strings(const char* strings[], unsigned lengths[], unsigned numbers[], size_t count);
int compareText(const char* strings[], unsigned lengths[], unsigned numbers[], size_t index1, size_t index2);
int main(void) {
const char* strings[] = {
"kun3",
"pesec12",
"strelec4",
"pesec2",
"kun10",
"dama1"
};
size_t count = sizeof(strings) / sizeof(strings[0]);
#if DEBUG==1
printf("%zu\n", count);
#endif
unsigned* lengths = (unsigned*)malloc(count * sizeof(unsigned));
if (lengths == NULL) exit(1);
unsigned* numbers = (unsigned*)malloc(count * sizeof(unsigned));
if (numbers == NULL) {
free(lengths);
lengths = NULL;
exit(2);
}
analyze_strings(strings, lengths, numbers, count);
#if DEBUG==1
for (size_t i = 0; i < count; i++) {
printf("%u ", lengths[i]);
}
putchar('\n');
for (size_t i = 0; i < count; i++) {
printf("%u ", numbers[i]);
}
putchar('\n');
#endif
for (size_t i = 0; i < count; i++) {
for (size_t j = 0; j < count; j++) {
printf("%s vs %s %d\n", strings[i], strings[j], compareText(strings, lengths, numbers, i, j));
}
}
free(lengths);
lengths = NULL;
free(numbers);
numbers = NULL;
return 0;
}
void analyze_strings(const char* strings[], unsigned lengths[], unsigned numbers[], size_t count) {
unsigned number;
unsigned nonDigitCount;
const char* ptr = NULL;
for (size_t i = 0; i < count; i++) {
number = 0;
nonDigitCount = 0;
ptr = strings[i];
while (*ptr) {
if (isdigit(*ptr)) {
number = atoi(ptr);
break;
}
else {
nonDigitCount++;
}
ptr++;
}
numbers[i] = number;
lengths[i] = nonDigitCount;
}
}
int compareText(const char* strings[], unsigned lengths[], unsigned numbers[], size_t index1, size_t index2) {
unsigned i = 0;
while (i < lengths[index1] && i < lengths[index2]) {
if (strings[index1][i] < strings[index2][i]) return -1;
else if (strings[index1][i] > strings[index2][i]) return 1;
i++;
}
if (i < lengths[index1]) return 1;
else if (i < lengths[index2]) return -1;
if (numbers[index1] < numbers[index2]) return -1;
else if (numbers[index1] > numbers[index2]) return 1;
return 0;
}
Zobrazeno 18 zpráv z 18.