计算Java中列表中每个唯一元素的关键字密度?

2023-12-13

我可以使用以下代码计算文本正文的关键字密度:

HashMap<String, Integer> frequencies = new HashMap<String, Integer>();

String[] splitTextArr = StringX.splitStrIntoWordsRtrnArr(text);

int articleWordCount = splitTextArr.length;

for (String splitText : splitTextArr) {


 if (frequencies.containsKey(splitText)) {

    Integer previousInt = frequencies.get(splitText);
    Integer newInt = ++previousInt;
    frequencies.put(splitText, newInt);
    } else {

    frequencies.put(splitText, 1);
    }
}

然后,我使用以下调用按出现次数最多的关键字到最少出现的关键字的顺序对关键字密度列表进行排序:

    Map<String, Integer> sortedMap = new LinkedHashMapX<String, Integer>().sortByValueInDescendingOrder(frequencies);

上面的代码按预期工作,但是我现在必须实现一个独特的要求。

SUMMARY:给定一个标题条目列表,我需要提取足够的关键字,以便列表中的每个标题都由一个关键字表示,然后计算该关键字表示的标题总数(例如见下文)。

EXAMPLE:假设我有以下 5 个标题,其形式为LinkedHashMap<String, String>:

title1: canon photo printer 
title2: canon camera canon
title3: wireless mouse
title4: wireless charger
title5: mouse trap

哪里的KEY代表titleID (i.e, title1, title2等)和VALUE代表实际的标题。

每个关键字的原始出现次数(按降序排列,大小写不敏感的)如下:

佳能:2 |鼠标:2 |无线:2 |相机:1 |充电器: 1 |照片:1 |打印机: 1 |陷阱:1

Note:每个标题每个关键字仅计数一次。所以虽然关键字canon出现 3 次,因为它在同一标题中出现两次(即title2) 只计算一次。

在之前的map, 关键字canon是出现在两个title1 & title2。由于每个标题都需要用一个关键字来表示,因此两个标题都可以用该关键字来表示canon。没有必要包含其他关键字title1 and title2(例如:photo, printer and camera),因为每个标题应仅由一个关键字表示(不多也不少)。尽管我们可以选择代表技术title1 and title2使用关键词photo and camera (or printer and camera) - 因为这会增加表示所有标题所需的关键字总数,所以这是不希望的。换句话说,我们希望用尽可能少的关键字来表示所有标题.

重要的部分是提取能够一次性“代表”列表中所有标题的最少数量的关键字,并跟踪每个关键字链接到的标题数量以及titleID。如果我们有一个包含 100 个标题的列表,而不是 5 个标题,其中关键字photo出现了 95 次(即比关键字出现的次数更多)canon) 关键字photo将用于替换关键字canon and title2将由关键字表示camera.

如果两个或多个关键字可以代表相同数量的标题,我们将按字母顺序选择第一个。因此,关键字mouse将用于表示标题title3 and title5, 而不是关键字wireless。类似地,表示title4关键字charger将被使用,因为字母 C 在字母表中位于字母 W 之前(即使关键字wireless出现两次并且关键字charger此后仅出现一次title3其中包含关键字wireless已经由关键字表示mouse而不是通过关键字wireless因此,当两个关键字代表相同数量的标题时,我们恢复使用字母表中的第一个关键字)

在前面 5 个标题的示例中,预期输出将具有以下格式:

LinkedHashMap<String, LinkedHashMap<Integer, ArrayList<String>> map = new LinkedHashMap<String, LinkedHashMap<Integer, ArrayList<String>>();

Where String代表关键字(例如canon or mouse), Integer表示由该关键字表示的唯一标题的数量(佳能 = 2、鼠标 = 2、充电器 = 1) and ArrayList<String>是一个列表titleIDs链接到该关键字。例如,关键字canon将链接到标题:title1 and title2。关键词mouse将链接到标题:title3 and title5和关键字charger将链接到标题:title4.

Note:wireless = 0,trap = 0,因此从最终结果中省略。

实现这一目标最有效的方法是什么?

Thanks


基本上,您需要创建某种数据结构,在其中需要跟踪关键字、它链接到的标题数量以及它关联的标题 ID;像这样的东西:

[canon, {2, [ title1, title2 ] } ]

如果是这种情况,那么这很糟糕,至少有两个原因:

  1. 使用计数作为键是不好的,因为计数会对其他元素重复
  2. 您可以通过返回给定关键字的标题 ID 列表的大小或长度来简单地计算计数。

我提出的解决方案有两个方面:使用类来捕获关键字和标题 ID 映射,然后使用一组这些映射对象。

班上

public class KeywordMapping implements Comparable<KeywordMapping> {
    
    private final String keyword;
    private TreeSet<String> titleIds = new TreeSet<String>();
    private int frequency;
    
    public KeywordMapping(String keyword, String titleId) {
        this.keyword = keyword;
        titleIds.add(titleId);
    }
    
    public String getKeyword () {
        return keyword;
    }
    
    public int getTitleCount () {
        return titleIds.size();
    }
    
    public void addTitleId (String titleId) {
        titleIds.add(titleId);
    }
    
    public String getFirstTitleId () {
        return titleIds.first();
    }
    
    public void incrementFrequency(String keyword) {
        if (this.keyword.equals(keyword)) {
            frequency++;            
        }
    }

    public int getFrequency() {
        return frequency;
    }

    public boolean containsTitle(String titleId) {
        return titleIds.contains(titleId);
    }

    @Override
    public int hashCode () {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( (keyword == null) ? 0 : keyword.hashCode());
        result = prime * result + ( (titleIds == null) ? 0 : titleIds.hashCode());
        return result;
    }

    @Override
    public boolean equals (Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        KeywordMapping other = (KeywordMapping) obj;
        if (keyword == null) {
            if (other.keyword != null) return false;
        } else if (!keyword.equals(other.keyword)) return false;
        if (titleIds == null) {
            if (other.titleIds != null) return false;
        } else if (!titleIds.equals(other.titleIds)) return false;
        return true;
    }

    @Override
    public String toString () {
        return "KeywordMapping [keyword=" + keyword + ", titleIds=" + titleIds + ", frequency=" + frequency
            + "]\n";
    }

    @Override
    public int compareTo (KeywordMapping o) {
        return COMPARATOR.compare(this, o);
    }

    private static final Comparator<KeywordMapping> COMPARATOR =
        Comparator.comparing( (KeywordMapping keyMapping) -> keyMapping.keyword);
}

这个解决方案是可以的。我宁愿使用生成器模式来构建这个类,并使这个类不可变,这样只有当所有标题 ID 都添加到该类中时,才会构建该类的每个实例。不过,让我解释一下这个实现的一些细节。我用了一个TreeSet存储标题 ID,以便它们按自然顺序(按字母顺序)排列。这对于存储标题 ID 来说并不重要,但稍后会很重要。最后,该类必须实现Comparable<T>如果您需要将这些对象插入可排序的集合中。

关键字映射的 TreeMap

TreeMap<String, KeywordMapping> mappings = new TreeMap<>();

使用树形图对于按键按自然顺序排列条目是必要的。

我创建了这个简单的 main 方法来演示这个实现

public static void main (String[] args) {
    List<String> titles = List.of("title2: canon camera canon",
        "title3: wireless mouse",
        "title1: canon photo printer",
        "title4: wireless charger", "title5: mouse trap");
    
    MasterMap mappings = new MasterMap(); // A class wrapping TreeMap<String, KeywordMapping> mappings (the code is shown later on)
    
    for (String title : titles) {
        String[] tokens = title.split(": ");
        Set<String> keywords = new HashSet<>(Arrays.asList(tokens[1].split(" ")));
        for (String keyword : keywords) {
            KeywordMapping km = mappings.get(keyword); // don't create duplicate keyword mapping objects
            if (km == null) {
                km = new KeywordMapping(keyword, tokens[0]);
            }
            km.addTitleId(tokens[0]);
            km.incrementFrequency(keyword);
            mappings.put(keyword, km); // update existing or new keyword mapping entry
        }
    }
    System.out.println(mappings);
    System.out.println("First entry: " + mappings.firstMapping());
    System.out.println("First title in first entry: " + mappings.firstMapping().getValue().getFirstTitleId());
}

该程序的输出是:

camera=KeywordMapping [keyword=camera, titleIds=[title2], frequency=1]
canon=KeywordMapping [keyword=canon, titleIds=[title1, title2], frequency=2]
charger=KeywordMapping [keyword=charger, titleIds=[title4], frequency=1]
mouse=KeywordMapping [keyword=mouse, titleIds=[title3, title5], frequency=2]
photo=KeywordMapping [keyword=photo, titleIds=[title1], frequency=1]
printer=KeywordMapping [keyword=printer, titleIds=[title1], frequency=1]
trap=KeywordMapping [keyword=trap, titleIds=[title5], frequency=1]
wireless=KeywordMapping [keyword=wireless, titleIds=[title3, title4], frequency=2]

First entry: camera=KeywordMapping [keyword=camera, titleIds=[title2], frequency=1]
First title in first entry: title2

请注意,即使原始标题列表不是按自然顺序排列的,在插入到KeywordMapping对象,支持标题 ID 列表的集合按顺序排列它们。这与打印树图中条目的顺序相同。

最后,每个标题的频率计数仅增加一次,因为标题 ID 列表由Set而不是一个ListJava 中的集合不允许重复。因此,对于像这样的情况title2: canon camera canon, 关键字canon只计算一次,因为对于该标题,关键字集仅{cannon, camera}.

包装 TreeMap 以添加功能

public class MasterMap {
    private TreeMap<String, KeywordMapping> mappings = new TreeMap<>();
    
    public KeywordMapping put(String key, KeywordMapping value) {
        return mappings.put(key, value);
    }
    
    public KeywordMapping get(String key) {
        return mappings.get(key);
    }
    
    public KeywordMapping firstMapping() {
        return mappings.firstEntry().getValue();
    }
    
    public String getKeywordForTitle (String titleId) {
        
        List<KeywordMapping> keywords = new ArrayList<>();
        
        Collection<KeywordMapping> values = mappings.values();
        Iterator<KeywordMapping> iter = values.iterator();
        
        while (iter.hasNext()) {
            KeywordMapping value = iter.next();
            if (value.containsTitle(titleId)) {
                keywords.add(value);
            }
        }
        
        KeywordMapping temp = keywords.stream()
            .max(Comparator.comparingInt(KeywordMapping::getFrequency)
                .thenComparing(KeywordMapping::getKeyword).reversed())
            .get();
        
        return temp.getKeyword();
    }
}

为了满足最后一个要求,我决定包装TreeMap到一个类中,以便我可以添加方法来执行最后一个操作:根据以下条件返回与给定标题关联的关键字:返回频率最高的关键字,或者如果频率相同,则返回按字母顺序排列的第一个关键字。实现的函数不会操纵KeywordMapping对象或以任何方式包装的树图。它的作用是使用一个Comparator为了根据该标准找到匹配的关键字。

添加更改后(之前的代码已更新),将“title3”传递给函数会产生以下输出:

Keyword for title3: mouse

这是因为与“title3”相关的两个关键字{mouse, wireless}具有相同的频率 (2),但“mouse”是按字母顺序排列的集合中的第一个关键字。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

计算Java中列表中每个唯一元素的关键字密度? 的相关文章

随机推荐