Java: TreeMap 是否有一种很好的方法来搜索重复字符串作为复合键的一部分
Java: TreeMap is there a nice way to search for duplicate strings as part of a composite key
我有一个 TreeMap
,其中键是基于两个字段的复合键。我希望能够在 TreeMap
中搜索第二个关键元素的匹配项 - 该元素可能有重复项。为了解释我正在尝试做的事情,请参阅以下内容,
public class CountySchoolsController {
static TreeMap<StudentKey, Student> schoolsMap = new TreeMap<>();
public static void main(String args[]) {
createSchoolsTreeMap();
System.out.println(schoolsMap.get(new StudentKey(1, "Holmes")));
}
private static TreeMap<StudentKey, Student> createSchoolsTreeMap() {
Student s1 = new Student(1, "Sherlock", "Holmes");
Student s2 = new Student(2, "John", "Watson");
Student s3 = new Student(3, "Franklin", "Holmes");
schoolsMap.put(new StudentKey(s1.getSchoolId(), s1.getLastname()), s1);
schoolsMap.put(new StudentKey(s2.getSchoolId(), s2.getLastname()), s2);
schoolsMap.put(new StudentKey(s3.getSchoolId(), s3.getLastname()), s3);
return schoolsMap;
}
}
public class StudentKey implements Comparable<StudentKey>{
int schoolId;
String lastname;
public StudentKey(int id, String lastname){
this.schoolId = id;
this.lastname = lastname;
}
public int getSchoolId() {
return schoolId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StudentKey that = (StudentKey) o;
return schoolId == that.schoolId &&
Objects.equals(lastname, that.lastname);
}
@Override
public int hashCode() {
return Objects.hash(schoolId, lastname);
}
@Override
public int compareTo(StudentKey o) {
return (this.schoolId + this.lastname).compareTo(o.schoolId + o.lastname);
}
}
public class Student {
int schoolId;
String firstname;
String lastname;
public Student(int schoolId, String firstname, String lastname) {
this.schoolId = schoolId;
this.firstname = firstname;
this.lastname = lastname;
}
public int getSchoolId() {
return schoolId;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
@Override
public String toString() {
return "Student{" +
"schoolId=" + schoolId +
", firstname='" + firstname + '\'' +
", lastname='" + lastname + '\'' +
'}';
}
}
当我 运行 这个代码时它 运行 很好并打印找到的 Student
,
Student{schoolId=1, firstname='Sherlock', lastname='Holmes'}
然而,我希望能够做的是仅搜索 lastname
中的 Holmes
和 return 两个由 ID 1 和 3 表示的记录。以及进行搜索喜欢这个我还需要能够对键的精确匹配进行搜索(如上例所示)。
理想情况下,如果我可以将 wildcard
用于 schoolId
就好了,但我认为这是不可能的。
我可以 return 键集值并对其进行迭代以仅在 lastname
上找到匹配项,但我认为这不会非常 performant - 如果您不同意,请告诉我,或者这是否是实现它的最佳方式?或者我应该以另一种方式实施吗?
试试这个。它流式传输映射的 entrySet
,仅过滤姓氏,然后映射到与该名称关联的值并将其放入列表中。我必须为此创建 lastname
字段 public
。在字段中输入 getters
会很有用。
List<Student> list = schoolsMap.entrySet().stream()
.filter(e -> e.getKey().lastname
.equals("Holmes"))
.map(Entry::getValue)
.collect(Collectors.toList());
list.forEach(System.out::println);
我决定在这方面更进一步。如果你在你的 StudentKey
class 中为所有 key
属性设置 getters
,你可以执行以下操作:
获取 Holmes
的所有姓氏
List<Student> names = getStudentsForKeyAttribute(
StudentKey::getLastName, "Holmes");
names.forEach(System.out::println);
获取 id = 3
的学生
List<Student> ids = getStudentsForKeyAttribute(StudentKey::getSchoolId, 3);
ids.forEach(System.out.println);
以下方法接受 AttributeExtractor
函数以从 StudentKey
中提取适当的属性,然后根据提供的参数进行过滤。
public <T> List<Student> getStudentsForKeyAttribute(
Function<StudentKey, T> attrExtractor, T keyAttribute) {
return schoolsMap.entrySet().stream()
.filter(e -> attrExtractor.apply(e.getKey())
.equals(keyAttribute))
.map(Entry::getValue)
.collect(Collectors.toList());
}
主要编辑:我添加了数据保留功能。第一次通过每个属性时,它会为该属性和 returns 请求的值构建映射。未来的调用使用现有地图。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Function;
// Main attribute for lookup
enum Attribute {
LASTNAME, GENDER
}
// subattribute for gender. For lastname it would jut be a name.
// I decided to use an enum for gender.
enum Gender {
M, F, O
};
public class CompositeSearch {
// Map of map.
// Example:
// Outer Map is for LastName attribute
// Inner map as all lists for common last names. All names are included.
// I had to use an Object to allow for different types (enums, strings, ints)
Map<Attribute, Map<Object, List<Student>>> studentsByAttribute = new HashMap<>();
// this provides an extractor for each type requested. It just maps the
// Attribute to the Student Key method call for that type.
Map<Attribute, Function<StudentKey, ?>> extractorsForType = new HashMap<>() {
{
put(Attribute.LASTNAME,
StudentKey::getLastName);
put(Attribute.GENDER, StudentKey::getGender);
}
};
// intiial data base
TreeMap<StudentKey, Student> schoolsMap = new TreeMap<>();
public static void main(String args[]) {
new CompositeSearch().start();
}
public void start() {
createSchoolsTreeMap();
// getting all female students.
List<Student> list = getStudentsForKeyAttribute(
Attribute.GENDER, Gender.F);
list.forEach(System.out::println);
System.out.println();
// getting all students with last name of holmes.
list = getStudentsForKeyAttribute(Attribute.LASTNAME, "Holmes");
list.forEach(System.out::println);
// All maps for Gender and lastnames have been created so
// the lookups below require two map retrievals. The attribute and the
// sub attribute
System.out.println();
list = getStudentsForKeyAttribute(
Attribute.GENDER, Gender.M);
list.forEach(System.out::println);
System.out.println();
list = getStudentsForKeyAttribute(Attribute.LASTNAME, "Watson");
list.forEach(System.out::println);
}
public <T> List<Student> getStudentsForKeyAttribute(
Attribute attr, T keyAttribute) {
@SuppressWarnings("unchecked")
Function<StudentKey, T> extractor = (Function<StudentKey, T>) extractorsForType
.get(attr);
if (!studentsByAttribute.containsKey(attr)) {
// need to create the map.
System.out.println("Building map for all " + attr);
// sub attribute map
Map<Object, List<Student>> subMap = new HashMap<>();
studentsByAttribute.put(attr, subMap);
for (Map.Entry<StudentKey, ?> e : schoolsMap
.entrySet()) {
T subAttribute = extractor.apply(e.getKey());
subMap.compute(subAttribute,
(k, v) -> v == null
? new ArrayList<>()
: v)
.add((Student)e.getValue());
}
} else {
System.out.println("Using existing map for all " + attr);
}
return studentsByAttribute.get(attr).get(keyAttribute);
}
// from here on out, everything is pretty normal.
private TreeMap<StudentKey, Student> createSchoolsTreeMap() {
List<Student> students = List.of(
new Student(1, "Sherlock", "Holmes",
Gender.M),
new Student(2, "John", "Watson", Gender.M),
new Student(3, "Franklin", "Holmes",
Gender.M),
new Student(4, "Frances", "Holmes",
Gender.F),
new Student(5, "Mary", "Wilson", Gender.F),
new Student(6, "Martha", "Watson",
Gender.F));
for (Student s : students) {
schoolsMap.put(new StudentKey(s), s);
}
return schoolsMap;
}
}
class StudentKey implements Comparable<StudentKey> {
private int schoolId;
private String lastname;
private Gender gender;
public StudentKey(Student student) {
this.schoolId = student.getSchoolId();
this.lastname = student.getLastname();
this.gender = student.getGender();
}
public int getSchoolId() {
return schoolId;
}
public String getLastName() {
return lastname;
}
public Gender getGender() {
return gender;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
StudentKey that = (StudentKey) o;
return schoolId == that.schoolId
&& Objects.equals(lastname, that.lastname);
}
@Override
public int hashCode() {
return Objects.hash(schoolId, lastname);
}
@Override
public int compareTo(StudentKey o) {
return (this.schoolId + this.lastname)
.compareTo(o.schoolId + o.lastname);
}
}
class Student {
int schoolId;
String firstname;
String lastname;
Gender gender;
public Student(int schoolId, String firstname,
String lastname, Gender gender) {
this.schoolId = schoolId;
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
}
public int getSchoolId() {
return schoolId;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public Gender getGender() {
return gender;
}
@Override
public String toString() {
return "Student{" + "schoolId=" + schoolId
+ ", firstname='" + firstname + '\''
+ ", lastname='" + lastname + '\'' + '}';
}
}
好吧,过滤很酷,但是很慢。所以如果你想要一些性能 - 让我们添加索引:
Map<String,Set<StudentKey>> lastNamesMap = new HashMap<>();
Set<StudentKey> getByLastName(String lastName)
return lastNamesMap.containsKey(s1.getLastname()) ? lastNamesMap.get(s1.getLastname()) : Collections.emptySet();
}
void addStudent(Student s1) {
final String k = s1.getLastname();
Set<StudentKey> keys;
if (lastNamesMap.containsKey(k)) {
keys = lastNamesMap.get(k);
} else {
keys = new TreeSet<>();
}
StudentKey key = new StudentKey(s1.getSchoolId(), s1.getLastname();
keys.add(key);
lastNamesMap.put(k,keys);
schoolsMap.put(key, s1);
}
我有一个 TreeMap
,其中键是基于两个字段的复合键。我希望能够在 TreeMap
中搜索第二个关键元素的匹配项 - 该元素可能有重复项。为了解释我正在尝试做的事情,请参阅以下内容,
public class CountySchoolsController {
static TreeMap<StudentKey, Student> schoolsMap = new TreeMap<>();
public static void main(String args[]) {
createSchoolsTreeMap();
System.out.println(schoolsMap.get(new StudentKey(1, "Holmes")));
}
private static TreeMap<StudentKey, Student> createSchoolsTreeMap() {
Student s1 = new Student(1, "Sherlock", "Holmes");
Student s2 = new Student(2, "John", "Watson");
Student s3 = new Student(3, "Franklin", "Holmes");
schoolsMap.put(new StudentKey(s1.getSchoolId(), s1.getLastname()), s1);
schoolsMap.put(new StudentKey(s2.getSchoolId(), s2.getLastname()), s2);
schoolsMap.put(new StudentKey(s3.getSchoolId(), s3.getLastname()), s3);
return schoolsMap;
}
}
public class StudentKey implements Comparable<StudentKey>{
int schoolId;
String lastname;
public StudentKey(int id, String lastname){
this.schoolId = id;
this.lastname = lastname;
}
public int getSchoolId() {
return schoolId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StudentKey that = (StudentKey) o;
return schoolId == that.schoolId &&
Objects.equals(lastname, that.lastname);
}
@Override
public int hashCode() {
return Objects.hash(schoolId, lastname);
}
@Override
public int compareTo(StudentKey o) {
return (this.schoolId + this.lastname).compareTo(o.schoolId + o.lastname);
}
}
public class Student {
int schoolId;
String firstname;
String lastname;
public Student(int schoolId, String firstname, String lastname) {
this.schoolId = schoolId;
this.firstname = firstname;
this.lastname = lastname;
}
public int getSchoolId() {
return schoolId;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
@Override
public String toString() {
return "Student{" +
"schoolId=" + schoolId +
", firstname='" + firstname + '\'' +
", lastname='" + lastname + '\'' +
'}';
}
}
当我 运行 这个代码时它 运行 很好并打印找到的 Student
,
Student{schoolId=1, firstname='Sherlock', lastname='Holmes'}
然而,我希望能够做的是仅搜索 lastname
中的 Holmes
和 return 两个由 ID 1 和 3 表示的记录。以及进行搜索喜欢这个我还需要能够对键的精确匹配进行搜索(如上例所示)。
理想情况下,如果我可以将 wildcard
用于 schoolId
就好了,但我认为这是不可能的。
我可以 return 键集值并对其进行迭代以仅在 lastname
上找到匹配项,但我认为这不会非常 performant - 如果您不同意,请告诉我,或者这是否是实现它的最佳方式?或者我应该以另一种方式实施吗?
试试这个。它流式传输映射的 entrySet
,仅过滤姓氏,然后映射到与该名称关联的值并将其放入列表中。我必须为此创建 lastname
字段 public
。在字段中输入 getters
会很有用。
List<Student> list = schoolsMap.entrySet().stream()
.filter(e -> e.getKey().lastname
.equals("Holmes"))
.map(Entry::getValue)
.collect(Collectors.toList());
list.forEach(System.out::println);
我决定在这方面更进一步。如果你在你的 StudentKey
class 中为所有 key
属性设置 getters
,你可以执行以下操作:
获取 Holmes
List<Student> names = getStudentsForKeyAttribute(
StudentKey::getLastName, "Holmes");
names.forEach(System.out::println);
获取 id = 3
List<Student> ids = getStudentsForKeyAttribute(StudentKey::getSchoolId, 3);
ids.forEach(System.out.println);
以下方法接受 AttributeExtractor
函数以从 StudentKey
中提取适当的属性,然后根据提供的参数进行过滤。
public <T> List<Student> getStudentsForKeyAttribute(
Function<StudentKey, T> attrExtractor, T keyAttribute) {
return schoolsMap.entrySet().stream()
.filter(e -> attrExtractor.apply(e.getKey())
.equals(keyAttribute))
.map(Entry::getValue)
.collect(Collectors.toList());
}
主要编辑:我添加了数据保留功能。第一次通过每个属性时,它会为该属性和 returns 请求的值构建映射。未来的调用使用现有地图。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Function;
// Main attribute for lookup
enum Attribute {
LASTNAME, GENDER
}
// subattribute for gender. For lastname it would jut be a name.
// I decided to use an enum for gender.
enum Gender {
M, F, O
};
public class CompositeSearch {
// Map of map.
// Example:
// Outer Map is for LastName attribute
// Inner map as all lists for common last names. All names are included.
// I had to use an Object to allow for different types (enums, strings, ints)
Map<Attribute, Map<Object, List<Student>>> studentsByAttribute = new HashMap<>();
// this provides an extractor for each type requested. It just maps the
// Attribute to the Student Key method call for that type.
Map<Attribute, Function<StudentKey, ?>> extractorsForType = new HashMap<>() {
{
put(Attribute.LASTNAME,
StudentKey::getLastName);
put(Attribute.GENDER, StudentKey::getGender);
}
};
// intiial data base
TreeMap<StudentKey, Student> schoolsMap = new TreeMap<>();
public static void main(String args[]) {
new CompositeSearch().start();
}
public void start() {
createSchoolsTreeMap();
// getting all female students.
List<Student> list = getStudentsForKeyAttribute(
Attribute.GENDER, Gender.F);
list.forEach(System.out::println);
System.out.println();
// getting all students with last name of holmes.
list = getStudentsForKeyAttribute(Attribute.LASTNAME, "Holmes");
list.forEach(System.out::println);
// All maps for Gender and lastnames have been created so
// the lookups below require two map retrievals. The attribute and the
// sub attribute
System.out.println();
list = getStudentsForKeyAttribute(
Attribute.GENDER, Gender.M);
list.forEach(System.out::println);
System.out.println();
list = getStudentsForKeyAttribute(Attribute.LASTNAME, "Watson");
list.forEach(System.out::println);
}
public <T> List<Student> getStudentsForKeyAttribute(
Attribute attr, T keyAttribute) {
@SuppressWarnings("unchecked")
Function<StudentKey, T> extractor = (Function<StudentKey, T>) extractorsForType
.get(attr);
if (!studentsByAttribute.containsKey(attr)) {
// need to create the map.
System.out.println("Building map for all " + attr);
// sub attribute map
Map<Object, List<Student>> subMap = new HashMap<>();
studentsByAttribute.put(attr, subMap);
for (Map.Entry<StudentKey, ?> e : schoolsMap
.entrySet()) {
T subAttribute = extractor.apply(e.getKey());
subMap.compute(subAttribute,
(k, v) -> v == null
? new ArrayList<>()
: v)
.add((Student)e.getValue());
}
} else {
System.out.println("Using existing map for all " + attr);
}
return studentsByAttribute.get(attr).get(keyAttribute);
}
// from here on out, everything is pretty normal.
private TreeMap<StudentKey, Student> createSchoolsTreeMap() {
List<Student> students = List.of(
new Student(1, "Sherlock", "Holmes",
Gender.M),
new Student(2, "John", "Watson", Gender.M),
new Student(3, "Franklin", "Holmes",
Gender.M),
new Student(4, "Frances", "Holmes",
Gender.F),
new Student(5, "Mary", "Wilson", Gender.F),
new Student(6, "Martha", "Watson",
Gender.F));
for (Student s : students) {
schoolsMap.put(new StudentKey(s), s);
}
return schoolsMap;
}
}
class StudentKey implements Comparable<StudentKey> {
private int schoolId;
private String lastname;
private Gender gender;
public StudentKey(Student student) {
this.schoolId = student.getSchoolId();
this.lastname = student.getLastname();
this.gender = student.getGender();
}
public int getSchoolId() {
return schoolId;
}
public String getLastName() {
return lastname;
}
public Gender getGender() {
return gender;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
StudentKey that = (StudentKey) o;
return schoolId == that.schoolId
&& Objects.equals(lastname, that.lastname);
}
@Override
public int hashCode() {
return Objects.hash(schoolId, lastname);
}
@Override
public int compareTo(StudentKey o) {
return (this.schoolId + this.lastname)
.compareTo(o.schoolId + o.lastname);
}
}
class Student {
int schoolId;
String firstname;
String lastname;
Gender gender;
public Student(int schoolId, String firstname,
String lastname, Gender gender) {
this.schoolId = schoolId;
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
}
public int getSchoolId() {
return schoolId;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public Gender getGender() {
return gender;
}
@Override
public String toString() {
return "Student{" + "schoolId=" + schoolId
+ ", firstname='" + firstname + '\''
+ ", lastname='" + lastname + '\'' + '}';
}
}
好吧,过滤很酷,但是很慢。所以如果你想要一些性能 - 让我们添加索引:
Map<String,Set<StudentKey>> lastNamesMap = new HashMap<>();
Set<StudentKey> getByLastName(String lastName)
return lastNamesMap.containsKey(s1.getLastname()) ? lastNamesMap.get(s1.getLastname()) : Collections.emptySet();
}
void addStudent(Student s1) {
final String k = s1.getLastname();
Set<StudentKey> keys;
if (lastNamesMap.containsKey(k)) {
keys = lastNamesMap.get(k);
} else {
keys = new TreeSet<>();
}
StudentKey key = new StudentKey(s1.getSchoolId(), s1.getLastname();
keys.add(key);
lastNamesMap.put(k,keys);
schoolsMap.put(key, s1);
}