从字符串中删除最后一次出现

5

我正在尝试将字符串截断到单个以逗号分隔的单词字符串中特定单词的第一次出现。例如:

deleteLastOccurrence("foo,bar,dog,cat,dog,bird","dog")

应该返回

"foo,bar,dog"

我有以下代码,但似乎无法正常工作:
public String deleteLastOccurrence(String original, String target){
    String[] arr = original.split(",");
    arr = Arrays.copyOfRange(arr, Arrays.asList(arr).indexOf(target), original.length()-1);
    path = StringUtils.join(pathArray,",");
}

有没有更简单的方法?提前感谢您的回答...

什么是“路径”?你的方法为什么“不”工作?(它对你输入的内容输出了什么结果?) - sarnold
我很难理解这个方法应该做什么。它是删除第二个参数的第一个实例后的所有内容吗?如果我在别人的代码中读到了你对“deleteLastOccurrance”的示例用法,我肯定不会期望它返回“foo,bar,dog”。相反,我会期望得到“foo,bar,dog,cat,bird”。 - Christopher Schultz
使用String.indexOf和String.substring的组合就足够了。 - dragon66
7个回答

7

使用正则表达式替换:

public static String deleteLastOccurrence(String original, String target){
    return original.replaceAll("(,)?\\b" + target + "\\b.*", "$1" + target);
}

这段代码也适用于目标单词是原始文本中的第一个或最后一个单词(因此正则表达式语法\b表示“单词边界”)。
另外,将您的方法重命名为deleteAfterFirstOccurrence(),因为您当前的名称具有误导性:与您想要的无关的是“最后一次出现”。
这是一个小测试:
public static void main(String[] args) {
    // Test for target in middle:
    System.out.println(deleteLastOccurrence("foo,bar,dog,cat,dog,bird,dog", "dog"));
    // Test for target at start:
    System.out.println(deleteLastOccurrence("dog,bar,dog,cat,dog,bird,dog", "dog"));
    // Test for target at end:
    System.out.println(deleteLastOccurrence("foo,bar,cat,bird,dog", "dog"));
}

输出:

foo,bar,dog
dog
foo,bar,cat,bird,dog

这不会移除所有的出现吗?而不仅仅是最后一次吗?(我的 Java 有点生疏) - Wolph
@WoLpH 这将修剪整个字符串,因为末尾有一个 .* - Bohemian
很好,我刚发完帖子就看到了新的编辑,解决了问题。 :) - christurnerio
@WoLpH:是的,看起来更像一个trimAfterOccurrance函数。 :) - christurnerio
@Bohemian,您是正则表达式方面的专家。而我从未在正则表达式中使用过括号“()”。 - The Original Android
显示剩余5条评论

2

更新:仔细查看问题后,我意识到我写的是方法名,而不是OP想要的结果。所以,它只是去掉了最后一次出现,而不是在它之后修剪。哦,好吧!:)

根据您的风格,您可能认为这并不更简单。但是,这是一个有趣的问题。我认为这段代码更清晰一些。

public class ReplaceLast {

public String deleteLastOccurrence(String fromThis, String word){
    int wordLength = word.length();
    if(fromThis.startsWith(word + ",")){
        return fromThis.substring(wordLength + 1);
    }
    if(fromThis.endsWith("," + word)){
        return fromThis.substring(0, fromThis.length() - wordLength - 1);
    }
    int index = fromThis.lastIndexOf("," + word + ",");
    if(index == -1){
        return fromThis;
    }
    return fromThis.substring(0, index) + fromThis.substring(index+word.length() + 1);
}
@Test
public void testNotThere() {
    String actual = deleteLastOccurrence("foo,bar,dog,cat,dog,bird","moose");
    assertEquals("foo,bar,dog,cat,dog,bird", actual);
}
@Test
public void testMiddle() {
    String actual = deleteLastOccurrence("foo,bar,dog,cat,dog,bird","dog");
    assertEquals("foo,bar,dog,cat,bird", actual);
}

@Test
public void testFirst() {
    String actual = deleteLastOccurrence("foo,bar,dog,cat,dog,bird","foo");
    assertEquals("bar,dog,cat,dog,bird", actual);
}

@Test
public void testLast() {
    String actual = deleteLastOccurrence("foo,bar,dog,cat,dog,bird","bird");
    assertEquals("foo,bar,dog,cat,dog", actual);
}

@Test
public void testSubword() {
    String actual = deleteLastOccurrence("foo,bar,dog,cat,dog,bird","bir");
    assertEquals("foo,bar,dog,cat,dog,bird", actual);
}
}

我相信这段代码将会在单词为"do"(而不是"dog")时修剪字符串。这是我认为的错误行为。(尽管OP有点不清楚 - 这就是你需要回去检查需求的地方。 :-)) - user949300
1
发现 @coreyhaines 正在练习Java是无价的 :) - gicappa

1

我尝试解决截取字符串中第一个特定单词的问题,而我并不关心原方法的名称(deleteLastOccurrence),因为我认为这个名称会误导人。

对于我来说,匹配单个单词而不是子单词的技巧是在句子前后添加两个逗号,然后使用带有逗号的单词进行检查。

例如,",dog," 将与 ",foo,bar,dog,cat,dog,bird," 进行匹配以检查其是否存在。

package gicappa;

public class So {
    public static String trimSentenceOnFirstOccurrenceOf(String sentence, String word) {
        if (word.isEmpty()) return sentence;

        if (!addCommasAround(sentence).contains(addCommasAround(word))) return sentence;

        return trimAddedCommasOf(substringOfSentenceUntilEndOfWord(addCommasAround(sentence), addCommasAround(word)));
    }

    public static String substringOfSentenceUntilEndOfWord(String string, String word) {
        return string.substring(0, string.indexOf(word) + word.length());
    }

    public static String trimAddedCommasOf(String string) {return string.substring(1,string.length()-1);}

    public static String addCommasAround(String s) {return "," + s + ","; }
}

如果您想进行一些测试,我用于TDD的方法如下:

package gicappa;

import org.junit.Test;

import static gicappa.So.trimSentenceOnFirstOccurrenceOf;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

public class SoTest {
    @Test
    public void it_returns_the_same_sentence_for_empty_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("foo,bar,dog,cat,dog,bird", ""), is(equalTo("foo,bar,dog,cat,dog,bird")));
    }

    @Test
    public void it_returns_the_same_sentence_for_not_contained_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("foo,bar,dog,cat,dog,bird", "s"), is(equalTo("foo,bar,dog,cat,dog,bird")));
    }

    @Test
    public void it_returns_the_first_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("foo,bar,dog,cat,dog,bird", "foo"), is(equalTo("foo")));
    }

    @Test
    public void it_returns_the_same_sentence_if_is_matched_the_last_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("foo,bar,dog,cat,dog,bird", "bird"), is(equalTo("foo,bar,dog,cat,dog,bird")));
    }

    @Test
    public void it_trims_after_the_end_of_the_first_matched_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("foo,bar,dog,cat,dog,bird", "dog"), is(equalTo("foo,bar,dog")));
    }

    @Test
    public void it_does_not_trim_for_a_subword_of_a_contained_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("foo,bar,dog,cat,dog,bird", "do"), is(equalTo("foo,bar,dog,cat,dog,bird")));
    }

    @Test
    public void it_does_not_trim_for_a_subword_of_an_already_contained_word() {
        assertThat(trimSentenceOnFirstOccurrenceOf("dog,foozzo,foo,cat,dog,bird", "foo"), is(equalTo("dog,foozzo,foo")));
    }
}

一个更面向对象的类的冗长重构也可以是:
package gicappa;

public class Sentence {
    private String s;

    public Sentence(String sentence) {
        this.s = sentence;
    }

    public String trimOnFirstOccurrenceOf(String word) {
        if (word.isEmpty() || csvSentenceContainsWord(word)) return s;

        return substringSentenceToEndOf(word);
    }

    private String substringSentenceToEndOf(String word) {
        return addCommasTo(s).substring(1, addCommasTo(s).indexOf(addCommasTo(word)) + addCommasTo(word).length()-1);
    }

    private boolean csvSentenceContainsWord(String word) {
        return !addCommasTo(s).contains(addCommasTo(word));
    }

    public static String addCommasTo(String s) {return "," + s + ",";}
}

使用方式如下:

new Sentence("dog,foozzo,foo,cat,dog,bird").trimOnFirstOccurrenceOf("foo"), is(equalTo("dog,foozzo,foo"))

1
我认为你必须返回 sentence.substring(0, sentence.indexOf(addCommasTo(word)) + word.length()); 否则你将匹配 "dog,foozzo,foo,cat,dog,bird" 中的 foo 与 "dog,foo" 匹配,而实际上你应该返回 dog,foozzo,foo。 - Andrea Parodi
是的,你说得对,但我需要更深入地理解它。谢谢。 - gicappa

0
这样怎么样:
public String deleteLastOccurrence(String original, String target){
    return original.replace("(^|,)" + target + "(,|$)", "");
}

我相信这将替换它们全部,不是吗?或者至少不是最后一个。 - coreyhaines
@coreyhaines:我希望 replace 函数只替换一个字符,而 replaceAll 函数则替换所有匹配项 :) - Wolph

0
这里尝试一个非正则表达式版本:
public String trimTo(String in, String matchNoCommas) {
   if (in.startsWith(matchNoCommas + ","))  // special check here...
      return matchNoCommas;
   int idx = in.indexOf("," + matchNoCommas+ ",");
   if (idx < 0)
      return in;
   return in.substring(0, idx + matchNoCommas.length()+1);
}

提供与@Bohemian的正则表达式版本相同的结果。由您决定哪个更易于理解。


0

也许我错了,但这样做不行吗?

public trimCommaSeparatedListToIncludeFirstOccurrenceOfWord(String listOfWords, String wordToMatch) {
    int startOfFirstOccurrenceOfWordToMatch = listOfWords.indexOf(wordToMatch);
    int endOfFirstOccurrenceOfWordToMatch = startOfFirstOccurrenceOfWordToMatch + wordToMatch.length() - 1;

    return listOfWords.substring(0, endOfFirstOccurrenceOfWordToMatch);
}

现在这可能不是原帖作者想要的,但我认为这就是原帖作者所要求的。例如:f("doggy,cat,bird", "dog")将返回"dog"

对于全词匹配,我会像其他人建议的那样使用正则表达式。


我理解你追求简单的想法,但是你至少应该检查一下不存在的单词情况,以避免错误的响应。可以这样写:"if (!listOfWords.contains(wordToMatch)) return listOfWords;" - gicappa
我认为在你的例子中,OP希望代码返回"doggy,cat,bird", "dog"。他想要搜索整个单词的出现。 - Andrea Parodi
啊,没错,@gicappa:每次我不写测试的时候,我都会错过一个简单的边界情况。 - J. B. Rainsberger

0

gonzoc0ding,在阅读所有回复后,我认为你的方法更简单更清晰,除了这一点需要修正:

public String deleteLastOccurrence(String original, String target){
    String[] arr = original.split(",");
    arr = Arrays.copyOfRange(arr,0, Arrays.asList(arr).indexOf(target));
    path = StringUtils.join(arr,",");
}

但也许我还没有理解您的要求...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接