Skip to content

Commit

Permalink
Merge pull request #148 from gdziadkiewicz/feature/FixByteStringCompare
Browse files Browse the repository at this point in the history
Fix ByteString Compare.
  • Loading branch information
jackfoxy authored Nov 12, 2019
2 parents 23f421a + 6770a5d commit ab6664b
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 31 deletions.
25 changes: 17 additions & 8 deletions src/FSharpx.Collections/ByteString.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@ type ByteString(array: byte[], offset: int, count: int) =

/// Compares two byte strings based on their structure.
static member Compare (a:ByteString, b:ByteString) =
let x,o,l = a.Array, a.Offset, a.Count
let x',o',l' = b.Array, b.Offset, b.Count
if o = o' && l = l' && x = x' then 0
elif x = x' then
if o = o' then if l < l' then -1 else 1
else if o < o' then -1 else 1
else let left, right = x.[o..(o+l-1)], x'.[o'..(o'+l'-1)] in
if left = right then 0 elif left < right then -1 else 1
let x, o, l = a.Array, a.Offset, a.Count
let x', o', l' = b.Array, b.Offset, b.Count
if LanguagePrimitives.PhysicalEquality x x' && o = o' then
if l = l' then
0
elif l < l' then
-1
else
1
else
if l < l' then
-1
elif l > l' then
1
else
let left, right = x.[o..(o+l-1)], x'.[o'..(o'+l'-1)]
LanguagePrimitives.GenericComparison left right

/// Compares two objects for equality. When both are byte strings, structural equality is used.
override x.Equals(other) =
Expand Down
83 changes: 60 additions & 23 deletions tests/FSharpx.Collections.Tests/ByteStringTest.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,60 @@ open Expecto.Flip
module ByteStringTests =
type BS = ByteString

let comparisonTests = [|
[| box (ByteString.create ""B); box (ByteString.create ""B); box 0 |]
[| box (ByteString.create "a"B); box (ByteString.create "a"B); box 0 |]
[| box (ByteString.create "a"B); box (ByteString.create "b"B); box -1 |]
[| box (ByteString.create "b"B); box (ByteString.create "a"B); box 1 |]
|]
let comparisonTests = [
// When the base array is different
"empty1", ByteString.create ""B, ByteString.create ""B, 0
"empty2", ByteString.create ""B, ByteString.create "a"B, -1
"empty3", ByteString.create "a"B, ByteString.create ""B, 1

"same1", ByteString.create "a"B, ByteString.create "a"B, 0
"smaller1", ByteString.create "a"B, ByteString.create "b"B, -1
"bigger1", ByteString.create "b"B, ByteString.create "a"B, 1

"longer1", ByteString.create "aa"B, ByteString.create "a"B, 1
"shorter1", ByteString.create "b"B, ByteString.create "aa"B, -1

//when the base array is the same
let x = "baab"B
"same2", ByteString(x,0,1), ByteString(x,0,1), 0
"same3", ByteString(x,0,1), ByteString(x,3,1), 0
"shorter2", ByteString(x,0,1), ByteString(x,0,2), -1
"shorter3", ByteString(x,0,1), ByteString(x,2,2), -1
"longer2", ByteString(x,0,2), ByteString(x,0,1), 1
"longet3", ByteString(x,1,2), ByteString(x,0,1), 1
"longer4", ByteString(x,2,2), ByteString(x,0,1), 1
]

let spanAndSplitTests = [|
[| box "Howdy! Want to play?"B; box ' 'B; box 6 |]
[| box "Howdy! Want to play?"B; box '?'B; box 19 |]
[| box "Howdy! Want to play?"B; box '\r'B; box 20 |]
"Howdy! Want to play?"B, ' 'B, 6
"Howdy! Want to play?"B, '?'B, 19
"Howdy! Want to play?"B, '\r'B, 20
|]

[<Tests>]
let testByteString =
testList "ByteString" [
test "test ByteString comparison should correctly return -1, 0, or 1" {
comparisonTests
|> Array.iter (fun x -> BS.Compare(unbox x.[0], unbox x.[1]) |> (Expect.equal "comparison" <| unbox x.[2]) ) }

testList "test ByteString comparison should correctly return -1, 0, or 1"
(comparisonTests
|> List.map (fun (name, x, y, expectedResult) -> test name { BS.Compare(x, y) |> Expect.equal "comparison" expectedResult }))


test "test ByteString_length should return the length of the byte string" {
let input = ByteString.create "Hello, world!"B
Expect.equal "length" 13 <| ByteString.length input }

test "test ByteString_span correctly breaks the ByteString on the specified predicate" {
spanAndSplitTests
|> Array.iter (fun x ->
let input = unbox x.[0]
let breakChar = unbox x.[1]
let breakIndex = unbox x.[2]
|> Array.iter (fun (input, breakChar, breakIndex) ->
let str = ByteString.create input
let expected = if input.Length = breakIndex then str, ByteString.empty
else BS(input, 0, breakIndex), BS(input, breakIndex, input.Length - breakIndex)
Expect.equal "ByteString * ByteString" expected <| ByteString.span ((<>) breakChar) str ) }

test "test ByteString_split correctly breaks the ByteString on the specified predicate" {
spanAndSplitTests
|> Array.iter (fun x ->
let input = unbox x.[0]
let breakChar = unbox x.[1]
let breakIndex = unbox x.[2]
|> Array.iter (fun (input, breakChar, breakIndex) ->
let str = ByteString.create input
let expected = if input.Length = breakIndex then str, ByteString.empty
else BS(input, 0, breakIndex), BS(input, breakIndex, input.Length - breakIndex)
Expand Down Expand Up @@ -114,9 +127,33 @@ module ByteStringTests =
Expect.equal "ByteString" (BS(input, 0, 5)) <| (ByteString.takeWhile ((<>) ' 'B) (ByteString.create input)) }

test "test takeUntil should return an empty ArraySegment when given an empty ArraySegment" {
Expect.equal "empty ByteString" ByteString.empty <| ByteString.takeUntil ((=) ' 'B) ByteString.empty }
Expect.equal "empty ByteString" ByteString.empty <| ByteString.takeUntil ((=) ' 'B) ByteString.empty }

test "test takeUntil should correctly split the input" {
let input = "abcde"B
Expect.equal "ByteString" (BS(input, 0, 2)) <| ByteString.takeUntil ((=) 'c'B) (ByteString.create input) }
let input = "abcde"B
Expect.equal "ByteString" (BS(input, 0, 2)) <| ByteString.takeUntil ((=) 'c'B) (ByteString.create input) }

testProperty "test if ByteString Compare follows lexicographic order" <| fun xs ys ->
BS.Compare(ByteString xs, ByteString ys) = LanguagePrimitives.GenericComparison xs ys

testSequenced <| testList "performance" [
test "Comparision should only compare relevent part of Array" {
let veryLargeArray1 = Array.init 2000 byte |> ByteString.create
let veryLargeArray2 = Array.init 2000 byte |> ByteString.create
let compareVeryLargeArray () = BS.Compare(veryLargeArray1, veryLargeArray2)

let theExactSameByteString1 = ByteString.take 2000 veryLargeArray1
let theExactSameByteString2 = ByteString.take 2000 veryLargeArray1
let compareExactSameByteArray () = BS.Compare(theExactSameByteString1, theExactSameByteString2)

let smallByteArray1 = ByteString.take 100 veryLargeArray1
let smallByteArray2 = ByteString.take 100 veryLargeArray2
let compareSmallByteArray () = BS.Compare(smallByteArray1, smallByteArray2)

Expect.equal "" (compareVeryLargeArray()) (compareExactSameByteArray())
compareExactSameByteArray |> Expect.isFasterThan "" compareVeryLargeArray
compareExactSameByteArray |> Expect.isFasterThan "" compareSmallByteArray
compareSmallByteArray |> Expect.isFasterThan "" compareVeryLargeArray
}
]
]

0 comments on commit ab6664b

Please sign in to comment.