7

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 :)

RFerwerda
  • 1,267
  • 2
  • 10
  • 24
  • 8
    Just place all characters in your method on one line. – Hamlet Hakobyan Sep 11 '13 at 10:48
  • 2
    Any reason you want to intentionally make things unreadable? – Moo-Juice Sep 11 '13 at 10:57
  • 3
    Completely unrelated, but another thing you might want to learn is that [magic numbers](http://en.wikipedia.org/wiki/Magic_number_(programming)#Unnamed_numerical_constants) are usually not a good thing to have. As Save has shown, it's better to use the characters instead of their numerical values to better show what the code does. As a rule of thumb, any code is read more than ten times as much as it is written. So your goal should always be to write code as clear and understandable as possible and not to use as many clever tricks as possible. You can optimize when what you have works. – Corak Sep 11 '13 at 11:54
  • This question can be linked to http://stackoverflow.com/q/617647 – 131 Feb 11 '15 at 23:11

6 Answers6

20

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()));
RvdV79
  • 2,002
  • 16
  • 36
  • 1
    Thanks for your quick reply! I've pasted the code in my program and it works just fine! I think I'll look more into using the ? : operators. – RFerwerda Sep 11 '13 at 10:50
  • That is a conditional statement or just an if ... then ... else ... statement. The if is replaced by the boolean statement (condition) the then is replaced by the ? and the else is replaced by the :. Good luck, glad it worked for you! – RvdV79 Sep 11 '13 at 10:54
6

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;           
}
Save
  • 11,450
  • 1
  • 18
  • 23
6

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;
}
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 1
    If you only get Int32 representations of each character in the top solution, change `return (char)((x - range.Item1 + 13) % 26) + range.Item1` to `return (char)((x - range.Item1 + 13) % 26 + range.Item1)` – snumpy Aug 30 '17 at 18:29
3

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.

Martin Costello
  • 9,672
  • 5
  • 60
  • 72
2

One more an alternative version:

static string ROT13(string input)
{
    String.Join("", input.Select(x => char.IsLetter(x) ? (x >= 65 && x <= 77) || (x >= 97 && x <= 109) ? (char)(x + 13) : (char)(x - 13) : x))
}
RB.
  • 36,301
  • 12
  • 91
  • 131
0
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;
}
Mustafa Poya
  • 2,615
  • 5
  • 22
  • 36