【问题标题】:Comparing elements from two lists比较两个列表中的元素
【发布时间】:2019-12-07 05:32:37
【问题描述】:

我从两家不同的书店获得了 2 个书名列表。这些标题可以相同,但写法不同,例如“例如”-“例如”,正如您所见,它们是相等的,但根本不相等。

这就是为什么我编写了流,它将从列表中净化元素(它将删除空格和特殊字母)并使它们相等,因此在流之后两者看起来都像“forexmaple”,所以它们现在是相等的。

private List<String> purifyListOfTitles(List<Book> listToPurify) {
        return listToPurify
                .stream()
                .map(Book::getTitle)
                .map(title -> title.replaceAll("[^A-Za-z]+", ""))
                .collect(Collectors.toList());
    }

问题是...我想获得一张地图,其中包含原始标题和书籍出现次数(最多出现 2 次,默认为 1)。 我编写了比较两个标题的算法,并将第一个书店的标题添加到地图,但我必须从第二个添加,但不知道如何获得这个标题。

说清楚……

我将第一家书店的书名与第二家书店的每个书名进行比较,如果相等,则添加 +1,如果 for 循环结束,我将添加第一家书店的迭代标题和出现次数.但是第二家书店的书名只有一次出现呢?我知道第一家书店的迭代标题索引,因此我可以使用.get(i) 方法从原始列表(带有未纯化的标题)中获取该标题,但我不知道第二家书店的迭代标题索引以获取原始标题。

我看到的唯一解决方案是,首先将 tite 与第二个书店的每个书名进行比较,然后将书名与第一个书店的每个书名进行比较,但这不是最佳解决方案......或者以某种方式取消了列表。

总而言之,我只有第一家书店的地图标题,如何添加第二家书店的标题被省略。 我想在地图中有原始标题(例如,纯化的是 houseisbig,但原始的是 House - is big)!我正在与纯化列表进行比较并添加原始标题。

班级:

package bookstore.scraper.rankingsystem;

import bookstore.scraper.Bookstore;
import bookstore.scraper.book.Book;
import bookstore.scraper.book.scrapingtypeservice.CategorizedBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toMap;

@Slf4j
@Component
public class CategorizedBooksRankingService {

    private final CategorizedBookService categorizedBookService;

    @Autowired
    public CategorizedBooksRankingService(CategorizedBookService categorizedBookService) {
        this.categorizedBookService = categorizedBookService;
    }

    public Map<String, Integer> getRankingForCategory(String category) {
        Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = chooseGetterImplementationByCategory(category);

        List<Book> merlinBooks = bookstoreWith15CategorizedBooks.get(Bookstore.MERLIN);
        List<Book> empikBooks = bookstoreWith15CategorizedBooks.get(Bookstore.EMPIK);

        List<String> purifiedMerlinBookTitles = purifyListOfTitles(merlinBooks);
        List<String> purifiedEmpikBookTitles = purifyListOfTitles(empikBooks);

        Map<String, Integer> bookTitleWithOccurrencesNumber =
                prepareTitleAndOccurrencesMap(merlinBooks, empikBooks, purifiedMerlinBookTitles, purifiedEmpikBookTitles);

        return getSortedLinkedHashMappedByValue(bookTitleWithOccurrencesNumber);
    }

    private Map<String, Integer> prepareTitleAndOccurrencesMap(List<Book> merlinBooks, List<Book> empikBooks, List<String> purifiedMerlinBookTitles, List<String> purifiedEmpikBookTitles) {
        Map<String, Integer> bookTitleWithOccurrencesNumber = new LinkedHashMap<>();

        int occurrencesOfIteratedBook;
        String iteratedMerlinTitle;

        for (int i = 0; i < purifiedMerlinBookTitles.size(); i++) {
            occurrencesOfIteratedBook = 1;
            iteratedMerlinTitle = purifiedMerlinBookTitles.get(i);
            for (String iteratedEmpikTitle : purifiedEmpikBookTitles) {

                if (iteratedMerlinTitle.equals(iteratedEmpikTitle))
                    occurrencesOfIteratedBook++;
            }
            bookTitleWithOccurrencesNumber.put(merlinBooks.get(i).getTitle(), occurrencesOfIteratedBook);
            //how to add to bookTitleWithOccurrencesNumber map book titles from second bookstore that are not equal to any of title
        }
        return bookTitleWithOccurrencesNumber;
    }

    private List<String> purifyListOfTitles(List<Book> listToPurify) {
        return listToPurify
                .stream()
                .map(Book::getTitle)
                .map(title -> title.replaceAll("[^A-Za-z]+", ""))
                .collect(Collectors.toList());
    }

    private Map<String, Integer> getSortedLinkedHashMappedByValue(Map<String, Integer> mapToSort) {
        return mapToSort.entrySet()
                .stream()
                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .collect(
                        toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2,
                                LinkedHashMap::new));
    }

    private Map<Bookstore, List<Book>> chooseGetterImplementationByCategory(String category) {
        if (category.equals("crimes"))
            return categorizedBookService.get15BooksFromCrimeCategory();
        if (category.equals("romances"))
            return categorizedBookService.get15BooksFromRomanceCategory();
        if (category.equals("fantasies"))
            return categorizedBookService.get15BooksFromFantasyCategory();
        if (category.equals("guides"))
            return categorizedBookService.get15BooksFromGuidesCategory();
        if (category.equals("biographies"))
            return categorizedBookService.get15BooksFromBiographiesCategory();
        else {
            log.error(category + " is invalid category");
            throw new IllegalArgumentException();
        }
    }
}

例子:

Book a = new Book.BookBuilder().withTitle("To - jest haha").build();
        Book b = new Book.BookBuilder().withTitle("Bubu").build();
        Book c = new Book.BookBuilder().withTitle("Kiki").build();
        Book d = new Book.BookBuilder().withTitle("sza . la").build();

        Book e = new Book.BookBuilder().withTitle("Tojest haha").build();
        Book f = new Book.BookBuilder().withTitle("bam").build();
        Book g = new Book.BookBuilder().withTitle("zzz").build();
        Book h = new Book.BookBuilder().withTitle("szaLa").build();


        List<Book> list1 = new ArrayList<>();
        list1.add(a);
        list1.add(b);
        list1.add(c);
        list1.add(d);

        List<Book> list2 = new ArrayList<>();
        list2.add(e);
        list2.add(f);
        list2.add(g);
        list2.add(h);

        Map<String,Long> z = countBooksByTitle(list1,list2);

z map 包含:{sza . la =2, Bubu=1, zzz=1, Kiki=1, bam=1, To - jest haha =2}

【问题讨论】:

  • 也许使用Set.removeAll 来计算两组之间的差异。
  • @IronMan 你能说明一下吗? Tbh,我考虑了好几个小时,但我看不到最简单的解决方案:/

标签: java algorithm list dictionary


【解决方案1】:

我有 2 个列表
...
我想获得一张包含书名和出现次数的地图

你可以这样做是一个单一的流链:

private Map<String, Long> countBooksByTitle(List<Book> list1, List<Book> list2) {
    return Stream.concat(list1.stream(), list2.stream())
            .map(book -> book.getTitle().replaceAll("[^A-Za-z]+", ""))
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

请注意,如果列表中有两本或多本不同的书且标题映射到相同的紧凑文本,则计数理论上可能高于 2。例如。因为你只保留字母,Streams for dummies 1Streams for dummies 2 将被视为 2 本书,标题为 Streamsfordummies


更新

要保留原始标题,请创建一个通过纯化标题进行比较但保留原始标题的帮助类,然后首先使用该类构建地图,然后将其解包到原始标题。

在下面的代码中,净化已被修改为也保留数字,并在保留字母的同时消除重音,例如 -> be,而问题代码将消除字母 -> b。这样 就不会比较相等。

由于计数代码无论如何都在映射键/值对,因此值也从Long 映射到Integer,只是为了表明可以做到。生成的地图也已修改为按标题排序。

助手类

public final class PurifiedTitle implements Comparable<PurifiedTitle> {
    private final String original;
    private final String purified;
    public PurifiedTitle(String title) {
        this.original = title;
        // Purified string has only lowercase letters and digits,
        // with no accents on the letters
        this.purified = Normalizer.normalize(title, Normalizer.Form.NFD)
                .replaceAll("\\P{Alnum}+", "")
                .toLowerCase(Locale.US);
    }
    @Override
    public String toString() {
        return this.original;
    }
    @Override
    public int compareTo(PurifiedTitle that) {
        return this.purified.compareTo(that.purified);
    }
    @Override
    public boolean equals(Object obj) {
        if (! (obj instanceof PurifiedTitle))
            return false;
        PurifiedTitle that = (PurifiedTitle) obj;
        return this.purified.equals(that.purified);
    }
    @Override
    public int hashCode() {
        return this.purified.hashCode();
    }
}

更新计数方法

private static Map<String, Integer> countBooksByTitle(List<Book> list1, List<Book> list2) {
    Collator collator = Collator.getInstance(Locale.US);
    collator.setStrength(Collator.PRIMARY);
    return Stream.concat(list1.stream(), list2.stream())
            .collect(Collectors.groupingBy(book -> new PurifiedTitle(book.getTitle()),
                                           Collectors.counting()))
            .entrySet().stream()
            .collect(Collectors.toMap(e -> e.getKey().toString(),
                                      e -> e.getValue().intValue(),
                                      Integer::sum,
                                      () -> new TreeMap<>(collator)));
}

测试

List<Book> list1 = Arrays.asList(
        new Book("To - jest haha"),
        new Book("Bubû"),
        new Book("Kiki"),
        new Book("bam 2"),
        new Book("sza . lä"));
List<Book> list2 = Arrays.asList(
        new Book("Tojest haha"),
        new Book("bam 1"),
        new Book("zzz"),
        new Book("száLa"));
System.out.println(countBooksByTitle(list1, list2));

输出

{bam 1=1, bam 2=1, Bubû=1, Kiki=1, sza . lä=2, To - jest haha=2, zzz=1}

【讨论】:

  • 这不是我想要的。现在在地图中我已经净化了标题,但我想要原件,但没有说清楚可能是我的错。已编辑的问题。
  • 您知道是否有解决方案吗?我是在与纯化列表进行比较并添加原始标题?
  • 我想省略转换为整数,所以我做了:pastebin.com/eWpUmAk6 但它将 book.getTitle() 标记为未解决。我做错了什么?
  • idk,但是方法引用有效,可能是 intellij 的一些错误
【解决方案2】:

对您的算法影响最小的可能解决方案:只要它们与第一个列表中的标题匹配,您就可以从第二个列表中删除它们。

通过这样做,第二个列表将在 for 循环之后仅包含不匹配的书。 然后你可以将它们全部添加到 map 中,出现 = 1。

您应该使用迭代器来浏览列表和删除项目。

    for (int i = 0; i < purifiedMerlinBookTitles.size(); i++) {
        occurrencesOfIteratedBook = 1;
        iteratedMerlinTitle = purifiedMerlinBookTitles.get(i);
        Iterator<String> it = purifiedEmpikBookTitles.iterator();
        while (it.hasNext()) {
            String iteratedEmpikTitle = it.next();
            if (iteratedMerlinTitle.equals(iteratedEmpikTitle)) {
                occurrencesOfIteratedBook++;
                it.remove();
            }
        }
        bookTitleWithOccurrencesNumber.put(merlinBooks.get(i).getTitle(), occurrencesOfIteratedBook);
    }
    // At this time purifiedEmpikBookTitles contains only unmatched titles
    purifiedEmpikBookTitles.forEach(title -> bookTitleWithOccurrencesNumber.put(title, 1));
    return bookTitleWithOccurrencesNumber;
}

【讨论】:

  • 与@Andreas 相同。我想像在这里一样拥有原始标题bookTitleWithOccurrencesNumber.put(merlinBooks.get(i).getTitle(), occurrencesOfIteratedBook); }
猜你喜欢
  • 2020-07-04
  • 1970-01-01
  • 2015-11-06
  • 2014-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多