0

I need to pull a bunch of key value pairs based on a predefined pattern from a string. An example of what I would need is this:

Pattern: {value1}-{value2}

String: Example-String

Result KVP:

{ Key: value1, value: Example },
{ Key: value2, value: String }

The catch is that the pattern could be pretty much anything (although the values I'd need to extract would always be surrounded in curly brackets), ie:

Pattern: {test1}\{test2}={value}

String: Example\Value=Result

Result KVP:

{ Key: test1, value: Example },
{ Key: test2, value: Value },
{ Key: value, value: Result }

What I have done so far isn't quite working and I'm quite certain that there has to be a more elegant way of doing this as opposed to my solution anyway so I thought I'd see if anyone here would have a good idea.

EDIT:

Here is essentially what I have so far (it's working, but IMO it's really ugly):

public List<KeyValuePair<string, string>> Example(string pattern, string input)
{

    var values = new List<KeyValuePair<string, string>>();

    var r1 = Regex.Matches(input, @"(\{[A-Z,a-z]*\})");
    string newregex = string.Empty;

    foreach (Match item in r1)
    {
        newregex = newregex.Replace(item.Value, "(.*?)"); //updates regex so that it adds this as a group for use later, ie: "{item1}-{item2}" will become "(.*?)-{item2}"
        string field = item.Value.Substring(1, item.Value.Length - 2); // {test1} will return "test1"

        values.Add(new KeyValuePair<string, string>(field, string.Empty));
    }

    newregex = $"{newregex}\\z"; // ensures that it matches to end of input

    var r2 = Regex.Match(input, newregex);

    // KVP index (used below)
    int val = 0;

    foreach (Group g in r2.Groups)
    {
        if (g.Value == input)
            continue; // first group will be equal to input, ignore

        values[val] = new KeyValuePair<string, string>(values[val].Key, g.Value); // update KVP at index with new KVP with the value
        val++;

    }

    return values;
}
BKF
  • 1
  • 2
  • Sounds like you need some regex – maccettura Nov 18 '19 at 19:37
  • Do you have a predefined list of delimeters (like `'-'` and `'='`), or at least some rule that defines a delimeter (i.e. any non-alphanumeric character)? – Rufus L Nov 18 '19 at 19:37
  • @RufusL currently we're only using '-' and '_'. I was hoping to come up with a general solution in case in future we have a need for different delimiters though. – BKF Nov 18 '19 at 19:40
  • Are you looking for a method that takes in two strings (a pattern and a search string) and returns a `List`? It would be extremely helpful to see what you have done so far so we can see where it's not working. – Rufus L Nov 18 '19 at 19:41
  • @RufusL essentially, yes, a method like that would be ideal. I'll edit my post with what I have done shortly. – BKF Nov 18 '19 at 19:42
  • @RufusL updated with code – BKF Nov 18 '19 at 20:09

1 Answers1

1

Unfortunately I don't know regular expressions very well, but one way to solve this is to walk through each character of the pattern string and create a list of keys and delimeters, after which we can walk through the search string, and find the index of each delimeter to get the current value, and then add a new KeyValuePair to a list.

Here's a rough sample that assumes good input:

public static List<KeyValuePair<string, string>> GetKVPs(string pattern, string search)
{
    var results = new List<KeyValuePair<string, string>>();
    var keys = new List<string>();
    var delimeters = new List<string>();
    var currentKey = string.Empty;
    var currentDelimeter = string.Empty;
    var processingKey = false;

    // Populate our lists of Keys and Delimeters
    foreach (var chr in pattern)
    {
        switch (chr)
        {
            case '}':
            {
                if (currentKey.Length > 0)
                {
                    keys.Add(currentKey);
                    currentKey = string.Empty;
                }

                processingKey = false;
                break;
            }
            case '{':
            {
                if (currentDelimeter.Length > 0)
                {
                    delimeters.Add(currentDelimeter);
                    currentDelimeter = string.Empty;
                }

                processingKey = true;
                break;
            }
            default:
            {
                if (processingKey)
                {
                    currentKey += chr;
                }
                else
                {
                    currentDelimeter += chr;
                }
                break;
            }
        }
    }

    if (currentDelimeter.Length > 0) delimeters.Add(currentDelimeter);

    var lastDelim = -1;

    // Find our Values based on the delimeter positions in the search string
    for (int i = 0; i < delimeters.Count; i++)
    {
        var delimIndex = search.IndexOf(delimeters[i], lastDelim + 1);

        if (delimIndex > -1)
        {
            var value = search.Substring(lastDelim + 1, delimIndex - lastDelim - 1);
            results.Add(new KeyValuePair<string, string>(keys[i], value));
            lastDelim = delimIndex + delimeters[i].Length - 1;
        }
    }

    // Add the item after the final delimeter if it exists:
    if (lastDelim > -1 && lastDelim < search.Length - 1)
    {
        results.Add(new KeyValuePair<string, string>(keys.Last(),
            search.Substring(lastDelim + 1)));
    }

    return results;
}

And an example of it in action:

public static void Main(string[] args)
{
    var results = GetKVPs(
        "{greeting}, {recipient}, this is {sender}.", 
        "Hello, Dolly, this is Louis.");

    foreach (var kvp in results)
    {
        Console.WriteLine($"{kvp.Key} = {kvp.Value}");
    }

    GetKeyFromUser("\nDone! Press any key to exit...");
}

Output

enter image description here

Rufus L
  • 36,127
  • 5
  • 30
  • 43