You are viewing [info]zandaleph's journal

Zack
22 August 2009 @ 01:25 pm

Having been challenged with sorting a linked list, and trying to brush up on my C++ at the same time, I set out this morning to best the challenge. I decided I would use the STL list type so that I could avoid the boilerplate of writing a linked list class and also have a built-in sort() algorithm against which to test the vailidity and performance of my version.

So, the first question is which sorting algorithm to use? Quicksort is the canonical answer from arrays, because it requires constant auxillary space in which to operate (typically just one element). However, quicksort also relies upon constant time access to any element in the array in order to maintain its O(N log(N)) performance. So, since quicksort won't work, the other obvious solution from arrays is mergesort. When operating on arrays, mergesort requires O(N) additional space to perform the merging step. (Technically this isn't true, but without the additional space mergesort takes longer to run.)

The key fact about mergesort on linked lists is that it no longer requires O(N) additional space. In fact, it can be done with zero additional space! This is because of the advantage linked lists have over arrays in insertion speed. Arbitrary insertion in a linked list (assuming you already have a pointer to the location you're inserting to) is constant time, whereas arrays require O(N) time to shift back all the items following the insertion by one space. Now instead of interleaving two sorted arrays into a "merged" array, we can simply insert items from the second sorted list into the first sorted list where they belong.

Typically, mergesort works by splitting the list in half and then recursing. Once you get down to zero or one element lists, you can guarantee that you are sorted and can start going back up the call stack. I want to say something like "this is impractical for linked lists", but the reality is that you have to walk through the entire list log(N) times, one way or another. I wanted to write a version that didn't recurse, so I did my mergesort backwards. On the first pass, we simply sort pairs of items. After that, each pair is sorted, so we can merge pairs. Next step is to merge adjacent 4-element pairs, and so on and so forth. Another way of looking at it is that instead of splitting the list into 2 parts, then 4, then 8, and so on, I'm splitting it into N/2 parts, then N/4, then N/8, and so on. This lets me re-use the right boundary pointer from the previous merge as the left boundary pointer for the next merge.

My first pass was as follows:
#include <list>

using namespace std;

template <typename T>
void mySort(list<T> &l)
{
    int i, stepsize = 1, listsize = l.size();
    typename list<T>::iterator left, middle, right, end=l.end();    
    for (stepsize = 1; stepsize < listsize; stepsize <<= 1) {
        right = l.begin();
        while ( right != end ) {
            left = middle = right;
            for (i = 0; i < stepsize && middle != end; i++) middle++;
            right = middle;
            for (i = 0; i < stepsize && right != end; i++) right++;
            while ( left != middle && middle != right ) {
                while ( left != middle && *left <= *middle )
                    left++;
                while ( middle != right && *middle < *left ) {
                    l.insert(left, *middle);
                    l.erase(middle++);
                }
            }
        }
    }
}
A few notes about the above code:

I use a local variable to store a reference to l.end() for the entire function. This saves me about half a second on a 10,000 element list versus calling l.end() each time. I would do the same thing for l.begin(), but because I might change the first element of the list I can't. Luckily, I only need to call l.begin() log(N) times.

I would use the advance() function to replace the for loops stepping through the list, but advance doesn't check against l.end(), and (at least my version of) the STL list is circular, in that ++l.end() == l.begin()! So, I have to write my own.

Finally, I wasn't sure if l.erase(middle++) was going to work like I wanted. lists are much more flexible when it comes to keeping iterators valid through insertions and deletions, but the one thing my STL book was sure to point out was that pointers to deleted elements would become invalidated. An hour or so after I wrote this and saw that it did indeed work, I found that on the next page it pointed out this exact line in another example and said that it was ok, but the seemingly similar l.erase(middle); middle++ was not. The difference is that in the correct version, middle already points to the next element before the previous element is deleted.

So, I wrote some boilerplate testing code and compared the result of this function against list.sort and found that it worked just fine, but it took approximately 3 times as long to run. I loaded up my profiler and tried to see where all the time was going, but all I could see was that a lot of time was spent freeing memory. Taking a glance at how the STL had done it, I noticed they were making use of a function named splice(). I looked at it and realized that it did exactly what I had meant when I wrote the insert / erase pair. Once I replaced that line with:

l.splice(left, l, middle++);

My version was only 10% slower than the STL's version. Not half bad; in two hours I had come very close to the reference implementation. I spent some more time trying to understand the STL's version, and found some very cool tricks with pointers (and an array of 64 lists, which empty only take up 8 bytes apiece), but felt that my version was more immediately readable. Of course, I had just written mine, y'all can be the judge of readability.

Over breakfast I had the idea that instead of using a set stepsize I could simply increment the middle and right pointers as long as the previous element was less than it, effectively taking advantage of any natural sorting the list might have. Unfortunately, with all the additional variables and assignments (plus a need to move onto other things) I was unable to get it to perform better than my original implementation. As it stands here, it runs about 15% slower than the STL's sort, or about 2% slower than my original.

template <typename T>
void mySort2(list<T> &l)
{
    typename list<T>::iterator left, middle, right, end=l.end(), 
                               lastleft=end, temp;
    while ( lastleft != l.begin() ) {
        lastleft = right = l.begin();
        while ( right != end ) {
            left = temp = middle = right;
            while ( ++middle != end && *middle >= *temp) { temp = middle; }
            if ( middle != end ) {
                temp = right = middle;
                while ( ++right != end && *right >= *temp) { temp = right; }
                lastleft = left;
            } else if ( left == lastleft ) {
                break;
            } else { // middle = end, so merge [lastleft, left) w/ [left, end)
                right = end;
                middle = left;
                left = lastleft;
            }
            while ( left != middle && middle != right) {
                while ( left != middle && *left <= *middle )
                    left ++;
                while ( middle != right && *middle < *left )
                    l.splice(left, l, middle++);
            }
        }
    }
}

As a final note, replacing the merging section with temporary lists and calls to list.splice and list.merge doesn't seem to improve performance by an appreciable degree. What they would offer is an easier way to encapsulate a variety of comparison operators instead of just using less than. Oh well, enough duplication of library code for now.

 
 
Zack
04 August 2009 @ 07:52 am
 Fell off my bike again yesterday.  No broken bones this time, but my knees aren't going to be thanking me for anything anytime soon.

Also had a horrible nightmare last night that someone had gotten root access on my machines and was making my life very difficult.  Combined with heal-thyself sleep, there were more than a few times I woke up and had a hard time convincing myself it hadn't actually happened.

Also, I'm buying a house this month.  That's pretty cool.
 
 
Zack
05 June 2009 @ 05:40 pm
Cynthia and I want to go hiking this weekend.  I've been working like a maniac all week, so I thought she'd get it all figured out, but I keep forgetting that she just moved here, and I've got about 8 years of experience on her.  So we're going to go hike Mount Pilchuck tomorrow.  Why there?  Because I've never been and I've heard great things about it, but mostly because I want to understand how Adam got lost there my sophomore year :-p

If you want to come, let us know!  According to google maps (here) it's a little less than 2 hours away.  Add in ~4 hours for the hike (detailed here) and we're looking at 8 hour round trip.  I'm doing all this math to justify leaving at 10am.  We've got room for two more (three at the outside) in our car, but we can take more if additional car owners want to come.  The more the merrier, we say.

Since I like bulleted lists, here's the information again in a readable format:
  • Where: Mt. Pilchuck (right next to Mt. Baker)
  • When: Saturday 6/6/09 leaving 10am from Seattle (start hiking a little before noon)
  • Who: Anyone who wants to come
  • Contact: Me via comment, phone, email, whatever
That's all for now!  Recap to follow.
 
 
Zack
25 May 2009 @ 11:53 am
So yes, I broke my arm.  However, it is not as dire as it could be; in fact I am almost fully functional.  Here's what happened and what's up:

Got back from our honeymoon Friday night (thanks again to Mr. Hacking who gave us a ride from SeaTac to our door).  Saturday morning woke up early because of jet lag, decided to get started on our promise to start biking with each other in the mornings.  After getting tires inflated and everything set, we set off to ride the burke (a nearby bike trail).  We weren't even off the first block when I found myself much too close behind cynthia and slammed on my brakes.  Apparently I slammed too hard because I went over the handlebars.  Left arm felt pretty bad, and I was again glad to be wearing a helmet.  Nothing felt too bad, so we hiked back to the apartment and put some ice on the arm.  Ended up sitting around at home for three hours reasoning that it was just a bad strain.  Eventually tried to get up to wash my hands, only to realize that I was in a lot more pain than I had bargained for.  We went to the hospital.

We were joined in the elevator to the emergency room by a nice woman who asked me a few questions about what I was there for.  Turned out she was the head nurse in the pit that day, so I was being diagnosed before I even signed in.  Three hours, a few x-rays, and some painkillers later, I was diagnosed as having a broken radius (right through the neck near the elbow), and sentenced to a week in a sling, with more x-rays to follow.  Oh, and a small prescription for painkillers

So, how am I now?  Doing alright.  I'm arguably able to take care of myself again, but am not much use around the house, sadly.  also, my right wrist is starting to complain about the one handed typing, so I'm gonna try to keep that to a minimum.  Of course, I'm back to work tomorrow, so we'll see how long that lasts...
 
 
Current Mood: okayokay
 
 
Zack
16 May 2009 @ 08:23 am
So I've been working on some toy code, and after a few passes I decide...

that pressing preview in the LJ editor deletes 95% of my post?!?  wtf fail.
 
 
Current Mood: disappointeddisappointed
 
 
Zack
13 May 2009 @ 07:31 pm
Having been soundly reprimanded for even considering sharing the fun, I will simply note that there have been numerous small annoyances with the accommodations.  One of which required a carpenter.  NOT OUR FAULT.

Also dried avocado leaves make a very interesting spice.

And now back to whatever it was I was doing.  Ah yes, enjoying the sunset. 
Tags:
 
 
Current Mood: happyhappy
 
 
Zack
12 May 2009 @ 11:35 am
Cynthia and I are now safely at our resort in Zihuatanejo, and the bay is absolutely gorgeous.  Getting here, however, was not without its fair share of difficulties.

We enjoyed another session of Dungeons and Dragons with the Hot Dice group (working title), and between our honeymoon and other member's other engagements it looks like it was our last for a month, I'm supposed to be sending another email about availability sometime this week.  Afterwards we prepped Ry for housesitting for us while we were gone, and then began packing.  Mike took us to the airport at about 10, and we were there in plenty of time for our flight, which was scheduled for an 11:45p takeoff.

And then our flight reservation didn't exist anymore.

It took a few minutes to figure out what was being communicated, but somehow we had ended up with flight reservations that looked like this:

Seattle to Houston
Depart: Sunday, May 10th 11:45pm PDT
Arrive: Monday, May 11th 5:50am CDT

Houston to Ixtapa / Zihuatanejo
Depart: Sunday, May 10th 11:30am CDT
Arrive: Sunday, May 10th 2:00pm CDT

Needless to say, this isn't what I had arranged when I purchased the tickets.  So I asked them to place us on the Monday flight to Zihuatanejo from Houston, and the friendly rep told us that Continental only flew from Houston to Ixtapa on Wednesday, Friday, and Sunday.

Crap.

A word of advice for situations like this: Don't Panic.  Almost unanimously situations like this are the airline's fault (as they would end up being in this case), and they will get you to where you belong as close to on-time as humanely possible as long as you apply calm, gentle pressure for them to do so.  In our case, it took an hour on the phone, bouncing back to my parents between each call for more numbers to dial since I didn't have the internet at my own fingertips.  The winning phone number ended up being Expedia, whom quickly connected me to a Continental rep (Continental put me on indefinite hold when I called them directly).  The rep started by saying I was SOL, but eventually discovered that Continental had originally scheduled the Houston to Ixtapa flight on Monday, and then had changed when the flight would be.  After that, we found ourselves the proud owners of an Alaska Air flight, scheduled to leave at 7am the next morning and get us there only two hours later than our original reservations.

The flight down here was nice and not overly long (it helped that we were able to sit in the exit row for both legs of the trip).  On the LAX to ZIH leg we sat across from another newlywed couple who has just been married two days prior in Long Beach, Zach and Lauren.  Customs was fairly straightforward (zapped in the head with an instant read thermometer), and immediately after exiting we met a representative from the resort who took us to a taxi which took us straight here (about a 15 minute drive).  The view is stunning, a 100 degree unobstructed view of almost the entire bay from about 100 feet above the water, and visible from almost anywhere in our suite.

More on the suite in the next post, it seems our trip is destined to be with quirks.  For now, I think I might go enjoy the swim-up bar.  With cute little psuedo-stools build into the floor of the pool.  and 2 for 1 Daquiries.
 
 
Current Mood: relaxedrelaxed
 
 
Zack
28 April 2009 @ 09:57 am
Cynthia and I are going to learn the dance to "Thriller" tonight at our apartment, and we'd love for people to come by and learn it with us.  We'll be doing the dance at our reception on Saturday.  The fun starts at 8pm, call me if you need directions. 
 
 
Zack
12 April 2009 @ 11:47 am
When I purchased my Apple G5 Tower in 2004, I went all out.  Shiny options, a huge monitor (23", zomg), and a huge price tag to match.  I was willing to put forth so much money because of two things:  1.  I wanted to give Macs the same chance I would give PCs, and 2.  I promised myself that I would buy no other computer for 5 years.  Looking back, I ended up breaking that promise to buy a 12" G4 laptop on clearance (for class, I realized I type much faster than I write), but I believed that I was still staying true to the spirit of the promise, which was to not replace the G5.

The five year mark came and went, and though Apple had just released shiny new computers, I found myself bogged down by a simple question.  Did I really need a new computer?  Despite two Video Card failures (one under AppleCare in the first two months, and one after 4 and a half years), the machine was still running quite strong.  I experienced none of the problems which would have been inevitable with a PC (I have yet to reinstall the OS, actually), and I was still finding new and exciting ways to use the computer.

What was worse, I thought Cynthia was going to make me get rid of the G5 once I got a new computer.  Even if I was ready for an Intel-based Mac, I wasn't ready to get rid of the machine that had introduced me to Unix (and OS X!), that had seen me through the bulk of my CS education.  Plus, I can see uses for the machine even after I move on to a new day-to-day machine.  Luckily, this turned out to be a misunderstanding.  Cynthia didn't see why I needed two laptops, and had been talking about the 12" G4 I had purchased after the G5.

In expressing to my father the oddity of having waited so long for something only to decide I didn't want it, he mentioned that he, too, was surprised I hadn't bought a new machine.  I don't remember the whole contents of the conversation, but I walked away with a new understanding that sometimes, it isn't about what you need, but what you want.

But what did I want?  Telecommuting, it seemed that perhaps a laptop was a more useful option than a new desktop.  Plus, almost my entire office uses laptops as desktop machines (I was definitely in the minority with my two towers, one running XP, one running Ubuntu), so I knew that the gap between laptop and tower was closing.  But which laptop did I want?  Certainly at least 15", but even those were running for quite a penny, and I'm trying to pay for a wedding in the month.

The answer came a week ago early monday morning.  I decided to peruse the Apple store, and found the links for clearance and refurbished machines in the lower left corner.  The clearance options were fairly limited, but they did have a 17" MacBook Pro (I have since learned that MBP is the preferred spelling), of the previous generation, for a very reasonable price.  After a quick call to their phone reps to make sure it would come with 10.5 and iLife, the purchase was made, and arrived thursday evening.  I spent the evening upgrading various software components and tweaking a few obvious parameters (the dock does not need icons for every installed app on the machine).

In the morning, I ran boot camp and installed Windows 7 Beta on it's own partition.  It runs very nicely.  It also runs games very nicely.  In the afternoon I installed VMWare Fusion, to try running the two OSes side by side.  Visio ran acceptably, games did not.  So much for their advertisements.  Played through Braid on Saturday, a very enjoyable game, if a bit convoluted story-wise.

Notes on OS X software: Migration assistant failed spectacularly, but without damaging anything.  In copying feed urls over (for comics), I discovered using curl and wc that RSS 2.0 is, in general, a more concise feed format (in comparison to 1.0 and Atom).  iTerm is an excellent Terminal.app replacement, though it took a while to find that the xterm title controls the window title, and the xterm icon title controls the tab title.  Machine is still clean as a whistle, but then again, I've only installed 5 new applications.  We'll see how long that lasts.
 
 
Zack
01 March 2009 @ 09:59 am
So, the customer came back and said "we need user permissions." Thus far, our idea of permissions goes something like:

if self.id == "admin":
self.admin = 1
Now, we have this nice user database code, which nicely encapsulates the notion of users and checking logins, etc. It was this code that I opened to see what I could twiddle to hack in some permissions in the week before release. It looked pretty standard (for us, which means that we had to write our own file-locking library), and I was thinking this would be an easy hack until I realized something was amiss. The web interface lets you change your password, but there was no exposed method for modifying a user's password. So I looked at the web code and found this:

def edit(self, **kwargs):
usercfg = cfg(locations.userdb)
userdict = usercfg.getcfg()
# validation code here...
userdict[kwargs["username"]] = kwargs["password"]
usercfg.write()
usercfg.done()
Notice the lack of calls to, say, the userdb library (the cfg library is our file-locking library).  So instead of writing this code at the library level, we wrote it at the web level, using the same low-level libraries we would have used in the userdb library.  Fine, this isn't the first time I've seen this; it won't be the last.  But what is maddening is that even those places where we did have corresponding functionality in the library, we wrote it from scratch for the web!

So even if I do hack the base library, I'll basically have to redo all of the web stuff anyway, instead of just a few select parts.  I wish this didn't seem like just business as usual.

Tags: