How to port following Ruby code into Crystal?

Code is worth 1000 words:

h = []

# --------------


h[0] ||= "" # if h[0] was exists, use that h[0]
["foo", " bar"].each {|e| h[0] << e }
p h # => ["foo bar"]

h is a Array(String), i want to update h[0] in the loop, how to do same things (or achieve a similar effect) in Crystal? (I do know String is immutable, but Array is not, right?)


EDIT: I try like this, not work.

h : Array(String)
h[0] = h[0]? || ""
["foo", " bar"].each { |e| h[0] = h[0].insert(-1, e) }
p h # => ["foo bar"]

In src/1.cr:21:1

 21 | h[0] = h[0]? || ""
      ^
Error: read before assignment to local variable 'h'

can you please just look at the official language reference before making all the obvious questions?

the syntax for initialize new array is [] of Type, thatā€™s one of the first thing you need to learn in order to use crystal.

I know that this could be serve as some references to people that new to crystal (so they can just google the result) but I think you are making a lot of noise recently.

1 Like

Obvious this problem has nothing to do with empty arrays.

h = [] of String
h[0] = h[0]? || ""

I did describe the problem not very well, i need improve it.


I think you are making a lot of noise recently.

Sorry for make you into trouble.

I ask question here instead of on discard or slack, just because the newcomer can search the correct answer here, because almost all my questions have not bad answers, I admit that some questions are a bit elementary, thatā€™s also because I really canā€™t find useful results on google, ruby or somewhere, and to be honest, also hope the forum looks more active too, I guess anyone who loves Crystal donā€™t want to see that the many user last post in the forum was 4 months ago.

And I really donā€™t think most of my questions are elementary, those likely the issues will occur when a skilled Rubyist getting started with Crystal, although, because i am porting a 2000+ LOC ruby gem into Crystal, meet a lot of issue in a short period time, so ask a lots of question here lately indeed.

I will ask less questions, i hope my question can help other newcomer people a lot.


EDIT: I read ALL official document and one ebook before ask those question, and took a lots of note, most of the question are not mention in the document, or not implemented, and i answer people questions too as much as possible.

1 Like

If you try to put a value in an array, but that index is out of bounds, you get an IndexError. Check the docs:

https://crystal-lang.org/api/1.6.2/Indexable/Mutable.html#[]%3D(index%3AInt%2Cvalue%3AT)%3AT-instance-method

This is different from Ruby. If you do that in Ruby all elements up to before that index are filled with nil, which is extremely bug-prone.

Now, this doesnā€™t make sense:

h = [] of String
h[0] = h[0]? || ""

Why do you check whatā€™s in index zero, if itā€™s clear that the array is empty?

Alternative, if you skipped some code just for brevity, you can do this:

h = [] of String

# There's some code here

h << "" unless h.empty?
2 Likes

Sorry for this question even confusing myself a lot, i really should think carefully before ask this question.

If you try to put a value in an array, but that index is out of bounds, you get an IndexError. Check the docs:

Yes, this really surprised me, i do know IndexError, but happen on empty array is ignored, thank you.

Why do you check whatā€™s in index zero, if itā€™s clear that the array is empty?

Hope following ruby code describe my question more clearly

h = []

x = ["foo", " bar"].each # just ignore this part.
y = ["foo", " bar"].each

loop do
  h[0] ||= ""  # <= the h here may or may not be empty.
  h[0] << x.next


  # h[1] ||= "" # probably add one or two element, but we don't know before the loop.
  # h[1] << y.next
end

p h # => ["foo bar"]

So, is it possible use Crystal rewrite that? probably h << "" not work in the loop for this case?

Thanks.

Not itā€™s not possible. Strings are immutable in Crystal.
If you want to change the value of h, you have to assign it: h = h + "".

1 Like

The h in above example is a Array, not String.

But your answer gave me some hints.

I hope following code can work, but as describe by asterite, index is out of bounds,

h = [] of String

x = ["foo", " bar"].each # just ignore this part.

loop do
  h[0] ||= "" # <= the h here may or may not be empty.

  _x = x.next

  break if _x.is_a? Iterator::Stop

  h[0] += _x
end

p h # => ["foo bar"]

But it works if h is a hash, following is expected answer.

h = {} of String => String

x = ["foo", " bar"].each # just ignore this part.

loop do
  h["foo"] ||= ""

  _x = x.next

  break if _x.is_a? Iterator::Stop

  h["foo"] +=  _x
end

p h # => ["foo bar"]

Thanks.

Is that literally the code that you are trying to port?

Becauseā€¦ the array h starts empty. But as soon as the loop starts thereā€™s this:

h[0] ||= ""

That means ā€œif thereā€™s nothing in position 0, put an empty stringā€[

So why not have h already start with that empty string?

Also, what do you need the strings for? If they are for building a string in a loop, you better use IO::Memory or String::Builder.

Also, this can be translated to Crystal:

h[0] ||= ""

The equivalent is:

h << "" if h.empty?

(I already said that in a previous post)

Also the loop kind of doesnā€™t make sense at allā€¦ why use .each to traverse the strings and build another string. It looks to me that doing x.join would be equivalent.

All of that to sayā€¦ itā€™s always better to post the full snippet of code you are trying to translate, otherwise everyone helping you will be a bit blind somewhere, and given unhelpful messages most of the time.

Very sorry for confusing. please just ignore my first array version example, itā€™s what i imagined, the original code actual use Hash, for the purpose of reduce code, i translate it into array, but i do wrong.

for curious, original version code is here, new my fixed version is here.

as you can see, i solved my issue based on peopleā€™s help.


h << ā€œā€ if h.empty?

Just for clarify, i do think h << "" if h.empty? not equivalent to the h[0] ||= "" one for my dirty code when i saw your post, :smile:, because it could not match the original example.

See the comment in the following code.

h = [] of String

x = ["foo", " bar"].each # just ignore this part.
y = ["foo", " bar"].each # just ignore this part.

loop do
  h << "" if h.empty?

  _x = x.next

  break if _x.is_a? Iterator::Stop

  h[-1] += _x

  # How about if want add a y.next to h[-2]?
  # h[-2] << y.next
end

p h # => ["foo bar"]

Also, what do you need the strings for? If they are for building a string in a loop, you better use IO::Memory or String::Builder.

Yes, this is the big part i need to change my way of thinking, i will read more code to investigate others for really use case.

Also the loop kind of doesnā€™t make sense at allā€¦ why use .each to traverse the strings and build another string. It looks to me that doing x.join would be equivalent.

Please just ignore my dirty code for now.