Найдите частично дублированные строки, сохраните первый экземпляр и оставьте нетронутыми остальные - PullRequest
0 голосов
/ 19 июня 2019

Я пробовал несколько вещей с RegEx в Notepad ++, но я даже не уверен, возможно ли это вообще. Я попробовал одну или две вещи с PowerShell, и ничего не работает.

Данные поступают так:

007.130.0001;E2
007.130.0001;E4
007.130.0001;M4 20.1
007.130.0001;M4 20.1 NX
007.130.0002;E2
007.130.0002;E4
007.130.0002;M2_duplicate
007.130.0002;M4 20.1
007.130.0002;M4 20.1 NX
007.130.0008;M4 20.1 NX
007.130.0008;M4 20.3_M4 25.3
007.130.0008;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate
011.130.0124;M4 20.1
011.130.0124;M4 20.1 NX

и я так хочу (вариант А):

007.130.0001;E2
;E4
;M4 20.1
;M4 20.1 NX
007.130.0002;E2
;E4
;M2_duplicate
;M4 20.1
;M4 20.1 NX
007.130.0008;M4 20.1 NX
;M4 20.3_M4 25.3
;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate
;M4 20.1
;M4 20.1 NX

или что (Вариант B):

007.130.0001;E2;E4;M4 20.1;M4 20.1 NX
007.130.0002;E2;E4;M2_duplicate;M4 20.1;M4 20.1 NX
007.130.0008;M4 20.1 NX;M4 20.3_M4 25.3;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate;M4 20.1;M4 20.1 NX

Так что в основном я хочу разбить строку на специальный символ (;) и проверить первую часть на наличие дубликатов со следующей строкой, удалив все из них, кроме первой, и оставив остальную часть строки нетронутой.

Самый близкий у меня был этот RegEx:

Find: ^([^;]+;).+\R(.*?\1.+(?:\R|$))+
Replace: \2

Но тогда я закончу с этим:

007.130.0001;M4 20.1 NX
007.130.0002;M4 20.1 NX
007.130.0008;M4 20.3_M4 25.3 NX
011.130.0124;M4 20.1 NX

Ответы [ 4 ]

1 голос
/ 19 июня 2019

Следующая последовательность команд powershell делает свое дело:

$repeats = [Linq.Enumerable]::Count([System.IO.File]::ReadLines("<path to current dir>\\data.txt")) - 1; copy-item -path data.txt -destination work.txt; for ($i=1; $i -le $repeats; $i++) { (Get-Content -Raw work.txt) -replace '(?s)(\d{3}\.\d{3}\.\d{4};)(([^\r\n]+[\r\n]+)*)\1', '$1$2' | Out-File result.txt; move-item -path result.txt -destination work.txt -force }; move-item -path work.txt -destination result.txt -force

Объяснение

Scripting

Для обсуждения командная строка разбита на одну команду на строку. Предполагается, что исходные данные находятся в 'data.txt and a temp file work.txt can be used. result.txt` будет содержать результат.

Основная идея:

  • Разработайте регулярное выражение, используя обратные ссылки для выражения повторного совпадения.
  • Повторно выполнить это регулярное выражение.
    Каждый прогон удаляет 1 дубликат для каждого значения в первом столбце.
  • Консервативно оцените максимальное количество повторений заранее.

Решение далеко не элегантное и эффективное (некоторые идеи см. В разделе обзора).

  1. Оцените количество прогонов. Как мы увидим, каждый прогон удаляет 1 дубликат для каждого значения в первом столбце. Таким образом, в худшем случае (т.е. каждая строка начинается с одного и того же префикса) это означает, что no. of lines - 1 выполняется. Определите это число, сохраните его в переменной $repeats.
    Кредиты : Эта строка была взята из другого ответа SO .

    $repeats = [Linq.Enumerable]::Count([System.IO.File]::ReadLines("<path to current dir>\\data.txt")) - 1;
    
  2. Делопроизводство: скопировать оригинал в рабочий файл

    copy-item -path data.txt -destination work.txt;
    
  3. Повторите замену $repeats раз

    for ($i=1; $i -le $repeats; $i++) {
    
  4. Замена на основе регулярных выражений.
    - Сопоставьте префикс строки + остаток строки + любое количество строк без префикса + повторяющийся префикс, встречающийся снова.
    - Делопроизводство: переименуйте файл результатов в рабочий файл

    Credits : Команда применить регулярное выражение к текстовому файлу, взятому из этого SO ответа

        (Get-Content -Raw work.txt) -replace '(?s)(\d{3}\.\d{3}\.\d{4};)(([^\r\n]+[\r\n]+)*)\1', '$1$2' | Out-File result.txt;
        move-item -path result.txt -destination work.txt -force 
    };
    
  5. Делопроизводство: переместить последний экземпляр рабочего файла в файл результатов

    move-item -path work.txt -destination result.txt -force
    

Regex

Диалект регулярного выражения для powershell - это .NET.

Задача заключается в удалении каждой копии префикса при сохранении промежуточного материала. Однократное выполнение регулярного выражения не будет успешным, поскольку последовательные совпадения будут перекрываться.

Пошаговое обсуждение:

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

(?s)

б. Шаблон соответствия префикса Очевидно, этот подшаблон необходимо изменить в соответствии с фактическим форматом префикса. Эта форма (3-3-4 десятичных знака vlock, разделенных .) получена из примера.
Обратите внимание на завершающий ; и скобки, чтобы определить группу захвата для совпадений этого подшаблона. На эту группу / матч захвата ссылаются позже

(\d{3}\.\d{3}\.\d{4};)

с. Промежуточный текст
Остаток строки, в которой совпадает подвыражение b. + последовательность разделителей строк + произвольное количество строк.

  Due to the greedy greedy ( 'match as much as you can' ) nature of repetition operators ( `*` ), this part would match the remainder of the file (assuming it ends with a line separator).

(([^\r\n]+[\r\n]+)*)

д. Префикс клона Префикс, соответствующий подвыражению из b., должен произойти снова, чтобы произошла замена. Фактически это соответствует последнему клону префикса, сопоставленному с b.

\1

Как и задумано, регулярное выражение обнаруживает клонов только в начале строки

Обзор

Хотя было бы возможно сопоставить весь набор префиксных клонов и их промежуточных строк по шаблону, аналогичному приведенному, - в основном выбирая не жадное сопоставление («сопоставляйте как можно меньше») - я не знать любой способ отбросить точно клоны префикса при указании замены.

Количество повторений может быть уменьшено путем сопоставления только последовательных строк с одинаковым префиксом, исключая второе вхождение в каждом совпадении. Таким образом, было бы несколько совпадений / замен за проход. В основном это уменьшает число итераций log ( no. of lines ). Он обязывает измененное регулярное выражение обслуживать 1 промежуточную строку между 2 последовательными вхождениями префикса. Это изменение должно относиться только к очень большим файлам

Табличная форма исходного файла предполагает, что данные поступают из базы данных или электронной таблицы.Эти рабочие среды были бы намного лучше подходящими для выполнения поставленной задачи, поэтому, если есть какая-либо возможность изменить данные перед тем, как они будут выгружены в виде файла, это должно быть предпочтительным способом.

Более подходящие инструменты, позволяющиедля некоторого вида анализа столбца и дедупликации в первом столбце могут быть доступны в виде соответствующих команд powershell или инструментов командной строки.

0 голосов
/ 19 июня 2019

Вот простой Perl-скрипт, который выполняет эту работу:

Запустите его в каталоге, где входной файл -

perl -nE 'chomp;($k,$v)=split(/;/,$_,2);$h{$k}.=";$v";}{say $_.$h{$_} for sort keys%h' file > output

cat output
007.130.0001;E2;E4;M4 20.1;M4 20.1 NX
007.130.0002;E2;E4;M2_duplicate;M4 20.1;M4 20.1 NX
007.130.0008;M4 20.1 NX;M4 20.3_M4 25.3;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate;M4 20.1;M4 20.1 NX

. Объяснение:

perl                        # invoke the perl interpreter
-nE                         # options, n:process 1 line at a time, E: execute
'                           # code delimiter
  chomp;                    # suppress linebreak
  ($k,$v)=split(/;/,$_,2);  # split on semi-colon, keep only 2 occurrences (key=before the semi-colon value=after the semi-colon)
  $h{$k}.=";$v";            # populate a hash table
  }{                        # end loop (-n option)
  say $_.$h{$_}             # display key and its values
  for sort keys%h           # for all sorted keys
'                           # code delimiter
file                        # input file
>                           # redirect output to
output                      # output file
0 голосов
/ 19 июня 2019

Если у вас есть процессор xslt, это может быть жизнеспособным подходом:

  • Превратить CSV-подобный входной файл в простой XML-файл
  • Применение таблицы стилей xslt к:

    • Группировать данные по содержимому первого столбца
    • Дублировать в первом столбце
    • Написать результат в текстовом формате

Используемая таблица стилей xsl (gcsv.xslt в команде):

<?xml version="1.0" encoding="UTF-8"?>
<!--
    SO
    /8881624/naidite-chastichno-dublirovannye-stroki-sohranite-pervyi-ekzemplyar-i-ostavte-netronutymi-ostalnye#8881625

    19.06.2019 14:57:14
-->
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:arc="http://xml.solusy.eu/oryco/mail/archive/190214"
    exclude-result-prefixes="#all"
    version="3.0"
>
    <!-- textual output and handy variables -->
    <xsl:output method="text"/>
    <xsl:variable name="delimiter" select="';'"/>
    <xsl:variable name="newline"   select="'&#x0a;'"/>

    <!-- group rows by the first column's content --> 
    <xsl:template match="/">
        <xsl:for-each-group
            select="/file/r"
            group-by="./c[1]/text()"
        >
                <xsl:apply-templates select="current-group()[position() = 1]/c"/>
                <xsl:apply-templates select="current-group()[position() > 1]"/>
        </xsl:for-each-group>
    </xsl:template>

    <!-- Deduplicate the first column in all but the first row of a group -->
    <xsl:template match="r">
        <xsl:apply-templates select="./c[position() > 1]"/>
    </xsl:template>

    <!-- Write out column content as plain text -->
    <xsl:template match="c">
        <xsl:value-of select="."/>
        <xsl:choose>
            <xsl:when test="position() = last()">
                <xsl:value-of select="$newline"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$delimiter"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="child::node() | @*"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

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

(Get-Content -Raw data.txt) -replace ';', '</c><c>' -replace '(?s)[\r\n]+$', '' -replace '(?m)^', '<r><c>' -replace '(?m)$', '</c></r>' -replace '(?s)^', "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n<file>" -replace '(?s)$', '</file>' | Out-File -Encoding UTF8 work.xml; java -jar "<path_to_saxon>" -s:"<path_to_work_dir>\work.xml" -xsl:"<path_to_work_dir>\gcsv.xslt" -o:"<path_to_work_dir>\result.txt"

Пошаговое объяснение:

  1. Превратить исходный файл в xml.
    Это легко для csv-подобного контента, если не происходит экранирование символов: - поля csv не разделены
    - разделитель поля char (;) не встречается в содержимом поля
    - все символы файла могут использоваться как есть в xml

    Каждая строка файла преобразуется в элемент <r>, каждое поле в строке превращается в элемент <c> с данными поля в качестве текстового содержимого. Весь файл обернут в один корневой элемент (<file>), и для того, чтобы процессоры требовательны к xslt, был добавлен стандартный пролог xml.

    Эти задачи могут быть реализованы с помощью серии операций замены базы регулярных выражений, которые превращают ; в </c><c> и вставляют <r><c> и </c></r> в начале и конце каждой строки, соответственно, в многострочном режиме ( убедитесь, что результат синтаксически действителен для xml).

    (Get-Content -Raw data.txt) -replace ';', '</c><c>' -replace '(?s)[\r\n]+$', '' -replace '(?m)^', '<r><c>' -replace '(?m)$', '</c></r>' -replace '(?s)^', "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n<file>" -replace '(?s)$', '</file>' | Out-File -Encoding UTF8 work.xml;
    
    1. Обработка входного файла процессором xslt.
      В примере команды используется бесплатный Saxon (Saxon HE; проверьте их домашнюю страницу для получения подробной информации о лицензии). Любой другой процессор xslt2 должен быть в порядке.

         java -jar "<path_to_saxon>" -s:"<path_to_work_dir>\work.xml" -xsl:"<path_to_work_dir>\gcsv.xslt" -o:"<path_to_work_dir>\result.txt"
      
0 голосов
/ 19 июня 2019

Не очень умное решение, но оно работает.

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

  • Ctrl + H
  • Найти что: ^([^;]+;)(.+)\R(?:\1|((?=[^;]+;)))
  • Заменить на: $1$2(?3\n$3:;)
  • check Wrap вокруг
  • check Регулярное выражение
  • UNCHECK . matches newline
  • Заменить все

Пояснение:

^                   # beginning of line
  ([^;]+;)          # group 1, 1 or more non semi-colon then a semi-colon
  (.+)              # group 2, 1 or more any character but newline
  \R                # any kind of linebreak
  (?:               # start non capture group
    \1              # same as group 1
   |                # OR
    (               # start group 3
      (?=[^;]+;)    # positive lookahead, make sure whave after: 1 or more non semi-colon then a semi-colon
    )               # end group 3
  )                 # end group

Замена:

$1              # content of group 1
$2              # content of group 2
(?3             # if group 3 exists
  \n$3          # linefeed then content of group 3  (you can use \r\n if you want)
 :              # else
  ;             # semicolon
)               # end conditional

Результат для данного примера:

007.130.0001;E2;E4;M4 20.1;M4 20.1 NX
007.130.0002;E2;E4;M2_duplicate;M4 20.1;M4 20.1 NX
007.130.0008;M4 20.1 NX;M4 20.3_M4 25.3;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate;M4 20.1;M4 20.1 NX

Снимок экрана:

enter image description here

...