Skip to content

Commit

Permalink
feat(qrcode): refactor binarySearchVersion function signature, and pr…
Browse files Browse the repository at this point in the history
…epare testcases
  • Loading branch information
yeqown committed Dec 2, 2021
1 parent 9ea54e8 commit a276223
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 46 deletions.
38 changes: 28 additions & 10 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,23 @@ func (v version) formatInfo(maskPattern int) *binary.Binary {
return result
}

var emptyVersion = version{Ver: -1}

// binarySearchVersion speed up searching target version in versions.
// initVer to set the low and high bound of the search range. TODO(@yeqown): refactor initVer as low, high
// low, high to set the left and right bound of the search range (min:0 to max:159).
// compare represents the function to compare the target version with the cursor version.
// negative means lower direction, positive means higher direction, zero mean hit.
func binarySearchVersion(initVer int, compare func(*version) int) (hit version, found bool) {
low := 0
high := len(versions) - 1

if initVer > 0 {
// each version only has 4 items in versions array,
// and them are ordered[ASC] already.
high = initVer*4 - 1
low = (initVer - 1) * 4
func binarySearchVersion(low, high int, compare func(*version) int) (hit version, found bool) {
// left low and high in a valid range
if low > high || low > _VERSIONS_ITEM_COUNT || high < 0 {
return emptyVersion, false
}

if low < 0 {
low = 0
}
if high >= _VERSIONS_ITEM_COUNT {
high = len(versions) - 1
}

for low <= high {
Expand All @@ -228,6 +232,20 @@ func binarySearchVersion(initVer int, compare func(*version) int) (hit version,
return hit, found
}

// defaultBinaryCompare built-in compare function for binary search.
func defaultBinaryCompare(ver int, ec ecLevel) func(cursor *version) int {
return func(cursor *version) int {
switch r := ver - cursor.Ver; r {
case 0:
default:
// v is bigger return positive; otherwise return negative.
return r
}

return int(ec - cursor.ECLevel)
}
}

// loadVersion get version config by specified version indicator and error correction level.
// we can speed up this process, by shrink the range to search.
func loadVersion(lv int, ec ecLevel) version {
Expand Down
8 changes: 8 additions & 0 deletions version_cfg.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package qrcode

const (
_VERSION_COUNT = 40 // (40 versions)
_VERSIONS_ITEM_COUNT = 160 // (40 versions x 4 error correction level)
)

// versions contains information about each QR Code version.
// NOTICE: item in version array MUST keep sorted according to
// QR Version sequential as the first key and Error Correction Level as the second (ASC).
var versions = []version{
{
Ver: 1,
Expand Down
110 changes: 74 additions & 36 deletions version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,68 +128,106 @@ func Test_analyzeVersion(t *testing.T) {
func Test_binarySearchVersion(t *testing.T) {
t.Logf("length of versions: %d", len(versions))

find := func(v int, ec ecLevel) func(cursor *version) int {
return func(cursor *version) int {
if cursor.Ver == v && cursor.ECLevel == ec {
return 0
}

if cursor.Ver > v || cursor.ECLevel > ec {
return -1
}

return 1
}
type args struct {
low, high int
v int
ecLv ecLevel
}

tests := []struct {
name string
ecLv ecLevel
args args
v int
// the position of the expected version in versions array
// [0...159]
want int
}{
{
name: "case 0",
ecLv: ErrorCorrectionLow,
v: 1,
args: args{
low: 0,
high: _VERSIONS_ITEM_COUNT,
ecLv: ErrorCorrectionLow,
v: 1,
},
want: 0,
},
{
name: "case 1",
ecLv: ErrorCorrectionHighest,
v: 40,
args: args{
low: 0,
high: _VERSIONS_ITEM_COUNT,
ecLv: ErrorCorrectionHighest,
v: 40,
},
want: 159,
},
{
name: "case 2",
args: args{
low: -1,
high: 200,
ecLv: ErrorCorrectionHighest,
v: 40,
},
want: 159,
},
{
name: "case 3",
args: args{
low: 180,
high: 0,
ecLv: ErrorCorrectionHighest,
v: 40,
},
want: -1,
},
{
name: "case 4",
args: args{
low: 180,
high: 0,
ecLv: ErrorCorrectionHighest,
v: 40,
},
want: -1,
},
{
name: "case 5",
args: args{
low: 0,
high: _VERSIONS_ITEM_COUNT,
ecLv: ErrorCorrectionLow,
v: 3,
},
want: 8,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, found := binarySearchVersion(tt.v, find(tt.v, tt.ecLv))
require.True(t, found)
require.Equal(t, versions[tt.want], got)
got, found := binarySearchVersion(tt.args.low, tt.args.high, defaultBinaryCompare(tt.args.v, tt.args.ecLv))

if tt.want >= 0 && tt.want <= _VERSIONS_ITEM_COUNT {
require.True(t, found)
require.Equal(t, versions[tt.want], got)
return
}

// could not find
require.False(t, found)
require.Equal(t, emptyVersion, got)
})
}
}

func Test_binarySearchVersion_all(t *testing.T) {
for _, v := range versions {
hit, found := binarySearchVersion(v.Ver, func(cursor *version) int {
if cursor.Ver == v.Ver && cursor.ECLevel == v.ECLevel {
return 0
}

// less
if cursor.Ver > v.Ver || cursor.ECLevel > v.ECLevel {
return -1
}

return 1
})

if !found {
t.Errorf("binarySearchVersions() failed to find version %d", v.Ver)
}
hit, found := binarySearchVersion(0, _VERSIONS_ITEM_COUNT, defaultBinaryCompare(v.Ver, v.ECLevel))
assert.True(t, found)
assert.Equal(t, v, hit)

//t.Logf("finding: version=%d, ecLevel=%d", v.Ver, v.ECLevel)
}
}

Expand Down

0 comments on commit a276223

Please sign in to comment.