Brad Fitzpatrick, founder of Livejournal, has a blog post entitled Naming twins in Python & Perl where he writes

Last night at Beau's party, one of Beau's guests mentioned he's expecting twins shortly, which is why is wife wasn't at the party.
I drunkenly suggested he name his kids two names that were anagrams of each other. I then wandered off downstairs to find such suitable names.
Because I'm supposed to be be working in Python these days, not Perl, I gathered what little Python knowledge I had and wrote out:

#!/usr/bin/python

by_anagram = {}

names_file = open("dist.male.first")
for line in names_file:
    # lines in file look like:
    # WILLIAM        2.451 14.812      5
    # we want just the first field.
    name = (line.split())[0]
    letters = [letter for letter in name]
    letters.sort()
    sorted_name = "".join(letters)
    if not sorted_name in by_anagram:
        by_anagram[sorted_name] = []
    by_anagram[sorted_name].append(name)

for sorted_name in by_anagram:
    if len(by_anagram[sorted_name]) < 2:
        continue

    print by_anagram[sorted_name]
Not so happy with it, but it worked, and I printed out the results and brought them up to the guy

You can guess where this is going. Below is my solution in C# 3.0

using System;
using System.Linq;
using System.IO;

namespace Name_agram{
    class Program{
        static void Main(string[] args){
            var names    = from line in File.ReadAllLines("dist.male.first.txt")
                           let name = line.Split(' ')[0]
                           group name by new string(name.ToCharArray().OrderBy(x=>x).ToArray()) into anagrams
                           select anagrams;
                                        
            foreach (var list_of_names in names) {
                if (list_of_names.Count() > 1)
                    Console.WriteLine(String.Join(" ",list_of_names.ToArray()));
            }
            Console.ReadLine();
        }
    }
}

There are a ton of solutions in various languages in the comments to Brad's post. Again, it is startling to see how different an idiomatic C# 3.0 solution using LINQ is from the traditional imperative/procedural style of programming.

Now Playing: Rick James - Give It To Me Baby


 

Tuesday, 08 January 2008 17:56:23 (GMT Standard Time, UTC+00:00)
See how this is done in pure XSLT here:

http://dnovatchev.spaces.live.com/blog/cns!44B0A32C2CCF7488!357.entry


Cheers,
Dimitre Novatchev
Wednesday, 09 January 2008 08:28:28 (GMT Standard Time, UTC+00:00)
> name = (line.split())[0]
> letters = [letter for letter in name]
> letters.sort()
> sorted_name = "".join(letters)

I'd do something like this:

name, = line.split()
letters = sorted(list(iter(name))
sorted_name = ''.join(letters)

"name," does value unpacking. I've never liked indexing for some reason to get first elements.

These could be combined into one line but I still like keeping things readable.

> if not sorted_name in by_anagram:
> by_anagram[sorted_name] = []
> by_anagram[sorted_name].append(name)

You can make this a little more idiomatic using:

by_anagram.setdefault(sorted_name, []).append(name)

It looks up the value of sorted_name of the dictionary. If it exists, it returns it. If not, it sets the value of key sorted_name to the 2nd argument (which creates a list). It then returns the value of key sorted_name, which will now either be the existing list or the newly created list which you can directly append to.

> for sorted_name in by_anagram:
> if len(by_anagram[sorted_name]) < 2:
> continue

> print by_anagram[sorted_name]

for name in by_anagram.itervalues():
if len(name) >= 2:
print name
Jay
Wednesday, 09 January 2008 11:40:31 (GMT Standard Time, UTC+00:00)
If we're going to comment on the solutions, I'd like to suggest that the C#3.0 version should include:

...
group name by ... into anagrams
where anagrams.Count() > 1
select anagrams;

Now the foreach loop is a loop purely over the items you're interested in. All the filtering is done in the LINQ "query".

Cheers
Matt
Wednesday, 09 January 2008 15:34:59 (GMT Standard Time, UTC+00:00)
att's comments fall in line with what I did with my VB translation of your solution. Also, you can improve your group logic. Strings are enumerable types so you can say:

name.OrderBy(x => x).ToArray()

Intellisense is broken and doesn't show that as an option. But, yes, you can do that without having to convert the string to a char array first.

Alternately, you could say:

(from x in name orderby x select x).ToArray()
Wednesday, 09 January 2008 18:02:14 (GMT Standard Time, UTC+00:00)
Pingback from http://oakleafblog.blogspot.com/2008/01/dare-obasanjo-compares-linq-with-python.html (updated 1/9/2008).
Comments are closed.