0

I'm working on the Matasano Crypto Challenges but I'm running into an odd bug. I have a Seq of integers, and on a call to Seq.length (or Seq.sum), I get a 'System.InvalidOperationException' with the explanation of "The input sequence has an insufficient number of elements." Normally this error is associated with calling Seq.take or similar on an empty Seq, but this Seq is not empty nor is the call one that would normally cause such an exception.

let HammingDistance (b1:seq<byte>) (b2:seq<byte>) : int =
    let bytePairs = Seq.zip b1 b2
    let HammingWeight (b:byte) =
        let bitPositions = {0 .. 7}
        let isSet (byte1:byte) (offset:int) = if (byte1 &&& ((byte 1) <<< offset)) > (byte 0) then 1 else 0
        Seq.map (isSet b) bitPositions |> Seq.sum
    let HammingDistanceBytes (byte1, byte2) =
        HammingWeight (byte1 ^^^ byte2)
    let distances = Seq.map HammingDistanceBytes bytePairs
    printfn "Distances: %A" distances
    printfn "Distances length: %d" (Seq.length distances)
    Seq.sum distances

The last few lines of output before the exception follow:

Distances length: 1435
Distances: seq [3; 3; 1; 5; ...]
Distances length: 1436
Distances: seq [3; 6; 2; 1; ...]
Distances length: 1437
Distances: seq [0; 3; 2; 3; ...]
Distances length: 1438
Distances: seq [3; 3; 4; 2; ...]

Can anyone explain why this exception is happening, or give any steps I should take to further diagnose the problem?

Arcsech
  • 36
  • 4
  • I don't see anything wrong with your code - but since sequences are lazy, it might be problem with the inputs that you are passing to the function (e.g. if they are stateful in some way, they might be evaluated repeatedly which could break things). Does it work if you convert the input sequences to arrays/lists first? – Tomas Petricek Mar 07 '15 at 22:33
  • Laziness turned out to be the problem, but not in the way you were thinking - b1 and b2 are derived from a larger Seq, and should always be equal in size. However, 1,439 is the point at which one of those pieces is larger than half the original Seq. So none of the operations before that point are valid, because when b2 was created it should have thrown the exception. – Arcsech Mar 07 '15 at 22:45

1 Answers1

0

The problem here was related to lazy evaluation - b2 could not have been generated in the first place, as it was derived from a larger list:

// message:seq<byte>        
let firstKeysizeBytes = Seq.take keysize message
let secondKeysizeBytes = Seq.skip keysize message |> Seq.take keysize

The exception just doesn't pop until the eventual sequence is forced to evaluate. The error message placement is extremely confusing, but such is life with lazy evaluation. I should probably move to using a strict sequential type.

Arcsech
  • 36
  • 4