Распарсить строку в Dictionary<string, string>
От: teapot2  
Дата: 25.04.13 10:37
Оценка:
Добрый день, друзья.

Необходимо мне в своем коде парсить строки вида "A=1; B=122; c=x=2,у=3" в словарь, в данном случае должно получиться такое:

A: 1
B: 122
c: x=2,y=3.

Захотел поначалу все это реализовать в виде функции, а теперь задумался — зачем изобретать велосипед, наверняка должны быть какие-то готовые функции в .net. Курил MSDN, нашел какие-то функции расширения класса string, — ToDictionary(...), — посмотрел я их, но там как-то все сложно и непонятно, какие-то аргументы типа IENumerable зачем-то. В общем, не понял я, можно ли их прикрутить для решения моей задачи.

Собственно, вопрос: есть ли какая-нибудь встроенная функция в дот-нете, которая делает subj, и которой можно управлять заданием двух массивов char: один содержит разделители элементов конфига в строке (в данном случае — единственный разделитель ';'), второй — разделители ключа и значения (если в одном элементе их несколько, то второй и последующие являются частью значения, как видно из приведенного выше примера). Ну, или, хотя бы часть этой работы. Поделитесь опытом, как бы вы реализовали такую штуку.
Re: Распарсить строку в Dictionary<string, string>
От: vmpire Россия  
Дата: 25.04.13 10:52
Оценка:
Здравствуйте, teapot2, Вы писали:

T>Добрый день, друзья.


T>Необходимо мне в своем коде парсить строки вида "A=1; B=122; c=x=2,у=3" в словарь, в данном случае должно получиться такое:


T>A: 1

T>B: 122
T>c: x=2,y=3.


string s = "A=1; B=122; c=x=2,у=3";
var dictionary = s.Split(';').ToDictionary(pair => pait.Split('=')[0], pair => pait.Split('=')[1]);


Синтаксис не проверял, если что
Re: Распарсить строку в Dictionary<string, string>
От: QrystaL Украина  
Дата: 25.04.13 11:11
Оценка: +2
Здравствуйте, teapot2, Вы писали:
T>...
T>Поделитесь опытом, как бы вы реализовали такую штуку.

string input = "A=1; B=122; c=x=2,у=3";
var delims1 = new[] { ';' };
var delims2 = new[] { '=' };
var result = input.Split(delims1)
    .Select(v => v.Split(delims2, 2))
    .ToDictionary(v => v[0].Trim(), v => v[1].Trim());
Re: Распарсить строку в Dictionary<string, string>
От: pugv Россия  
Дата: 25.04.13 14:50
Оценка:
Здравствуйте, teapot2, Вы писали:

T>Собственно, вопрос: есть ли какая-нибудь встроенная функция в дот-нете, которая делает subj, и которой можно управлять заданием двух массивов char: один содержит разделители элементов конфига в строке (в данном случае — единственный разделитель ';'), второй — разделители ключа и значения (если в одном элементе их несколько, то второй и последующие являются частью значения, как видно из приведенного выше примера). Ну, или, хотя бы часть этой работы. Поделитесь опытом, как бы вы реализовали такую штуку.


Можно еще регуляркой попробовать:

public static Dictionary<string, string> ParseConfig(string Config, string elementSeparators, string valueSeparators)
{
    Dictionary<string, string> result = new Dictionary<string, string>();

    string re = "((([^" + elementSeparators + "=]+)[" + valueSeparators + "]([^" + elementSeparators + "]+))[\\s]*)";
    Match m = Regex.Match(Config, re);
    while (m.Groups.Count == 5)
    {
        result.Add(m.Groups[3].Value, m.Groups[4].Value);
        m = m.NextMatch();
    }

    return result;
}
Re[2]: Распарсить строку в Dictionary<string, string>
От: teapot2  
Дата: 26.04.13 13:43
Оценка:
Здравствуйте, QrystaL, Вы писали:

QL>
QL>string input = "A=1; B=122; c=x=2,у=3";
QL>var delims1 = new[] { ';' };
QL>var delims2 = new[] { '=' };
QL>var result = input.Split(delims1)
QL>    .Select(v => v.Split(delims2, 2))
QL>    .ToDictionary(v => v[0].Trim(), v => v[1].Trim());
QL>


Спасибо за ответ!

Насколько усложнится эта схема, если мы захотим управлять инцидентами с возможным дублированием ключей в списке, предусмотрев один из трех вариантов реакции:
— бросать исключение;
— сохранять первый встретившийся элемент;
— каждый раз перезаписывать элемент с таким же ключом (т.е. в итоге сохранится последний);

Скажем, предусмотрев дополнительный параметр такого типа:

        public enum OnDuplicateKey
        {
            RaiseError = 0,
            SaveFirst  = 1,
            Rewrite    = 2
        }
Re[3]: Распарсить строку в Dictionary<string, string>
От: QrystaL Украина  
Дата: 26.04.13 14:44
Оценка:
Здравствуйте, teapot2, Вы писали:
T>Насколько усложнится эта схема, если мы захотим управлять инцидентами с возможным дублированием ключей в списке

Как-то так:

public static IDictionary<String, String> ParseToDictionary(String input, OnDuplicateKey strategy)
{
    var delims1 = new[] { ';' };
    var delims2 = new[] { '=' };
    var preResult = input.Split(delims1)
        .Select(v => v.Split(delims2, 2))
        .ToLookup(v => v[0].Trim(), v => v[1].Trim());
    switch (strategy)
    {
        case OnDuplicateKey.RaiseError:
            return preResult.ToDictionary(g => g.Key, g => g.Single());
        case OnDuplicateKey.SaveFirst:
            return preResult.ToDictionary(g => g.Key, g => g.First());
        case OnDuplicateKey.Rewrite:
            return preResult.ToDictionary(g => g.Key, g => g.Last());
        default:
            return null;
    }
}
Re[4]: Распарсить строку в Dictionary<string, string>
От: teapot2  
Дата: 29.04.13 06:58
Оценка:
Здравствуйте, QrystaL, Вы писали:

QL>Как-то так:


QL>
QL>public static IDictionary<String, String> ParseToDictionary(String input, OnDuplicateKey strategy)
QL>{
QL>    var delims1 = new[] { ';' };
QL>    var delims2 = new[] { '=' };
QL>    var preResult = input.Split(delims1)
QL>        .Select(v => v.Split(delims2, 2))
QL>        .ToLookup(v => v[0].Trim(), v => v[1].Trim());
QL>    switch (strategy)
QL>    {
QL>        case OnDuplicateKey.RaiseError:
QL>            return preResult.ToDictionary(g => g.Key, g => g.Single());
QL>        case OnDuplicateKey.SaveFirst:
QL>            return preResult.ToDictionary(g => g.Key, g => g.First());
QL>        case OnDuplicateKey.Rewrite:
QL>            return preResult.ToDictionary(g => g.Key, g => g.Last());
QL>        default:
QL>            return null;
QL>    }
QL>}
QL>


Спасибо. Уже кое-что. Но и с этим кодом есть проблемы.

1. Он "падает" на "пустых" конфигурационных элементах. Конечно, ситуацию двух разделителей подряд (когда в строке встретится такое "...;;...") пофиксить достаточно просто, надо вызвать метод split с соответствующей опцией:
...
   var delims1 = new[] { ';' };
   var delims2 = new[] { '=' };
   var preResult = input.Split(delims1, StringSplitOptions.RemoveEmptyEntries)
...

Но этот трюк "обламывается", если между разделителями встретится пробельный символ: "...; ;..."
Как отфильтровывать пустые элементы?

2. Эстетический момент. Можно ли обойтись без switcha'а в коде — а то как-то не вполне кошерно "функционально" получается? ЕМНИП, где-то на Хабре мне встречалось описание аналогичного случая — когда с использованием трюка на лямбдах удавалось убрать switch из кода, заменив его выборкой из предварительно составленного словаря, ключом которого был "аргумент" switch, а значением метод.
Re[5]: Распарсить строку в Dictionary<string, string>
От: QrystaL Украина  
Дата: 29.04.13 07:31
Оценка:
Здравствуйте, teapot2, Вы писали:
T>Спасибо. Уже кое-что. Но и с этим кодом есть проблемы.

Вы ведь не думаете, что на форуме вам напишут готовый production код?

По поводу остальных вопросов:

var delims1 = new[] { ';' };
var delims2 = new[] { '=' };
var strategiesSet = new Dictionary<OnDuplicateKey, Func<IGrouping<String, String>, String>>
    {
        { OnDuplicateKey.RaiseError, g => g.Single() },
        { OnDuplicateKey.SaveFirst, g => g.First() },
        { OnDuplicateKey.Rewrite, g => g.Last() }
    };
return input.Split(delims1)
    .Select(v => v.Split(delims2, 2))
    .Where(v => v.Length == 2)
    .ToLookup(v => v[0].Trim(), v => v[1].Trim())
    .ToDictionary(g => g.Key, strategiesSet[strategy]);
Re[6]: Распарсить строку в Dictionary<string, string>
От: teapot2  
Дата: 30.04.13 12:14
Оценка:
Здравствуйте, QrystaL, спасибо за ответ.

Вы писали:

QL>Вы ведь не думаете, что на форуме вам напишут готовый production код?

Ну до production тут еще весьма далеко. С другой стороны, production code я все-таки написал — не в функциональном стиле. В чисто академических целях хочется довести до такого же состояния и код в функциональном стиле, чтобы посмотреть, действительно ли он будет более простым и понятным. Разумеется, на форуме мне его не напишут, но помочь в тех вопросах, которыми я владею не очень уверенно, могут весьма существенно. Что, собственно, и происходит — за это спасибо.

QL>По поводу остальных вопросов:


QL>
QL>var delims1 = new[] { ';' };
QL>var delims2 = new[] { '=' };
QL>var strategiesSet = new Dictionary<OnDuplicateKey, Func<IGrouping<String, String>, String>>
QL>    {
QL>        { OnDuplicateKey.RaiseError, g => g.Single() },
QL>        { OnDuplicateKey.SaveFirst, g => g.First() },
QL>        { OnDuplicateKey.Rewrite, g => g.Last() }
QL>    };
QL>return input.Split(delims1)
QL>    .Select(v => v.Split(delims2, 2))
QL>    .Where(v => v.Length == 2)
QL>    .ToLookup(v => v[0].Trim(), v => v[1].Trim())
QL>    .ToDictionary(g => g.Key, strategiesSet[strategy]);
QL>


Кажется, потихоньку начинаю въезжать. В приведенном варианте.Where мне не понравилось вот что: он игнорирует элементы конфигурации типа ключ-без-начения ("... ; a ; ..."). Более логично для таких элементов бросать исключение (как я понимаю, для этого достаточно заменить ==2 на >= 1 в .Where, но это не наш путь — см. ниже). Сам я написал указанный фрагмент следующим образом:
return = arg                  .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
                              .Select(v => v.Trim())
                              .Where(v => v.Length > 0)
                              .Select(v => v.Split(new[] { '=' }, 2))
                              ...

Кстати, в продакшене исключения надо кидать свои, а не полагаться на системные. Следующая задача — прикрутить сюда проверку и выбрасывание своих исключений. На своем текущем уровне понимания вопроса я бы сделал это как-то так:

Определил бы свою функцию-проверяльщик
    public static class Utility
    {
        public static bool ValidateKeyAndValue(this string[] sa)
        {
// assert(sa.Length == 1 || sa.Length == 2)
            if (string.IsNullOrEmpty(sa[0])) throw new Exception("Не задан ключ в элементе конфигурации");
            if (string.IsNullOrEmpty(sa[1])) throw new Exception("Не задано значение для ключа " + sa[0]);
            return true;
        }
    }


И дальше добавил бы отдельный .Where:
return = arg                  .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
                              .Select(v => v.Trim())
                              .Where(v => v.Length > 0)
                              .Select(v => v.Split(new[] { '=' }, 2))
                              .Where(v => v.ValidateKeyAndValue())
                              ...

Можно ли это сделать более изящно/правильно/эффективно?
Дальше нам предстоит внести в этот код управление выкидыванием исключения в зависимости от значения флага.
Re: Распарсить строку в Dictionary<string, string>
От: alexanderfedin США http://alexander-fedin.pixels.com/
Дата: 14.05.13 23:36
Оценка:
Здравствуйте, teapot2, Вы писали:

T>Добрый день, друзья.


T>Необходимо мне в своем коде парсить строки вида "A=1; B=122; c=x=2,у=3" в словарь, в данном случае должно получиться такое:


T>A: 1

T>B: 122
T>c: x=2,y=3.

T>Захотел поначалу все это реализовать в виде функции, а теперь задумался — зачем изобретать велосипед, наверняка должны быть какие-то готовые функции в .net. Курил MSDN, нашел какие-то функции расширения класса string, — ToDictionary(...), — посмотрел я их, но там как-то все сложно и непонятно, какие-то аргументы типа IENumerable зачем-то. В общем, не понял я, можно ли их прикрутить для решения моей задачи.


T>Собственно, вопрос: есть ли какая-нибудь встроенная функция в дот-нете, которая делает subj, и которой можно управлять заданием двух массивов char: один содержит разделители элементов конфига в строке (в данном случае — единственный разделитель ';'), второй — разделители ключа и значения (если в одном элементе их несколько, то второй и последующие являются частью значения, как видно из приведенного выше примера). Ну, или, хотя бы часть этой работы. Поделитесь опытом, как бы вы реализовали такую штуку.


В связи с тем, что Ваши аппетиты растут не по дням, а по часам, я бы предложил Вам использовать какой-нибудь Coco/R.
Вполне понятная грамматика, легко поддерживать и расширять, compile-time генерация кода парсера.
Respectfully,
Alexander Fedin.
Re[2]: Распарсить строку в Dictionary<string, string>
От: Аноним  
Дата: 22.05.13 10:01
Оценка:
Здравствуйте, alexanderfedin, Вы писали:

A>В связи с тем, что Ваши аппетиты растут не по дням, а по часам, я бы предложил Вам использовать какой-нибудь Coco/R.

A>Вполне понятная грамматика, легко поддерживать и расширять, compile-time генерация кода парсера.
Это даже не "стрелять из пушки по воробьям". Это "запустить по воробъям МБР с РГЧ" (по одной "Ч" на каждого воробья ). Мой "наколеночный" (но полностью рабочий ибо оттестирован) код содержит менее сотни строк, половина из которых содержит только одну фигурную скобку. Полностью написан, отлажен и оттестирован за пару-тройку часов. Просто изначально я хотел избежать затрат этого времени. Но не вышло. Боюсь, предлагаемый Вами вариант потребует трудозатрат, больших на порядки.
Re[3]: Распарсить строку в Dictionary<string, string>
От: teapot2  
Дата: 22.05.13 10:02
Оценка:
Это был я
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.