Java:比较不同顺序关键字的字符串

3

我有两个看起来像这样的字符串:

String str1 = "[0.7419,0.7710,0.2487]";
String str2 = "[\"0.7710\",\"0.7419\",\"0.2487\"]";

我希望能够比较它们并且不考虑顺序的差异使它们相等...

哪种方法最快、最简单?

我应该将每个字符串拆分成数组并且对两个数组进行比较吗?还是不需要?我想我需要去除 "[","]",""" 这些字符以使其更清晰,所以我去掉了它们。我也将“,”替换为“ ”,但我不知道这是否有帮助...

提前感谢:)

编辑:我的字符串并不总是一组双精度浮点数或浮点数。它们也可以是实际单词或一组字符。


3
您的字符串表示Set<Double>(至少您是这么说的)。因此,请将它们解析为Set<Double>,然后进行比较。 - M. Prokhorov
你的字符串里面只会有数字吗?还是可能包含其他字符? - Schidu Luca
我会将这些数字解析为Double并放入List中。然后逐个排序和比较。 - FuriousSpider
这只是我发布的一个例子。它们不一定总是双精度或浮点数,它们也可能是一组字符。但还是谢谢! - T. Kofidis
1
一个更慢但更好的解决方案是将它们作为JSONArrays读取。因为它们看起来就像这样。也许这样比较它们会更容易。 - Jack Flamp
显示剩余3条评论
5个回答

2
因为您拥有混合的结果类型,所以您需要首先将其作为混合输入处理。
以下是我如何替换它,特别是对于较长的字符串。
private Stream<String> parseStream(String in) {
    //we'll skip regex for now and can simply hard-fail bad input later
    //you can also do some sanity checks outside this method
    return Arrays.stream(in.substring(1, in.length() - 1).split(",")) //remove braces
        .map(s -> !s.startsWith("\"") ? s : s.substring(1, s.length() - 1)); //remove quotes
}

接下来,我们有一串字符串需要解析成基本类型或字符串(因为我假设我们没有某种奇怪的对象序列化):

private Object parse(String in) {
    //attempt to parse as number first. Any number can be parsed as a double/long
    try {
        return in.contains(".") ? Double.parseDouble(in) : Long.parseLong(in);
    } catch (NumberFormatException ex) {
        //it's not a number, so it's either a boolean or unparseable
        Boolean b = Boolean.parseBoolean(in); //if not a boolean, #parseBoolean is false
        b = in.toLowerCase().equals("false") && !b ? b : null; //so we map non-false to null
        return b != null ? b : in; //return either the non-null boolean or the string
    }
}

使用这个方法,我们可以将混合流转换为混合集合:
Set<Object> objs = this.parseStream(str1).map(this::parse).collect(Collectors.toSet());
Set<Object> comp = this.parseStream(str2).map(this::parse).collect(Collectors.toSet());
//we're using sets, keep in mind the nature of different collections and how they compare their elements here
if (objs.equals(comp)) {
    //we have a matching set
}

最后,一些合理性检查的例子将确保输入字符串上有适当的括号等。尽管其他人说过,但我学习了集合语法作为{a, b, ...c},序列/列表语法作为[a, b, ...c],两者在此处有不同的比较。

将解析为Long、Double或Boolean有什么优势,而不是简单地将所有内容保留为字符串? - DodgyCodeException
1
相同值的不同数字在解析为字符串时将不匹配,例如 1237.01237.00 - Rogue
说得好。下一个问题:你是不是想说 substring(1) 而不是 substring(0)?(后者只会返回 this。) - DodgyCodeException
是的,我修复了早上的那个 off-by-one 错误。没有什么比清晨修复错误更让人愉快的了。 - Rogue

1

可以通过以下方法来实现,制作一个使用TreeSet实现的字符串集合,这样排序就可以内置处理了。只需要将set的两个字符串进行简单转换,并使用equals方法进行比较。 尝试下面的代码:

String str1 = "[0.7419,0.7710,0.2487]";
        String str2 = "[\"0.7710\",\"0.7419\",\"0.2487\"]";
        String jsonArray = new JSONArray(str2).toString();
        Set<String> set1 = new TreeSet<String>(Arrays.asList(str1.replace("[", "").replace("]", "").split(",")));
        Set<String> set2 = new TreeSet<String>(Arrays.asList(jsonArray.replace("[", "").replace("]", "").replace("\"", "").split(",")));
        if(set1.equals(set2)){
             System.out.println(" str1 and str2 are equal");
       }

在上面的代码中,我借助了 jsonArray 来删除"\"字符。 注意:

但是,如果一个字符串中有重复元素并且另一个字符串中的数字不同,则此方法将不起作用,因为集合不会保留重复项。

尝试使用保留重复元素的列表来解决问题。
String str1 = "[0.7419,0.7710,0.2487]";
            String str2 = "[\"0.7710\",\"0.7419\",\"0.2487\"]";
            String jsonArray = new JSONArray(str2).toString();
            List<String> list1=new ArrayList<String>(Arrays.asList(str1.replace("[", "").replace("]", "").split(",")));
            List<String> list2=new ArrayList<String>(Arrays.asList(jsonArray.replace("[", "").replace("]", "").replace("\"", "").split(",")));
            Collections.sort(list1);
            Collections.sort(list2);
            if(list1.equals(list2)){
                  System.out.println("str1 and str2 are equal");
            }

如果在从set1中移除元素后,set2为空,这并不意味着它们相等。这可能意味着set2set1的子集。应该使用set1.equals(set2)来判断它们是否相等。 - DodgyCodeException
1
如果从集合2中删除了集合1的所有元素,那么如果集合2变为空,则它们相等,否则不相等。 - Raju Sharma
1
如果set2{1, 2, 3},而set1{1, 2, 3, 4},那么从set2中移除set1将会得到一个空集合。但它们仍然不相等。 - Rogue

0

这是一个使用 HashSet 的相当简单的解决方案。

Set 的好处:

  • 它不能包含重复项。
  • 元素的插入/删除是O(1)。
  • 比数组快得多。在这里,保持元素顺序也不重要,所以没关系。

    String str1 = "[0.7419,0.7710,0.2487]";
    String str2 = "[\"0.7710\",\"0.7419\",\"0.2487\"]";
    
    Set<String> set1 = new HashSet<>();
    Set<String> set2 = new HashSet<>();
    
    String[] split1 = str1.replace("[", "").replace("]", "").split(",");
    String[] split2 = str2.replace("[", "").replace("]", "").replace("\"", "").split(",");
    set1.addAll(Arrays.asList(split1));
    set2.addAll(Arrays.asList(split2));
    
    System.out.println("set1: "+set1);
    System.out.println("set2: "+set2);
    
    boolean isEqual = false;
    if(set1.size() == set2.size()){
        set1.removeAll(set2);
        if(set1.size() ==0){
            isEqual = true;
        }
    }
    
    System.out.println("str1 and str2 "+( isEqual ? "Equal" : "Not Equal") );
    

输出:

set1: [0.7710, 0.2487, 0.7419]
set2: [0.7710, 0.2487, 0.7419]
str1 and str2 Equal

或者只需对较长的字符串进行子串处理,然后拆分一次。不需要用迭代的方式实现。 - Rogue
equals方法在HashSet中效率低下,不要使用它。一旦您看到AbstractSet中的实现,希望您能理解。不建议使用Java的equals和hashcode方法。 - nagendra547
2
@nagendra547,那个建议不仅是错误的,而且很危险。#equals/#hashcode 是语言的关键构造。#equals(对于 HashSet)将使用所有这些比较(例如大小),然后是 #containsAll,它将在传递的集合元素上使用 HashSetO(1) 包含方法(而不是父级)。使用 #removeAll 将迭代输入集合并调用 HashSet 上的(同样也是)O(1) #remove 方法。所以就你所说的“低效”的论点而言,那只是一派空话。你只是在重复发明轮子。 - Rogue
1
@nagendra547,那个SO问题与你手头的评论无关,你的评论是在集合本身上使用#equals(而不是将可哈希性/相等性实现到对象中)。 - Rogue
1
是的,我做到了,因为我在上面的评论中文字面上描述了它的工作原理,你有什么理由吗?你认为什么地方“低效”/慢?因为从我所看到的,你并没有节省任何效率,却要付出不必要修改集合、重新发明轮子和增加代码的代价。我会称之为过早优化,但我认为在这种情况下甚至没有优化。 - Rogue
显示剩余9条评论

0

像这样:

    String[] a1 = str1.replaceAll("^\\[|\\]$", "").split(",", -1);
    String[] a2 = str2.replaceAll("^\\[|\\]$", "").split(",", -1);
    for (int i = 0; i < a2.length; i++)
        a2[i] = a2[i].replaceAll("^\\\"|\\\"$", "");
    Arrays.sort(a1);
    Arrays.sort(a2);
    boolean stringsAreEqual = Arrays.equals(a1, a2);

或者您可以使用完全功能的方法(这可能会略微降低效率):

    boolean stringsAreEqual = Arrays.equals(
            Arrays.stream(str1.replaceAll("^\\[|\\]$", "").split(",", -1))
                    .sorted()
                    .toArray(),
            Arrays.stream(str2.replaceAll("^\\[|\\]$", "").split(",", -1))
                    .map(s -> s.replaceAll("^\\\"|\\\"$", ""))
                    .sorted()
                    .toArray()
    );

使用数组而不是使用集合(如其他人所提出的)的优点在于,数组通常使用更少的内存,并且它们可以容纳重复项。如果您的问题域可以包含每个字符串中的重复元素,则无法使用集合。

对于大量元素,这是一种低效的解决方案。 当您只需要检查两者的相等性时,您正在对两个数组进行排序。 - nagendra547
1
顺便提一下,如果使用 java.util.HashSet 由于无法处理重复项而成为问题,可以将其替换为 Google Guava HashMultiset:http://google.github.io/guava/releases/22.0/api/docs/com/google/common/collect/HashMultiset.html。 - yegodm

0

Google GSON可以通过将值读取为Set<String>来很好地处理这个任务:

    final String str1 = "[0.7419,0.7710,0.2487]";
    final String str2 = "[\"0.7710\",\"0.7419\",\"0.2487\"]";
    final String str3 = "[\"0.3310\",\"0.7419\",\"0.2487\"]";
    final Gson gson = new Gson();
    final Type setOfStrings = new TypeToken<Set<String>>() {}.getType();
    final Set<String> set1 = gson.fromJson(str1, setOfStrings);
    final Set<String> set2 = gson.fromJson(str2, setOfStrings);
    final Set<String> set3 = gson.fromJson(str3, setOfStrings);

    System.out.println("Set #1:" + set1);
    System.out.println("Set #2:" + set2);
    System.out.println("Set #3:" + set3);
    System.out.println("Set #1 is equivalent to Set #2: " + set1.equals(set2));
    System.out.println("Set #1 is equivalent to Set #3: " + set1.equals(set3));

输出结果为:

Set #1:[0.7419, 0.7710, 0.2487]
Set #2:[0.7710, 0.7419, 0.2487]
Set #3:[0.3310, 0.7419, 0.2487]
Set #1 is equivalent to Set #2: true
Set #1 is equivalent to Set #3: false

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