# Am I using max correctly?

**URL:** <https://forum.crystal-lang.org/t/am-i-using-max-correctly/598>\
**Category:** Help & Support\
**Created:** [March 25, 2019, 12:24am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598 "2019-03-25T00:24:58Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 12:24am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/1 "2019-03-25T00:24:58Z")

</div>

Doesn’t work: [https://play.crystal-lang.org/#/r/6kor/edit](https://play.crystal-lang.org/#/r/6kor/edit)

```auto
struct Item
end

test = Hash(Int32, Item).new
test[1] = Item.new
test[5] = Item.new
test[2] = Item.new
test[25] = Item.new

puts test.max[0]

```

Works: [https://play.crystal-lang.org/#/r/6kot](https://play.crystal-lang.org/#/r/6kot)

```auto
test = Hash(Int32, Int32).new

test[1] = 0
test[5] = 0
test[2] = 0
test[25] = 0

puts test.max[0]

```

Is this a bug, or am I doing it wrong? :)

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 12:29am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/2 "2019-03-25T00:29:48Z")

</div>

When using a struct, the error is:

```auto
in macro 'macro_94090632191376' /usr/lib/crystal/tuple.cr:293, line 5:

   1. 
   2. cmp = self[0] <=> other[0]
   3. return cmp unless cmp == 0
   4.     
> 5. cmp = self[1] <=> other[1]
   6. return cmp unless cmp == 0
   7.     

undefined method '<=>' for Item

```

However… max doesn’t check against the values, it checks against the keys, right? So why is it complaining about `Item`? In fact, it doesn’t even sort by the values.

If it did, [https://play.crystal-lang.org/#/r/6koy/edit](https://play.crystal-lang.org/#/r/6koy/edit) would return `{2, 5}`, not `{25, 4}`? It sorts by the keys!?

---

<div class="post-metadata">

**Author:** ![arcage](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/arcage/32/1250_2.png) [@arcage](https://forum.crystal-lang.org/u/arcage)\
**Post date:** [March 25, 2019, 12:55am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/3 "2019-03-25T00:55:27Z")

</div>

`Hash(K,V)` type includes `Enumerable({K,V})`. And `#max` method is defined in Enumerable.

This means targets of the comparison for `#max`(or `#min`) are not values of that hash, but tuple of key and value(`{key, value}`)

Therefore, `test.max` in the last example will return the maximum object in `{1, 1}`, `{5, 2}`, `{2, 5}` and `{25, 4}`.

That is `{25, 4}`, because that the comparison of two tuples compares the first element of both tuples at first.

If you want to get the key of the max value of that hash, you can use `#max_by` like [https://play.crystal-lang.org/#/r/6kp7](https://play.crystal-lang.org/#/r/6kp7) .

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 12:57am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/4 "2019-03-25T00:57:48Z")

</div>

> [@arcage](#):
>
> That is `{25, 4}` , because that the comparison of two tuples compares the **first element** of both tuples at first.

Yeah, but it’s using [0] (which is the key) to sort them right? So why is it saying it has a problem with `Item`? Which is the value ([1]). If the comparison is using [0], the value shouldn’t have any effect on what the `max` is?

---

<div class="post-metadata">

**Author:** ![arcage](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/arcage/32/1250_2.png) [@arcage](https://forum.crystal-lang.org/u/arcage)\
**Post date:** [March 25, 2019, 1:13am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/5 "2019-03-25T01:13:05Z")

</div>

The `Enumerable#max` is not only for Hash.

When the `#max` method is called for `{0, 1}` and `{0, 2}`, the comparison of the second value of both tuples will be needed.

Actually, there cannot be duplicate keys in the Hash.

But compiler cannot know that.

This is why the compiler requires `#<=>` method to the type of the hash value.

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 1:22am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/6 "2019-03-25T01:22:53Z")

</div>

Interesting, thanks @arcage!

I’m still puzzled on [https://play.crystal-lang.org/#/r/6kpg](https://play.crystal-lang.org/#/r/6kpg), it shows `{25, 4}` ? If the second value of both tuples are required for the comparison, why doesn’t it return `{2, 52}`?

---

<div class="post-metadata">

**Author:** ![arcage](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/arcage/32/1250_2.png) [@arcage](https://forum.crystal-lang.org/u/arcage)\
**Post date:** [March 25, 2019, 1:37am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/7 "2019-03-25T01:37:23Z")

</div>

The second value(value of Hash) of the tuples will be compared only when the first value(key of Hash) of the tuples are same.

In the case of Hash, keys are always different values.

But for the compiler, this is not a comparison of “tuple of key and value of the Hash” but only of just “tuple of Int32 and Int32 object”.

Because the compiler cannot know the first values are always different, the compiler think that “When the first values are same, I probably need the second value comparison”.

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [March 25, 2019, 2:18am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/8 "2019-03-25T02:18:11Z")

</div>

@gring `max[0]` is not “compute the maximum value of the first element”. It’s “compute the maximum, then give me the first element”. I guess you want `max_by &.[0]`. But please also learn about how Enumerable works, how Hash is an Enumerable of Tuple, and how tuples are sorted, as arcage explains.

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 2:33am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/9 "2019-03-25T02:33:03Z")

</div>

I created an issue here, [https://github.com/crystal-lang/crystal/issues/7585](https://github.com/crystal-lang/crystal/issues/7585)

Thanks for your attention

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [March 25, 2019, 2:47am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/10 "2019-03-25T02:47:22Z")

</div>

Just note that it’s not an issue. It’s how things work if you combine what the Enumerable module does with how Hash includes Enumerable.

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 2:59am UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/11 "2019-03-25T02:59:35Z")

</div>

I closed it. I think i’m getting too involved with code that is beyond the scope of my knowledge, apologies

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [March 25, 2019, 4:34pm UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/12 "2019-03-25T16:34:57Z")

</div>

@girng I’ll try to explain it briefly.

There’s the [Enumerable(T)](https://crystal-lang.org/api/0.27.2/Enumerable.html) module. It depends on an abstract `each` method whose job is to yield elements of type `T` to a block. `Array(T)` for example includes `Enumerable(T)`, meaning that `each` will yield all of the array’s elements in order to the `block`. Relying on the `each` method we can define many more methods: `select` (use `each` and just keep the ones that the block given to `select` return `true`), `all?` (use `each` and determine if all the elements yielded to the block given to `all?` return `true`), etc. `max` is also defined in terms of `each`: traverse all elements and find the one with the maximum value. I suggest you take a look at [Enumerable’s source code](https://github.com/crystal-lang/crystal/blob/master/src/enumerable.cr).

Now, `Hash(K, V)` includes `Enumerable({K, V})`. What that means is that if you do:

```crystal
h = {1 => 'a', 2 => 'b'}
h.each do |x|
  p x
end

```

then `x` will be of type `{Int32, Char}`, which is syntax sugar for `Tuple(Int32, Char)`. That is, a `Hash` can be seen as a sequence of key-value tuples.

Then you can do `hash.max` and that will return the tuple with the maximum value, considering a hash as a sequence of tuples. Comparing two tuples is comparing their elements in order, the greater one is greater, so `{2, 1}` is greater than `{1, 10}` (2 \> 1), but less than `{10, 1}` (2 \< 10) and than `{2, 3}` (2 == 2 so we move on to the second element and we have that 1 \< 10).

Now, there’s something else. You can do:

```crystal
h = {1 => 'a', 2 => 'b'}
h.each do |key, value|
  # ...
end

```

Huh? But `Hash#each` is supposedly yielding a single tuple with two elements, how come it seems it’s yielding two elements? That’s another feature of Crystal, similar to what Ruby does (Ruby does it only for Array, Crystal only for Tuple) where if a block yields a tuple you can unpack the tuple elements in the block like that. So this works too:

```crystal
a = [{1, 2}, {3, 4}]
a.each do |x, y|
  # x, y == 1, 2
  # x, y == 3, 4
  # ...
end

```

Letting `Hash` be `Enumerable` is great because it means we can apply a lot of methods like `max`, `select`, `all?`, etc., to it.

I hope this explained a bit of what’s going on with the `max` method on `Hash`.

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 5:02pm UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/13 "2019-03-25T17:02:39Z")

</div>

> [@asterite](#):
>
> the tuple with the maximum value, considering a hash as a sequence of tuples. Comparing two tuples is comparing their elements in order, the greater one is greater, so `{2, 1}` is greater than `{1, 10}` (2 \> 1), but less than `{10, 1}` (2 \< 10) and than `{2, 3}` (2 == 2 so we move on to the second element and we have that 1 \< 10).

OHHHH \<light-bulb moment\>, ding

Yeah, I think I get it now!! So the magic happens on that comparison operator (\>)  
Line 772:

```auto
 if i == 0 || value > max

```

If it’s just an Array, it’s comparing just the value. If it’s iterating a Hash, the comparison operator now uses the **Tuple’s** ELEMENTS, in order. In my case, it would be {Int32, Item}. Since my `Item` struct doesn’t have a comparison method overload, the compiler is going to error!

I hope I got that right?!

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [March 25, 2019, 5:14pm UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/14 "2019-03-25T17:14:02Z")

</div>

@girng That’s correct! 🙂

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 5:18pm UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/15 "2019-03-25T17:18:06Z")

</div>

HAHA. This is pretty sneaky though!!

Check this out

```auto
test = Hash(Int32, Int32).new

test[26] = 3
test[2] = 1
test[26] = 1

puts test.max

```

Output: **{26, 1}**

However…

```auto
test = Hash(Int32, Int32).new

test[26] = 1
test[2] = 1
test[26] = 3

puts test.max

```

Output: **{26, 3}**

Order matters!

* * *

With that said, for my case, I should use `test.keys.max`? Which means I don’t have to add an operator comparison overload to my `Item` struct.

---

<div class="post-metadata">

**Author:** ![girng](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/girng/32/127_2.png) [@girng](https://forum.crystal-lang.org/u/girng)\
**Post date:** [March 25, 2019, 5:24pm UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/16 "2019-03-25T17:24:58Z")

</div>

Sweet! I was pulling my hair out bawhaha

That was pretty cool cause I read your post, then it clicked in my brain “oh wow”. I then started replying what I “now know”, but it felt like I was plagiarizing you… That’s a weird phenomena, whatever it is

edit: I guess it’s called LEARNING lol

---

<div class="post-metadata">

**Author:** ![alex-lairan](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/alex-lairan/32/218_2.png) [@alex-lairan](https://forum.crystal-lang.org/u/alex-lairan)\
**Post date:** [March 26, 2019, 5:48pm UTC](https://forum.crystal-lang.org/t/am-i-using-max-correctly/598/17 "2019-03-26T17:48:57Z")

</div>

Hi,

With this code it worked 🙂

```auto
struct Item
  def <=>(other)
  	0
  end
end

test = Hash(Int32, Item).new

test[1] = Item.new
test[5] = Item.new
test[2] = Item.new
test[25] = Item.new

puts test.max[0]

```

[https://play.crystal-lang.org/#/r/6l88](https://play.crystal-lang.org/#/r/6l88)
