【问题标题】:Make select -unique on arraylist return arraylist instead of string使 arraylist 上的 select -unique 返回 arraylist 而不是字符串
【发布时间】:2019-11-19 10:51:46
【问题描述】:

我在下面的课程中有三个数组列表。我想让它们保持独一无二。但是,如果 arraylist 中只有一个项目(字符串)并且您使用 select -unique (或任何其他方法来实现此目的),它将返回字符串而不是字符串列表。用@() 包围它也不起作用,因为这会将它转换为数组而不是数组列表,我无法向其中添加内容。

任何仍然有效的建议?我之前尝试过 HashSets,但不知何故在这些方面有过可怕的经历。请参阅我之前的帖子。Post on hashset issue

代码如下:

Class OrgUnit
{
    [String]$name
    $parents
    $children
    $members


    OrgUnit($name){
        $this.name = $name
        $this.parents = New-Object System.Collections.ArrayList
        $this.children = New-Object System.Collections.ArrayList
        $this.members = New-Object System.Collections.ArrayList
    }

    addChild($child){
        # > $null to supress output
        $tmp = $this.children.Add($child)
        $this.children = $this.children | select -Unique
    }

    addParent($parent){
        # > $null to supress output
        $tmp = $this.parents.Add($parent) 
        $this.parents = $this.parents | select -Unique
    }

    addMember($member){
        # > $null to supress output
        $tmp = $this.members.Add($member)
        $this.members = $this.members | select -Unique
    }
}

【问题讨论】:

    标签: powershell


    【解决方案1】:

    您正在向数组中添加一个新项目,然后从中选择唯一的项目,并在每次添加成员时重新分配它。这效率极低,不妨试试以下方法:

    if (-not $this.parents.Contains($parent)) {
      $this.parents.Add($parent) | out-null
    }
    

    即使通过 out-null 抑制效率最低的输出也会快得多。

    【讨论】:

    • 我假设$tmp 用于删除.Add() 之后的消息。
    • 我觉得没有做最后一个解决方案几乎很尴尬。我之前做过两次不同的事情,然后我开始进行隧道视觉,这导致我按照第一篇文章中的方式做。但是,是的,这种方法似乎是显而易见的答案。谢谢! :) 带有哈希集的那个也让我遇到了我链接到的帖子中的问题。
    • @Spyral:看看here。似乎没有 100% 正确/错误:-)
    【解决方案2】:

    如果该项目已添加,请与 .Contains() 检查,因此您不必在之后一直使用 Select-Object -Unique 消除重复项。

    if (-not $this.children.Contains($child))
    {
        [System.Void]($this.children.Add($child))
    }
    

    【讨论】:

    • 做得很好 (+1)。顺便说一句:语法上更简单的输出抑制技术是使用$null = ...——这样你就不需要(...);也就是说:$null = $this.children.Add($child)
    【解决方案3】:

    正如已经指出的那样,由于效率低下,因此值得改变您的方法:

    如果新元素与Select-Object -Unique 重复,则不要盲目地附加然后可能删除它,而是使用 测试 来决定一个元素是需要附加还是已经存在。

    Patrick's helpful answer 是这种优化方法的直接实现,它将大大加快您的代码速度,并且应该可以接受,除非数组列表变得非常大。

    作为这种优化的一个副作用 - 因为数组列表只使用.Add() 就地修改 - 你原来的问题就消失了。


    按要求回答问题:

    如果您希望它们即使在以后的分配过程中也保留给定类型,只需类型约束您的(成员)变量。

    也就是说,就像您对$name 所做的那样,将您希望成员约束的类型放在成员变量声明的左侧:

    [System.Collections.ArrayList] $parents
    [System.Collections.ArrayList] $children
    [System.Collections.ArrayList] $members
    

    但是,这会将这些成员变量初始化为$null,这意味着您将不能只在.add*() 方法中调用.Add();因此,构造一个(最初为空的)实例作为声明的一部分:

    [System.Collections.ArrayList] $parents =  [System.Collections.ArrayList]::new()
    [System.Collections.ArrayList] $children = [System.Collections.ArrayList]::new()
    [System.Collections.ArrayList] $members =  [System.Collections.ArrayList]::new()
    

    此外,您确实必须在您的 Select-Object -Unique 管道周围使用 @(...);虽然这确实输出了一个数组(类型为[object[]]),但类型约束会导致该数组转换为[System.Collections.ArrayList] 实例,如下所述。

    @(...) 的需求有点令人惊讶 - 见底部。

    类型约束说明:

    • 如果您分配的值不属于该变量所限制的类型,PowerShell 会尝试将其转换为该类型; 您可以将其视为对每个分配的约束类型隐式执行强制转换:

      • 这可能失败,如果分配的值根本不可转换;然而,PowerShell 的类型转换通常非常灵活。

      • 1234563请注意,这必然涉及到每次构造一个new[System.Collections.ArrayList],松散地说,它变成了输入集​​合的浅层克隆。
    • 重新分配$null的陷阱:

      • 如果约束类型是值类型(如果它的.IsValueType 属性报告$true),分配$null 将导致类型的默认值时间>;比如执行
        [int] $i = 42; $i = $null后,$i不是$null,是0。

      • 如果约束类型是引用类型(例如[System.Collections.ArrayList]),分配$null 将真正将$null 存储在变量中,尽管稍后尝试分配非null 值将再次导致转换为约束类型。

  • 本质上,这与参数变量中使用的技术相同,也可以用于常规变量中。

    • 使用常规变量(函数或脚本中的局部变量),您必须还初始化变量以使类型约束起作用(使变量均匀被创建);例如:
      [System.Collections.ArrayList] $alist = 1, 2
  • 应用于您的代码的简化版本:

    Class OrgUnit
    {
    
      [string] $name
      # Type-constrain $children too, just like $name above, and initialize
      # with an (initially empty) instance.
      [System.Collections.ArrayList] $children = [System.Collections.ArrayList]::new()
    
      addChild($child){
        # Add a new element.
        # Note the $null = ... to suppress the output from the .Add() method.
        $null = $this.children.Add($child)
    
        # (As noted, this approach is inefficient.)
        # Note the required @(...) around the RHS (see notes in the last section).
        # Due to its type constraint, $this.children remains a [System.Collections.ArrayList] (a new instance is created from the
        # [object[]] array that @(...) outputs).
        $this.children = @($this.children | Select-Object -Unique)
      }
    
    }
    

    有了类型约束,.children 属性现在仍然是[System.Collections.ArrayList]:

    PS> $ou = [OrgUnit]::new(); $ou.addChild(1); $ou.children.GetType().Name
    ArrayList   # Proof that $children retained its type identity.
    

    注意:需要@(...) - 以确保数组赋值以成功转换为[System.Collections.ArrayList] - 有点令人惊讶,因为以下适用于类似的 generic 列表类型 [System.Collections.Generic.List[object]]:

    # OK: A scalar (single-object) input results in a 1-element list.
    [System.Collections.Generic.List[object]] $list = 'one'
    

    相比之下,这不适用于[System.Collections.ArrayList]:

    # !! FAILS with a scalar (single object)
    # Error message: Cannot convert the "one" value of type "System.String" to type "System.Collections.ArrayList".
    [System.Collections.ArrayList] $list = 'one'
    
    # OK
    # Forcing the RHS to an array ([object[]]) fixes the problem.
    [System.Collections.ArrayList] $list = @('one')
    

    【讨论】:

      【解决方案4】:

      试试这个:

      Add-Type -AssemblyName System.Collections
      
      Class OrgUnit
      {
          [String]$name
          $parents
          $children
          $members
      
      
          OrgUnit($name){
              $this.name = $name
              $this.parents  = [System.Collections.Generic.List[object]]::new()
              $this.children = [System.Collections.Generic.List[object]]::new()
              $this.members  = [System.Collections.Generic.List[object]]::new()
          }
      
          addChild($child){
              # > $null to supress output
              $tmp = $this.children.Add($child)
              $this.children = [System.Collections.Generic.List[object]]@($this.children | select -Unique)
          }
      
          addParent($parent){
              # > $null to supress output
              $tmp = $this.parents.Add($parent) 
              $this.parents = [System.Collections.Generic.List[object]]@($this.parents | select -Unique)
          }
      
          addMember($member){
              # > $null to supress output
              $tmp = $this.members.Add($member)
              $this.members = [System.Collections.Generic.List[object]]@($this.members | select -Unique)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-03
        • 2014-03-14
        • 2014-08-19
        • 2012-08-29
        • 2013-10-25
        • 1970-01-01
        • 1970-01-01
        • 2013-03-06
        相关资源
        最近更新 更多