'Insert a newline character every 10 characters in a string using Julia

I want to insert a newline character every 10 characters in a protein sequence :

seq="MSKNKSPLLNESEKMMSEMLPMKVSQSKLNYEEKVYIPTTIRNRKQHCFRRFFPYIALFQ"

In Perl, it is very easy :

$seq=~s/(.{10})/$1\n/g ; # does the job!

perl -e '$seq="MSKNKSPLLNESEKMMSEMLPMKVSQSKLNYEEKVYIPTTIRNRKQHCFRRFFPYIALFQ"; $seq=~s/(.{10})/$1\n/g; print $seq'
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ

In Julia,

replace(seq, r"(.{10})" , "\n")

does not work because I don't know a way to get the capture group (.{10}) and substitute it with itself + "\n"

julia> replace(seq, r"(.{10})" , "\n")
"\n\n\n\n\n\n"

So to do that, I need 2 steps :

    julia> a=matchall(r"(.{1,10})" ,seq)
    6-element Array{SubString{UTF8String},1}:
     "MSKNKSPLLN"
     "ESEKMMSEML"
     "PMKVSQSKLN"
     "YEEKVYIPTT"
     "IRNRKQHCFR"
     "RFFPYIALFQ"

    julia> b=join(a, "\n")
    "MSKNKSPLLN\nESEKMMSEML\nPMKVSQSKLN\nYEEKVYIPTT\nIRNRKQHCFR\nRFFPYIALFQ"

    julia> println(b)
    MSKNKSPLLN
    ESEKMMSEML
    PMKVSQSKLN
    YEEKVYIPTT
    IRNRKQHCFR
    RFFPYIALFQ

# Caution :    
a=matchall(r"(.{10})" ,seq) # wrong if seq is not exactly a multiple of 10 !

julia> seq
"MSKNKSPLLNESEKMMSEMLPMKVSQSKLNYEEKVYIPTTIRNRKQHCFRRFFPYIAL"

julia> matchall(r"(.{10})" ,seq)
5-element Array{SubString{UTF8String},1}:
"MSKNKSPLLN"
"ESEKMMSEML"
"PMKVSQSKLN"
"YEEKVYIPTT"
"IRNRKQHCFR"

julia> matchall(r"(.{1,10})" ,seq)
6-element Array{SubString{UTF8String},1}:
"MSKNKSPLLN"
"ESEKMMSEML"
"PMKVSQSKLN"
"YEEKVYIPTT"
"IRNRKQHCFR"
"RFFPYIAL" 

Is there a one step solution or a better (faster) way?

Just for fun a benchmark with all these interesting answers ! (updated with julia 5.0)

function loop(a)
 last = 0
 #create the interval, in your case 10
 salt = 10
 #iterate in string (starts in the 10th value, don't forget julia use 1 to first index)
 for i in salt:salt+1:length(a)
    # replace the string for a new one with '\n'
    a = string(a[1:i], '\n', a[i+1:length(a)])
    last = Int64(i)
 end
 # replace the rest
 a = string(a[1:length(a) - last % salt + 1], '\n', a[length(a) - last % salt + 2:length(a)])
 println(a)
end

function regex1(seq)
  a=matchall(r"(.{1,10})" ,seq)
  b=join(a, "\n")
  println(b)
end

function regex2(seq)
  a=join(split(replace(seq, r"(.{10})", s"\1 ")), "\n")
  println(a)
end

function regex3(seq)
  a=replace(seq, r"(.{10})", Base.SubstitutionString("\\1\n"))
  a= chomp(a) # because there is a new line at the end
  println(a)
end

function intrapad(seq::String)
  buf = IOBuffer((length(seq)*11)>>3) # big enough buffer
  for i=1:10:length(seq)
    write(buf,SubString(seq,i,i+9),'\n')
  end
  #return
  print(takebuf_string(buf))
end

function join_substring(seq)
  a=join((SubString(seq,i,i+9) for i=1:10:length(seq)),'\n')
  println(a)
end

seq="MSKNKSPLLNESEKMMSEMLPMKVSQSKLNYEEKVYIPTTIRNRKQHCFRRFFPYIALFQ"

for i = 1:5
  println("loop :")
  @time loop(seq)
  println("regex1 :")
  @time regex1(seq)
  println("regex2 :")
  @time regex2(seq)
  println("regex3 :")
  @time regex3(seq)
  println("intrapad :")
  @time intrapad(seq)
  println("join substring :")
  @time join_substring(seq)
end

I changed the benchmark to execute 5 times @time and I post here the results after 5 execution of @time :

loop :
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIA
LFQ
  0.000013 seconds (53 allocations: 3.359 KB)
regex1 :
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ
  0.000013 seconds (49 allocations: 1.344 KB)
regex2 :
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ
  0.000017 seconds (47 allocations: 1.703 KB)
regex3 :
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ
  0.000013 seconds (31 allocations: 976 bytes)
intrapad :
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ
  0.000007 seconds (9 allocations: 608 bytes)
join substring :
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ
  0.000012 seconds (21 allocations: 800 bytes)

Intrapad is now first ;)



Solution 1:[1]

If speed is an issue, it might be best to avoid heavier tools such as regular expressions and try to just get the job done low-level, like so:

function intrapad(seq::String)
  buf = IOBuffer((length(seq)*11)>>3) # big enough buffer
  for i=1:10:length(seq)
    write(buf,SubString(seq,i,i+9),'\n')
  end
  return takebuf_string(buf)
end

Speed comes from minimizing allocation using IOBuffer and SubStrings. Using BenchmarkTools package we have:

julia> @benchmark intrapad(seq)
BenchmarkTools.Trial: 
  memory estimate:  624.00 bytes
  allocs estimate:  10
  minimum time:     729.00 ns (0.00% GC)
  median time:      767.00 ns (0.00% GC)
  mean time:        862.99 ns (7.84% GC)
  maximum time:     26.86 ?s (96.21% GC)

julia> @benchmark replace(seq, r"(.{10})", Base.SubstitutionString("\\1\n"))
BenchmarkTools.Trial: 
  memory estimate:  720.00 bytes
  allocs estimate:  26
  minimum time:     2.18 ?s (0.00% GC)
  median time:      2.29 ?s (0.00% GC)
  mean time:        2.43 ?s (3.85% GC)
  maximum time:     531.31 ?s (98.95% GC)

Only a 2.5x speedup. The replace function is very well implemented!

Another way to go without regular expression is

join((SubString(seq,i,i+9) for i=1:10:length(seq)),'\n')

Which is not as fast (10x slower, no memory allocation penalty on my machine), but very readable.

Solution 2:[2]

Something like:

julia> split(replace(seq, r"(.{10})", s"\1 "))
6-element Array{SubString{String},1}:
 "MSKNKSPLLN"
 "ESEKMMSEML"
 "PMKVSQSKLN"
 "YEEKVYIPTT"
 "IRNRKQHCFR"
 "RFFPYIALFQ"

If you want it as a string, use join():

julia> join(split(replace(seq, r"(.{10})", s"\1 ")), "\n")
"MSKNKSPLLN\nESEKMMSEML\nPMKVSQSKLN\nYEEKVYIPTT\nIRNRKQHCFR\nRFFPYIALFQ"

julia> println(ans)
MSKNKSPLLN
ESEKMMSEML
PMKVSQSKLN
YEEKVYIPTT
IRNRKQHCFR
RFFPYIALFQ

Solution 3:[3]

I don't know how you can make with REGEX, but i think it could solve your problem:

a = "oiaoueaoeuaoeuaoeuaoeuaoteuhasonetuhaonetuahounsaothunsaotuaosu"
last = 0
#create the interval, in your case 10
salt = 10
#iterate in string (starts in the 10th value, don't forget julia use 1 to first index)
for i in salt:salt+1:length(a)
    # replace the string for a new one with '\n'
    a = string(a[1:i], '\n', a[i+1:length(a)])
    last = Int64(i)
end
# replace the rest
a = string(a[1:length(a) - last % salt + 1], '\n', a[length(a) - last % salt + 2:length(a)])
println(a)

Solution 4:[4]

I step by the required line-length through a range to format DNA sequences. In the following snippet, the step is 60.

last_x = 0
new_seq = ""
for x in 1:60:length(seq)
    if x+59 < length(seq[x:end])
        new_seq = join([new_seq, seq[x:(x+59)], "\n"])
    end
    last_x = x
end
new_seq = join([new_seq, seq[last_x:end], "\n"])

If seq is

ATTCGACTCTTATGCCTATCGCTAGCTAGCATCTATTCGACTCTTATGCCTATCGCTAGCTAGCATCAATTCGACTCTTATGCCTATCGCTAGCTAGCATCGATTCGACTCTTATGCCTATCGCTAGCTAGCATCCATTCGACTCTTATGCCTATCGCTAGCTAGCATCTATTCGACTCTTATGCCTATCGCTAGCTAGCATCAATTCGACTCTTATGCCTATCGCTAGCTAGCATCGATTCGACTCTTA

then, the printed output becomes

println(new_seq)
ATTCGACTCTTATGCCTATCGCTAGCTAGCATCTATTCGACTCTTATGCCTATCGCTAGC
TAGCATCAATTCGACTCTTATGCCTATCGCTAGCTAGCATCGATTCGACTCTTATGCCTA
TCGACTCTTA

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 pmargreff
Solution 4 Eric Kofoid