Adding a couple of useful extensions to the String type in Dart, to find if a string contains another string more than once.

I recently came upon this issue while working on Shortcut Keeper, my Flutter desktop app.

I needed to find a way to check if a string like “Command-K Command-T” had the substring “Command” more than once, and if so, from which index did the second occurrence of the substring begin.

To solve this, I ended up writing a couple of simple extension methods to Dart’s String type, which can be useful for anyone facing this issue!

The general issue

In other words, we need to find:

1) If a string contains another string (stringToFind) more than once, and

2) The index where this second occurrence can be found.

We can then further extend this to be able to potentially find the locations of the nth occurrence of stringToFind.

Of course, we could also solve this with regular expressions (via Dart’s RegExp class), but I prefer the more readable way presented here.

Solution

Finding the second occurrence of a substring

The extension I ended up with is a pretty straightforward one:

extension SecondOccurenceOfSubstring on String {
  int secondIndexOf(String stringToFind) {
    if (indexOf(stringToFind) == -1) return -1;
    return indexOf(stringToFind, indexOf(stringToFind) + 1);
  }
  bool hasSecondOccurence(String stringToFind) {
    return secondIndexOf(stringToFind) != -1;
  }
}

Here, we take advantage of the second optional parameter (start) of the indexOf method from Dart’s core library, to set from where to begin searching for the string. If the string is not found, we return the conventional -1.

The hasSecondOccurence method is a convenient way to check if a substring appears more than once in the provided string.

We can then simply use it like this:

String s1 = "Command-K Command-T";
s1.secondIndexOf("Command"); // Returns 10
s1.hasSecondOccurence("Command"); // Returns true
s1.secondIndexOf("Control"); // Returns -1
s1.hasSecondOccurence("Control"); // Returns false
String s2 = "Give it away give it away give it away now";
s2.secondIndexOf("away"); // Returns 21

This works great, so we can go one step further and build a more general extension, for finding the location of any occurrence of a substring in a string.

Finding the nth occurrence of a substring

Now, we want to provide a method, such that we can find, for example, the index of the fourth (n = 4) occurrence of a substring in a string:

String s3 = "Give it away give it away give it away give it away give it away now";
s3.nThIndexOf("away", 4); // Returns 47

In the above example, the fourth "away" substring can be found after 47 characters in our provided string s3.

We can use this extension to do that:

extension NthOccurrenceOfSubstring on String {
  int nThIndexOf(String stringToFind, int n) {
    if (indexOf(stringToFind) == -1) return -1;
    if (n == 1) return indexOf(stringToFind);
    int subIndex = -1;
    while (n > 0) {
      subIndex = indexOf(stringToFind, subIndex + 1);
      n -= 1;
    }
    return subIndex;
  }
  
  bool hasNthOccurrence(String stringToFind, int n) {
    return nThIndexOf(stringToFind, n) != -1;
  }
}

Again, we apply the same strategy as above, with the indexOf method, to move closer each time to the nth occurrence that we want. To help with understanding, this is what happens in the nThIndexOf method when we call it with n = 4:

Here are some more examples using this extension:

String s3 = "Give it away give it away give it away give it away give it away now";
s3.nThIndexOf("away", 2); // Returns 21
s3.nThIndexOf("away", 3); // Returns 34
s3.nThIndexOf("away", 4); // Returns 47
s3.nThIndexOf("away", 5); // Returns 60
s3.nThIndexOf("away", 6); // Returns -1
s3.hasNthOccurrence("away", 3); // true, we can find "away" at least 3 times in the string
s3.hasNthOccurrence("away", 6); // false, we cannot find a 6th occurrence of "away" in the string

We can now use these string extensions easily anywhere we need this kind of functionality in our Dart code.

Hope this small tip was helpful!