Удалить дубликаты из строки


17

Вдохновлен этим скромным вопросом StackOverflow .

Идея проста; учитывая String и массив Strings, удалите любые экземпляры слов в массиве (игнорируя регистр) из входной строки, кроме первой, вместе с любыми дополнительными пробелами, которые это может оставить. Слова должны соответствовать целым словам во входной строке, а не частям слов.

например "A cat called matt sat on a mat and wore a hat A cat called matt sat on a mat and wore a hat", ["cat", "mat"]должен выводить"A cat called matt sat on a mat and wore a hat A called matt sat on a and wore a hat"

вход

  • В качестве входных данных можно использовать либо строку, либо массив строк или массив строк, где входная строка является первым элементом. Эти параметры могут быть в любом порядке.
  • Входная строка не может быть принята как список строк, разделенных пробелом.
  • Входная строка не будет иметь начальных, конечных или последовательных пробелов.
  • Все входные данные будут содержать только символы [A-Za-z0-9], за исключением входной строки, также включающей пробелы.
  • Входной массив может быть пустым или содержать слова, отсутствующие во входной строке.

Выход

  • Вывод может быть либо возвращаемым значением из функции, либо распечатан в STDOUT
  • Вывод должен быть в том же случае, что и исходная строка

Контрольные примеры

the blue frog lived in a blue house, [blue] -> the blue frog lived in a house
he liked to read but was filled with dread wherever he would tread while he read, [read] -> he liked to read but was filled with dread wherever he would tread while he
this sentence has no matches, [ten, cheese] -> this sentence has no matches
this one will also stay intact, [] -> this one will also stay intact
All the faith he had had had had no effect on the outcome of his life, [had] -> All the faith he had no effect on the outcome of his life
5 times 5 is 25, [5, 6] -> 5 times is 25
Case for different case, [case] -> Case for different
the letters in the array are in a different case, [In] -> the letters in the array are a different case
This is a test Will this be correct Both will be removed, [this,will] -> This is a test Will be correct Both be removed

Поскольку это кодовый гольф, выигрывает самое низкое количество байтов!

Ответы:


9

R , 84 байта

function(s,w,S=el(strsplit(s," ")),t=tolower)cat(S[!duplicated(x<-t(S))|!x%in%t(w)])

Попробуйте онлайн!

Меньше 100 байт на вызове, это тоже не ?

Объяснение:

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

  1. дубликаты и
  2. в w

или, альтернативно, переворачивая это с ног на голову, сохраняя те, которые

  1. первое вхождение слова ИЛИ
  2. не в w.

duplicatedаккуратно возвращает логические индексы тех, которые не являются первыми вхождениями, поэтому !duplicated()возвращает индексы тех, которые являются первыми вхождениями, и x%in%wвозвращает логические индексы для xтех, которые находятся в w. Ухоженная.


6

Java 8, 117 110 байт

a->s->{for(String x:a)for(x="(?i)(.*"+x+".* )"+x+"( |$)(.*)";s.matches(x);s=s.replaceAll(x,"$1$3"));return s;}

Объяснение:

Попробуйте онлайн.

a->s->{                // Method with String-array and String parameters and String return
  for(String x:a)      //  Loop over the input-array
    for(x="(?i)(.*"+x+".* )"+x+"( |$)(.*)";
                       //   Regex to match
        s.matches(x);  //   Inner loop as long as the input matches this regex
      s=s.replaceAll(x,"$1$3")); 
                       //    Replace the regex-match with the 1st and 3rd capture groups
  return s;}           //  Return the modified input-String

Дополнительное объяснение для регулярного выражения:

(?i)(.*"+x+".* )"+x+"( |$)(.*)   // Main regex to match:
(?i)                             //  Enable case insensitivity
    (                            //  Open capture group 1
     .*                          //   Zero or more characters
       "+x+"                     //   The input-String
            .*                   //   Zero or more characters, followed by a space
               )                 //  End of capture group 1
                "+x+"            //  The input-String again
                     (           //  Open capture group 2
                       |$        //   Either a space or the end of the String
                         )       //  End of capture group 2
                          (      //  Open capture group 3
                           .*    //   Zero or more characters
                             )   //  End of capture group 3

$1$3                             // Replace the entire match with:
$1                               //  The match of capture group 1
  $3                             //  concatted with the match of capture group 3

4

MATL , 19 18 байт

"Ybtk@kmFyfX<(~)Zc

Входные данные: массив строк, затем строка.

Попробуйте онлайн! Или проверьте все тестовые случаи .

Как это устроено

"        % Take 1st input (implicit): cell array of strings. For each
  Yb     %   Take 2nd input (implicit) in the first iteration: string; or
         %   use the string from previous iteration. Split on spaces. Gives
         %   a cell array of strings
  tk     %   Duplicate. Make lowercase
  @k     %   Push current string from the array taken as 1st input. Make
         %   lowercase
  m      %   Membership: gives true-false array containing true for strings
         %   in the first input argument that equal the string in the second
         %   input argument
  F      %   Push false
  y      %   Duplicate from below: pushes the true-false array again
  f      %   Find: integer indices of true entries (may be empty)
  X<     %   Minimum (may be empty)
  (      %   Assignment indexing: write false in the true-false array at that
         %   position. So this replaces the first true (if any) by false
  ~      %   Logical negate: false becomes true, true becomes false
  )      %   Reference indexing: in the array of (sub)strings that was
         %   obtained from the second input, keep only those indicated by the
         %   (negated) true-false array
  Zc     %   Join strings in the resulting array, with a space between them
         % End (implicit). Display (implicit)

3

Perl 5 , 49 байт

@B=<>;$_=join$",grep!(/^$_$/xi~~@B&&$v{+lc}++),@F

Попробуйте онлайн!

Сохранено 9 (!!) байтов благодаря @TonHospel !


1
Это, кажется, не для This is a test Will this be correct Both will be removed+ this will. Вторые два слова удаляются правильно, но оно также удаляется beпосле второго willпо некоторым причинам.
Кевин Круйссен

1
@KevinCruijssen Хммм, я понимаю, почему это происходит в данный момент. Я постараюсь внимательно посмотреть на завтрашний обед, но сейчас я исправлюсь по цене +4. Спасибо, что дали мне знать!
Дом Гастингс

Для 49:@B=<>;$_=join$",grep!(/^$_$/xi~~@B&&$v{+lc}++),@F
Тон Хоспел

@TonHospel Ааа, потратил некоторое время, пытаясь позвонить lcбез паренов. Потрясающие! И использование регулярных выражений для массива намного лучше, спасибо! Я изо всех сил, чтобы вспомнить все ваши советы!
Дом Гастингс

2

Pyth, 27 байт

jdeMf!}r0eT@mr0dQmr0dPT._cz

Попробуйте онлайн

объяснение

jdeMf!}r0eT@mr0dQmr0dPT._cz
                          z  Take the string input.
                       ._c   Get all the prefixes...
    f    eT@                 ... which end with something...
     !}         Q    PT      ... which is not in the input and the prefix...
       r0   mr0d mr0d        ... case insensitive.
jdeM                         Join the ends of each valid prefix.

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


2

Stax , 21 байт CP437

åìøΓ²¬$M¥øHΘQä~╥ôtΔ♫╟

25 байт при распаковке,

vjcm[]Ii<;e{vm_]IU>*Ciyj@

Результатом является массив. Удобный вывод для Stax - один элемент на строку.

Запускать и отлаживать онлайн!

объяснение

vj                           Convert 1st input to lowercase and split at spaces,
  c                          Duplicate at the main stack
   m                         Map array with the rest of the program 
                                 Implicitly output
    []I                      Get the first index of the current array element in the array
       i<                    Test 1: The first index is smaller than the iteration index
                                 i.e. not the first appearance
         ;                   2nd input
          {vm                Lowercase all elements
             _]I             Index of the current element in the 2nd input (-1 if not found)
                U>           Test 2: The index is non-negative
                                 i.e. current element is a member of the 2nd input
                  *C         If test 1 and test 2, drop the current element
                                 and go on mapping the next
                    iyj@     Fetch the corresponding element in the original input and return it as the mapped result
                                 This preserves the original case

2

Perl 6 , 49 байт

->$_,+w{~.words.grep:{.lcw».lc||!(%){.lc}++}}

Проверь это

Expanded:

->              # pointy block lambda
  $_,           # first param 「$_」 (string)
  +w            # slurpy second param 「w」 (words)
{

  ~             # stringify the following (joins with spaces)

  .words        # split into words (implicit method call on 「$_」)

  .grep:        # take only the words we want

   {
     .lc        # lowercase the word being tested
               # is it not an element of
     w».lc      # the list of words, lowercased

     ||         # if it was one of the words we need to do a secondary check

     !          # Boolean invert the following
                # (returns true the first time the word was found)

     (
       %        # anonymous state Hash variable
     ){ .lc }++ # look up with the lowercase of the current word, and increment
   }
}

2

Perl 5 , 50 48 байтов

Включает +1в себя для-p

Укажите целевую строку, за которой следует каждое слово фильтра в отдельных строках STDIN:

perl -pe '$"="|";s%\b(@{[<>]})\s%$&x!$v{lc$1}++%iegx;chop';echo
This is a test Will this be correct Both will be removed
this
will
^D
^D

Требуется chopтолько для исправления завершающего пробела в случае удаления последнего слова

Просто код:

$"="|";s%\b(@{[<>]})\s%$&x!$v{lc$1}++%iegx;chop

Попробуйте онлайн!


1

JavaScript (ES6), 98 байт

s=>a=>s.split` `.filter(q=x=>(q[x=x.toLowerCase()]=eval(`/\\b${x}\\b/i`).test(a)<<q[x])<2).join` `

1

К4 , 41 байт

Решение:

{" "/:x_/y@>y:,/1_'&:'(_y)~/:\:_x:" "\:x}

Примеры:

q)k){" "/:x_/y@>y:,/1_'&:'(_y)~/:\:_x:" "\:x}["A cat called matt sat on a mat and wore a hat A cat called matt sat on a mat and wore a hat";("cat";"mat")]
"A cat called matt sat on a mat and wore a hat A called matt sat on a and wore a hat"

q)k){" "/:x_/y@>y:,/1_'&:'(_y)~/:\:_x:" "\:x}["Case for different case";enlist "case"]
"Case for different"

q)k){" "/:x_/y@>y:,/1_'&:'(_y)~/:\:_x:" "\:x}["the letters in the array are in a different case";enlist "In"]
"the letters in the array are a different case"

q)k){" "/:x_/y@>y:,/1_'&:'(_y)~/:\:_x:" "\:x}["5 times 5 is 25";(1#"5";1#"6")]
"5 times is 25"

Объяснение:

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

{" "/:x_/y@>y:,/1_'&:'(_y)~/:\:_x:" "\:x} / the solution
{                                       } / lambda with implicit x & y args
                                  " "\:x  / split (\:) on whitespace " "
                                x:        / save result as x
                               _          / lowercase x
                          ~/:\:           / match (~) each right (/:), each left (\:)
                      (_y)                / lowercase y
                   &:'                    / where (&:) each ('), ie indices of matches
                1_'                       / drop first of each result
              ,/                          / flatten
            y:                            / save result as y
         y@>                              / descending indices (>) apply (@) to y
      x_/                                 / drop (_) from x
 " "/:                                    / join (/:) on whitespace " "

1

JavaScript (Node.js) , 75 байт

f=(s,a)=>a.map(x=>s=s.replace(eval(`/\\b${x}\\b */ig`),s=>i++?"":s,i=0))&&s

Попробуйте онлайн!


1
Поскольку это не рекурсивная функция, вам не нужно включать f=в ваш счетчик байтов. Вы также можете сохранить байты по выделке параметров, заменяя (s,a)=>с , s=>a=>а затем вызвать функцию с f(s)(a).
Лохматый

@ Shaggy да, но я действительно возражаю против игры в гольф определения функции, потому что главная сделка - игра в гольф тела. но это хороший совет :)
DanielIndie

1

JavaScript ES6, 78 байт

f=(s,a,t={})=>s.split` `.filter(w=>a.find(e=>w==e)?(t[w]?0:t[w]=1):1).join` `

Как это устроено:

f=(s,a,t={})=> // Function declaration; t is an empty object by default
s.split` ` // Split the string into an array of words
.filter(w=> // Declare a function that, if it returns false, will delete the word
  a.find(e=>w==e) // Returns undeclared (false) if the word isn't in the list
  ?(t[w]?0 // If it is in the list and t[w] exists, return 0 (false)
    :t[w]=1) // Else make t[w] exist and return 1 (true)
  :1) // If the word isn't in the array, return true (keep the word for sure)
.join` ` // Rejoin the string

2
Добро пожаловать в PPCG! Поскольку вы не используете имя функции fдля рекурсивного вызова, безымянная функция также будет допустимой отправкой, поэтому вы можете сохранить два байта, отбросив f=.
Мартин Эндер

Добро пожаловать в PPCG! К сожалению, это не удается, когда речь идет о разных случаях.
Лохматый

Если бы не это, вы могли бы уменьшить это до 67 байтов
Shaggy

@MartinEnder Спасибо за совет!
Ян

@ Шагги, использующий входной массив в качестве объекта, - интересная идея, о которой я не задумывался. Я постараюсь исправить проблему с делом.
Ян

0

PowerShell v3 или новее, 104 байта

Param($s,$w)$w|?{$_-and$s-match($r="\b$_(?: |$)")}|%{$h,$t=$s-split$r;$s="$h$($Matches.0)$(-join$t)"};$s

За счет одного байта он может работать в PS 2.0, заменяя $Matches.0на $Matches[0].

Длинная версия:

Param($s, $w)
$w | Where-Object {$_ -and $s -match ($r = "\b$_(?: |$)")} |    # Process each word in the word list, but only if it matches the RegEx (which will be saved in $r).
    ForEach-Object {                                            # \b - word boundary, followed by the word $_, and either a space or the end of the string ($)
        $h, $t = $s -split $r                                   # Split the string on all occurrences of the word; the first substring will end up in $h(ead), the rest in $t(ail) (might be an array)
        $s = "$h$($Matches.0)$(-join $t)"                       # Create a string from the head, the first match (can't use the word, because of the case), and the joined tail array
    }
$s                                                              # Return the result

Используйте
Save as Whwhat.ps1 и вызывайте со строкой и словами в качестве аргументов. Если нужно передать более одного слова, слова должны быть заключены в @ ():

.\Whatever.ps1 -s "A cat called matt sat on a mat and wore a hat A cat called matt sat on a mat and wore a hat" -w @("cat", "mat")

Альтернатива без файла (может быть вставлена ​​непосредственно в консоль PS):
сохраните сценарий как ScriptBlock (внутри фигурных скобок) в переменной, затем вызовите его метод Invoke () или используйте его с Invoke-Command:

$f={Param($s,$w)$w|?{$_-and$s-match($r="\b$_(?: |$)")}|%{$h,$t=$s-split$r;$s="$h$($Matches.0)$(-join$t)"};$s}
$f.Invoke("A cat called matt sat on a mat and wore a hat A cat called matt sat on a mat and wore a hat", @("cat", "mat"))
Invoke-Command -ScriptBlock $f -ArgumentList "A cat called matt sat on a mat and wore a hat A cat called matt sat on a mat and wore a hat", @("cat", "mat")

0

Javascript, 150 байт

s=(x, y)=>{let z=new Array(y.length).fill(0);let w=[];for(f of x)(y.includes(f))?(!z[y.indexOf(f)])&&(z[y.indexOf(f)]=1,w.push(f)):w.push(f);return w}

Помимо проблем с игрой в гольф (посмотрите на другие решения JS для некоторых подсказок там), это берет первый ввод как массив слов и выводит массив слов, который не разрешен спецификацией задачи. Это также терпит неудачу, когда различные случаи вовлечены.
Лохматый

@Shaggy "Вывод может быть либо возвращаемым значением из функции". Похоже, он возвращает значение из функции?
aimorris

0

Чистый , 153 142 138 134 байта

import StdEnv,StdLib,Text
@ =toUpperCase
$s w#s=split" "s
=join" "[u\\u<-s&j<-[0..]|and[i<>j\\e<-w,i<-drop 1(elemIndices(@e)(map@s))]]

Попробуйте онлайн!

Определяет функцию $ :: String [String] -> String, буквально делая то, что описывает задача. Он находит и удаляет каждое вхождение после первого, для каждого целевого слова.


0

Сетчатка, 46 37 байт

+i`(^|,)((.+),.*\3.* )\3( |$)
$2
.*,

-14 байтов благодаря @Neil и +5 байтов за исправление ошибки.

Ввод в формате word1,word2,word3,sentence, потому что я не уверен, как сделать многострочный ввод (где входы используются по-разному) ..

Объяснение:

Попробуйте онлайн.

+i`(^|,)((.+),.*\3.* )\3( |$)   Main regex to match:
+i`                              Enable case insensitivity
   (^|,)                          Either the start of the string, or a comma
        (                         Open capture group 2
         (                         Open capture group 3
          .+                        1 or more characters
            )                      Close capture group 3
             ,                     A comma
              .*                   0 or more characters
                \3                 The match of capture group 3
                  .*               0 or more characters, followed by a space
                     )            Close capture group 2
                      \3          The match of capture group 2 again
                        ( |$)     Followed by either a space, or it's the end of the string
$2                              And replace everything with:
                                 The match of capture group 2

.*,                             Then get everything before the last comma (the list)
                                 and remove it (including the comma itself)

1
Как написано, вы можете упростить первую строку до +i`((.+),.*\2.* )\2( |$)второй, $1но я заметил, что ваш код often,he intended to keep ten geeseвсе равно не работает.
Нил

@Neil Спасибо за гольф -14, и исправил ошибку с +1.
Кевин Круйссен

... за исключением того, что теперь это не удается в одном из оригинальных тестовых случаев ...
Нил

@Neil Ах, упс .. Исправлено снова для +4 байта.
Кевин Круйссен

Что ж, хорошие новости в том, что я думаю, что вы можете использовать \bвместо (^|,), но плохие новости в том, что я думаю, что вам нужно \b\3\b(хотя я еще не разработал подходящий тестовый пример).
Нил

0

Красный , 98 байт

func[s w][foreach v w[parse s[thru[any" "v ahead" "]any[to remove[" "v ahead[" "| end]]| skip]]]s]

Попробуйте онлайн!

f: func [s w][ 
    foreach v w [                   ; for each string in the array
        parse s [                   ; parse the input string as follows:
            thru [                  ; keep everything thru: 
                any " "             ; 0 or more spaces followed by
                v                   ; the current string from the array followed by
                ahead " "           ; look ahead for a space
            ]
            any [ to remove [       ; 0 or more: keep to here; then remove: 
                " "                 ; a space followed by 
                v                   ; the current string from the array
                ahead [" " | end]]  ; look ahead for a space or the end of the string
            | skip                  ; or advance the input by one 
            ]
        ]
    ]
    s                               ; return the processed string 
]

0

Шелуха , 13 байт

wüöVËm_Ṗ3+⁰ew

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

объяснение

wüöVËm_Ṗ3+⁰ew  Inputs: list of strings L (explicit, accessed with ⁰), string S (implicit).
               For example, L = ["CASE","for"], s = "Case for a different case".
            w  Split S on spaces: ["Case","for","a","different","case"]
 ü             Remove duplicates wrt an equality predicate.
               This means that a function is called on each pair of strings,
               and if it returns a truthy value, the second one is removed.
  öVËm_Ṗ3+⁰e    The predicate. Arguments are two strings, say A = "Case", B = "case".
           e    Put A and B into a list: ["Case","case"]
         +⁰     Concatenate with L: ["CASE","for","Case","case"]
       Ṗ3       All 3-element subsets: [["CASE","for","Case"],["CASE","for","case"],
                                        ["CASE","Case","case"],["for","Case","case"]]
  öV            Does any of them satisfy this:
    Ë            All strings are equal
     m_          after converting each character to lowercase.
                In this case, ["CASE","Case","case"] satisfies the condition.
               Result: ["Case","for","a","different"]
w              Join with spaces, print implicitly.

0

Мин , 125 байт

=a () =b a 1 get =c a 0 get " " split
(:d (b d in?) ((c d in?) (d b append #b) unless) (d b append #b) if) foreach
b " " join

Входные данные находятся quotв стеке с входной строкой в ​​качестве первого элемента, а quotповторяющиеся строки - в качестве второго элемента, т.е.

("this sentence has no matches" ("ten" "cheese"))


0

AWK , 120 байт

NR%2{for(;r++<NF;)R[tolower($r)]=1}NR%2==0{for(;i++<NF;$i=$(i+s))while(R[x=tolower($(i+s))])U[x]++?++s:i++;NF-=s}NR%2==0

Попробуйте онлайн!

Часть «удалить пробелы» сделала это немного сложнее, чем я думал. Установка поля в "", удаляет поле, но оставляет дополнительный разделитель.

Ссылка TIO имеет 28 дополнительных байтов для разрешения нескольких записей.

Ввод дан более 2 строк. Первая строка - это список слов, а вторая - «предложение». Обратите внимание, что «слово» и «слово» не считаются идентичными приложенным знакам препинания. Наличие требований пунктуации, вероятно, сделает эту проблему еще более забавной .



0

Python 2 , 140 байт

from re import*
p='\s?%s'
S,A=input()
for a in A:S=sub(p%a,lambda s:s.end()==search(p%a,S,flags=I).end()and s.group()or'',S,flags=I)
print S

Попробуйте онлайн!

Объяснение:

re.sub(..)может принимать в качестве аргумента функцию вместо строки замены. Итак, у нас есть какая-то необычная лямбда. Функция вызывается для каждого экземпляра шаблона, и в эту функцию передается один объект - matchobject. Этот объект имеет информацию о найденном происшествии. Меня интересует индекс этого события, который можно получить с помощью start()или end()функции. Последний короче, поэтому он используется.

Чтобы исключить замену первого вхождения слова, я использовал другую функцию поиска по регулярному выражению, чтобы получить точно первый, а затем сравнить индексы, используя тот же end()

Флаг re.Iявляется короткой версиейre.IGNORECASES

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.