// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Text.RegularExpressions; namespace osu.Game.Utils { public static class NamingUtils { /// /// Given a set of and a target , /// finds a "best" name closest to that is not in . /// /// /// /// This helper is most useful in scenarios when creating new objects in a set /// (such as adding new difficulties to a beatmap set, or creating a clone of an existing object that needs a unique name). /// If is already present in , /// this method will append the lowest possible number in brackets that doesn't conflict with /// to and return that. /// See osu.Game.Tests.Utils.NamingUtilsTest for concrete examples of behaviour. /// /// /// and are compared in a case-insensitive manner, /// so this method is safe to use for naming files in a platform-invariant manner. /// /// public static string GetNextBestName(IEnumerable existingNames, string desiredName) { string pattern = $@"^(?i){Regex.Escape(desiredName)}(?-i)( \((?[1-9][0-9]*)\))?$"; var regex = new Regex(pattern, RegexOptions.Compiled); var takenNumbers = new HashSet(); foreach (string name in existingNames) { var match = regex.Match(name); if (!match.Success) continue; string copyNumberString = match.Groups[@"copyNumber"].Value; if (string.IsNullOrEmpty(copyNumberString)) { takenNumbers.Add(0); continue; } takenNumbers.Add(int.Parse(copyNumberString)); } int bestNumber = 0; while (takenNumbers.Contains(bestNumber)) bestNumber += 1; return bestNumber == 0 ? desiredName : $"{desiredName} ({bestNumber})"; } } }