【问题标题】:grouping items without duplicating logic对项目进行分组而不重复逻辑
【发布时间】:2016-03-11 20:31:57
【问题描述】:

在进行命令式编程时,我经常发现自己编写代码来对这样的项目进行分组:

function group(items):
    groups <- new Groups
    curGroup <- new Group
    for item in items:
        if item doesn't belong in curGroup:
            if curGroup is good:
                add curGroup to groups
            curGroup <- new Group
        add item to curGroup
    if curGroup is good:
        add curGroup to groups
    return groups

不幸的是,这段代码存在一些缺陷:

  • if curGroup is good: add curGroup to groups 代码重复。虽然条件中的条件可以分解为一个函数,但调用该函数并将 curGroups 添加到组的逻辑仍然出现两次,并且很容易忘记第二次出现。

  • 创建新组的逻辑出现两次。这个逻辑可能很简单,如果不是,则可以将其分解为一个单独的函数,但与第一个要点一样,它表明流程不正确。

  • 第一个项目可能无法通过归属检查,在这种情况下,我们会在创建新组后立即创建一个新组。这个问题可能看起来微不足道,但有时需要明确阻止将初始空组添加到groups。无论如何,它表明所需逻辑的表达不正确。

我想知道是否有一种更简洁的方式来表达这种逻辑。我为这个问题的抽象性质道歉,但这个问题出现在多种情况下。如果需要在特定编程语言的上下文中解决这个问题,您可以假设 Java。

【问题讨论】:

  • 我不认为这真的应该被标记为 Java,你的伪代码不是 Java,既然你在谈论算法结构和流程,那么人们用什么语言回答并不重要吗?你说人们应该假设 Java,但为什么呢?
  • @KevinWells 从根本上说,这个问题适用于任何命令式编程语言。然而,很多时候问题会收到 cmets 以获得更具体的细节,并且可用于解决问题的确切工具可能取决于语言。我可以删除 Java 提及和标签,但我会期望被要求提供更多上下文。
  • 很公平,我认为这是一个不讨论特定语言的有效问题(因为它实际上更多的是关于良好的编码实践),但我理解避免以后可能会提到的任何事情的冲动
  • @KevinWells 至少我可以更改标签的顺序,使algorithm 成为主要标签。编辑:这似乎不起作用。删除标签。

标签: algorithm design-patterns imperative-programming


【解决方案1】:

我处理这个问题的方法是在您的第一个 if 语句中添加一个附加条件,以便它包含最终 if 语句的逻辑。如果该项目不属于当前组,或者如果我在items 中的最后一个item 上,我会确保它将curGroup 添加到group

这不是一个巨大的改进(它仍然是八行代码,我不喜欢奇怪的嵌套if 语句),但我目前想不出更好的解决方案。

它很好地解决了你的三个问题:

  1. if curGroup is good: add curGroup to groups 不再重复
  2. Group 添加到Groups 也不再重复
  3. 这不是我的重组直接解决的。但是,您可以通过确保在组为空时始终说某个项目属于某个组(这对我来说很有意义,但我不知道您的分组实际在做什么的细节)来轻松避免问题 3。

这可能是这样的:

function group(items):
    groups <- new Groups
    curGroup <- new Group
    for item in items: 
        if item doesn't belong in curGroup || item is last item:
            if item is last item:
                add item to curGroup
            if curGroup is good:
                add curGroup to groups
            curGroup <- new Group
        add item to curGroup
    return groups

我很高兴看到比这更好、更精致的解决方案,但我想我会发布这个至少让事情顺利进行

更新:

您可以采取不同的方向(如果它适用于 Java,我更习惯于 C#)。与其构建一组组,不如构建一个 Hash Map(我将其称为字典,因为它在 C# 中就是这样),键是您计算的某个值,以确定项目属于哪个组,并且value 是一组项目。确定项目属于哪个组的功能应该与您当前检查项目是否属于当前组的方式非常相似。

那么您的代码将如下所示:

function group(items):
    groups <- new Dictionary<string, Group>
    for item in items:
        groupKey <- item.FindKey()
        if !groups.ContainsKey(groupKey):
            add new group to groups with key of groupKey
        add item to groups[groupKey]
    return groups

这种方法的优点:

  1. 没有重复代码
  2. 物品的顺序无关紧要,而在此之前确实很重要(在某些情况下,这可能是一个缺点,如果是这样,请告诉我,有办法解决这个问题)。
  3. 易于查看为什么项目属于给定组(便于调试)

缺点

  1. 如前所述,保持秩序可能很重要
  2. 使用更复杂的数据结构(尽管 HashMap/Dictionary 没有那么复杂)
  3. 可能很难为项目编写 FindKey 函数(尽管我怀疑在大多数情况下不会这样)

【讨论】:

  • 这个答案确实解决了第 1 点,但是您仍然有逻辑要创建一个新组两次,所以它没有解决第 2 点。在实践中,这个答案的真正问题是很难确定是否一个项目是最后一个项目。在 Java 中,我不能只使用 foreach 循环:我还必须有一个计数器或使用一个迭代器。此外,步骤add item to curGroup` 出现了两次,虽然微不足道,但表明流程很尴尬。
  • @Solomonoff'sSecret 我添加了一种完全不同的方法,以防更适用于您的情况,并且可以解决您的所有三个问题
【解决方案2】:

解决此问题的一种方法是将is good 组过滤器从分组循环中分离出来——将其视为后期处理或需求驱动的处理。您可能会争辩说,将两者结合起来(如您的问题所示)是过早优化导致笨拙代码的一个示例。

如果您使用内部循环while itemGroup 进行迭代,您自然可以避免重复您的new Group 代码。它还应该有助于解决您对第一个项目的担忧,让您更容易将组中的第一个项目与其他项目区别对待:

function group(items):
    groups <- new Groups
    while(items not empty):
        curGroup <- new Group
        using items:
            add current item to curGroup
            advance to next item
        while(items not empty):
            using items:
                if current item belongs in curGroup:
                    add current item to curGroup
                    advance to next item
                else exit inner loop
        if(curGroup is good):
            add curGroup to groups
    return groups

请注意,上面的伪代码使用items 作为迭代器。

尽管您的问题是关于命令式编程的,但看看 groupBy 的 Haskell 实现可能会有所启发。

【讨论】:

  • 当您说迭代组时,我不确定我是否理解您的意思。这些组一开始就完全是空的,那么在什么都没有迭代的时候你会怎么做呢?
  • 我的意思是,外循环的每次迭代都应该创建并填充一个组,并且(可能)将其添加到Groups
  • 也许为你的意思添加伪代码会有所帮助,因为这对我来说仍然没有多大意义
  • 这个建议很有道理。那么唯一重复的逻辑是add group to allGroups,它会出现在分组循环中和之后。代码异味的范围会减少但不会消除。
猜你喜欢
  • 1970-01-01
  • 2021-11-16
  • 2021-09-24
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多