O C# tem built-in de suporte para a análise do número de página cordas?
Pergunta
O C# tem suporte integrado para análise de cadeias de caracteres de números de página?Por que os números de página, quero dizer que o formato que você pode entrar em um diálogo imprimir, que é uma mistura de vírgula e o traço-delimitado.
Algo como isto:
1,3,5-10,12
O que seria muito bom é uma solução que me deu de volta algum tipo de lista de todos os números de página representado pela seqüência de caracteres.No exemplo acima, a obtenção de uma lista de volta, como isso seria bom:
1,3,5,6,7,8,9,10,12
Eu só quero evitar rolando meus próprios se há uma maneira fácil de fazê-lo.
Solução
Deve ser simples:
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;
}
}
}
Editar: obrigado pela correção ;-)
Outras dicas
Ele não tem um modo interno para fazer isso, mas seria trivial fazer usando a corda.Divisão.
Simplesmente dividida em ',' então você tem uma série de cadeias de caracteres que representam números de página ou intervalos.Iterar sobre essa série e fazer uma Seqüência de caracteres.Divisão de" -".Se não houver um resultado, é um simples número de página, de modo a colocá-lo na sua lista de páginas.Se houver um resultado, siga para a esquerda e para a direita do " - " como os limites e uso de um simples loop for para adicionar cada número de página a sua lista final sobre esse intervalo.
Não pode demorar, mas 5 minutos para fazer, então, talvez, outro de 10 a adicionar em algumas verificações de sanidade para lançar erros quando o usuário tentar inserir dados inválidos (como em "1-2-3", ou algo assim.)
Keith abordagem parece bom.Eu coloquei uma mais ingênuo abordagem usando listas.Este tem de verificação de erro, por isso espero que deve pegar mais problemas:-
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));
}
Abaixo está o código acabei de colocar juntos para fazer isso..Você pode entrar em formato..1-2,5 abcd,6,7,20-15,,,,,,
fácil de suplementos para outros formatos
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;
}
Aqui está algo que eu cozinhei por algo semelhante.
Ele lida com os seguintes tipos de intervalos:
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
Ele não procurar valores duplicados, de modo a definir 1,5,-10 irá produzir a sequência 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);
}
}
}
}
Você não pode ter certeza, até os casos de teste.No meu caso eu gostaria de ser o espaço em branco delimitado em vez de vírgula delimitada.Fazer a análise um pouco mais complexa.
[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 );
}
Note que Keith resposta ( ou uma pequena variação) vai falhar o último teste, onde não há espaço em branco entre o intervalo de token.Isso requer um tokenizador e um bom analisador de lookahead.
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();
}
}
}
}
Uma linha de aproximação com Split
e 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) });
Aqui está uma versão ligeiramente modificada do lassevk do código que manipula a cadeia.Operação de divisão dentro do Regex jogo.É escrito como um método de extensão e você pode facilmente lidar com as duplicatas problema usando o Disinct() extensão do LINQ.
/// <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;
}
}
}
}
}
A resposta veio-me:
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 não é eficiente como o código a seguir.String métodos são mais eficientes do que Regex e devem ser utilizados sempre que possível.
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();
}
}
}
A minha solução:
- retornar a lista de números inteiros
- invertida/typo/duplicados possível:1,-3,5-,7-10,12-9 => 1,3,5,7,8,9,10,12,11,10,9 (usado quando você deseja extrair, repita páginas)
- opção para definir o total de páginas:1,-3,5-,7-10,12-9 (Nmax=9) => 1,3,5,7,8,9,9
autocompletar: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(); }