Acknowledged
CIQQA-4100

Array.removeAll() does not accept null despite documentation

According to the documentation, Array.removeAll() should accept null as a parameter, although its exact behavior in that case is not specified. One might expect it to remove all entries.

However, the compiler does not accept null as a valid argument. I will attach a screenshot in a follow-up post to illustrate the issue.

  • You've declared an array (implicitly or explicitly), to be Array<EvccWidgetBaseSiteView>, so it should never have a null value in it. If it is not possible to have a null in the collection, it does not make sense to remove a null value.

    Understand, thank you for the clarification and for taking the time to outline alternative ways to clear the array. I do need to keep the reference intact, so I will go with the second, more efficient code sample you suggested.

  • @flowstate hit the nail on the head here. You've declared an array (implicitly or explicitly), to be Array<EvccWidgetBaseSiteView>, so it should never have a null value in it. If it is not possible to have a null in the collection, it does not make sense to remove a null value.

    That brings us to the root misunderstanding. The removeAll function removes all values in the Array that compare equal to the parameter. In your case, when passing null, it would remove all values equal to null. So, unless you have an array that contains all null values, you cannot clear an Array by calling array.removeAll(null).

    If you are wanting to get an empty Array, the easy way is to just assign it as array = []. If you want to keep the array reference the same, but you want to remove all items from it, you have to be a little trickier. You could remove each element until the array is empty...

    for (var i = 0, j = a.size(); i < j; ++i) {
      a.remove(a[0]);
    }

    I actually think it is more efficient to do this...

    if (a.size() != 0) {
      var x = a[0];
    
      for (var i = 0; j < a.size(); i < j; ++i) {
        a[i] = x;
      }
    
      a.removeAll(x);
    }

    The issue is that each call to remove does a reallocation and copy of the underlying array, so the above code is 101 calls into the vm (.size and .remove). The alternative is 3 calls into the vm (.size and .removeAll). The removeAll call will do 99 in-place copies, but only 1 reallocation at the very end.

    import Toybox.Test;
    import Toybox.Lang;
    
    (:test)
    module ArrayPerf {
    
        function generate_array(aSize as Number) as Array<Number>{
            var arr = new [ aSize ];
    
            for (var i = 0; i < aSize; ++i) {
                arr[i] = i;
            }
    
            return arr as Array<Number>;
        }
    
        function do_test_remove_one(a as Array<Number>) as Void {
            for (var i = 0, j = a.size(); i < j; ++i) {
                a.remove(a[0]);
            }
        }
    
        function do_test_remove_all(a as Array<Number>) as Void {
            if (a.size() != 0) {
                var x = a[0];
    
                for (var i = 0, j = a.size(); i < j; ++i) {
                    a[i] = x;
                }
    
                a.removeAll(x);
            }
        }
    
        function test_remove_one(aCount as Number, aSize as Number) as Number {
            var ticks = 0;
    
            for (var i = 0; i < aCount; ++i) {
                var a = generate_array(aSize);
    
                var start = System.getTimer();
                do_test_remove_one(a);
    
                ticks += (System.getTimer() - start);
            }
    
            return ticks;
        }
    
        function test_remove_all(aCount as Number, aSize as Number) as Number {
            var ticks = 0;
    
            for (var i = 0; i < aCount; ++i) {
                var a = generate_array(aSize);
    
                var start = System.getTimer();
                do_test_remove_all(a);
    
                ticks += (System.getTimer() - start);
            }
    
            return ticks;
        }
    
        (:test)
        function test_remove_one_from_array_size_10(aLogger as Logger) as Boolean {
            var sum = 0;
    
            for (var i = 0; i < 10; ++i) {
                var value = test_remove_one(100, 10);
                sum += value;
    
                aLogger.debug(Lang.format("$1$ms", [ value ]));
            }
    
            aLogger.debug(Lang.format("Average: $1$ms", [ sum / 10.0f ]));
            return true;
        }
    
        (:test)
        function test_remove_one_from_array_size_100(aLogger as Logger) as Boolean {
            var sum = 0;
    
            for (var i = 0; i < 10; ++i) {
                var value = test_remove_one(100, 100);
                sum += value;
    
                aLogger.debug(Lang.format("$1$ms", [ value ]));
            }
    
            aLogger.debug(Lang.format("Average: $1$ms", [ sum / 10.0f ]));
            return true;
        }
    
        (:test)
        function test_remove_one_from_array_size_1000(aLogger as Logger) as Boolean {
            var sum = 0;
    
            for (var i = 0; i < 10; ++i) {
                var value = test_remove_one(100, 1000);
                sum += value;
    
                aLogger.debug(Lang.format("$1$ms", [ value ]));
            }
    
            aLogger.debug(Lang.format("Average: $1$ms", [ sum / 10.0f ]));
            return true;
        }
    
    
        (:test)
        function test_remove_all_from_array_size_10(aLogger as Logger) as Boolean {
            var sum = 0;
    
            for (var i = 0; i < 10; ++i) {
                var value = test_remove_all(100, 10);
                sum += value;
    
                aLogger.debug(Lang.format("$1$ms", [ value ]));
            }
    
            aLogger.debug(Lang.format("Average: $1$ms", [ sum / 10.0f ]));
            return true;
        }
    
        (:test)
        function test_remove_all_from_array_size_100(aLogger as Logger) as Boolean {
            var sum = 0;
    
            for (var i = 0; i < 10; ++i) {
                var value = test_remove_all(100, 100);
                sum += value;
    
                aLogger.debug(Lang.format("$1$ms", [ value ]));
            }
    
            aLogger.debug(Lang.format("Average: $1$ms", [ sum / 10.0f ]));
            return true;
        }
    
        (:test)
        function test_remove_all_1000(aLogger as Logger) as Boolean {
            var sum = 0;
    
            for (var i = 0; i < 10; ++i) {
                var value = test_remove_all(100, 1000);
                sum += value;
    
                aLogger.debug(Lang.format("$1$ms", [ value ]));
            }
    
            aLogger.debug(Lang.format("Average: $1$ms", [ sum / 10.0f ]));
            return true;
        }
    }
    

    That produces the following in the simulator...

    ------------------------------------------------------------------------------
    Executing test ArrayPerf.test_remove_one_from_array_size_10...
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): Average: 3.200000ms
    PASS
    ------------------------------------------------------------------------------
    Executing test ArrayPerf.test_remove_one_from_array_size_100...
    DEBUG (10:31): 32ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 31ms
    DEBUG (10:31): 32ms
    DEBUG (10:31): 47ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 31ms
    DEBUG (10:31): 47ms
    DEBUG (10:31): 31ms
    DEBUG (10:31): 47ms
    DEBUG (10:31): Average: 33.000000ms
    PASS
    ------------------------------------------------------------------------------
    Executing test ArrayPerf.test_remove_one_from_array_size_1000...
    DEBUG (10:31): 327ms
    DEBUG (10:31): 343ms
    DEBUG (10:31): 299ms
    DEBUG (10:31): 281ms
    DEBUG (10:31): 360ms
    DEBUG (10:31): 249ms
    DEBUG (10:31): 359ms
    DEBUG (10:31): 327ms
    DEBUG (10:31): 311ms
    DEBUG (10:31): 360ms
    DEBUG (10:31): Average: 321.600006ms
    PASS
    ------------------------------------------------------------------------------
    Executing test ArrayPerf.test_remove_all_from_array_size_10...
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): Average: 1.600000ms
    PASS
    ------------------------------------------------------------------------------
    Executing test ArrayPerf.test_remove_all_from_array_size_100...
    DEBUG (10:31): 15ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 15ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 15ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 16ms
    DEBUG (10:31): 0ms
    DEBUG (10:31): Average: 10.900000ms
    PASS
    ------------------------------------------------------------------------------
    Executing test ArrayPerf.test_remove_all_1000...
    DEBUG (10:31): 187ms
    DEBUG (10:31): 156ms
    DEBUG (10:31): 155ms
    DEBUG (10:31): 140ms
    DEBUG (10:31): 235ms
    DEBUG (10:31): 127ms
    DEBUG (10:31): 172ms
    DEBUG (10:31): 78ms
    DEBUG (10:31): 155ms
    DEBUG (10:31): 218ms
    DEBUG (10:31): Average: 162.300003ms
    PASS

  • Seems clunky, but I guess you could iterate through the array and call remove() on each element.

     Yes, that is what I am doing now. Since my array is small, this should work well enough. With larger arrays, you can only hope that the search performed by the API runs in the same direction as your iteration over the elements.

  • Seems clunky, but I guess you could iterate through the array and call remove() on each element.

  • I would expect it to remove all instances of null, personally.

    Yes, you are right, that would make sense.

    I was actually looking for a way to clear an array without creating a new instance, since existing references to the array should remain intact. That is why I was hoping that passing null might achieve that.