根据子值填充父 List 元素

Posted

技术标签:

【中文标题】根据子值填充父 List 元素【英文标题】:Populate parent List elements based on child values 【发布时间】:2012-03-06 14:46:43 【问题描述】:

考虑以下代码:

AuditProgressReport

public class AuditProgressReport

    private List<AuditProgressReport> audit_progress_reports = null;

    private String name = null;
    private String description = null;

    private int compliant;
    private int non_compliant;
    private int not_completed ;

    /**
     * 
     */
    public AuditProgressReport()
    
        super();
    

    public AuditProgressReport(
        String name_param,
        int compliant_param,
        int non_compliant_param,
        int not_completed_param)
    
        super();

        this.name = name_param;
        this.compliant = compliant_param;
        this.non_compliant = non_compliant_param;
        this.not_completed = not_completed_param;
    

    public void addToCompliant(int compl_to_add_param)
    
        this.compliant += compl_to_add_param;
    

    public void addToNonCompliant(int non_compl_to_add_param)
    
        this.non_compliant += non_compl_to_add_param;
    

    public void addToNotCompleted(int not_compl_param)
    
        this.not_completed += not_compl_param;
    

    public void setAuditProgressReports(List<AuditProgressReport> report_category_nodes_param)
    
        this.audit_progress_reports = report_category_nodes_param;
    

    public List<AuditProgressReport> getAuditProgressReports()
    
        return this.audit_progress_reports;
    

    public void setCompliant(int compliantParam)
    
        this.compliant = compliantParam;
    

    public int getCompliant()
    
        return this.compliant;
    

    public void setNonCompliant(int nonCompliantParam)
    
        this.non_compliant = nonCompliantParam;
    

    public int getNonCompliant()
    
        return this.non_compliant;
    

    public void setNotCompleted(int notCompletedParam)
    
        this.not_completed = notCompletedParam;
    

    public int getNotCompleted()
    
        return this.not_completed;
    

    public void setName(String name_param)
    
        this.name = name_param;
    

    public String getName()
    
        return this.name;
    

    public void setDescription(String description_param)
    
        this.description = description_param;
    

    public String getDescription()
    
        return this.description;
    

    @Override
    public String toString()
    
        return ("Compliant["+this.compliant+
            "] Non-Compliant["+this.non_compliant+
            "] Not-Completed["+this.not_completed+"]");
    

还有 CLASS 测试员

public class Tester

    public static void main(String[] args)
    

        List<AuditProgressReport> main_level = new ArrayList<AuditProgressReport>();

        AuditProgressReport ar_1_1 = new AuditProgressReport("ar_1_1",0,0,0);
        AuditProgressReport ar_1_2 = new AuditProgressReport("ar_1_2",0,0,0);

        AuditProgressReport ar_1_1_1 = new AuditProgressReport("ar_1_1_1",0,0,0);
        AuditProgressReport ar_1_1_2 = new AuditProgressReport("ar_1_1_2",15,65,20);
        AuditProgressReport ar_1_1_3 = new AuditProgressReport("ar_1_1_3",20,30,50);

        AuditProgressReport ar_1_1_1_1 = new AuditProgressReport("ar_1_1_1_1",5,5,90);
        AuditProgressReport ar_1_1_1_2 = new AuditProgressReport("ar_1_1_1_2",55,5,40);
        AuditProgressReport ar_1_1_1_3 = new AuditProgressReport("ar_1_1_1_3",35,35,30);

        List<AuditProgressReport> arl_1_1_1 = new ArrayList<AuditProgressReport>();
        arl_1_1_1.add(ar_1_1_1_1);
        arl_1_1_1.add(ar_1_1_1_2);
        arl_1_1_1.add(ar_1_1_1_3);

        ar_1_1_1.setAuditProgressReports(arl_1_1_1);

        List<AuditProgressReport> arl_1_1 = new ArrayList<AuditProgressReport>();
        arl_1_1.add(ar_1_1_1);
        arl_1_1.add(ar_1_1_2);
        arl_1_1.add(ar_1_1_3);

        AuditProgressReport ar_1_2_1 = new AuditProgressReport("ar_1_2_1",10,30,60);
        AuditProgressReport ar_1_2_2 = new AuditProgressReport("ar_1_2_2",20,20,60);



        List<AuditProgressReport> arl_1_2 = new ArrayList<AuditProgressReport>();
        arl_1_2.add(ar_1_2_1);
        arl_1_2.add(ar_1_2_2);

        ar_1_1.setAuditProgressReports(arl_1_1);

        ar_1_2.setAuditProgressReports(arl_1_2);

        main_level.add(ar_1_1);
        main_level.add(ar_1_2);


        Tester tester = new Tester();

        for(AuditProgressReport prog_rep : main_level)
        
            tester.populateParents(prog_rep, null);
        

        //TODO Now check the values...
    

    private void populateParents(
        AuditProgressReport audit_progress_param,
        AuditProgressReport parent_param)
    
        List<AuditProgressReport> audit_progress = 
            audit_progress_param.getAuditProgressReports();

        System.out.println("name["+audit_progress_param.getName()+"]");

        if(parent_param != null)
        
            int compl = audit_progress_param.getCompliant();
            int nonCompl = audit_progress_param.getNonCompliant();
            int notCompleted = audit_progress_param.getNotCompleted();

            parent_param.addToCompliant(compl);
            parent_param.addToNonCompliant(nonCompl);
            parent_param.addToNotCompleted(notCompleted);
        

        if(audit_progress != null && ! audit_progress.isEmpty())
        
            for(AuditProgressReport prog_rep : audit_progress)
            
                this.populateParents(prog_rep,audit_progress_param);
            
        
    

当您运行此程序时,您会注意到列表中父元素的值会更新为子列表中值的总和。

我面临的问题是我希望它在整个树中一直更新,而不仅仅是直接父级。

有没有一种模式可以帮助我实现这一目标?

见下图:

【问题讨论】:

对于每个节点,将其值设置为其子节点值的总和。闻起来像递归;) 你不能让每个父母都听听它的孩子吗?还可以为您节省手动更新的一些麻烦。 【参考方案1】:

根据您的班级名称,我猜您希望在运行时实时查看审核进度。所以我的假设:

树形结构变化不大,创建后基本固定 节点值经常变化,计数器初始状态为 0

这是一个有效的实现:

每个节点都维护其父节点的完整列表 节点以 0 值插入 当一个节点值改变或简单地增加时,通过应用前一个节点值之间的增量来更新节点列表中父节点的值

因此结构始终是最新的,节点插入仍然是可能的并且不会影响现有节点。

如果许多审计线程同时运行并将值报告到结构中,您必须注意并发问题并使用AtomicInteger 作为计数器持有者。

这是一个务实的设计,真诚地我没有找到任何匹配的模式。与排序算法一样,在这种情况下尝试使用模式可能会适得其反。

【讨论】:

【参考方案2】:

其他发帖人建议使用Observer pattern。观察者模式是Pub/Sub pattern 的子集。我建议在观察者模式上使用它。

Observer 模式和 Pub/Sub 模式的主要区别在于,在 Observer 模式中,Observer 既是 ChangeEvents 的发布者,也是消息的分发者。它本质上是将每个 Observable 变成一个 EventDispatcher。在传统的 Pub/Sub 模式中,Observables 只是 ChangeEvents 的发布者。 ChangeEvents 被发布到一个单独的 EventDispatchingService 中,该服务处理事件需要发送到的订阅者。

尝试使用观察者模式来跟踪全局变化是很困难的。例如,如果要计算调用addToCompliant() 方法的次数,则必须在 Observable 的每个实例上添加 Observer。使用 Event Pub/Sub,您的观察者类可以订阅以侦听 ChangeEvent 的类型,并且它将接收所有这些。我用过的最好的(恕我直言)事件发布/订阅库是Google Guava's Event Bus。在您的特定情况下,我会执行以下操作。

public class EventBusSingleton 
    public static final EventBus INSTANCE = new EventBus("My Event Bus");


public class ComplianceChange 
    private AuditProgressReport changedReport;
    private int delta;

    public ComplianceChange(AuditProgressReport changedReport, int delta) 
        this.changedReport = changedReport;
        this.delta = delta;
    

    ...


public class AuditProgressReport 

    ...
    private AuditProgressReport parent;

    public AuditProgressReport getParent() 
        return parent;
    

    public void addToCompliant(int delta) 
        this.compliant += delta;
        ComplianceChange change = new ComplianceChange(this, delta);
        EventBusSingleton.INSTANCE.post(change);
    
    ...


public class ComplianceChangeHandler 

    @Subscribe
    public void notifyParent(ComplianceChange event) 
        AuditProgressReport parent = event.getChangedReport().getParent();
        int delta = event.getDelta();
        parent.addToCompliant(delta);
    

    @Subscribe
    public void somethingElse(ComplianceChange event) 
        // Do Something Else
    


// Somewhere during initialization
EventBusSingleton.INSTANCE.register(new ComplianceChangeHandler());

【讨论】:

我认为 Pub/Sub 模式不是解决这个问题的最佳选择,因为问题太简单了,无法使用这种模式。在这个问题中,只有直接父节点想知道其子节点值的变化。对于更复杂的问题,我会使用 Pub/Sub 模式,即两个不同包上的两个不相关类之间的异步通信。事实上,有了这个包,通信的两个部分在编译时或运行时是不知道彼此的。【参考方案3】:

像其他人建议的那样,我会使用观察者模式。每个父节点监听子节点的变化。

但我的解决方案与 @zmf 的不同,因为如果您有一棵有很多子节点的大树,并且每次更新时您必须对每个值求和,您将花费大量处理时间。

如果每次更新子节点时只发送旧值和新值之间的差值会怎样。让我们举个例子。你从这棵树开始:

[12]--+--[10]-----[10]
      |
      +--[ 2]--+--[  ]
               |
               +--[ 2]

然后你像这样更新一个孩子

[12]--+--[10]-----[10]
      |
      +--[ 2]--+--[ 3]
               |
               +--[ 2]

使用值“3”更新的节点通过调用 parent.updateNode(3) 方法将其更改发送给父节点。父节点只需将其当前值(在本例中为“2”)与其从子节点接收的值相加。所以它将更新为值“5”

[12]--+--[10]-----[10]
      |
      +--[ 5]--+--[ 3]
               |
               +--[ 2]

新值为“5”的节点会调用parent.updateNode(3),最终的解决方案是

[15]--+--[10]-----[10]
      |
      +--[ 5]--+--[ 3]
               |
               +--[ 2]

恕我直言,此解决方案更好,因为每个 updateNode() 方法只需将其自己的当前值与从其子节点接收到的更改相加,并使用接收到的相同值调用其父级。您不必从每个孩子那里获取价值并将所有价值相加。如果您有一棵大树,这将为您节省大量时间。因此,在本例中,当您将值从 0 更改为 3 时。您将获得 2 次对 parent.updateNode(3) 的调用,并且每个父项都会得到更新。

【讨论】:

【参考方案4】:
public void updateNode(int value) 

    if (value != this.value) 
        this.value = value;

        if (getParent() != null) 
            int sum = 0;
            for (Node n : getParent().getChildren()) 
                sum += n.getValue();
            
            getParent.updateNode(sum);
         
    

【讨论】:

以上是关于根据子值填充父 List 元素的主要内容,如果未能解决你的问题,请参考以下文章

XSLT 根据最大​​子节点对父节点进行排序

根据特定的子值获取 json 值

如何使用JS根据组合框中的子值设置值?

Regex根据子值查找所有XML值

根据前几行填充(更新)父ID?

如何根据自己的宽度而不是父 div 将 CSS 边距/填充设置为子 div?