diff --git a/go.mod b/go.mod index 7e04a6a3a3..117fd84994 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 github.com/BurntSushi/toml v0.3.1 + github.com/Jeffail/tunny v0.1.4 github.com/PuerkitoBio/goquery v1.5.0 github.com/RichardKnop/machinery v1.6.9 github.com/alecthomas/chroma v0.10.0 @@ -97,7 +98,6 @@ require ( github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.16.0 - golang.org/x/exp v0.0.0-20231127185646-65229373498e golang.org/x/net v0.19.0 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.15.0 diff --git a/go.sum b/go.sum index 93bf5b7431..b3e56a59bd 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2/go.mod h1:EfRHD2k+Kd7ijnqlwO github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Jeffail/tunny v0.1.4 h1:chtpdz+nUtaYQeCKlNBg6GycFF/kGVHOr6A3cmzTJXs= +github.com/Jeffail/tunny v0.1.4/go.mod h1:P8xAx4XQl0xsuhjX1DtfaMDCSuavzdb2rwbd0lk+fvo= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -858,8 +860,6 @@ golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= -golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= diff --git a/routers/repo/repo_statistic.go b/routers/repo/repo_statistic.go index 8124d1495d..8d4891ea21 100755 --- a/routers/repo/repo_statistic.go +++ b/routers/repo/repo_statistic.go @@ -2,6 +2,7 @@ package repo import ( "errors" + "sync" "time" "code.gitea.io/gitea/models" @@ -12,8 +13,23 @@ import ( "code.gitea.io/gitea/services/mailer" "gitea.com/macaron/macaron" + "github.com/Jeffail/tunny" ) +var statisticMutex sync.Mutex +var statisticWg sync.WaitGroup + +type StatisticInput struct { + Date string + Repo *models.Repository + T time.Time + ErrorProjects []string + ReposRadar []*models.RepoStatistic + IsInitMinMaxRadar bool + MinRepoRadar models.RepoStatistic + MaxRepoRadar models.RepoStatistic +} + func StatisticAuto() { go RepoStatisticAuto() go TimingCountData() @@ -52,136 +68,196 @@ func RepoStatisticDaily(date string) { isInitMinMaxRadar := false var error_projects = make([]string, 0) - for _, repo := range repos { - projectName := getDistinctProjectName(repo) - log.Info("start statistic: %s", projectName) - var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64 - repoGitStat, err := models.GetRepoKPIStats(repo) - if err != nil { - log.Error("GetRepoKPIStats failed: %s", projectName) - } else { - numDevMonths = repoGitStat.DevelopAge - numKeyContributor = repoGitStat.KeyContributors - numWikiViews = repoGitStat.WikiPages - numContributor = repoGitStat.Contributors - numCommitsGrowth = repoGitStat.CommitsAdded - numCommitLinesGrowth = repoGitStat.CommitLinesModified - numContributorsGrowth = repoGitStat.ContributorsAdded - numCommits = repoGitStat.Commits + pool := tunny.NewFunc(4, statisticOneRepo) + defer pool.Close() + statisticWg.Add(len(repos)) + for _, repo := range repos { + input := &StatisticInput{ + Date: date, + Repo: repo, + T: t, + ErrorProjects: error_projects, + ReposRadar: reposRadar, + IsInitMinMaxRadar: isInitMinMaxRadar, + MinRepoRadar: minRepoRadar, + MaxRepoRadar: maxRepoRadar, } + pool.Process(input) + } + statisticWg.Wait() - var issueFixedRate float32 - if repo.NumIssues != 0 { - issueFixedRate = float32(repo.NumClosedIssues) / float32(repo.NumIssues) + if len(error_projects) > 0 { + mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage) + } + + //radar map + log.Info("begin statistic radar") + for _, radarInit := range reposRadar { + if radarInit.IsMirror && setting.RadarMap.IgnoreMirrorRepo { + radarInit.Impact = 0 + radarInit.Completeness = 0 + radarInit.Liveness = 0 + radarInit.ProjectHealth = 0 + radarInit.TeamHealth = 0 + radarInit.Growth = 0 + radarInit.RadarTotal = 0 } else { - issueFixedRate = float32(setting.RadarMap.ProjectHealth0IssueCloseRatio) + radarInit.Impact = normalization.Normalization(radarInit.Impact, minRepoRadar.Impact, maxRepoRadar.Impact) + radarInit.Completeness = normalization.Normalization(radarInit.Completeness, minRepoRadar.Completeness, maxRepoRadar.Completeness) + radarInit.Liveness = normalization.Normalization(radarInit.Liveness, minRepoRadar.Liveness, maxRepoRadar.Liveness) + radarInit.ProjectHealth = normalization.Normalization(radarInit.ProjectHealth, minRepoRadar.ProjectHealth, maxRepoRadar.ProjectHealth) + radarInit.TeamHealth = normalization.Normalization(radarInit.TeamHealth, minRepoRadar.TeamHealth, maxRepoRadar.TeamHealth) + radarInit.Growth = normalization.Normalization(radarInit.Growth, minRepoRadar.Growth, maxRepoRadar.Growth) + radarInit.RadarTotal = normalization.GetRadarValue(radarInit.Impact, radarInit.Completeness, radarInit.Liveness, radarInit.ProjectHealth, radarInit.TeamHealth, radarInit.Growth) } + models.UpdateRepoStat(radarInit) + } - var numVersions int64 - numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{}) - if err != nil { - log.Error("GetReleaseCountByRepoID failed(%s): %v", projectName, err) - } + log.Info("finish statistic: radar") - var datasetSize int64 - datasetSize, err = getDatasetSize(repo) - if err != nil { - log.Error("getDatasetSize failed(%s): %v", projectName, err) - } +} - var numComments int64 - numComments, err = models.GetCommentCountByRepoID(repo.ID) - if err != nil { - log.Error("GetCommentCountByRepoID failed(%s): %v", projectName, err) - } +func statisticOneRepo(input interface{}) interface{} { + defer statisticWg.Done() - beginTime, endTime := getStatTime(date) - var numVisits int - numVisits, err = repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime) - if err != nil { - log.Error("AppointProjectView failed(%s): %v", projectName, err) - } + statisticInput := input.(*StatisticInput) + repo := statisticInput.Repo + projectName := getDistinctProjectName(repo) + log.Info("start statistic: %s", projectName) + var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64 + repoGitStat, err := models.GetRepoKPIStats(repo) + if err != nil { + log.Error("GetRepoKPIStats failed: %s", projectName) + } else { + numDevMonths = repoGitStat.DevelopAge + numKeyContributor = repoGitStat.KeyContributors + numWikiViews = repoGitStat.WikiPages + numContributor = repoGitStat.Contributors + numCommitsGrowth = repoGitStat.CommitsAdded + numCommitLinesGrowth = repoGitStat.CommitLinesModified + numContributorsGrowth = repoGitStat.ContributorsAdded + numCommits = repoGitStat.Commits - repoStat := models.RepoStatistic{ - RepoID: repo.ID, - Date: date, - Name: repo.Name, - Alias: repo.Alias, - IsPrivate: repo.IsPrivate, - IsMirror: repo.IsMirror, - IsFork: repo.IsFork, - RepoCreatedUnix: repo.CreatedUnix, - OwnerName: repo.OwnerName, - NumWatches: int64(repo.NumWatches), - NumStars: int64(repo.NumStars), - NumForks: int64(repo.NumForks), - NumDownloads: repo.CloneCnt, - NumComments: numComments, - NumVisits: int64(numVisits), - NumClosedIssues: int64(repo.NumClosedIssues), - NumVersions: numVersions, - NumDevMonths: numDevMonths, - RepoSize: repo.Size, - DatasetSize: datasetSize, - NumModels: repo.ModelCnt, - NumWikiViews: numWikiViews, - NumCommits: numCommits, - NumIssues: int64(repo.NumIssues), - NumPulls: int64(repo.NumPulls), - IssueFixedRate: issueFixedRate, - NumContributor: numContributor, - NumKeyContributor: numKeyContributor, - NumCommitsGrowth: numCommitsGrowth, - NumCommitLinesGrowth: numCommitLinesGrowth, - NumContributorsGrowth: numContributorsGrowth, - NumCloudbrain: repo.AiTaskCnt, - NumDatasetFile: repo.DatasetCnt, - NumModelConvert: models.QueryModelConvertCountByRepoID(repo.ID), - } + } - dayBeforeDate := t.AddDate(0, 0, -1).Format("2006-01-02") - repoStatisticsBefore, err := models.GetRepoStatisticByDate(dayBeforeDate, repo.ID) + var issueFixedRate float32 + if repo.NumIssues != 0 { + issueFixedRate = float32(repo.NumClosedIssues) / float32(repo.NumIssues) + } else { + issueFixedRate = float32(setting.RadarMap.ProjectHealth0IssueCloseRatio) + } - if err != nil { - log.Error("get data of day before the date failed ", err) - } else { - if len(repoStatisticsBefore) > 0 { - repoStatisticBefore := repoStatisticsBefore[0] - repoStat.NumWatchesAdded = repoStat.NumWatches - repoStatisticBefore.NumWatches - repoStat.NumStarsAdded = repoStat.NumStars - repoStatisticBefore.NumStars - repoStat.NumForksAdded = repoStat.NumForks - repoStatisticBefore.NumForks - repoStat.NumDownloadsAdded = repoStat.NumDownloads - repoStatisticBefore.NumDownloads - repoStat.NumCommentsAdded = repoStat.NumComments - repoStatisticBefore.NumComments - repoStat.NumClosedIssuesAdded = repoStat.NumClosedIssues - repoStatisticBefore.NumClosedIssues - repoStat.NumCommitsAdded = repoStat.NumCommits - repoStatisticBefore.NumCommits - repoStat.NumIssuesAdded = repoStat.NumIssues - repoStatisticBefore.NumIssues - repoStat.NumPullsAdded = repoStat.NumPulls - repoStatisticBefore.NumPulls - repoStat.NumContributorAdded = repoStat.NumContributor - repoStatisticBefore.NumContributor - repoStat.NumModelsAdded = repoStat.NumModels - repoStatisticBefore.NumModels - repoStat.NumCloudbrainAdded = repoStat.NumCloudbrain - repoStatisticBefore.NumCloudbrain - repoStat.NumModelConvertAdded = repoStat.NumModelConvert - repoStatisticBefore.NumModelConvert - repoStat.NumDatasetFileAdded = repoStat.NumDatasetFile - repoStatisticBefore.NumDatasetFile - } - } - day4MonthsAgo := t.AddDate(0, -4, 0) - repoStatisticFourMonthsAgo, err := models.GetOneRepoStatisticBeforeTime(day4MonthsAgo) - if err != nil { - log.Error("Get data of 4 moth ago failed.", err) - } else { - repoStat.NumCommentsGrowth = repoStat.NumComments - repoStatisticFourMonthsAgo.NumComments - repoStat.NumIssuesGrowth = repoStat.NumIssues - repoStatisticFourMonthsAgo.NumIssues - } + var numVersions int64 + numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{}) + if err != nil { + log.Error("GetReleaseCountByRepoID failed(%s): %v", projectName, err) + } + + var datasetSize int64 + datasetSize, err = getDatasetSize(repo) + if err != nil { + log.Error("getDatasetSize failed(%s): %v", projectName, err) + } - models.SyncStatDataToRepo(repo) + var numComments int64 + numComments, err = models.GetCommentCountByRepoID(repo.ID) + if err != nil { + log.Error("GetCommentCountByRepoID failed(%s): %v", projectName, err) + } - if _, err = models.InsertRepoStat(&repoStat); err != nil { - log.Error("InsertRepoStat failed(%s): %v", projectName, err) - log.Error("failed statistic: %s", projectName) - error_projects = append(error_projects, projectName) + beginTime, endTime := getStatTime(statisticInput.Date) + var numVisits int + numVisits, err = repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime) + if err != nil { + log.Error("AppointProjectView failed(%s): %v", projectName, err) + } - continue + repoStat := models.RepoStatistic{ + RepoID: repo.ID, + Date: statisticInput.Date, + Name: repo.Name, + Alias: repo.Alias, + IsPrivate: repo.IsPrivate, + IsMirror: repo.IsMirror, + IsFork: repo.IsFork, + RepoCreatedUnix: repo.CreatedUnix, + OwnerName: repo.OwnerName, + NumWatches: int64(repo.NumWatches), + NumStars: int64(repo.NumStars), + NumForks: int64(repo.NumForks), + NumDownloads: repo.CloneCnt, + NumComments: numComments, + NumVisits: int64(numVisits), + NumClosedIssues: int64(repo.NumClosedIssues), + NumVersions: numVersions, + NumDevMonths: numDevMonths, + RepoSize: repo.Size, + DatasetSize: datasetSize, + NumModels: repo.ModelCnt, + NumWikiViews: numWikiViews, + NumCommits: numCommits, + NumIssues: int64(repo.NumIssues), + NumPulls: int64(repo.NumPulls), + IssueFixedRate: issueFixedRate, + NumContributor: numContributor, + NumKeyContributor: numKeyContributor, + NumCommitsGrowth: numCommitsGrowth, + NumCommitLinesGrowth: numCommitLinesGrowth, + NumContributorsGrowth: numContributorsGrowth, + NumCloudbrain: repo.AiTaskCnt, + NumDatasetFile: repo.DatasetCnt, + NumModelConvert: models.QueryModelConvertCountByRepoID(repo.ID), + } + + dayBeforeDate := statisticInput.T.AddDate(0, 0, -1).Format("2006-01-02") + repoStatisticsBefore, err := models.GetRepoStatisticByDate(dayBeforeDate, repo.ID) + + if err != nil { + log.Error("get data of day before the date failed ", err) + } else { + if len(repoStatisticsBefore) > 0 { + repoStatisticBefore := repoStatisticsBefore[0] + repoStat.NumWatchesAdded = repoStat.NumWatches - repoStatisticBefore.NumWatches + repoStat.NumStarsAdded = repoStat.NumStars - repoStatisticBefore.NumStars + repoStat.NumForksAdded = repoStat.NumForks - repoStatisticBefore.NumForks + repoStat.NumDownloadsAdded = repoStat.NumDownloads - repoStatisticBefore.NumDownloads + repoStat.NumCommentsAdded = repoStat.NumComments - repoStatisticBefore.NumComments + repoStat.NumClosedIssuesAdded = repoStat.NumClosedIssues - repoStatisticBefore.NumClosedIssues + repoStat.NumCommitsAdded = repoStat.NumCommits - repoStatisticBefore.NumCommits + repoStat.NumIssuesAdded = repoStat.NumIssues - repoStatisticBefore.NumIssues + repoStat.NumPullsAdded = repoStat.NumPulls - repoStatisticBefore.NumPulls + repoStat.NumContributorAdded = repoStat.NumContributor - repoStatisticBefore.NumContributor + repoStat.NumModelsAdded = repoStat.NumModels - repoStatisticBefore.NumModels + repoStat.NumCloudbrainAdded = repoStat.NumCloudbrain - repoStatisticBefore.NumCloudbrain + repoStat.NumModelConvertAdded = repoStat.NumModelConvert - repoStatisticBefore.NumModelConvert + repoStat.NumDatasetFileAdded = repoStat.NumDatasetFile - repoStatisticBefore.NumDatasetFile } + } + day4MonthsAgo := statisticInput.T.AddDate(0, -4, 0) + repoStatisticFourMonthsAgo, err := models.GetOneRepoStatisticBeforeTime(day4MonthsAgo) + if err != nil { + log.Error("Get data of 4 moth ago failed.", err) + } else { + repoStat.NumCommentsGrowth = repoStat.NumComments - repoStatisticFourMonthsAgo.NumComments + repoStat.NumIssuesGrowth = repoStat.NumIssues - repoStatisticFourMonthsAgo.NumIssues + } + + models.SyncStatDataToRepo(repo) + + if _, err = models.InsertRepoStat(&repoStat); err != nil { + log.Error("InsertRepoStat failed(%s): %v", projectName, err) + log.Error("failed statistic: %s", projectName) + statisticMutex.Lock() + { + statisticInput.ErrorProjects = append(statisticInput.ErrorProjects, projectName) + } + statisticMutex.Unlock() + return nil + } + + statisticMutex.Lock() + { tempRepoStat := models.RepoStatistic{ RepoID: repoStat.RepoID, @@ -195,102 +271,74 @@ func RepoStatisticDaily(date string) { Growth: normalization.GetRepoGrowthInitValue(repoStat.NumCommitLinesGrowth, repoStat.NumIssuesGrowth, repoStat.NumCommitsGrowth, repoStat.NumContributorsGrowth, repoStat.NumCommentsGrowth), } - reposRadar = append(reposRadar, &tempRepoStat) + statisticInput.ReposRadar = append(statisticInput.ReposRadar, &tempRepoStat) - if !isInitMinMaxRadar { + if !statisticInput.IsInitMinMaxRadar { if !setting.RadarMap.IgnoreMirrorRepo || (setting.RadarMap.IgnoreMirrorRepo && !tempRepoStat.IsMirror) { - minRepoRadar = tempRepoStat - maxRepoRadar = tempRepoStat - isInitMinMaxRadar = true + statisticInput.MinRepoRadar = tempRepoStat + statisticInput.MaxRepoRadar = tempRepoStat + statisticInput.IsInitMinMaxRadar = true } } else { if !setting.RadarMap.IgnoreMirrorRepo || (setting.RadarMap.IgnoreMirrorRepo && !tempRepoStat.IsMirror) { - if tempRepoStat.Impact < minRepoRadar.Impact { - minRepoRadar.Impact = tempRepoStat.Impact + if tempRepoStat.Impact < statisticInput.MinRepoRadar.Impact { + statisticInput.MinRepoRadar.Impact = tempRepoStat.Impact } - if tempRepoStat.Impact > maxRepoRadar.Impact { - maxRepoRadar.Impact = tempRepoStat.Impact + if tempRepoStat.Impact > statisticInput.MaxRepoRadar.Impact { + statisticInput.MaxRepoRadar.Impact = tempRepoStat.Impact } - if tempRepoStat.Completeness < minRepoRadar.Completeness { - minRepoRadar.Completeness = tempRepoStat.Completeness + if tempRepoStat.Completeness < statisticInput.MinRepoRadar.Completeness { + statisticInput.MinRepoRadar.Completeness = tempRepoStat.Completeness } - if tempRepoStat.Completeness > maxRepoRadar.Completeness { - maxRepoRadar.Completeness = tempRepoStat.Completeness + if tempRepoStat.Completeness > statisticInput.MaxRepoRadar.Completeness { + statisticInput.MaxRepoRadar.Completeness = tempRepoStat.Completeness } - if tempRepoStat.Liveness < minRepoRadar.Liveness { - minRepoRadar.Liveness = tempRepoStat.Liveness + if tempRepoStat.Liveness < statisticInput.MinRepoRadar.Liveness { + statisticInput.MinRepoRadar.Liveness = tempRepoStat.Liveness } - if tempRepoStat.Liveness > maxRepoRadar.Liveness { - maxRepoRadar.Liveness = tempRepoStat.Liveness + if tempRepoStat.Liveness > statisticInput.MaxRepoRadar.Liveness { + statisticInput.MaxRepoRadar.Liveness = tempRepoStat.Liveness } - if tempRepoStat.ProjectHealth < minRepoRadar.ProjectHealth { - minRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth + if tempRepoStat.ProjectHealth < statisticInput.MinRepoRadar.ProjectHealth { + statisticInput.MinRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth } - if tempRepoStat.ProjectHealth > maxRepoRadar.ProjectHealth { - maxRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth + if tempRepoStat.ProjectHealth > statisticInput.MaxRepoRadar.ProjectHealth { + statisticInput.MaxRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth } - if tempRepoStat.TeamHealth < minRepoRadar.TeamHealth { - minRepoRadar.TeamHealth = tempRepoStat.TeamHealth + if tempRepoStat.TeamHealth < statisticInput.MinRepoRadar.TeamHealth { + statisticInput.MinRepoRadar.TeamHealth = tempRepoStat.TeamHealth } - if tempRepoStat.TeamHealth > maxRepoRadar.TeamHealth { - maxRepoRadar.TeamHealth = tempRepoStat.TeamHealth + if tempRepoStat.TeamHealth > statisticInput.MaxRepoRadar.TeamHealth { + statisticInput.MaxRepoRadar.TeamHealth = tempRepoStat.TeamHealth } - if tempRepoStat.Growth < minRepoRadar.Growth { - minRepoRadar.Growth = tempRepoStat.Growth + if tempRepoStat.Growth < statisticInput.MinRepoRadar.Growth { + statisticInput.MinRepoRadar.Growth = tempRepoStat.Growth } - if tempRepoStat.Growth > maxRepoRadar.Growth { - maxRepoRadar.Growth = tempRepoStat.Growth + if tempRepoStat.Growth > statisticInput.MaxRepoRadar.Growth { + statisticInput.MaxRepoRadar.Growth = tempRepoStat.Growth } } } - - log.Info("finish statistic: %s", getDistinctProjectName(repo)) } + statisticMutex.Unlock() - if len(error_projects) > 0 { - mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage) - } - - //radar map - log.Info("begin statistic radar") - for _, radarInit := range reposRadar { - if radarInit.IsMirror && setting.RadarMap.IgnoreMirrorRepo { - radarInit.Impact = 0 - radarInit.Completeness = 0 - radarInit.Liveness = 0 - radarInit.ProjectHealth = 0 - radarInit.TeamHealth = 0 - radarInit.Growth = 0 - radarInit.RadarTotal = 0 - } else { - radarInit.Impact = normalization.Normalization(radarInit.Impact, minRepoRadar.Impact, maxRepoRadar.Impact) - radarInit.Completeness = normalization.Normalization(radarInit.Completeness, minRepoRadar.Completeness, maxRepoRadar.Completeness) - radarInit.Liveness = normalization.Normalization(radarInit.Liveness, minRepoRadar.Liveness, maxRepoRadar.Liveness) - radarInit.ProjectHealth = normalization.Normalization(radarInit.ProjectHealth, minRepoRadar.ProjectHealth, maxRepoRadar.ProjectHealth) - radarInit.TeamHealth = normalization.Normalization(radarInit.TeamHealth, minRepoRadar.TeamHealth, maxRepoRadar.TeamHealth) - radarInit.Growth = normalization.Normalization(radarInit.Growth, minRepoRadar.Growth, maxRepoRadar.Growth) - radarInit.RadarTotal = normalization.GetRadarValue(radarInit.Impact, radarInit.Completeness, radarInit.Liveness, radarInit.ProjectHealth, radarInit.TeamHealth, radarInit.Growth) - } - models.UpdateRepoStat(radarInit) - } - - log.Info("finish statistic: radar") - + log.Info("finish statistic: %s", getDistinctProjectName(repo)) + return nil } func getDistinctProjectName(repo *models.Repository) string { diff --git a/vendor/github.com/Jeffail/tunny/LICENSE b/vendor/github.com/Jeffail/tunny/LICENSE new file mode 100644 index 0000000000..99a62c6298 --- /dev/null +++ b/vendor/github.com/Jeffail/tunny/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Ashley Jeffs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Jeffail/tunny/README.md b/vendor/github.com/Jeffail/tunny/README.md new file mode 100644 index 0000000000..fa8ef45e91 --- /dev/null +++ b/vendor/github.com/Jeffail/tunny/README.md @@ -0,0 +1,134 @@ +![Tunny](tunny_logo.png "Tunny") + +[![godoc for Jeffail/tunny][1]][2] +[![goreportcard for Jeffail/tunny][3]][4] + +Tunny is a Golang library for spawning and managing a goroutine pool, allowing +you to limit work coming from any number of goroutines with a synchronous API. + +A fixed goroutine pool is helpful when you have work coming from an arbitrary +number of asynchronous sources, but a limited capacity for parallel processing. +For example, when processing jobs from HTTP requests that are CPU heavy you can +create a pool with a size that matches your CPU count. + +## Install + +``` sh +go get github.com/Jeffail/tunny +``` + +Or, using dep: + +``` sh +dep ensure -add github.com/Jeffail/tunny +``` + +## Use + +For most cases your heavy work can be expressed in a simple `func()`, where you +can use `NewFunc`. Let's see how this looks using our HTTP requests to CPU count +example: + +``` go +package main + +import ( + "io/ioutil" + "net/http" + "runtime" + + "github.com/Jeffail/tunny" +) + +func main() { + numCPUs := runtime.NumCPU() + + pool := tunny.NewFunc(numCPUs, func(payload interface{}) interface{} { + var result []byte + + // TODO: Something CPU heavy with payload + + return result + }) + defer pool.Close() + + http.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) { + input, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Internal error", http.StatusInternalServerError) + } + defer r.Body.Close() + + // Funnel this work into our pool. This call is synchronous and will + // block until the job is completed. + result := pool.Process(input) + + w.Write(result.([]byte)) + }) + + http.ListenAndServe(":8080", nil) +} +``` + +Tunny also supports timeouts. You can replace the `Process` call above to the +following: + +``` go +result, err := pool.ProcessTimed(input, time.Second*5) +if err == tunny.ErrJobTimedOut { + http.Error(w, "Request timed out", http.StatusRequestTimeout) +} +``` + +You can also use the context from the request (or any other context) to handle timeouts and deadlines. Simply replace the `Process` call to the following: + +``` go +result, err := pool.ProcessCtx(r.Context(), input) +if err == context.DeadlineExceeded { + http.Error(w, "Request timed out", http.StatusRequestTimeout) +} +``` + +## Changing Pool Size + +The size of a Tunny pool can be changed at any time with `SetSize(int)`: + +``` go +pool.SetSize(10) // 10 goroutines +pool.SetSize(100) // 100 goroutines +``` + +This is safe to perform from any goroutine even if others are still processing. + +## Goroutines With State + +Sometimes each goroutine within a Tunny pool will require its own managed state. +In this case you should implement [`tunny.Worker`][tunny-worker], which includes +calls for terminating, interrupting (in case a job times out and is no longer +needed) and blocking the next job allocation until a condition is met. + +When creating a pool using `Worker` types you will need to provide a constructor +function for spawning your custom implementation: + +``` go +pool := tunny.New(poolSize, func() Worker { + // TODO: Any per-goroutine state allocation here. + return newCustomWorker() +}) +``` + +This allows Tunny to create and destroy `Worker` types cleanly when the pool +size is changed. + +## Ordering + +Backlogged jobs are not guaranteed to be processed in order. Due to the current +implementation of channels and select blocks a stack of backlogged jobs will be +processed as a FIFO queue. However, this behaviour is not part of the spec and +should not be relied upon. + +[1]: https://godoc.org/github.com/Jeffail/tunny?status.svg +[2]: http://godoc.org/github.com/Jeffail/tunny +[3]: https://goreportcard.com/badge/github.com/Jeffail/tunny +[4]: https://goreportcard.com/report/Jeffail/tunny +[tunny-worker]: https://godoc.org/github.com/Jeffail/tunny#Worker diff --git a/vendor/github.com/Jeffail/tunny/tunny.go b/vendor/github.com/Jeffail/tunny/tunny.go new file mode 100644 index 0000000000..5957983a17 --- /dev/null +++ b/vendor/github.com/Jeffail/tunny/tunny.go @@ -0,0 +1,309 @@ +// Copyright (c) 2014 Ashley Jeffs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tunny + +import ( + "context" + "errors" + "sync" + "sync/atomic" + "time" +) + +//------------------------------------------------------------------------------ + +// Errors that are used throughout the Tunny API. +var ( + ErrPoolNotRunning = errors.New("the pool is not running") + ErrJobNotFunc = errors.New("generic worker not given a func()") + ErrWorkerClosed = errors.New("worker was closed") + ErrJobTimedOut = errors.New("job request timed out") +) + +// Worker is an interface representing a Tunny working agent. It will be used to +// block a calling goroutine until ready to process a job, process that job +// synchronously, interrupt its own process call when jobs are abandoned, and +// clean up its resources when being removed from the pool. +// +// Each of these duties are implemented as a single method and can be averted +// when not needed by simply implementing an empty func. +type Worker interface { + // Process will synchronously perform a job and return the result. + Process(interface{}) interface{} + + // BlockUntilReady is called before each job is processed and must block the + // calling goroutine until the Worker is ready to process the next job. + BlockUntilReady() + + // Interrupt is called when a job is cancelled. The worker is responsible + // for unblocking the Process implementation. + Interrupt() + + // Terminate is called when a Worker is removed from the processing pool + // and is responsible for cleaning up any held resources. + Terminate() +} + +//------------------------------------------------------------------------------ + +// closureWorker is a minimal Worker implementation that simply wraps a +// func(interface{}) interface{} +type closureWorker struct { + processor func(interface{}) interface{} +} + +func (w *closureWorker) Process(payload interface{}) interface{} { + return w.processor(payload) +} + +func (w *closureWorker) BlockUntilReady() {} +func (w *closureWorker) Interrupt() {} +func (w *closureWorker) Terminate() {} + +//------------------------------------------------------------------------------ + +// callbackWorker is a minimal Worker implementation that attempts to cast +// each job into func() and either calls it if successful or returns +// ErrJobNotFunc. +type callbackWorker struct{} + +func (w *callbackWorker) Process(payload interface{}) interface{} { + f, ok := payload.(func()) + if !ok { + return ErrJobNotFunc + } + f() + return nil +} + +func (w *callbackWorker) BlockUntilReady() {} +func (w *callbackWorker) Interrupt() {} +func (w *callbackWorker) Terminate() {} + +//------------------------------------------------------------------------------ + +// Pool is a struct that manages a collection of workers, each with their own +// goroutine. The Pool can initialize, expand, compress and close the workers, +// as well as processing jobs with the workers synchronously. +type Pool struct { + queuedJobs int64 + + ctor func() Worker + workers []*workerWrapper + reqChan chan workRequest + + workerMut sync.Mutex +} + +// New creates a new Pool of workers that starts with n workers. You must +// provide a constructor function that creates new Worker types and when you +// change the size of the pool the constructor will be called to create each new +// Worker. +func New(n int, ctor func() Worker) *Pool { + p := &Pool{ + ctor: ctor, + reqChan: make(chan workRequest), + } + p.SetSize(n) + + return p +} + +// NewFunc creates a new Pool of workers where each worker will process using +// the provided func. +func NewFunc(n int, f func(interface{}) interface{}) *Pool { + return New(n, func() Worker { + return &closureWorker{ + processor: f, + } + }) +} + +// NewCallback creates a new Pool of workers where workers cast the job payload +// into a func() and runs it, or returns ErrNotFunc if the cast failed. +func NewCallback(n int) *Pool { + return New(n, func() Worker { + return &callbackWorker{} + }) +} + +//------------------------------------------------------------------------------ + +// Process will use the Pool to process a payload and synchronously return the +// result. Process can be called safely by any goroutines, but will panic if the +// Pool has been stopped. +func (p *Pool) Process(payload interface{}) interface{} { + atomic.AddInt64(&p.queuedJobs, 1) + + request, open := <-p.reqChan + if !open { + panic(ErrPoolNotRunning) + } + + request.jobChan <- payload + + payload, open = <-request.retChan + if !open { + panic(ErrWorkerClosed) + } + + atomic.AddInt64(&p.queuedJobs, -1) + return payload +} + +// ProcessTimed will use the Pool to process a payload and synchronously return +// the result. If the timeout occurs before the job has finished the worker will +// be interrupted and ErrJobTimedOut will be returned. ProcessTimed can be +// called safely by any goroutines. +func (p *Pool) ProcessTimed( + payload interface{}, + timeout time.Duration, +) (interface{}, error) { + atomic.AddInt64(&p.queuedJobs, 1) + defer atomic.AddInt64(&p.queuedJobs, -1) + + tout := time.NewTimer(timeout) + + var request workRequest + var open bool + + select { + case request, open = <-p.reqChan: + if !open { + return nil, ErrPoolNotRunning + } + case <-tout.C: + return nil, ErrJobTimedOut + } + + select { + case request.jobChan <- payload: + case <-tout.C: + request.interruptFunc() + return nil, ErrJobTimedOut + } + + select { + case payload, open = <-request.retChan: + if !open { + return nil, ErrWorkerClosed + } + case <-tout.C: + request.interruptFunc() + return nil, ErrJobTimedOut + } + + tout.Stop() + return payload, nil +} + +// ProcessCtx will use the Pool to process a payload and synchronously return +// the result. If the context cancels before the job has finished the worker will +// be interrupted and ErrJobTimedOut will be returned. ProcessCtx can be +// called safely by any goroutines. +func (p *Pool) ProcessCtx(ctx context.Context, payload interface{}) (interface{}, error) { + atomic.AddInt64(&p.queuedJobs, 1) + defer atomic.AddInt64(&p.queuedJobs, -1) + + var request workRequest + var open bool + + select { + case request, open = <-p.reqChan: + if !open { + return nil, ErrPoolNotRunning + } + case <-ctx.Done(): + return nil, ctx.Err() + } + + select { + case request.jobChan <- payload: + case <-ctx.Done(): + request.interruptFunc() + return nil, ctx.Err() + } + + select { + case payload, open = <-request.retChan: + if !open { + return nil, ErrWorkerClosed + } + case <-ctx.Done(): + request.interruptFunc() + return nil, ctx.Err() + } + + return payload, nil +} + +// QueueLength returns the current count of pending queued jobs. +func (p *Pool) QueueLength() int64 { + return atomic.LoadInt64(&p.queuedJobs) +} + +// SetSize changes the total number of workers in the Pool. This can be called +// by any goroutine at any time unless the Pool has been stopped, in which case +// a panic will occur. +func (p *Pool) SetSize(n int) { + p.workerMut.Lock() + defer p.workerMut.Unlock() + + lWorkers := len(p.workers) + if lWorkers == n { + return + } + + // Add extra workers if N > len(workers) + for i := lWorkers; i < n; i++ { + p.workers = append(p.workers, newWorkerWrapper(p.reqChan, p.ctor())) + } + + // Asynchronously stop all workers > N + for i := n; i < lWorkers; i++ { + p.workers[i].stop() + } + + // Synchronously wait for all workers > N to stop + for i := n; i < lWorkers; i++ { + p.workers[i].join() + p.workers[i] = nil + } + + // Remove stopped workers from slice + p.workers = p.workers[:n] +} + +// GetSize returns the current size of the pool. +func (p *Pool) GetSize() int { + p.workerMut.Lock() + defer p.workerMut.Unlock() + + return len(p.workers) +} + +// Close will terminate all workers and close the job channel of this Pool. +func (p *Pool) Close() { + p.SetSize(0) + close(p.reqChan) +} + +//------------------------------------------------------------------------------ diff --git a/vendor/github.com/Jeffail/tunny/tunny_logo.png b/vendor/github.com/Jeffail/tunny/tunny_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..16b7b73b5d90b3e458929303c8b5c38f22f7d46a GIT binary patch literal 53281 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/Jeffail/tunny/worker.go b/vendor/github.com/Jeffail/tunny/worker.go new file mode 100644 index 0000000000..5d9c522a67 --- /dev/null +++ b/vendor/github.com/Jeffail/tunny/worker.go @@ -0,0 +1,126 @@ +// Copyright (c) 2014 Ashley Jeffs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tunny + +//------------------------------------------------------------------------------ + +// workRequest is a struct containing context representing a workers intention +// to receive a work payload. +type workRequest struct { + // jobChan is used to send the payload to this worker. + jobChan chan<- interface{} + + // retChan is used to read the result from this worker. + retChan <-chan interface{} + + // interruptFunc can be called to cancel a running job. When called it is no + // longer necessary to read from retChan. + interruptFunc func() +} + +//------------------------------------------------------------------------------ + +// workerWrapper takes a Worker implementation and wraps it within a goroutine +// and channel arrangement. The workerWrapper is responsible for managing the +// lifetime of both the Worker and the goroutine. +type workerWrapper struct { + worker Worker + interruptChan chan struct{} + + // reqChan is NOT owned by this type, it is used to send requests for work. + reqChan chan<- workRequest + + // closeChan can be closed in order to cleanly shutdown this worker. + closeChan chan struct{} + + // closedChan is closed by the run() goroutine when it exits. + closedChan chan struct{} +} + +func newWorkerWrapper( + reqChan chan<- workRequest, + worker Worker, +) *workerWrapper { + w := workerWrapper{ + worker: worker, + interruptChan: make(chan struct{}), + reqChan: reqChan, + closeChan: make(chan struct{}), + closedChan: make(chan struct{}), + } + + go w.run() + + return &w +} + +//------------------------------------------------------------------------------ + +func (w *workerWrapper) interrupt() { + close(w.interruptChan) + w.worker.Interrupt() +} + +func (w *workerWrapper) run() { + jobChan, retChan := make(chan interface{}), make(chan interface{}) + defer func() { + w.worker.Terminate() + close(retChan) + close(w.closedChan) + }() + + for { + // NOTE: Blocking here will prevent the worker from closing down. + w.worker.BlockUntilReady() + select { + case w.reqChan <- workRequest{ + jobChan: jobChan, + retChan: retChan, + interruptFunc: w.interrupt, + }: + select { + case payload := <-jobChan: + result := w.worker.Process(payload) + select { + case retChan <- result: + case <-w.interruptChan: + w.interruptChan = make(chan struct{}) + } + case _, _ = <-w.interruptChan: + w.interruptChan = make(chan struct{}) + } + case <-w.closeChan: + return + } + } +} + +//------------------------------------------------------------------------------ + +func (w *workerWrapper) stop() { + close(w.closeChan) +} + +func (w *workerWrapper) join() { + <-w.closedChan +} + +//------------------------------------------------------------------------------ diff --git a/vendor/golang.org/x/exp/LICENSE b/vendor/golang.org/x/exp/LICENSE deleted file mode 100644 index 6a66aea5ea..0000000000 --- a/vendor/golang.org/x/exp/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/exp/PATENTS b/vendor/golang.org/x/exp/PATENTS deleted file mode 100644 index 733099041f..0000000000 --- a/vendor/golang.org/x/exp/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google hereby grants to You a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this section) -patent license to make, have made, use, offer to sell, sell, import, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go deleted file mode 100644 index 2c033dff47..0000000000 --- a/vendor/golang.org/x/exp/constraints/constraints.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package constraints defines a set of useful constraints to be used -// with type parameters. -package constraints - -// Signed is a constraint that permits any signed integer type. -// If future releases of Go add new predeclared signed integer types, -// this constraint will be modified to include them. -type Signed interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 -} - -// Unsigned is a constraint that permits any unsigned integer type. -// If future releases of Go add new predeclared unsigned integer types, -// this constraint will be modified to include them. -type Unsigned interface { - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr -} - -// Integer is a constraint that permits any integer type. -// If future releases of Go add new predeclared integer types, -// this constraint will be modified to include them. -type Integer interface { - Signed | Unsigned -} - -// Float is a constraint that permits any floating-point type. -// If future releases of Go add new predeclared floating-point types, -// this constraint will be modified to include them. -type Float interface { - ~float32 | ~float64 -} - -// Complex is a constraint that permits any complex numeric type. -// If future releases of Go add new predeclared complex numeric types, -// this constraint will be modified to include them. -type Complex interface { - ~complex64 | ~complex128 -} - -// Ordered is a constraint that permits any ordered type: any type -// that supports the operators < <= >= >. -// If future releases of Go add new ordered types, -// this constraint will be modified to include them. -type Ordered interface { - Integer | Float | ~string -} diff --git a/vendor/golang.org/x/exp/slices/cmp.go b/vendor/golang.org/x/exp/slices/cmp.go deleted file mode 100644 index fbf1934a06..0000000000 --- a/vendor/golang.org/x/exp/slices/cmp.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package slices - -import "golang.org/x/exp/constraints" - -// min is a version of the predeclared function from the Go 1.21 release. -func min[T constraints.Ordered](a, b T) T { - if a < b || isNaN(a) { - return a - } - return b -} - -// max is a version of the predeclared function from the Go 1.21 release. -func max[T constraints.Ordered](a, b T) T { - if a > b || isNaN(a) { - return a - } - return b -} - -// cmpLess is a copy of cmp.Less from the Go 1.21 release. -func cmpLess[T constraints.Ordered](x, y T) bool { - return (isNaN(x) && !isNaN(y)) || x < y -} - -// cmpCompare is a copy of cmp.Compare from the Go 1.21 release. -func cmpCompare[T constraints.Ordered](x, y T) int { - xNaN := isNaN(x) - yNaN := isNaN(y) - if xNaN && yNaN { - return 0 - } - if xNaN || x < y { - return -1 - } - if yNaN || x > y { - return +1 - } - return 0 -} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go deleted file mode 100644 index 5e8158bba8..0000000000 --- a/vendor/golang.org/x/exp/slices/slices.go +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package slices defines various functions useful with slices of any type. -package slices - -import ( - "unsafe" - - "golang.org/x/exp/constraints" -) - -// Equal reports whether two slices are equal: the same length and all -// elements equal. If the lengths are different, Equal returns false. -// Otherwise, the elements are compared in increasing index order, and the -// comparison stops at the first unequal pair. -// Floating point NaNs are not considered equal. -func Equal[S ~[]E, E comparable](s1, s2 S) bool { - if len(s1) != len(s2) { - return false - } - for i := range s1 { - if s1[i] != s2[i] { - return false - } - } - return true -} - -// EqualFunc reports whether two slices are equal using an equality -// function on each pair of elements. If the lengths are different, -// EqualFunc returns false. Otherwise, the elements are compared in -// increasing index order, and the comparison stops at the first index -// for which eq returns false. -func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool { - if len(s1) != len(s2) { - return false - } - for i, v1 := range s1 { - v2 := s2[i] - if !eq(v1, v2) { - return false - } - } - return true -} - -// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair -// of elements. The elements are compared sequentially, starting at index 0, -// until one element is not equal to the other. -// The result of comparing the first non-matching elements is returned. -// If both slices are equal until one of them ends, the shorter slice is -// considered less than the longer one. -// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. -func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int { - for i, v1 := range s1 { - if i >= len(s2) { - return +1 - } - v2 := s2[i] - if c := cmpCompare(v1, v2); c != 0 { - return c - } - } - if len(s1) < len(s2) { - return -1 - } - return 0 -} - -// CompareFunc is like [Compare] but uses a custom comparison function on each -// pair of elements. -// The result is the first non-zero result of cmp; if cmp always -// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), -// and +1 if len(s1) > len(s2). -func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { - for i, v1 := range s1 { - if i >= len(s2) { - return +1 - } - v2 := s2[i] - if c := cmp(v1, v2); c != 0 { - return c - } - } - if len(s1) < len(s2) { - return -1 - } - return 0 -} - -// Index returns the index of the first occurrence of v in s, -// or -1 if not present. -func Index[S ~[]E, E comparable](s S, v E) int { - for i := range s { - if v == s[i] { - return i - } - } - return -1 -} - -// IndexFunc returns the first index i satisfying f(s[i]), -// or -1 if none do. -func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { - for i := range s { - if f(s[i]) { - return i - } - } - return -1 -} - -// Contains reports whether v is present in s. -func Contains[S ~[]E, E comparable](s S, v E) bool { - return Index(s, v) >= 0 -} - -// ContainsFunc reports whether at least one -// element e of s satisfies f(e). -func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { - return IndexFunc(s, f) >= 0 -} - -// Insert inserts the values v... into s at index i, -// returning the modified slice. -// The elements at s[i:] are shifted up to make room. -// In the returned slice r, r[i] == v[0], -// and r[i+len(v)] == value originally at r[i]. -// Insert panics if i is out of range. -// This function is O(len(s) + len(v)). -func Insert[S ~[]E, E any](s S, i int, v ...E) S { - m := len(v) - if m == 0 { - return s - } - n := len(s) - if i == n { - return append(s, v...) - } - if n+m > cap(s) { - // Use append rather than make so that we bump the size of - // the slice up to the next storage class. - // This is what Grow does but we don't call Grow because - // that might copy the values twice. - s2 := append(s[:i], make(S, n+m-i)...) - copy(s2[i:], v) - copy(s2[i+m:], s[i:]) - return s2 - } - s = s[:n+m] - - // before: - // s: aaaaaaaabbbbccccccccdddd - // ^ ^ ^ ^ - // i i+m n n+m - // after: - // s: aaaaaaaavvvvbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // - // a are the values that don't move in s. - // v are the values copied in from v. - // b and c are the values from s that are shifted up in index. - // d are the values that get overwritten, never to be seen again. - - if !overlaps(v, s[i+m:]) { - // Easy case - v does not overlap either the c or d regions. - // (It might be in some of a or b, or elsewhere entirely.) - // The data we copy up doesn't write to v at all, so just do it. - - copy(s[i+m:], s[i:]) - - // Now we have - // s: aaaaaaaabbbbbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // Note the b values are duplicated. - - copy(s[i:], v) - - // Now we have - // s: aaaaaaaavvvvbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // That's the result we want. - return s - } - - // The hard case - v overlaps c or d. We can't just shift up - // the data because we'd move or clobber the values we're trying - // to insert. - // So instead, write v on top of d, then rotate. - copy(s[n:], v) - - // Now we have - // s: aaaaaaaabbbbccccccccvvvv - // ^ ^ ^ ^ - // i i+m n n+m - - rotateRight(s[i:], m) - - // Now we have - // s: aaaaaaaavvvvbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // That's the result we want. - return s -} - -// Delete removes the elements s[i:j] from s, returning the modified slice. -// Delete panics if s[i:j] is not a valid slice of s. -// Delete is O(len(s)-j), so if many items must be deleted, it is better to -// make a single call deleting them all together than to delete one at a time. -// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those -// elements contain pointers you might consider zeroing those elements so that -// objects they reference can be garbage collected. -func Delete[S ~[]E, E any](s S, i, j int) S { - _ = s[i:j] // bounds check - - return append(s[:i], s[j:]...) -} - -// DeleteFunc removes any elements from s for which del returns true, -// returning the modified slice. -// When DeleteFunc removes m elements, it might not modify the elements -// s[len(s)-m:len(s)]. If those elements contain pointers you might consider -// zeroing those elements so that objects they reference can be garbage -// collected. -func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { - i := IndexFunc(s, del) - if i == -1 { - return s - } - // Don't start copying elements until we find one to delete. - for j := i + 1; j < len(s); j++ { - if v := s[j]; !del(v) { - s[i] = v - i++ - } - } - return s[:i] -} - -// Replace replaces the elements s[i:j] by the given v, and returns the -// modified slice. Replace panics if s[i:j] is not a valid slice of s. -func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { - _ = s[i:j] // verify that i:j is a valid subslice - - if i == j { - return Insert(s, i, v...) - } - if j == len(s) { - return append(s[:i], v...) - } - - tot := len(s[:i]) + len(v) + len(s[j:]) - if tot > cap(s) { - // Too big to fit, allocate and copy over. - s2 := append(s[:i], make(S, tot-i)...) // See Insert - copy(s2[i:], v) - copy(s2[i+len(v):], s[j:]) - return s2 - } - - r := s[:tot] - - if i+len(v) <= j { - // Easy, as v fits in the deleted portion. - copy(r[i:], v) - if i+len(v) != j { - copy(r[i+len(v):], s[j:]) - } - return r - } - - // We are expanding (v is bigger than j-i). - // The situation is something like this: - // (example has i=4,j=8,len(s)=16,len(v)=6) - // s: aaaaxxxxbbbbbbbbyy - // ^ ^ ^ ^ - // i j len(s) tot - // a: prefix of s - // x: deleted range - // b: more of s - // y: area to expand into - - if !overlaps(r[i+len(v):], v) { - // Easy, as v is not clobbered by the first copy. - copy(r[i+len(v):], s[j:]) - copy(r[i:], v) - return r - } - - // This is a situation where we don't have a single place to which - // we can copy v. Parts of it need to go to two different places. - // We want to copy the prefix of v into y and the suffix into x, then - // rotate |y| spots to the right. - // - // v[2:] v[:2] - // | | - // s: aaaavvvvbbbbbbbbvv - // ^ ^ ^ ^ - // i j len(s) tot - // - // If either of those two destinations don't alias v, then we're good. - y := len(v) - (j - i) // length of y portion - - if !overlaps(r[i:j], v) { - copy(r[i:j], v[y:]) - copy(r[len(s):], v[:y]) - rotateRight(r[i:], y) - return r - } - if !overlaps(r[len(s):], v) { - copy(r[len(s):], v[:y]) - copy(r[i:j], v[y:]) - rotateRight(r[i:], y) - return r - } - - // Now we know that v overlaps both x and y. - // That means that the entirety of b is *inside* v. - // So we don't need to preserve b at all; instead we - // can copy v first, then copy the b part of v out of - // v to the right destination. - k := startIdx(v, s[j:]) - copy(r[i:], v) - copy(r[i+len(v):], r[i+k:]) - return r -} - -// Clone returns a copy of the slice. -// The elements are copied using assignment, so this is a shallow clone. -func Clone[S ~[]E, E any](s S) S { - // Preserve nil in case it matters. - if s == nil { - return nil - } - return append(S([]E{}), s...) -} - -// Compact replaces consecutive runs of equal elements with a single copy. -// This is like the uniq command found on Unix. -// Compact modifies the contents of the slice s and returns the modified slice, -// which may have a smaller length. -// When Compact discards m elements in total, it might not modify the elements -// s[len(s)-m:len(s)]. If those elements contain pointers you might consider -// zeroing those elements so that objects they reference can be garbage collected. -func Compact[S ~[]E, E comparable](s S) S { - if len(s) < 2 { - return s - } - i := 1 - for k := 1; k < len(s); k++ { - if s[k] != s[k-1] { - if i != k { - s[i] = s[k] - } - i++ - } - } - return s[:i] -} - -// CompactFunc is like [Compact] but uses an equality function to compare elements. -// For runs of elements that compare equal, CompactFunc keeps the first one. -func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { - if len(s) < 2 { - return s - } - i := 1 - for k := 1; k < len(s); k++ { - if !eq(s[k], s[k-1]) { - if i != k { - s[i] = s[k] - } - i++ - } - } - return s[:i] -} - -// Grow increases the slice's capacity, if necessary, to guarantee space for -// another n elements. After Grow(n), at least n elements can be appended -// to the slice without another allocation. If n is negative or too large to -// allocate the memory, Grow panics. -func Grow[S ~[]E, E any](s S, n int) S { - if n < 0 { - panic("cannot be negative") - } - if n -= cap(s) - len(s); n > 0 { - // TODO(https://go.dev/issue/53888): Make using []E instead of S - // to workaround a compiler bug where the runtime.growslice optimization - // does not take effect. Revert when the compiler is fixed. - s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)] - } - return s -} - -// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. -func Clip[S ~[]E, E any](s S) S { - return s[:len(s):len(s)] -} - -// Rotation algorithm explanation: -// -// rotate left by 2 -// start with -// 0123456789 -// split up like this -// 01 234567 89 -// swap first 2 and last 2 -// 89 234567 01 -// join first parts -// 89234567 01 -// recursively rotate first left part by 2 -// 23456789 01 -// join at the end -// 2345678901 -// -// rotate left by 8 -// start with -// 0123456789 -// split up like this -// 01 234567 89 -// swap first 2 and last 2 -// 89 234567 01 -// join last parts -// 89 23456701 -// recursively rotate second part left by 6 -// 89 01234567 -// join at the end -// 8901234567 - -// TODO: There are other rotate algorithms. -// This algorithm has the desirable property that it moves each element exactly twice. -// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes. -// The follow-cycles algorithm can be 1-write but it is not very cache friendly. - -// rotateLeft rotates b left by n spaces. -// s_final[i] = s_orig[i+r], wrapping around. -func rotateLeft[E any](s []E, r int) { - for r != 0 && r != len(s) { - if r*2 <= len(s) { - swap(s[:r], s[len(s)-r:]) - s = s[:len(s)-r] - } else { - swap(s[:len(s)-r], s[r:]) - s, r = s[len(s)-r:], r*2-len(s) - } - } -} -func rotateRight[E any](s []E, r int) { - rotateLeft(s, len(s)-r) -} - -// swap swaps the contents of x and y. x and y must be equal length and disjoint. -func swap[E any](x, y []E) { - for i := 0; i < len(x); i++ { - x[i], y[i] = y[i], x[i] - } -} - -// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap. -func overlaps[E any](a, b []E) bool { - if len(a) == 0 || len(b) == 0 { - return false - } - elemSize := unsafe.Sizeof(a[0]) - if elemSize == 0 { - return false - } - // TODO: use a runtime/unsafe facility once one becomes available. See issue 12445. - // Also see crypto/internal/alias/alias.go:AnyOverlap - return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) && - uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1) -} - -// startIdx returns the index in haystack where the needle starts. -// prerequisite: the needle must be aliased entirely inside the haystack. -func startIdx[E any](haystack, needle []E) int { - p := &needle[0] - for i := range haystack { - if p == &haystack[i] { - return i - } - } - // TODO: what if the overlap is by a non-integral number of Es? - panic("needle not found") -} - -// Reverse reverses the elements of the slice in place. -func Reverse[S ~[]E, E any](s S) { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } -} diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go deleted file mode 100644 index b67897f76b..0000000000 --- a/vendor/golang.org/x/exp/slices/sort.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp - -package slices - -import ( - "math/bits" - - "golang.org/x/exp/constraints" -) - -// Sort sorts a slice of any ordered type in ascending order. -// When sorting floating-point numbers, NaNs are ordered before other values. -func Sort[S ~[]E, E constraints.Ordered](x S) { - n := len(x) - pdqsortOrdered(x, 0, n, bits.Len(uint(n))) -} - -// SortFunc sorts the slice x in ascending order as determined by the cmp -// function. This sort is not guaranteed to be stable. -// cmp(a, b) should return a negative number when a < b, a positive number when -// a > b and zero when a == b. -// -// SortFunc requires that cmp is a strict weak ordering. -// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. -func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { - n := len(x) - pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp) -} - -// SortStableFunc sorts the slice x while keeping the original order of equal -// elements, using cmp to compare elements in the same way as [SortFunc]. -func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { - stableCmpFunc(x, len(x), cmp) -} - -// IsSorted reports whether x is sorted in ascending order. -func IsSorted[S ~[]E, E constraints.Ordered](x S) bool { - for i := len(x) - 1; i > 0; i-- { - if cmpLess(x[i], x[i-1]) { - return false - } - } - return true -} - -// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the -// comparison function as defined by [SortFunc]. -func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool { - for i := len(x) - 1; i > 0; i-- { - if cmp(x[i], x[i-1]) < 0 { - return false - } - } - return true -} - -// Min returns the minimal value in x. It panics if x is empty. -// For floating-point numbers, Min propagates NaNs (any NaN value in x -// forces the output to be NaN). -func Min[S ~[]E, E constraints.Ordered](x S) E { - if len(x) < 1 { - panic("slices.Min: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - m = min(m, x[i]) - } - return m -} - -// MinFunc returns the minimal value in x, using cmp to compare elements. -// It panics if x is empty. If there is more than one minimal element -// according to the cmp function, MinFunc returns the first one. -func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { - if len(x) < 1 { - panic("slices.MinFunc: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - if cmp(x[i], m) < 0 { - m = x[i] - } - } - return m -} - -// Max returns the maximal value in x. It panics if x is empty. -// For floating-point E, Max propagates NaNs (any NaN value in x -// forces the output to be NaN). -func Max[S ~[]E, E constraints.Ordered](x S) E { - if len(x) < 1 { - panic("slices.Max: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - m = max(m, x[i]) - } - return m -} - -// MaxFunc returns the maximal value in x, using cmp to compare elements. -// It panics if x is empty. If there is more than one maximal element -// according to the cmp function, MaxFunc returns the first one. -func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { - if len(x) < 1 { - panic("slices.MaxFunc: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - if cmp(x[i], m) > 0 { - m = x[i] - } - } - return m -} - -// BinarySearch searches for target in a sorted slice and returns the position -// where target is found, or the position where target would appear in the -// sort order; it also returns a bool saying whether the target is really found -// in the slice. The slice must be sorted in increasing order. -func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) { - // Inlining is faster than calling BinarySearchFunc with a lambda. - n := len(x) - // Define x[-1] < target and x[n] >= target. - // Invariant: x[i-1] < target, x[j] >= target. - i, j := 0, n - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if cmpLess(x[h], target) { - i = h + 1 // preserves x[i-1] < target - } else { - j = h // preserves x[j] >= target - } - } - // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. - return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target))) -} - -// BinarySearchFunc works like [BinarySearch], but uses a custom comparison -// function. The slice must be sorted in increasing order, where "increasing" -// is defined by cmp. cmp should return 0 if the slice element matches -// the target, a negative number if the slice element precedes the target, -// or a positive number if the slice element follows the target. -// cmp must implement the same ordering as the slice, such that if -// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. -func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) { - n := len(x) - // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . - // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. - i, j := 0, n - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if cmp(x[h], target) < 0 { - i = h + 1 // preserves cmp(x[i - 1], target) < 0 - } else { - j = h // preserves cmp(x[j], target) >= 0 - } - } - // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i. - return i, i < n && cmp(x[i], target) == 0 -} - -type sortedHint int // hint for pdqsort when choosing the pivot - -const ( - unknownHint sortedHint = iota - increasingHint - decreasingHint -) - -// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf -type xorshift uint64 - -func (r *xorshift) Next() uint64 { - *r ^= *r << 13 - *r ^= *r >> 17 - *r ^= *r << 5 - return uint64(*r) -} - -func nextPowerOfTwo(length int) uint { - return 1 << bits.Len(uint(length)) -} - -// isNaN reports whether x is a NaN without requiring the math package. -// This will always return false if T is not floating-point. -func isNaN[T constraints.Ordered](x T) bool { - return x != x -} diff --git a/vendor/golang.org/x/exp/slices/zsortanyfunc.go b/vendor/golang.org/x/exp/slices/zsortanyfunc.go deleted file mode 100644 index 06f2c7a248..0000000000 --- a/vendor/golang.org/x/exp/slices/zsortanyfunc.go +++ /dev/null @@ -1,479 +0,0 @@ -// Code generated by gen_sort_variants.go; DO NOT EDIT. - -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package slices - -// insertionSortCmpFunc sorts data[a:b] using insertion sort. -func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { - for i := a + 1; i < b; i++ { - for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// siftDownCmpFunc implements the heap property on data[lo:hi]. -// first is an offset into the array where the root of the heap lies. -func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) { - root := lo - for { - child := 2*root + 1 - if child >= hi { - break - } - if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) { - child++ - } - if !(cmp(data[first+root], data[first+child]) < 0) { - return - } - data[first+root], data[first+child] = data[first+child], data[first+root] - root = child - } -} - -func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { - first := a - lo := 0 - hi := b - a - - // Build heap with greatest element at top. - for i := (hi - 1) / 2; i >= 0; i-- { - siftDownCmpFunc(data, i, hi, first, cmp) - } - - // Pop elements, largest first, into end of data. - for i := hi - 1; i >= 0; i-- { - data[first], data[first+i] = data[first+i], data[first] - siftDownCmpFunc(data, lo, i, first, cmp) - } -} - -// pdqsortCmpFunc sorts data[a:b]. -// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. -// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf -// C++ implementation: https://github.com/orlp/pdqsort -// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ -// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. -func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) { - const maxInsertion = 12 - - var ( - wasBalanced = true // whether the last partitioning was reasonably balanced - wasPartitioned = true // whether the slice was already partitioned - ) - - for { - length := b - a - - if length <= maxInsertion { - insertionSortCmpFunc(data, a, b, cmp) - return - } - - // Fall back to heapsort if too many bad choices were made. - if limit == 0 { - heapSortCmpFunc(data, a, b, cmp) - return - } - - // If the last partitioning was imbalanced, we need to breaking patterns. - if !wasBalanced { - breakPatternsCmpFunc(data, a, b, cmp) - limit-- - } - - pivot, hint := choosePivotCmpFunc(data, a, b, cmp) - if hint == decreasingHint { - reverseRangeCmpFunc(data, a, b, cmp) - // The chosen pivot was pivot-a elements after the start of the array. - // After reversing it is pivot-a elements before the end of the array. - // The idea came from Rust's implementation. - pivot = (b - 1) - (pivot - a) - hint = increasingHint - } - - // The slice is likely already sorted. - if wasBalanced && wasPartitioned && hint == increasingHint { - if partialInsertionSortCmpFunc(data, a, b, cmp) { - return - } - } - - // Probably the slice contains many duplicate elements, partition the slice into - // elements equal to and elements greater than the pivot. - if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) { - mid := partitionEqualCmpFunc(data, a, b, pivot, cmp) - a = mid - continue - } - - mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp) - wasPartitioned = alreadyPartitioned - - leftLen, rightLen := mid-a, b-mid - balanceThreshold := length / 8 - if leftLen < rightLen { - wasBalanced = leftLen >= balanceThreshold - pdqsortCmpFunc(data, a, mid, limit, cmp) - a = mid + 1 - } else { - wasBalanced = rightLen >= balanceThreshold - pdqsortCmpFunc(data, mid+1, b, limit, cmp) - b = mid - } - } -} - -// partitionCmpFunc does one quicksort partition. -// Let p = data[pivot] -// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. -// On return, data[newpivot] = p -func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for i <= j && (cmp(data[i], data[a]) < 0) { - i++ - } - for i <= j && !(cmp(data[j], data[a]) < 0) { - j-- - } - if i > j { - data[j], data[a] = data[a], data[j] - return j, true - } - data[i], data[j] = data[j], data[i] - i++ - j-- - - for { - for i <= j && (cmp(data[i], data[a]) < 0) { - i++ - } - for i <= j && !(cmp(data[j], data[a]) < 0) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - data[j], data[a] = data[a], data[j] - return j, false -} - -// partitionEqualCmpFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. -// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. -func partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for { - for i <= j && !(cmp(data[a], data[i]) < 0) { - i++ - } - for i <= j && (cmp(data[a], data[j]) < 0) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - return i -} - -// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end. -func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) bool { - const ( - maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted - shortestShifting = 50 // don't shift any elements on short arrays - ) - i := a + 1 - for j := 0; j < maxSteps; j++ { - for i < b && !(cmp(data[i], data[i-1]) < 0) { - i++ - } - - if i == b { - return true - } - - if b-a < shortestShifting { - return false - } - - data[i], data[i-1] = data[i-1], data[i] - - // Shift the smaller one to the left. - if i-a >= 2 { - for j := i - 1; j >= 1; j-- { - if !(cmp(data[j], data[j-1]) < 0) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - // Shift the greater one to the right. - if b-i >= 2 { - for j := i + 1; j < b; j++ { - if !(cmp(data[j], data[j-1]) < 0) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - } - return false -} - -// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns -// that might cause imbalanced partitions in quicksort. -func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { - length := b - a - if length >= 8 { - random := xorshift(length) - modulus := nextPowerOfTwo(length) - - for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { - other := int(uint(random.Next()) & (modulus - 1)) - if other >= length { - other -= length - } - data[idx], data[a+other] = data[a+other], data[idx] - } - } -} - -// choosePivotCmpFunc chooses a pivot in data[a:b]. -// -// [0,8): chooses a static pivot. -// [8,shortestNinther): uses the simple median-of-three method. -// [shortestNinther,∞): uses the Tukey ninther method. -func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) { - const ( - shortestNinther = 50 - maxSwaps = 4 * 3 - ) - - l := b - a - - var ( - swaps int - i = a + l/4*1 - j = a + l/4*2 - k = a + l/4*3 - ) - - if l >= 8 { - if l >= shortestNinther { - // Tukey ninther method, the idea came from Rust's implementation. - i = medianAdjacentCmpFunc(data, i, &swaps, cmp) - j = medianAdjacentCmpFunc(data, j, &swaps, cmp) - k = medianAdjacentCmpFunc(data, k, &swaps, cmp) - } - // Find the median among i, j, k and stores it into j. - j = medianCmpFunc(data, i, j, k, &swaps, cmp) - } - - switch swaps { - case 0: - return j, increasingHint - case maxSwaps: - return j, decreasingHint - default: - return j, unknownHint - } -} - -// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. -func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) { - if cmp(data[b], data[a]) < 0 { - *swaps++ - return b, a - } - return a, b -} - -// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. -func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int { - a, b = order2CmpFunc(data, a, b, swaps, cmp) - b, c = order2CmpFunc(data, b, c, swaps, cmp) - a, b = order2CmpFunc(data, a, b, swaps, cmp) - return b -} - -// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. -func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int { - return medianCmpFunc(data, a-1, a, a+1, swaps, cmp) -} - -func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { - i := a - j := b - 1 - for i < j { - data[i], data[j] = data[j], data[i] - i++ - j-- - } -} - -func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) { - for i := 0; i < n; i++ { - data[a+i], data[b+i] = data[b+i], data[a+i] - } -} - -func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) { - blockSize := 20 // must be > 0 - a, b := 0, blockSize - for b <= n { - insertionSortCmpFunc(data, a, b, cmp) - a = b - b += blockSize - } - insertionSortCmpFunc(data, a, n, cmp) - - for blockSize < n { - a, b = 0, 2*blockSize - for b <= n { - symMergeCmpFunc(data, a, a+blockSize, b, cmp) - a = b - b += 2 * blockSize - } - if m := a + blockSize; m < n { - symMergeCmpFunc(data, a, m, n, cmp) - } - blockSize *= 2 - } -} - -// symMergeCmpFunc merges the two sorted subsequences data[a:m] and data[m:b] using -// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum -// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz -// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in -// Computer Science, pages 714-723. Springer, 2004. -// -// Let M = m-a and N = b-n. Wolog M < N. -// The recursion depth is bound by ceil(log(N+M)). -// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. -// The algorithm needs O((M+N)*log(M)) calls to data.Swap. -// -// The paper gives O((M+N)*log(M)) as the number of assignments assuming a -// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation -// in the paper carries through for Swap operations, especially as the block -// swapping rotate uses only O(M+N) Swaps. -// -// symMerge assumes non-degenerate arguments: a < m && m < b. -// Having the caller check this condition eliminates many leaf recursion calls, -// which improves performance. -func symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) { - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[a] into data[m:b] - // if data[a:m] only contains one element. - if m-a == 1 { - // Use binary search to find the lowest index i - // such that data[i] >= data[a] for m <= i < b. - // Exit the search loop with i == b in case no such index exists. - i := m - j := b - for i < j { - h := int(uint(i+j) >> 1) - if cmp(data[h], data[a]) < 0 { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[a] reaches the position before i. - for k := a; k < i-1; k++ { - data[k], data[k+1] = data[k+1], data[k] - } - return - } - - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[m] into data[a:m] - // if data[m:b] only contains one element. - if b-m == 1 { - // Use binary search to find the lowest index i - // such that data[i] > data[m] for a <= i < m. - // Exit the search loop with i == m in case no such index exists. - i := a - j := m - for i < j { - h := int(uint(i+j) >> 1) - if !(cmp(data[m], data[h]) < 0) { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[m] reaches the position i. - for k := m; k > i; k-- { - data[k], data[k-1] = data[k-1], data[k] - } - return - } - - mid := int(uint(a+b) >> 1) - n := mid + m - var start, r int - if m > mid { - start = n - b - r = mid - } else { - start = a - r = m - } - p := n - 1 - - for start < r { - c := int(uint(start+r) >> 1) - if !(cmp(data[p-c], data[c]) < 0) { - start = c + 1 - } else { - r = c - } - } - - end := n - start - if start < m && m < end { - rotateCmpFunc(data, start, m, end, cmp) - } - if a < start && start < mid { - symMergeCmpFunc(data, a, start, mid, cmp) - } - if mid < end && end < b { - symMergeCmpFunc(data, mid, end, b, cmp) - } -} - -// rotateCmpFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: -// Data of the form 'x u v y' is changed to 'x v u y'. -// rotate performs at most b-a many calls to data.Swap, -// and it assumes non-degenerate arguments: a < m && m < b. -func rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) { - i := m - a - j := b - m - - for i != j { - if i > j { - swapRangeCmpFunc(data, m-i, m, j, cmp) - i -= j - } else { - swapRangeCmpFunc(data, m-i, m+j-i, i, cmp) - j -= i - } - } - // i == j - swapRangeCmpFunc(data, m-i, m, i, cmp) -} diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go deleted file mode 100644 index 99b47c3986..0000000000 --- a/vendor/golang.org/x/exp/slices/zsortordered.go +++ /dev/null @@ -1,481 +0,0 @@ -// Code generated by gen_sort_variants.go; DO NOT EDIT. - -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package slices - -import "golang.org/x/exp/constraints" - -// insertionSortOrdered sorts data[a:b] using insertion sort. -func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { - for i := a + 1; i < b; i++ { - for j := i; j > a && cmpLess(data[j], data[j-1]); j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// siftDownOrdered implements the heap property on data[lo:hi]. -// first is an offset into the array where the root of the heap lies. -func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { - root := lo - for { - child := 2*root + 1 - if child >= hi { - break - } - if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) { - child++ - } - if !cmpLess(data[first+root], data[first+child]) { - return - } - data[first+root], data[first+child] = data[first+child], data[first+root] - root = child - } -} - -func heapSortOrdered[E constraints.Ordered](data []E, a, b int) { - first := a - lo := 0 - hi := b - a - - // Build heap with greatest element at top. - for i := (hi - 1) / 2; i >= 0; i-- { - siftDownOrdered(data, i, hi, first) - } - - // Pop elements, largest first, into end of data. - for i := hi - 1; i >= 0; i-- { - data[first], data[first+i] = data[first+i], data[first] - siftDownOrdered(data, lo, i, first) - } -} - -// pdqsortOrdered sorts data[a:b]. -// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. -// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf -// C++ implementation: https://github.com/orlp/pdqsort -// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ -// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. -func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { - const maxInsertion = 12 - - var ( - wasBalanced = true // whether the last partitioning was reasonably balanced - wasPartitioned = true // whether the slice was already partitioned - ) - - for { - length := b - a - - if length <= maxInsertion { - insertionSortOrdered(data, a, b) - return - } - - // Fall back to heapsort if too many bad choices were made. - if limit == 0 { - heapSortOrdered(data, a, b) - return - } - - // If the last partitioning was imbalanced, we need to breaking patterns. - if !wasBalanced { - breakPatternsOrdered(data, a, b) - limit-- - } - - pivot, hint := choosePivotOrdered(data, a, b) - if hint == decreasingHint { - reverseRangeOrdered(data, a, b) - // The chosen pivot was pivot-a elements after the start of the array. - // After reversing it is pivot-a elements before the end of the array. - // The idea came from Rust's implementation. - pivot = (b - 1) - (pivot - a) - hint = increasingHint - } - - // The slice is likely already sorted. - if wasBalanced && wasPartitioned && hint == increasingHint { - if partialInsertionSortOrdered(data, a, b) { - return - } - } - - // Probably the slice contains many duplicate elements, partition the slice into - // elements equal to and elements greater than the pivot. - if a > 0 && !cmpLess(data[a-1], data[pivot]) { - mid := partitionEqualOrdered(data, a, b, pivot) - a = mid - continue - } - - mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot) - wasPartitioned = alreadyPartitioned - - leftLen, rightLen := mid-a, b-mid - balanceThreshold := length / 8 - if leftLen < rightLen { - wasBalanced = leftLen >= balanceThreshold - pdqsortOrdered(data, a, mid, limit) - a = mid + 1 - } else { - wasBalanced = rightLen >= balanceThreshold - pdqsortOrdered(data, mid+1, b, limit) - b = mid - } - } -} - -// partitionOrdered does one quicksort partition. -// Let p = data[pivot] -// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. -// On return, data[newpivot] = p -func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for i <= j && cmpLess(data[i], data[a]) { - i++ - } - for i <= j && !cmpLess(data[j], data[a]) { - j-- - } - if i > j { - data[j], data[a] = data[a], data[j] - return j, true - } - data[i], data[j] = data[j], data[i] - i++ - j-- - - for { - for i <= j && cmpLess(data[i], data[a]) { - i++ - } - for i <= j && !cmpLess(data[j], data[a]) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - data[j], data[a] = data[a], data[j] - return j, false -} - -// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. -// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. -func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for { - for i <= j && !cmpLess(data[a], data[i]) { - i++ - } - for i <= j && cmpLess(data[a], data[j]) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - return i -} - -// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end. -func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool { - const ( - maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted - shortestShifting = 50 // don't shift any elements on short arrays - ) - i := a + 1 - for j := 0; j < maxSteps; j++ { - for i < b && !cmpLess(data[i], data[i-1]) { - i++ - } - - if i == b { - return true - } - - if b-a < shortestShifting { - return false - } - - data[i], data[i-1] = data[i-1], data[i] - - // Shift the smaller one to the left. - if i-a >= 2 { - for j := i - 1; j >= 1; j-- { - if !cmpLess(data[j], data[j-1]) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - // Shift the greater one to the right. - if b-i >= 2 { - for j := i + 1; j < b; j++ { - if !cmpLess(data[j], data[j-1]) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - } - return false -} - -// breakPatternsOrdered scatters some elements around in an attempt to break some patterns -// that might cause imbalanced partitions in quicksort. -func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) { - length := b - a - if length >= 8 { - random := xorshift(length) - modulus := nextPowerOfTwo(length) - - for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { - other := int(uint(random.Next()) & (modulus - 1)) - if other >= length { - other -= length - } - data[idx], data[a+other] = data[a+other], data[idx] - } - } -} - -// choosePivotOrdered chooses a pivot in data[a:b]. -// -// [0,8): chooses a static pivot. -// [8,shortestNinther): uses the simple median-of-three method. -// [shortestNinther,∞): uses the Tukey ninther method. -func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) { - const ( - shortestNinther = 50 - maxSwaps = 4 * 3 - ) - - l := b - a - - var ( - swaps int - i = a + l/4*1 - j = a + l/4*2 - k = a + l/4*3 - ) - - if l >= 8 { - if l >= shortestNinther { - // Tukey ninther method, the idea came from Rust's implementation. - i = medianAdjacentOrdered(data, i, &swaps) - j = medianAdjacentOrdered(data, j, &swaps) - k = medianAdjacentOrdered(data, k, &swaps) - } - // Find the median among i, j, k and stores it into j. - j = medianOrdered(data, i, j, k, &swaps) - } - - switch swaps { - case 0: - return j, increasingHint - case maxSwaps: - return j, decreasingHint - default: - return j, unknownHint - } -} - -// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. -func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { - if cmpLess(data[b], data[a]) { - *swaps++ - return b, a - } - return a, b -} - -// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. -func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int { - a, b = order2Ordered(data, a, b, swaps) - b, c = order2Ordered(data, b, c, swaps) - a, b = order2Ordered(data, a, b, swaps) - return b -} - -// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. -func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int { - return medianOrdered(data, a-1, a, a+1, swaps) -} - -func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) { - i := a - j := b - 1 - for i < j { - data[i], data[j] = data[j], data[i] - i++ - j-- - } -} - -func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) { - for i := 0; i < n; i++ { - data[a+i], data[b+i] = data[b+i], data[a+i] - } -} - -func stableOrdered[E constraints.Ordered](data []E, n int) { - blockSize := 20 // must be > 0 - a, b := 0, blockSize - for b <= n { - insertionSortOrdered(data, a, b) - a = b - b += blockSize - } - insertionSortOrdered(data, a, n) - - for blockSize < n { - a, b = 0, 2*blockSize - for b <= n { - symMergeOrdered(data, a, a+blockSize, b) - a = b - b += 2 * blockSize - } - if m := a + blockSize; m < n { - symMergeOrdered(data, a, m, n) - } - blockSize *= 2 - } -} - -// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using -// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum -// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz -// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in -// Computer Science, pages 714-723. Springer, 2004. -// -// Let M = m-a and N = b-n. Wolog M < N. -// The recursion depth is bound by ceil(log(N+M)). -// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. -// The algorithm needs O((M+N)*log(M)) calls to data.Swap. -// -// The paper gives O((M+N)*log(M)) as the number of assignments assuming a -// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation -// in the paper carries through for Swap operations, especially as the block -// swapping rotate uses only O(M+N) Swaps. -// -// symMerge assumes non-degenerate arguments: a < m && m < b. -// Having the caller check this condition eliminates many leaf recursion calls, -// which improves performance. -func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[a] into data[m:b] - // if data[a:m] only contains one element. - if m-a == 1 { - // Use binary search to find the lowest index i - // such that data[i] >= data[a] for m <= i < b. - // Exit the search loop with i == b in case no such index exists. - i := m - j := b - for i < j { - h := int(uint(i+j) >> 1) - if cmpLess(data[h], data[a]) { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[a] reaches the position before i. - for k := a; k < i-1; k++ { - data[k], data[k+1] = data[k+1], data[k] - } - return - } - - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[m] into data[a:m] - // if data[m:b] only contains one element. - if b-m == 1 { - // Use binary search to find the lowest index i - // such that data[i] > data[m] for a <= i < m. - // Exit the search loop with i == m in case no such index exists. - i := a - j := m - for i < j { - h := int(uint(i+j) >> 1) - if !cmpLess(data[m], data[h]) { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[m] reaches the position i. - for k := m; k > i; k-- { - data[k], data[k-1] = data[k-1], data[k] - } - return - } - - mid := int(uint(a+b) >> 1) - n := mid + m - var start, r int - if m > mid { - start = n - b - r = mid - } else { - start = a - r = m - } - p := n - 1 - - for start < r { - c := int(uint(start+r) >> 1) - if !cmpLess(data[p-c], data[c]) { - start = c + 1 - } else { - r = c - } - } - - end := n - start - if start < m && m < end { - rotateOrdered(data, start, m, end) - } - if a < start && start < mid { - symMergeOrdered(data, a, start, mid) - } - if mid < end && end < b { - symMergeOrdered(data, mid, end, b) - } -} - -// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: -// Data of the form 'x u v y' is changed to 'x v u y'. -// rotate performs at most b-a many calls to data.Swap, -// and it assumes non-degenerate arguments: a < m && m < b. -func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) { - i := m - a - j := b - m - - for i != j { - if i > j { - swapRangeOrdered(data, m-i, m, j) - i -= j - } else { - swapRangeOrdered(data, m-i, m+j-i, i) - j -= i - } - } - // i == j - swapRangeOrdered(data, m-i, m, i) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 87fc536d41..715992958e 100755 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -61,6 +61,9 @@ github.com/360EntSecGroup-Skylar/excelize/v2 # github.com/BurntSushi/toml v0.3.1 ## explicit github.com/BurntSushi/toml +# github.com/Jeffail/tunny v0.1.4 +## explicit; go 1.13 +github.com/Jeffail/tunny # github.com/Microsoft/go-winio v0.5.2 ## explicit; go 1.13 github.com/Microsoft/go-winio @@ -1145,8 +1148,6 @@ golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/knownhosts # golang.org/x/exp v0.0.0-20231127185646-65229373498e ## explicit; go 1.20 -golang.org/x/exp/constraints -golang.org/x/exp/slices # golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 ## explicit; go 1.12 golang.org/x/image/bmp