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.

Parents
  • @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

Comment
  • @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

Children
No Data