3

I've been wrapping my head around this to find an "elegant" solution but I'm not quite satisfied with it.

Possible input strings:

foo () bar ()

() bar

foo

()()foo () bar

There can be "unlimited" brackets and optional, non-bracket text inbetween the brackets. The content of these empty brackets are supposed to filled with data taking from a List<string> in the order of the list entries. If there no entries or not sufficient entries the brackets are untouched.

Possible string replacements:

foo () bar () replaced with x, y will result in foo (x) bar (y)

foo () bar () replaced with x will result in foo (x) bar ()

foo () bar () replaced with x, y, z will result in foo (x) bar (y)

I hope you get the idea.

Solutions: The solutions I had so far are fiddling around with indexes and a lot special logic to handle the different cases.

I wondered if there is a more elegant solution with, for example regex. Maybe I'm too close at the problem right now and there is a simple solution :-)

Here is an approach I'm not really happy about (readability / easy to understand):

  var guiIdentifierIndex = 0;
  var guiIdentifierList = new List<string>{"x", "y", "z", "x", "y"};

  var sourcePathItem = "foo ()";
  string targetString = "";
  var splittedPath = sourcePathItem.Split(new string[] { BRACKETS }, StringSplitOptions.None);
  for (int index = 0; index < splittedPath.Length; index++)
  {
    var subPath = splittedPath[index];
    var guiIdentifier = string.Empty;
    if (guiIdentifierIndex < guiIdentifierList.Count)
    {
      guiIdentifier = guiIdentifierList[guiIdentifierIndex];
      guiIdentifierIndex++;
    }
    targetString += subPath;
    if (index < splittedPath.Length - 1)
      targetString += string.Format("({0})", guiIdentifier);
  }

http://volatileread.com/utilitylibrary/snippetcompiler?id=22718

Cœur
  • 37,241
  • 25
  • 195
  • 267
DerApe
  • 3,097
  • 2
  • 35
  • 55

8 Answers8

1

You can use regular expressions, e.g.

  String source = "foo () bar ()";

  var guiIdentifierList = new List<String> { 
    "x", "y", "z", "x", "y" };

  int guiIdentifierIndex = 0; 

  // result == "foo (x) bar (y)"
  String result = Regex.Replace(source, @"\(\)", (MatchEvaluator) (
    (match) => "(" + (guiIdentifierIndex < guiIdentifierList.Count 
                      ? guiIdentifierList[guiIdentifierIndex++] 
                      : "") + ")"
  ));
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • This won't work if there are not enough identifier in the list (out of range) – DerApe Jul 16 '15 at 11:52
  • @derape: you're right - I've edited the code - I've not paid attention to *If there no entries or not sufficient entries the brackets are untouched* - usually, if there're two few items, a better choice is to throw exception. – Dmitry Bychenko Jul 16 '15 at 11:58
  • Yay =) Could there be any problems with the `guiIdentifierIndex` being in a closure? – DerApe Jul 16 '15 at 12:15
  • No; unfortunally, `match.Index` is the index within the *original string*, not within all the matches, so I have to implement it like that. – Dmitry Bychenko Jul 16 '15 at 12:20
0

Split the replacement string (x,y,z) on commas, then loop through the resulting array replacing the first occurrence of () by the appropriate value.

This link shows you how to replace the first instance : Replace first occurrence of pattern in a string

Community
  • 1
  • 1
PaulF
  • 6,673
  • 2
  • 18
  • 29
0

How about this:

var template = "foo () bar ()";
var replacements = new[] {"x", "y", "z"};

var components = template.Split(new []{"()"}, StringSplitOptions.RemoveEmptyEntries);
var sb = new StringBuilder();

var replacementCount = replacements.Count();

for (int i = 0; i < components.Count(); i++)
{
    var value = i >= replacementCount ? String.Empty : replacements[i];
    sb.AppendFormat("{0}({1})", components[i], value);
}

var substitutedTemplate = sb.ToString();
Prabu
  • 4,097
  • 5
  • 45
  • 66
0
var numbers = new List<string>(new[] { "1", "2", "3" });
string original = "() test ()()";

String[] tokens = original.Split(new [] {"()"}, StringSplitOptions.None);

if (tokens.Count() >= numbers.Count())
    return original;

return string.Concat(tokens.Take(tokens.Count() - 1)
                           .Select((t, i) => t + "(" + numbers[i] + ")"));
GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • The constraint you introduced by the if condition does not meet my requirements. more tokens then numbers has to be supported (brackets will stay empty) – DerApe Jul 16 '15 at 12:03
0

You could try to construct the target string in one foor loop like this ("Copy and Substitute")

EDIT: Now with "lookahead" to not substitute if closing bracket is missing.

var guiIdentifierIndex = 0;
var guiIdentifierList = new List<string> { "x", "y", "z" };

var sourcePathItem = "foo () bar () func()";
string targetString = "";
int srcIndex = 0;

foreach(char c in sourcePathItem)
{
    targetString += c;
    char lookahead = srcIndex < sourcePathItem.Length - 1 ? sourcePathItem[++srcIndex] : ' ';

    if (c == '(' && lookahead == ')' 
        && guiIdentifierIndex < guiIdentifierList.Count)
    {
        targetString += guiIdentifierList[guiIdentifierIndex++];
    }
}
Console.WriteLine("Target: " + targetString);

This substitutes foo () bar () with x, y to foo (x) bar (y).

Generally I think using a Stringbuilder for the target string would be better when sourcePathItem and guidIdentifierList is getting bigger. e.g. less resource usage

Greetings

Muraad Nofal
  • 794
  • 3
  • 6
0

I would do this way:

List<string> guiIdentifiers = new List<string>{"x", "y", "z", "x", "y"};

string input = "foo () () () () () ()";

string[] splitPath = Regex.Split(input, @"(\(\))");

for (int i = 0; i < splitPath.Length; i++)
{
  if (splitPath[i] == "()" && guiIdentifiers.Count > 0)
  {
    splitPath[i] = string.Format("({0})", guiIdentifiers.First()); 
    guiIdentifiers.Remove(guiIdentifiers.First());
  }
}
string result = string.Join("", splitPath);

And note that split is an irregular verb that has the third form as split also, splitted is really weird and I do not recommend you to use.

3Ducker
  • 346
  • 1
  • 9
  • I do not have to stick to string split, thats why I asked ;-) – DerApe Jul 16 '15 at 11:55
  • And it does not use string split, it uses Regex.Split. The difference is that regex keeps the delimiters in the array. Or you mean you wouldn't split the string anyhow? – 3Ducker Jul 16 '15 at 11:57
  • Well, I was thinking about not posting any solution at all to not influence you guys, because I was open to all kinds of solutions to this, but I guess that is not how I should ask questions :-D – DerApe Jul 16 '15 at 12:12
  • I think if you didn't post your solution split would appear in everyone's solution because that's the point you should use for this purpose. – 3Ducker Jul 16 '15 at 12:18
0

Hi you could used Regex groups. https://msdn.microsoft.com/en-us/library/ewy2t5e0(v=vs.110).aspx

The code below matches on ( and create a regex group which you could the used to replace.

 var sourcePathItem = "()()foo () bar";
        var q = new Queue<string>(new string[]{ "x", "y", "z", "x", "y" });
        var replaced = Regex.Replace(sourcePathItem, @"\(", m =>

            (m.Groups[1].Value + "(" + q.Dequeue())
        );
-1
 StringBuilder sb = new StringBuilder();
 String[] parts = sourcePathItem.Split(new String[]{"()"}, StringSplitOptions.None);

 for (Int32 i = 0; i < parts.Length; i++){
      sb.Append(parts[i]);
      if (i < guiIdentifierList.Count && i + 1 < parts.Length)
          sb.Append("(" + guiIdentifierList[i] + ")");
 }

 var result = sb.ToString();
JamesFaix
  • 8,050
  • 9
  • 37
  • 73