【问题标题】:Creating date range string from list of 12 month booleans从 12 个月布尔值列表创建日期范围字符串
【发布时间】:2015-11-15 15:43:28
【问题描述】:

我有一个包含 12 个布尔属性的 c# 类;

可用一月, 可用二月, 可用三月, 可用Apr...等

此类的每个实例都可以将任意数量的它们设为真,并且通常它们按顺序运行。 IE。 AvailableFeb - AvailableApr 将是真的,但没有其他人。有时只有一个布尔值是真的,即仅在一个月内可用。有时它们都可以,即全年可用。

有时,这是一个棘手的问题,它们将有两个范围,即 2 月至 4 月和 8 月至 10 月可用。

我正在尝试编写一个函数来返回一个字符串来表示所做的选择。

只选择了 1 个我想返回的布尔值(例如):“仅限 2 月”。

选择的范围(例如):“Jan-Mar”。

多个范围(例如):“Jan-Mar, Aug-Nov”。

混合单曲和范围(例如):“Jan-Mar, Sep”。

全部选中:“全年”。

范围是连续月份被指定为真的地方,例如Jan、Feb、Mar 为真应导致 Jan-Mar。

我曾尝试使用带有简单条件检查的循环,但它很混乱,我无法正确处理,我很遗憾将它们存储为单独的属性而不是 int 数组,但我现在被困住了。我想知道是否有一种方法可以使用反射然后循环将布尔值存储在另一个属性中。到目前为止还没有运气。非常感谢任何帮助!

谢谢

【问题讨论】:

  • 混合选择时,你如何决定哪个是范围,哪个是单选?对我来说,这似乎是一个非常不直观的系统。
  • 如果任何 bool 为真但双方均为假,则它是单一的,否则它是范围的一部分。是的,我知道,这不是很好。
  • 但是如果例如检查以下内容:FebMaySep?
  • 这是否会在两个范围内结束?一个包含FebMay,另一个只包含Sep
  • 但是如果我想表达FebMay-Sep呢?

标签: c# system.reflection


【解决方案1】:

如果你想稍微改变一下你的班级,那就按照这种方式来吧!

您可以拥有 12 个属性并将它们映射到单个 int 数组中。所以你有长度为 12 的 int 数组。当intArray[0] = 1 表示第一个月可用时。如果intArray[0] = 0 表示第一个月不可用,依此类推。

您将此 int 数组转换为具有 base-16 表示的字符串,该字符串表示每个月的基于 1 的索引,但如果它不可用,则改为使用 0。稍后我们使用字典通过给出数字来获取月份的名称。

示例 1:

"003456000a00"

这意味着月份 3th,4th,5th,6tha = 10th 可用。然后使用正则表达式解析它并找到匹配项。正则表达式模式为[^0]+。表示它匹配除0 之外的任何字符。

所以正则表达式会给我们这个匹配项。

Match 1 : 3456
Match 2 : a

第一次匹配长度大于1 表示它的范围是几个月。所以我们取第一个字符和最后一个字符。我们加入他们-。这里将是36。所以匹配1应该变成

Mar-Jun

第二个匹配长度只有1 意味着它是一个月。所以它应该变成

Oct

因为我们有两场比赛,所以我们用, 加入这些比赛,最后输出是Mar-Jun , Oct

示例 2:

"020000000000"

匹配

Match 1 : 2

因为我们只有 1 个匹配并且匹配长度是 1,所以应该变成

Feb Only

示例 3:

"023456780000"

匹配

Match 1 : 234567

这只是一场比赛,但这场比赛的长度不止一场。所以我们只需要27 并与- 一起加入。

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
}

更新:

如果要在构造函数中分配布尔值,可以将构造函数更改为 this。

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();

【讨论】:

  • 很好的答案!感谢您抽出宝贵的时间来写这篇文章——我从没想过使用 RE。这个解决方案唯一的问题是它总是在一月份开始,但是比如说,我把 Oct、Nov、Dec、Jan、Feb 设为 true,那么它应该是 Oct-Feb。上述解决方案给出了 Jan-Feb、Oct-Dec。我会尝试找出如何改变它。
  • @Grenville 添加另一个条件来解决该问题,请参见示例 5 ;)
  • 现在可以了!我将其更新为答案,因为它允许灵活的月份名称(即,如果我们想说一月而不是一月)。我没有过多地更改我的类,而是将其创建为一个单独的类,然后按原样调用它,返回 new AvailableYear(Convert.ToInt32(AvailableJan), Convert.ToInt32(AvailableFeb), Convert.ToInt32(AvailableMar).... , Convert.ToInt32(AvailableDec)).ToString();
  • @Grenville 查看更新。您还可以将构造函数更改为接受bool[] 而不是int[],然后您可以简化您编写的内容。
【解决方案2】:

你可以像这样不经思考地做到这一点:

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();
}

【讨论】:

  • 谢谢,这是一个很好的答案,我修复了一些代码并运行了,但即使 Jan 不包括在内,它仍然从 Jan 开始。
  • @Grenville 哎呀,小错误,这就是我不编译它的结果。我修复了这个错误,将“rangeStart = 0”更改为“rangeStart = i”。
  • 干杯布鲁斯!这已经解决了这个问题:) 现在如果我选择 Dec、Jan、Feb、Mar 为 true。该字符串是 Dec-Jan-Mar 而不是 Dec-Mar。我现在想弄清楚为什么。
  • @Grenville 这是因为在环绕代码中我放了 sb[4] == '-' 而不是 sb[3] == '-'。现在应该可以工作了。
  • 现在完美运行!非常感谢
【解决方案3】:

因为您不能像这样简单地迭代成员,您可以创建一些属性来通过在属性中使用订单号来检测那些布尔型成员。例如

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

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

您可以通过列表中的反射和搜索序列来获取它们。看一看:Reflection - get attribute name and value on property

但更好的解决方案是在没有反射和ranges 的情况下工作:

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

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

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

但两种解决方案都应该有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多