The foreach/remove object challenge

Tagged: 

Viewing 14 reply threads
  • Author
    Posts
    • #1016032
      Luc Fullenwarth
      Moderator
      Member Points: 16,066
      Rank: 4

      You cannot remove objects of an array while iterating it with the foreach statement.
      Here is an example to illustrate the problem.

      $Array = [System.Collections.ArrayList]@(
          1
          2
          3
      )
      	
      foreach ($Item in $Array) {
          If ($Item -eq 2) {
              $Array.Remove($Item)
          }
      }
      	
      $Array

      And here is the output

      Collection was modified; enumeration operation may not execute.
      At line:7 char:10
      + foreach ($Item in $Array) {
      +          ~~~~~
      + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
      + FullyQualifiedErrorId : System.InvalidOperationException
       
      1
      3

      Challenge: Who is able to fix this piece of code while keeping the foreach statement at line 7 and the remove method at line 9?

      • This topic was modified 2 years, 11 months ago by Luc Fullenwarth. Reason: Fixed line references
      avatar
    • #1016040
      Andrey Voronin
      Participant
      Member Points: 308
      Rank: 2

       

      $ArrayList = [System.Collections.ArrayList]@(
          1
          2
          3
      )
      	
      $Array = $ArrayList.ToArray()
      foreach ($Item in $Array) {
          If ($Item -eq 2) {
              $ArrayList.Remove($Item)
          }
      }
      	
      $ArrayList
      
    • #1016184
      Luc Fullenwarth
      Moderator
      Member Points: 16,066
      Rank: 4

      Not so bad 🙂
      The result is here, but you change the type of the object, which can have an impact if there is more code to follow and which uses the new object instead of the original one.
      But you are on the right way…

      PS> $ArrayList.GetType()
      
      IsPublic IsSerial Name                                     BaseType
      -------- -------- ----                                     --------
      True     True     ArrayList                                System.Object
      
      
      PS> $Array.GetType()
      
      IsPublic IsSerial Name                                     BaseType
      -------- -------- ----                                     --------
      True     True     Object[]                                 System.Array
      
    • #1018200
      Andrey Voronin
      Participant
      Member Points: 308
      Rank: 2

       

      $list = [System.Collections.Generic.List[int]]@(1,2,3,2,4)
      [void]$list.RemoveAll({ $args[0] -eq 2})
      $list

      😉

      • #1018386
        Luc Fullenwarth
        Moderator
        Member Points: 16,066
        Rank: 4

        Nice try, but you forgot a part of the challenge which is to keep the foreach statement.

        By the way, there are many workarounds to this problem which are all not using the the foreach statement.
        You jus showed us one of them.

        Your first attempt was only one word close to the solution 😉

         

    • #1018398
      Andrey Voronin
      Participant
      Member Points: 308
      Rank: 2

      I give up.

    • #1018526
      David Figueroa
      Participant
      Member Points: 4,118
      Rank: 3

      I can’t wait for the answer on this one.. I’ve been burned by this multiple times.  The only solution I’ve come up with is to create a second arraylist, and add each element that I *didn’t* want removed to that one, and then use that arraylist.  This seems grossly inefficient (memory wise).

      David F.

      avatar
    • #1018533
      Paolo Frigo
      Participant
      Member Points: 433
      Rank: 2

      Hi all,

      I think I can help you. That should be one possible answer to solve your problem.

      $Array = [System.Collections.ArrayList]@(
          1
          2
          3
      )
      
      $count = 0
      foreach ($Item in $Array) {    
          If ($Item -eq 2) {        
              $Array.RemoveAt($count)    }
          $count += 1 
      }
          
      $Array

      And the object type it’s not changed either…

      PS > $Array.GetType()
      
      IsPublic IsSerial Name BaseType
      -------- -------- ---- --------
      True True ArrayList System.Object

      It took me less than one minute to solve it, because I looked it more as a developer than a sysadmin script challange.

      You’re pointing to the .NET class and the answer it’s very frequently in the doc: https://docs.microsoft.com/en-us/dotnet/api/system.collections.arraylist?view=netframework-4.7.2#methods

       

      • #1018545
        Luc Fullenwarth
        Moderator
        Member Points: 16,066
        Rank: 4

        @paolofrigo

        I’ve copied/pasted/ran your code and got the same error as in the problem statement.

        Collection was modified; enumeration operation may not execute.
        At line:8 char:10
        + foreach ($Item in $Array) {
        +          ~~~~~
        + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
        + FullyQualifiedErrorId : System.InvalidOperationException
    • #1018551
      Luc Fullenwarth
      Moderator
      Member Points: 16,066
      Rank: 4

      @ All

      Here is a hint: Andrey’s first attempt is really close to the solution I’ve found. You need only to change one word in his code.

      Of course, if you find another way to solve the problem while respecting the rules described in the statement, it’s even better. Then we would have more than one solution to our problem.

      I will give the answer on tuesday if nobody has found it before.

      Have fun!

      • This reply was modified 2 years, 11 months ago by Luc Fullenwarth. Reason: Rewording
      • This reply was modified 2 years, 11 months ago by Luc Fullenwarth. Reason: Grammar
    • #1018560
      Andrey Voronin
      Participant
      Member Points: 308
      Rank: 2

      Typecasting?

      $ArrayList = [System.Collections.ArrayList]@(
          1
          2
          3
      )
          
      foreach ($Item in [array]$ArrayList) {
          If ($Item -eq 2) {
              $ArrayList.Remove($Item)
          }
      }
          
      $ArrayList

      But I’m not sure, what’s happening in memory…

      avatar
      • #1018574
        Luc Fullenwarth
        Moderator
        Member Points: 16,066
        Rank: 4

        @wand3rvogel

        You’ve found another solution.
        This one is even one line shorter than my solution because you don’t enumerate a second array.

        I guess this works because by casting the array you are virtually creating a new one…

        Well done Andrey!

    • #1018580
      Luc Fullenwarth
      Moderator
      Member Points: 16,066
      Rank: 4

      @ all

      You can still try to improve the other solution or find another one 😉

    • #1018588
      Paolo Frigo
      Participant
      Member Points: 433
      Rank: 2

      @Luc Fullenwarth,

      It’s getting the error, but the “2”  it”s gone from the Arraylist, you can suppress that error if you want to go down that path.

       

      Ok, an alternative to foreach will be simple for loop.

      $Array = [System.Collections.ArrayList]@(
          1
          2
          3
      )
      for ($i=0;$i -lt $Array.Count; $i++) {    
          If ($Array[$i] -eq 2) {       
              $Array.RemoveAt($i)
          }    
      }
      
      $Array

      In conclusion.

      In this case a for loop, it’s for me better suited for what you want to achieve.

      It will work with no errors, it will be more obvious without hacks, it will make your code simpler to read, which I think should always be the end goal.

       

       

      • This reply was modified 2 years, 11 months ago by Paolo Frigo.
      • #1018741
        Luc Fullenwarth
        Moderator
        Member Points: 16,066
        Rank: 4

        @paolofrigo

        Yep, like Andrey’s second attempt, the for loop is one of the many possible workarounds,
        but unfortunately outside the scope of the challenge too. 🙂

    • #1020976
      Paolo Frigo
      Participant
      Member Points: 433
      Rank: 2

      @Luc, I still prefer the for loop, but what about this solution?

      $Array = [System.Collections.ArrayList]@(
          1
          2
          3
      )
          
      foreach ($Item in $Array.clone()) {
          If ($Item -eq 2) {
              $Array.Remove($Item)
          }
      }
          
      $Array

      Do you like this one?

      avataravatar
    • #1021643
      Luc Fullenwarth
      Moderator
      Member Points: 16,066
      Rank: 4

      @paolofrigo

      Excellent!
      You found the solution Andrey was near to find and you improved it.
      I was initially thinking at:

      $ArrayList = [System.Collections.ArrayList]@(
          1
          2
          3
      )
      
      $Array = $ArrayList.Clone()
      foreach ($Item in $Array) {
          If ($Item -eq 2) {
              $ArrayList.Remove($Item)
          }
      }
      
      $ArrayList

      But your solution is even one line shorter.
      We have now three possible solutions.

      @ All

      Thanks for your participation! 🙂

      avatar
    • #1021749
      Luc Fullenwarth
      Moderator
      Member Points: 16,066
      Rank: 4

      @paolofrigo

      Besides, the point in this challenge isn’t about what I like or not, it’s about being compliant with the original statement.
      It’s a game with a frame and rules.
      You have probably to step outside your coding habits 😉

      Thanks again for your participation!

      avatar
      • #1025767
        Ramon Tan
        Participant
        Member Points: 764
        Rank: 2

        Totally agree!!!  As a beginner, I am learning the more subtle aspects of Powershell by following the responses to the challenge problem.  The suggestions and comments are thought-provoking and educational.  I hope there will be more programming challenges such as the one Mr Fullenwarth submitted.  Many thanks to all.

        avatar
    • #1030567
      teqqra
      Participant
      Member Points: 95
      Rank: 1

      Yeah! thanks! will try the next one!

      avatar
Viewing 14 reply threads
  • You must be logged in to reply to this topic.
© 4sysops 2006 - 2022

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account