How to replace custom tabs with spaces in a string, depend on the size of the tab?
Asked Answered
A

14

12

I'm trying to write a python function not using any modules that will take a string that has tabs and replace the tabs with spaces appropriate for an inputted tabstop size. It can't just replace all size-n tabs by n spaces though, since a tab could be 1 to n spaces. I'm really confused, so if anyone could just point me in the right direction I'd greatly appreciate it.

For instance, if tabstop is size 4 originally:

123\t123 = 123 123 #one space in between

but changed to tabstop 5:

123\t123 = 123  123 #two spaces in between

I think I need to pad the end of the string with spaces until string%n==0 and then chunk it, but I'm pretty lost at the moment..

Acrid answered 17/4, 2013 at 6:14 Comment(5)
do you want "_" for each tab(\t) ?Freytag
It would be a good idea to add a bunch of testcases to your questionGleaning
What happen if blocksize is 5 and string is more longer, e.g. 123456\t ? Result is: 1234_56___ ? 1234_6____ ? 123456_?Insectarium
I may be missing something, but a tabstop is not size "n". A tabstop is \t which is one character and is always size 1. Do you want to replace spaces with tabs, maybe? Or spaces with fewer spaces?Prady
Ohhhh. Okay. I see what you mean. You should probably rephrase your question as it's a bit confusing at first.Prady
F
4

Since you wan't a python function that doesn't use any external module, I think you should design first the algorithm of your function...

I would propose to iterate on every char of the string ; if char i is a tab, you need to compute how many spaces to insert : the next "aligned" index is ((i / tabstop) + 1) * tabstop. So you need to insert ((i / tabstop) + 1) * tabstop - (i % tabstop). But an easier way is to insert tabs until you are aligned (i.e. i % tabstop == 0)

def replace_tab(s, tabstop = 4):
  result = str()
  for c in s:
    if c == '\t':
      while (len(result) % tabstop != 0):
        result += ' ';
    else:
      result += c    
  return result
Fubsy answered 17/4, 2013 at 6:38 Comment(6)
Thanks everybody for the help. This is exactly what I was looking for I was just having a mental block trying to wrap my mind around the algorithm, so thanks again!Acrid
Anybody know how to change this to work with multiple tabs in a row? seems that it only picks up the first oneAcrid
In the test I ran multiple tab were ok: replace_tab('123\t12\t1\t123456\t1234\t12345678\n') returns '123.12..1...123456..123412345678' (with dots replacing spaces for readability)Sunk
BTW, I think that other answers with list comprehension, split and join are far more elegant...Sunk
For multiple tabs like "uint8_t\t\tvalue;" I inserted "if len(result) % tabstop == 0: result += ' '" before the while loop.Cheekbone
This shouldn't be upvoted as it is incorrect - I doubt it was ever tested. A leading tab simply gets thrown away, replaced by nothing. (And only the first tab is replaced, though that negative feature is documented.)Idio
P
5

For a tab length of 5:

>>> s = "123\t123"
>>> print ''.join('%-5s' % item for item in s.split('\t'))
123  123  
>>> 
Prady answered 17/4, 2013 at 7:24 Comment(1)
Or: (5*' ').join(s.split('\t'))Brooking
F
4

Since you wan't a python function that doesn't use any external module, I think you should design first the algorithm of your function...

I would propose to iterate on every char of the string ; if char i is a tab, you need to compute how many spaces to insert : the next "aligned" index is ((i / tabstop) + 1) * tabstop. So you need to insert ((i / tabstop) + 1) * tabstop - (i % tabstop). But an easier way is to insert tabs until you are aligned (i.e. i % tabstop == 0)

def replace_tab(s, tabstop = 4):
  result = str()
  for c in s:
    if c == '\t':
      while (len(result) % tabstop != 0):
        result += ' ';
    else:
      result += c    
  return result
Fubsy answered 17/4, 2013 at 6:38 Comment(6)
Thanks everybody for the help. This is exactly what I was looking for I was just having a mental block trying to wrap my mind around the algorithm, so thanks again!Acrid
Anybody know how to change this to work with multiple tabs in a row? seems that it only picks up the first oneAcrid
In the test I ran multiple tab were ok: replace_tab('123\t12\t1\t123456\t1234\t12345678\n') returns '123.12..1...123456..123412345678' (with dots replacing spaces for readability)Sunk
BTW, I think that other answers with list comprehension, split and join are far more elegant...Sunk
For multiple tabs like "uint8_t\t\tvalue;" I inserted "if len(result) % tabstop == 0: result += ' '" before the while loop.Cheekbone
This shouldn't be upvoted as it is incorrect - I doubt it was ever tested. A leading tab simply gets thrown away, replaced by nothing. (And only the first tab is replaced, though that negative feature is documented.)Idio
K
4

I use .replace function that is very simple:

line = line.replace('\t', ' ')
Knudsen answered 17/11, 2015 at 9:54 Comment(1)
This approach simply does not provide what the question asks for ...Bim
O
2

Sorry, i misread the question the first time.

This is a recursive version that should work for any number of tabs in the input :

def tabstop ( s , tabnum = 4):
    if not '\t' in s:
        return s
    l = s.find('\t')
    return s[0:l]+' '*(tabnum-l)+tabstop(s[l+1:],tabnum)
Oswald answered 17/4, 2013 at 6:16 Comment(1)
Nice idea, but this approach does not work when the length of the substrings between the tabs is larger than tabnum ( ' '*negativeNumber gives an empty string).Bim
M
2

I think Remi's answer is the simplest but it has a bug, it doesn't account for the case when you are already on a "tab stop" column. Tom Swirly pointed this out in the comments. Here's a tested fix to his suggestion:

def replace_tab(s, tabstop = 4):
    result = str()

    for c in s:
        if c == '\t':
            result += ' '
            while ((len(result) % tabstop) != 0):
                result += ' '
        else:
            result += c    

    return result
Marathi answered 12/11, 2015 at 0:4 Comment(0)
E
2

Here is the easiest way

def replaceTab(text,tabs)
    return text.replace('\t', ' ' * tabs)
Elbow answered 5/9, 2019 at 17:41 Comment(1)
This approach simply does not provide what the question asks for ...Bim
I
1

This code can help you:

initial_string = "My \tstring \ttest\t"
block_size = "5"
"".join([("{block_value:"+str(block_size)+"s}").format(block_value=block) 
    for block in initial_string.split("\t")])

You will need to study: format, split and join function and list comprehension concept.

Insectarium answered 17/4, 2013 at 7:12 Comment(1)
This approach does not work for strings in which the distance between the Tabs is larger than block_size.Bim
B
1

This programm replaces all the tabs for spaces in a file:

def tab_to_space (line, tab_lenght = 8):
    """this function change all the tabs ('\\t') for spaces in a string, 
        the lenght of the tabs is 8 by default"""

    while '\t' in line:
        first_tab_init_pos = line.find('\t')
        first_tab_end_pos = (((first_tab_init_pos // tab_lenght)+1) * tab_lenght)
        diff = first_tab_end_pos - first_tab_init_pos
        if diff == 0:
            spaces_string = ' ' * tab_lenght
        else:
            spaces_string = ' ' * diff
        line = line.replace('\t', spaces_string, 1)
    return line


inputfile = open('inputfile.txt', 'r')
outputfile = open('outputfile.txt', 'w')
for line in inputfile:
    line = tab_to_space(line)
    outputfile.write(line)
inputfile.close()
outputfile.close()
Botheration answered 29/6, 2014 at 6:42 Comment(0)
S
1

if you have the requirement where you want to add n spaces instead of custom tab you can simply write below code. I have shown the implementation using two functions, each having different way to solve it.You can use any of the function!

for eg. let the string be in the variable 'code' and 'x' be the size of tab

code = "def add(x, y)\f\treturn x + y"
x=4

def convertTabs(code, x):
    temp=""
    for i in range(0,x):
        temp+=" "
    return code.replace("\t",temp) 

def converTabs1(code,x):
    return code.replace("\t",x*" ")

both the functions above will give the same value, but the second one is super awesome !

Somatist answered 11/4, 2018 at 20:35 Comment(2)
This approach simply does not provide what the question asks for ...Bim
Can you please provide better explanation for your comment ? @ClaudioSomatist
T
0

I needed something similar, here's what I came up with:

import re

def translate_tabs(tabstop = 8):
  offset = [0]
  def replace(match, offset=offset):
    offset[0] += match.start(0)
    return " " * (tabstop - offset[0] % tabstop)
  return replace

re.sub(r'\t', translate_tabs(4), "123\t123") 
# => '123 123'

re.sub(r'\t', translate_tabs(5), "123\t123")
# => '123  123'
Tempietempla answered 20/8, 2014 at 16:39 Comment(2)
This approach does not correctly work for multiple Tabs in the string, because it does not consider the change of the Tab offset depending on the previous replacements not being a single space.Bim
See my answer for how can you fix it keeping the main idea of this interesting approach.Bim
S
0

Use the re.sub is enough.

def untabify(s, tabstop = 4):
    return re.sub(re.compile(r'\t'), ' '*tabstop, s)
Solvable answered 7/9, 2016 at 9:20 Comment(1)
This approach simply does not provide what the question asks for ...Bim
B
0

Fix for @rémi answer This implementation honors the leading tab and any consecutive tabs

def replace_tab(s, tabstop=4):
    result = str()
    for c in s:
        if c == '\t':
            if (len(result) % tabstop == 0):
                result += ' ' * tabstop
            else:
                while (len(result) % tabstop != 0):
                    result += ' '
        else:
            result += c
    return result
Bootee answered 7/8, 2020 at 7:15 Comment(1)
What about ``` if c == '\t': result += ' ' while (len(result) % tabstop != 0): result += ' ' ``` ?Bim
Y
0
def expand_tabs(text: str, width: int = 8) -> str:
    """
    Expand each tab to one or more spaces
    """
    assert width > 0
    while (i := text.find('\t')) >= 0:
        text = text[:i] + ' ' * (width - i % width) + text[i+1:]
    return text
Yak answered 7/4, 2022 at 17:56 Comment(1)
Extremely inefficient approach. On each Tab occurrence the string must be resized (expensive) and each Tab occurrence the find starts to search unnecessary from the very begin of the string for the next Tab.Bim
B
0

Just only because this below does not fit into a comment to the answer of kzar who came up with a quite interesting approach (which does not answer the question because it uses a module), but didn't it correct :

import re
offsetAddon = 0
def spaces(tabSize=8):
  def replace(match):
    global offsetAddon
    spaceMultipl = (tabSize - (match.start(0) + offsetAddon) % tabSize)
    offsetAddon += (spaceMultipl - 1)
    return " " * spaceMultipl    
  return replace
tab=r'\t'
s="\t1\t12\t123\t1234\t12345\t123456\t1234567\t12345678\t12\t"
print(f'''"{re.sub(tab, spaces(4), s)}"''') # gives:  
# "    1   12  123 1234    12345   123456  1234567 12345678    12  "
Bim answered 6/8, 2023 at 0:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.