k8s.io/test-infra/triage@v0.0.0-20240520184403-27c6b4c223d8/summarize/map_abstractions.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18  This file contains abstractions of certain complex map types that are used often throughout
    19  summarize.go. They also have some often-used methods such as getting just the keys, or getting the
    20  key-value pairs as a map.
    21  */
    22  
    23  package summarize
    24  
    25  import (
    26  	"sort"
    27  )
    28  
    29  // map[string][]failure type alias
    30  
    31  // failuresGroup maps strings to failure slices.
    32  type failuresGroup map[string][]failure
    33  
    34  // failuresGroupPair is a representation of a failuresGroup key-value mapping as a two-element
    35  // struct.
    36  type failuresGroupPair struct {
    37  	Key      string    `json:"key"`
    38  	Failures []failure `json:"failures"`
    39  }
    40  
    41  // keys provides the failuresGroup's keys as a string slice.
    42  func (fg *failuresGroup) keys() []string {
    43  	result := make([]string, 0, len(*fg))
    44  
    45  	for key := range *fg {
    46  		result = append(result, key)
    47  	}
    48  
    49  	return result
    50  }
    51  
    52  // asSlice returns the failuresGroup as a failuresGroupPair slice.
    53  func (fg *failuresGroup) asSlice() []failuresGroupPair {
    54  	result := make([]failuresGroupPair, 0, len(*fg))
    55  
    56  	for str, failures := range *fg {
    57  		result = append(result, failuresGroupPair{str, failures})
    58  	}
    59  
    60  	return result
    61  }
    62  
    63  // sortByMostFailures returns a failuresGroupPair slice sorted by the number of failures in each
    64  // pair, descending. If the number of failures is the same for two pairs, they are sorted alphabetically
    65  // by their keys.
    66  func (fg *failuresGroup) sortByMostFailures() []failuresGroupPair {
    67  	result := fg.asSlice()
    68  
    69  	// Sort the slice.
    70  	sort.Slice(result, func(i, j int) bool {
    71  		iFailures := len(result[i].Failures)
    72  		jFailures := len(result[j].Failures)
    73  
    74  		if iFailures == jFailures {
    75  			return result[i].Key < result[j].Key
    76  		}
    77  
    78  		return iFailures > jFailures
    79  	})
    80  
    81  	return result
    82  }
    83  
    84  // equal determines whether this failuresGroup is deeply equal to another failuresGroup.
    85  func (a *failuresGroup) equal(b *failuresGroup) bool {
    86  	// First check the length to deal with different-length maps
    87  	if len(*a) != len(*b) {
    88  		return false
    89  	}
    90  
    91  	for key, failuresA := range *a {
    92  		// Make sure the other map contains the same keys
    93  		if failuresB, ok := (*b)[key]; ok {
    94  			// Check lengths
    95  			if len(failuresA) != len(failuresB) {
    96  				return false
    97  			}
    98  			// Compare the failures slices
    99  			for i := range failuresA {
   100  				if failuresA[i] != failuresB[i] {
   101  					return false
   102  				}
   103  			}
   104  		} else {
   105  			// The other map is missing a key
   106  			return false
   107  		}
   108  	}
   109  
   110  	return true
   111  }
   112  
   113  // map[string]failuresGroup type alias, which is really a map[string]map[string][]failure type alias
   114  
   115  // nestedFailuresGroups maps strings to failuresGroup instances.
   116  type nestedFailuresGroups map[string]failuresGroup
   117  
   118  // nestedFailuresGroupsPair is a representation of a nestedFailuresGroups key-value mapping as a
   119  // two-element struct.
   120  type nestedFailuresGroupsPair struct {
   121  	Key   string        `json:"key"`
   122  	Group failuresGroup `json:"group"`
   123  }
   124  
   125  // keys provides the nestedFailuresGroups's keys as a string slice.
   126  func (nfg *nestedFailuresGroups) keys() []string {
   127  	result := make([]string, len(*nfg))
   128  
   129  	iter := 0
   130  	for key := range *nfg {
   131  		result[iter] = key
   132  		iter++
   133  	}
   134  
   135  	return result
   136  }
   137  
   138  // asSlice returns the nestedFailuresGroups as a nestedFailuresGroupsPair slice.
   139  func (nfg *nestedFailuresGroups) asSlice() []nestedFailuresGroupsPair {
   140  	result := make([]nestedFailuresGroupsPair, len(*nfg))
   141  
   142  	iter := 0
   143  	for str, group := range *nfg {
   144  		result[iter] = nestedFailuresGroupsPair{str, group}
   145  		iter++
   146  	}
   147  
   148  	return result
   149  }
   150  
   151  // sortByMostAggregatedFailures returns a nestedFailuresGroupsPair slice sorted by the aggregate
   152  // number of failures across all failure slices in each failuresGroup, descending. If the aggregate
   153  // number of failures is the same for two pairs, they are sorted alphabetically by their keys.
   154  func (nfg *nestedFailuresGroups) sortByMostAggregatedFailures() []nestedFailuresGroupsPair {
   155  	result := nfg.asSlice()
   156  
   157  	// Pre-compute the aggregate failures for each element of result so that the less
   158  	// function doesn't have to compute it on every compare.
   159  	// aggregates maps nestedFailuresGroups strings to number of aggregate failures across all of
   160  	// their failure slices.
   161  	aggregates := make(map[string]int, len(*nfg))
   162  	for str, fg := range *nfg {
   163  		aggregate := 0
   164  		for _, group := range fg {
   165  			aggregate += len(group)
   166  		}
   167  		aggregates[str] = aggregate
   168  	}
   169  
   170  	// Sort the slice.
   171  	sort.Slice(result, func(i, j int) bool {
   172  		if aggregates[result[i].Key] == aggregates[result[j].Key] {
   173  			return result[i].Key < result[j].Key
   174  		}
   175  
   176  		return aggregates[result[i].Key] > aggregates[result[j].Key]
   177  	})
   178  
   179  	return result
   180  }
   181  
   182  // equal determines whether this nestedFailuresGroups object is deeply equal to another nestedFailuresGroups object.
   183  func (a *nestedFailuresGroups) equal(b *nestedFailuresGroups) bool {
   184  	// First check the length to deal with different-length maps
   185  	if len(*a) != len(*b) {
   186  		return false
   187  	}
   188  
   189  	for key, failuresGroupA := range *a {
   190  		// Make sure the other map contains the same keys
   191  		if failuresGroupB, ok := (*b)[key]; ok {
   192  			if !failuresGroupA.equal(&failuresGroupB) {
   193  				return false
   194  			}
   195  		} else {
   196  			// The other map is missing a key
   197  			return false
   198  		}
   199  	}
   200  
   201  	return true
   202  }