Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Форматные строки  (Прочитано 6112 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« : 09-12-2009 17:01 » 

Может кто-нибудь знает штатное средство в .NET, умеющее печатать строчку по формату, заданному в стиле C-шного printf?

Т.е. на входе имеется строка типа "f%04ds", как с её помощью в .NET получить серию строк вида "f0000s", "f0001s" и т.д.

Как-то не радует перспектива писать свой конвертор C-style форматных строк в .NET-style форматные вида "f{0:0000}s".
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #1 : 09-12-2009 17:14 » 

Дим,

ты имеешь в виду вот это:

Код:
strStr = String.Format("int = {0:D} *** float = {1:f} *** {2:S} *** {3:0}", 5, 5.5, "lala", 0.000);

на выходе: "int = 5 *** float = 5,50 *** lala *** 0"

или я не так поняла? Тогда извини...
Записан

холоднокровней, Маня, Ви не на работе
Алексей++
кот глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #2 : 09-12-2009 17:47 » 

А вставка блока кода на с++ возможна ?
Записан

Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #3 : 09-12-2009 18:00 » 

Леш, куда ты хочешь его вставлять?  Здесь была моя ладья...
Записан

холоднокровней, Маня, Ви не на работе
Алексей++
кот глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #4 : 09-12-2009 18:07 » 

Может я что-то путаю, Ну вроде дот-нет позиционировался как кроссплатформенный и кроссязыковый ? И можно делать вставки на любом языке, так же, как в с++ можно делать вставки ассемблера
« Последнее редактирование: 10-12-2009 03:51 от Алексей1153++ » Записан

Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #5 : 09-12-2009 21:48 » 

Леш,
не знаю, ничего не могу сказать...
Записан

холоднокровней, Маня, Ви не на работе
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 10-12-2009 08:59 » 

Malaja, я имею в виду вот это как конечный результат преобразования, а на входе форматная строка с синтаксисом printf.

В общем, свой конвертор написал.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #7 : 10-12-2009 10:52 » 

понятно. тогда прости за ненужную инфу.
Записан

холоднокровней, Маня, Ви не на работе
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #8 : 10-12-2009 10:59 » 

ой, нескромно так Улыбаюсь
Код в студию Улыбаюсь
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 10-12-2009 15:25 » 

На выходных. Он у меня сейчас не универсальный, а заточенный только под целые числа и под один аргумент.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 12-12-2009 13:08 » 

Собственно, вот.
Код: (Text)
    class C
    {
        private class CFormat
        {
            [Flags]
            private enum FormatFlags
            {
                None = 0,
                LeftAlign = 1,
                PlusSign = 2,
                FirstZeros = 4,
                Blank = 8,
                LastZeros = 16
            }

            private struct NumberParameter
            {
                public bool Exist;
                public int? Value;
            }

            private delegate object NextArg(Type expectedType);

            private string result;
            private Stack args;

            public CFormat(string format, params object[] args)
            {
                this.args = new Stack();
                for (int i = args.Length - 1; i >= 0; --i)
                {
                    this.args.Push(args[i]);
                }
                Regex regex = new Regex(@"%(?<flag>[-+0 #]*)(?<width>\*|\d+)?(?<precision>\.\d*)?(?<size>[hlIw]|ll|I32|I64)?(?<type>[cCdiouxXeEfgGnpsS])");
                this.result = regex.Replace(format, new MatchEvaluator(this.replace)).Replace("%%", "%");
            }

            public string Result
            {
                get
                {
                    return this.result;
                }
            }

            private object getNextArg(Type expectedType)
            {
                if (this.args.Count == 0)
                {
                    throw new ArgumentOutOfRangeException();
                }
                object arg = this.args.Pop();
                if (arg.GetType() != expectedType)
                {
                    throw new InvalidCastException();
                }
                return arg;
            }

            private string replace(Match match)
            {
                FormatFlags flags = this.parseFlags(match.Groups["flag"].Value);
                NumberParameter width = this.parseWidth(match.Groups["width"].Value, this.getNextArg);
                NumberParameter precision = this.parsePrecision(match.Groups["precision"].Value);
                string type = match.Groups["type"].Value;
                object value = this.getNextArg(this.detectValueType(type, match.Groups["size"].Value));
                string result = string.Empty;
                switch (type)
                {
                    case "i":
                    case "d":
                        Int64 intValue = Convert.ToInt64(value);
                        result = string.Format("{0:D}", Math.Abs(intValue));
                        string sign = string.Empty;
                        if (intValue >= 0)
                        {
                            if ((flags & FormatFlags.PlusSign) != 0)
                            {
                                sign = "+";
                            }
                            else if ((flags & FormatFlags.Blank) != 0)
                            {
                                sign = " ";
                            }
                        }
                        else
                        {
                            sign = "-";
                        }
                        int fieldSize = width.Value.HasValue ? width.Value.Value - (sign + result).Length : 0;
                        StringBuilder fieldBuilder = new StringBuilder();
                        char fieldChar =
                            (flags & FormatFlags.FirstZeros) != 0 && !precision.Exist && (flags & FormatFlags.LeftAlign) == 0
                                ? '0' : ' ';
                        for(int i = 0; i < fieldSize; ++i)
                        {
                                fieldBuilder.Append(fieldChar);
                        }
                        string field = fieldBuilder.ToString();
                        if (field.Length > 0 && field[0] == '0')
                        {
                            result = sign + field + result;
                        }
                        else if ((flags & FormatFlags.LeftAlign) != 0)
                        {
                            result = sign + result + field;
                        }
                        else
                        {
                            result = field + sign + result;
                        }
                        break;
                    default:
                        throw new InvalidDataException();
                }
                return result;
            }

            private Type detectValueType(string type, string size)
            {
                switch (type)
                {
                    case "i":
                    case "d":
                        switch (size)
                        {
                            case "h":
                                return typeof(short);
                            case "l":
                                return typeof(long);
                            case "ll":
                            case "I64":
                                return typeof(Int64);
                            case "I32":
                                return typeof(Int32);
                            default:
                                return typeof(int);
                        }
                    default:
                        return null;
                }
            }

            private FormatFlags parseFlags(string flags)
            {
                FormatFlags result = FormatFlags.None;
                foreach (char c in flags)
                {
                    switch (c)
                    {
                        case '-':
                            result |= FormatFlags.LeftAlign;
                            break;
                        case '+':
                            result |= FormatFlags.PlusSign;
                            break;
                        case ' ':
                            result |= FormatFlags.Blank;
                            break;
                        case '0':
                            result |= FormatFlags.FirstZeros;
                            break;
                        case '#':
                            result |= FormatFlags.LastZeros;
                            break;
                    }
                }
                return result;
            }

            private NumberParameter parseNumberParameter(string numberParameter)
            {
                NumberParameter result = new NumberParameter();
                result.Exist = numberParameter != string.Empty;
                if(result.Exist)
                {
                    result.Value = Convert.ToInt32(numberParameter);
                }
                else
                {
                    result.Value = null;
                }
                return result;
            }

            private NumberParameter parseWidth(string width, NextArg getNextArg)
            {
                if (width == "*")
                {
                    NumberParameter result = new NumberParameter();
                    result.Exist = true;
                    result.Value = Convert.ToInt32(getNextArg(typeof(int)));
                    return result;
                }
                else
                {
                    return this.parseNumberParameter(width);
                }
            }

            private NumberParameter parsePrecision(string precision)
            {
                bool isPoint = precision.Length > 0 && precision[0] == '.';
                if (isPoint)
                {
                    NumberParameter result = this.parseNumberParameter(precision.Substring(1));
                    result.Exist = true;
                    return result;
                }
                else
                {
                    return this.parseNumberParameter(precision);
                }
            }
        }

        public static string sprintf(string format, params object[] args)
        {
            return new CFormat(format, args).Result;
        }

        public static void printf(string format, params object[] args)
        {
            Console.Write(C.sprintf(format, args));
        }

        public static void fprintf(Stream openedFile, string format, params object[] args)
        {
            new StreamWriter(openedFile).Write(C.sprintf(format, args));
        }
    }

Сделал универсальный и расширяемый анализатор, но из всех возможных значений типов терпения хватило только на целые числа. Остальное мне сейчас не надо, а нуждающиеся допишут сами.

Использование типа:
Код: (Text)
C.printf("'%-8ld', '%+08i' '%*d'\n", (long)12, -32, 8, 24);
Вывод:
Код:
'12      ', '-0000032' '      24'

Кто баги найдёт - пишите.

Ведёт оно себя строже, чем в C, в части контроля типов.

P.S. Добавил звёздочку (см. ниже).
« Последнее редактирование: 13-12-2009 08:36 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Алексей++
кот глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #11 : 12-12-2009 13:36 » 

Ещё есть звёздочка, я вот так сразу не нашёл, где обрабатывается. Например

Код:
...("% *d",7,5) -> "       5"
Записан

Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 12-12-2009 14:45 » 

Алексей1153++, да, звёздочки в этой реализации нет. Нужно расширить регулярное выражение и кое-что перекомпоновать.

Пока добавлял звёздочку, баг нашёл из-за порчи дизайна - говорили же умные люди, что нефиг в функции побочные эффекты закладывать. Хорошо бы перепроектировать.
« Последнее редактирование: 12-12-2009 15:08 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Алексей++
кот глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #13 : 13-12-2009 05:46 » 

А что за побочные эффекты ?

Кстати, "x","X" (большой X - для заглавных хексов) - тоже целые Улыбаюсь
 
« Последнее редактирование: 13-12-2009 05:48 от Алексей1153++ » Записан

Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #14 : 13-12-2009 07:40 » 

Алексей1153++, нет, u, o и x - это уже желающие пусть сами дописывают.

Побочные эффекты в том, что функция вычисления ширины теперь тоже берёт аргумент, и, в частности, поэтому получение значения должно быть обязательно после вызова функции вычисления ширины, и два раза повторяется проверка на выход за пределы массива. Некрасиво это.

Чуть переделал, вынеся логику контроля типов и выхода за пределы в отедльную функцию - генератор аргументов. Но всё равно сохраняется неочевидность внутри replace, что получение значения должно следовать обязательно после вычисления ширины. Несовершенный код.

Нужно, чтобы аргументы извлекались в одном месте, но извлечение аргумента при вычислении ширины возможно лишь в результате анализа. Размазывать логику вычисления ширины по двум функциям тоже нехорошо. Сливать всё в одну функцию replace - это получить перегруженный деталями код. У кого какие предложения будут? Решение выглядело бы изящнее, если бы вместо регулярных выражений, предоставляющих неупорядоченное множество групп, был бы написан собственный автомат разбора, с явной последовательностью состояний, но это было бы более громоздко. Так и хочется написать что-то вроде анонимного метода или блока кода, передаваемого как аргумент функции.

Добавил делегат в параметры функции вычисления ширины, чтобы синтаксически указать в replace зависимость функции вычисления ширины от стека аргументов. Всё равно не изящно. Улыбаюсь
« Последнее редактирование: 13-12-2009 08:41 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Алексей++
кот глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #15 : 13-12-2009 09:00 » 

Глянул, как делается в CString - там тоже для определения длины буфера назначения код парсинга прогоняется два раза.

А может быть, сделать мини-кеш, буквально на одну строчку - если входная строка такая же, как предыдущая, то на выход уходит сразу готовая (также предыдущая) выходная строка ?
Записан

Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #16 : 13-12-2009 09:51 » 

По-моему с длиной строк в .NET проблем нет. Единственно, что складывание строк в циклах не является оптимальными - лучше использовать StringBuilder.

По поводу кэша была идея: сформировать сначала форматную строчку в стиле .NET, а потом к ней применять аргументы. Но контроль типов идёт лесом, а, с другой стороны, такое решение лучше делать по схеме Regex (т.е. формат задать в конструкторе, а потом печатать с его помощью аргументы), нежели статическим методом - усложняется использование. Поэтому такой вариант выгоден лишь для более узкой задачи - печати разных данных по одному формату.
« Последнее редактирование: 13-12-2009 09:52 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines