C++ Builder
| Главная | Уроки | Статьи | FAQ | Форум | Downloads | Литература | Ссылки | RXLib | Диски |

 
Разбиение строки, надо разбить AnsiString по запятым
Tempus
Отправлено: 18.05.2005, 11:25


Не зарегистрирован







Господа, не подскажите как сделать мксимально быстро по скорости следующую весчь:

Есть строка AnsiString, разделенная 8 запятыми, нужно получить 9 строк, на которые она разделена.

Сразу оговорюсь, что создать TStrings и задать в нем CommaText не работает, т.к. внутри строк могут быть пробелы. Есть ли способ более быстрый, чем проход по всей базовой строке?
VovaN
Отправлено: 18.05.2005, 11:37


Дежурный стрелочник

Группа: Участник
Сообщений: 72



По скорости не знаю, но сделать можно что-то типа такого:
while(i=str.Pos(","))
{
скопировать в строку str.SubString(0,i);
обрезать строку по i (от начала);
}
Tempus
Отправлено: 18.05.2005, 11:46


Не зарегистрирован







За совет спасибо. Можно и не обрезать, а сохранять позицию последней запятой. Правда насчет скорости не уверен — и Pos и Substring очень медленные.
** Rius
Отправлено: 18.05.2005, 12:37


Не зарегистрирован







Можно обратиться напрямую к буферу данных AnsiString'а (AnsiString::c_str()), да еще и на ассмеблере эту часть написать. Будет быстрее некуда.
Tempus
Отправлено: 18.05.2005, 12:40


Ученик-кочегар

Группа: Участник
Сообщений: 14



Ну к буферу данных AnsiString понятно как обратиться, а вот с ассмом не знаком sad.gif
Gedeon
Отправлено: 18.05.2005, 12:52


Ветеран

Группа: Модератор
Сообщений: 1742



Можно так
CODE

   Memo1->Lines->Clear();
   int len = Edit1->Text.Length();
   char *Temp = new char(len);
   strcpy(Temp,Edit1->Text.c_str());
   AnsiString T;
   for(int counter=0;counter<len;counter++)
   {
    if(Temp[counter]==',')
       {
           Memo1->Lines->Add(T);
           T = "";
       }
       else
       {
           T += AnsiString(Temp[counter]);
       }
   }
   delete Temp;
Tempus
Отправлено: 18.05.2005, 12:57


Ученик-кочегар

Группа: Участник
Сообщений: 14



Тогда такой вопрос — при += для AnsiString будет по новой выделяться память поб ее буфер? К чему спрашиваю — возможно SubString быстрее.

(Я сейчас в офисе и не могу проверить разные варианты на то какой быстрее)
avc*
Отправлено: 18.05.2005, 13:48


Не зарегистрирован







А еще можно так
CODE

AnsiString str("a1,a2,a3,a4");
for (char *cp=str.c_str(); *cp; cp++ )
   if(*cp == ',') *cp = '\n';

TStringList *lst = new TStringList;
lst->Text = str;

// здесь работаем с отдельными строками
// проверка ShowMessage(lst->Text);
delete lst;

dvv
Отправлено: 18.05.2005, 14:45


Дежурный стрелочник

Группа: Участник
Сообщений: 43



Идея хорошая, сам таким пользуюсь, но так делать нельзя!
Дело в том, что .c_str() "возвращает указатель на строку, содержащюю те же символы, что и в AnsiString". Замена символа в c_str() не означает замену в AnsiString.
Предворительно нужно создать в памяти объект так, как говорил Гедеон. Только длину создаваемой строки нужно увеличить на символ:
CODE

AnsiString str("a1,a2,a3,a4");
int len = str.Length();
char *Temp = new char(len+1);
strcpy(Temp,str.c_str());

for (int i=0;i<len;i++ )
if(Temp[i] == ',') Temp[i] = '\n';

TStringList *lst = new TStringList;
lst->Text = AnsiString(Temp);
. . .

lst->String[2] будет содержать "a3".
Что касается скорости , то вышеописанный способ работает намного быстрее чем методы AnsiString-a. Но если нужно увеличить скорость еще выше, то пользуйтесь функциями работы с памятью. Например вместо strcpy используйте memcpy.
Если строка длиной 1-2 кБайта, можно пользоватся методами AnsiString-a и не возиться с char*.

Отредактировано dvv — 18/05/2005, 14:59
avc*
Отправлено: 18.05.2005, 15:01


Не зарегистрирован







QUOTE

Идея хорошая, сам таким пользуюсь, но так делать нельзя!
Дело в том, что .c_str() "возвращает указатель на строку, содержащюю те же символы, что и в AnsiString".
Предворительно нужно создать в памяти объект так, как говорил Гедеон. Только длину создаваемой строки нужно увеличить на символ:

Можно. Незачем создавать новую строку. Можно работать в старой так как размер строки не изменяется.
Это не идея, а вполне рабочий код.
Tempus
Отправлено: 19.05.2005, 10:41


Ученик-кочегар

Группа: Участник
Сообщений: 14



И так, господа, я посмотел некоторые варианты алгоритма, и вот к чему пришел:

1. c_str() действительно возвращает указатель на буфер строки, но корректно работать с ним можно только, если не меняется длина строки — судя по всему длина AnsiString вычисляется не по буферу, а при его заполнении стандартными методами.

2. Наиболее быстрым способом оказался (что меня несколько удивило) следующий:
CODE


int len=str.Length(),done=0;
for (int i=1; i<len+1; i++)
{
  if ((str[i]==',')&&(done<8))
   {
     str[i]='\n';
     done++;
   }
}

StrList->Text=str;

StrList это указатель на TStringsList

Отредактировано Gedeon — 19/05/2005, 11:39
Gedeon
Отправлено: 19.05.2005, 11:45


Ветеран

Группа: Модератор
Сообщений: 1742



2 Tempus

Пользуйтесь тэгами [CODE]

user posted image
AVC
Отправлено: 19.05.2005, 12:16


Ветеран

Группа: Модератор
Сообщений: 1583



QUOTE

2. Наиболее быстрым способом оказался (что меня несколько удивило) следующий:

Меня тоже. smile.gif (это самый медленный способ).
CODE

void __fastcall TF_List::Button7Click(TObject *Sender)
{
AnsiString str;
AnsiString prot;
int        done;
double    dltt;
__int64    qp_freq, qp_begin, qp_end;
QueryPerformanceFrequency ((LARGE_INTEGER*)&qp_freq);


str = "";
for (int i=0; i < 2000; i++)  str += AnsiString::StringOfChar('a',100) + ",";
QueryPerformanceCounter ((LARGE_INTEGER*)&qp_begin);
for (char *cp=str.c_str(); *cp; cp++ )
  if(*cp == ',') *cp = '\n';
QueryPerformanceCounter((LARGE_INTEGER*)&qp_end);
dltt = (double(qp_end — qp_begin) * double(1000)) / double(qp_freq);
prot += FormatFloat("0.000", dltt) + "\n";
/* 0.567 ----------------------------------------------------- */



str = "";
for (int i=0; i < 2000; i++)  str += AnsiString::StringOfChar('a',100) + ",";
int len=str.Length();
done = 0;
QueryPerformanceCounter ((LARGE_INTEGER*)&qp_begin);
for (int i=1; i <= len; i++)
{  if (str[i]==',' && done < 8)
    {  str[i]='\n';
       done++;
    }
}
QueryPerformanceCounter((LARGE_INTEGER*)&qp_end);
dltt = (double(qp_end — qp_begin) * double(1000)) / double(qp_freq);
prot += FormatFloat("0.000", dltt) + "\n";
/* 9.007 ----------------------------------------------------- */


str = "";
for (int i=0; i < 2000; i++)  str += AnsiString::StringOfChar('a',100) + ",";
done = 0;
QueryPerformanceCounter ((LARGE_INTEGER*)&qp_begin);
for (char *cp=str.c_str(); *cp; cp++ )
  if(*cp == ',')
   {  if(++done > 8)  break;
      *cp = '\n';
   }
QueryPerformanceCounter((LARGE_INTEGER*)&qp_end);
dltt = (double(qp_end — qp_begin) * double(1000)) / double(qp_freq);
prot += FormatFloat("0.000", dltt) + "\n";
/* 0.005 ----------------------------------------------------- */

ShowMessage(prot);
}

Нужны пояснения?
Tempus
Отправлено: 19.05.2005, 13:15


Ученик-кочегар

Группа: Участник
Сообщений: 14



Wow!!!

Спасибо за пример, надеюсь в некомерческих целях использовать Ваш код можно :-)

Я сравнивал другими алгоритмами, не пытаясь оптимизировать этот. К тому же скорость работы проверял "на глаз" — на выборке из 36000 строк.
avc*
Отправлено: 19.05.2005, 13:26


Не зарегистрирован







Можно и в комерческих, но тогда с упоминанием Форума smile.gif (кыргуду)
Gedeon
Отправлено: 19.05.2005, 15:18


Ветеран

Группа: Модератор
Сообщений: 1742



А зачем считать время в килосекундах?
Guest
Отправлено: 19.05.2005, 15:56


Не зарегистрирован







А где сказано, что это в секундах?
Просто для уменьшения числа лидирующих 0.
Gedeon
Отправлено: 19.05.2005, 16:49


Ветеран

Группа: Модератор
Сообщений: 1742



QUOTE (Guest @ 19/05/2005, 15:56)
А где сказано, что это в секундах?

Я — тупой. user posted image В милисекундах.
QUOTE

Просто для уменьшения числа лидирующих 0.

Согласен, так удобнее.

Отредактировано Gedeon — 19/05/2005, 16:49
Tempus
Отправлено: 20.05.2005, 10:41


Ученик-кочегар

Группа: Участник
Сообщений: 14



Еще один вопрос в продолжение темы:

Нужна функция, которая бы сравнивала строки, без учета регистра (так же как AnsiCompareIC). Если с учетом регистра, то можно так:
CODE

char StrCompare(char *s1, char *s2)
{
char res;
for (s1;(*s1)*(*s2);s1++,s2++)
 {
   if ((res=*s1-*s2)) return res;
 }
return (*s1-*s2);
}


а как сделать, чтобы без учета регистра?

Отредактировано Tempus — 20/05/2005, 10:42
Guest
Отправлено: 20.05.2005, 12:49


Не зарегистрирован







Пример преобразования к верхнему регистру
(ТОЛЬКО для английских и русских)
CODE

unsigned char *cp;
.........
if ((*cp >= 0x61 && *cp <= 0x7A) || (*cp >= 0xE0 && *cp <= 0xFF))   *cp -= 0x20;
else if (*cp == 0xB8)   *cp = 0xA8;

Tempus
Отправлено: 20.05.2005, 13:06


Ученик-кочегар

Группа: Участник
Сообщений: 14



Спасибо, а часом в stdio.h нет стандартной функции приведения char к верхнему регистру?
Guest
Отправлено: 20.05.2005, 13:59


Не зарегистрирован







Есть такое
QUOTE

The CharUpper function converts a character string or a single character to uppercase. If the operand is a character string, the function converts the characters in place. This function supersedes the AnsiUpper function.

LPTSTR CharUpper(
   LPTSTR lpsz  // single character or pointer to string
  );

Gedeon
Отправлено: 20.05.2005, 14:32


Ветеран

Группа: Модератор
Сообщений: 1742



так а
CODE

int strcmpi(const char *s1, const char *s2);

int _wcscmpi(const wchar_t *s1, const wchar_t *s2);


не подходит? пример
CODE

   char *s1 = "Sasha";
   char *s2 = "sasha";
   int res = strcmp(s1,s2);
   printf("%d\n",res);
   int res1 = strcmpi(s1,s2);
   printf("%d\n",res1);
   system("PAUSE");
   return 0;
[/QUOTE]
Результат
QUOTE

-32
0

Вернуться в Вопросы программирования в C++Builder