Resetting only part of a HashMap stable variable

Brief Context

The task should be simple in theory, but I just can’t figure out how to do it. I have a HashMap that stores player scores. There are 3 types of scores for each player that have the same structure saved in a stable variable. 2 of these I don’t want to touch while I’d like to empty / reset one of them.

Code being used in production

The structure is the following:

private stable var _allTimePlayerScores : [(Principal, PlayerHistoricalStats)] = [];

private var allTimePlayerScores : HashMap.HashMap<Principal, PlayerHistoricalStats> =
    HashMap.fromIter(_allTimePlayerScores.vals(), 0, Principal.equal, Principal.hash);

The types mentioned above:

// historical stats is compressed stats without battle records
public type HistoricalStats = {
    wins: Nat;
    losses: Nat;
    points: Nat;
};

// player historical stats contains all historical stats for a player
public type PlayerHistoricalStats = {
    arenaStats: HistoricalStats;
    tournamentStats: HistoricalStats;
    botStats: HistoricalStats;
};

Pseudo code

My intuition would say, just loop through and set everything to 0. Or replace already existing list array an empty array.

for (userScore in userScores) {
    userScore.tournamentStats.wins := 0
    userScore.tournamentStats.losses := 0
    userScore.tournamentStats.points := 0
}

Updated Code

public shared ({caller}) func resetTournamentScores () : async () {
    for ((k, v) in allTimePlayerScores.entries()) {
        v.tournamentStats.wins := 0;
    };
    return;
};

For which I’m getting the error: type error [M0073], expected mutable assignment target

To anyone taking the time to read through and help me think of solutions: THANK YOU!

Solution

   public shared ({caller}) func resetTournamentScores () : async () {

        allTimePlayerScores := HashMap.map<Principal, PlayerHistoricalStats, PlayerHistoricalStats>(allTimePlayerScores, Principal.equal, Principal.hash, func (k, v) {
            Debug.print("[Before]: " # debug_show(v));

            let updatedScores : PlayerHistoricalStats = {
                arenaStats = v.arenaStats;
                tournamentStats = {
                    wins : Nat = 0;
                    losses : Nat = 0;
                    points : Nat = 0};
                botStats = v.botStats;
            };

            Debug.print("[After]: " # debug_show(updatedScores));
            return updatedScores;
        });

        return;

    };

I’d like to finish up by thanking everyone in the comment section! All of these answers were incredibly helpful!

1 Like

Hi,

If I understood correctly, I would use the map function of the HashMap. You will get a new hashmap with reseted content and unmodified content.

map

1 Like

That sounds like it. Do you have a working example of something similar somewhere? If I understand this correctly, the mapping function would copy over the hashmap while allowing me to apply a function. And in this function I should specify if the key is A, just copy, if key is B, then add empty array instead?

Maybe that gist will help

Gist HashMap.mapFilter
or
in Playground

1 Like

Thank you for creating this snippet! When I run your code I don’t see any of the prints that you’ve put in, do you know why that can be? Is it because of something related to Playground?

Also a related question, in for example Node.js of Python if I want to print a variable no matter it’s structure I can just put it into a print statement just for me to see its structure. In Motoko this seems to be different. Is there a way to print _allTimePlayerScores or allTimePlayerScores in the code I mentioned in the original post?

For your first question, yes, because as I know playground don’t display debug message. I did that gist after trying and learning how to use mapfilter and I did it into my own canister. I save it as reference if I need it later.

Second question: No you need to concatenate the Text something like:

Debug.print("2] " # Nat.toText(k) # "-" # v);

or using

import D "mo:base/Debug";
D.print(debug_show(("hello", 42, "world")))

see

or doc. and search for text operator

1 Like

Based on your two answers I got a lot closer. Now it’s a matter of modifying a stable variable. Which I’m clearly doing wrong.

public shared ({caller}) func resetTournamentScores () : async () {
    for ((k, v) in allTimePlayerScores.entries()) {
        v.tournamentStats.wins := 0;
        v.tournamentStats.losses := 0;
        v.tournamentStats.points := 0;
    };
    return;
};

Apparently I can’t reassign values for stable variables so simply, which makes sense, but what I’d like to do is shown above. I do not wish to remove anything from the stable variable, only set to 0 certain parts of the object, shown above.

The error received is: type error [M0073], expected mutable assignment target

The variable I’m trying to modify was created with var, so I’m not quite sure what’s immutable here.

I think because you’re trying to modify a value provided by entries(), which is probably immutable.

I expect something like this should work (pseudo code):

allTimePlayerScores := HashMap.map(allTimePlayerScores, …, …, func (k, v) {
  // return a new v here
});
1 Like

Yes, that makes a lot of sense. Do you know a working example somewhere? I’m not familiar how mapping functions work in Motoko, and I’m sure unless all. the types are matching it won’t even let me make small "Debug.print"s until I figure it out exactly.

The …s are supposed to be types? The documentation is not very clear how it should be used (for noobs like me at least)

They represent the keyEq and keyHash arguments. See the documentation for HashMap.map:

In your code you supplied Principal.equal and Principal.hash when you created the hash map.

1 Like

So in theory something like this should work, right? Of course it leaves the HashMap unchanged

public shared ({caller}) func resetTournamentScores () : async () {
    allTimePlayerScores := HashMap.map(allTimePlayerScores, Principal.equal, Principal.hash, func (k, v) {
        Debug.print("[1]: " # debug_show(v));
        return v;
    });
    return;
};

This produces the error: type error [M0103], cannot infer type of variable

I think you probably need to either:

  1. Provide type arguments to HashMap.map, i.e. HashMap.map<Principal, PlayerHistoricalStats, PlayerHistoricalStats>

or

  1. Add type annotations to the mapping function, i.e. func (k : Principal, v : PlayerHistoricalStats) -> PlayerHistoricalStats {
2 Likes

Thank you so much! Combining your multiple answers, I managed to create a working solution. I’ll edit the original post to reflect the solution.