views:

619

answers:

8

If you were to have a naming system in your app where the app contains say 100 actions, which creates new objects, like:

Blur
Sharpen
Contrast
Darken
Matte
...

and each time you use one of these, a new instance is created with a unique editable name, like Blur01, Blur02, Blur03, Sharpen01, Matte01, etc. How would you generate the next available unique name, so that it's an O(1) operation or near constant time. Bear in mind that the user can also change the name to custom names, like RemoveFaceDetails, etc.

It's acceptable to have some constraints, like restricting the number of characters to 100, using letters, numbers, underscores, etc...

EDIT: You can also suggest solutions without "filling the gaps" that is without reusing the already used, but deleted names, except the custom ones of course.

+3  A: 

You can easily do it in O(m) where m is the number of existing instances of the name (and not dependent on n, the number of items in the list.

  1. Look up the string S in question. If S isn't in the list, you're done.
  2. S exists, so construct S+"01" and check for that. Continue incrementing (e.g. next try S+"02" until it doesn't exist.

This gives you unique names but they're still "pretty" and human-readable.

Unless you expect a large number of duplicates, this should be "near-constant" time because m will be so small.

Caveat: What if the string naturally ends with e.g. "01"? In your case this sounds unlikely so perhaps you don't care. If you do care, consider adding more of a suffix, e.g. "_01" instead of just "01" so it's easier to tell them apart.

Jason Cohen
Good idea for the underscore.
Joan Venge
+7  A: 

I would create a static integer in action class that gets incremented and assigned as part of each new instance of the class. For instance:

class Blur
{
    private static int count = 0;

    private string _name;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Blur()
    {
        _name = "Blur" + count++.ToString();
    }
}

Since count is static, each time you create a new class, it will be incremented and appended to the default name. O(1) time.

EDIT

If you need to fill in the holes when you delete, I would suggest the following. It would automatically queue up numbers when items are renamed, but it would be more costly overall:

class Blur
    {
        private static int count = 0;
        private static Queue<int> deletions = new Queue<int>();

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                Delete();
            }
        }

        private int assigned;

        public Blur()
        {
            if (deletions.Count > 0)
            {
                assigned = deletions.Dequeue();
            }
            else
            {
                assigned = count++;
            }
            _name = "Blur" + assigned.ToString();
        }

        public void Delete()
        {
            if (assigned >= 0)
            {
                deletions.Enqueue(assigned);
                assigned = -1;
            }
        }
    }

Also, when you delete an object, you'll need to call .Delete() on the object.

CounterClass Dictionary version

class CounterClass
{
   private int count;
   private Queue<int> deletions;

   public CounterClass()
   {
      count = 0;
      deletions = new Queue<int>();
   }

   public string GetNumber()
   {
      if (deletions.Count > 0)
      {
          return deletions.Dequeue().ToString();
      }
      return count++.ToString();
   }

   public void Delete(int num)
   {
      deletions.Enqueue(num);
   }
}

you can create a Dictionary to look up counters for each string. Just make sure you parse out the index and call .Delete(int) whenever you rename or delete a value.

Mark Synowiec
This is what I came here to say.
GWLlosa
Thanks Mark, but what about deleting objects?
Joan Venge
You're still going to have unique names after the deletion, just with a 'hole' in your sequence. Is that really a problem?
GWLlosa
You are right, maybe not. It's how some of the similar apps I use behaves, so thought I should do the same.
Joan Venge
If you really don't want the hole, I'd suggest adding a queue to keep track of deletions. I'll update my post.
Mark Synowiec
Thanks Mark, interesting idea.
Joan Venge
I like this one Mark, good one. If I use this, then I have to do the same for custom global names, right, things that are outside any class, names like RemoveFaceDetails, etc?
Joan Venge
This solution alone has one problem. If the user renames an existing object, for example Blur02 to Blur07, you will get a collision when you generate Blur07. There is no way around checking against all existing names.
Daniel Brückner
Further, do not reuse deleted values. This will confuse the user because he renamed Blur03 to SomeNicerName and suddenly Blur03 reappears.
Daniel Brückner
a queue will not handle out-of-order deletes. i.e. delete Blur04 then Blur03 will cause the next available to be Blur04, not Blur03. Use a sorted list.
Erich Mirabal
Thanks danbruc, you mean Blur03 reappers when a new object is created? Because it will just pop up then, not after he renames something. But I see your point.
Joan Venge
Another good point Erich. So at the end of the day, if filling gaps is not very good, then I can leave it out.
Joan Venge
Yeah, if youre not using classes and youd like to create counters for each string, you can always create a Dictionary<string, CounterClass> to save values for each string. Youll also need to create a class for the counter. Ill add CounterClass above. ;)
Mark Synowiec
That is what I mean. You create a new object and it gets a name you deleted or modified some time ago because of the reusing. It is tempting to reuse the holes from an astehtic point of view and it avoids getting larger and larger numbers, but it is not intuitive to the user.
Daniel Brückner
I agree with danbruc and Eric, and if you do simplify to not reusing numbers, just go to a basic Dictionary<string,int> and increment the int value every time you use it. No need to get fancy with that implementation.
Mark Synowiec
are you suggesting one class per action? i.e. a Blur class, a Sharpen class? If so, I believe that will be too much trouble. Use a "factory" class with 'ActionName' and 'UserOverrideName' properties. Then, create a SequentialName class with a reference back to the factory that created it.
Erich Mirabal
To respond to danbruc's first post, if you need to ensure that you don't create a duplicate (ie the user changes Blur03 to Blur07), parse the name when the user changes the value, and if they used a standard name and set your counter to max(current in dict,user value + 1).
Mark Synowiec
I assumed that with actions like blur, sharpen, matte,... theyd probably have some type of class associated with their functionality anyway.
Mark Synowiec
If you parse the user edited name and update the corresponding counter if it is a standard name I will crash the appliction in seconds. I rename Blur03 to Blur2147483647 and blur once again. Or just rename to Blur9999999999999999999.
Daniel Brückner
That's a good case to use for testing this. I agree that the system needs to be a little more intelligent to be failsafe.
Mark Synowiec
@Erich: can you please post a small example of what you describe?
Joan Venge
@danbruc: actually I didn't understand how it would crash?
Joan Venge
Also let's say I left out "filling the gaps", but what about user renaming things to say Blur03, where it's available but highest is 15, etc, or other custom names, these will require lookups, right?
Joan Venge
<300 chars:NameFactory BaseName UserName ToString() { return nullOrEmpty(UserName) ? BaseName : UserName } GetNextName() { ... } AddGap() { ... }SequentialName : IDisposable Parent Sequence ToString() { return Parent + Sequence.ToString("00") } Dispose() { Parent.AddGap(this) }
Erich Mirabal
A: 

If you want O(1) time then just track how many instances of each you have. Keep a hashtable with all of the possible objects, when you create an object, increment the value for that object and use the result in the name.

Bob
This is not enough. The user could rename an existing object to a name that will be generated later resulting in a collision.
Daniel Brückner
A: 

You're definitely not going to want to expose a GUID to the user interface.

Are you proposing an initial name like "Blur04", letting the user rename it, and then raising an error message if the user's custom name conflicts? Or silently renaming it to "CustomName01" or whatever?

You can use a Dictionary to check for duplicates in O(1) time. You can have incrementing counters for each effect type in the class that creates your new effect instances. Like Kevin mentioned, it gets more complex if you have to fill in gaps in the numbering when an effect is deleted.

Jesse Smith
Yep, the initial name say gonna be Box04, and then if they rename it to something else they have to do it explicitly. It's not gonna be editable right away because most of the time they won't rename things. When they do, it also has be unique or the app is gonna add the next available number to it.
Joan Venge
+6  A: 

I refer you to Michael A. Jackson's Two Rules of Program Optimization:

  1. Don't do it.
  2. For experts only: Don't do it yet.

Simple, maintainable code is far more important than optimizing for a speed problem that you think you might have later.

I would start simple: build a candidate name (e.g. "Sharpen01"), then loop through the existing filters to see if that name exists. If it does, increment and try again. This is O(N2), but until you get thousands of filters, that will be good enough.

If, sometime later, the O(N2) does become a problem, then I'd start by building a HashSet of existing names. Then you can check each candidate name against the HashSet, rather than iterating. Rebuild the HashSet each time you need a unique name, then throw it away; you don't need the complexity of maintaining it in the face of changes. This would leave your code easy to maintain, while only being O(N).

O(N) will be good enough. You do not need O(1). The user is not going to click "Sharpen" enough times for there to be any difference.

Joe White
+2  A: 

You could do something like this:

    private Dictionary<string, int> instanceCounts = new Dictionary<string, int>();

    private string GetNextName(string baseName)
    {
        int count = 1;

        if (instanceCounts.TryGetValue(baseName, out count))
        {
            // the thing already exists, so add one to it
            count++;
        }

        // update the dictionary with the new value
        instanceCounts[baseName] = count;

        // format the number as desired
        return baseName + count.ToString("00");
    }

You would then just use it by calling GetNextName(...) with the base name you wanted, such as string myNextName = GetNextName("Blur");

Using this, you wouldn't have to pre-init the dictionary. It would fill in as you used the various base words. Also, this is O(1).

Erich Mirabal
Thanks Erich, How do you handle deletion of objects, aka "holes"?
Joan Venge
Is there a good reason to handle that case in practice? If you did want to handle holes, you could use a sorted list to hold the deleted names.Dictionary<string, SortedList<String>> holes = new ...on delete, add the deleted item to holes[baseName] and check that list before the count dictionary.
Erich Mirabal
Actually that's a good suggestion, I don't think there is a compelling reason. I am all for best practice, so if it doesn't add any value, I can leave that out.
Joan Venge
+1  A: 

I would create a dictionary with a string key and a integer value, storing the next number to use for a given action. This will be almost O(1) in practice.

private IDictionary<String, Int32> NextFreeActionNumbers = null;       

private void InitializeNextFreeActionNumbers()
{
   this.NextFreeActionNumbers = new Dictionary<String, Int32>();

   this.NextFreeActionNumbers.Add("Blur", 1);
   this.NextFreeActionNumbers.Add("Sharpen", 1);
   this.NextFreeActionNumbers.Add("Contrast", 1);
   // ... and so on ...
}

private String GetNextActionName(String action)
{
   Int32 number = this.NextFreeActionNumbers[action];

   this.NextFreeActionNumbers[action] = number + 1;

   return String.Format("{0} {1}", action, number);
}

And you will have to check against collisions with user edited values. Again a dictionary might be a smart choice. There is no way around that. What ever way you generate your names, the user can always change a existing name to the next one you generate unless you include all existing names into the generation schema. (Or use a special character that is not allowed in user edited names, but that would be not that nice.)

Because of the comments on reusing the holes I want to add it here, too. Don't resuse the holes generated be renaming or deletion. This will confuse the user because names he deleted or modified will suddenly reappear.

Daniel Brückner
+1  A: 

I would look for ways to simplify the problem.

Are there any constraints that can be applied? As an example, would it be good enough if each user can only have one (active) type of action? Then, the actions could be distinguished using the name (or ID) of the user.

  • Blur (Ben F)
  • Blur (Adrian H)
  • Focus (Ben F)

Perhaps this is not an option in this case, but maybe something else would be possible. I would go to great lengths in order to avoid the complexity in some of the proposed solutions!

Lars A. Brekken
Hi Lars, but there will be more than 1 of the same action. And in the grand scheme of things, all these actions will be visible and yeah one or more is gonna be selected, etc.
Joan Venge
Can you use the date is was created instead of the name, or perhaps a small description that specifies what makes one Blur different from the other one? Why are there several to begin with?
Lars A. Brekken