C# - Разделяне на тръба с екранирана тръба в данните?

Имам разделен файл, който бих искал да разделя (използвам C#). Например:

This|is|a|test

Някои от данните обаче могат да съдържат канал в себе си. Ако го направи, ще бъде екраниран с обратна наклонена черта:

This|is|a|pip\|ed|test (this is a pip|ed test)

Чудя се дали има regexp или някакъв друг метод за разделяне на това само на "чистите" тръби (тоест тръби, които нямат обратна наклонена черта пред тях). Текущият ми метод е да заменя екранираните канали с персонализиран фрагмент от текст, да го разделя на канали и след това да заменя моя персонализиран текст с канал. Не е много елегантно и не мога да не мисля, че има по-добър начин. Благодаря за всяка помощ.


person Frijoles    schedule 28.04.2011    source източник
comment
Виждали ли сте тази (чудовищна) тема. Не е пряк отговор, но се надяваме тласък в правилната посока.   -  person dawebber    schedule 28.04.2011
comment
Ами ако искате буквална обратна наклонена черта в края на едно от парчетата?   -  person Random832    schedule 28.04.2011


Отговори (6)


Просто използвайте String.IndexOf(), за да намерите следващата тръба. Ако предишният знак не е обратна наклонена черта, използвайте String.Substring(), за да извлечете думата. Като алтернатива можете да използвате String.IndexOfAny(), за да намерите следващото срещане на тръбата или обратната наклонена черта.

Правя много анализи по този начин и това наистина е доста ясно. Възприемайки моя подход, ако се направи правилно, също ще има тенденция да работи по-бързо.

РЕДАКТИРАНЕ

Всъщност може би нещо подобно. Би било интересно да видим как това се сравнява по отношение на производителността с решение на RegEx.

public List<string> ParseWords(string s)
{
    List<string> words = new List<string>();

    int pos = 0;
    while (pos < s.Length)
    {
        // Get word start
        int start = pos;

        // Get word end
        pos = s.IndexOf('|', pos);
        while (pos > 0 && s[pos - 1] == '\\')
        {
            pos++;
            pos = s.IndexOf('|', pos);
        }

        // Adjust for pipe not found
        if (pos < 0)
            pos = s.Length;

        // Extract this word
        words.Add(s.Substring(start, pos - start));

        // Skip over pipe
        if (pos < s.Length)
            pos++;
    }
    return words;
}
person Jonathan Wood    schedule 28.04.2011
comment

Тук няма рекурсия, защото за да има рекурсия, трябва да дефинирате нещо по отношение на самото себе си - ще забележите липса на правилото count/2 от дясната страна във вашия код.

% two paths, variable and non-variable
% and a base case to start the count
count([S|L], N) :- var(S), !, count(L, N0),  N is N0+1.
count([S|L], N) :- nonvar(S), !, count(L, N).
count([], 0).

Като алтернатива това може да се направи просто с findall/3.

count_alt(L, N) :- findall(S, (member(S, L), var(S)), D), length(D, N).
- person KaeL; 28.04.2011
comment
Ако не добавите думите към List<string> и не го върнете, методът за ръчно анализиране ще бъде около 5 пъти по-бърз от метода на регулярен израз. Ако добавите обратно разходите за управление на List<string>, това е около 3 пъти по-бързо, така или иначе на моята машина. - person Cᴏʀʏ; 28.04.2011
comment
Вижте моята актуализация... Промених теста си и намалих изпълнението на Regex до около 1,6 пъти по-бавно, но все пак печелите! - person Cᴏʀʏ; 28.04.2011
comment
Мисля, че това има проблем, ако последната дума е празна/празна. Имам файл с 37 имена на заглавни колони, но последният елемент на всеки ред е празен, така че редовете завършват с тръба | но без допълнително празно място; думите в този случай връщат само 36 - person Adam; 26.04.2019
comment
Мисля, че това също може да се сблъска с проблеми, когато има скрита обратна наклонена черта в края на поле.. като данни\\|още данни|.. справяне с това главоболие от клиентски данни ›.‹ - person Adam; 26.04.2019
comment
@Adam: Това е отпреди осем години, но може да се твърди, че ако едно поле е празно, то не трябва да се брои. Така че просто казвам, че как ще се работи в този случай зависи от изискванията. Не би трябвало да е трудно да модифицирате кода, за да го обработвате по различен начин. (И ще се радвам да го персонализирам според различни изисквания, но това би било като платен консултант. - person Jonathan Wood; 26.04.2019
comment
благодаря @JonathanWood :) Успях да го модифицирам леко, за да отговаря на нуждите ми - много оценявам! - person Adam; 16.05.2019

Това трябва да го направи:

string test = @"This|is|a|pip\|ed|test (this is a pip|ed test)";
string[] parts = Regex.Split(test, @"(?<!(?<!\\)*\\)\|");

Регулярният израз основно казва: разделяне на тръби, които не са предшествани от екраниращ знак. Не трябва да си приписвам заслуги за това обаче, просто отвлякох регулярния израз от тази публикация и го опрости.

РЕДАКТИРАНЕ

По отношение на производителността, в сравнение с метода за ръчно анализиране, предоставен в тази тема, открих, че това внедряване на Regex е 3 до 5 пъти по-бавно от изпълнението на Jonathon Wood, използвайки по-дългия тестов низ, предоставен от OP.

С това казано, ако не създадете или добавите думите към List<string> и вместо това върнете void, методът на Jon идва около 5 пъти по-бърз от метода Regex.Split() (0,01ms срещу 0,002ms) за чисто разделяне на низа. Ако добавите обратно разходите за управление и връщане на List<string>, това беше около 3,6 пъти по-бързо (0,01ms срещу 0,00275ms), осреднено за няколко комплекта от милион итерации. Не използвах статичния Regex.Split() за този тест, вместо това създадох нов екземпляр на Regex с израза по-горе извън моя тестов цикъл и след това извиках неговия метод Split.

АКТУАЛИЗАЦИЯ

Използването на статичната функция Regex.Split() всъщност е много по-бързо от повторното използване на екземпляр на израза. С това внедряване използването на регулярен израз е само около 1,6 пъти по-бавно от изпълнението на Jon (0,0043ms срещу 0,00275ms)

Резултатите бяха същите при използване на разширения регулярен израз от публикацията, към която свързах.

person Cᴏʀʏ    schedule 28.04.2011
comment
Ако приемем, че обратните наклонени черти също могат да бъдат екранирани (напр. "This|is|a|pip\\|ed|test (this is a pip|ed test)"), това не работи. Ще трябва да използвате пълния от споменатата публикация. - person porges; 28.04.2011
comment
@Прав си, Поргес. Това е първото нещо, което си помислих, когато реших да напиша код за това :) - person Oscar Mederos; 28.04.2011

Попаднах на подобен сценарий, за мен броят на тръбите беше фиксиран (не тръби с "\|") . Ето как се справих.

string sPipeSplit = "This|is|a|pip\\|ed|test (this is a pip|ed test)";
string sTempString = sPipeSplit.Replace("\\|", "¬"); //replace \| with non printable character
string[] sSplitString = sTempString.Split('|');
//string sFirstString = sSplitString[0].Replace("¬", "\\|"); //If you have fixed number of fields and you are copying to other field use replace while copying to other field.
/* Or you could use a loop to replace everything at once
foreach (string si in sSplitString)
{
    si.Replace("¬", "\\|");
}
*/
person Akshay    schedule 15.12.2016

Ето още едно решение.

Едно от най-красивите неща в програмирането са няколкото начина за даване на решение на един и същи проблем:

string text = @"This|is|a|pip\|ed|test"; //The original text
string parsed = ""; //Where you will store the parsed string

bool flag = false;
foreach (var x in text.Split('|')) {
    bool endsWithArroba = x.EndsWith(@"\");
    parsed += flag ? "|" + x + " " : endsWithArroba ? x.Substring(0, x.Length-1) : x + " ";
    flag = endsWithArroba;
}
person Oscar Mederos    schedule 28.04.2011
comment
Това е доста гладко, но не е много добър избор, ако сте загрижени за производителността. - person Jonathan Wood; 28.04.2011
comment
@Jonathan Както казах, това е просто друг начин да направите това. Няма смисъл да поставяте код, подобен на предоставения от вас. Съгласен съм с вас, въпреки че производителността може да не е нещо наистина важно в този проблем. - person Oscar Mederos; 28.04.2011
comment
Не те критикувах, че си го публикувал. Всъщност споменах, че е гладко. Просто коментирах ефективността на този подход. - person Jonathan Wood; 28.04.2011
comment
Мисля, че този и този на @Jonathan са доста хлъзгави, така че +1 и за двата. - person Justin Morgan; 29.04.2011

Решението на Кори е доста добро. Но ако предпочитате да не работите с Regex, тогава можете просто да направите нещо, търсейки "\|" и го заменете с някакъв друг знак, след това направете вашето разделяне, след което го заменете отново с "\|".

Друг вариант е да направите разделянето, след това да прегледате всички низове и ако последният символ е \, след това да го съедините със следващия низ.

Разбира се, всичко това игнорира какво се случва, ако имате нужда от екранирана обратна наклонена черта преди тръба... като "\\|".

Като цяло обаче клоня към regex.

Честно казано, предпочитам да използвам FileHelpers, защото, въпреки че това не е разделено със запетая, това е основно едно и също нещо. И те имат страхотна история за защо не трябва да пишете тези неща сами.

person Erik Funkenbusch    schedule 28.04.2011

Можете да направите това с регулярен израз. След като решите да използвате обратна наклонена черта като екраниращ знак, имате два случая на екраниране, които трябва да отчетете:

  • Бягство от тръба: \|
  • Бягство от обратна наклонена черта, която искате да се тълкува буквално.

И двете могат да бъдат направени в един и същ регулярен израз. Екранираните обратни наклонени черти винаги ще бъдат два \ знака заедно. Последователните, екранирани обратни наклонени черти винаги ще бъдат четни числа от \ знака. Ако намерите нечетна поредица от \ преди тръба, това означава, че имате няколко екранирани обратни наклонени черти, последвани от екранирана тръба. Така че искате да използвате нещо подобно:

/^(?:((?:[^|\\]|(?:\\{2})|\\\|)+)(?:\||$))*/

Може би объркващо, но трябва да работи. Обяснение:

^              #The start of a line
(?:...
    [^|\\]     #A character other than | or \ OR
    (?:\\{2})* #An even number of \ characters OR
    \\\|       #A literal \ followed by a literal |
...)+          #Repeat the preceding at least once
(?:$|\|)       #Either a literal | or the end of a line
person Justin Morgan    schedule 28.04.2011
comment
@Justin по някаква причина не работи на моя компютър. Освен това липсва ). - person Oscar Mederos; 28.04.2011
comment
@Oscar – Имаше толкова много вложени скоби, че беше трудно да се следи. Опитай сега. - person Justin Morgan; 28.04.2011
comment
@Justin сега работи, въпреки че се случва същото с решението на @Cory: A\\|b трябва да стане A\|b вместо A\\ и b. Първият \\ е знак като всеки друг, а вторият е екраниран от |, така че вторият ще бъде премахнат и изречението ще остане такова, каквото е. - person Oscar Mederos; 28.04.2011
comment
@Oscar - Ако въведете A\\|b, вие сте екранирали самия символ обратна наклонена черта, така че трябва да се тълкува като A` plus b. To get A\|b, you would input A\\\|b. That's how I would expect it to work, myself, and it's consistent with most escape schemes I've seen. In C#, for example, the string \\\n` би било литерал `` и връщане на каретка. - person Justin Morgan; 28.04.2011
comment
@Justin, зависи как го приемаш. Когато някой ви каже: I want to parse the string ABC\DE, трябва да приемете, че \ вече е екраниран. В противен случай оригиналният пример няма смисъл, защото самият C# ще даде грешка, ако напишете \| защото тук няма да избягате от нищо. За да възобновя, това, което мисля, е, че низът за анализиране е буквален (вече екраниран). - person Oscar Mederos; 28.04.2011
comment
@Oscar - Разбирам какво целиш. От друга страна, ако не го направите по този начин, няма да има начин да имате вход, завършващ с буквална обратна наклонена черта. Ако искахте A\ и b, нито A\|b, нито A\\|b биха работили. Декларирането на \ като екраниращ знак принуждава потребителя да го екранира в целия текст, но позволява всички възможни входове. Това може дори да не е валидно за ситуацията на питащия, но реших да избера най-малко ограничаващия вариант. Между другото, изглежда, че и двамата сме се сблъскали със собствените правила за избягване на Stack Overflow. - person Justin Morgan; 28.04.2011