Is it possible to write a ROT13 in one line?

12.7k views Asked by At

I have the following code which I would like to see as a oneliner. However, since I am very new to C#, I currently have no clue on how to do this...

Code:

static string ROT13 (string input)
{
    if (string.IsNullOrEmpty(input)) return input;

    char[] buffer = new char[input.Length];

    for (int i = 0; i < input.Length; i++)
    {
        char c = input[i];
        if (c >= 97 && c <= 122)
        {
            int j = c + 13;
            if (j > 122) j -= 26;
            buffer[i] = (char)j;
        }
        else if (c >= 65 && c <= 90)
        {
            int j = c + 13;
            if (j > 90) j -= 26;
            buffer[i] = (char)j;
        }
        else
        {
            buffer[i] = (char)c;
        }
    }
    return new string(buffer);
}

I am sorry for any inconvenience, just trying to learn more about this pretty language :)

7

There are 7 answers

2
RvdV79 On BEST ANSWER

What about this? I just happen to have this code lying around, it isn't pretty, but it does the job. Just to make sure: One liners are fun, but they usually do not improve readability and code maintainability... So I'd stick to your own solution :)

static string ROT13(string input)
{
    return !string.IsNullOrEmpty(input) ? new string (input.ToCharArray().Select(s =>  { return (char)(( s >= 97 && s <= 122 ) ? ( (s + 13 > 122 ) ? s - 13 : s + 13) : ( s >= 65 && s <= 90 ? (s + 13 > 90 ? s - 13 : s + 13) : s )); }).ToArray() ) : input;            
}

If you need more clarification, just ask.

Just added this one too, for the lovers of even more beautiful oneliners (and a bit better to read too) :-)

 public static string Rot13(string input) => Regex.Replace(input, "[a-zA-Z]", new MatchEvaluator(c => ((char)(c.Value[0] + (Char.ToLower(c.Value[0]) >= 'n' ? -13 : 13))).ToString()));
0
Save On

Just an alternative version that uses other chars in the comparison to make things more "clear"

static string ROT13(string input)
{
  return !string.IsNullOrEmpty(input) ? new string(input.Select(x => (x >= 'a' && x <= 'z') ? (char)((x - 'a' + 13) % 26 + 'a') : ((x >= 'A' && x <= 'Z') ? (char)((x - 'A' + 13) % 26 + 'A') : x)).ToArray()) : input;           
}
1
Daniel Hilgarth On

Not really a one liner but still shorter than your original code and more understandable than the other answer:

static string Rot13(string input)
{
    if(input == null)
        return null;
    Tuple<int, int>[] ranges = { Tuple.Create(65, 90), Tuple.Create(97, 122) };
    var chars = input.Select(x =>
    {
        var range = ranges.SingleOrDefault(y => x >= y.Item1 && x <= y.Item2);
        if(range == null)
            return x;
        return (char)((x - range.Item1 + 13) % 26) + range.Item1;
    });

    return string.Concat(chars);
}

Another version that even better expresses what happens in ROT13 is this:

static string Rot13(string input)
{
    var lowerCase = Enumerable.Range('a', 26).Select(x => (char)x).ToArray();
    var upperCase = Enumerable.Range('A', 26).Select(x => (char)x).ToArray();
    var mapItems = new[]
    {
        lowerCase.Zip(lowerCase.Skip(13).Concat(lowerCase.Take(13)), (k, v) => Tuple.Create(k, v)),
        upperCase.Zip(upperCase.Skip(13).Concat(upperCase.Take(13)), (k, v) => Tuple.Create(k, v))
    };
    var map = mapItems.SelectMany(x => x).ToDictionary(x => x.Item1, x => x.Item2);

    return new string(input.Select(x => Map(map, x)).ToArray());
}

static char Map(Dictionary<char, char> map, char c)
{
    char result;
    if(!map.TryGetValue(c, out result))
        return c;
    return result;
}
0
Maria Kazachenok On

One more an alternative version:

static string ROT13(string input)
{
    return String.Join("", input.Select(x => char.IsLetter(x) ? (x >= 65 && x <= 77) || (x >= 97 && x <= 109) ? (char)(x + 13) : (char)(x - 13) : x))
}
0
Mark Markovich On
public static string Rot131(string message)
{
    string result = "";
    foreach (var s in message)
    {
        if ((s >= 'a' && s <= 'm') || (s >= 'A' && s <= 'M'))
            result += Convert.ToChar((s + 13)).ToString();
        else if ((s >= 'n' && s <= 'z') || (s >= 'N' && s <= 'Z'))
            result += Convert.ToChar((s - 13)).ToString();
        else result += s;
    }
    return result;
}
0
Martin Costello On

Here's another version of ROT13 but that avoids LINQ to not allocate as much memory due to less intermediate steps.

public static string Rot13(string input)
{
    int length = input.Length;
    Span<char> rot13 = length <= 256 ? stackalloc char[length] : new char[length];

    for (int i = 0; i < length; i++)
    {
        rot13[i] = Rot13(input[i]);
    }

    return rot13.ToString();

    static char Rot13(char c)
    {
        if (!char.IsLetter(c))
        {
            return c;
        }

        char baseline = char.IsUpper(c) ? 'A' : 'a';
        return (char)(((c - baseline + 13) % 26) + baseline);
    }
}

Using BenchmarkDotNet, this gives the following results on my computer compared to the current top answer:

BenchmarkDotNet v0.13.6, Windows 11 (10.0.22621.1992/22H2/2022Update/SunValley2)
12th Gen Intel Core i7-1270P, 1 CPU, 16 logical and 12 physical cores
.NET SDK 7.0.306
  [Host]     : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2
  DefaultJob : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2


| Method |     Mean |   Error |   StdDev | Ratio | RatioSD |   Gen0 | Allocated | Alloc Ratio |
|------- |---------:|--------:|---------:|------:|--------:|-------:|----------:|------------:|
|     V1 | 266.5 ns | 5.41 ns |  9.75 ns |  1.00 |    0.00 | 0.0458 |     432 B |        1.00 |
|     V2 | 166.7 ns | 3.44 ns | 10.03 ns |  0.64 |    0.04 | 0.0136 |     128 B |        0.30 |

I know it's not one line, but terser doesn't necessarily mean better, and I found this post from the one-liner being copied into a private codebase.

0
Alex de Waal On

As rot13 is only applicable to continuous sequences of 26 characters only 'A'-'Z' and 'a'-'z' of ASCII are to be targeted. To easeĀ¹ character processing, they're 32 (0x20) apart, by masking a character in these ranges with 0x1F makes 'A' and 'a' equal to 1, until 'Z' and 'z' equal to 26.

In disregard of the question this is not a oneliner, as they're hard to read.

    static string Rot13(string str)
    {
        var buffer = new char[str.Length];
        var i = 0;
        foreach(var c in str)
        {
            buffer[i++] = R13(c);
        }
        return new string(buffer);
        
        char R13(char c)
        {
            if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')
                return (char)(c + ((c & 0x1F) > 13 ? -13 : 13));
            return c;
        }
    }

1) This design came from a time when cpu registers often had only 8 bits, clock frequencies were in MHz and RAM was in kbytes.