За последние 24 часа нас посетили 22329 программистов и 1021 робот. Сейчас ищут 646 программистов ...

удобная работа со строками [решение, рассуждение]

Тема в разделе "Решения, алгоритмы", создана пользователем Koc, 6 июн 2009.

  1. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    Хорошо хоть не бросать исключение. А то дойдет до абсурда
    PHP:
    1. <?php
    2. try {
    3.     Str::Format();
    4. } catch StringException as $e {
    5.     // ...
    6. }
    7.  
    http://code.google.com/p/strclass/sourc ... baa4bb8d0#
    реализовал дефолты для левых констант.
     
  2. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Это моя ошибка. find использует встроенные mb_strr?i?pos. Не использует встроенные stri?str метод take. И за счет изобретения велосипеда Косу удалось сделать его быстрее, чем оригинал. Кос, ты должен собой гордиться :)

    PHP:
    1. <?php
    2.     require_once 'init.php';
    3.     require_once ENGINE_PATH . 'Lib/Str/IStr.php';
    4.  
    5.     $test = new StrTest;
    6.     /**
    7.      * 0.263025045395
    8.      * 0.266486883163
    9.      * 0.252983093262
    10.      * 0.288603067398
    11.      * 0.290389060974
    12.      */ // $test->testGenerator();
    13.      
    14.     /**
    15.      * 3.85818910599
    16.      * 3.69979500771
    17.      * 3.74817490578
    18.      * 4.03902196884
    19.      * 3.74049687386
    20.      */ // $test->testCore();
    21.  
    22.     /**
    23.      * 2.79515790939
    24.      * 2.74578690529
    25.      * 2.84421300888
    26.      * 2.82366919518
    27.      * 2.76611804962
    28.      */ // $test->testWrapper();
    PHP:
    1. <?php
    2.  
    3. class StrTest {
    4.     private $cycles = 1000;
    5.     private $strLen = 1000;
    6.  
    7.     public function genRandStr () {
    8.         $symbs = "йцУке нгШщзхї ФівАпрОл джЭячсМ итьБю";
    9.         $len = strlen($symbs);
    10.         $str = '';
    11.         for($i=$this->strLen/5;$i--;) {
    12.             $str .= mb_substr($symbs, mt_rand(0, $len-5), 5);
    13.         }
    14.         return $str;
    15.     }
    16.  
    17.     public function testGenerator () {
    18.         $s = microtime(1);
    19.         for($i=$this->cycles; $i--;) {
    20.             $this->genRandStr();
    21.         }
    22.         echo microtime(1) - $s;
    23.     }
    24.  
    25.     public function testCore () {
    26.         $s = microtime(1);
    27.         for($i=$this->cycles; $i--;) {
    28.             mb_strstr  ($this->genRandStr(), 'цук');
    29.             mb_stristr ($this->genRandStr(), 'іва');
    30.             mb_strstr  ($this->genRandStr(), 'єяч');
    31.             mb_stristr ($this->genRandStr(), 'итьбю');
    32.             mb_strstr  ($this->genRandStr(), 'ФівА');
    33.             mb_stristr ($this->genRandStr(), 'прОл');
    34.             mb_strstr  ($this->genRandStr(), 'ФівА');
    35.             mb_stristr ($this->genRandStr(), 'прОл');
    36.             mb_strstr  ($this->genRandStr(), 'джЭ');
    37.             mb_stristr ($this->genRandStr(), 'ить');
    38.         }
    39.         echo microtime(1) - $s;
    40.     }
    41.  
    42.  
    43.     public function testWrapper () {
    44.         $s = microtime(1);
    45.         for($i=$this->cycles; $i--;) {
    46.             Str::take('цук',   $this->genRandStr(), Str::RIGHT_WITH);
    47.             Str::take('іва',   $this->genRandStr(), Str::RIGHT_WITH, false);
    48.             Str::take('єяч',   $this->genRandStr(), Str::RIGHT_WITH);
    49.             Str::take('итьбю', $this->genRandStr(), Str::RIGHT_WITH, false);
    50.             Str::take('ФівА',  $this->genRandStr(), Str::RIGHT_WITH);
    51.             Str::take('прОл',  $this->genRandStr(), Str::RIGHT_WITH, false);
    52.             Str::take('ФівА',  $this->genRandStr(), Str::RIGHT_WITH);
    53.             Str::take('прОл',  $this->genRandStr(), Str::RIGHT_WITH, false);
    54.             Str::take('джЭ',   $this->genRandStr(), Str::RIGHT_WITH);
    55.             Str::take('ить',   $this->genRandStr(), Str::RIGHT_WITH, false);
    56.         }
    57.         echo microtime(1) - $s;
    58.     }
    59. }
    60.  
    61. ?>
     
  3. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Эта функция мало того, что глючная, так еще и с багом:
    Код (Text):
    1. echo Str::changeCase('t "x', Str::TITLE);
    2. T \"x
     
  4. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    PHP:
    1. <?php
    2. $string = '...';
    3.  
    4. // echo mb_strlen($string); // 9000
    5.  
    6. function coreFunc ($string) {
    7.     return mb_convert_case($string, MB_CASE_TITLE);
    8. }
    9.  
    10. function symbRegexp ($string) {
    11.     $pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue';
    12.     return preg_replace($pattern, 'mb_strtoupper(\'$0\')', $string);
    13. }
    14.  
    15. function wordRegexp ($string) {
    16.      return preg_replace_callback ("/([\p{L}\p{Nd}]+)/", 'upFirst', $string);
    17. }
    18.  
    19. function upFirst ($m) {
    20.     return mb_strtoupper(mb_substr($m[0], 0, 1)) . mb_substr($m[0], 1);
    21. }
    22.  
    23. $s = microtime(1);
    24. for ($i = 100; $i--;) {
    25.     /**
    26.      * 0.410108089447
    27.      * 0.414505004883
    28.      * 0.435932159424
    29.      */ // coreFunc ($string);
    30.  
    31.     /**
    32.      * 9.61156892776
    33.      * 9.20857191086
    34.      * 9.88236117363
    35.      */ // symbRegexp ($string);
    36.  
    37.     /**
    38.      * 1.25071811676
    39.      * 1.13894200325
    40.      * 1.21463418007
    41.      */ // wordRegexp ($string);
    42. }
    43. echo microtime(1) - $s;
    Вывод - в данном лучше использовать встроенную функцию, а если ее функционала не хватает - тогда уж использовать поиск слов, с которыми работать. То, что сейчас - не годится никуда. Тем более при поиске по словам более корректные результаты:

    PHP:
    1. <?php
    2. echo coreFunc  ("привет!как дела?чем занимаешься?"), "\n"; // Привет!Как Дела?Чем Занимаешься?
    3. echo symbRegexp("привет!как дела?чем занимаешься?"), "\n"; // Привет!как Дела?чем Занимаешься?
    4. echo wordRegexp("привет!как дела?чем занимаешься?"), "\n"; // Привет!Как Дела?Чем Занимаешься?
     
  5. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    PHP:
    1. <?
    2. function coreFunc ($string) {
    3.     return mb_convert_case($string, MB_CASE_TITLE);  
    4. }
    5.  
    6. function symbRegexp ($string) {
    7.     $pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue'
    8.     return preg_replace($pattern, 'mb_strtoupper(\'$0\')', $string);
    9. }
    резалты этих ф-ций будут разные.

    на входе:
    стрОКа стока
    coreFunc:
    Строка Стока
    symbRegexp:
    СтрОКа Стока

    так что сравнивать их не совсем корректно
     
  6. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    ну предположим для ASCII
    $pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/e';
    можно заменить на
    $pattern = '/(^|\W)(\w)/e';


    немного обновил, потесть сейчас
    http://code.google.com/p/strclass/sourc ... 9f393288b#
     
  7. kostyl

    kostyl Guest

    А в твоем тесте она точно её использует, тоесть включен ли мультибайт?
     
  8. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    афаик, \w распространяется на символы, коды которых ниже 127. С кириллицей даже в win-1251 оно работать не будет, ибо она находится между 128 и 255.
    А вот и доказательство:
    Код (Text):
    1. shock@shock:~$ du -b en.php
    2. 55      en.php
    3. shock@shock:~$ cat en.php
    4. <?php var_dump(preg_match("/^[\w]+$/", "String")); ?>
    5. shock@shock:~$ php -f en.php
    6. int(1)
    7.  
    8. shock@shock:~$ du -b 1251.php
    9. 55      1251.php
    10. shock@shock:~$ cat 1251.php
    11. <?php var_dump(preg_match("/^[\w]+$/", "Строка")); ?>
    12. shock@shock:~$ php -f 1251.php
    13. int(0)
    14.  
    15. shock@shock:~$ du -b utf8.php
    16. 61      utf8.php
    17. shock@shock:~$ cat utf8.php
    18. <?php var_dump(preg_match("/^[\w]+$/", "Строка")); ?>
    19. shock@shock:~$ php -f utf8.php
    20. int(0)
    По количеству байт видно, что в файле 1251.php действительно однобайтовая кодировка, тем не менее нужные символы оно не нашло. Я не понимаю, почему ты так сопротивляешься предложенному поиску по словам, если он реально лучше по многим параметрам.


    kostyl, я уже ниже сказал, что функцию перепутал и быстрее встроенной функция, которая самонаписана Косом и действительно показывает лучшие результаты. Выше есть код для теста.
     
  9. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    кстати, модификатор "u" тут не нужен:
    Код (Text):
    1. "/([\p{L}\p{Nd}])/ue"
    И еще:
    PHP:
    1.  
    2. <?php
    3.         /**/ public static function changeCase;
    4.         public static function length;
    5.         public static function trim;
    6.         public static function reverse;
    7.         /**/ private static function caseInvert;
    8.         /**/ private static function caseRand;
    9.         /**/ private static function charCaseInvert;
    10.         /**/ private static function charCaseRand;
    11.         public static function fill;
    Я не совсем согласен с месторасположением приватных функций. Они относятся только к методу "changeCase", а стоят между методами "reverse" и "fill". Думаю, их стоит засунуть либо в конец класса, либо в начало, либо сразу после "changeCase"
     
  10. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    TheShock
    у меня вроде работает \w с кириллицей при cp1251

    а находятся методы там потому что я все новые методы в конец класса добавляю. fill аоявился позже caseInvert. Да, я и сам думал перенести их.
     
  11. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    ну, тесты выше...

    я догадался. думаю, лучшим решением будет их перенести после changeCase?
     
  12. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    да, мейби лучше за changeCase

    я запускал example/ascii.php - регистр приводит как нужно. Не через cli запускал правда.

    готовится к выходу скопипастенный метод size - размер строки в байтах.
     
  13. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    у меня возникла идея улучшения одного метода. сейчас поэкспериментирую и покажу.
    (Юбилейный 500 пост)
     
  14. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Идея себя оправдала и она в следующем. При мультибайтовом поиске (метод StrU::take) мы передаем методу и заставляем его работать с мультибайтовой строкой. Сначала, при помощи self::find мы ищем позицию символа нужного вхождения (не байта, а именно символа), после этого определяем количество символов (не байт) в строке функцией mb_strlen, а потом обрезаем все функцией mb_substr. Но почему бы не работать с этими строками не как с массивом юникод-символов, а как с массивом байт? Совершенно не меняя при этом интерфейс
    Смотрите. Строка "метод trim" в unicode представлении - это 10 байт: 04.4C|04.35|04.42|04.3E|04.2B|?|74|72|69|6D
    если мы будем искать в нем по букве "м", то, конечно, можно поискать ее как символ с кодом 04.2B в многобайтовой строке, но это будет медленнее, чем искать два байта в однобайтовой строке - нету разделения на символы. Но, несмотря на способ, которым мы будем пользоваться, вернется нам один результат - строка начиная с байтов 04.2B и до конца. А теперь практика:

    PHP:
    1. <?php
    2. error_reporting(E_ALL | E_STRICT);
    3.  
    4. define('RIGHT', 1);
    5. define('LEFT',  2);
    6. define('RIGHT_WITH', 3);
    7. define('LEFT_WITH',  4);
    8. define('CS', true);
    9. define('CI', false);
    10.  
    11. function coreTake ($what, $where, $caseSensitive = CI) {
    12.     return $caseSensitive ? mb_strstr($where, $what) : mb_stristr($where, $what);
    13. }
    14.  
    15. function multiTake ($what, $where, $caseSensitive = CI, $type = RIGHT_WITH) {
    16.     // Поиск символов в строке
    17.     $pos = ($caseSensitive) ? mb_strpos($where, $what) : mb_stripos($where, $what);
    18.    
    19.     if ($pos === false) return '';
    20.    
    21.     switch ($type) {
    22.         case RIGHT:
    23.             return mb_substr($where, $pos + mb_strlen($what));
    24.        
    25.         case LEFT:
    26.             return mb_substr($where, 0, $pos);
    27.        
    28.         case RIGHT_WITH:
    29.             return mb_substr($where, $pos);
    30.        
    31.         case LEFT_WITH:
    32.             return mb_substr($where, 0, $pos + mb_strlen($what));
    33.     }
    34. }
    35.  
    36. function byteTake ($what, $where, $caseSensitive = CI, $type = RIGHT_WITH) {
    37.     // Поиск байт в массиве байтов
    38.     $pos = ($caseSensitive) ? strpos($where, $what) : strpos(mb_strtolower($where), mb_strtolower($what));
    39.    
    40.     if ($pos === false) return '';
    41.    
    42.     switch ($type) {
    43.         case RIGHT:
    44.             return substr($where, $pos + strlen($what));
    45.        
    46.         case LEFT:
    47.             return substr($where, 0, $pos);
    48.        
    49.         case RIGHT_WITH:
    50.             return substr($where, $pos);
    51.        
    52.         case LEFT_WITH:
    53.             return substr($where, 0, $pos + strlen($what));
    54.     }
    55. }
    56.  
    57. $str = str_repeat('йцУке нгШщзхї stronger bджЭячсМ итьБю ек', 13);
    58. $str.= str_repeat('холох mirrow ФівАпрОл gджЭячсМ итьБю his', 12);
    59. // echo mb_strlen($str); // 1000 - символов
    60. // echo strlen($str); // 1638 - байт
    61. $s = microtime(1);
    62. for ($i=10000; $i--;) {
    63.  
    64.     // Case Sensitive
    65.  
    66.     /** coreTake Case Sensitive
    67.      * 0.299010038376
    68.      * 0.290163993835
    69.      * 0.326658964157
    70.      */ // coreTake  ('олох mi', $str, CS);
    71.  
    72.     /** multiTake Case Sensitive
    73.      * 0.271375894547
    74.      * 0.271709918976
    75.      * 0.296041965485
    76.      */ // multiTake ('олох mi', $str, CS);
    77.  
    78.     /** byteTake Case Sensitive
    79.      * 0.108669996262
    80.      * 0.116374015808
    81.      * 0.111712932587
    82.      */ // byteTake  ('олох mi', $str, CS);
    83.  
    84.     // Case InSensitive
    85.  
    86.     /** coreTake Case InSensitive
    87.      * 4.53858208656
    88.      * 4.55345606804
    89.      * 4.48540306091
    90.      */ // coreTake  ('олох mi', $str, CI);
    91.  
    92.     /** multiTake Case InSensitive
    93.      * 4.34134888649
    94.      * 4.30748605728
    95.      * 4.39033198357
    96.      */ // multiTake ('олох mi', $str, CI);
    97.  
    98.     /** byteTake Case InSensitive
    99.      * 2.72171115875
    100.      * 2.58283400536
    101.      * 2.75850009918
    102.      */ // byteTake  ('олох mi', $str, CI);
    103.  
    104. }
    105. echo microtime(1) - $s;
    106.  
    107. ?>
    Ну и для того, чтобы убедиться, что все функции работают правильно:
    PHP:
    1.  
    2. <?php
    3. echo coreTake  ('r', 'string'), "\n"; // ring
    4. echo multiTake ('r', 'string'), "\n"; // ring
    5. echo byteTake  ('r', 'string'), "\n"; // ring
    6.  
    7. echo coreTake  ('р', 'строка'), "\n"; // рока
    8. echo multiTake ('р', 'строка'), "\n"; // рока
    9. echo byteTake  ('р', 'строка'), "\n"; // рока
    10. ?>

    UPD:
    Меня посетила мысль, что, возможно, byteTake может дать сбой в определенных обстоятельствах: осуществляется поиск однобайтного символа (например, D == 0x44), но первым идет двухбайтовый символ, содержащий одним из байтов такой, поиск которого осуществляется (например, ф == 0x04.0x44). Но опасения не оправдались:
    PHP:
    1. <?php echo byteTake  ('D', 'тестофая строка зDесь'), "\n"; // Dесь ?>
    Для того, чтобы проверить, действительно ли соответствуют эти символы (ф и D) заявленным байтам - создайте html-документ со следующим содержимым и откройте у себя в браузере:
    Код (Text):
    1. &#x0444; &#x44;
     
  15. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    о-хох. Еще утро и я не расчехлился.
    То есть ты утверждаешь, что mb_ ф-ции могут быть заменены ихними простыми одномайтовыми аналогами?

    зы: ф-ция length всегда возвращает кол-во байт? 1 символ ASCII == 1 байт. Но как быть с UNICODE? это я к тому, что чувак написал длинную ф- а ты просто тма берешь и пишешь:
    // echo strlen($str); // 1638 - байт



    mb_strtolower($what) можно будет заменить на self::changeCase и тогда весь метод take вынести в StrCommon
     
  16. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    нет. я не утверждаю, что ВСЕ функции могут быть заменены. Но некоторые - могут. Нельзя заменить "length" , потому что она должна возвращать количество символов, а не байт, но можно заменить функции, где ищется один массив байт в другом. По крайней мере, я так думаю :)
     
  17. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    для size можно использовать просто length а не ту здоровую ф-цию по ссылке?
     
  18. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Ребят, не забываем что такое setlocale и его нужно ставить, если у вас не юникодные строки!
    А для юникода конечно есть флаг u. А про первое все забывают и удивляются - а хрена не пашет?!
     
  19. зависит от используемой локали
     
  20. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    Psih
    я чего-то не пойму. TheShock всегда настаивает что флаг u нужно убирать.

    флоппик
    так что использовать вместо \W и \w для ASCII?
     
  21. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    [\p{L}\p{Nd}] работает что с флагом, что без флага - одинаково. Но флаг значительно(в пять раз) замедляет работу выражения: http://www.php.ru/forum/viewtopic.php?p=163775#163775. Зачем этот флаг, если честно, я слабо понимаю :))

    Про локаль - да, забыл. Не работаю не с Юникодом уже очень давно. Тем более мой KWrite его не любит

    я вообще не осилил, что та функция делает :)) имхо, она очень странная. на сколько я знаю, strlen считает именно количество байт, и пример выше это доказывает
     
  22. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    TheShock
    Потому что юникодные строки из-за своей переменной длинны символов всегда медленее обычных. Это особенность UTF8 и она такова везде.

    Флаг нужен для таких вещей:

    PHP:
    1. <?php
    2. preg_match('/[а-я]+/ui', $text, $matches);
    Что-бы небыло торможения, надо использовать UTF-16, там все символы 2 байта, в итоге скорость работы практически такая-же.
     
  23. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Да, для интервалов флаг нужен, хотя и с флагом бывают пропуски. приходится дописывать чтото типа /[а-ягтіїґ]/ui

    про UTF-16 я тоже уже думал. мне вообще она больше 8 нравится. но все же, сейчас стандартом стала утф-8
     
  24. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    TheShock
    По той простой причине, что латиница в нём всё-же 1 байт. Хотя если чисто русский текст - почему-бы не юзать UTF-16? :)
     
  25. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Psih
    да я знаю. он - легче по весу и он - обратно совместим со старыми кодировками и устаревшими браузерами. тем не менее, процессорное время дороже места на жестком диске, вроде :)