2008-09-02 9 views
17

क्या सी # में पृष्ठ संख्याओं के तारों को पार्स करने के लिए अंतर्निहित समर्थन है? पृष्ठ संख्याओं से, मेरा मतलब है कि आप प्रिंट संवाद में प्रवेश कर सकते हैं जो कॉमा और डैश-डिलीमिट का मिश्रण है।क्या सी # में पृष्ठ-संख्या तारों को पार्स करने के लिए अंतर्निहित समर्थन है?

कुछ इस तरह:

1,3,5-10,12 

क्या वास्तव में अच्छा होगा एक समाधान है कि मुझे वापस स्ट्रिंग के प्रतिनिधित्व वाले सभी पेज नंबर की सूची के कुछ प्रकार दे दी है। उपरोक्त उदाहरण में, वापस इस प्रकार की सूची हो रही अच्छा होगा:

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

मैं बस अपना खुद का रोलिंग वहाँ एक आसान तरीका यह करने के लिए अगर बचना चाहते हैं।

+2

रिवर्स कार्रवाई करने के लिए, http://stackoverflow.com/questions/7688881/convert-list-to-number-range-string देखना – Grhm

उत्तर

19

सरल होना चाहिए:

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

संपादित करें: ठीक ;-)

+0

मैं दो परिवर्तनों का सुझाव देता हूं: (1) 'जारी रखें'; पहले 'उपज वापसी संख्या' के बाद; ', जो आपको' अन्य 'की आवश्यकता होगी और (2) तुलना को' end> ​​= start' में बदल देगा, जो सक्षम होगा आप एकल आइटम श्रेणियों जैसे '1-1' का समर्थन करने के लिए। –

+0

@ माइकल टेपर - सलाह के लिए जयकार। (1) मुझे लगता है कि 'जारी रखें' का उपयोग कर 'प्रारंभिक' एक शुद्ध कोडिंग शैली की बात है। मैं उस 'स्पार्टन' शैली को पसंद करता हूं लेकिन मुझे लगता है कि मेरी टीम के अधिकांश देव एक ठोस 'अन्य' ब्लॉक पसंद करते हैं, खासकर जब यह इस तरह की कुछ पंक्तियां होती है। (2) मैंने जानबूझकर बहुत सारी त्रुटि जांच से बचाया और इस नमूना को अच्छा और सरल रखने के लिए। ऐसे भार हैं जिन्हें आप जोड़ सकते हैं - उदाहरण के लिए यदि किसी श्रेणी को पार्स नहीं किया जा सकता है तो यह कोड बस इसे छोड़ देता है, लेकिन कुछ प्रकार का अपवाद बेहतर हो सकता है क्योंकि छोड़ने से चुपचाप त्रुटियों में कमी हो सकती है। – Keith

7

इसमें ऐसा करने का एक अंतर्निहित तरीका नहीं है, लेकिन स्ट्रिंग का उपयोग करना मुश्किल होगा। स्प्लिट।

बस ',' पर विभाजित करें, तो आपके पास तारों की एक श्रृंखला है जो पृष्ठ संख्या या श्रेणियों का प्रतिनिधित्व करती है। उस श्रृंखला पर Iterate और एक स्ट्रिंग करें। '-' के विभाजन। यदि कोई परिणाम नहीं है, तो यह एक सादा पृष्ठ संख्या है, इसलिए इसे पृष्ठों की अपनी सूची में चिपकाएं। यदि कोई परिणाम है, तो सीमा के रूप में '-' के बाएं और दाएं को ले जाएं और उस पृष्ठ पर प्रत्येक पृष्ठ संख्या को अपनी अंतिम सूची में जोड़ने के लिए लूप के लिए एक सरल का उपयोग करें।

नहीं ले सकते, लेकिन 5 मिनट ऐसा करने के लिए, तो शायद एक और 10 कुछ विवेक में जोड़ने के लिए त्रुटियों फेंक की जाँच करता है उपयोगकर्ता इनपुट अमान्य डेटा की कोशिश करता है जब ("1-2-3" या कुछ और की तरह।)

+0

[@ डैनियल जेनिंग्स] (http://stackoverflow.com/questions/40161/does-c-have-built-in-support-for-parsing-page-number-strings#40165) यह एक उचित दृष्टिकोण की तरह लगता है। मुझे लगा कि यह सुनिश्चित करने के लायक था कि माइक्रोसॉफ्ट के पास कहीं भी पेज नम्बरस्ट्रिंग पार्सर नहीं था, जो कि सभी विचित्र किनारे के मामलों को संभाला था। –

5

कीथ के दृष्टिकोण अच्छा लगता है के लिए धन्यवाद। मैंने सूचियों का उपयोग करके एक और बेवकूफ दृष्टिकोण रखा है। यह त्रुटि इसलिए उम्मीद है कि जाँच के सबसे समस्याओं उठाना चाहिए हैं: -

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

यहाँ कुछ मैं कुछ इसी तरह के लिए पकाया जाता है।

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

यहाँ कि रेगुलर एक्सप्रेशन से मेल के अंदर 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; 
        } 
       } 
      } 
     } 
    } 
3

नीचे कोड है जिसे मैंने अभी करने के लिए एक साथ रखा है .. आप प्रारूप में प्रवेश कर सकते हैं ..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; 
    } 
0

जवाब मैं के साथ आया था के लिए आसान:

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

आप तब तक सुनिश्चित नहीं हो सकते जब तक आपके पास परीक्षण के मामले न हों। मेरे मामले में मैं अल्पविराम के बजाय सीमांकित स्थान खाली होना पसंद करूंगा। यह पार्सिंग थोड़ा और जटिल बना देता है।

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

ध्यान दें कि कीथ के जवाब (या एक छोटा सा बदलाव) अंतिम परीक्षण असफल हो जायेगी जहां सीमा टोकन के बीच खाली स्थान के नहीं है। इसके लिए एक टोकननाइज़र और लुकहेड के साथ एक उचित पार्सर की आवश्यकता होती है।

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

रेगेक्स निम्नलिखित कोड के रूप में प्रभावी नहीं है। स्ट्रिंग विधियों Regex से अधिक कुशल हैं और जब संभव हो तो इस्तेमाल किया जाना चाहिए। Split साथ

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

     } 
    } 
} 
0

एक पंक्ति दृष्टिकोण और 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) }); 
संबंधित मुद्दे