【问题标题】:Is there a set-of-sets where sets can be addressed by any of their members?是否存在一组集合,其中任何成员都可以对集合进行寻址?
【发布时间】:2019-06-26 03:44:07
【问题描述】:

我正在尝试在 Java(或 Groovy)中找到这样的数据结构:

MemberAdressableSetsSet mass = new MemberAdressableSetsSet();
mass.addSet(["a","b"]);
mass.addSet(["c","d","e"]);
mass.get("d").add("f");
String output = Arrays.toString(mass.get("e").toArray());
System.out.println(output); // [ "c", "d", "e", "f" ] (ordering irrelevant)

这样的东西存在吗?如果没有,有没有办法用普通的 Java 代码实现这样的东西,不会给 CPU 或内存带来数周的噩梦?

编辑:更严格

MemberAdressableSetsSet mass = new MemberAdressableSetsSet();
Set<String> s1 = new HashSet<String>();
s1.add("a");
Set<String> s2 = new HashSet<String>();
s2.add("c");s2.add("d");s2.add("e");
mass.addSet(s1);
mass.addSet(s2);
Set<String> s3 = new HashSet<String>();
s3.add("a");s3.add("z");

mass.addSet(s3);
/* s3 contains "a", which is already in a subset of mass, so:
 * Either
 *   - does nothing and returns false or throws Exception
 *   - deletes "a" from its previous subset before adding s3
 *      => possibly returns the old subset
 *      => deletes the old subset if that leaves it empty
 *      => maybe requires an optional parameter to be set
 *   - removes "a" from the new subset before adding it
 *      => possibly returns the new subset that was actually added
 *      => does not add the new subset if purging it of overlap leaves it empty
 *      => maybe requires an optional parameter to be set
 *   - merges all sets that would end up overlapping
 *   - adds it with no overlap checks, but get("a") returns an array of all sets containing it
 */

mass.get("d").add("f");
String output = Arrays.toString(mass.get("e").toArray());
System.out.println(output); // [ "c", "d", "e", "f" ] (ordering irrelevant)

mass.get("d") 将返回包含"d"mass 中的Set&lt;T&gt;。类似于 get() 在 HashMap 中的工作方式:

HashMap<String,LinkedList<Integer>> map = new HashMap<>();
LinkedList<Integer> list = new LinkedList<>();
list.add(9);
map.put("d",list);
map.get("d").add(4);
map.get("d"); // returns a LinkedList with contents [9,4]

【问题讨论】:

  • 您的问题不清楚。 get("d") 到底是什么意思?如果get("d") 在多个集合中,如果"d" 在多个集合中,会发生什么?您是否希望能够做这样的事情:set s = ["a","b"]; mass.addSet(set); s.add(...); 并且还更新了质量集? ["a", "b"] 的具体类型是什么?它不是有效的 Java 语法。
  • 另外,get(..) 返回什么类型? (它需要吗?)根据各种操作addSetgetadd 和您需要的任何其他操作的复杂性,说明您可接受的最低要求。 (换句话说,用更严格的语言告诉我们“CPU 或内存噩梦”是什么意思。)
  • @StephenC 更好吗?
  • 您没有回答有关复杂性的问题。

标签: java set


【解决方案1】:

到目前为止我能想到的最好的看起来是这样的:

import java.util.HashMap;
import java.util.Set;

public class MemberAdressableSetsSet {
    private int next_id = 1;
    private HashMap<Object,Integer> members = new HashMap();
    private HashMap<Integer,Set> sets = new HashMap();
    public boolean addSet(Set s) {
        if (s.size()==0) return false;
        for (Object member : s) {
            if (members.get(member)!=null) return false;
        }
        sets.put(next_id,s);
        for (Object member : s) {
            members.put(member,next_id);
        }
        next_id++;
        return true;
    }
    public boolean deleteSet(Object member) {
        Integer id = members.get(member);
        if (id==null) return false;
        Set set = sets.get(id);
        for (Object m : set) {
            members.remove(m);
        }
        sets.remove(id);
        return true;
    }
    public boolean addToSet(Object member, Object addition) {
        Integer id = members.get(member);
        if (id==null) throw new IndexOutOfBoundsException();
        if (members.get(addition)!=null) return false;
        sets.get(id).add(addition);
        members.put(addition,id);
        return true;
    }
    public boolean removeFromSet(Object member) {
        Integer id = members.get(member);
        if (id==null) return false;
        Set s = sets.get(id);
        if (s.size()==1) sets.remove(id);
        else s.remove(member);
        members.remove(member);
        return true;
    }
    public Set getSetClone(Object member) {
        Integer id = members.get(member);
        if (id==null) throw new IndexOutOfBoundsException();
        Set copy = new java.util.HashSet(sets.get(id));
        return copy;
    }
}

这有一些缺点:

  • 集合不可直接访问,这使得所有未通过显式定义的转换方法公开的Set 方法和属性都无法访问,除非克隆是可接受的选项
  • 类型信息丢失。
    • 说添加了Set&lt;Date&gt;
      它不会抱怨尝试将 File 对象添加到该集合。

至少 Sets 丢失的类型信息不会扩展到它们的成员:Set.contains() 仍然按预期工作,尽管双方在被contains() 比较之前都被类型转换为Object。例如,当询问是否包含 (Object)3L 时,包含 (Object)3 的集合不会返回 true,反之亦然。

包含(Object)(new java.util.Date(10L)) 的集合在被问及是否包含(Object)(new java.sql.Date(10L))(反之亦然)时返回true,但即使前面没有(Object)也是如此,所以我猜那是“按预期工作”¯\_(ツ)_/¯

【讨论】:

    【解决方案2】:

    您需要多久访问一次一个元素?可能值得使用地图并将相同的Set 引用存储在多个键下。

    我会防止地图和子集的外部突变,并提供帮助方法来完成所有更新:

    public class MemberAdressableSets<T> {
        Map<T, Set<T>> data = new HashMap<>();
    
        public void addSet(Set<T> dataSet) {
            if (dataSet.stream().anyMatch(data::containsKey)) {
                throw Exception("Key already in member addressable data");
            }
            Set<T> protectedSet = new HashSet<>(dataSet);
            dataSet.forEach(d -> data.put(d, protectedSet));
        }
    
        public void updateSet(T key, T... newData) {
            Set<T> dataSet = data.get(key);
            Arrays.stream(newData).forEach(dataSet::add);
            Arrays.stream(newData).forEach(d -> data.put(d, dataSet));
        }
    
        public Set<T> get(T key) {
            return Collections.unmodifiableSet(data.get(key));
        }
    }
    

    或者,如果密钥不存在,您可以更新addSetupdateSet 以创建新的Set 实例,并使updateSet 永远不会throw。您还需要扩展此类以处理合并集的情况。即处理用例:

    mass.addSet(["a","b"]);
    mass.addSet(["a","c"]);
    

    【讨论】:

      【解决方案3】:

      此解决方案允许 mass.get("d").add("f"); 之类的内容影响存储在 mass 中的子集,但存在重大缺陷。

      import java.util.Iterator;
      import java.util.LinkedHashSet;
      import java.util.Set;
      
      public class MemberAdressableSetsSetDirect {
          private LinkedHashSet<Set> sets;
          public void addSet(Set newSet) {
              sets.add(newSet);
          }
          public Set removeSet(Object member) {
              Iterator<Set> it = sets.iterator();
              while (it.hasNext()) {
                  Set s = it.next();
                  if (s.contains(member)) {
                      it.remove();
                      return s;
                  }
              }
              return null;
          }
          public int removeSets(Object member) {
              int removed = 0;
              Iterator<Set> it = sets.iterator();
              while (it.hasNext()) {
                  Set s = it.next();
                  if (s.contains(member)) {
                      it.remove();
                      removed++;
                  }
              }
              return removed;
          }
          public void deleteEmptySets() {
              sets.removeIf(Set::isEmpty);
          }
          public Set get(Object member) {
              for (Set s : sets) {
                  if (s.contains(member)) return s;
              }
              return null;
          }
          public Set[] getAll(Object member) {
              LinkedHashSet<Set> results = new LinkedHashSet<>();
              for (Set s : sets) {
                  if (s.contains(member)) results.add(s);
              }
              return (Set[]) results.toArray();
          }
      }
      

      没有针对重叠的内置保护,因此我们的访问不可靠,并且引入了无数空集的可能性,需要通过手动调用 deleteEmptySets() 定期清除,因为此解决方案无法检测到如果通过直接访问修改了子集。

      MemberAdressableSetsSetDirect massd = new MemberAdressableSetsSetDirect();
      Set s1 = new HashSet();Set s2 = new HashSet();Set s3 = new HashSet();
      s1.add("a");s1.add("b");
      s2.add("c");s2.add("d");
      s3.add("e");
      massd.addSet(s1);massd.addSet(s2);
      massd.get("c").add("a");
      // massd.get("a") will now either return the Set ["a","b"] or the Set ["a","c","d"]
      // (could be that my usage of a LinkedHashSet as the basis of massd
      //  at least makes it consistently return the set added first)
      massd.get("e").remove("e");
      // the third set is now empty, can't be accessed anymore,
      // and massd has no clue about that until it's told to look for empty sets
      massd.get("c").remove("d");
      massd.get("c").remove("c");
      // if LinkedHashSet makes this solution act as I suspected above,
      // this makes the third subset inaccessible except via massd.getAll("a")[1]
      

      此外,此解决方案也无法保留类型信息。
      这甚至不会发出警告:

      MemberAdressableSetsSetDirect massd = new MemberAdressableSetsSetDirect();
      Set<Long> s = new HashSet<Long>();
      s.add(3L);
      massd.addSet(s);
      massd.get(3L).add("someString");
      // massd.get(3L) will now return a Set with contents [3L, "someString"]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多