File: Utilities\LineParser.cs
Web Access
Project: src\src\Microsoft.ML.Core\Microsoft.ML.Core.csproj (Microsoft.ML.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Runtime.CompilerServices;
 
namespace Microsoft.ML.Internal.Utilities
{
    [BestFriend]
    internal static class LineParser
    {
        public static (bool isSuccess, string key, float[] values) ParseKeyThenNumbers(string line, bool invariantCulture)
        {
            if (string.IsNullOrWhiteSpace(line))
                return (false, null, null);
 
            ReadOnlySpan<char> trimmedLine = line.AsSpan().TrimEnd(); // TrimEnd creates a Span, no allocations
 
            int firstSeparatorIndex = trimmedLine.IndexOfAny(' ', '\t'); // the first word is the key, we just skip it
            ReadOnlySpan<char> valuesToParse = trimmedLine.Slice(start: firstSeparatorIndex + 1);
 
            float[] values = AllocateFixedSizeArrayToStoreParsedValues(valuesToParse);
 
            int toParseStartIndex = 0;
            int valueIndex = 0;
            for (int i = 0; i <= valuesToParse.Length; i++)
            {
                if (i == valuesToParse.Length || valuesToParse[i] == ' ' || valuesToParse[i] == '\t')
                {
                    if (invariantCulture)
                    {
                        if (DoubleParser.TryParse(valuesToParse.Slice(toParseStartIndex, i - toParseStartIndex), out float parsed))
                            values[valueIndex++] = parsed;
                        else
                            return (false, null, null);
                    }
                    else
                    {
                        if (float.TryParse(valuesToParse.Slice(toParseStartIndex, i - toParseStartIndex).ToString(), out float parsed))
                            values[valueIndex++] = parsed;
                        else
                            return (false, null, null);
                    }
 
                    toParseStartIndex = i + 1;
                }
            }
 
            return (true, trimmedLine.Slice(0, firstSeparatorIndex).ToString(), values);
        }
 
        /// <summary>
        /// we count the number of values first to allocate a single array with of proper size
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float[] AllocateFixedSizeArrayToStoreParsedValues(ReadOnlySpan<char> valuesToParse)
        {
            int valuesCount = 0;
 
            for (int i = 0; i < valuesToParse.Length; i++)
                if (valuesToParse[i] == ' ' || valuesToParse[i] == '\t')
                    valuesCount++;
 
            return new float[valuesCount + 1]; // + 1 because the line is trimmed and there is no whitespace at the end
        }
    }
}