Каков наилучший (самый дешевый) способ ввода сложных строк CamelCase?

У меня есть большое количество входящих фраз в реальном времени, которые необходимо преобразовать в alpha only - CamelCase по словам и точкам разделения.

Это то, что я придумал до сих пор, но есть ли более дешевый и быстрый способ выполнить эту задачу?

function FoxJourneyLikeACamelsHump(string $string): string {
  $string = preg_replace("/[^[:alpha:][:space:]]/u", ' ', $string);
  $string = ucwords($string);
  $camelCase = preg_replace('/\s+/', '', $string);
  return $camelCase;
}

// $expected = "ThQuCkBrWnFXJumpsVRThLZyDG";
$string = " Th3 qu!ck br0wn f0x jumps 0v3r th3 l@zy d0g. ";
$is = FoxJourneyLikeACamelsHump($string);

Полученные результаты:

Предложений: 100000000
Общее время: 40.844197034836 seconds
в среднем: 0.000000408


person mkungla    schedule 08.04.2017    source источник
comment
Пожалуйста, прекратите использовать так много жирного шрифта.   -  person Daedalus    schedule 08.04.2017
comment
@ Daedalus, что у тебя за проблема с жирным шрифтом в нужном месте?   -  person mkungla    schedule 08.04.2017
comment
справедливо, я не собираюсь никого раздражать. Думал привлечь внимание к препятствиям, с которыми я сталкиваюсь, и никому не нужно много читать, но буду помнить об этом в следующий раз.   -  person mkungla    schedule 08.04.2017
comment
Вы делаете 0,2 миллиарда замен на основе регулярных выражений примерно за 41 секунду - этого недостаточно?   -  person Robin Mackenzie    schedule 08.04.2017
comment
Вы не говорите, почему текущая производительность является проблемой: вам может понадобиться немного больше сформулировать ситуацию, чтобы контекстуализировать ее. Возможно, мы смотрим не на ту часть проблемы. Как намекает @RobinMackenzie... это может быть для меня преждевременной оптимизацией. У вас действительно есть проблема, связанная с бизнесом, которую вы пытаетесь решить? то есть: это занимает слишком много времени, и в результате мы теряем деньги. Вот тогда и может понадобиться микрооптимизация. Не говорю, что у вас нет законного дела; но ты не объясняешь это.   -  person Adam Cameron    schedule 08.04.2017
comment
Поскольку вы хотите иметь дело со строками юникода, вы не можете использовать такие функции, как ucwords или ucfirst, которые не поддерживают юникод.   -  person Casimir et Hippolyte    schedule 08.04.2017


Ответы (4)


Ваш код достаточно эффективен. Вы все еще можете улучшить с помощью нескольких настроек:

  • Укажите разделитель для ucwords, чтобы ему не приходилось искать \t, \n и т. д., которых не будет в вашей строке после первого шага. В среднем это дает улучшение на 1%;
  • Вы можете выполнить последний шаг с заменой пробела без регулярного выражения. Это дает до 20% улучшения.

Код:

function FoxJourneyLikeACamelsHump(string $string): string {
    $string = preg_replace("/[^[:alpha:][:space:]]/u", ' ', $string);
    $string = ucwords($string, ' ');
    $camelCase = str_replace(' ', '', $string);
    return $camelCase;
}

Время выхода исходной и улучшенной версии см. на rextester.com.

Примечание. Поскольку вы использовали ucwords, ваш код нельзя надежно использовать для строк Unicode в целом. Чтобы покрыть это, вам нужно будет использовать такую ​​​​функцию, как mb_convert_case:

$string = mb_convert_case($string,  MB_CASE_TITLE);

... но это влияет на производительность.

person trincot    schedule 08.04.2017

Сравнивая с 3 альтернативами, я считаю, что ваш метод самый быстрый. Вот результаты 100 000 итераций:

array(4) {
  ["Test1"]=>
  float(0.23144102096558)
  ["Test2"]=>
  float(0.41140103340149)
  ["Test3"]=>
  float(0.31215810775757)
  ["Test4"]=>
  float(0.98423790931702)
}

Где Test1 — ваше, Test2 и Test3 — мое, а Test4 — из ответа @RizwanMTuman (с исправлением).

Я подумал, что использование preg_split может дать вам возможность оптимизировать. В этой функции используется только одно регулярное выражение, которое возвращает массив только альфа-элементов, к которым вы затем применяете ucfirst:

function FoxJourneyLikeACamelsHump_2(string $string): string {
    return implode('', array_map(function($word) {
        return ucfirst($word);
    }, preg_split("/[^[:alpha:]]/", $string, null, PREG_SPLIT_NO_EMPTY)));
}

Это можно дополнительно оптимизировать, используя foreach вместо array_map (см. with-static-function">здесь):

function FoxJourneyLikeACamelsHump_3(string $string): string {
    $validItems = preg_split("/[^[:alpha:]]/u", $string, null, PREG_SPLIT_NO_EMPTY);
    $result = '';
    foreach($validItems as $item) {
        $result .= ucfirst($item);
    }
    return $result;
}

Это заставляет меня предположить, что 2 регулярных выражения и 1 ucwords быстрее, чем 1 регулярное выражение и несколько ucfirst.

Полный тестовый скрипт:

<?php

// yours
function FoxJourneyLikeACamelsHump_1(string $string): string {
  $string = preg_replace("/[^[:alpha:][:space:]]/u", ' ', $string);
  $string = ucwords($string);
  $camelCase = preg_replace('/\s+/', '', $string);
  return $camelCase;
}

// mine v1
function FoxJourneyLikeACamelsHump_2(string $string): string {
    return implode('', array_map(function($word) {
        return ucfirst($word);
    }, preg_split("/[^[:alpha:]]/", $string, null, PREG_SPLIT_NO_EMPTY)));
}

// mine v2
function FoxJourneyLikeACamelsHump_3(string $string): string {
    $validItems = preg_split("/[^[:alpha:]]/u", $string, null, PREG_SPLIT_NO_EMPTY);
    $result = '';
    foreach($validItems as $item) {
        $result .= ucfirst($item);
    }
    return $result;
}

// Rizwan with a fix
function FoxJourneyLikeACamelsHump_4(string $string): string {
    $re = '/(?:\b|\d+)([a-z])|[\d+ +!.@]/';
    $result = preg_replace_callback($re,function ($matches) {
        return (isset($matches[1]) ? strtoupper($matches[1]) : '');
    },$string);
    return $result;
}


// $expected = "ThQuCkBrWnFXJumpsVRThLZyDG";
$test1 = 0;
$test2 = 0;
$test3 = 0;
$test4 = 0;

$loops = 100000;

$time_start = microtime(true);
for($i=0; $i<$loops; $i++) {
    $string = " Th3 qu!ck br0wn f0x jumps 0v3r th3 l@zy d0g. ";
    $is = FoxJourneyLikeACamelsHump_1($string);
    if($loops==1) echo $is."\n";
}
$time_end = microtime(true);
$test1 = $time_end - $time_start;

$time_start = microtime(true);
for($i=0; $i<$loops; $i++) {
    $string = " Th3 qu!ck br0wn f0x jumps 0v3r th3 l@zy d0g. ";
    $is = FoxJourneyLikeACamelsHump_2($string);
    if($loops==1) echo $is."\n";
}
$time_end = microtime(true);
$test2 = $time_end - $time_start;

$time_start = microtime(true);
for($i=0; $i<$loops; $i++) {
    $string = " Th3 qu!ck br0wn f0x jumps 0v3r th3 l@zy d0g. ";
    $is = FoxJourneyLikeACamelsHump_3($string);
    if($loops==1) echo $is."\n";
}
$time_end = microtime(true);
$test3 = $time_end - $time_start;

$time_start = microtime(true);
for($i=0; $i<$loops; $i++) {
    $string = " Th3 qu!ck br0wn f0x jumps 0v3r th3 l@zy d0g. ";
    $is = FoxJourneyLikeACamelsHump_4($string);
    if($loops==1) echo $is."\n";
}
$time_end = microtime(true);
$test4 = $time_end - $time_start;

var_dump(array('Test1'=>$test1, 'Test2'=>$test2, 'Test3'=>$test3, 'Test4'=>$test4));
person Robin Mackenzie    schedule 08.04.2017

Вы можете попробовать это регулярное выражение:

(?:\b|\d+)([a-z])|[\d+ +!.@]

UPDTAE ( Запустите его здесь )

Что ж, вышеизложенная идея состоит в том, чтобы показать вам, как это должно работать в регулярном выражении:

Ниже приведена php-реализация вышеуказанного регулярного выражения. Вы можете сравнить его со своим, так как это позволяет выполнять операцию с помощью одной операции замены:

<?php

$re = '/(?:\b|\d+)([a-z])|[\d+ +!.@]/';
$str = 'Th3 qu!ck br0wn f0x jumps 0v3r th3 l@zy d0g. ';
$subst=strtoupper('\\1');

$result = preg_replace_callback($re,function ($matches) {
return (isset($matches[1]) ? strtoupper($matches[1]) : '');
    },$str);

echo $result;

?>

Демонстрация регулярных выражений

person Rizwan M.Tuman    schedule 08.04.2017
comment
ОП спрашивает о скорости. Также не является ли \U regex101.com чем-то особенным? - person vallentin; 08.04.2017
comment
Понижение и пометка, поскольку это не отвечает на вопрос. Используемый диалект регулярных выражений недействителен в PHP и явно не тестировался в PHP. - person Adam Cameron; 08.04.2017
comment
@AdamCameron Я обновил ответ .. не кажется ли вам, что было преждевременно помечать ответ, который не дал вам реализацию php, а скорее общее представление о решении? - person Rizwan M.Tuman; 08.04.2017
comment
Вы получаете кучу ошибок PHP Notice: Undefined offset: 1, а также правильный вывод с этим. Вы можете попробовать return (isset($matches[1]) ? strtoupper($matches[1]) : ''); - person Robin Mackenzie; 08.04.2017
comment
@RobinMackenzie, ты абсолютно прав .. обновлено ... спасибо, приятель - person Rizwan M.Tuman; 08.04.2017
comment
@rizwan: я пометил это как не ответ, но может быть с некоторыми изменениями. Вы, кажется, действительно согласны. Не уверен, в чем проблема. Ваше обновление теперь хорошо, и я удалю свой отрицательный голос. - person Adam Cameron; 08.04.2017
comment
@ Адам Кэмерон, расслабься, приятель, я тоже не так уж серьезен :) .. но у меня никогда не было флага, поэтому я не совсем понимаю, что такое пометка;) - person Rizwan M.Tuman; 08.04.2017

Прежде чем думать об улучшении производительности кода, вам нужно сначала создать работающий код. На самом деле вы пытаетесь создать код, который обрабатывает строки в кодировке utf8 (поскольку вы добавили модификатор u в свой шаблон); но со строкой: liberté égalité fraternité ваш код возвращает Liberté égalité Fraternité вместо Liberté Égalité Fraternité, потому что ucwords (или ucfirst) не может работать с многобайтовыми символами .

Попробовав разные подходы (с preg_split и preg_replace_callback), кажется, что эта версия preg_match_all самая быстрая:

function FoxJourneyLikeACamelsHumpUPMA(string $string): string {
    preg_match_all('~\pL+~u', $string, $m);
    foreach ($m[0] as &$v) {
        $v = mb_strtoupper(mb_substr($v, 0, 1)) . mb_strtolower(mb_substr($v, 1));
    }
    return implode('', $m[0]);
}

Очевидно, что он медленнее вашего исходного кода, но мы не можем сравнивать эти разные коды, так как ваш не работает.

person Casimir et Hippolyte    schedule 09.04.2017