Skip to content

implement Map::update #2041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builtin/builtin.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ impl Map {
set[K : Hash + Eq, V](Self[K, V], K, V) -> Unit
size[K, V](Self[K, V]) -> Int
to_array[K, V](Self[K, V]) -> Array[(K, V)]
update[K : Hash + Eq, V](Self[K, V], K, (V?) -> V) -> V
values[K, V](Self[K, V]) -> Iter[V]
}
impl[K, V] Default for Map[K, V]
Expand Down
11 changes: 7 additions & 4 deletions builtin/iter.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -1029,10 +1029,13 @@ pub fn Iter::group_by[T, K : Eq + Hash](
let result = Map::new()
for element in self {
let key = f(element)
match result.get(key) {
Some(arr) => result.set(key, arr + [element])
None => result.set(key, [element])
}
let _ = result.update(key, fn(arr) {
match arr {
Some(existing) => existing + [element]
None => [element]
}
})

}
result
}
53 changes: 53 additions & 0 deletions builtin/linked_hash_map.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,59 @@ pub fn Map::get_or_init[K : Hash + Eq, V](
}
}

///|
/// Updates the value associated with a key in the map using a function.
///
/// This method applies a function to the current value associated with the key
/// (or `None` if the key does not exist) and sets the key to the result.
///
/// # Parameters
///
/// * `self` - The map to update.
/// * `key` - The key whose value will be updated.
/// * `f` - A function that takes the current value (or `None`) and returns the new value.
///
/// # Returns
///
/// The new value associated with the key after the update.
///
/// # Example
///
/// ```moonbit
/// test "Map::update" {
/// let map = { "a": 1, "b": 2 }
///
/// // Update an existing key
/// let result1 = map.update("a", fn(v) {
/// match v {
/// Some(val) => val * 2
/// None => 0
/// }
/// })
/// assert_eq!(result1, 2)
/// assert_eq!(map.get("a"), Some(2))
///
/// // Update a non-existing key
/// let result2 = map.update("c", fn(v) {
/// match v {
/// Some(val) => val
/// None => 42
/// }
/// })
/// assert_eq!(result2, 42)
/// assert_eq!(map.get("c"), Some(42))
/// }
/// ```
pub fn Map::update[K : Hash + Eq, V](
self : Map[K, V],
key : K,
f : (V?) -> V
) -> V {
let value = f(self.get(key))
self.set(key, value)
value
}

Comment on lines +308 to +317
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal was to be efficient, which is not the case with this implementation.

I think you can copy the code of set and modify the line curr_entry.value = value to curr_entry.value = f(Some(value)) and the let entry = { .., value, ..} to let entry = { .., value : f(None), .. }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this:

  if self.size >= self.growAt {
    self.grow()
  }
  let hash = key.hash()
  let (idx, psl) = for psl = 0, idx = hash & self.capacity_mask {
    match self.entries[idx] {
      None => break (idx, psl)
      Some(curr_entry) => {
        if curr_entry.hash == hash && curr_entry.key == key {
          let new_value = f(Some(value))
          curr_entry.value = new_value
          return new_value
        }
        if psl > curr_entry.psl {
          self.push_away(idx, curr_entry)
          break (idx, psl)
        }
        continue psl + 1, (idx + 1) & self.capacity_mask
      }
    }
  }
  let new_value = f(None)
  let entry = { prev: self.tail, next: None, psl, key, value: new_value, hash }
  self.add_entry_to_tail(idx, entry)
  return new_value

///|
/// Check if the hash map contains a key.
pub fn Map::contains[K : Hash + Eq, V](self : Map[K, V], key : K) -> Bool {
Expand Down
127 changes: 127 additions & 0 deletions builtin/linked_hash_map_wbtest.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -887,3 +887,130 @@ test "op_equal_key_value_mismatch" {
// This should return false as the values are different
assert_eq!(map1 == map2, false)
}

///|
test "Map::update basic functionality" {
let map = { "a": 1, "b": 2 }
let _ = map.update("a", fn(v) {
match v {
Some(val) => val * 2
None => 0
}
})
assert_eq!(map.get("a"), Some(2))
let _ = map.update("c", fn(v) {
match v {
Some(val) => val
None => 42
}
})
assert_eq!(map.get("c"), Some(42))
}

///|
test "Map::update with complex operations" {
let map = { "counter": 0 }
let _ = map.update("counter", fn(v) {
match v {
Some(val) => val + 1
None => 1
}
})
assert_eq!(map.get("counter"), Some(1))

// Increment again
let _ = map.update("counter", fn(v) {
match v {
Some(val) => val + 1
None => 1
}
})
assert_eq!(map.get("counter"), Some(2))

// Reset counter
let _ = map.update("counter", fn(_) { 0 })
assert_eq!(map.get("counter"), Some(0))
}

///|
test "Map::update with different value types" {
let map : Map[String, Array[Int]] = {}
let res1 = map.update("numbers", fn(v) {
match v {
Some(arr) => {
let new_arr = []
for i in arr {
new_arr.push(i)
}
new_arr.push(1)
new_arr
}
None => [1]
}
})
assert_eq!(res1[0], 1)
assert_eq!(map.get("numbers").unwrap().length(), 1)

// Add more elements
let res2 = map.update("numbers", fn(v) {
match v {
Some(arr) => {
let new_arr = []
for i in arr {
new_arr.push(i)
}
new_arr.push(2)
new_arr.push(3)
new_arr
}
None => [2, 3]
}
})
assert_eq!(res2, [1, 2, 3])
assert_eq!(map.get("numbers").unwrap().length(), 3)
}

///|
test "Map::update with multiple operations" {
let map = {}

// Initialize multiple keys
let _ = map.update("a", fn(_) { 1 })
let _ = map.update("b", fn(_) { 2 })
let _ = map.update("c", fn(_) { 3 })
assert_eq!(map.size(), 3)
assert_eq!(map.get("a"), Some(1))
assert_eq!(map.get("b"), Some(2))
assert_eq!(map.get("c"), Some(3))

// Update all values at once
for k in ["a", "b", "c"] {
let _ = map.update(k, fn(v) {
match v {
Some(val) => val * 10
None => 0
}
})

}
assert_eq!(map.get("a"), Some(10))
assert_eq!(map.get("b"), Some(20))
assert_eq!(map.get("c"), Some(30))
}

///|
test "update preserves order" {
let map = {}

// Add keys in specific order
let _ = map.update("first", fn(_) { 1 })
let _ = map.update("second", fn(_) { 2 })
let _ = map.update("third", fn(_) { 3 })

// Update middle key
let _ = map.update("second", fn(_) { 22 })

// Check that order is preserved
let keys = map.keys().collect()
assert_eq!(keys, ["first", "second", "third"])
}