Чтение двойных значений из текстового файла

Попытка чтения данных из текстового файла с помощью приложения С#. Существует несколько строк данных, и каждая из них начинается с целого числа, а затем идет группа двойных значений. Часть текстового файла выглядит следующим образом,

   33 0.573140941467E-01 0.112914262390E-03 0.255553577735E-02 0.497192659486E-04 0.141869181079E-01-0.147813598922E-03
   34 0.570076593453E-01 0.100112550891E-03 0.256427138318E-02-0.868691490164E-05 0.142821920093E-01-0.346011975369E-03
   35 0.715507714946E-01 0.316132133031E-03-0.106581466521E-01-0.920513736900E-04 0.138018668842E-01-0.212219497066E-03

Здесь 33, 34, 35 - целочисленные значения, за которыми следуют 6 двойных значений. И эти двойные значения не обязательно имеют пробел или какой-либо другой разделитель между ними. то есть, если двойное число отрицательно, то перед ним будет "-", и это займет место. Таким образом, в принципе, возможно, что все 6 двойных значений будут вместе.

Теперь проблема в том, как извлечь это изящно?

Что я пробовал:

String.Split(' ');

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

Это может быть легко решено в C++ с помощью sscanf.

double a, b, c, d, e, f;

sscanf(string, "%d %lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &e, &f);
// here string contains a line of data from text file.

Текстовый файл, содержащий двойные значения, создается сторонним инструментом, и я не могу контролировать его вывод.

Есть ли способ, как целые и двойные значения могут быть изящно извлечены построчно?

Ответ 1

Если я вижу это правильно, у вас есть формат данных с фиксированной шириной. Чем вы можете просто проанализировать этот факт.

т.е. если предположить, что значения находятся в файле d:\temp\doubles.txt:

void Main()
{
    var filename = @"d:\temp\doubles.txt";
    Func<string, string[]> split = (s) =>
    {
        string[] res = new string[7];
        res[0] = s.Substring(0, 2);
        for (int i = 0; i < 6; i++)
        {
            res[i + 1] = s.Substring(2 + (i * 19), 19);
        }
        return res;
    };
    var result = from l in File.ReadAllLines(filename)
                 let la = split(l)
                 select new
                 {
                    i = int.Parse(la[0]),
                     d1 = double.Parse(la[1]),
                     d2 = double.Parse(la[2]),
                     d3 = double.Parse(la[3]),
                     d4 = double.Parse(la[4]),
                     d5 = double.Parse(la[5]),
                     d6 = double.Parse(la[6])

                 };
    foreach (var e in result)
    {
        Console.WriteLine($"{e.i}, {e.d1}, {e.d2}, {e.d3}, {e.d4}, {e.d5}, {e.d6}");
    }
}

Выходы:

33, 0.0573140941467, 0.00011291426239, 0.00255553577735, 4.97192659486E-05, 0.0141869181079, -0.000147813598922
34, 0.0570076593453, 0.000100112550891, 0.00256427138318, -8.68691490164E-06, 0.0142821920093, -0.000346011975369
35, 0.0715507714946, 0.000316132133031, -0.0106581466521, -9.205137369E-05, 0.0138018668842, -0.000212219497066

PS: С вашими точными данными int должно выделять больше места.

Ответ 2

Решите это с помощью регулярного выражения. Мой первый снимок:

"[\s-+]\d+\.\d+E[+-]\d\d"

Я только что попробовал это так:

using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace ConsoleApp1 {

    class Program {
        static void Main(string[] args) {
            var fileContents =
                  "33 0.573140941467E-01 0.112914262390E-03 0.255553577735E-02 0.497192659486E-04 0.141869181079E-01-0.147813598922E-03"
                + "34 0.570076593453E-01 0.100112550891E-03 0.256427138318E-02-0.868691490164E-05 0.142821920093E-01-0.346011975369E-03"
                + "35 0.715507714946E-01 0.316132133031E-03-0.106581466521E-01-0.920513736900E-04 0.138018668842E-01-0.212219497066E-03";

            var rex = new Regex(@"[\s-+]\d+\.\d+E[+-]\d\d", RegexOptions.Multiline);
            foreach (Match match in rex.Matches(fileContents)) {
                double d = double.Parse(match.Value.TrimStart(), NumberFormatInfo.InvariantInfo);
                Console.WriteLine("found a match: " + match.Value.TrimStart() + " => " + d);
            }

            Console.ReadLine();
        }
    }
}

С этим выводом (немецкая локализация, с запятой в качестве десятичного разделителя):

found a match: 0.573140941467E-01 => 0,0573140941467
found a match: 0.112914262390E-03 => 0,00011291426239
found a match: 0.255553577735E-02 => 0,00255553577735
found a match: 0.497192659486E-04 => 4,97192659486E-05
found a match: 0.141869181079E-01 => 0,0141869181079
found a match: -0.147813598922E-03 => -0,000147813598922
found a match: 0.570076593453E-01 => 0,0570076593453
found a match: 0.100112550891E-03 => 0,000100112550891
found a match: 0.256427138318E-02 => 0,00256427138318
found a match: -0.868691490164E-05 => -8,68691490164E-06
found a match: 0.142821920093E-01 => 0,0142821920093
found a match: -0.346011975369E-03 => -0,000346011975369
found a match: 0.715507714946E-01 => 0,0715507714946
found a match: 0.316132133031E-03 => 0,000316132133031
found a match: -0.106581466521E-01 => -0,0106581466521
found a match: -0.920513736900E-04 => -9,205137369E-05
found a match: 0.138018668842E-01 => 0,0138018668842
found a match: -0.212219497066E-03 => -0,000212219497066

Ответ 3

Я просто выбрал неоптимальный вариант и заменил строку "E-" на что-то другое, а заменил весь отрицательный знак пробелом и отрицательным знаком ("-"), а затем отменил все значения "E-".

Тогда я смог использовать split для извлечения значений.

private static IEnumerable<double> ExtractValues(string values)
{
    return values.Replace("E-", "E*").Replace("-", " -").Replace("E*", "E-").Split(' ').Select(v => double.Parse(v));
}

Ответ 4

void Main()
{
    var filename = @"d:\temp\doubles.txt";
    Func<string, string[]> split = (s) =>
    {
        string[] str= new string[7];
        str[0] = s.Substring(0, 2);
        for (int i = 0; i < 6; i++)
        {
            str[i + 1] = s.Substring(2 + (i * 19), 19);
        }
        return str;
    };
    var result = from l in File.ReadAllLines(filename)
                 let la = split(l)
                 select new
                 {
                    i = int.Parse(la[0]),
                     d1 = double.Parse(la[1]),
                     d2 = double.Parse(la[2]),
                     d3 = double.Parse(la[3]),
                     d4 = double.Parse(la[4]),
                     d5 = double.Parse(la[5]),
                     d6 = double.Parse(la[6])

                 };
    foreach (var e in result)
    {
        Console.WriteLine($"{e.i}, {e.d1}, {e.d2}, {e.d3}, {e.d4}, {e.d5}, {e.d6}");
    }
}

Ответ 5

Вы можете сделать это:

public void ParseFile(string fileLocation)
{
   string[] lines = File.ReadAllLines(fileLocation);

   foreach(var line in lines)
   {
       string[] parts = var Regex.Split(line, "(?((?<!E)-)| )");

       if(parts.Any())
       {
          int first = int.Parse(parts[0]);

          double[] others = parts.Skip(1).Select(a => double.Parse(a)).ToArray();
       }
   }
}   

Ответ 6

Ответы, которые я видел до сих пор, настолько сложны. Вот простой, не задумываясь,

Согласно комментарию @Veljko89, я обновил код с поддержкой неограниченного количества номеров

    List<double> ParseLine(string line)
    {
        List<double> ret = new List<double>();

        ret.Add(double.Parse(line.Substring(0, line.IndexOf(' '))));
        line = line.Substring(line.IndexOf(' ') + 1);

        for (; !string.IsNullOrWhiteSpace(line); line = line.Substring(line.IndexOf('E') + 4))
        {
            ret.Add(double.Parse(line.Substring(0, line.IndexOf('E') + 4)));
        }

        return ret;
    }

Ответ 7

Если мы не можем использовать string.Split, мы можем попытаться разбить регулярные выражения с помощью Regex.Split; для данного line

string line = @"  33 0.573140941467E-01 0.112914262390E-03 0.255553577735E-02 0.497192659486E-04 0.141869181079E-01-0.147813598922E-03";

Мы можем попробовать

// Split either
//   1. by space
//   2. zero length "char" which is just after a [0..9] digit and followed by "-" or "+"
var items = Regex
  .Split(line, @" |((?<=[0-9])(?=[+-]))")
  .Where(item => !string.IsNullOrEmpty(item)) // we don't want empty parts 
  .Skip(1)                                    // skip 1st 33
  .Select(item => double.Parse(item));        // we want double

Console.WriteLine(string.Join(Environment.NewLine, items));

и получить

0.573140941467E-01
0.112914262390E-03
0.255553577735E-02
0.497192659486E-04
0.141869181079E-01
-0.147813598922E-03

В случае текстового файла мы должны разделить каждую строку:

Regex regex = new Regex(@" |((?<=[0-9])(?=[+-]))");

var records = File
  .ReadLines(@"c:\MyFile.txt") 
  .Select(line => regex
     .Split(line)
     .Where(item => !string.IsNullOrEmpty(item))
     .Skip(1)
     .Select(item => double.Parse(item))
     .ToArray());

Demo:

  string[] test = new string[] {
     // your examples
     "  33 0.573140941467E-01 0.112914262390E-03 0.255553577735E-02 0.497192659486E-04 0.141869181079E-01-0.147813598922E-03",
     "  34 0.570076593453E-01 0.100112550891E-03 0.256427138318E-02-0.868691490164E-05 0.142821920093E-01-0.346011975369E-03",
     " 35 0.715507714946E-01 0.316132133031E-03-0.106581466521E-01-0.920513736900E-04 0.138018668842E-01-0.212219497066E-03",

     // Some challenging cases (mine)
     "    36 123+456-789    123e+78 9.9e-95 0.0001", 
  };

  Regex regex = new Regex(@" |((?<=[0-9])(?=[+-]))");

  var records = test
    .Select(line => regex
      .Split(line)
      .Where(item => !string.IsNullOrEmpty(item))
      .Skip(1)
      .Select(item => double.Parse(item))
      .ToArray());

  string testReport = string.Join(Environment.NewLine, records
    .Select(record => $"[{string.Join(", ", record)}]"));

  Console.WriteLine(testReport);

Результат:

[0.0573140941467, 0.00011291426239, 0.00255553577735, 4.97192659486E-05, 0.0141869181079, -0.000147813598922]
[0.0570076593453, 0.000100112550891, 0.00256427138318, -8.68691490164E-06, 0.0142821920093, -0.000346011975369]
[0.0715507714946, 0.000316132133031, -0.0106581466521, -9.205137369E-05, 0.0138018668842, -0.000212219497066]
[123, 456, -789, 1.23E+80, 9.9E-95, 0.0001]

Ответ 8

Еще одно решение, обрабатывающее каждую строку отдельно и включающее значение int:

    static void Main(string[] args) {
        string[] fileLines = {
            "33 0.573140941467E-01 0.112914262390E-03 0.255553577735E-02 0.497192659486E-04 0.141869181079E-01-0.147813598922E-03",
            "34 0.570076593453E-01 0.100112550891E-03 0.256427138318E-02-0.868691490164E-05 0.142821920093E-01-0.346011975369E-03",
            "35 0.715507714946E-01 0.316132133031E-03-0.106581466521E-01-0.920513736900E-04 0.138018668842E-01-0.212219497066E-03"
        };

        var rex = new Regex(@"\b([-+]?\d+(?:\.\d+(?:E[+-]\d+)?)?)\b", RegexOptions.Compiled);
        foreach (var line in fileLines) {

            var dblValues = new List<double>();
            foreach (Match match in rex.Matches(line)) {
                string strVal = match.Groups[1].Value;
                double number = Double.Parse(strVal, NumberFormatInfo.InvariantInfo);
                dblValues.Add(number);
            }

            Console.WriteLine(string.Join("; ", dblValues));
        }

        Console.ReadLine();
    }
}

Результат/вывод:

33; 0,0573140941467; 0,00011291426239; 0,00255553577735; 4,97192659486E-05; 0,0141869181079; -0,000147813598922
34; 0,0570076593453; 0,000100112550891; 0,00256427138318; -8,68691490164E-06; 0,0142821920093; -0,000346011975369
35; 0,0715507714946; 0,000316132133031; -0,0106581466521; -9,205137369E-05; 0,0138018668842; -0,000212219497066