Эффективный способ извлечь только один элемент из строки с разделителями

MySQL-Version (кредиты @Gordon Linoff)

SELECT
    ID
FROM
    <table>
GROUP BY
    ID
HAVING
    SUM(IF(day(DUE_DT + interval 1 Day) = 1, 1, 0)) = COUNT(ID);

Исходный ответ:

SELECT MAX(DUE_DT) FROM <table> WHERE ID = <the desired ID>

или если вы хотите все MAX ( DUE_DT) для каждого уникального идентификатора

SELECT ID, MAX(DATE) FROM <table> GROUP BY ID

2
задан Andrew Morton 18 January 2019 в 21:46
поделиться

5 ответов

В документации для String.Split есть несколько полезных замечаний, относящихся к этой проблеме, хотя перед тем, как это обнаружить, я написал следующее.

Один из способов сделать это - найти разделитель с помощью метода String.IndexOf - вы можете указать индекс, с которого начинается поиск, так что можно пропустить пункты без необходимости просматривать каждый персонаж. (Проверка каждого символа происходит за кулисами, но это немного быстрее, чем делать это самостоятельно.)

Я придумал метод расширения, добавив новый класс с именем "ExtensionMethods.cs" в решение с помощью это содержание:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        /// <summary>
        /// Get the nth item from a delimited string.
        /// </summary>
        /// <param name="s">The string to retrieve a delimited item from.</param>
        /// <param name="delimiter">The character used as the item delimiter.</param>
        /// <param name="n">Zero-based index of item to return.</param>
        /// <returns>The nth item or an empty string.</returns>
        public static string Split(this string s, char delimiter, int n)
        {

            int pos = pos = s.IndexOf(delimiter);

            if (n == 0 || pos < 0)
            { return (pos >= 0) ? s.Substring(0, pos) : s; }

            int nDelims = 1;

            while (nDelims < n && pos >= 0)
            {
                pos = s.IndexOf(delimiter, pos + 1);
                nDelims++;
            }

            string result = "";

            if (pos >= 0)
            {
                int nextDelim = s.IndexOf(delimiter, pos + 1);
                result = (nextDelim < 0) ? s.Substring(pos + 1) : s.Substring(pos + 1, nextDelim - pos - 1);
            }

            return result;
        }

    }
}

И небольшая программа для его проверки:

using System;
using System.Diagnostics;
using System.Linq;
using ExtensionMethods;

namespace ConsoleApp1
{

    class Program
    {

        static void Main(string[] args)
        {
            // test data...
            string s = string.Join(";", Enumerable.Range(65, 26).Select(c => (char)c));
            s = s.Insert(3, ";;;");

            string o = "";

            Stopwatch sw = new Stopwatch();

            sw.Start();
            for (int i = 1; i <= 1000000; i++) {
                o = s.Split(';', 21);
            }
            sw.Stop();
            Console.WriteLine("Item directly selected: " + sw.ElapsedMilliseconds);

            sw.Restart();
            for (int i = 1; i <= 1000000; i++) {
                o = s.Split(';')[21];
            }
            sw.Stop();
            Console.WriteLine("Item from split array:  " + sw.ElapsedMilliseconds + "\r\n");


            Console.WriteLine(s);
            Console.WriteLine(o);

            Console.ReadLine();

        }
    }
}

Пример вывода:

Элемент, выбранный напрямую: 1016
[ 115] Элемент из разделенного массива: 1345

A; B ;;;; C; D; E; F; G; H; I; J; K; L; M; N; O; P; Q ; R; S; T; U; V; W; X; Y; Z
S


Ссылка: Как выполнить и вызвать a Пользовательский метод расширения (Руководство по программированию в C #)

0
ответ дан Andrew Morton 18 January 2019 в 21:46
поделиться

попробуйте это:

public static string MyExtension(this string s, char delimiter, int n)
{
    var begin = n== 0 ? 0 : Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n);
    if (begin == -1)
        return null;
    var end = s.IndexOf(delimiter, begin +  (n==0?0:1));
    if (end == -1 ) end = s.Length;
    //var end = Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n + 1);
    var result = s.Substring(begin +1, end - begin -1 );

    return result;
}

PS: используется библиотека Westwind.Utilities


Код теста:

void Main()
{

     string s = string.Join(";", Enumerable.Range(65, 26).Select(c => (char)c));
            s = s.Insert(3, ";;;");

            string o = "";

            Stopwatch sw = new Stopwatch();

            sw.Start();
            for (int i = 1; i <= 1000000; i++) {
                o = s.Split(';', 21);
            }
            sw.Stop();
            Console.WriteLine("Item directly selected: " + sw.ElapsedMilliseconds);


            sw.Restart();
            for (int i = 1; i <= 1000000; i++) {
                o = s.MyExtension(';', 21);
            }
            sw.Stop();
            Console.WriteLine("Item directly selected by MyExtension: " + sw.ElapsedMilliseconds);

            sw.Restart();
            for (int i = 1; i <= 1000000; i++) {
                o = s.Split(';')[21];
            }
            sw.Stop();
            Console.WriteLine("Item from split array:  " + sw.ElapsedMilliseconds + "\r\n");


            Console.WriteLine(s);
            Console.WriteLine(o);

}

public static class MyExtensions
{
    /// <summary>
    /// Get the nth item from a delimited string.
    /// </summary>
    /// <param name="s">The string to retrieve a delimited item from.</param>
    /// <param name="delimiter">The character used as the item delimiter.</param>
    /// <param name="n">Zero-based index of item to return.</param>
    /// <returns>The nth item or an empty string.</returns>
    public static string Split(this string s, char delimiter, int n)
    {

        int pos = pos = s.IndexOf(delimiter);

        if (n == 0 || pos < 0)
        { return (pos >= 0) ? s.Substring(0, pos) : s; }

        int nDelims = 1;

        while (nDelims < n && pos >= 0)
        {
            pos = s.IndexOf(delimiter, pos + 1);
            nDelims++;
        }

        string result = "";

        if (pos >= 0)
        {
            int nextDelim = s.IndexOf(delimiter, pos + 1);
            result = (nextDelim < 0) ? s.Substring(pos + 1) : s.Substring(pos + 1, nextDelim - pos - 1);
        }

        return result;
    }

    public static string MyExtension(this string s, char delimiter, int n)
    {
        var begin = n== 0 ? 0 : Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n);
        if (begin == -1)
            return null;
        var end = s.IndexOf(delimiter, begin +  (n==0?0:1));
        if (end == -1 ) end = s.Length;
        //var end = Westwind.Utilities.StringUtils.IndexOfNth(s, delimiter, n + 1);
        var result = s.Substring(begin +1, end - begin -1 );

        return result;
    }

}

Результаты:

[ 112]

Редактировать: Благодаря @Kalten я улучшил решение еще больше. Значительные различия были замечены в результатах тестов.

0
ответ дан Derviş Kayımbaşıoğlu 18 January 2019 в 21:46
поделиться

Используя следующее регулярное выражение: ^([^;]*;){21}(.*?);, при этом вам не нужно генерировать список разделенных дыр для поиска желаемой позиции, и как только вы достигнете ее, это будет зависеть от того, существует или нет.

Объяснение :

^ --> start of a line.

([^;]*;){Position - 1} --> notice that the symbol ; here is the delimiter, the expression will loop Pos - 1 times

(.*?) --> Non-Greedy .*

DEMO

Подробнее о регулярных выражениях на C #: документация [1111 ]

В приведенном ниже примере я реализовал два примера, чтобы показать вам, как это работает.

Метод сопоставления : документация (в основном он ищет только первое вхождение шаблона) RegexOptions.Singleline : Обрабатывает входные данные как сигнальную линию.

Код C #

Console.WriteLine("First Delimiter : ");
        int Position = 22;
        char delimiter = ',';
        string pattern = @"^([^" + delimiter + "]*" + delimiter + "){" + (Position - 1) + @"}(.*?)" + delimiter;
        Regex regex = new Regex(pattern, RegexOptions.Singleline);
        // First Example
        string Data = @"AAV,zzz,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22ABC,23,24,24";
        Match Re = regex.Match(Data);
        if (Re.Groups.Count > 0)
            Console.WriteLine("\tMatch found : " + Re.Groups[2]);


        // Second Example
        Console.WriteLine("Second Delimiter : ");
        Position = 8;
        delimiter = ';';
        pattern = @"^([^" + delimiter + "]*" + delimiter + "){" + (Position - 1) + @"}(.*?)" + delimiter;
        Data = @"61d2e3f6-bcb7-4cd1-a81e-4f8f497f0da2;0;192.100.0.102:4362;2014-02-14;283;0;354;23;0;;;""0x8D15A2913C934DE"";Thursday, 19-Jun-14 22:58:10 GMT;";
        regex = new Regex(pattern, RegexOptions.Singleline);
        Re = regex.Match(Data);
        if (Re.Groups.Count > 0)
            Console.WriteLine("\tMatch found : " + Re.Groups[2]);

Вывод:

Первый разделитель:

    Match found : 22ABC

Второй разделитель:

[ 113]
0
ответ дан lagripe 18 January 2019 в 21:46
поделиться

Слишком поздно для «ответа», но этот код дает мне время выполнения около 0,75 секунд с обеими строками, обработанными 1 000 000 раз. Разница на этот раз в том, что теперь я не маршалирую объект, а использую указатели.

И на этот раз я возвращаю одну новую строку (String.Substring).

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        string testString1 = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24";
        string testString2 = "61d2e3f6-bcb7-4cd1-a81e-4f8f497f0da2;0;192.100.0.102:4362;2014-02-14;283;0;354;23;0;;;\"0x8D15A2913C934DE\";Thursday, 19-Jun-14 22:58:10 GMT;";

        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 1; i < 1000000; i++)
        {
            Delimit(testString1, ',', 22);
            Delimit(testString2, ';', 6);
        }
        sw.Stop();
        Console.WriteLine($"==>{sw.ElapsedMilliseconds}");
        Console.ReadLine();
    }

    static string Delimit(string stringUnderTest, char delimiter, int skipCount)
    {
        const int SIZE_OF_UNICHAR = 2;

        int i = 0;
        int index = 0;
        char c = Char.MinValue;

        GCHandle handle = GCHandle.Alloc(stringUnderTest, GCHandleType.Pinned);
        try
        {
            IntPtr ptr = handle.AddrOfPinnedObject();
            for (i = 0; i < skipCount; i++)
                while ((char)Marshal.ReadByte(ptr, index += SIZE_OF_UNICHAR) != delimiter) ;
            i = index;
            while ((c = (char)Marshal.ReadByte(ptr, i += SIZE_OF_UNICHAR)) != delimiter) ;
        }
        finally
        {
            if (handle.IsAllocated)
                handle.Free();
        }

        return stringUnderTest.Substring((index + SIZE_OF_UNICHAR) >> 1, (i - index - SIZE_OF_UNICHAR) >> 1);
    }
}
0
ответ дан Clay Ver Valen 18 January 2019 в 21:46
поделиться

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

Поскольку все строки c # реализуют IEnumerable<char>, довольно просто разработать метод, который требует нулевого выделения строк:

static public IEnumerable<char> GetDelimitedField(this IEnumerable<char> source, char delimiter, int index)
{
    foreach (var c in source)
    {
        if (c == delimiter) 
        {
            if (--index < 0) yield break;
        }
        else
        {
            if (index == 0) yield return c;
        }
    }
}

Возвращает результат как IEnumerable<char>, но его дешево преобразовать в строка. В любом случае это будет намного более короткая строка.

static public string GetDelimitedString(this string source, char delimiter, int index)
{
    var result = source.GetDelimitedField(delimiter, index);
    return new string(result.ToArray());
}

И вы можете назвать это так:

var input ="Zero,One,Two,Three,Four,Five,Six";
var output = input.GetDelimitedString(',',5);
Console.WriteLine(output);

Вывод:

Five

Пример на DotNetFiddle

0
ответ дан John Wu 18 January 2019 в 21:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: