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 + "".

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.