Боже мой, это полно пробелов!

42

Некоторые люди настаивают на использовании пробелов для табуляции и отступов.

Для подведения итогов, это бесспорно так. По определению табуляторы должны использоваться для табулирования.

Даже для отступов табуляторы объективно превосходят:

  • В сообществе Stack Exchange существует явный консенсус .

  • Использование одного пробела для отступа визуально неприятно; использование более чем одного расточительно.

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

  • Регулируя ширину вкладки 1 , один и тот же файл выглядит по-разному на каждом компьютере, поэтому каждый может использовать свою любимую ширину отступа, не изменяя фактический файл.

  • Все хорошие текстовые редакторы используют табуляторы по умолчанию (и определение).

  • Я так говорю и я всегда прав!

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

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

задача

Напишите программу или функцию, которая выполняет следующее:

  1. Прочитать одну строку из STDIN или в качестве аргумента командной строки или функции.

  2. Определите все места, где пробелы были использованы для табуляции или отступа.

    Пробел - это отступ, если он происходит в начале строки.

    Выполнение двух или более пробелов является табуляцией, если это не отступ.

    Единое пространство , которое не отступ может или не может быть использовано для подведения итогов. Как и ожидалось, когда вы используете один и тот же символ для разных целей, нет простого способа узнать. Поэтому скажем, что это место было использовано для путаницы .

  3. Определите максимально возможную ширину табуляции 1, для которой все пробелы, используемые для табуляции или отступа, можно заменить табуляторами, не изменяя внешний вид файла.

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

  4. Используя ранее определенную ширину табуляции, замените все пробелы, используемые для табуляции или отступа, табуляторами.

    Кроме того, по возможности, не изменяя внешний вид файла, замените все пробелы, используемые для путаницы, табуляторами. (Если сомневаетесь, избавьтесь от пробелов.)

  5. Верните измененную строку из вашей функции или распечатайте ее в STDOUT.

Примеры

  • Все пространства

    a    bc   def  ghij
    

    Табулирование

    Каждый цикл пробелов дополняет предыдущую строку непробельных символов шириной 5, поэтому правильная ширина табуляции равна 5, а правильный вывод 2 равен

    a--->bc-->def->ghij
    
  • Первые два пространства

    ab  cde f
    ghi jk lm
    

    Табулирование, другие путаницы.

    Правильная ширина вкладки 4, так что правильный выход 2 является

    ab->cde>f
    ghi>jk lm
    

    Последний пробел остается нетронутым, так как он будет представлен как два пробела, если заменить его табулятором:

    ab->cde>f
    ghi>jk->lm
    
  • Все, кроме одного пробела

    int
        main( )
        {
            puts("TABS!");
        }
    

    отступы, другая путаница.

    Уровни отступов - 0, 4 и 8 пробелов, поэтому правильная ширина табуляции равна 4, а правильный вывод 2 равен

    int
    --->main( )
    --->{
    --->--->puts("TABS!");
    --->}
    

    Пробел в ( )будет представлен как три пробела, если заменить его табулятором, поэтому он останется нетронутым.

  • Первые два пространства

      x yz w
    

    отступы, другие путаница.

    Правильная ширина вкладки равна 2, а правильный вывод 2 равен

    ->x>yz w
    

    Последний пробел будет представлен как два пробела, если он будет заменен табулятором, поэтому он останется нетронутым.

  • Первые два пространства

      xy   zw
    

    отступы, остальные три - табуляция.

    Только ширина вкладка 1 позволяет устранить все пробелы, поэтому правильный выход 2 является

    >>xy>>>zw
    
  • Все пространства

    a b c d
    

    путаница

    Там нет длинной возможной ширины вкладки, так что правильный выход 2 является

    a b c d
    

Дополнительные правила

  • Ввод будет полностью состоять из печатных символов ASCII и перевода строки.

  • Вы можете предположить, что текст содержит не более 100 строк и не более 100 символов в каждой строке.

  • Если вы выберете STDOUT для вывода, вы можете распечатать один завершающий перевод строки.

  • Применяются стандартные правила .


1 Ширина табуляции определяется как расстояние в символах между двумя последовательными остановками табуляции с использованием моноширинного шрифта.
2 Стрелки ASCII представляют табуляторы, которые Stack Exchange отказывается отрисовывать должным образом, для которых я представил отчет об ошибке. Фактический вывод должен содержать фактические табуляторы.

Деннис
источник
9
+1 за окончательное решение этой бессмысленной проблемы
пробелов и пробелов
2
programs should be as short as possibleЯ считаю, что нашел давно потерянного брата Артура Уитни !!
kirbyfan64sos
13
Вкладки - это нечестивые демоны, которые заслуживают того, чтобы их биты были разорваны на части, а их код ASCII опозорен до тех пор, пока их некомпетентный недостаток души полностью не превратился в мякоть. Эррр, я имею в виду +1, хороший вызов, хотя он пахнет богохульством. ;)
Ручка двери
1
Я плакал каждый раз, когда коллега добавляет вкладку в мой красивый пробел с отступом. Затем я обнаружил CTRL + K + F в Visual Studio. Я делаю это каждый раз, когда открываю измененный файл. Моя жизнь стала лучше
Майкл М.

Ответы:

5

Pyth, 102 103 байта

=T|u?<1hHiGeHGsKmtu++J+hHhGlhtH+tG]+HJ.b,YN-dk<1u+G?H1+1.)Gd]0]0cR\ .zZ8VKVNp?%eNT*hNd*/+tThNTC9p@N1)pb

Попробуйте онлайн

Интересная идея, но поскольку вкладки на входе нарушают концепцию, не очень удобны в использовании.

Редактировать: Исправлена ​​ошибка. большое спасибо @aditsu

Брайан Так
источник
Это сбой на "ABC D"
Aditsu
@aditsu дерьмо! Спасибо за хедз-ап. Мне нужны лучшие тестовые случаи: P
Брайан Так
5

PowerShell, 414 409 байт

function g($a){if($a.length-gt2){g $a[0],(g $a[1..100])}else{if(!$a[1]){$a[0]}else{g $a[1],($a[0]%$a[1])}}}{$a[0]}else{g $a[1],($a[0]%$a[1])}}}
$b={($n|sls '^ +|(?<!^)  +' -a).Matches}
$n=$input-split"`n"
$s=g(&$b|%{$_.Index+$_.Length})
($n|%{$n=$_
$w=@(&$b)
$c=($n|sls '(?<!^| ) (?! )'-a).Matches
$w+$c|sort index -d|%{$x=$_.Index
$l=$_.Length
if($s-and!(($x+$l)%$s)){$n=$n-replace"(?<=^.{$x}) {$l}",("`t"*(($l/$s),1-ge1)[0])}}
$n})-join"`n"

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

Как выполнить

Скопируйте код в SpaceMadness.ps1файл, затем передайте ввод в скрипт. Я предполагаю, что файл, который нужно преобразовать, называется taboo.txt:

Из PowerShell:

cat .\taboo.txt | .\SpaceMadness.ps1

Из командной строки:

type .\taboo.txt | powershell.exe -File .\SpaceMadness.txt

Я протестировал его с PowerShell 5, но он должен работать на 3 или выше.

тестирование

Вот быстрый скрипт PowerShell, который полезен для тестирования выше:

[CmdletBinding()]
param(
    [Parameter(
        Mandatory=$true,
        ValueFromPipeline=$true
    )]
    [System.IO.FileInfo[]]
    $File
)

Begin {
    $spaces = Join-Path $PSScriptRoot SpaceMadness.ps1
}

Process {
     $File | ForEach-Object {
        $ex = Join-Path $PSScriptRoot $_.Name 
        Write-Host $ex -ForegroundColor Green
        Write-Host ('='*40) -ForegroundColor Green
        (gc $ex -Raw | & $spaces)-split'\r?\n'|%{[regex]::Escape($_)} | Write-Host -ForegroundColor White -BackgroundColor Black
        Write-Host "`n"
    }
}

Поместите это в тот же каталог SpaceMadness.ps1, в котором я его называю tester.ps1, назовите так:

"C:\Source\SomeFileWithSpaces.cpp" | .\tester.ps1
.\tester.ps1 C:\file1.txt,C:\file2.txt
dir C:\Source\*.rb -Recurse | .\tester.ps1

Вы поняли идею. Он выкладывает содержимое каждого файла после конвертации, прогон [RegEx]::Escape()которого проходит через пробелы и табуляции, поэтому очень удобно видеть, что на самом деле было изменено.

Вывод выглядит так (но с цветами):

C:\Scripts\Powershell\Golf\ex3.txt
========================================
int
\tmain\(\ \)
\t\{
\t\tputs\("TABS!"\);
\t}

объяснение

Самая первая строка определяет наибольший общий множитель / функцию делителя gнастолько кратко, насколько мне удалось, которая принимает массив (произвольное число чисел) и рекурсивно вычисляет GCD, используя евклидов алгоритм .

Цель этого заключалась в том, чтобы выяснить «максимально возможную ширину табуляции», взяв индекс + длину каждого отступа и табуляции, как определено в вопросе, а затем подав его в эту функцию, чтобы получить GCD, который, я думаю, является наилучшим из возможных. сделать для ширины вкладки. Длина путаницы всегда будет равна 1, поэтому она ничего не вносит в этот расчет.

$bопределяет блок скриптов, потому что, к сожалению, мне нужно дважды вызывать этот фрагмент кода, поэтому я сохраняю несколько байтов таким образом. Этот блок принимает строку (или массив строк) $nи запускает регулярное выражение в ней ( slsили Select-String), возвращая совпадающие объекты. На самом деле я получаю здесь и отступы, и таблицы в одном, что действительно сэкономило мне дополнительную обработку, захватив их отдельно.

$nиспользуется для разных вещей внутри и вне основного цикла (очень плохо, но необходимо здесь, чтобы я мог встроить его в $bскрипт-блок и использовать его как внутри, так и снаружи цикла без длинного param()объявления и передачи аргументов.

$sполучает назначенную ширину табуляции, вызывая $bблок массива строк во входном файле, затем суммируя индекс и длину каждого совпадения, возвращая массив сумм в качестве аргумента в функцию GCD. Так $sчто размер нашей вкладки теперь останавливается.

Затем цикл начинается. Мы перебираем каждую строку в массиве входных строк $n. Первое, что я делаю в цикле, это присваиваю $n(локальная область) значение текущей строки по вышеуказанной причине.

$w получает значение вызова scriptblock только для текущей строки (отступы и табуляции для текущей строки).

$cполучает аналогичное значение, но вместо этого мы находим все путаницы .

Я складываю $wи $cпредставляю собой массивы, давая мне один массив со всеми необходимыми совпадениями пробелов sortв порядке убывания по индексу и начинаю итерацию по каждому совпадению для текущей строки.

Сортировка важна. Ранее я обнаружил, что замена частей строки на основе значений индекса - плохая идея, когда строка замены меньше и меняет длину строки! Другие индексы становятся недействительными. Поэтому, начиная с наивысших индексов в каждой строке, я проверяю, чтобы строка была только короче с конца и перемещалась назад, чтобы индексы всегда работали.

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

В !(($x+$l)%$s)этом есть единственная точка, где я проверяю, следует ли заменить путаницу вкладкой или нет. Если индекс плюс длина, разделенная на ширину табуляции, не имеют остатка, то лучше заменить это совпадение на табуляцию (эта математика всегда будет работать с отступами и табуляциями , потому что их размер определяет ширину табуляции). начать с).

Для замены каждая итерация цикла соответствия работает с текущей строкой ввода, поэтому это совокупный набор замен. Регулярное выражение просто ищет $lпробелы, которым предшествует $xлюбой символ. Мы заменяем его $l/$sсимволами табуляции (или 1, если это число меньше нуля).

Эта часть (($l/$s),1-ge1)[0]- причудливый замысловатый способ сказать if (($l/$s) -lt 0) { 1 } else { $l/$s }или альтернативно [Math]::Max(1,($l/$s)). Он создает массив $l/$sи 1, затем использует -ge 1для возврата массив, содержащий только элементы, которые больше или равны единице, затем берет первый элемент. Он поставляется на несколько байт короче, чем [Math]::Maxверсия.

Поэтому, как только все замены выполнены, текущая строка возвращается из итерации ForEach-Object( %), а когда все они возвращаются (массив фиксированных строк), она -joinредактируется с помощью новых строк (так как мы разбиваем на новые строки в начале).

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

Вкладки 4 лайф

briantist
источник
4

PHP - 278 210 байт

Функция работает путем проверки ширины каждой вкладки, начиная со значения 100, максимальной длины строки и, следовательно, максимальной ширины вкладки.

Для каждой ширины вкладки мы разбиваем каждую строку на «блоки» этой длины. Для каждого из этих блоков:

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

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

function($s){for($t=101;--$t;){$c='';foreach(split('
',$s)as$l){$e='';foreach(str_split($l,$t)as$b){if(ereg('  [^ ]',$e.$b))continue 3;$c.=($e=substr($b,-1))==' '?rtrim($b).'   ':$b;}$c.='
';}return$c;}return$s;}

Вот негольфированная версия:

function convertSpacesToTabs($string)
{
    for ($tabWidth = 100; $tabWidth > 0; --$tabWidth)
    {
        $convertedString = '';
        foreach (explode("\n", $string) as $line)
        {
            $lastCharacter = '';
            foreach (str_split($line, $tabWidth) as $block)
            {
                if (preg_match('#  [^ ]#', $lastCharacter.$block))
                {
                    continue 3;
                }

                $lastCharacter = substr($block, -1);
                if ($lastCharacter == ' ')
                {
                    $convertedString .= rtrim($block) ."\t";
                }
                else
                {
                    $convertedString .= $block;
                }
            }

            $convertedString .= "\n";
        }

        return $convertedString;
    }

    return $string;
}

Отдельное спасибо DankMemes за сохранение 2 байта.

Черная дыра
источник
1
Вы можете сохранить 2 байта, используя for($t=101;--$t;)вместоfor($t=100;$t;--$t)
DankMemes
4

CJam, 112

qN/_' ff=:e`{0:X;{_0=X+:X+}%}%_:+{~;\(*},2f=0\+{{_@\%}h;}*:T;\.f{\~\{@;1$({;(T/)9c*}{\;T{T%}&S9c?}?}{1$-@><}?}N*

Попробуйте онлайн

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

Объяснение:

qN/          read input and split into lines
_            duplicate the array (saving one copy for later)
' ff=        replace each character in each line with 0/1 for non-space/space
:e`          RLE-encode each line (obtaining chunks of spaces/non-spaces)
{…}%         transform each line
  0:X;       set X=0
  {…}%       transform each chunk, which is a [length, 0/1] array
    _0=      copy the first element (the length)
    X+:X     increment X by it
    +        and append to the array; this is the end position for the chunk
_            duplicate the array (saving one copy for later)
:+           join the lines (putting all the chunks together in one array)
{…},         filter the array using the block to test each chunk
  ~          dump the chunk (length, 0/1, end) on the stack
  ;          discard the end position
  \(         bring the length to the top and decrement it
  *          multiply the 2 values (0/1 for non-space/space, and length-1)
              the result is non-zero (true) iff it's a chunk of at least 2 spaces
2f=          get all the end positions of the multiple-space chunks
0\+          prepend a 0 to deal with the empty array case
{…}*         fold the array using the block
  {_@\%}h;   calculate gcd of 2 numbers
:T;          save the resulting value (gcd of all numbers) in variable T
\            swap the 2 arrays we saved earlier (input lines and chunks)
.f{…}        for each chunk and its corresponding line
  \~         bring the chunk to the top and dump it on the stack
              (length, 0/1, end position)
  \          swap the end position with the 0/1 space indicator
  {…}        if 1 (space)
    @;       discard the line text
    1$(      copy the chunk length and decrement it
    {…}      if non-zero (multiple spaces)
      ;      discard the end position
      (T/)   divide the length by T, rounding up
      9c*    repeat a tab character that many times
    {…}      else (single space)
      \;     discard the length
      T{…}&  if T != 0
        T%   calculate the end position mod T
      S9c?   if non-zero, use a space, else use a tab
    ?        end if
  {…}        else (non-space)
    1$-      copy the length and subtract it from the end position
              to get the start position of the chunk
    @>       slice the line text beginning at the start position
    <        slice the result ending at the chunk length
              (this is the original chunk text)
  ?          end if
N*           join the processed lines using a newline separator
aditsu
источник
1

PowerShell , 165 160 153 152 142 138 137 байт

param($s)@((0..99|%{$s-split"(
|..{0,$_})"-ne''-replace(' '*!$_*($s[0]-ne32)+' +$'),"`t"-join''})-notmatch'(?m)^ |\t '|sort{$_|% Le*})[0]

Попробуйте онлайн!

Менее гольф:

param($spacedString)

$tabed = 0..99|%{
    $spacedString `
        -split "(\n|..{0,$_})" -ne '' `
        -replace (' '*!$_*($spacedString[0]-ne32)+' +$'),"`t" `
        -join ''
}

$validated = $tabed -notmatch '(?m)^ |\t '

$sorted = $validated|sort{$_|% Length}    # sort by a Length property

@($sorted)[0]  # $shortestProgram is an element with minimal length
Mazzy
источник