如何解决计算Java列表中每个唯一元素的关键字密度? 班级关键字映射的TreeMap包装 TreeMap 以增加功能
我可以使用以下代码计算文本正文的关键字密度:
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);
总结: 给定标题条目列表,我需要提取足够多的关键字,以便列表中的每个标题都由一个关键字表示,然后计算该关键字表示的标题总数(例如见下文)。
示例:假设我有以下 5 个 LinkedHashMap<String,String>
形式的标题:
title1: canon photo printer
title2: canon camera canon
title3: wireless mouse
title4: wireless charger
title5: mouse trap
其中 KEY
代表 titleID(即 title1、title2 等),VALUE
代表实际标题。
每个关键字的原始出现次数(按降序排列,不区分大小写)如下:
佳能:2 |鼠标:2 |无线:2 |相机:1 |充电器:1 |照片:1 |打印机:1 |陷阱:1
注意:每个关键字每个标题只计算一次。因此,虽然关键字canon出现了3次,但由于它在同一个标题(即title2)中出现了两次,所以只计算一次。
在前面的 map
中,关键字 canon 出现在 title1 和 title2 中。由于每个标题都需要由一个关键字来表示,因此两个标题都可以由关键字canon 表示。不需要包含 title1 和 title2 中的其他关键字(例如:photo、printer 和 camera),因为每个标题都应该由一个关键字表示(不是更多,也不是更少)。尽管我们可以选择使用关键字photo 和camera(或printer)来表示title1 和title2 和 camera) - 因为这会增加表示所有标题所需的关键字总数,所以这是不希望的。换句话说,我们希望用尽可能少的关键字表示所有标题。
重要的部分是提取能够一次“代表”列表中所有标题的最少数量的关键字,并跟踪每个关键字链接的标题数量和titleID .如果不是 5 个标题,我们有一个包含 100 个标题的列表,其中关键字 photo 出现了 95 次(即比关键字 canon 出现的次数多)关键字 photo 将用于替换关键字 canon,而 title2 将由关键字 camera 表示。
如果两个或多个关键字可以表示相同数量的标题,我们将按字母顺序选择第一个。因此,关键字mouse 将用于表示标题title3 和title5,而不是关键字wireless。类似地,为了表示 title4,关键字 charger 将被用作字母 C 出现在字母 W 之前(即使关键字 wireless 出现两次,关键字 charger 只出现一次,因为包含关键字 wireless 的 title3 已经由关键字 表示mouse 而不是关键字 wireless 所以当两个关键字代表相同数量的标题时,我们恢复使用字母表中的第一个关键字)
LinkedHashMap<String,LinkedHashMap<Integer,ArrayList<String>> map = new LinkedHashMap<String,ArrayList<String>>();
其中 String
代表关键字(例如 canon 或 mouse),Integer
代表该关键字代表的唯一标题数( canon = 2,mouse = 2,Charger = 1) 和 ArrayList<String>
是链接到该关键字的 titleID 列表。例如,关键字 canon 将链接到标题:title1 和 title2。关键字 mouse 将链接到标题:title3 和 title5,关键字 charger 将链接到标题: title4。
注意: wireless = 0,trap = 0 所以从最终结果中省略。
实现这一目标的最有效方法是什么?
谢谢
解决方法
基本上,您需要创建某种类型的数据结构,您需要在其中跟踪关键字、链接的标题数量以及关联的标题 ID;像这样:
[canon,{2,[ title1,title2 ] } ]
如果是这种情况,这至少有两个原因:
- 使用计数作为键是不好的,因为其他元素的计数会重复
- 您可以通过返回给定关键字的标题 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);
}
这个解决方案没问题。我宁愿使用 Builder Pattern 来构建它并使这个类不可变,以便只有在所有标题 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,frequency=1]
trap=KeywordMapping [keyword=trap,titleIds=[title5],frequency=1]
wireless=KeywordMapping [keyword=wireless,title4],frequency=2]
First entry: camera=KeywordMapping [keyword=camera,frequency=1]
First title in first entry: title2
请注意,即使原始标题列表不是按自然顺序排列的,但在插入 KeywordMapping
对象后,支持标题 ID 列表的集合会按顺序排列它们。这与树图中条目的打印顺序相同。
最后,每个标题的频率计数仅增加一次,因为标题 ID 列表由 Set
而不是 List
支持,并且 Java 中的集合不允许重复。因此,对于像 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”是按字母顺序排列的第一个关键字。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。