2

I'm trying to write a function (or the best R alternative), given a deck vector and a hand vector, that takes the first element from the deck and adds it to the hand.

# initialize the deck.  We don't care about suits, so modulus 13
deck <- sample(52, 52, replace = FALSE, prob = NULL) %% 13 + 1

# initilize the hand
hand <- vector(mode="numeric", length=0)

#deal a card from the deck to the hand.
# it's these two lines I'd like to put in something like a function 
#   and return the modified vectors
  phand <- c(hand, deck[1])
  deck <- deck[-1]

In something like Python, you might do it like this:

def dealcard(deck,hand)

   # do something to deck and hand
   return deck, hand

deck,hand = dealcard(deck,hand)

Looking at the first answer to this: modify variable within R function

There are ways as @Dason showed, but really - you shouldn't!

The whole paradigm of R is to "pass by value". @Rory just posted the normal way to handle it - just return the modified value...

So what is the most R-thonic way to accomplish this "modifying multiple arguments to a function"?

I could see accomplishing something similar with a matrix where one column uses numbers to indicate which hand a row/card is dealt to, but it would make the code much less intuitive. It's easy to imagine a function:

score <- ScoreFunction(DealerHand)

vs.:

score <- ScoreFunction(DeckMatrix, 1)
Community
  • 1
  • 1
  • 5
    Return a list. First element is `deck`, second is `hand` (or vice versa). – Alex A. Apr 15 '15 at 21:39
  • Thank you! Is there a way to do this in one line, similar to the Python example? Or do I need to create a new list variable to hold the returned list so I can parse it out? (3 lines vs 1)? – Dale Frakes Apr 15 '15 at 21:50
  • Just always keep it in a list. Or even create an S3 class extending the list class. – Gregor Thomas Apr 15 '15 at 21:54
  • 1
    Otherwise no, R doesn't support multiple assignment syntax such as `deck,hand = dealcard(deck, hand)`. – Gregor Thomas Apr 15 '15 at 21:57

1 Answers1

3

Short answer

You can return a list from your function.

Simple example

dealcard <- function(deck, hand) {
    # There needs to be a card to deal
    stopifnot(length(deck) > 0)

    # Add the card from the top of the deck to the hand
    hand <- c(hand, deck[1])

    # Remove the card from the deck
    deck <- deck[-1]

    # Create a named list from the modified inputs
    output <- list(deck=deck, hand=hand)

    return(output)
}

But note that this approach does not actually modify the deck and hand objects in the global environment. (You can do that using <<-, but in most cases you shouldn't.) So you can do this instead:

# Initialize deck and hand as a list
# Full deck, empty hand
deck.hand <- list(deck=sample(52, 52, replace=FALSE), hand=NULL)

# Deal a card
deck.hand <- with(deck.hand, dealcard(deck, hand))

Now if you look at deck.hand, you'll see that the deck and hand items have been updated appropriately.

More involved example

This can be easily expanded to deal to a certain player when there are multiple players in the same list.

# Initialize the game
# Three players, everyone starts with empty hands
game <- list(deck=sample(52, 52, replace=FALSE) %% 13 + 1,
             dealer=NULL, sally=NULL, john=NULL)


dealcard <- function(game, player, deck="deck") {
    # Make sure the player specified is in the game
    stopifnot(player %in% names(game) && deck %in% names(game))

    # There needs to be a card to deal
    stopifnot(length(game[[deck]]) > 0)

    # Give the next card to the specified player
    game[[player]] <- c(game[[player]], game[[deck]][1])

    # Remove the card from the deck
    game[[deck]] <- game[[deck]][-1]

    # Return the new state of the game
    return(game)
}

# Give everyone a card
game <- dealcard(game, "dealer")
game <- dealcard(game, "sally")
game <- dealcard(game, "john")

This uses the syntax list[["itemname"]]. The list item is passed as a character string. This is equivalent to extracting the item using $, i.e. list$itemname.

Here we're passing in the whole list, modifying only the necessary parts of it, and returning the whole modified list. Setting the original list equal to the returned value is just like modifying the list in place.

Now if you look at the contents of game, each player will have one card and the deck will be missing those that were distributed.

Extensions for fun

I got a little too into this.

Want to deal five cards to each player in succession?

players <- names(game)[names(game) != "deck"]

for (i in 1:5) {
    for (player in players) {
        game <- dealcard(game, player)
    }
}

Take a look at game again. Each player has 5 cards, and since the loops are structured in this way, the dealing order mimics that of a standard card game.

You can return a card to the deck by using "deck" as the player and the player as the deck, like so:

# John returns his first card to the deck
game <- dealcard(game, player="deck", deck="john")

Given a function that scores a hand in some way, you can obtain the score for each player easily.

# Example scoring
scorehand <- function(game, player) {
    return(sum(game[[player]]))
}

sapply(players, scorehand, game=game)

That will show you the current score for each player by applying the scorehand() function to each element of the vector players, which consists of the names of the players.

TL;DR

Use a list.

Community
  • 1
  • 1
Alex A.
  • 5,466
  • 4
  • 26
  • 56
  • Thank you - this is very helpful. I can't upvote because my account is new. This list method may work well, but I'll have to see how it would handle multiple hands from the same deck. – Dale Frakes Apr 15 '15 at 23:54
  • @DaleFrakes: Take a look now. – Alex A. Apr 16 '15 at 20:12
  • Thank you for your updated response and solutions - pretty cool stuff! I took your advice and in another part of this program adopted "return a list from a function" for multiple arguments. I still can't vote for your solution, but I did mark it answered. – Dale Frakes Apr 17 '15 at 23:32
  • @DaleFrakes: I'm glad you were able to get this working for you! – Alex A. Apr 18 '15 at 01:50