Craig Murphy dropped a little coding challenge on his blog the other day. This prompted me to write the responses as below. I’ve tried to target each one towards using a particular technology as the framework has developed. Have a look and tell me which is your preferred answer, or propose a new one (but again try to target a language feature). I’d love to know who you why you choose that language feature over another. Option 1. With Linq private const string alphabet = "abcdefghijklmnopqrstuvwxyz";
static void Main(string[] args)
{
char c = Console.ReadKey().KeyChar;
var myChars = from character in alphabet
where character <= c
select character;
var fullRow = myChars.Skip(1).Reverse().Concat(myChars);
var myRows = (from character in myChars
select ConvertToSpaces(character, fullRow));
var reverseRows = myRows.Reverse().Skip(1);
myRows = myRows.Concat(reverseRows);
Console.WriteLine(Environment.NewLine);//Skip the ReadKey line
foreach (string line in myRows)
Console.WriteLine(line);
Console.ReadKey();
}
private static string ConvertToSpaces(char wanted, IEnumerable<char> characters)
{
return new string(
(from character in characters
select character == wanted ? character : ' ').ToArray());
}
Option 2. With Generics
class Program
{
private const string alphabet = "abcdefghijklmnopqrstuvwxyz";
static void Main(string[] args)
{
char c = Console.ReadKey().KeyChar;
List<char> myChars = new List<char>();
foreach (char character in alphabet)
{
if (character <= c)
myChars.Add(character);
}
List<char> temp = new List<char>(Reverse<char>(AllButFirst<char>(myChars)));
temp.AddRange(myChars);
string fullRow = new string(temp.ToArray());
List<string> myRows = new List<string>();
foreach (char character in myChars)
{
myRows.Add(ConvertToSpaces(character, fullRow));
}
IEnumerable<string> reverseRows = AllButFirst<string>(Reverse<string>(myRows));
myRows.AddRange(reverseRows);
Console.WriteLine(Environment.NewLine);//Skip the ReadKey line
foreach (string line in myRows)
Console.WriteLine(line);
Console.ReadKey();
}
private static IEnumerable<T> Reverse<T>(IEnumerable<T> myChars)
{
Stack<T> reverse = new Stack<T>();
foreach (T character in myChars)
{
reverse.Push(character);
}
while (reverse.Count > 0)
{
yield return reverse.Pop();
}
}
private static IEnumerable<T> AllButFirst<T>(IEnumerable<T> myChars)
{
bool first = true;
foreach (T character in myChars)
{
if (first)
first = false;
else
yield return character;
}
}
private static string ConvertToSpaces(char wanted, IEnumerable<char> characters)
{
StringBuilder result = new StringBuilder();
foreach (char character in characters)
{
result.Append(character == wanted ? character : ' ');
}
return result.ToString();
}
}
Option 3. Using IEnumerable
class ProgramDNv2
{
private const string alphabet = "abcdefghijklmnopqrstuvwxyz";
static void Main(string[] args)
{
DiamondChars diamond = new DiamondChars();
diamond.chars = GetChars( Console.ReadKey().KeyChar);
foreach (string line in diamond)
{
Console.WriteLine(line);
}
Console.ReadKey();
}
private static string GetChars(char MidChar)
{
string chars = alphabet.Substring(0, alphabet.IndexOf(MidChar) + 1);
return chars;
}
private class DiamondChars : IEnumerable<String>
{
public string chars;
#region IEnumerable<string> Members
public IEnumerator<string> GetEnumerator()
{
int index = 0;
foreach (char character in chars)
{
yield return MakeLine(character, index);
index++;
}
index--;
foreach (char character in chars.Reverse())
{
if (index < chars.Length - 1)
{
yield return MakeLine(character, index);
}
index--;
}
}
#endregion
private string MakeLine(char character, int index)
{
string line = new string(chars[index], 1).PadLeft(chars.Length - index);
if (index > 0)
return line + new string(chars[index], 1).PadLeft(index * 2);
else
return line;
}
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}
Option 4. Simple recursion
class ProgramDNv1
{
static void Main(string[] args)
{
string input = "abcde";
StringBuilder result = new StringBuilder();
BuildDiamond(0, input, result);
Console.WriteLine(result);
}
private static void BuildDiamond(int index, string input, StringBuilder result)
{
string line = new string(input[index], 1).PadLeft(input.Length - index);
string line2 = new string(input[index], 1).PadLeft(index * 2);
result.Append(line);
if (index > 0)
result.Append(line2);
result.Append(Environment.NewLine);
if (index < input.Length - 1)
{
BuildDiamond(index + 1, input, result);
result.Append(line);
if (index > 0)
result.Append(line2);
result.Append(Environment.NewLine);
}
}
}
Now add a quick comment below telling you preferred implementation and briefly why. Thanks.
Need to talk through a proxy? Try this. You'll probably want to refactor in a factory method to allow the correct creation and re-creation of the WebRequest.
Note that attempt one uses the default proxy with Windows authentication parameters, subsequent attempts ask for a user name and password. Or at least they will once you convert the comment into a nice call to a GUI.
int retries = 0;
bool retry = false;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://alski.net");
HttpWebResponse response = null;
do
{
try
{
Console.WriteLine("Attempt " + retries.ToString());
response = (HttpWebResponse)request.GetResponse();
Console.WriteLine("Success");
}
catch (WebException wex)
{
Console.WriteLine(wex.Message);
if ( ((HttpWebResponse)wex.Response).StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
{
//Can't change proxy after request has been submitted so we recreate the request
request = (HttpWebRequest)WebRequest.Create(request.RequestUri);
switch (retries++)
{
case 0:
retry = true;
request.Proxy = WebRequest.GetSystemWebProxy();
break;
case 1:
case 2:
case 3:
request.Proxy = WebRequest.DefaultWebProxy;
//Show form to ask for credentials and maybe change proxy
request.Proxy.Credentials = new NetworkCredential("user", "passsword");
break;
default:
retry = false;
break;
}
}
}
}
while (response == null
&& retry);
I wrote this little piece of code a while ago, and the more I see it the more I'm proud of it. It allows you to create strongly typed objects from each row of a data result, and yield them, so you can dispose them individually without needing the whole list in memory. It also filters out null results so you can correlate data from external sources.
public delegate T CreationVisitor<T>(SqlDataReader reader);
public IEnumerator<T> Select<T>(CreationVisitor<T> visitor, string sql)
{
using (SqlConnection connection = new SqlConnection(_connectionString))
{
connection.Open();
using (SqlCommand shredderCommand = new SqlCommand(sql, connection))
{
shredderCommand.CommandTimeout = _timeOut;
using (SqlDataReader reader = shredderCommand.ExecuteReader(CommandBehavior.SingleResult))
{
while (reader.Read())
{
T dalObject = visitor(reader);
if (dalObject != null)
yield return dalObject;
}
}
}
}
}
Called as
return houseKeeper.Select<AuditContent>(
delegate(SqlDataReader reader)
{
string shredderFilename = (string)reader["file_name"].ToString();
…etc
if (File.Exists(guidFilename))
{
return new AuditContent(shredderFilename);
}
return null;
}, sql);
I'll have to try the re-write as a 3.5 Lambda function, it would be very terse then.
I seem to have problems remembering the syntax remembering the syntax for anonymous delegates, so here it is. return GetUnfilteredList().FindAll(new Predicate<MyObject>( delegate (MyObject instance) { return (instance.IsWanted)); })); What strikes me as a write this though is how confusing it is that the delegate doesn't have a return type. Maybe that's why I can never remember the syntax.
Cameron and myself were wondering what actually happens in various scenarios and so we came up with some Unit Tests. What we found
With local dlls it tries several paths
bin/Debug/TestAssembly.DLL.
bin/Debug/TestAssembly/TestAssembly.DLL.
bin/Debug/TestAssembly.EXE.
bin/Debug/TestAssembly/TestAssembly.EXE.
You need a full strong name to load from the GAC all four parts
You can get a strong name by attempting to load a dll locally i.e.
if you have a local dll with a strongname you can attempt to load it without a full strongname, and the local dll will be read to get the strongname and then the dll can be read FROM THE GAC
Yes, this last item seems a little insane, but please try the unit test if you don’t believe me.
Please let me know if you can think of more test scenarios to add.
I've been looking for an article on converting foreachs into a less memory intensive operation and I remember reading http://msdn2.microsoft.com/en-us/vcsharp/bb264519.aspx before, I just couldn't find it.
Basically it uses an example of all numbers in the New York phone book to develop a means of streaming the loops using custom iterators using yield instead of loading it all in and looping through it all.
With dotnet2.0 there a great set of functions added onto the generic list classes and their associates. I am particularly fond of anything which makes use of Predicate<T> like filter, but I've recently realised that this functionality is flawed.
The great 'Personal threading maturity model' barrier
How capable do you rate yourself with multi-threading? Fairly well, but that's a bit subjective isn't it. Have a quick look at the 'Personal threading maturity model', and hopefully you can give a more objective rating now. Until recently I was naive, probably a 2-3 on the Maturity model. I've used background workers to ease the UI and used lock based data structures to negotiate concurring problems.
Breaking the barrier
it may not be clear on today's hardware that further learning is necessary. Unfortunately, since manycore machines are not yet available
Larry O'Brien - January 2007
Well its now October 2007, and he was spot on. Personally I am starting to see my knowledge grow exponentially at the moment, and I can put it down to two factors.
Now I've replaced my old machine, I've become core aware. Multi-threading is now required to avoid the 50% flat out CPU graph.
At work, we are building a multi-processing system. Basically think of a big queue and a farm of application servers. Each application server picks up what it can handle according to current CPU/Memory constraints.
In both cases, the whole thing becomes a lesson in creating chunks of code that can be run in parallel. The easy bit is where we have piece of data and it goes through one path of logic. The challenge is where we have loops.
List<T>.Foreach
I now think that this functionality will NEVER be used. It will be consigned to the 'nice shame I doesn't respect manycore' bin. Instead people will be looking for MapReduce functionality, of which there are quite a few implementations already. Joel kicked off a rush of these implementations with his post Can Your Programming Language Do This?.
http://www.ericsink.com/entries/multicore_map.html
Strange, I can't find any other implementations, well that is apart from my own and that reeds refining yet.
Instead we want List<T>.Map(Predicate<T> fn) and List<T>.Reduce(Predicate<T> fn) and the only problem is they don't exist yet.
Strangely the static AssemblyName.ReferenceMatchesDefinition function is rather poorly documented in the MSDN documentation that came with my copy of Visual Studio 2005It does seem to work the way the name suggests.UpdateMSDN online has the correct info.Junfeng Zhang as always comes up with the implicit workingsAssemblyName.ReferenceMatchesDefinition Assembly Identity --- ReferenceIdentity and DefinitionIdentity, Comparison and Transformation
SummaryPros and Cons of upgrading to Visual Studio 2005 and dotNet 2.0
DotNet2.0
What's new http://msdn2.microsoft.com/en-US/library/7cz8t42e(VS.80).aspx and http://msdn2.microsoft.com/en-US/library/t357fb32.aspx
Pros
Assemblies should just compile. However there will be a large number of warnings. These can be switched off with either an attribute on the method(Visual Studio 2005 gives you a help link detailing this) or by using the #pragma warning in the file.
Cons
Deployment of ASP.NET seems very different. While web sites compiled against dotnet1.1 will just work when the server is updated to dotnet2.0, there does seem to be a big difference in the output of a dotnet2.0 compilation seems very different (unless that's just in debug)
Visual Studio 2005
Pros
Backwards compatibilty
If you download MSBEE from http://msdn.microsoft.com/vstudio/downloads/tools/msbee/default.aspx then you can compile against dotnet1.1 or dotnet2.0
ASP.Net
Web deployment now built into Studio 2005
IDE
Integrated reSharper
Refactor support built in (Method extraction, find ALL references, Rename)
Smart Tag support in IDE (Shift-Alt-F10) provides access to unknown types, or rename vars
Rename vars with Preview
Data support
Hugely improved Stongly typed dataset support in IDE - although investigation maybe needed to work out how to use with <XXXX>Framework
Cons
Will require upgrade, so costs for new licence.
The project files and solutions are in a new format. Studio automatically performs the conversion for you, BUT you can't save in a backwards compatible format. i.e. Unless everybody moves to VS2005 then we will either end up with parallel project files or developers using VS2005 cannot check in changes to projects into Version control.
Strange as it seems but “Server encountered an internal error. For more information, turn on customErrors in the server’s .config file.” is actually begging for you to add,
<system.runtime.remoting>
<customErrors mode="off"/>
</system.runtime.remoting>
Thanks go to Chris Taylor for pointing this out. I didn't think it ever seemed to work properly before!
http://dotnetjunkies.com/WebLog/chris.taylor/articles/5566.aspx