HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Given an index return item from opposite end of list

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
itemreturnlistendindexfromoppositegiven

Problem

Given the following code:

namespace AtBash
{
  class Program
 {
    static List letters = new List {
        "Aleph", "Beis", "Gimmel", "Daled", "Hei", "Vav", "Zayin", "Chies", "Teis",
        "Yud", "Chof", "Lamed", "Mem", "Nun", "Samech", "Ayin", "Pei", "Tzadik",
        "Kuf", "Reish", "Shin", "Taf"
    };
    static string AtBash(int index) => letters[Math.Abs((index + 1) - letters.Count)];

    static void Main(string[] args)
    {
        foreach (var item in letters)
        {
            Console.WriteLine(letters.IndexOf(item));
            Console.WriteLine($"{item} -> {AtBash(letters.IndexOf(item))}");
        }
    }
  }
}


Where the purpose is:


Given an index (say 0) return the corresponding item from the other end of the array (for our example 22).

This is the part of the code that actually does anything:

static string AtBash(int index) => letters[Math.Abs((index + 1) - letters.Count)];


The code works but I am interested in knowing if it can be made faster/more efficient. I would also like to know if there is an official term for this kind of algorithm.

Solution

If the entire goal is absolute performance of the AtBash() request, I would simply reverse the letters at startup. I'm speaking outside the context of this particular program and more towards your library where I am making the assumption that the AtBash() method calls vs. startup ratio is much higher to infinite.

If that's the case and we can eat some startup time, and you really want that per-access performance on AtBash(), simply eat a tiny bit of memory to get there:

static List letters = new List {
    "Aleph", "Beis", "Gimmel", "Daled", "Hei", "Vav", "Zayin", "Chies", "Teis",
    "Yud", "Chof", "Lamed", "Mem", "Nun", "Samech", "Ayin", "Pei", "Tzadik",
    "Kuf", "Reish", "Shin", "Taf"
};
static List lettersReversed = letters.AsEnumerable().Reverse().ToList();
static string AtBash(int index) => lettersReversed[index];


Compare the IL in the initial accessor, letters[Math.Abs((index + 1) - letters.Count)]:

IL_0000:  ldsfld      UserQuery+Program.letters
IL_0005:  ldarg.0     
IL_0006:  ldc.i4.1    
IL_0007:  add         
IL_0008:  ldsfld      UserQuery+Program.letters
IL_000D:  callvirt    System.Collections.Generic.List.get_Count
IL_0012:  sub         
IL_0013:  call        System.Math.Abs
IL_0018:  callvirt    System.Collections.Generic.List.get_Item
IL_001D:  ret


To the improved accessor, letters[letters.Count - (index + 1)]:

IL_0000:  ldsfld      UserQuery+Program.letters
IL_0005:  ldsfld      UserQuery+Program.letters
IL_000A:  callvirt    System.Collections.Generic.List.get_Count
IL_000F:  ldarg.0     
IL_0010:  ldc.i4.1    
IL_0011:  add         
IL_0012:  sub         
IL_0013:  callvirt    System.Collections.Generic.List.get_Item
IL_0018:  ret


To the fastest accessor, lettersReversed[index]:

IL_0000:  ldsfld      UserQuery+Program.lettersReversed
IL_0005:  ldarg.0     
IL_0006:  callvirt    System.Collections.Generic.List.get_Item
IL_000B:  ret


Given your very minimal amount of actual memory in the example (which I don't think is likely to increase), I think this is a perfectly reasonable approach for any high-throughput library.

Feel free to benchmark this. The more accesses per startup, the better the payoff. You should already see payoff pretty quickly because sorting an array of a few dozen items is very fast and that's all the time we're adding to get the long-term benefits here. I wouldn't be surprised if your program even with trivial levels of access already performs better with this approach.

Code Snippets

static List<string> letters = new List<string> {
    "Aleph", "Beis", "Gimmel", "Daled", "Hei", "Vav", "Zayin", "Chies", "Teis",
    "Yud", "Chof", "Lamed", "Mem", "Nun", "Samech", "Ayin", "Pei", "Tzadik",
    "Kuf", "Reish", "Shin", "Taf"
};
static List<string> lettersReversed = letters.AsEnumerable().Reverse().ToList();
static string AtBash(int index) => lettersReversed[index];
IL_0000:  ldsfld      UserQuery+Program.letters
IL_0005:  ldarg.0     
IL_0006:  ldc.i4.1    
IL_0007:  add         
IL_0008:  ldsfld      UserQuery+Program.letters
IL_000D:  callvirt    System.Collections.Generic.List<System.String>.get_Count
IL_0012:  sub         
IL_0013:  call        System.Math.Abs
IL_0018:  callvirt    System.Collections.Generic.List<System.String>.get_Item
IL_001D:  ret
IL_0000:  ldsfld      UserQuery+Program.letters
IL_0005:  ldsfld      UserQuery+Program.letters
IL_000A:  callvirt    System.Collections.Generic.List<System.String>.get_Count
IL_000F:  ldarg.0     
IL_0010:  ldc.i4.1    
IL_0011:  add         
IL_0012:  sub         
IL_0013:  callvirt    System.Collections.Generic.List<System.String>.get_Item
IL_0018:  ret
IL_0000:  ldsfld      UserQuery+Program.lettersReversed
IL_0005:  ldarg.0     
IL_0006:  callvirt    System.Collections.Generic.List<System.String>.get_Item
IL_000B:  ret

Context

StackExchange Code Review Q#128506, answer score: 11

Revisions (0)

No revisions yet.