Click here to Skip to main content
15,880,405 members
Please Sign up or sign in to vote.
5.00/5 (4 votes)
See more:
Your coding challenge of the week is a simple one. Or so it seems.

Given a text string, convert the tabs to spaces. Assuming tab markers are 4 characters apart

-text
[ ][ ][tab]Hello, world!

becomes
-text
[ ][ ][ ][ ]Hello, World!


Where [ ] = space character, [tab] = tab character (I chose to use these instead of " " and " " (or "\t") to make it easier to spot the spaces and tabs.)

Another test case is left as an exercise for the reader.
-text
[ ][ ][ ][tab][tab][ ][tab]Hello, world!



Again: A T-shirt awarded for audacity, for style, for outrageous use of inappropriate languages. Mostly for the entry that entertains us the most.

Last week's challenge Coding challenge: bad word filter[^] was won by PIEBALDConsult under the "God I miss coding" category. Send Sean @codeprject your address and T-shirt size and a Christmas present will come your way.

What I have tried:

To remain calm, mostly.

Got a coding challenge idea? Send it to me at chris@codeproject.com.
Posted
Updated 6-Dec-16 21:24pm
v3
Comments
Afzaal Ahmad Zeeshan 1-Dec-16 17:27pm    
Do you mean tab spaces, or just tab as in character?
CHill60 2-Dec-16 4:45am    
"outrageous use of inappropriate languages" ... darn it, I was going to provide a VB6 solution then remembered I destroyed the install CDs in a ritual bonfire last Samhain
Graeme_Grant 2-Dec-16 7:04am    
Any reason you are converting words to Title Case as well???
PIEBALDconsult 2-Dec-16 8:49am    
Been there, done that. I'll resurrect some ancient code.

And don't forget this little beauty...
https://www.codeproject.com/Articles/32593/Untabify-and-Tabify

Patrice T 3-Dec-16 15:28pm    
I think some solutions will fail with:
"[ ][ ][ ][ ][ ][tab]Hello, world!"

Page 1 of 2


Brainfuck


Well, that was fun :)
>,[>+++++++++<[>->+<<-]>[>-------------------------------->-------------------------------->]<++++++++++++++++++++++++++++++++[.--------------------------------]<++++++++++++++++++++++++++++++++.>>>,]

To run this, paste the code in this online interpreter[^], fill in your input in the "input" field and click "execute".

So what's actually going on here? Here is the code explained (while Brainfuck ignores characters that it doesn't know, the code below isn't runnable because the commentary contains some chars that are valid in Brainfuck).
>  # Jump a cell ahead. We'll need the first cell later.
,  # Find a char in the input.
[  # If there is no input, jump past the matching ]. Here that ends the program.

  >  # Jump another cell ahead.
  +++++++++  # Put the value 9 in this cell.

  <  # Jump one cell back (to our input cell).
  [>->+<<-]  # While this cell is not 0, do these things: a) decrement the value at the next cell. b) increment the value two cells ahead. c) decrement the value of our input cell

  # Now the cell that originally contained our input is 0, the cell ahead of that one is non-zero if the input is no tab, zero if it is a tab (I'll call this the "signal cell"), and the cell ahead of THAT one is a copy of our original input.

  >  # Jump to the signal cell.
  [  # If the cell is zero, jump to the matching ], otherwise continue.
    >--------------------------------  # Jump to our input copy and decrease the value by 32.
    >--------------------------------  # Jump another cell ahead and do the same. (This cell starts at 0 and won't end up at -32 but 224 because of underflow. It doesn't matter though.
    >  # Jump to the next cell, to be sure that this one is zero and we are not stuck in a loop.
  ]  # If the current cell value is zero (which it is!), just continue the program (otherwise go back to the matching [).

  <  # Jump back a cell. If our input was a tab, this new cell will be the cell we skipped in the beginning. If it wasn't a tab, it will be our "224 cell".
  ++++++++++++++++++++++++++++++++  # Increase by 32. In case of a tab, this will be 32, otherwise 0.
  [.--------------------------------]  # If the value is not 0 (then it must be a space), print it and make sure it only gets called once. This makes sure that a tab gets replaced with TWO spaces as the challenge requires.

  <  # Jump back a cell. If our input was a tab this will be 0, otherwise the original char minus 32.
  ++++++++++++++++++++++++++++++++  # Increase by 32.
  .  # Print.

>>>  # Jump three cells ahead, so we don't have to worry about our past cells. Here we also keep our "spare cell" in mind like in the beginning of the program.
,  # Look for a char in the input.
]  # Is it 0? No input anymore and close the program. Not 0? Go back to the first [.
 
Share this answer
 
v2
Comments
Chris Maunder 1-Dec-16 18:28pm    
-2 points for using the dumbest language ever. +4 points for the pain involved.
Matthew Dennis 5-Dec-16 17:23pm    
actually probably not in the top 10 dumbest languages. Try Malbolge https://en.wikipedia.org/wiki/Malbolge. I'm not even going to attempt it.
Maybe I took things too literally.

C#
using System;
using System.Collections.Generic;
using System.Linq;

namespace Test
{
    public class TabConversion
    {              
        public static void Main(string[] args)
        {
            string literalTest1 = "[ ][ ][tab]Hello, world!";
            string literalTest2 = "[ ][ ][ ][tab][tab][ ][tab]Hello, world!";

            DoTests(new string[] { literalTest1, literalTest2 }, "[tab]", "[ ][ ]");
            DoTests(new string[] { 
                literalTest1.Replace("[tab]", "\t").Replace("[ ]"," "), 
                literalTest2.Replace("[tab]", "\t").Replace("[ ]"," ") }, 
                "\t", "  ");
        }

        // This is probably the fastest way
        public static string ReplaceTabs(string text, string tabText, string spaceText)
        {
            return text.Replace(tabText, spaceText);
        }

        // This is a bad way, but hey, it works
        public static string ReplaceTabsBySplitting(string text, string tabText, string spaceText)
        {
            var splits = text.Split(new string[] { tabText }, StringSplitOptions.None);
            if (splits.Length > 1)
            {
                return string.Join(spaceText, splits);
            }
            else
            {
                return text;
            }
        }

        // What is this I don't even
        public static string ReplaceTabsByLinq(string text, string tabText, string spaceText)
        {
            List<string> t = new List<string>();
            text.Split(new string[] { tabText }, StringSplitOptions.None).ToList().ForEach(s => { t.Add(s); t.Add(tabText); });
            string result = string.Join("", t.Select<string, string="">(s => s == tabText ? spaceText : s).ToArray<string>().Reverse().Skip(1).Reverse());
            return result;
        }

        // Felt bad about replacing the tabs.  Tab lives matter!
        public static string UnreplaceTabs(string text, string tabText, string spaceText)
        {
            return text.Replace(spaceText, tabText);
        }

        // DRY
        public static void DoTests(string[] tests, string tabText, string spaceText)
        {
            foreach (string test in tests)
            {
                Console.WriteLine("-----------------------------");
                Console.WriteLine("Original\t\"" + test + "\"");
                Console.WriteLine();
                Console.WriteLine("string.Replace\t\"" + ReplaceTabs(test, tabText, spaceText) + "\"");
                Console.WriteLine("string.Split\t\"" + ReplaceTabsBySplitting(test, tabText, spaceText) + "\"");
                Console.WriteLine("Using LINQ\t\"" + ReplaceTabsByLinq(test, tabText, spaceText) + "\"");
                Console.WriteLine("Space->Tab\t\"" + UnreplaceTabs(test, tabText, spaceText) + "\"");
                Console.WriteLine();
            }
        }
    }
}


Could probably get the LINQ method to a single line, but out of time for now.
 
Share this answer
 
v2
Thought I'd give this a go, but alas my solution seems too sensible for this challenge... :D

Using C# enumerables and yield...
C#
public static partial class Extensions
{
    public static string ToTabified(this string input, int markerSpacing = 4)
    {
        return new string(tabEnumerable(input, markerSpacing).ToArray());
    }

    private static IEnumerable<char> tabEnumerable(
        IEnumerable<char> input,
        int markerSpacing)
    {
        var n = 0;
        using (var e = input.GetEnumerator())
            while (e.MoveNext())
            {
                if (e.Current == '\t')
                {
                    // at least one
                    yield return ' ';
                    n++;

                    // then fill to next marker
                    while (n % markerSpacing != 0)
                    {
                        yield return ' ';
                        n++;
                    }
                }
                else
                {
                    yield return e.Current;
                    n++;
                }
            }
    }
}
 
Share this answer
 
This is fun. Must support and keep it going.
Assuming "every single tab is replaced with two spaces". A quick one in Python again.
Python
"""
tabskiller.py
by Peter Leow the tabs assassin

"""

import re

def convertTabs2Spaces(m):
    # return 2 spaces to replace the spotted tab
    return " "*2

def main():
    # [ ][ ][tab]Hello, world!
    originalSentence = '  	Hello, world!'
    print('Testing 1,2,3...')
    print('Before:')
    print(originalSentence)
    print('After:')
    
    # tab in regex
    patternTab=r'\t'

    convertedSentence = re.sub(patternTab, convertTabs2Spaces, originalSentence)
    print(convertedSentence)

    print('UAT! Use Ctrl+Tab to enter a tab in the IDLE shell. Hit Enter-only to end. Enjoy...')

    while True:
        yourSentence = input('Input a sentence mixed with tabs and spaces:\n')
        if yourSentence == '':
            break
        convertedSentence = re.sub(patternTab, convertTabs2Spaces, yourSentence)
        print(convertedSentence)
  
main()

The output:
Testing 1,2,3...
Before:
  	    Hello, world!
After:
    Hello, world!
UAT! Use Ctrl+Tab to enter a tab. Hit Enter-only to end. Enjoy...
Input a sentence mixed with tabs and spaces:
			                Hello, world!
          Hello, world!
Input a sentence mixed with tabs and spaces:

Run it on a local IDLE not those online versions.
If I can find time, may come up another one using other languages. No promise though.
++++++++++++++++++++++++++++++++++++++
As promised, here comes the C# version:
C#
/* 
 * TabsKiller.cs
 * by Peter Leow the tabs assassin
 */

using System;
using System.Text;

class TabsKiller
{
    static void Main(string[] args)
    {
        Console.WriteLine("Testing 1,2,3...");
        Console.WriteLine("Before");
        // [ ][ ][tab]Hello, world!
        string originalSentence = "  	Hello, world!";
        Console.WriteLine(originalSentence);

        Console.WriteLine("After");
        string convertedSentence = convertTabs2Spaces(originalSentence);
        Console.WriteLine(convertedSentence);

        Console.WriteLine("UAT! Hit Enter-only to end. Enjoy...");
        while (true)
        {
            Console.WriteLine("Input a sentence mixed with tabs and spaces:");
            string yourSentence = Console.ReadLine();
            if (yourSentence == string.Empty)
            {
                break;
            }
            Console.WriteLine(convertTabs2Spaces(yourSentence));
        }
    }

    static string convertTabs2Spaces(string sentence)
    {
        StringBuilder sb = new StringBuilder(sentence);
        // replace each tab with 2 spaces
        sb.Replace("\t", "  ");
        return sb.ToString();
    }
}
 
Share this answer
 
v15
Just wanted to show that - even though many people say regex is so cool and elegant (???) - regex is sooo slow :-)

C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace CodingFromTheField.NukeStrings
{
    class Program
    {        
        static void Main(string[] args)
        {
            string input = "   \t\t \tHello, world!";

            Nuker nu = new Nuker();

            nu.NukeThis(input, "\t", "    ");

            input.WaitForUser();
        }
    }

    public class Nuker
    {
        public string OrgString { get; set; }

        protected Stopwatch Timer { get; set; }

        protected long RT1 { get; set; }

        protected long RT2 { get; set; }

        protected const int Iterations = 10000;

        protected Dictionary<string, string> Escaper = new Dictionary<string, string> { { " ", "[ ]" }, { "\t", "[tab]" } };

        public Nuker() { }

        public string NukeThis(string input, string replace, string replacement)
        {
            string ret = input;

            this.OrgString = ret;

            ret = Measure1(input, replace, replacement);

            Console.WriteLine("Measure1: \"{0}\".Replace(\"{1}\", \"{2}\") -> {3} ms ({4} iterations)",
                              input.Escape(Escaper), replace.Escape(Escaper), replacement.Escape(Escaper), this.RT1, Iterations);

            Console.WriteLine("\"{0}\" nuked to \"{1}\"", input.Escape(Escaper), ret.Escape(Escaper));

            Measure2(input, replace, replacement);
            
            Console.WriteLine("Measure1: Regex.Replace(\"{0}\", \"{1}\", \"{2}\") -> {3} ms ({4} iterations)",
                              input.Escape(Escaper), replace.Escape(Escaper), replacement.Escape(Escaper), this.RT2, Iterations);

            Console.WriteLine("\"{0}\" nuked to \"{1}\"", input.Escape(Escaper), ret.Escape(Escaper));

            long fact = this.RT2 / this.RT1;

            Console.WriteLine("String.Replace is ~{0} times {1} than Regex.Replace!", fact, (fact > 1) ? "faster" : "slower" );

            return ret;
        }

        protected string Measure1(string input, string replace, string replacement)
        {
            string ret = input;

            StartTimer();

            for (int cnt = 0; cnt <= Iterations; cnt++)
            { ret = input.Replace(replace, replacement); }

            this.RT1 = StopTimer();

            return ret;
        }

        protected string Measure2(string input, string replace, string replacement)
        {
            string ret = input;

            StartTimer();

            replace = Regex.Escape(replace);

            for (int cnt = 0; cnt <= Iterations; cnt++)
            { ret = Regex.Replace(input, replace + "[0-9]", replacement, RegexOptions.IgnoreCase); ; }

            this.RT2 = StopTimer();

            return ret;
        }

        protected void StartTimer()
        {
            if (this.Timer == null)
            { this.Timer = new Stopwatch(); }

            else
            { this.Timer.Reset(); }

            this.Timer.Start();
        }

        protected long StopTimer()
        {
            long ret = 0;

            if (this.Timer.IsRunning)
            {
                ret = this.Timer.ElapsedMilliseconds;

                this.Timer.Reset();
            }

            return ret;
        }
    }

    public static class ExtensionMethods
    {
        public static void WaitForUser(this object value)
        { Console.WriteLine("\r\npress any key to exit"); Console.ReadKey(); }

        public static string Escape(this string value, Dictionary<string, string> escaper)
        {
            string ret = value;

            foreach (KeyValuePair<string, string> kvp in escaper)
            { ret = ret.Replace(kvp.Key, kvp.Value); }

            return ret;
        }
    }
}
 
Share this answer
 
Comments
ScottM1 2-Dec-16 4:59am    
I think you may have read too much into it.
mfp labs 2-Dec-16 6:28am    
hehehe - as alwayws - code 'matures' while writing :-)
After starting with the regex implementation I thought, testing the performance diffs between direct string massage and regex would be a good idea.
Sharing the findings did sound like a good idea to me.

Btw. - Output
Measure1: "[ ][ ][ ][tab][tab][ ][tab]Hello,[ ]world!".Replace("[tab]", "[ ][ ][ ][ ]") -> 2 ms (10000 iterations)
"[ ][ ][ ][tab][tab][ ][tab]Hello,[ ]world!" nuked to "[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello,[ ]world!"

Measure1: Regex.Replace("[ ][ ][ ][tab][tab][ ][tab]Hello,[ ]world!", "[tab]", "[ ][ ][ ][ ]") -> 16 ms (10000 iterations)
"[ ][ ][ ][tab][tab][ ][tab]Hello,[ ]world!" nuked to "[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello,[ ]world!"

String.Replace is ~8 times faster than Regex.Replace!

press any key to exit

All the best
Michael
why not a VB.net one?

here:

VB
Module Module1

     Sub Main()
          Dim TestStrings As New List(Of String)
          TestStrings.Add("[ ][ ][tab]Hello, world!")
          TestStrings.Add("[ ][ ][ ][tab][tab][ ][tab]Hello, world!")

          Const TabStopAt As Integer = 4

          Console.WriteLine("Running Tap Replacement Tests...")
          Console.WriteLine()

          RunTests(TestStrings, TabStopAt)

          Console.WriteLine()
          Console.WriteLine("Press any Key to end this App")
          Console.ReadKey()
     End Sub


     Sub RunTests(Tests As List(Of String), TapStop As Integer)
          For Each Test As String In Tests
               Console.WriteLine("Performing new Test!")
               Console.WriteLine("#####################")
               Console.WriteLine("Original String: {0}", Test)
               Test = RemoveDisplayStrings(Test)
               Console.WriteLine("Cleared String : {0}", Test)
               Test = ReplaceTabs(Test, TapStop)
               Console.WriteLine("Replaced String: {0}", Test)
               Test = AddDisplayStrings(Test)
               Console.WriteLine("Debug String   : {0}", Test)
               Console.WriteLine("#####################")
               Console.WriteLine()
          Next
     End Sub


     Function ReplaceTabs(Original As String, TabStop As Integer) As String
          Dim this As New List(Of Char)
          For Each c As Char In Original.ToCharArray
               If c = ControlChars.Tab Then
                    this.AddRange(New String(" "c, GetDiffToTabMarker(this.Count, TabStop)).ToCharArray)
               Else
                    this.Add(c)
               End If
          Next

          Return New String(this.ToArray)
     End Function


     Function GetDiffToTabMarker(Length As Integer, TabStop As Integer) As Integer
          Dim this As Integer = Length Mod TabStop
          If this > 0 Then this = TabStop - this
          If this = 0 Then this = TabStop
          Return this
     End Function

     Function RemoveDisplayStrings(Original As String) As String
          Return Original.Replace("[ ]", " ").Replace("[tab]", ControlChars.Tab)
     End Function

     Function AddDisplayStrings(Original As String) As String
          Return Original.Replace(" ", "[ ]").Replace(ControlChars.Tab, "[tab]")
     End Function
End Module



Output:
Running Tap Replacement Tests...

Performing new Test!
#####################
Original String: [ ][ ][tab]Hello, world!
Cleared String :   	Hello, world!
Replaced String:     Hello, world!
Debug String   : [ ][ ][ ][ ]Hello,[ ]world!
#####################

Performing new Test!
#####################
Original String: [ ][ ][ ][tab][tab][ ][tab]Hello, world!
Cleared String :    		 	Hello, world!
Replaced String:             Hello, world!
Debug String   : [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello,[ ]world!
#####################


Press any Key to end this App
 
Share this answer
 
v2
Time for good old plain C:
C++
/* Replace tabs with spaces */
char *untab(int tabwidth, const char *src)
{
    int i, spaces;
    int pos = 0;
    int charpos = 0;
    char *dst;

    /* Allocate enough memory to untab a string containing only tabs */
    dst = (char *)malloc(tabwidth * strlen(src) + 1);
    while (*src)
    {
        switch (*src)
        {
        case '\t' :
            spaces = tabwidth - (linepos % tabwidth);
            for (i = 0; i < spaces; i++)
                dst[pos++] = ' ';
            charpos += spaces;
            break;
        case '\n' :
            charpos = -1;
        default :
            dst[pos++] = *src;
            ++charpos;
        }
        ++src;
    }
    dst[pos] = '\0';
    return dst;
}

Input / Output:
[ ][ ][tab]Hello,[ ]world!
[ ][ ][ ][ ]Hello,[ ]world!
    Hello, world!
[ ][ ][ ][tab][tab][ ][tab]Hello,[ ]world!
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello,[ ]world!
            Hello, world!


When there is only one output channel (e.g. a file ) the untab() function may be changed to process only single characters and print the output directly when making charpos static. This would avoid allocating memory for each line. But I choosed the above solution to support formatted output and as it is.


For those who would like to reproduce it (may be enhanced to support reading from file, stdin, pipes and output to file):
C++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Insert untab() from above here */

void print(const char *s)
{
    int newline = 0;
    while (*s)
    {
        switch (*s)
        {
            case '\t' : printf("[tab]"); break;
            case ' '  : printf("[ ]"); break;
            case '\n' : newline = 1;
            default   : putchar(*s);
        }
        ++s;
    }
    /* Using puts("") here to print a system dependant newline */
    if (!newline)
        puts("");
}

void test(int tabwidth)
{
    char *untabbed;
    const char *test1 = "  \tHello, world!";
    const char *test2 = "   \t\t \tHello, world!";

    untabbed = untab(tabwidth, test1);
    print(test1);
    print(untabbed);
    printf("%s", untabbed);
    puts("");
    free(untabbed);

    untabbed = untab(tabwidth, test2);
    print(test2);
    print(untabbed);
    printf("%s", untabbed);
    puts("");
    free(untabbed);
}

int main()
{
    test(4);
    return 0;
}
 
Share this answer
 
v3
C#
With 3 lines of code and 25 bytes 

******************** File a.lex ********************
%{
/* Thanks to CPallini for pointing out a bug
   my vote for a T-Shirt is for him  */
%}
%%
\t printf(" ");
%%
******************** end of a.lex ********************

Compile & execute
flex a.lex
gcc lex.yy.c -lfl
./a.out < samples

where samples is a file containing all sorts of test strings with tabs and or spaces

Make it more fancy by adding new pattern recognition
A special return code like on last like int main(){return(yylex());}
.....
and so on., kind of freak though, had'nt used lex since years
 
Share this answer
 
v2
Comments
CPallini 2-Dec-16 9:27am    
syntax is not 100% correct (comments should written %{ ... %})
However the major problem of your code, is that it replaces every instance of a tab with four space. This is not the requested behavior (as far as I understand).
lbesteban 2-Dec-16 12:59pm    
Yep, you're right, my mistake reading it
"Given a text string, convert the tabs to spaces. "

Proper solution is to print only one space. which reduces 3 bytes.

My bad, too anxious to see that actually something i had not used since left university in late 90's could be so quick to solve this.

******************** File a.lex ********************
%%
\t printf(" ");
%%
******************** end of a.lex ********************

And format is correct, this does not have comments

Yes, in flex you can have comments but a parser is defined as

[block of definitions]
%%
[block of token recognition]
%%
[a program logic]

Comments are like in any C language when enclosed in between %{ [...code lines... ]%}
CPallini 2-Dec-16 13:54pm    
Indeed lex & bison were (are) fascinating.
:-)
it won't get the tshirt but cannot do without a VB6 solution :-)

disappointed I didn't get a goto in to the answer

VB
Private Const TabStop = 4
Private Function TabExpand(strInput As String) As String
Dim i As Integer, col As Integer, tabFound As Integer, StartPos As Integer, Padlen As Integer
Dim strOutput As String
    StartPos = 1
    tabFound = InStr(StartPos, strInput, vbTab)
    If tabFound = 0 Then
        TabExpand = strInput
        Exit Function
    End If
    Do While tabFound > 0
        strOutput = strOutput & Mid(strInput, StartPos, tabFound - StartPos)
        Padlen = TabStop - (Len(strOutput) Mod TabStop)
        strOutput = strOutput & Space(Padlen)
        StartPos = tabFound + 1
        tabFound = InStr(StartPos, strInput, vbTab)
    Loop
    If StartPos < Len(strInput) Then
        strOutput = strOutput & Mid(strInput, StartPos)
    End If
    TabExpand = strOutput
End Function
 
Share this answer
 
using System.Text;
using NUnit.Framework;
namespace Code_Project_Challenge
{
    class TabConverter
    {
        static int tabLength = 4;
        public string Convert(string str)
        {
            var sb = new StringBuilder(str.Length);
            int charCount = 0, spacesToAdd;

            foreach (var c in str)
            {
                if (c == '\t')
                {
                    spacesToAdd = tabLength - (charCount % tabLength);
                    sb.Append(' ', spacesToAdd);
                    charCount += spacesToAdd;
                }
                else
                {
                    sb.Append(c);
                    charCount++;
                }
            }

            return sb.ToString();
        }
    }

    [TestFixture]
    public class TabConverterTest
    {
        [Test]
        [TestCase("  \tHello, world!", "    Hello, world!")]
        [TestCase("   \t\t \tHello, world!", "            Hello, world!")]
        public void Returns(string inStr, string expected)
        {
            var sut = new TabConverter();
            var result = sut.Convert(inStr);
            Assert.AreEqual(expected, result);
        }
    }
}
 
Share this answer
 
v2
Lua:
local function tab2blank(s)
  local n=1
  return s:gsub('(.-)\t', function(s) n = n + #s; b = 4 - (n-1) % 4; n = n + b; return s.. string.rep(' ', b) end)
end


Test program:
local function format(s)
  return s:gsub("([ \t])", function (p) return p == ' ' and '[ ]' or '[tab]' end )
end

local t = { "  \tHello, world!", "   \t\t \tHello, world!" }

local function tab2blank(s)
  local n=1
  return s:gsub('(.-)\t', function(s) n = n + #s; b = 4 - (n-1) % 4; n = n + b; return s.. string.rep(' ', b) end)
end

for k,s in ipairs(t) do
  print("item " .. k .. ":")
  print(format(s) .. " -> " .. format(tab2blank(s)))
end

Output:
item 1:
[ ][ ][tab]Hello,[ ]world! -> [ ][ ][ ][ ]Hello,[ ]world!
item 2:
[ ][ ][ ][tab][tab][ ][tab]Hello,[ ]world! -> [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello,[ ]world!
 
Share this answer
 
The code:

C#
static string ReplaceTabWithSpaces(string text)
{
	var spaceCount = 0;
	var builder = new StringBuilder();
	for (var pos = 0; pos < text.Length; pos++)
	{
		if (text[pos] == ' ')
		{
			spaceCount++;
			if (spaceCount == 4)
			{
				spaceCount = 0;
				builder.Append("    ");
			}
		}
		else if (text[pos] == '\t')
		{
			builder.Append("    ");
			spaceCount = 0;
		}
		else
		{
			if (spaceCount > 0)
			{
				builder.Append(string.Empty.PadLeft(spaceCount));
				spaceCount = 0;
			}
			builder.Append(text[pos]);
		}
	}
	if (spaceCount > 0)
	{
		builder.Append(string.Empty.PadLeft(spaceCount));
	}
	return builder.ToString();
}

static string Decode(string text)
{
	return text.Replace("[ ]", " ").Replace("[tab]", "\t");
}

static string Encode(string text)
{
	return text.Replace(" ", "[ ]").Replace("\t", "[tab]");
}

static void Main(string[] args)
{
	var text = "[ ][ ][tab]Hello, world!";
	Console.WriteLine("Input : {0}", text);
	Console.WriteLine("Output: {0}", Encode(ReplaceTabWithSpaces(Decode(text))));

	text = "[ ][ ][ ][tab][tab][ ][tab]Hello, world!";
	Console.WriteLine("Input : {0}", text);
	Console.WriteLine("Output: {0}", Encode(ReplaceTabWithSpaces(Decode(text))));

	Console.ReadLine();
}


Output:

Input : [ ][ ][tab]Hello, world!
Output: [ ][ ][ ][ ]Hello,[ ]world!
Input : [ ][ ][ ][tab][tab][ ][tab]Hello, world!
Output: [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello,[ ]world!
 
Share this answer
 
v2
I have included 2 version: ExpandSimple for traditional; & ExpandCompact for something a little different.

Code:

C#
class Program
{
    static void Main(string[] args)
    {
        int tabstopSize = 4;

        string Test1 = "  \tHello, World!";
        string validate1 = "    Hello, World!";

        string Test2 = "   \t\t \tHello, world!";

        string result1a = Test1.ExpandSimple(tabstopSize);

        Console.WriteLine($"Simple 1       Before : {Test1.ShowSpacing()}");
        Console.WriteLine($"                After : {result1a.ShowSpacing()}");
        Console.WriteLine($"              Passed? : {result1a.Equals(validate1)}");
        Console.WriteLine("");

        string result1b = Test1.ExpandCompact(tabstopSize);

        Console.WriteLine($"Compact 1      Before : {Test1.ShowSpacing()}");
        Console.WriteLine($"                After : {result1b.ShowSpacing()}");
        Console.WriteLine($"              Passed? : {result1b.Equals(validate1)}");
        Console.WriteLine("");
        Console.WriteLine($"Simple 1 = Compact 1? : {result1a.Equals(result1b)}");
        Console.WriteLine("");

        string result2a = Test2.ExpandSimple(tabstopSize);

        Console.WriteLine($"Simple 2       Before : {Test2.ShowSpacing()}");
        Console.WriteLine($"                After : {result2a.ShowSpacing()}");
        Console.WriteLine("");

        string result2b = Test2.ExpandCompact(tabstopSize);

        Console.WriteLine($"Compact 2      Before : {Test2.ShowSpacing()}");
        Console.WriteLine($"                After : {result2b.ShowSpacing()}");
        Console.WriteLine("");
        Console.WriteLine($"Simple 2 = Compact 2? : {result2a.Equals(result2b)}");
        Console.WriteLine("");

        Console.WriteLine("-- Press any key to exit --");
        Console.ReadKey();
    }
}

static class Extensions
{
    public static string ExpandSimple(this string input, int tabstopSize)
    {
        int count = 0, pos = 0, inputLength = input.Length;
        var chars = input.ToCharArray();
        var output = new Stack<char>();

        while (pos < inputLength)
        {
            if (chars[pos] == ' ')
            {
                output.Push(chars[pos]);
                count++;
            }
            else if (chars[pos] == '\t')
            {
                for (int i = 0; i < tabstopSize - count; i++)
                    output.Push(' ');
                count = 0;
            }
            else
            {
                break;
            }
            pos++;
        }
        return (new StringBuilder()).Append(new string(output.Select(x => x).ToArray())).Append(new string(chars.Skip(pos).Take(inputLength - pos).ToArray())).ToString();
    }

    public static string ExpandCompact(this string input, int tabstopSize)
    {
        int index = 0;
        return string.Join(string.Empty, input.ToCharArray().Select((x) => x.Equals('\t') ? x.ConvertTabChar(ref index, tabstopSize) : x.ConvertNormChar(ref index)).ToArray());
    }

    public static string ConvertNormChar(this char c, ref int count)
    {
        count++;
        return new string(new char[] { c });
    }

    public static string ConvertTabChar(this char c, ref int count, int tabstopSize)
    {
        int pad = tabstopSize - count % tabstopSize;
        count += pad;
        return new string(Enumerable.Repeat(' ', pad).ToArray());
    }

    public static string ShowSpacing(this string input)
    {
        int pos = 0, inputLength = input.Length;
        var chars = input.ToCharArray();
        var sb = new StringBuilder();
        while (pos < inputLength)
        {
            if (chars[pos] == ' ')
            {
                sb.Append("[ ]");
            }
            else if (chars[pos] == '\t')
            {
                sb.Append(@"[tab]");
            }
            else
            {
                sb.Append(new string(chars.Skip(pos).Take(inputLength - pos).ToArray()));
                break;
            }
            pos++;
        }
        return sb.ToString();
    }
}


Output:

Simple 1       Before : [ ][ ][tab]Hello, World!
                After : [ ][ ][ ][ ]Hello, World!
              Passed? : True

Compact 1      Before : [ ][ ][tab]Hello, World!
                After : [ ][ ][ ][ ]Hello, World!
              Passed? : True

Simple 1 = Compact 1? : True

Simple 2       Before : [ ][ ][ ][tab][tab][ ][tab]Hello, world!
                After : [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello, world!

Compact 2      Before : [ ][ ][ ][tab][tab][ ][tab]Hello, world!
                After : [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello, world!

Simple 2 = Compact 2? : True

-- Press any key to exit --
 
Share this answer
 
v3
QBASIC baby! My dad would be so proud. Hah.

VB
DIM TABBYTABS$
DIM SPACYMCSPACE$
TABBYTABS$ = "   " + CHR$(9) + CHR$(9) + " " + CHR$(9) + "Hello World!"
FOR I = 1 TO LEN(TABBYTABS$) STEP 1
    IF ASC(MID$(TABBYTABS$, I, 1)) = 9 THEN
        SPACYMCSPACE$ = SPACYMCSPACE$ + " "
    ELSE
        SPACYMCSPACE$ = SPACYMCSPACE$ + MID$(TABBYTABS$, I, 1)
    END IF
NEXT I
PRINT TABBYTABS$
PRINT SPACYMCSPACE$
 
Share this answer
 
v2
In javascript his can be done with a single compound statement. By using indexOf function we can determine the position of the tab character by this expression
JavaScript
tabPosition=theText.indexOf("\t")
which gives us a zero-indexed position or -1 if there are no remaining tabs.

The number of spaces we need to add will be at most four, but we should trim that by the remainder of tabPosition divided by 4 (i.e. tabPosition%4), so that the total number of characters after the replacement is an exact multiple of 4 characters. i.e. we should replace the tab character by this string expression:
JavaScript
("    ").substr(0,4-tabPosition%4)

There may be more than one tab in the text so we should repeat the replacement as long as tabPosition isn't -1. Putting that all together gives:

JavaScript
while((tabPosition=theText.indexOf("\t"))!=-1)theText=theText.replace("\t",("    ").substr(0,4-tabPosition%4));


That single line is all we need to covnvert theText into a tabbed-by-indiviudal-space-characters version. Obviously it's more useful as a function, but why not add a bit more functionality so that tabs can be 2, 3, 4 ....12 character positions apart? We just need to replace the number 4 in our coumpund statement by a variable, and make whitespace string contain more space characters.
JavaScript
function tabs2Spaces(theText,tabSpacing)
{
	while((tabPosition=theText.indexOf("\t"))!=-1)theText=theText.replace("\t",("            ").substr(0,tabSpacing-tabPosition%tabSpacing));
	return theText;
}

// show how the function can align tab spaces irrespective of no of characters before:
document.writeln ("Using tab positions 4 characters apart:")
document.writeln(tabs2Spaces("\tHello World",4));
document.writeln(tabs2Spaces("  \tHello World",4));
document.writeln(tabs2Spaces("  \tHello World",4));
document.writeln(tabs2Spaces("   \tHello World",4));
document.writeln(tabs2Spaces("    \tHello World",4));
document.writeln(tabs2Spaces("     \tHello World",4));
document.writeln(tabs2Spaces("      \tHello World",4));
document.writeln(tabs2Spaces("       \tHello World",4));
document.writeln ("Using tab positions 6 characters apart:")
document.writeln(tabs2Spaces("\tHello World",6));
document.writeln(tabs2Spaces("  \tHello World",6));
document.writeln(tabs2Spaces("  \tHello World",6));
document.writeln(tabs2Spaces("   \tHello World",6));
document.writeln(tabs2Spaces("    \tHello World",6));
document.writeln(tabs2Spaces("     \tHello World",6));
document.writeln(tabs2Spaces("      \tHello World",6));
document.writeln(tabs2Spaces("       \tHello World",6));
document.writeln ("Using tab positions 9 characters apart:")
document.writeln(tabs2Spaces("\tHello World",9));
document.writeln(tabs2Spaces("  \tHello World",9));
document.writeln(tabs2Spaces("  \tHello World",9));
document.writeln(tabs2Spaces("   \tHello World",9));
document.writeln(tabs2Spaces("    \tHello World",9));
document.writeln(tabs2Spaces("     \tHello World",9));
document.writeln(tabs2Spaces("      \tHello World",9));
document.writeln(tabs2Spaces("       \tHello World",9));


.... which outputs this text ...
Using tab positions 4 characters apart:
    Hello World
    Hello World
    Hello World
    Hello World
        Hello World
        Hello World
        Hello World
        Hello World
Using tab positions 6 characters apart:
      Hello World
      Hello World
      Hello World
      Hello World
      Hello World
      Hello World
            Hello World
            Hello World
Using tab positions 9 characters apart:
         Hello World
         Hello World
         Hello World
         Hello World
         Hello World
         Hello World
         Hello World
         Hello World
 
Share this answer
 
Quick and dirty javascript

JavaScript
var TAB_LEN = 4;
// The function
function tabs2Spaces (str){
	return str.replace(/^([\ \t]+)/, (m, p)=>{
		return p.split('').reduce((p, q) => { 
			return q==' '?p+q : p+' '.repeat(TAB_LEN - p.length % TAB_LEN); 
		}, '');
	});
}
// The test
function showSpaces(str){
	return str.replace(/^([\ \t]+)/, (m, p)=>{
		return p.replace(/\ /g, '[ ]').replace(/\t/g, '[tab]');
	});	
}
var tests = ['  	Hello, world!', '   		 	Hello, World!'];
tests.forEach( s=> { 
	console.log('Before:\t' + showSpaces(s));
	console.log('After:\t' + showSpaces(tabs2Spaces(s)));
});
/* OUTPUT
Before: [ ][ ][tab]Hello, world!
After:  [ ][ ][ ][ ]Hello, world!
Before: [ ][ ][ ][tab][tab][ ][tab]Hello, World!
After:  [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]Hello, World!
*/
 
Share this answer
 
v2
CSS
body {
  tab-size: 4;
}
 
Share this answer
 
v2
Comments
Patrice T 3-Dec-16 14:09pm    
I fear it is not so simple.
Kornfeld Eliyahu Peter 3-Dec-16 15:03pm    
And CSS is NOT a programming language...
I tried to make a joke at 01:30 (night), but it was as bad as my mood :-)
Patrice T 3-Dec-16 15:25pm    
:-)
setlocal enabledelayedexpansion
for /f "tokens=*" %%a in (h:\myFile.txt) do (
set row=%%a
set row=!row:,= !
echo.!row!>>h:\changed.txt
)

put text in myfile.txt
save as xxx.bat and run via windows command prompt
it replaces spaces with tabs - because otherwise
 
Share this answer
 
String extension with nice turtle pace immutable string manipulation.

C#
public static string RemoveTabs(this string input) {
    if (string.IsNullOrWhiteSpace(input)) {
         return input;
    }

    int idx = 0;

    while (true) {
        if (idx >= input.Length) {
            break;
        }

        if (input[idx] == '\t') {
            input = input.Remove(idx, 1);
            int cur = idx;

            while (cur < idx + 4) {
                input = input.Insert(cur, '[ ]');
                cur++;
            }

            idx += 4;
            continue;
        }

        idx++;
    }

    input = input.Replace("[tab]", "[ ][ ][ ][ ]");
 
    return input;
}


Use:
C#
string test = "CONVERT\tTABS\t\t-\tTo SPACES".RemoveTabs();
Console.WriteLine(test);
// Or
test = "CONVERT\tTABS\t\t-\tTo SPACES";
test = test.RemoveTabs();
Console.WriteLine(test);


Had to add a second.. ILE RPG (UNTESTED)

D test S 45A   INZ('CONVERT x'05' TABS x'05'- x'05' To SPACES') VARYING
D tester S 45A   INZ('CONVERT [tab] TABS [tab]- [tab] To SPACES') VARYING
test = %ScanRpl(x'05' : '[ ][ ][ ][ ]' : test);
tester = %ScanRpl('[tab]' : '[ ][ ][ ][ ]' : tester);


Ruby

test = "CONVERT [tab] TABS [tab]- [tab] To SPACES"
test = test.gsub("[tab]", "[ ][ ][ ][ ]")
 
Share this answer
 
v6
LINQy version of my previous solution. It works properly for tabs placed anywhere in the text, always aligning to %4 (or %tabLength).

using System.Linq;
using NUnit.Framework;
namespace Code_Project_Challenge
{
    class TabConverter
    {
        static int tabLength = 4;

        public string Convert(string str)
        {
            int charCount = 0, spacesToAdd;

            return string.Join("", str.Select(ch => {
                if (ch == '\t')
                {
                    spacesToAdd = tabLength - (charCount % tabLength);
                    charCount += spacesToAdd;
                    return new string(' ', spacesToAdd);
                }
                else
                {
                    charCount++;
                    return ch.ToString();
                }
            }));
        }
    }

    [TestFixture]
    public class TabConverterTest
    {
        [Test]
        [TestCase("  \tHello, world!", "    Hello, world!")]
        [TestCase("   \t\t \tHello, world!", "            Hello, world!")]
        [TestCase("   \ta     \t \tHello\t, world!", "    a           Hello   , world!")]
        public void Returns(string inStr, string expected)
        {
            var sut = new TabConverter();
            var result = sut.Convert(inStr);
            Assert.AreEqual(expected, result);
        }
    }
}
 
Share this answer
 
Comments
Graeme_Grant 2-Dec-16 12:52pm    
Looks awfully close to what I had above with ExpandCompact...
Jacek M Glen 5-Dec-16 6:42am    
Oh, I missed your solution before posting mine.
Indeed, the approach is very similar. Seems great minds think alike ;)

losvce 5-Dec-16 10:08am    
This LINQy version is far, far better than what I tried in my solution. I just couldn't get it to work in the time I spent on it. Thanks for the perspective!

Page 1 of 2
1 2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900