anchor.diff: +open System
  +open System.Collections.Generic
   open System.IO
  +open System.Text
  +
   #time
   let ``pride and prejudice`` = __SOURCE_DIRECTORY__ + "../pride-and-prejudice.txt"
   let test = __SOURCE_DIRECTORY__ + "../test.txt"
  
positive.code: 
  // make the sorted list 1 bigger to prevent resizing
  let list = new System.Collections.Generic.SortedList<string, int>(25 + 1)
  let rec loop : string -> unit = function
      | null -> () // end of file
      | line -> 
          let [| word; (Int count) |] = line.Split(',')
          if list.Count < 25 then list.Add(word, count)
          else // swap if greater than the min
              let min = list |> Seq.minBy (fun kvp -> kvp.Value)
              if min.Value < count then
                  list.Remove(min.Key) |> ignore
                  list.Add(word, count)
                      
          loop <| reader.ReadLine()
  loop <| reader.ReadLine()    
  
  list 
  |> Seq.sortByDescending (fun kvp -> kvp.Value)
  
negative.code: 
  let input = new StreamReader(``pride and prejudice``)
  //let input = new StreamReader(test)
  
  let (|IsAlnum|_|) c = if System.Char.IsLetterOrDigit c then Some () else None
  
  // note: each line in word_freqs is "word,count", so these two funcs just make 
  // the code easier to read
  let getWord (x : obj) = (x :?> string).Split(',').[0]
  let getFreq (x : obj) = (x :?> string).Split(',').[1] |> Int32.Parse
  
  let findWords (line : string) =
      seq {
          data.[2] <- -1 :> obj
          data.[3] <- 0  :> obj
          for i = 0 to line.Length-1 do
              match line.[i], data.[2] :?> int with
              | IsAlnum, -1 -> // start of word
                  data.[2] <- i :> obj
                  data.[3] <- 1 :> obj
              | IsAlnum, _  -> // another char for word
                  data.[3] <- (data.[3] :?> int + 1) :> obj
              | _, -1 -> () // no word, not started, ignore
              | _ -> // end of word
                  yield line.Substring(data.[2] :?> int, data.[3] :?> int)
                  data.[2] <- -1 :> obj
                  data.[3] <- 0  :> obj
  
          if (data.[3] :?> int) > 0 then 
              yield line.Substring(data.[2] :?> int, data.[3] :?> int)
      }
      |> Seq.map (fun x -> x.ToLower())
      |> Seq.filter (fun x -> x.Length >= 2 && stopWords.Contains x |> not)
  
  let write (stream : Stream) word count =
      data.[5] <- sprintf "%s,%6d\n" word count :> obj
  
      printfn "writing %s %d" word count
  
      stream.Write(Encoding.ASCII.GetBytes(data.[5] :?> string), 0, (data.[5] :?> string).Length)
      stream.Flush()
  
  let incrFreq (word : string) =
      // since the reader reads 128 chars at a time, so use this to track where we are
      data.[6] <- 0L :> obj
      wordFreqs.Seek(0L, SeekOrigin.Begin) |> ignore
      use reader = new StreamReader(wordFreqs, Encoding.ASCII, false, 128, true)
  
      data.[7] <- reader.ReadLine() :> obj
      while data.[7] <> null && data.[6] <> null do        
          if word = getWord data.[7] then
              wordFreqs.Seek(data.[6] :?> int64, SeekOrigin.Begin) |> ignore
              write wordFreqs word (getFreq data.[7] + 1)
  
              // set read pos to null to indicate we've found and incremented count
              data.[6] <- null
          else
              // note: +1 here to account for the newline character
              data.[6] <- (data.[6] :?> int64 + int64 (data.[7] :?> string).Length + 1L) :> obj
              data.[7] <- reader.ReadLine() :> obj
  
      if data.[6] <> null then
          write wordFreqs word 1
      
  data.[1] <- input.ReadLine() :> obj
  while (data.[1] <> null) do
      data.[1] :?> string |> findWords |> Seq.iter incrFreq
      data.[1] <- input.ReadLine() :> obj
  
  // PART 2
  wordFreqs.Seek(0L, SeekOrigin.Begin)
  