4

What's the most idiomatic way to create an array from several variables without nil values?

Given these variables:

a = 1
b = nil
c = 3

I would like to create an array ary:

ary #=> [1, 3]

I could use Array#compact:

ary = [a, b, c].compact
ary #=> [1, 3]

But putting everything in an array just to remove certain elements afterwards doesn't feel right.

Using if statements on the other hand produces more code:

ary = []
ary << a if a
ary << b if b
ary << c if c
ary #=> [1, 3]

Is there another or a preferred way or are there any advantages or drawbacks using either of the above?


PS: false doesn't necessarily have to be considered. The variables are either truthy (numbers / strings / arrays / hashes) or nil.

9
  • Is your concern that you have to loop through the values twice, or that it simply doesn't sound good to you? Commented Sep 17, 2015 at 7:30
  • @ndn it's not a technical reason. I'd use compact if I already had an array. But since I'm creating a new array, I think it would be cleaner not to add the nil values in the first place. But that is just my opinion :-) Commented Sep 17, 2015 at 7:32
  • You would have to either list them one by one in the code explicitly or create a collection and loop through it in one way or another. No matter what syntax sugar is out there, I don't think there is any other option. Would you have been happy if the method was called non_nil_values instead? Commented Sep 17, 2015 at 7:42
  • 1
    #compact! is IMO the best way, everybody understands it does just that at a glance, and there is good chance that beeing part of standard library it has minimal overhead. I won't bet on it, but it might be even faster than a stream of #<<s and ifs. Commented Sep 17, 2015 at 8:54
  • 1
    @undur_gongor yes, that would probably reduce some visual clutter, but implementation-wise it just moves the if into a method. Commented Sep 17, 2015 at 11:36

3 Answers 3

4

If you are concerned about performance, best way would be probably to use destructive #compact! to avoid allocating memory for second array.

Sign up to request clarification or add additional context in comments.

3 Comments

You're right, compact would return a new array. On the other hand, compact! might return nil, so you have to separate the assignment from the method call, i.e. ary = [a, b, c]; ary.compact!
@Stefan #compact! will never return nil in this case, you have certain set of variables so array is guaranted not to be empty.
[1, 2, 3].compact! returns nil and that is expected: "Returns nil if no changes were made"
2

I was hoping for a way to somehow "skip" the nil values during array creation. But after thinking about this for a while, I realized that this can't be achieved because of Ruby's way to handle multiple values. There's no concept of a "list" of values, multiple values are always represented as an array.

If you assign multiple values, Ruby creates an array:

ary = 1, nil, 3
#=> [1, nil, 3]

Same for a method taking a variable number of arguments:

def foo(*args)
  args
end

foo(1, nil, 3)
#=> [1, nil, 3]

So even if I would patch Array with a class method new_without_nil, I would end up with:

def Array.new_without_nil(*values)
  values.compact!
  values
end

This just moves the code elsewhere.

Everything is an object

From an OO point of view, there's nothing special about nil - it's an object like any other. Therefore, removing nil's is not different from removing 1's.

Using a bunch of if statements on the other hand is something I'm trying to avoid when writing object oriented code. I prefer sending messages to objects.

Regarding "advantages or drawbacks":

[...] with compact / compact!

  • creates full array and shrinks it as needed
  • short code, often fits in one line
  • is easily recognized
  • evaluates each item once
  • faster (compiled C code)

[...] with << and if statements

  • creates empty array and grows it as needed
  • long code, one line per item
  • purpose might not be as obvious
  • items can easily be commented / uncommented
  • evaluates each item twice
  • slower (interpreted Ruby code)

Verdict:

I'll use compact, might have been obvious.

3 Comments

Not sure what you mean by "There's no concept of a "list" of values". Arrays are technically lists. Or did you mean linked lists? Does it really matter, since you will still have to use the same linear space and time either way? Also, I am curious, did you benchmark the two solutions? Intuitively, I would think that as the length of the array grows, the if solution would be faster (assuming there are enough nil values). It definitely is uglier though.
@ndn that's why I put list in quotes. I just wanted to point out that variable arguments in Ruby always create an additional array object. Therefore, a method def foo(*args) doesn't receive multiple arguments, it receives a single argument: the array. And it's pointless to create a new array from the elements, because you already have an array.
Regarding benchmarks: compact seems to be faster. I think that's because the if statements have to be interpreted one by one.
0

Here is a solution that uses a hash.

With these values put in an array:

a = 1; b = nil; c = 3; d = nil; e = 10;
ary = [a, b, c, d, e]

There are two nil items in the result which would require a compact to remove both "nil" items.

However the same variables added to a hash:

a = 1; b = nil; c = 3; d = nil; e = 10;
hash = {a => nil, b => nil, c => nil, d => nil, e => nil}

There is just one "nil" item in the result which can easily be removed by hash.delete(nil).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.