Reputation: 10140
This question is more theoretical than practical, but still.
I've been looking for a chance to improve the following code from the string memory allocation standpoint:
/* Output for n = 3:
*
* ' #'
* ' ##'
* '###'
*
*/
public static string[] staircase(int n) {
string[] result = new string[n];
for(var i = 0; i < result.Length; i++) {
var spaces = string.Empty.PadLeft(n - i - 1, ' ');
var sharpes = string.Empty.PadRight(i + 1, '#');
result[i] = spaces + sharpes;
}
return result;
}
PadHelper is the method, that is eventually called under the hood twice per iteration.
So, correct me if I'm wrong, but it seems like memory is allocated at least 3 times per iteration.
Any code improvements will be highly appreciated.
Upvotes: 2
Views: 2510
Reputation: 163
You can also try putting things on the stack where it makes sense.
public void StaircaseSpan()
{
Span<char> characters = stackalloc char[n];
characters.Fill(' ');
var rows = new string[n];
for (int i = n - 1; i >= 0; i--)
{
characters[i] = '#';
rows[i] = characters.ToString();
}
}
n=3
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|---------------- |---------:|---------:|---------:|-------:|----------:|
| StaircaseSpan | 33.26 ns | 0.161 ns | 0.151 ns | 0.0688 | 144 B |
| StaircaseString | 67.98 ns | 0.494 ns | 0.438 ns | 0.1223 | 256 B |
n=8
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|---------------- |----------:|---------:|---------:|-------:|----------:|
| StaircaseSpan | 85.79 ns | 0.940 ns | 0.880 ns | 0.1950 | 408 B |
| StaircaseString | 209.93 ns | 0.499 ns | 0.417 ns | 0.4170 | 872 B |
Upvotes: 1
Reputation: 24136
You can save on both allocations and speed by starting with a string that contains all the Spaces and all the Sharpes you're ever going to need, and then taking substrings from that, as follows:
public string[] Staircase2()
{
string allChars = new string(' ', n - 1) + new string('#', n); // n-1 spaces + n sharpes
string[] result = new string[n];
for (var i = 0; i < result.Length; i++)
result[i] = allChars.Substring(i, n);
return result;
}
I used BenchmarkDotNet to compare Staircase1
(your original approach) with Staircase2
(my approach above) from n=2
upto n=8
, see the results below.
It shows that Staircase2
is always faster (see the Mean
column), and it allocates fewer bytes starting from n=3
.
| Method | n | Mean | Error | StdDev | Allocated |
|----------- |-- |------------:|-----------:|-----------:|----------:|
| Staircase1 | 2 | 229.36 ns | 4.3320 ns | 4.0522 ns | 92 B |
| Staircase2 | 2 | 92.00 ns | 0.7200 ns | 0.6735 ns | 116 B |
| Staircase1 | 3 | 375.06 ns | 3.3043 ns | 3.0908 ns | 156 B |
| Staircase2 | 3 | 114.12 ns | 2.8933 ns | 3.2159 ns | 148 B |
| Staircase1 | 4 | 507.32 ns | 3.8995 ns | 3.2562 ns | 236 B |
| Staircase2 | 4 | 142.78 ns | 1.4575 ns | 1.3634 ns | 196 B |
| Staircase1 | 5 | 650.03 ns | 15.1515 ns | 25.7284 ns | 312 B |
| Staircase2 | 5 | 169.25 ns | 1.9076 ns | 1.6911 ns | 232 B |
| Staircase1 | 6 | 785.75 ns | 16.9353 ns | 15.8413 ns | 412 B |
| Staircase2 | 6 | 195.91 ns | 2.9852 ns | 2.4928 ns | 292 B |
| Staircase1 | 7 | 919.15 ns | 11.4145 ns | 10.6771 ns | 500 B |
| Staircase2 | 7 | 237.55 ns | 4.6380 ns | 4.9627 ns | 332 B |
| Staircase1 | 8 | 1,075.66 ns | 26.7013 ns | 40.7756 ns | 620 B |
| Staircase2 | 8 | 255.50 ns | 2.6894 ns | 2.3841 ns | 404 B |
This doesn't mean that Staircase2 is the absolute best possible, but certainly there is a way that is better than the original.
Upvotes: 2
Reputation: 78134
StringBuilder
is always an answer when it comes to string allocations; I'm sure you know that so apparently you want something else. Well, since your strings are all the same length, you can declare a single char[]
array, populate it every time (only requires changing one array element on each iteration) and then use the string(char[])
constructor:
public static string[] staircase(int n)
{
char[] buf = new char[n];
string[] result = new string[n];
for (var i = 0; i < n - 1; i++)
{
buf[i] = ' ';
}
for (var i = 0; i < n; i++)
{
buf[n - i - 1] = '#';
result[i] = new string(buf);
}
return result;
}
Upvotes: 1
Reputation: 2738
You can project your desired results using the Linq Select
method. For example, something like this:
public static string[] staircase(int n) {
return Enumerable.Range(1, n).Select(i => new string('#', i).PadLeft(n)).ToArray();
}
Alternate approach using an int
array:
public static string[] staircase(int n) {
return (new int[n]).Select((x,i) => new string('#', i+1).PadLeft(n)).ToArray();
}
HTH
Upvotes: 1
Reputation: 152501
how about:
result[i] = new string('#',i).PadLeft(n)
?
Note that this still allocates two strings internally, but I honestly don't see that as a problem. The garbage collector will take care of it for you.
Upvotes: 3