Создание строки диапазона дат из списка логических значений за 12 месяцев

У меня есть класс С#, который содержит 12 логических свойств;

AvaialableJan, AvailableFeb, AvailableMar, AvailableApr... и т.д.

Каждый экземпляр этого класса может иметь любое их количество как истинное, и обычно они выполняются последовательно. т.е. AvailableFeb - AvailableApr будет истинным, но не другим. Иногда будет только одно логическое значение true, т. е. доступное только в течение одного месяца. Иногда они все будут, т.е. доступны круглый год.

Иногда, и это сложный момент, у них будет два диапазона, то есть доступные с февраля по апрель и с августа по октябрь.

Я пытаюсь написать функцию, возвращающую строку для представления сделанного выбора.

Выбрано только 1 логическое значение, которое я хотел бы вернуть (например): «Только февраль».

Выбранный диапазон (например): "Январь-Март".

Несколько диапазонов (например): "январь-март, август-ноябрь".

Смешанный сингл и диапазон (например): «Январь-Мар, Сентябрь».

Все выбрано: "Круглый год".

Диапазон — это диапазон, в котором последовательные месяцы были назначены истинными, например Ян, февраль, март, если истинны, должны привести к январю-марту.

Я пытался использовать цикл с простыми проверками условий, но это просто беспорядок, и я не могу понять это правильно, я сожалею о том, что храню их как отдельные свойства, а не массив int, но сейчас я застрял с этим. Интересно, есть ли способ сохранить логические значения внутри другого свойства, используя отражение, а затем цикл. Пока не везло. Любая помощь высоко ценится!

Спасибо


person Grenville    schedule 15.11.2015    source источник
comment
При смешанных выборках, как вы решаете, какой из них является диапазоном, а какой — одиночным? Мне кажется, что это очень неинтуитивная система.   -  person poke    schedule 15.11.2015
comment
Если какое-либо логическое значение истинно, но обе стороны ложны, то оно одиночное, в противном случае оно является частью диапазона. Да, я знаю, это не здорово.   -  person Grenville    schedule 15.11.2015
comment
Но что, если, например. проверяются: Feb, May, Sep?   -  person poke    schedule 15.11.2015
comment
Это закончится двумя диапазонами или нет? Один содержит Feb, May другой только Sep?   -  person BendEg    schedule 15.11.2015
comment
Но что, если я хочу вместо этого выразить Feb и May-Sep?   -  person poke    schedule 15.11.2015
comment
@poke - Тогда февраль будет правдой, а май, июнь, июль, август, сентябрь - правдой.   -  person Grenville    schedule 15.11.2015
comment
Итак, для диапазона вы фактически выбираете все месяцы в этом диапазоне? Из вашего вопроса вообще ничего не понятно.   -  person poke    schedule 15.11.2015
comment
Диапазон только там, где есть последовательные истинные значения.   -  person Grenville    schedule 15.11.2015
comment
Спасибо, буду обновлять.   -  person Grenville    schedule 15.11.2015


Ответы (3)


Если вы хотите немного изменить свой класс, следуйте этому пути!.

Вы можете иметь 12 свойств и отображать их в один массив целых чисел. Итак, у вас есть массив целых чисел длиной 12. когда intArray[0] = 1 означает, что доступен первый месяц. если intArray[0] = 0 означает, что первый месяц недоступен и так далее.

Вы конвертируете этот массив int в строку с представлением base-16 индекса каждого месяца, основанного на 1, но если он недоступен, вместо этого поставьте 0. Позже мы воспользуемся словарем, чтобы получить название месяца, указав вместо него его номер.

Пример 1:

"003456000a00"

Это означает, что доступны месяцы 3th,4th,5th,6th и a = 10th. затем используйте регулярное выражение, чтобы проанализировать это и найти совпадения. Шаблон регулярного выражения будет [^0]+. означает, что он соответствует любому символу, кроме 0.

Таким образом, регулярное выражение даст нам эти совпадения.

Match 1 : 3456
Match 2 : a

Длина первого совпадения больше 1 означает диапазон месяцев. поэтому мы берем первый символ и последний. и мы присоединяемся к ним с -. Здесь это будут 3 и 6. Итак, совпадение 1 должно стать

Mar-Jun

Длина второго совпадения всего 1 означает, что это один месяц. Так должно стать

Oct

Поскольку у нас есть два совпадения, мы соединяем их с помощью ,, и, наконец, на выходе получается Mar-Jun , Oct.

Пример 2:

"020000000000"

Спички

Match 1 : 2

Поскольку у нас есть только 1 совпадение, а длина совпадения равна 1, это должно стать

Feb Only

Пример 3:

"023456780000"

Спички

Match 1 : 234567

Это только одно совпадение, но длина этого совпадения больше одного. поэтому мы просто берем 2 и 7 и присоединяем к ним -.

Feb-Jul

Пример 4:

"123456789abc"

Спички

Match 1 : 123456789abc

Как видите, у нас есть все месяцы. длина этого совпадения 12, так что должно быть

Year round

Пример 5:

"123456000abc"

Спички

Match 1 : 123456
Match 2 : abc

Здесь у нас есть два матча. это может быть Jan-Jun , Oct-Dec, но лучшее представление (как вы упомянули в комментарии) - Oct-Jun. Это должно быть abc123456. Итак, мы проверяем, заканчивается ли последнее совпадение на c, а первое совпадение начинается на 1, затем мы соединяем последнее совпадение с первым совпадением.

Oct-Jun

Код:

Это станет просто, как вы можете видеть.

internal class AvailableYear
{
    private readonly int[] _available;
    private static readonly Regex MatchTrue = new Regex("[^0]+");
    private static readonly Dictionary<string, string> GetName = new Dictionary<string, string>
    {
        {"1","Jan" },
        {"2","Feb" },
        {"3","Mar" },
        {"4","Apr" },
        {"5","May" },
        {"6","Jun" },
        {"7","Jul" },
        {"8","Aug" },
        {"9","Sep" },
        {"a","Oct" },
        {"b","Nov" },
        {"c","Dec" },
    };

    public AvailableYear(params int[] available)
    {
        if (available.Length > 12) throw new IndexOutOfRangeException("given parameters should not exceed 12 months.");

        _available = available;
    }

    public int AvaialableJan
    {
        get { return _available[0]; }
        set { _available[0] = value; }
    }

    public int AvailableFeb
    {
        get { return _available[1]; }
        set { _available[1] = value; }
    }
    public int AvailableMar
    {
        get { return _available[2]; }
        set { _available[2] = value; }
    }
    public int AvailableApr
    {
        get { return _available[3]; }
        set { _available[3] = value; }
    }
    public int AvaialableMay
    {
        get { return _available[4]; }
        set { _available[4] = value; }
    }
    public int AvaialableJun
    {
        get { return _available[5]; }
        set { _available[5] = value; }
    }
    public int AvaialableJul
    {
        get { return _available[6]; }
        set { _available[6] = value; }
    }
    public int AvaialableAug
    {
        get { return _available[7]; }
        set { _available[7] = value; }
    }
    public int AvaialableSep
    {
        get { return _available[8]; }
        set { _available[8] = value; }
    }
    public int AvaialableOct
    {
        get { return _available[9]; }
        set { _available[9] = value; }
    }
    public int AvaialableNov
    {
        get { return _available[10]; }
        set { _available[10] = value; }
    }
    public int AvaialableDec
    {
        get { return _available[11]; }
        set { _available[11] = value; }
    }

    public override string ToString()
    {
        string values = string.Join("", _available.Select((x, i) => x == 0 ? "0" : Convert.ToString(i + 1, 16)));
        var matches = MatchTrue.Matches(values).Cast<Match>().Select(x => x.Value).ToList();

        if (matches.Count == 0)
        {
            return "None";
        }
        if (matches[0].Length == 12)
        {
            return "Year round";
        }
        if (matches.Count == 1 && matches[0].Length == 1)
        {
            return GetName[matches[0]] + " Only";
        }
        else
        {
            if (matches.First().StartsWith("1") && matches.Last().EndsWith("c"))
            {
                matches[0] = matches.Last() + matches.First();
                matches.RemoveAt(matches.Count - 1);
            }

            List<string> output = new List<string>();

            foreach (var match in matches)
            {
                if (match.Length == 1)
                {
                    output.Add(GetName[match]);
                }
                else
                {
                    output.Add(GetName[match.First().ToString()] + "-" +
                               GetName[match.Last().ToString()]);
                }
            }

            return string.Join(", ", output);
        }
    }
}

Вот тест.

static void Main()
{
    AvailableYear ay = new AvailableYear(1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0);
    Console.WriteLine(ay.ToString());

    // Output : Jan , Apr-Jul , Nov
}

Обновление:

Если вы хотите присвоить логические значения в конструкторе, вы можете изменить конструктор на это.

public AvailableYear(params bool[] available)
{
    if (available.Length > 12) throw new IndexOutOfRangeException("given parameters should not exceed 12 months.");

    _available = available.Select(Convert.ToInt32).ToArray();
}

И создайте такой экземпляр. без написания Convert.ToInt32 каждый раз.

return new AvailableYear(AvailableJan, AvailableFeb, AvailableMar...., AvailableDec).ToString();
person M.kazem Akhgary    schedule 15.11.2015
comment
Отличный ответ! Спасибо, что нашли время написать это - я никогда не думал об использовании RE. Единственная проблема с этим решением заключается в том, что оно всегда начинается в январе, но, например, у меня были октябрь, ноябрь, декабрь, январь, февраль как истина, тогда это должен быть октябрь-февраль. Приведенное выше решение дает январь-февраль, октябрь-декабрь. Я постараюсь понять, как это изменить. - person Grenville; 15.11.2015
comment
@Grenville добавьте еще одно условие, чтобы решить эту проблему, см. пример 5;) - person M.kazem Akhgary; 15.11.2015
comment
Теперь это работает! Я обновил это, чтобы он стал ответом, поскольку он позволяет использовать гибкие названия месяцев (т. Е. Если мы хотим сказать «январь», а не просто «январь»). Вместо того, чтобы так сильно менять свой класс, я просто создал его как отдельный класс, а затем назвал его так: верните новый AvailableYear(Convert.ToInt32(AvailableJan), Convert.ToInt32(AvailableFeb), Convert.ToInt32(AvailableMar).... , Convert.ToInt32(AvailableDec)).ToString(); - person Grenville; 16.11.2015
comment
@Grenville смотрите обновление. вы также можете изменить конструктор, чтобы он принимал bool[] вместо int[], и тогда вы можете упростить то, что вы написали. - person M.kazem Akhgary; 16.11.2015

Вы можете сделать это без отражения следующим образом:

string FormatMonths(MyClass myObject)
{
    return FormatMonths(
        myObject.AvailableJan,
        myObject.AvailableFeb,
        myObject.AvailableMar,
        myObject.AvailableApr,
        myObject.AvailableMay,
        myObject.AvailableJun,
        myObject.AvailableJul,
        myObject.AvailableAug,
        myObject.AvailableSep,
        myObject.AvailableOct,
        myObject.AvailableNov,
        myObject.AvailableDec);
}


private static string[] months = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

string FormatMonthRange(int startMonth, int endMonth, StringBuilder sb)
{
    if (startMonth == 0 && endMonth == 11)
        sb.Append("Year round");
    else if (endMonth == 11 && sb[0] == 'J' && sb[1] == 'a')
    {
        // this deals with wrap around from December to January:
        if (sb.Length > 3 && sb[3] == '-')
            sb.Remove(0, 4);
        sb.Insert(0, months[startMonth] + "-");
    }
    else
    {
        if (sb.Length > 0)
            sb.Append(", ");
        sb.Append(months[startMonth]);
        if (startMonth != endMonth)
            sb.Append("-").Append(months[endMonth]);
    }
}

string FormatMonths(params bool[] monthBools)
{
    var sb = new StringBuilder();
    int rangeStart = -1;
    for (int i = 0; i < monthBools.Length; i++)
    {
        if (monthBools[i])
        {
            if (rangeStart < 0)
                rangeStart = i;
        }
        else
        {
            if (rangeStart >= 0)
            {
                FormatMonthRange(rangeStart, i - 1, sb);
            }
            rangeStart = -1;
        }
    }
    if (rangeStart >= 0)
        FormatMonthRange(rangeStart, monthBools.Length - 1, sb);
    if (sb.Length == 3)
        sb.Append(" only");
    return sb.ToString();
}
person Bryce Wagner    schedule 15.11.2015
comment
Спасибо, чувак, это хороший ответ, я исправил несколько фрагментов кода и запустил, но он продолжал начинаться в январе, даже если Ян не был включен. - person Grenville; 15.11.2015
comment
@Grenville Ой, крошечная ошибка, это то, что я получаю за то, что не скомпилировал ее. Я исправил ошибку, изменив rangeStart = 0 на rangeStart = i. - person Bryce Wagner; 15.11.2015
comment
Здоровья Брюс! Это решило эту проблему :) теперь, если я выберу Dec, Jan, Feb, Mar как true. Строка декабрь-январь-март, а не декабрь-март. Я сейчас пытаюсь понять, почему. - person Grenville; 15.11.2015
comment
@Grenville Это произошло потому, что в циклическом коде я поставил sb[4] == '-' вместо sb[3] == '-'. Должно работать сейчас. - person Bryce Wagner; 15.11.2015
comment
Теперь работает отлично! Огромное спасибо - person Grenville; 15.11.2015

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

class Year
{
    [Month(0)]
    public bool AvaialableJan { // ... }

    [Month(1)]
    public bool AvaialableFeb { // ... }
}

Чем вы могли бы получить их через отражение в списке и поиск последовательностей. Взгляните на: Reflection - получить имя и значение атрибута для свойства

Но лучшим решением было бы работать без отражений и некоего ranges:

public class Month
{
    public string Name { //... }
}

public class Range
{
    public List<Month> Months //...
}

public class Year
{
    public List<Range> Ranges //...
}

Но оба решения должны работать.

person BendEg    schedule 15.11.2015