Sunday 21 December 2014

Weekend code puzzle: Adam Presley's answer - redux (Go)

G'day:
Adam Presley is a bit of a star. I had a look at his original entry to the code puzzle and decided I didn't like it as it was too long-winded ("Weekend code puzzle: Adam Presley's answer (Go)"). He's subsequently come back to me with a simplified version, listed on his own blog: Response To Adam Cameron's Code Review. He took my comments in good grace, which is very good of him.

Let's have a look at this new version:


// getSubseries.go

package main

import (
    "flag"
    "fmt"
    "os"
    "strconv"
    "strings"
)

type SubArrayResult struct {
    Sequence []int
    Total    int
}

var input = flag.String("input", "", "Comma delimited list of integer numbers")
var threshold = flag.Int("threshold", 10, "Integer number for array slice to not exceed")

func main() {
    flag.Parse()

    /*
     * Array of integers to determine longest sequence from. These are a string
     * list on the command line that are converted to integers.
     */
    inputArray := make([]int, 0)

    if *input == "" {
        fmt.Println("Please provide a valid input of comma delimited integers")
        os.Exit(1)
    }

    /*
     * Get the input from the console args, and split them up
     * into integers in an array
     */
    for _, value := range strings.Split(*input, ",") {
        i, err := strconv.Atoi(value)
        if err != nil {
            fmt.Println("ERROR - Unable to convert", value, "to integer")
            os.Exit(1)
        }

        inputArray = append(inputArray, i)
    }

    /*
     * Loop, slice, sum
     */
    sequences := make([]SubArrayResult, 0)

    for index, _ := range inputArray {
        sequence := make([]int, 0)
        total := 0

        inputSlice := inputArray[index:]

        for _, value := range inputSlice {
            if (total + value) <= *threshold {
                total += value
                sequence = append(sequence, value)
            } else {
                break
            }
        }

        if len(sequence) > 0 {
            result := SubArrayResult{
                sequence,
                total,
            }

            sequences = append(sequences, result)
        }
    }

    /*
     * Who wins?
     */
    longestIndex := 0
    longestLength := 0

    for index, item := range sequences {
        if len(item.Sequence) > longestLength {
            longestLength = len(item.Sequence)
            longestIndex = index
        }

        if len(item.Sequence) == longestLength && item.Total > sequences[longestIndex].Total {
            longestLength = len(item.Sequence)
            longestIndex = index
        }
    }

    if len(sequences) > 0 {
        winningSequence := sequences[longestIndex]

        fmt.Println("")
        fmt.Println("Sequence", winningSequence.Sequence, "wins with a length of", len(winningSequence.Sequence), " for a total of", winningSequence.Total)
    } else {
        fmt.Println("No winners :(")
    }
}

I won't go through the syntactical minutiae here, as I also need to write up my own answer using Go, and I think I use all the constructs in my code as are used here, so will discuss in one place.

It runs from the commandline, thus:


C:\src\go\src\adampresley>go run getSubseries.go -input=100,50,50,50,50,50,500,100,60,60,60,60,60,500 -threshold=500

Sequence [100 60 60 60 60 60] wins with a length of 6  for a total of 400

C:\src\go\src\adampresley>

That was the trickiest test, and it passed fine. I also ran the one that tripped up his original version:


C:\src\go\src\adampresley>go run getSubseries.go -input=600,700,800,900 -threshold=500
No winners :(

C:\src\go\src\adampresley>


It's good to see he's fixed that one.

This version is much more to the point than the previous version, which is good. Adam's had an interesting approach here: he extracts all the subseries which fall within the threshold, and then examines those to pull out the most fitting one. This'll be a byproduct of his initial approach which used parallel processing to expedite matters. He explains this on his initial entry which is on Github, here: https://github.com/adampresley/adamCameronCodeChallenge201411. One thing I do like about his initial take on things is to leverage Go-specific features like this. My approach with all languages so far has pretty much taken the same route, and is using very generic code: the logic and approach has always been the same, with the differences being down to how various languages implement the coding elements I'm using. This is useful too, as it makes for an interesting comparison, even if it's not the ideal approach to solving the challenge for the language concerned.

From a code perspective, I might have factored-out some of the phases here into helper functions:
  • preprocess the command line args
  • the main function which returns the result
  • within that have sub functions for extracting the subseries
  • and finding the best one
The main() function is pretty long here. And the comment blocks might not be necessary if the code was sub-functioned.

I'm bloody pleased Adam put both these entries in, especially the second one. I'd never set out to look at Go before, but our conversation has piqued my interest in it now. As a result I felt compelled to work through my own answer, which took about 8h in total to write, debug, test (yeah, sorry, no TDD on this one) and re-debug. The good thing is now I can understand Adam's code above just fine, and I think I'll put some effort into going through his initial entry now. I want to know about these "goroutines", for one thing.

Cheers mate!

--
Adam

PS: I was hoping to write-up my own Go solution today, but I got off to a late start, and working out how to test it, then tracking down a glitch the tests revealed took me longer than the hour-or-so I was expecting to take. I've got a bit of a truncated afternoon today as my mate Leanne has scored us some tickets to go see The Cure, so I need to head over to the other side of London to do that, soonish. This is cool... I was a huge Cure fan in my formative years (even - occasionally - with pointy-up hair, etc), and whilst my musical whim has moved on a bit now, I still really like their stuff and have not been to a gig of any description since Glastonbury 2012, so really looking fwd to it. Time to go put my lippy on, and do some back-combing...