Add searchable android.DirectorySortedPaths

Add an android.DirectorySortedPaths that stores paths sorted such
that all paths in a directory including subdirectories are in a
contiguous subslice.  This will allow efficient O(log(N)) finding
of all paths in a directory using a binary search on the directory
prefix.

Test: TestDirectorySortedPaths in paths_test.go
Change-Id: I5a06a89351ae06e88c06526be54a6b79075361b7
This commit is contained in:
Colin Cross
2017-11-03 15:20:35 -07:00
parent df93350ccd
commit 5e6cfbead9
2 changed files with 105 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ import (
"fmt"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/google/blueprint"
@@ -380,6 +381,38 @@ func (p Paths) FilterOutByExt(ext string) Paths {
return ret
}
// DirectorySortedPaths is a slice of paths that are sorted such that all files in a directory
// (including subdirectories) are in a contiguous subslice of the list, and can be found in
// O(log(N)) time using a binary search on the directory prefix.
type DirectorySortedPaths Paths
func PathsToDirectorySortedPaths(paths Paths) DirectorySortedPaths {
ret := append(DirectorySortedPaths(nil), paths...)
sort.Slice(ret, func(i, j int) bool {
return ret[i].String() < ret[j].String()
})
return ret
}
// PathsInDirectory returns a subslice of the DirectorySortedPaths as a Paths that contains all entries
// that are in the specified directory and its subdirectories.
func (p DirectorySortedPaths) PathsInDirectory(dir string) Paths {
prefix := filepath.Clean(dir) + "/"
start := sort.Search(len(p), func(i int) bool {
return prefix < p[i].String()
})
ret := p[start:]
end := sort.Search(len(ret), func(i int) bool {
return !strings.HasPrefix(ret[i].String(), prefix)
})
ret = ret[:end]
return Paths(ret)
}
// WritablePaths is a slice of WritablePaths, used for multiple outputs.
type WritablePaths []WritablePath

View File

@@ -340,3 +340,75 @@ func TestPathForModuleInstall(t *testing.T) {
})
}
}
func TestDirectorySortedPaths(t *testing.T) {
makePaths := func() Paths {
return Paths{
PathForTesting("a.txt"),
PathForTesting("a/txt"),
PathForTesting("a/b/c"),
PathForTesting("a/b/d"),
PathForTesting("b"),
PathForTesting("b/b.txt"),
PathForTesting("a/a.txt"),
}
}
expected := []string{
"a.txt",
"a/a.txt",
"a/b/c",
"a/b/d",
"a/txt",
"b",
"b/b.txt",
}
paths := makePaths()
reversePaths := make(Paths, len(paths))
for i, v := range paths {
reversePaths[len(paths)-i-1] = v
}
sortedPaths := PathsToDirectorySortedPaths(paths)
reverseSortedPaths := PathsToDirectorySortedPaths(reversePaths)
if !reflect.DeepEqual(Paths(sortedPaths).Strings(), expected) {
t.Fatalf("sorted paths:\n %#v\n != \n %#v", paths.Strings(), expected)
}
if !reflect.DeepEqual(Paths(reverseSortedPaths).Strings(), expected) {
t.Fatalf("sorted reversed paths:\n %#v\n !=\n %#v", reversePaths.Strings(), expected)
}
expectedA := []string{
"a/a.txt",
"a/b/c",
"a/b/d",
"a/txt",
}
inA := sortedPaths.PathsInDirectory("a")
if !reflect.DeepEqual(inA.Strings(), expectedA) {
t.Errorf("FilesInDirectory(a):\n %#v\n != \n %#v", inA.Strings(), expectedA)
}
expectedA_B := []string{
"a/b/c",
"a/b/d",
}
inA_B := sortedPaths.PathsInDirectory("a/b")
if !reflect.DeepEqual(inA_B.Strings(), expectedA_B) {
t.Errorf("FilesInDirectory(a/b):\n %#v\n != \n %#v", inA_B.Strings(), expectedA_B)
}
expectedB := []string{
"b/b.txt",
}
inB := sortedPaths.PathsInDirectory("b")
if !reflect.DeepEqual(inB.Strings(), expectedB) {
t.Errorf("FilesInDirectory(b):\n %#v\n != \n %#v", inA.Strings(), expectedA)
}
}