Updated GetCDRs filtering for internalDB

This commit is contained in:
Trial97
2020-08-14 10:58:01 +03:00
committed by Dan Christian Bogos
parent c85e0389bc
commit 7bf4c2ddb8
6 changed files with 197 additions and 395 deletions

View File

@@ -37,7 +37,6 @@ func GetCacheOpt(apiOpt *string) string {
// composeArgsReload add the ItemID to AttrReloadCache
// for a specific CacheID
func composeArgsReload(args utils.ArgsGetCacheItem) (rpl map[string][]string) {
// ToDo: make the load cache API compatible with gob encoding
rpl = make(map[string][]string)
switch args.CacheID {
case utils.CacheResourceProfiles:

View File

@@ -1132,10 +1132,9 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
if len(fltrSlc.ids) == 0 {
continue
}
grpMpIDs := utils.NewStringSet([]string{})
grpMpIDs := make(utils.StringSet)
for _, id := range fltrSlc.ids {
grpIDs := Cache.tCache.GetGroupItemIDs(utils.CacheCDRsTBL, utils.ConcatenatedKey(fltrSlc.key, id))
grpMpIDs.AddSlice(grpIDs)
grpMpIDs.AddSlice(Cache.tCache.GetGroupItemIDs(utils.CacheCDRsTBL, utils.ConcatenatedKey(fltrSlc.key, id)))
}
if grpMpIDs.Size() == 0 {
if filter.Count {
@@ -1145,14 +1144,14 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
}
if cdrMpIDs == nil {
cdrMpIDs = grpMpIDs
} else {
cdrMpIDs.Intersect(grpMpIDs)
if cdrMpIDs.Size() == 0 {
if filter.Count {
return nil, 0, nil
}
return nil, 0, utils.ErrNotFound
continue
}
cdrMpIDs.Intersect(grpMpIDs)
if cdrMpIDs.Size() == 0 {
if filter.Count {
return nil, 0, nil
}
return nil, 0, utils.ErrNotFound
}
}
if cdrMpIDs == nil {
@@ -1164,8 +1163,7 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
continue
}
for _, id := range fltrSlc.ids {
grpIDs := Cache.tCache.GetGroupItemIDs(utils.CacheCDRsTBL, utils.ConcatenatedKey(fltrSlc.key, id))
for _, id := range grpIDs {
for _, id := range Cache.tCache.GetGroupItemIDs(utils.CacheCDRsTBL, utils.ConcatenatedKey(fltrSlc.key, id)) {
if !cdrMpIDs.Has(id) {
continue
}
@@ -1200,8 +1198,11 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
return nil, 0, err
}
}
paginatorOffsetCounter := 0
var offset int
if filter.Paginator.Offset != nil {
offset = *filter.Paginator.Offset
}
filter.Prepare()
for key := range cdrMpIDs {
x, ok := Cache.Get(utils.CacheCDRsTBL, key)
if !ok || x == nil {
@@ -1210,360 +1211,50 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
cdr := x.(*CDR)
// default indexed filters
if (len(filter.CGRIDs) > 0 && !utils.SliceHasMember(filter.CGRIDs, cdr.CGRID)) ||
(len(filter.RunIDs) > 0 && !utils.SliceHasMember(filter.RunIDs, cdr.RunID)) ||
(len(filter.OriginIDs) > 0 && !utils.SliceHasMember(filter.OriginIDs, cdr.OriginID)) ||
(len(filter.OriginHosts) > 0 && !utils.SliceHasMember(filter.OriginHosts, cdr.OriginHost)) ||
(len(filter.Sources) > 0 && !utils.SliceHasMember(filter.Sources, cdr.Source)) ||
(len(filter.ToRs) > 0 && !utils.SliceHasMember(filter.ToRs, cdr.ToR)) ||
(len(filter.RequestTypes) > 0 && !utils.SliceHasMember(filter.RequestTypes, cdr.RequestType)) ||
(len(filter.Tenants) > 0 && !utils.SliceHasMember(filter.Tenants, cdr.Tenant)) ||
(len(filter.Categories) > 0 && !utils.SliceHasMember(filter.Categories, cdr.Category)) ||
(len(filter.Accounts) > 0 && !utils.SliceHasMember(filter.Accounts, cdr.Account)) ||
(len(filter.Subjects) > 0 && !utils.SliceHasMember(filter.Subjects, cdr.Subject)) ||
if len(filter.CGRIDs) > 0 {
matchCGRID := false
for _, cgrid := range filter.CGRIDs {
if cdr.CGRID == cgrid {
matchCGRID = true
break
}
}
if !matchCGRID {
continue
}
}
if len(filter.RunIDs) > 0 {
matchRunID := false
for _, runid := range filter.RunIDs {
if cdr.RunID == runid {
matchRunID = true
break
}
}
if !matchRunID {
continue
}
}
if len(filter.OriginIDs) > 0 {
matchOriginID := false
for _, originid := range filter.OriginIDs {
if cdr.OriginID == originid {
matchOriginID = true
break
}
}
if !matchOriginID {
continue
}
}
if len(filter.OriginHosts) > 0 {
matchOriginHost := false
for _, originHost := range filter.OriginHosts {
if cdr.OriginHost == originHost {
matchOriginHost = true
break
}
}
if !matchOriginHost {
continue
}
}
if len(filter.Sources) > 0 {
matchSource := false
for _, source := range filter.Sources {
if cdr.Source == source {
matchSource = true
break
}
}
if !matchSource {
continue
}
}
if len(filter.ToRs) > 0 {
matchToR := false
for _, tor := range filter.ToRs {
if cdr.ToR == tor {
matchToR = true
break
}
}
if !matchToR {
continue
}
}
if len(filter.RequestTypes) > 0 {
matchRequestType := false
for _, req := range filter.RequestTypes {
if cdr.RequestType == req {
matchRequestType = true
break
}
}
if !matchRequestType {
continue
}
}
if len(filter.Tenants) > 0 {
matchTenant := false
for _, tnt := range filter.Tenants {
if cdr.Tenant == tnt {
matchTenant = true
break
}
}
if !matchTenant {
continue
}
}
if len(filter.Categories) > 0 {
matchCategorie := false
for _, cat := range filter.Categories {
if cdr.Category == cat {
matchCategorie = true
break
}
}
if !matchCategorie {
continue
}
}
if len(filter.Accounts) > 0 {
matchAccount := false
for _, acc := range filter.Accounts {
if cdr.Account == acc {
matchAccount = true
break
}
}
if !matchAccount {
continue
}
}
if len(filter.Subjects) > 0 {
matchSubject := false
for _, subject := range filter.Subjects {
if cdr.Subject == subject {
matchSubject = true
break
}
}
if !matchSubject {
continue
}
}
if len(filter.DestinationPrefixes) > 0 {
matchdst := false
for _, dst := range filter.DestinationPrefixes {
if strings.HasPrefix(cdr.Destination, dst) {
matchdst = true
break
}
}
if !matchdst {
continue
}
}
(len(filter.NotCGRIDs) > 0 && utils.SliceHasMember(filter.NotCGRIDs, cdr.CGRID)) ||
(len(filter.NotRunIDs) > 0 && utils.SliceHasMember(filter.NotRunIDs, cdr.RunID)) ||
(len(filter.NotOriginIDs) > 0 && utils.SliceHasMember(filter.NotOriginIDs, cdr.OriginID)) ||
(len(filter.NotOriginHosts) > 0 && utils.SliceHasMember(filter.NotOriginHosts, cdr.OriginHost)) ||
(len(filter.NotSources) > 0 && utils.SliceHasMember(filter.NotSources, cdr.Source)) ||
(len(filter.NotToRs) > 0 && utils.SliceHasMember(filter.NotToRs, cdr.ToR)) ||
(len(filter.NotRequestTypes) > 0 && utils.SliceHasMember(filter.NotRequestTypes, cdr.RequestType)) ||
(len(filter.NotTenants) > 0 && utils.SliceHasMember(filter.NotTenants, cdr.Tenant)) ||
(len(filter.NotCategories) > 0 && utils.SliceHasMember(filter.NotCategories, cdr.Category)) ||
(len(filter.NotAccounts) > 0 && utils.SliceHasMember(filter.NotAccounts, cdr.Account)) ||
(len(filter.NotSubjects) > 0 && utils.SliceHasMember(filter.NotSubjects, cdr.Subject)) ||
if len(filter.NotCGRIDs) > 0 {
matchCGRID := true
for _, cgrid := range filter.NotCGRIDs {
if cdr.CGRID == cgrid {
matchCGRID = false
break
}
}
if !matchCGRID {
continue
}
}
if len(filter.NotRunIDs) > 0 {
matchRunID := true
for _, runid := range filter.NotRunIDs {
if cdr.RunID == runid {
matchRunID = false
break
}
}
if !matchRunID {
continue
}
}
if len(filter.NotOriginIDs) > 0 {
matchOriginID := true
for _, originID := range filter.NotOriginIDs {
if cdr.OriginID == originID {
matchOriginID = false
break
}
}
if !matchOriginID {
continue
}
}
if len(filter.NotOriginHosts) > 0 {
matchOriginHost := true
for _, originHost := range filter.NotOriginHosts {
if cdr.OriginHost == originHost {
matchOriginHost = false
break
}
}
if !matchOriginHost {
continue
}
}
if len(filter.NotSources) > 0 {
matchSource := true
for _, source := range filter.NotSources {
if cdr.Source == source {
matchSource = false
break
}
}
if !matchSource {
continue
}
}
if len(filter.NotToRs) > 0 {
matchToR := true
for _, tor := range filter.NotToRs {
if cdr.ToR == tor {
matchToR = false
break
}
}
if !matchToR {
continue
}
}
if len(filter.NotRequestTypes) > 0 {
matchRequestType := true
for _, req := range filter.NotRequestTypes {
if cdr.RequestType == req {
matchRequestType = false
break
}
}
if !matchRequestType {
continue
}
}
if len(filter.NotTenants) > 0 {
matchTenant := true
for _, tnt := range filter.NotTenants {
if cdr.Tenant == tnt {
matchTenant = false
break
}
}
if !matchTenant {
continue
}
}
if len(filter.NotCategories) > 0 {
matchCategorie := true
for _, cat := range filter.NotCategories {
if cdr.Category == cat {
matchCategorie = false
break
}
}
if !matchCategorie {
continue
}
}
if len(filter.NotAccounts) > 0 {
matchAccount := true
for _, acc := range filter.NotAccounts {
if cdr.Account == acc {
matchAccount = false
break
}
}
if !matchAccount {
continue
}
}
if len(filter.NotSubjects) > 0 {
matchSubject := true
for _, subject := range filter.NotSubjects {
if cdr.Subject == subject {
matchSubject = false
break
}
}
if !matchSubject {
continue
}
}
if len(filter.NotDestinationPrefixes) > 0 {
matchdst := true
for _, dst := range filter.NotDestinationPrefixes {
if strings.HasPrefix(cdr.Destination, dst) {
matchdst = false
break
}
}
if !matchdst {
continue
}
(len(filter.Costs) > 0 && !utils.Float64SliceHasMember(filter.Costs, cdr.Cost)) ||
(len(filter.NotCosts) > 0 && utils.Float64SliceHasMember(filter.NotCosts, cdr.Cost)) ||
(len(filter.DestinationPrefixes) > 0 && !utils.HasPrefixSlice(filter.DestinationPrefixes, cdr.Destination)) ||
(len(filter.NotDestinationPrefixes) > 0 && utils.HasPrefixSlice(filter.NotDestinationPrefixes, cdr.Destination)) ||
(filter.OrderIDStart != nil && cdr.OrderID < *filter.OrderIDStart) ||
(filter.OrderIDEnd != nil && cdr.OrderID >= *filter.OrderIDEnd) ||
(filter.AnswerTimeStart != nil && !filter.AnswerTimeStart.IsZero() && cdr.AnswerTime.Before(*filter.AnswerTimeStart)) ||
(filter.AnswerTimeEnd != nil && !filter.AnswerTimeEnd.IsZero() && cdr.AnswerTime.After(*filter.AnswerTimeEnd)) ||
(filter.SetupTimeStart != nil && !filter.SetupTimeStart.IsZero() && cdr.SetupTime.Before(*filter.SetupTimeStart)) ||
(filter.SetupTimeEnd != nil && !filter.SetupTimeEnd.IsZero() && cdr.SetupTime.Before(*filter.SetupTimeEnd)) ||
(len(filter.MinUsage) != 0 && cdr.Usage < minUsage) ||
(len(filter.MaxUsage) != 0 && cdr.Usage > maxUsage) {
continue
}
// normal filters
if len(filter.Costs) > 0 {
matchCost := false
for _, cost := range filter.Costs {
if cdr.Cost == cost {
matchCost = true
break
}
}
if !matchCost {
continue
}
}
if len(filter.NotCosts) > 0 {
matchCost := true
for _, cost := range filter.NotCosts {
if cdr.Cost == cost {
matchCost = false
break
}
}
if !matchCost {
continue
}
}
if filter.OrderIDStart != nil &&
cdr.OrderID < *filter.OrderIDStart {
continue
}
if filter.OrderIDEnd != nil &&
cdr.OrderID >= *filter.OrderIDEnd {
continue
}
if filter.AnswerTimeStart != nil &&
!filter.AnswerTimeStart.IsZero() && // With IsZero we keep backwards compatible with APIerSv1
cdr.AnswerTime.Before(*filter.AnswerTimeStart) {
continue
}
if filter.AnswerTimeEnd != nil &&
!filter.AnswerTimeEnd.IsZero() &&
cdr.AnswerTime.After(*filter.AnswerTimeEnd) {
continue
}
if filter.SetupTimeStart != nil &&
!filter.SetupTimeStart.IsZero() &&
cdr.SetupTime.Before(*filter.SetupTimeStart) {
continue
}
if filter.SetupTimeEnd != nil &&
!filter.SetupTimeEnd.IsZero() &&
cdr.SetupTime.Before(*filter.SetupTimeEnd) {
continue
}
if len(filter.MinUsage) != 0 &&
cdr.Usage < minUsage {
continue
}
if len(filter.MaxUsage) != 0 &&
cdr.Usage > maxUsage {
continue
}
if filter.MinCost != nil {
if filter.MaxCost == nil {
@@ -1574,7 +1265,7 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
if cdr.Cost < 0 {
continue
}
} else if cdr.Cost < *filter.MinCost || cdr.Cost > *filter.MaxCost {
} else if cdr.Cost < *filter.MinCost || cdr.Cost >= *filter.MaxCost {
continue
}
} else if filter.MaxCost != nil {
@@ -1589,16 +1280,14 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
if len(filter.ExtraFields) != 0 {
passFilter := true
for extFldID, extFldVal := range filter.ExtraFields {
val, has := cdr.ExtraFields[extFldID]
passFilter = val == extFldVal
if extFldVal == utils.MetaExists {
if _, has := cdr.ExtraFields[extFldID]; !has {
passFilter = false
break
}
} else if cdr.ExtraFields[extFldID] != extFldVal {
passFilter = false
passFilter = has
}
if !passFilter {
break
}
}
if !passFilter {
continue
@@ -1607,13 +1296,12 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
if len(filter.NotExtraFields) != 0 {
passFilter := true
for notExtFldID, notExtFldVal := range filter.NotExtraFields {
val, has := cdr.ExtraFields[notExtFldID]
passFilter = val != notExtFldVal
if notExtFldVal == utils.MetaExists {
if _, has := cdr.ExtraFields[notExtFldID]; has {
passFilter = false
break
}
} else if cdr.ExtraFields[notExtFldID] == notExtFldVal {
passFilter = false
passFilter = !has
}
if !passFilter {
break
}
}
@@ -1623,9 +1311,8 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
}
if filter.OrderBy == utils.EmptyString { // if do not have to order exit early
if filter.Paginator.Offset != nil &&
paginatorOffsetCounter <= *filter.Paginator.Offset {
paginatorOffsetCounter++
if offset > 0 {
offset--
continue
}
if filter.Paginator.Limit != nil &&
@@ -1636,15 +1323,14 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
//pass all filters and append to slice
cdrs = append(cdrs, cdr)
}
if filter.OrderBy != utils.EmptyString &&
filter.Paginator.Offset != nil &&
len(cdrs) < *filter.Paginator.Offset { // if we have offset populated but not enough cdrs return
if len(cdrs) <= offset { // if we have offset populated but not enough cdrs return
if filter.Count {
return nil, 0, nil
}
return nil, 0, utils.ErrNotFound
}
if filter.Count {
cdrs = cdrs[offset:]
if filter.Paginator.Limit != nil &&
len(cdrs) >= *filter.Paginator.Limit {
return nil, int64(*filter.Paginator.Limit), nil
@@ -1716,9 +1402,7 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
return nil, 0, fmt.Errorf("Invalid value : %s", separateVals[0])
}
if filter.Paginator.Offset != nil {
cdrs = cdrs[*filter.Paginator.Offset:]
}
cdrs = cdrs[offset:]
if filter.Paginator.Limit != nil {
if len(cdrs) > *filter.Paginator.Limit {
cdrs = cdrs[:*filter.Paginator.Limit]

View File

@@ -676,6 +676,38 @@ type CDRsFilter struct {
Paginator
}
// Prepare will sort all the slices in order to search more faster
func (fltr *CDRsFilter) Prepare() {
sort.Strings(fltr.CGRIDs)
sort.Strings(fltr.NotCGRIDs)
sort.Strings(fltr.RunIDs)
sort.Strings(fltr.NotRunIDs)
sort.Strings(fltr.OriginIDs)
sort.Strings(fltr.NotOriginIDs)
sort.Strings(fltr.OriginHosts)
sort.Strings(fltr.NotOriginHosts)
sort.Strings(fltr.Sources)
sort.Strings(fltr.NotSources)
sort.Strings(fltr.ToRs)
sort.Strings(fltr.NotToRs)
sort.Strings(fltr.RequestTypes)
sort.Strings(fltr.NotRequestTypes)
sort.Strings(fltr.Tenants)
sort.Strings(fltr.NotTenants)
sort.Strings(fltr.Categories)
sort.Strings(fltr.NotCategories)
sort.Strings(fltr.Accounts)
sort.Strings(fltr.NotAccounts)
sort.Strings(fltr.Subjects)
sort.Strings(fltr.NotSubjects)
// sort.Strings(fltr.DestinationPrefixes)
// sort.Strings(fltr.NotDestinationPrefixes)
sort.Sort(sort.Float64Slice(fltr.Costs))
sort.Sort(sort.Float64Slice(fltr.NotCosts))
}
// RPCCDRsFilter is a filter used in Rpc calls
// RPCCDRsFilter is slightly different than CDRsFilter by using string instead of Time filters
type RPCCDRsFilter struct {

View File

@@ -973,3 +973,61 @@ func TestAppendToSMCostFilter(t *testing.T) {
t.Errorf("Expected: %s ,received: %s ", ToJSON(expected), ToJSON(smfltr))
}
}
func TestCDRsFilterPrepare(t *testing.T) {
fltr := &CDRsFilter{
CGRIDs: []string{"5", "6", "1", "3", "2", "4"},
NotCGRIDs: []string{"5", "6", "1", "3", "2", "4"},
RunIDs: []string{"5", "6", "1", "3", "2", "4"},
NotRunIDs: []string{"5", "6", "1", "3", "2", "4"},
OriginIDs: []string{"5", "6", "1", "3", "2", "4"},
NotOriginIDs: []string{"5", "6", "1", "3", "2", "4"},
OriginHosts: []string{"5", "6", "1", "3", "2", "4"},
NotOriginHosts: []string{"5", "6", "1", "3", "2", "4"},
Sources: []string{"5", "6", "1", "3", "2", "4"},
NotSources: []string{"5", "6", "1", "3", "2", "4"},
ToRs: []string{"5", "6", "1", "3", "2", "4"},
NotToRs: []string{"5", "6", "1", "3", "2", "4"},
RequestTypes: []string{"5", "6", "1", "3", "2", "4"},
NotRequestTypes: []string{"5", "6", "1", "3", "2", "4"},
Tenants: []string{"5", "6", "1", "3", "2", "4"},
NotTenants: []string{"5", "6", "1", "3", "2", "4"},
Categories: []string{"5", "6", "1", "3", "2", "4"},
NotCategories: []string{"5", "6", "1", "3", "2", "4"},
Accounts: []string{"5", "6", "1", "3", "2", "4"},
NotAccounts: []string{"5", "6", "1", "3", "2", "4"},
Subjects: []string{"5", "6", "1", "3", "2", "4"},
NotSubjects: []string{"5", "6", "1", "3", "2", "4"},
Costs: []float64{5, 6, 1, 3, 2, 4},
NotCosts: []float64{5, 6, 1, 3, 2, 4},
}
exp := &CDRsFilter{
CGRIDs: []string{"1", "2", "3", "4", "5", "6"},
NotCGRIDs: []string{"1", "2", "3", "4", "5", "6"},
RunIDs: []string{"1", "2", "3", "4", "5", "6"},
NotRunIDs: []string{"1", "2", "3", "4", "5", "6"},
OriginIDs: []string{"1", "2", "3", "4", "5", "6"},
NotOriginIDs: []string{"1", "2", "3", "4", "5", "6"},
OriginHosts: []string{"1", "2", "3", "4", "5", "6"},
NotOriginHosts: []string{"1", "2", "3", "4", "5", "6"},
Sources: []string{"1", "2", "3", "4", "5", "6"},
NotSources: []string{"1", "2", "3", "4", "5", "6"},
ToRs: []string{"1", "2", "3", "4", "5", "6"},
NotToRs: []string{"1", "2", "3", "4", "5", "6"},
RequestTypes: []string{"1", "2", "3", "4", "5", "6"},
NotRequestTypes: []string{"1", "2", "3", "4", "5", "6"},
Tenants: []string{"1", "2", "3", "4", "5", "6"},
NotTenants: []string{"1", "2", "3", "4", "5", "6"},
Categories: []string{"1", "2", "3", "4", "5", "6"},
NotCategories: []string{"1", "2", "3", "4", "5", "6"},
Accounts: []string{"1", "2", "3", "4", "5", "6"},
NotAccounts: []string{"1", "2", "3", "4", "5", "6"},
Subjects: []string{"1", "2", "3", "4", "5", "6"},
NotSubjects: []string{"1", "2", "3", "4", "5", "6"},
Costs: []float64{1, 2, 3, 4, 5, 6},
NotCosts: []float64{1, 2, 3, 4, 5, 6},
}
if fltr.Prepare(); !reflect.DeepEqual(exp, fltr) {
t.Errorf("Expected %s,received %s", ToJSON(exp), ToJSON(fltr))
}
}

View File

@@ -26,19 +26,14 @@ import (
// Binary string search in slice
func IsSliceMember(ss []string, s string) bool {
sort.Strings(ss)
if i := sort.SearchStrings(ss, s); i < len(ss) && ss[i] == s {
return true
}
return false
return SliceHasMember(ss, s)
}
// SliceHasMember searches within a *sorted* slice
// useful to search in shared vars (no slice sort)
func SliceHasMember(ss []string, s string) bool {
if i := sort.SearchStrings(ss, s); i < len(ss) && ss[i] == s {
return true
}
return false
i := sort.SearchStrings(ss, s)
return i < len(ss) && ss[i] == s
}
// SliceWithoutMember removes s string from slice ss (if found)
@@ -106,3 +101,20 @@ func SliceStringToIface(slc []string) (ifc []interface{}) {
}
return
}
// Float64SliceHasMember searches within a *sorted* slice
// useful to search in shared vars (no slice sort)
func Float64SliceHasMember(ss []float64, s float64) bool {
i := sort.SearchFloat64s(ss, s)
return i < len(ss) && ss[i] == s
}
// HasPrefixSlice iterates over slice members and returns true if one the element has that prefix
func HasPrefixSlice(prfxs []string, el string) bool {
for _, prfx := range prfxs {
if strings.HasPrefix(el, prfx) {
return true
}
}
return false
}

View File

@@ -41,6 +41,14 @@ func TestSliceHasMember(t *testing.T) {
}
}
func TestFlaot64SliceHasMember(t *testing.T) {
if !Float64SliceHasMember([]float64{1, 2, 3, 4, 5}, 5) {
t.Error("Expecting: true, received: false")
}
if Float64SliceHasMember([]float64{1, 2, 3, 4, 5}, 6) {
t.Error("Expecting: true, received: false")
}
}
func TestSliceWithoutMember(t *testing.T) {
rcv := SliceWithoutMember([]string{"1", "2", "3", "4", "5"}, "5")
sort.Strings(rcv)
@@ -65,6 +73,15 @@ func TestSliceMemberHasPrefix(t *testing.T) {
}
}
func TestHasPrefixSlice(t *testing.T) {
if !HasPrefixSlice([]string{"1", "2", "3", "4", "5"}, "123") {
t.Error("Expecting: true, received: false")
}
if HasPrefixSlice([]string{"1", "2", "3", "4", "5"}, "689") {
t.Error("Expecting: true, received: false")
}
}
func TestAvg(t *testing.T) {
if rcv := Avg([]float64{}); rcv != 0 {
t.Errorf("Expecting: 0, received: %+v", rcv)