# 使用LINQ挑战管理范围[英] Managing ranges with LINQ challenge

### 问题描述

• 1,2,3,5,6,7 -> 1-3,5-7
• 1,3,5,7 -> 1,3,5,7
• 1,2,5,6 -> 1,2,5,6
• 1,2,3,6,7 -> 1-3,6,7

```[Flags]
public enum DaysOfWeek
{
Sunday = 0x1,
Monday = 0x2,
Tuesday = 0x4,
Wednesday = 0x8,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}
```

```var days = Enum.GetValues(typeof(DaysOfWeek))
.Cast<DaysOfWeek>()
.Where(dow => Model.DaysOfWeek.HasFlag(dow));
```

## 推荐答案

```class NumberRange
{
public int Start { get; set;}
public int End { get; set;}
public override string ToString()
{
return Start == End ? Start.ToString() : String.Format("{0}-{1}",Start,End);
}
}
```

```public static IEnumerable<NumberRange> ToRanges(this IEnumerable<int> numbers)
{
NumberRange currentRange = null;
foreach(var number in numbers)
{
if (currentRange == null)
currentRange = new NumberRange() { Start = number, End = number };
else if (number == currentRange.End + 1)
currentRange.End = number;
else
{
yield return currentRange;
currentRange = new NumberRange { Start = number, End = number };
}
}
if (currentRange != null)
{
yield return currentRange;
}
}
```

```String.Join(",",
new int[] { 1,2,3,5,7,8,9,11 }
.ToRanges()
.Select(r => r.ToString()))
```

## 其他推荐答案

```void Main()
{
Console.WriteLine(AggregateString(new int[]{1,2,3,5,6,7}));   //1-3,5-7
Console.WriteLine(AggregateString(new int[]{1,3,5,7}));       //1,3,5,7
Console.WriteLine(AggregateString(new int[]{1,2,5,6}));       //1,2,5,6
Console.WriteLine(AggregateString(new int[]{1,2,3,6,7 }));    //1-3,6,7
}

string AggregateString(int[] ary)
{
List<List<int>> result=new List<List<int>>();
ary.Aggregate((m,n)=>
{
if(m == n-1)
{
if(result.LastOrDefault()!=null && result.LastOrDefault().Last() ==m)
else
}
else
{
if(result.LastOrDefault()==null)
}
return n;
});
return string.Join(",", result.Select(s=>s.Count()>2?
string.Join("-",new string[]{s.First().ToString(),s.Last().ToString()}) :
string.Join(",",s.Select(x=>x.ToString()).ToArray())).ToArray());
}
```

## 其他推荐答案

```static IEnumerable<string> GetRange(IEnumerable<int> range)
{
using(IEnumerator<int> iter = range.GetEnumerator())
if(iter.MoveNext())
{
int last = iter.Current;
int start = iter.Current;
while(iter.MoveNext())
{
int curr = iter.Current;
if (curr == last+1)
{
last = curr;
continue;
}
// found gap
if (start == last)   // one isolated value
{
yield return start.ToString();
}
else if (last - start == 1) // two in a row.
{
yield return start.ToString();
yield return last.ToString();
}
else
{
yield return string.Format("{0}-{1}", start,last);
}
start = curr;
last = curr;
}

if (start == last)   // one isolated value
{
yield return start.ToString();
}
else if (last - start == 1) // two in a row.
{
yield return start.ToString();
yield return last.ToString();
}
else
{
yield return string.Format("{0}-{1}", start,last);
}
}
}
```

### 问题描述

Given the following numbers (representing days of week): 1,2,3,4,5,6,7.

Here are some combination examples and their desired output:

• 1,2,3,5,6,7 -> 1-3,5-7
• 1,3,5,7 -> 1,3,5,7
• 1,2,5,6 -> 1,2,5,6
• 1,2,3,6,7 -> 1-3,6,7

The idea is that 3 or more consecutive days become a range while single or non-following days are rendered separately (or is it nicer to make range starting from 2).

I don't know where to start should I write a complicated ifed function or this can be done with one of the LINQ functions?
Any juicy suggestions?

I used numbers to simplify the idea of ranges, but in my code I have an flagged enum declared as follows:

```[Flags]
public enum DaysOfWeek
{
Sunday = 0x1,
Monday = 0x2,
Tuesday = 0x4,
Wednesday = 0x8,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}
```

I have an entity OpeningTimes with a field DaysOfWeek, that tells what days in week the hour-ranges (defined in another property) of this entity apply to.

So the get the above I use (to really get numbers I'd add Select using index + 1):

```var days = Enum.GetValues(typeof(DaysOfWeek))
.Cast<DaysOfWeek>()
.Where(dow => Model.DaysOfWeek.HasFlag(dow));
```

I think the idea is to first remove the numbers within a range.

I believe I'm looking for an aggregation function that receives the previous value as well, and can return another value-type, so I can make a function that if current value -1 equals prev. value, I wait for the next value, until range is not consecutive (or if element stands for itself) which is when I yield return the last bulk as an anonymous object and start working on the new one.

Then I'll make a formatting function that says if (item.First != item.Last) string.Join("-", item.First, Item.Last);

## 推荐答案

Interesting problem. I decided for readability to have a class representing a range:

```class NumberRange
{
public int Start { get; set;}
public int End { get; set;}
public override string ToString()
{
return Start == End ? Start.ToString() : String.Format("{0}-{1}",Start,End);
}
}
```

and an extension method to turn an IEnumerable of ordered integers into an IEnumerable of ranges:

```public static IEnumerable<NumberRange> ToRanges(this IEnumerable<int> numbers)
{
NumberRange currentRange = null;
foreach(var number in numbers)
{
if (currentRange == null)
currentRange = new NumberRange() { Start = number, End = number };
else if (number == currentRange.End + 1)
currentRange.End = number;
else
{
yield return currentRange;
currentRange = new NumberRange { Start = number, End = number };
}
}
if (currentRange != null)
{
yield return currentRange;
}
}
```

And with that in place you can get the ranges and format them however you want:

```String.Join(",",
new int[] { 1,2,3,5,7,8,9,11 }
.ToRanges()
.Select(r => r.ToString()))
```

## 其他推荐答案

Here is what I thought:

```void Main()
{
Console.WriteLine(AggregateString(new int[]{1,2,3,5,6,7}));   //1-3,5-7
Console.WriteLine(AggregateString(new int[]{1,3,5,7}));       //1,3,5,7
Console.WriteLine(AggregateString(new int[]{1,2,5,6}));       //1,2,5,6
Console.WriteLine(AggregateString(new int[]{1,2,3,6,7 }));    //1-3,6,7
}

string AggregateString(int[] ary)
{
List<List<int>> result=new List<List<int>>();
ary.Aggregate((m,n)=>
{
if(m == n-1)
{
if(result.LastOrDefault()!=null && result.LastOrDefault().Last() ==m)
else
}
else
{
if(result.LastOrDefault()==null)
}
return n;
});
return string.Join(",", result.Select(s=>s.Count()>2?
string.Join("-",new string[]{s.First().ToString(),s.Last().ToString()}) :
string.Join(",",s.Select(x=>x.ToString()).ToArray())).ToArray());
}
```

## 其他推荐答案

Here's my take on it. (Unfortunately, I couldn't prevent duplicating one section:

```static IEnumerable<string> GetRange(IEnumerable<int> range)
{
using(IEnumerator<int> iter = range.GetEnumerator())
if(iter.MoveNext())
{
int last = iter.Current;
int start = iter.Current;
while(iter.MoveNext())
{
int curr = iter.Current;
if (curr == last+1)
{
last = curr;
continue;
}
// found gap
if (start == last)   // one isolated value
{
yield return start.ToString();
}
else if (last - start == 1) // two in a row.
{
yield return start.ToString();
yield return last.ToString();
}
else
{
yield return string.Format("{0}-{1}", start,last);
}
start = curr;
last = curr;
}

if (start == last)   // one isolated value
{
yield return start.ToString();
}
else if (last - start == 1) // two in a row.
{
yield return start.ToString();
yield return last.ToString();
}
else
{
yield return string.Format("{0}-{1}", start,last);
}
}
}
```