Создайте синопсис строки

Учитывая неизвестную строку неизвестного размера, например. выражение ScriptBlock или что-то вроде:

$Text = @'
LOREM IPSUM

Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
'@

Я хотел бы свести строку к одной строке (заменить все последовательные пробелы на один пробел) и усечь ее до определенного $Length:

$Length = 32
$Text = $Text -Replace '\s+', ' '
if ($Text.Length -gt $Length) { $Text = $Text.SubString(0, $Length) }
$Text
LOREM IPSUM Lorem Ipsum is simpl

Проблема в том, что если речь идет о большой строке, она не очень эффективна для замены пробелов: она заменяет все пробелы во всей строке $Text, где нужно заменить только первые несколько пробелов, пока у меня не будет строки необходимый размер ($Length = 32).
Менять местами операции -replace и SubString нежелательно, так как это вернет меньшую длину, чем требуется, или даже один пробел для любой строки $Text, которая начинается примерно с 32 пробелов.

Вопрос:
Как я могу эффективно объединить две операции (-replace и SubString), чтобы не заменять больше пробелов, чем необходимо, и получать строку необходимой длины (в случае, если строка $Text больше требуемой длины)?


Обновлять

Думаю, я близок к этому, используя делегат MatchEvaluator:

$Length = 8
$TotalSpaces = 0
$Delegate = {
    if ($Args[0].Index - $TotalSpaces -gt $Length) {
        '{break}'
        ([Ref]$TotalSpaces).Value = [int]::MaxValue
    }
    else { ([Ref]$TotalSpaces).Value += $Args[0].Value.Length }
}
[regex]::Replace('test 0 1 2 3 4 5 6 7 8 9', '\s+', $Delegate)
test01234{break}56789

Теперь вопрос в том, как я могу прервать обработку регулярных выражений на {break}?
Обратите внимание, что из соображений производительности я действительно хочу вырваться, а не заменять <regular-expression> найденным совпадением (из-за этого создается впечатление, что оно остановилось).

Сейчас у меня нет надлежащего доступа к компьютеру, но если вы обрабатываете строку произвольной длины, возможно, вам захочется взглянуть на перегрузку Match(String, Int32) класса Regex — см. Learn.microsoft.com/en-us /dotnet/api/… — и просто вытяните все до первого совпадения пробелов в начале строки, затем вызовите его снова, начиная с текущего совпадения, пока не получите желаемую длину вывода...

mclayton 24.07.2024 12:46

Или даже перегрузка Match(String, Int32, Int32)- Learn.microsoft.com/en-us/dotnet/api/…, которая делает то же самое, но останавливается после считывания заданного количества входных символов, поэтому вы не обрабатываете всю строку, если больше нет пробелов...

mclayton 24.07.2024 12:50

@mclayton, спасибо за подсказку. Я мог бы даже использовать [Regex]::Replace($String, $String, $Length), который, по крайней мере, остановится после $Length замены.

iRon 24.07.2024 12:54

Нп. Обратите внимание, что это все равно может привести к дорогостоящему копированию строки в возвращаемое значение для оставшейся части строки, которую он оставляет «незамещенной». От того, какая производительность вам нужна, зависит, стоит ли накатывать что-то посложнее или нет.

mclayton 24.07.2024 12:58

вы не можете выйти из средства оценки соответствия регулярных выражений, лучшее, что вы можете сделать, очень похоже на то, что вы сделали бы в ForEach-Object, if ($imDoneReplacingCondition) { return }

Santiago Squarzon 24.07.2024 15:02

Я попробовал решение, в котором используется Match(String, Int32, Int32), и производительность странно низкая, если входная строка длинная - как будто оно что-то делает для сканирования всей строки, независимо от того, насколько коротко окно поиска. Например, попробуйте $regex = [regex] "\s+"; $text = "LOREM IPSUM" * 2000; $regex.Match($text, 0, 100), а затем измените * 2000 на * 20000000. Ручной анализ @SantiagoSquarzon работает намного лучше на очень длинных строках...

mclayton 24.07.2024 15:55
В чем разница между методом "==" и equals()
В чем разница между методом "==" и equals()
Это один из наиболее часто задаваемых вопросов новичкам на собеседовании. Давайте обсудим его на примере.
Замена символа по определенному индексу в JavaScript
Замена символа по определенному индексу в JavaScript
В JavaScript существует несколько способов заменить символ в строке по определенному индексу.
4
6
99
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы не можете.
Ваш лучший способ повысить эффективность (и я не уверен, насколько сильно) — это сначала сократить исходную строку до подстроки, потому что вы уже знаете, что в любом случае собираетесь уменьшить ее размер, поэтому нет смысла разрабатывать файл размером 10 МБ, если вы только в конечном итоге понадобятся первые 100 КБ.

Что-то вроде ($Text.Substring(0, $Length * 2) -replace '\ +', ' ').Substring(0, $Lenght)
Я использовал $Length * 2, но вы можете использовать любое измерение, которое захотите, в зависимости от того, сколько пробелов вы реально ожидаете в исходной (под) строке.
Я думаю, что-нибудь от $Length * 1.25 и выше должно быть достаточно

Ответ принят как подходящий

Возможно, более ручной подход быстрее, чем попытка сделать это с помощью регулярного выражения, конечно, это намного больше кода.

$Text = @'
LOREM IPSUM
Lorem   Ipsum is
   simply dummy    text
'@

$Length = 32
$sb = [System.Text.StringBuilder]::new($Length)

foreach ($char in $Text.GetEnumerator()) {
    if ($sb.Length -eq $Length) {
        break
    }

    if ([char]::IsWhiteSpace($char)) {
        if (-not $prevSpace) {
            $sb = $sb.Append(' ')
        }

        $prevSpace = $true
        continue
    }

    $sb = $sb.Append($char)
    $prevSpace = $false
}

$sb.ToString()

Очень похожий подход с использованием String.Create , возможно, даже быстрее, но потребуется предварительная компиляция или Add-Type ее. Пример вы можете найти здесь.

Хороший. В качестве настройки производительности вы можете отслеживать позицию первого символа без пробелов в запуске, а затем использовать substring для копирования всего цикла, а не посимвольно в каждой итерации - хотя это колебания и обходные пути, поскольку это делает код немного более сложным. сложно, но если $Length большое, возможно, оно того стоит...

mclayton 24.07.2024 15:35

Кроме того, в классе пробелов - \s - есть несколько дополнительных символов - Learn.microsoft.com/en-us/dotnet/standard/base-types/…, если это имеет значение для ОП...

mclayton 24.07.2024 15:36

@mclayton не уверен, что использование подстроки повысит производительность, поскольку он уже обрабатывает символ за символом, а добавление к построителю строк уже очень эффективно. substring придется еще раз перебрать исходную строку, когда это уже происходит. что касается остальных символов пробелов, то есть табуляции, я не уверен, как iRon хочет с ними справиться. будем ждать его отзыва.

Santiago Squarzon 24.07.2024 15:41

Отлично, зашёл сюда, чтобы предложить то же самое ^_^ Кстати, вы можете упростить логику ветвления с помощью одного блока elseif ([char]::IsWhiteSpace()){ if (!$prevSpace){$sb.Append(' ')} $prevSpace = $true } посередине.

Mathias R. Jessen 24.07.2024 17:14

это намного лучше @MathiasR.Jessen! Спасибо

Santiago Squarzon 24.07.2024 17:24

@mclayton, Сантьяго, спасибо за ваши предложения и помощь, я поигрался с использованием [Regex]::match('\s+', $Index) и добавлением подстрок, но это не быстрее, к тому же результаты очень нестабильны. Я поместил эталон в отдельный ответ (вы можете использовать его в своем, тогда я удалю свой ответ). Точка безубыточности при общем подходе, как в вопросе, составляет размер текста в 3000 символов. Это означает, что я, вероятно, реализую гибридное решение, то есть: if ($Text.Length -gt 3000) { <# use your solution #> }

iRon 24.07.2024 23:45

@iRon Я также сделал несколько скамеек, чтобы посмотреть, насколько хорошо string.Create работает (спойлер, чертовски хорошо). подход с использованием регулярных выражений, безусловно, лучше подходит для небольших строк. добавил скамейки в ту же суть: gist.github.com/santisq/…

Santiago Squarzon 25.07.2024 00:10

Тест:

$Length = 32

$Sizes = 50, 100, 200, 400, 800, 1600 # words
$Strings = @(
    foreach ($Size in $Sizes) {
        -Join @('Word ') * $Size
    }
)

$Iterations = 1000

@(
    $Results = [Ordered]@{ Name = 'Question' }
    for ($i = 1; $i -le $Iterations; $i++) {
        foreach ($String in $Strings) {
            $Results["$($String.Length)"] += (Measure-Command {
                $Text = $String -Replace '\s+', ' '
                if ($Text.Length -gt $Length) { $Text = $Text.SubString(0, $Length) }
                $Void = $Text
            }).TotalMilliseconds
        }
    }
    $Void.Length | Should -be $Length
    [PSCustomObject]$Results

    $Results = [Ordered]@{ Name = 'Santiago' }
    for ($i = 1; $i -le $Iterations; $i++) {
        foreach ($String in $Strings) {
            $Results["$($String.Length)"] += (Measure-Command {
                $sb = [System.Text.StringBuilder]::new($Length)

                foreach ($char in $Text.GetEnumerator()) {
                    if ($sb.Length -eq $Length) {
                        break
                    }

                    if ([char]::IsWhiteSpace($char)) {
                        if (-not $prevSpace) {
                            $sb = $sb.Append(' ')
                        }

                        $prevSpace = $true
                        continue
                    }

                    $sb = $sb.Append($char)
                    $prevSpace = $false
                }

                $Void = $sb.ToString()
            }).TotalMilliseconds
        }
    }
    $Void.Length | Should -be $Length
    [PSCustomObject]$Results
) | Format-Table
Name       250   500  1000  2000  4000   8000
----       ---   ---  ----  ----  ----   ----
Question 28.15 12.18 20.19 35.81 68.50 129.03
Santiago 79.05 57.73 54.25 55.33 54.73  54.12

Другие вопросы по теме

Я не могу извлечь таблицы .sql CREATE TABLE для каждой таблицы в базе данных SQL Azure в репозитории Azure
Измените OuterXml данного узла, чтобы сохранить его в файле, но сделайте узел бесполезным
Возникает ошибка при попытке отключить конвейер Azure DevOps с помощью Powershell
Как просмотреть все видеофайлы (вид: видео) независимо от того, какое расширение имеют эти файлы (может быть .mkv/.mp4/.mov и т. д.)?
Как преобразовать вывод команды так, чтобы в powershell сохранялись только последние 1000 строк
Get-MgGroupMember по отображаемому имени вместо userID
Привести к сбою конвейера, если в журнале появляется определенное предложение. Найдите подстроку, но игнорируйте определенную подстроку
Родительская ссылка Azure DevOps REST API на существующую ошибку рабочего элемента
Сделать конвейер сбоем, если в журнале появляется определенное предложение
Извлечение имени и адреса электронной почты менеджеров пользователей AD в существующий сценарий