C#에는 페이지 번호 문자열 구문 분석 기능이 기본적으로 지원됩니까?
문제
C#에는 페이지 번호 문자열 구문 분석을 기본적으로 지원합니까?페이지 번호는 쉼표와 대시로 구분된 혼합 형식으로 인쇄 대화 상자에 입력할 수 있는 형식을 의미합니다.
이 같은:
1,3,5-10,12
정말 좋은 점은 문자열로 표시되는 모든 페이지 번호의 일종의 목록을 나에게 돌려주는 솔루션입니다.위의 예에서는 다음과 같은 목록을 다시 얻는 것이 좋습니다.
1,3,5,6,7,8,9,10,12
나는 쉬운 방법이 있다면 내 자신의 롤링을 피하고 싶습니다.
해결책
간단해야 합니다.
foreach( string s in "1,3,5-10,12".Split(',') )
{
// try and get the number
int num;
if( int.TryParse( s, out num ) )
{
yield return num;
continue; // skip the rest
}
// otherwise we might have a range
// split on the range delimiter
string[] subs = s.Split('-');
int start, end;
// now see if we can parse a start and end
if( subs.Length > 1 &&
int.TryParse(subs[0], out start) &&
int.TryParse(subs[1], out end) &&
end >= start )
{
// create a range between the two values
int rangeLength = end - start + 1;
foreach(int i in Enumerable.Range(start, rangeLength))
{
yield return i;
}
}
}
편집하다: 수정해 주셔서 감사합니다 ;-)
다른 팁
이를 수행하는 기본 제공 방법은 없지만 String.Split을 사용하면 간단합니다.
간단히 ','로 분할하면 페이지 번호나 범위를 나타내는 일련의 문자열이 생깁니다.해당 시리즈를 반복하고 '-'의 String.Split을 수행합니다.결과가 없으면 일반 페이지 번호이므로 페이지 목록에 붙여 넣으세요.결과가 있으면 '-'의 왼쪽과 오른쪽을 경계로 사용하고 간단한 for 루프를 사용하여 해당 범위의 최종 목록에 각 페이지 번호를 추가합니다.
5분만 투자하면 됩니다. 그런 다음 사용자가 잘못된 데이터(예: "1-2-3" 등)를 입력하려고 할 때 오류가 발생하도록 온전한 검사를 추가하는 데 10분 정도 더 걸릴 수 있습니다.
Keith의 접근 방식은 좋은 것 같습니다.나는 목록을 사용하여 좀 더 순진한 접근 방식을 취했습니다.여기에는 오류 검사가 있으므로 대부분의 문제를 해결해야 합니다.
public List<int> parsePageNumbers(string input) {
if (string.IsNullOrEmpty(input))
throw new InvalidOperationException("Input string is empty.");
var pageNos = input.Split(',');
var ret = new List<int>();
foreach(string pageString in pageNos) {
if (pageString.Contains("-")) {
parsePageRange(ret, pageString);
} else {
ret.Add(parsePageNumber(pageString));
}
}
ret.Sort();
return ret.Distinct().ToList();
}
private int parsePageNumber(string pageString) {
int ret;
if (!int.TryParse(pageString, out ret)) {
throw new InvalidOperationException(
string.Format("Page number '{0}' is not valid.", pageString));
}
return ret;
}
private void parsePageRange(List<int> pageNumbers, string pageNo) {
var pageRange = pageNo.Split('-');
if (pageRange.Length != 2)
throw new InvalidOperationException(
string.Format("Page range '{0}' is not valid.", pageNo));
int startPage = parsePageNumber(pageRange[0]),
endPage = parsePageNumber(pageRange[1]);
if (startPage > endPage) {
throw new InvalidOperationException(
string.Format("Page number {0} is greater than page number {1}" +
" in page range '{2}'", startPage, endPage, pageNo));
}
pageNumbers.AddRange(Enumerable.Range(startPage, endPage - startPage + 1));
}
아래는 이 작업을 수행하기 위해 방금 작성한 코드입니다.와 같은 형식으로 입력할 수 있습니다.1-2,5abcd,6,7,20-15,,,,,,
다른 형식에 쉽게 추가 가능
private int[] ParseRange(string ranges)
{
string[] groups = ranges.Split(',');
return groups.SelectMany(t => GetRangeNumbers(t)).ToArray();
}
private int[] GetRangeNumbers(string range)
{
//string justNumbers = new String(text.Where(Char.IsDigit).ToArray());
int[] RangeNums = range
.Split('-')
.Select(t => new String(t.Where(Char.IsDigit).ToArray())) // Digits Only
.Where(t => !string.IsNullOrWhiteSpace(t)) // Only if has a value
.Select(t => int.Parse(t)) // digit to int
.ToArray();
return RangeNums.Length.Equals(2) ? Enumerable.Range(RangeNums.Min(), (RangeNums.Max() + 1) - RangeNums.Min()).ToArray() : RangeNums;
}
비슷한 것을 위해 내가 요리한 것이 있습니다.
다음 유형의 범위를 처리합니다.
1 single number
1-5 range
-5 range from (firstpage) up to 5
5- range from 5 up to (lastpage)
.. can use .. instead of -
;, can use both semicolon, comma, and space, as separators
중복된 값을 확인하지 않으므로 세트는 1,5,-10 시퀀스를 생성합니다 1, 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10.
public class RangeParser
{
public static IEnumerable<Int32> Parse(String s, Int32 firstPage, Int32 lastPage)
{
String[] parts = s.Split(' ', ';', ',');
Regex reRange = new Regex(@"^\s*((?<from>\d+)|(?<from>\d+)(?<sep>(-|\.\.))(?<to>\d+)|(?<sep>(-|\.\.))(?<to>\d+)|(?<from>\d+)(?<sep>(-|\.\.)))\s*$");
foreach (String part in parts)
{
Match maRange = reRange.Match(part);
if (maRange.Success)
{
Group gFrom = maRange.Groups["from"];
Group gTo = maRange.Groups["to"];
Group gSep = maRange.Groups["sep"];
if (gSep.Success)
{
Int32 from = firstPage;
Int32 to = lastPage;
if (gFrom.Success)
from = Int32.Parse(gFrom.Value);
if (gTo.Success)
to = Int32.Parse(gTo.Value);
for (Int32 page = from; page <= to; page++)
yield return page;
}
else
yield return Int32.Parse(gFrom.Value);
}
}
}
}
테스트 케이스가 있기 전까지는 확신할 수 없습니다.내 경우에는 쉼표로 구분되는 대신 공백으로 구분되는 것을 선호합니다.구문 분석이 좀 더 복잡해집니다.
[Fact]
public void ShouldBeAbleToParseRanges()
{
RangeParser.Parse( "1" ).Should().BeEquivalentTo( 1 );
RangeParser.Parse( "-1..2" ).Should().BeEquivalentTo( -1,0,1,2 );
RangeParser.Parse( "-1..2 " ).Should().BeEquivalentTo( -1,0,1,2 );
RangeParser.Parse( "-1..2 5" ).Should().BeEquivalentTo( -1,0,1,2,5 );
RangeParser.Parse( " -1 .. 2 5" ).Should().BeEquivalentTo( -1,0,1,2,5 );
}
Keith의 답변(또는 작은 변형)은 범위 토큰 사이에 공백이 있는 마지막 테스트에서 실패합니다.이를 위해서는 토크나이저와 예측 기능이 있는 적절한 파서가 필요합니다.
namespace Utils
{
public class RangeParser
{
public class RangeToken
{
public string Name;
public string Value;
}
public static IEnumerable<RangeToken> Tokenize(string v)
{
var pattern =
@"(?<number>-?[1-9]+[0-9]*)|" +
@"(?<range>\.\.)";
var regex = new Regex( pattern );
var matches = regex.Matches( v );
foreach (Match match in matches)
{
var numberGroup = match.Groups["number"];
if (numberGroup.Success)
{
yield return new RangeToken {Name = "number", Value = numberGroup.Value};
continue;
}
var rangeGroup = match.Groups["range"];
if (rangeGroup.Success)
{
yield return new RangeToken {Name = "range", Value = rangeGroup.Value};
}
}
}
public enum State { Start, Unknown, InRange}
public static IEnumerable<int> Parse(string v)
{
var tokens = Tokenize( v );
var state = State.Start;
var number = 0;
foreach (var token in tokens)
{
switch (token.Name)
{
case "number":
var nextNumber = int.Parse( token.Value );
switch (state)
{
case State.Start:
number = nextNumber;
state = State.Unknown;
break;
case State.Unknown:
yield return number;
number = nextNumber;
break;
case State.InRange:
int rangeLength = nextNumber - number+ 1;
foreach (int i in Enumerable.Range( number, rangeLength ))
{
yield return i;
}
state = State.Start;
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
case "range":
switch (state)
{
case State.Start:
throw new ArgumentOutOfRangeException();
break;
case State.Unknown:
state = State.InRange;
break;
case State.InRange:
throw new ArgumentOutOfRangeException();
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
default:
throw new ArgumentOutOfRangeException( nameof( token ) );
}
}
switch (state)
{
case State.Start:
break;
case State.Unknown:
yield return number;
break;
case State.InRange:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
한 줄 접근 방식 Split
그리고 Linq
string input = "1,3,5-10,12";
IEnumerable<int> result = input.Split(',').SelectMany(x => x.Contains('-') ? Enumerable.Range(int.Parse(x.Split('-')[0]), int.Parse(x.Split('-')[1]) - int.Parse(x.Split('-')[0]) + 1) : new int[] { int.Parse(x) });
다음은 Regex 일치 내에서 string.Split 작업을 처리하는 lassevk 코드의 약간 수정된 버전입니다.이는 확장 메서드로 작성되었으며 LINQ의 Disinct() 확장을 사용하여 중복 문제를 쉽게 처리할 수 있습니다.
/// <summary>
/// Parses a string representing a range of values into a sequence of integers.
/// </summary>
/// <param name="s">String to parse</param>
/// <param name="minValue">Minimum value for open range specifier</param>
/// <param name="maxValue">Maximum value for open range specifier</param>
/// <returns>An enumerable sequence of integers</returns>
/// <remarks>
/// The range is specified as a string in the following forms or combination thereof:
/// 5 single value
/// 1,2,3,4,5 sequence of values
/// 1-5 closed range
/// -5 open range (converted to a sequence from minValue to 5)
/// 1- open range (converted to a sequence from 1 to maxValue)
///
/// The value delimiter can be either ',' or ';' and the range separator can be
/// either '-' or ':'. Whitespace is permitted at any point in the input.
///
/// Any elements of the sequence that contain non-digit, non-whitespace, or non-separator
/// characters or that are empty are ignored and not returned in the output sequence.
/// </remarks>
public static IEnumerable<int> ParseRange2(this string s, int minValue, int maxValue) {
const string pattern = @"(?:^|(?<=[,;])) # match must begin with start of string or delim, where delim is , or ;
\s*( # leading whitespace
(?<from>\d*)\s*(?:-|:)\s*(?<to>\d+) # capture 'from <sep> to' or '<sep> to', where <sep> is - or :
| # or
(?<from>\d+)\s*(?:-|:)\s*(?<to>\d*) # capture 'from <sep> to' or 'from <sep>', where <sep> is - or :
| # or
(?<num>\d+) # capture lone number
)\s* # trailing whitespace
(?:(?=[,;\b])|$) # match must end with end of string or delim, where delim is , or ;";
Regex regx = new Regex(pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
foreach (Match m in regx.Matches(s)) {
Group gpNum = m.Groups["num"];
if (gpNum.Success) {
yield return int.Parse(gpNum.Value);
} else {
Group gpFrom = m.Groups["from"];
Group gpTo = m.Groups["to"];
if (gpFrom.Success || gpTo.Success) {
int from = (gpFrom.Success && gpFrom.Value.Length > 0 ? int.Parse(gpFrom.Value) : minValue);
int to = (gpTo.Success && gpTo.Value.Length > 0 ? int.Parse(gpTo.Value) : maxValue);
for (int i = from; i <= to; i++) {
yield return i;
}
}
}
}
}
내가 생각해낸 대답은 다음과 같다.
static IEnumerable<string> ParseRange(string str)
{
var numbers = str.Split(',');
foreach (var n in numbers)
{
if (!n.Contains("-"))
yield return n;
else
{
string startStr = String.Join("", n.TakeWhile(c => c != '-'));
int startInt = Int32.Parse(startStr);
string endStr = String.Join("", n.Reverse().TakeWhile(c => c != '-').Reverse());
int endInt = Int32.Parse(endStr);
var range = Enumerable.Range(startInt, endInt - startInt + 1)
.Select(num => num.ToString());
foreach (var s in range)
yield return s;
}
}
}
Regex는 다음 코드처럼 효율적이지 않습니다.문자열 메서드는 Regex보다 효율적이므로 가능하면 사용해야 합니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] inputs = {
"001-005/015",
"009/015"
};
foreach (string input in inputs)
{
List<int> numbers = new List<int>();
string[] strNums = input.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string strNum in strNums)
{
if (strNum.Contains("-"))
{
int startNum = int.Parse(strNum.Substring(0, strNum.IndexOf("-")));
int endNum = int.Parse(strNum.Substring(strNum.IndexOf("-") + 1));
for (int i = startNum; i <= endNum; i++)
{
numbers.Add(i);
}
}
else
numbers.Add(int.Parse(strNum));
}
Console.WriteLine(string.Join(",", numbers.Select(x => x.ToString())));
}
Console.ReadLine();
}
}
}
내 솔루션:
- 정수 목록 반환
- 역순/오타/중복 가능:1,-3,5-,7-10,12-9 => 1,3,5,7,8,9,10,12,11,10,9 (추출, 반복 페이지를 원할 때 사용)
- 총 페이지 수를 설정하는 옵션:1,-3,5-,7-10,12-9 (N최대=9) => 1,3,5,7,8,9,9
자동완성:1,-3,5-,8 (N최대=9) => 1,3,5,6,7,8,9,8
public static List<int> pageRangeToList(string pageRg, int Nmax = 0) { List<int> ls = new List<int>(); int lb,ub,i; foreach (string ss in pageRg.Split(',')) { if(int.TryParse(ss,out lb)){ ls.Add(Math.Abs(lb)); } else { var subls = ss.Split('-').ToList(); lb = (int.TryParse(subls[0],out i)) ? i : 0; ub = (int.TryParse(subls[1],out i)) ? i : Nmax; ub = ub > 0 ? ub : lb; // if ub=0, take 1 value of lb for(i=0;i<=Math.Abs(ub-lb);i++) ls.Add(lb<ub? i+lb : lb-i); } } Nmax = Nmax > 0 ? Nmax : ls.Max(); // real Nmax return ls.Where(s => s>0 && s<=Nmax).ToList(); }