C# dispone del supporto integrato per l'analisi delle stringhe dei numeri di pagina?

StackOverflow https://stackoverflow.com/questions/40161

  •  09-06-2019
  •  | 
  •  

Domanda

C# dispone di supporto integrato per l'analisi di stringhe di numeri di pagina?Per numeri di pagina intendo il formato che potresti inserire in una finestra di dialogo di stampa che è un misto di virgole e trattini delimitati.

Qualcosa come questo:

1,3,5-10,12

Ciò che sarebbe davvero carino è una soluzione che mi restituisca una sorta di elenco di tutti i numeri di pagina rappresentati dalla stringa.Nell'esempio sopra, sarebbe carino ottenere un elenco come questo:

1,3,5,6,7,8,9,10,12

Voglio solo evitare di lanciarne uno mio se c'è un modo semplice per farlo.

È stato utile?

Soluzione

Dovrebbe essere semplice:

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

Modificare: grazie per la correzione ;-)

Altri suggerimenti

Non ha un modo integrato per farlo, ma sarebbe banale farlo usando String.Split.

Dividi semplicemente su "," quindi avrai una serie di stringhe che rappresentano numeri o intervalli di pagina.Itera su quella serie ed esegui un String.Split di '-'.Se non c'è un risultato, è un semplice numero di pagina, quindi inseriscilo nell'elenco delle pagine.Se c'è un risultato, prendi la sinistra e la destra del "-" come limiti e usa un semplice ciclo for per aggiungere ogni numero di pagina all'elenco finale in quell'intervallo.

Non ci vogliono che 5 minuti per farlo, poi forse altri 10 per aggiungere alcuni controlli di integrità per generare errori quando l'utente tenta di inserire dati non validi (come "1-2-3" o qualcosa del genere).

L'approccio di Keith sembra carino.Ho messo insieme un approccio più ingenuo utilizzando gli elenchi.Questo ha un controllo degli errori quindi si spera che dovrebbe rilevare la maggior parte dei problemi: -

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

Di seguito è riportato il codice che ho appena messo insieme per fare questo..Puoi inserire nel formato come...1-2,5abcd,6,7,20-15,,,,,,

facile da aggiungere per altri formati

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

Ecco qualcosa che ho inventato per qualcosa di simile.

Gestisce i seguenti tipi di intervalli:

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

Non verifica la presenza di valori duplicati, quindi set 1,5,-10 produrrà la sequenza 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);
            }
        }
    }
}

Non puoi esserne sicuro finché non avrai casi di prova.Nel mio caso preferirei che fosse delimitato da spazi bianchi anziché delimitato da virgole.Rende l'analisi un po' più complessa.

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

Nota che la risposta di Keith (o una piccola variazione) fallirà l'ultimo test in cui è presente uno spazio bianco tra il token dell'intervallo.Ciò richiede un tokenizzatore e un parser adeguato con 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();
            }
        }
    }
}

Approccio a una linea con 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) });

Ecco una versione leggermente modificata del codice di lassevk che gestisce l'operazione string.Split all'interno della corrispondenza Regex.È scritto come metodo di estensione e puoi gestire facilmente il problema dei duplicati utilizzando l'estensione Disinct() di 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;
                    }
                }
            }
        }
    }

La risposta che mi è venuta in mente:

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 non è efficiente come segue il codice.I metodi stringa sono più efficienti di Regex e dovrebbero essere utilizzati quando possibile.

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

        }
    }
}

La mia soluzione:

  • restituisce un elenco di numeri interi
  • invertito/errore di battitura/duplicato possibile:1,-3,5-,7-10,12-9 => 1,3,5,7,8,9,10,12,11,10,9 (utilizzato quando si desidera estrarre, ripetere le pagine)
  • opzione per impostare il totale delle pagine:1,-3,5-,7-10,12-9 (Nmax=9) => 1,3,5,7,8,9,9
  • completamento automatico:1,-3,5-,8 (Nmax=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();
    }
    
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top