Есть ли эквивалент класса Scanner в С# для строк?

В Java я могу передать сканеру строку, а затем делать удобные вещи, например, scanner.hasNext() или scanner.nextInt(), scanner.nextDouble() и т. д.

Это позволяет довольно чистый код для разбора строки, содержащей ряды чисел.

Как это делается в C#?

Если бы у вас была строка, в которой говорилось бы:

"0 0 1 22 39 0 0 1 2 33 33"

В Java я бы передал это сканеру и сделал

while(scanner.hasNext()) 
    myArray[i++] = scanner.nextInt();

Или что-то очень похожее. Как это сделать на С#?


person mmcdole    schedule 06.04.2009    source источник
comment
Из интереса (для нас, C#-людей) не могли бы вы показать код инициализации сканера — например, нужно ли указывать тип, который вы сканируете?   -  person Steve    schedule 06.04.2009
comment
Scanner s = новый сканер (ввод), где ввод представляет собой много разных типов вещей (строка, файл, читаемый, входной поток и т. д.) java.sun.com/javase/6/docs/api/java/util/Scanner.html. Также есть методы hasNext (например, hasNextInt()), чтобы увидеть, является ли то, что вы ищете, следующей вещью, которую нужно прочитать.   -  person TofuBeer    schedule 06.04.2009
comment
Он также имеет общий hasNext(), чтобы просто посмотреть, остались ли в строке какие-либо токены любого типа.   -  person mmcdole    schedule 06.04.2009


Ответы (7)


Я собираюсь добавить это как отдельный ответ, потому что он сильно отличается от ответа, который я уже дал. Вот как вы можете начать создавать свой собственный класс Scanner:

class Scanner : System.IO.StringReader
{
  string currentWord;

  public Scanner(string source) : base(source)
  {
     readNextWord();
  }

  private void readNextWord()
  {
     System.Text.StringBuilder sb = new StringBuilder();
     char nextChar;
     int next;
     do
     {
        next = this.Read();
        if (next < 0)
           break;
        nextChar = (char)next;
        if (char.IsWhiteSpace(nextChar))
           break;
        sb.Append(nextChar);
     } while (true);
     while((this.Peek() >= 0) && (char.IsWhiteSpace((char)this.Peek())))
        this.Read();
     if (sb.Length > 0)
        currentWord = sb.ToString();
     else
        currentWord = null;
  }

  public bool hasNextInt()
  {
     if (currentWord == null)
        return false;
     int dummy;
     return int.TryParse(currentWord, out dummy);
  }

  public int nextInt()
  {
     try
     {
        return int.Parse(currentWord);
     }
     finally
     {
        readNextWord();
     }
  }

  public bool hasNextDouble()
  {
     if (currentWord == null)
        return false;
     double dummy;
     return double.TryParse(currentWord, out dummy);
  }

  public double nextDouble()
  {
     try
     {
        return double.Parse(currentWord);
     }
     finally
     {
        readNextWord();
     }
  }

  public bool hasNext()
  {
     return currentWord != null;
  }
}
person BlueMonkMN    schedule 06.04.2009
comment
Несмотря на то, что этот код может удобно представлять функциональность, подобную той, что предоставляет Java, я подозреваю, что та же проблема может быть решена более эффективно с помощью какой-либо аналогичной альтернативы, которая не должна анализировать значение дважды (один раз, чтобы увидеть, можно ли его проанализировать, и один раз, чтобы на самом деле получить значение). - person BlueMonkMN; 07.04.2009

Используя часть уже полученных ответов, я создал StringReader, который может извлекать Enum и любой тип данных, реализующий IConvertible.

Использование

using(var reader = new PacketReader("1 23 ErrorOk StringValue 15.22")
{
     var index = reader.ReadNext<int>();
     var count = reader.ReadNext<int>();
     var result = reader.ReadNext<ErrorEnum>();
     var data = reader.ReadNext<string>();
     var responseTime = reader.ReadNext<double>();
}

Реализация

public class PacketReader : StringReader
{
    public PacketReader(string s)
        : base(s)
    {
    }

    public T ReadNext<T>() where T : IConvertible
    {
        var sb = new StringBuilder();

        do
        {
            var current = Read();
            if (current < 0)
                break;

            sb.Append((char)current);

            var next = (char)Peek();
            if (char.IsWhiteSpace(next))
                break;

        } while (true);

        var value = sb.ToString();

        var type = typeof(T);
        if (type.IsEnum)
            return (T)Enum.Parse(type, value);

        return (T)((IConvertible)value).ToType(typeof(T), System.Globalization.CultureInfo.CurrentCulture);
    }

}
person Dennis    schedule 31.10.2014

Хотя это не та же фундаментальная концепция, то, что вы ищете, можно сделать с помощью этого лямбда-выражения:

string foo = "0 0 1 22 39 0 0 1 2 33 33";

int[] data = foo.Split(' ').Select(p => int.Parse(p)).ToArray();

Что это делает, так это сначала Split string, используя пробел в качестве разделителя. Затем функция Select позволяет вам указать псевдоним для данного члена в массиве (который я назвал «p» в этом примере), а затем выполнить операцию над этим элементом, чтобы получить окончательный результат. Затем вызов ToArray() превращает этот абстрактный перечисляемый класс в конкретный массив.

Итак, в этом конце это разбивает string, затем преобразует каждый элемент в int и заполняет int[] результирующими значениями.

person Adam Robinson    schedule 06.04.2009
comment
Весь смысл сканера в том, что он работает с любыми числами (не только целыми). - person Samuel; 06.04.2009
comment
Затем примените ту же концепцию, просто замените int.Parse на double.Parse, float.Parse и т. д. - person Adam Robinson; 06.04.2009
comment
Это все еще не хватает точки. Что, если в строке 5 целых чисел, 2 двойных и число с плавающей запятой? Ваше решение вообще не помогает. - person Samuel; 06.04.2009
comment
Вот почему я сказал, что концепция сканера не совсем такая же, но она соответствует идее того, что сделано в его примере кода. В этом смысл примера кода; если это не представляет того, что вы действительно хотите сделать, то это бессмысленно. - person Adam Robinson; 06.04.2009
comment
И как вы собираетесь отличить поплавок от двойника в строке? Если я вызову hasNext(), как мне узнать, какую функцию nextXX() вызывать? - person Adam Robinson; 06.04.2009
comment
hasNext(), если у сканера есть действительный токен для любого следующего числа. - person Samuel; 06.04.2009
comment
Это не отвечает на мой вопрос; hasNext() может сказать мне, что рядом есть десятичное число. Если я вызову nextInt(), что произойдет? Как узнать, следует ли вызывать nextFloat() или nextDouble()? - person Adam Robinson; 06.04.2009
comment
Программист называет это, потому что он хочет знать, является ли следующим токеном float или double. Возможно, вам следует прочитать документацию по Java для Scanner? - person Samuel; 06.04.2009
comment
Мне не нужно читать документацию по Java... Я не разработчик Java, я разработчик C#, здесь, чтобы ответить на вопрос о C#. Мне тоже неинтересно спорить с кем-то, кто его троллит. - person Adam Robinson; 06.04.2009
comment
Как можно ответить на вопрос, предпосылку которого не знаешь? Сканеры предназначены для чтения всевозможных вещей из потока. - person Samuel; 06.04.2009
comment
Я ожидаю, что предпосылка вопроса будет в вопросе, а не требует, чтобы разумный человек, которому адресован вопрос (разработчик С#), должен был провести собственное исследование, чтобы ответить на него. Исходя из этого, на вопрос был дан ответ. - person Adam Robinson; 06.04.2009

Насколько мне известно, для этого в фреймворке нет встроенных классов. Вы должны были бы свернуть свой собственный.

Это было бы не слишком сложно. Хорошая версия C# может реализовать IEnumerable, чтобы вы могли сказать:

var scanner = new Scanner<int>(yourString);
foreach(int n in scanner)
    ; // your code
person driis    schedule 06.04.2009
comment
Весь смысл сканера в том, что он работает с любыми числами (не только целыми). - person Samuel; 06.04.2009
comment
Нет: пример кода работает только для целых чисел так же, как этот код. Мне нравится общая идея. - person Joel Coehoorn; 06.04.2009
comment
у класса Scanner гораздо больше методов, и часто они используются для чтения разных вещей из одного и того же Scanner. Например, прочитайте строку, затем прочитайте число. - person TofuBeer; 06.04.2009
comment
Это, безусловно, сработает, но вы можете использовать встроенные языковые средства для обхода коллекций и выполнения преобразований (лямбда-выражений), как я описал, без необходимости создавать свои собственные. Каждому свое! - person Adam Robinson; 06.04.2009
comment
В целом, выбранная вами реализация и ее полнота действительно зависят от того, что необходимо выполнить. - person driis; 06.04.2009
comment
@driis Ага. Если цель состоит в том, чтобы просто разбить строку чисел, вероятно, просто пойти по лямбда-маршруту. Если вы просматриваете строку, в которой перемешаны символьные и числовые данные, возможно, единственным (разумным) подходом будет свертывание вашей собственной строки. - person Adam Robinson; 06.04.2009

Чтобы максимально приблизиться к вашему синтаксису, это будет работать, если вас интересует только один тип ("int" в примере):

static void Main(string[] args)
{
   if (args.Length == 0) { args = new string[] { "3", "43", "6" }; }
   IEnumerator<int> scanner = (from arg in args select int.Parse(arg)).GetEnumerator();
   while (scanner.MoveNext())
   {
      Console.Write("{0} ", scanner.Current);
   }            
}

Вот еще более мощная версия, которая позволяет вам получить доступ к любому типу, поддерживаемому реализацией строки IConvertible:

static void Main(string[] args)
{
    if (args.Length == 0) { args = new string[] { "3", "43", "6" }; }
    var scanner = args.Select<string, Func<Type, Object>>((string s) => {
            return (Type t) =>
            ((IConvertible)s).ToType(t, System.Globalization.CultureInfo.InvariantCulture); 
        }).GetEnumerator();
    while (scanner.MoveNext())
    {
        Console.Write("{0} ", scanner.Current(typeof(int)));
    }            
}

Просто передайте другой тип оператору typeof в цикле while, чтобы выбрать тип.

Для них обоих требуются последние версии C# и .NET Framework.

person David Gladfelter    schedule 06.04.2009

Вы можете использовать linq для этого следующим образом:

string text = "0 0 1 22 39 0 0 1 2 33 33";
text.Where(i => char.IsNumber(i)).Write(); // do somthing usefull here...
person Fraser    schedule 06.04.2009

Я бы сделал это одним из нескольких способов в зависимости от того, 1) вы используете последнюю версию .NET framework с поддержкой LINQ и 2) вы знаете, что значения являются допустимыми целыми числами. Вот функция для демонстрации обоих:

  int[] ParseIntArray(string input, bool validateRequired)
  {
     if (validateRequired)
     {
        string[] split = input.Split();
        List<int> result = new List<int>(split.Length);
        int parsed;
        for (int inputIdx = 0; inputIdx < split.Length; inputIdx++)
        {
           if (int.TryParse(split[inputIdx], out parsed))
              result.Add(parsed);
        }
        return result.ToArray();
     }
     else
        return (from i in input.Split()
                select int.Parse(i)).ToArray();
  }

Основываясь на комментариях в других ответах, я предполагаю, что вам нужна проверка. После прочтения этих комментариев я думаю, что самое близкое, что вы получите, это int.TryParse и double.TryParse, которые представляют собой комбинацию hasNextInt и nextInt (или комбинацию hasNextDouble и nextDouble).

person BlueMonkMN    schedule 06.04.2009