General Approach

My general approach to song-listen modeling is multiplicative. That is I’m going to start with a vector that lasts 16 weeks in which the song is listened to the same number of times every week. Then I am going to MULTIPY that vector by other vectors that are centered on 1.0, because multiplying by 1 will have no effect. The other vectors are:

  1. a declining vector, from 1 to 0, so the listens fade out
  2. a trend vector, which multiplies song listens in certain weeks
  3. a noise vector, so it doesn’t all look too smooth

Constants

Here are constants that drive the game simulation

library(tidyverse)
MAX_DURATION <- 16 ## maximum number of weeks a song lasts
#how many songs to make
N_SONGS <- 6
SONG_NAMES <- c("Rockin","Rollin","Hippin","Hoppen","Jazz","DeFunk")
#If true, the song responds to the trend
SONG_ON_TREND <- c(TRUE, FALSE, FALSE, TRUE, FALSE, FALSE)
#how many listens each starts at
LISTENS_START_AT <- c(40,40,60,25,20,30)
#how long the duration of each song is, in weeks
SONG_DURATIONS <- c(12,5,15,16,3,10)

Generate Decline

Function to generate a linear decline from 1 to zero over a certain number of weeks, staying at zero until a max number of weeks. Later we can deal with a curving decline. The graph shows the generic decline

generate_decline <- function(last_week,max_weeks) {
  #first make a sequence that declines from a first value to zero
  #we make it one too long and then clip the last value, which is zero
  #this gives us all non-zero values
  result <- seq(1,0,length.out=last_week+1)[c(1:last_week)]
  #then if its too short, we add zeros to the end
  if (last_week < max_weeks) result <- c(result,rep(0,length.out=max_weeks-last_week))
  result
}
linear_decline <- generate_decline(5,10)
plot(linear_decline,type="l")

Trend Boost

A trend boost is the boost a song gets if it is “on trend” in a given week. Eventually there could be different trends; for now, just one. For now, I’m going to say the trend kicks in week 6 and lasts 4 weeks – and it triples the listens for songs that are on trend. It is just a vector of 16 numbers with 1.0 for the normal weeks and 3.0 for the trend weeks.

trend_boost <- c(rep(1,length.out=5),
                 rep(3,length.out=4),
                 rep(1,length.out=7))
barplot(trend_boost)

Generate a data frame with song listen data

# our data frame has columns for the song name, what week it is, listens and cumulative listens
song_listens_df = data.frame(song_name=character(),
                             week=integer(),
                             listens = integer(), 
                             cumulative_listens=integer())
# populate the song listens data frame
for(song in 1:N_SONGS) {
  
 # all songs start out about the same (randomness will make them vary a bit)
 start_at <- LISTENS_START_AT[song]
 
 # randomness is a vector with a random multiplier for each week
 randomness <- rnorm(MAX_DURATION,mean=1,sd=.25)
 
 # this is a vector of declining values
 declining <- generate_decline(SONG_DURATIONS[song],MAX_DURATION)
 
 # of vector of listens multiplies a flat line at the start value by randomness by declining
 listens <- start_at * randomness * declining
 
 # add the effect of the trend, if the song is effected by the trend
 if (SONG_ON_TREND[song]) listens <- trend_boost * listens
 
 #make these into integers
 listens <- floor (listens)
                      
 # we use this built-in R function, cumsum, to accumulate the listens
 cum_listens <- cumsum(listens)
 #ok, now we can make a dataframe for this song, and add it to our overall dataframe
 new_df = data.frame(song_name = SONG_NAMES[song],
                      week = 1:MAX_DURATION,
                      listens = listens,
                      cumulative_listens = cum_listens)
 song_listens_df <- rbind(song_listens_df,new_df)
}
song_listens_df

plot weekly listens

First, I’ll plot it the way Matthew did, but I have to say I like the column plot with the smoothed function better! Can you see which songs are getting a boost in week 6?

ggplot(data=song_listens_df,aes(x=week,y=listens,color=song_name)) +
        geom_line() + 
        coord_cartesian(ylim=c(0,100)) +
        facet_wrap( ~ song_name)

ggplot(data=song_listens_df,aes(x=week,y=listens,fill=song_name)) +
        geom_col() + geom_smooth(fill=NA,color="dimgray",method="loess") +
        coord_cartesian(ylim=c(0,100)) +
        facet_wrap( ~ song_name)

plot cumuluative listens

Can you see which songs are getting a boost in week 6? Look for the steeper slopes from week 6 to 10.

ggplot(data=song_listens_df,aes(x=week,y=cumulative_listens,color=song_name)) +
        geom_line(size=1) +
        coord_cartesian(ylim=c(0,800))

Showing where the boost is

Does graphing where the trend boost is at the bottom help? Note only two songs respond to the boost, though it shows in every plot. Who is responding?

trend_df <- data.frame(week=1:16,trend=trend_boost-1)
ggplot() +
        geom_col(data=song_listens_df,aes(x=week,y=listens,fill=song_name)) + 
        geom_smooth(data=song_listens_df,aes(x=week,y=listens,fill=song_name),
                    fill=NA,color="dimgray",method="loess") +
         geom_col(data=trend_df,aes(x=week,y=trend)) +
        coord_cartesian(ylim=c(0,80)) +
        facet_wrap( ~ song_name)

ggplot() +
        geom_line(data=song_listens_df,
                  aes(x=week,y=cumulative_listens,color=song_name),size = 1) +
        geom_col(data=trend_df,aes(x=week,y=trend)) +
        coord_cartesian(ylim=c(0,800))

And the answer is….

The songs that respond to the boost are Rockin and Hoppen. Hippen looks like it does (steep slope in the relevant weeks, but its just a song that’s doing well over all). Jazz is the world’s best music (that no one listens to). Rollin burned out quickly; DeFunk started out the same as jazz, but it lasted a lot longer in the market.

LS0tCnRpdGxlOiAiTXVzaWMgU3R1ZGlvIEdhbWUgLS0gU2ltdWxhdGVkIERhdGEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgR2VuZXJhbCBBcHByb2FjaApNeSBnZW5lcmFsIGFwcHJvYWNoIHRvIHNvbmctbGlzdGVuIG1vZGVsaW5nIGlzIG11bHRpcGxpY2F0aXZlLiBUaGF0IGlzIEknbSBnb2luZyB0byBzdGFydCB3aXRoIGEgdmVjdG9yIHRoYXQgbGFzdHMgMTYgd2Vla3MgaW4gd2hpY2ggdGhlIHNvbmcgaXMgbGlzdGVuZWQgdG8gdGhlIHNhbWUgbnVtYmVyIG9mIHRpbWVzIGV2ZXJ5IHdlZWsuIFRoZW4gSSBhbSBnb2luZyB0byBNVUxUSVBZIHRoYXQgdmVjdG9yIGJ5IG90aGVyIHZlY3RvcnMgdGhhdCBhcmUgY2VudGVyZWQgb24gMS4wLCBiZWNhdXNlIG11bHRpcGx5aW5nIGJ5IDEgd2lsbCBoYXZlIG5vIGVmZmVjdC4gVGhlIG90aGVyIHZlY3RvcnMgYXJlOgoKCjEuIGEgZGVjbGluaW5nIHZlY3RvciwgZnJvbSAxIHRvIDAsIHNvIHRoZSBsaXN0ZW5zIGZhZGUgb3V0CjIuIGEgdHJlbmQgdmVjdG9yLCB3aGljaCBtdWx0aXBsaWVzIHNvbmcgbGlzdGVucyBpbiBjZXJ0YWluIHdlZWtzCjMuIGEgbm9pc2UgdmVjdG9yLCBzbyBpdCBkb2Vzbid0IGFsbCBsb29rIHRvbyBzbW9vdGgKCiMgQ29uc3RhbnRzCkhlcmUgYXJlIGNvbnN0YW50cyB0aGF0IGRyaXZlIHRoZSBnYW1lIHNpbXVsYXRpb24KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCgojIG1heGltdW0gbnVtYmVyIG9mIHdlZWtzIGEgc29uZyBsYXN0cywgZS5nLiB0aGUgbGVuZ3RoIG9mIHRoZSBzaW11bGF0aW9uIGluIHdlZWtzCk1BWF9EVVJBVElPTiA8LSAxNiAKCiNob3cgbWFueSBzb25ncyB0byBtYWtlCk5fU09OR1MgPC0gNgoKI25hbWUgdG8gdXNlIGZvciB0aGUgc29uZ3MKU09OR19OQU1FUyA8LSBjKCJSb2NraW4iLCJSb2xsaW4iLCJIaXBwaW4iLCJIb3BwZW4iLCJKYXp6IiwiRGVGdW5rIikKCiNJZiB0cnVlLCB0aGUgc29uZyByZXNwb25kcyB0byB0aGUgdHJlbmQKI1NvIFJvY2tpbiBhbmQgSG9wcGVuIHNob3VsZCByZXNwb25kClNPTkdfT05fVFJFTkQgPC0gYyhUUlVFLCBGQUxTRSwgRkFMU0UsIFRSVUUsIEZBTFNFLCBGQUxTRSkKCgojaG93IG1hbnkgbGlzdGVucyBlYWNoIHNvbmcgc3RhcnRzIGF0LCBpbiB3ZWVrIG9uZQpMSVNURU5TX1NUQVJUX0FUIDwtIGMoNDAsNDAsNjAsMjUsMjAsMzApCgojaG93IGxvbmcgdGhlIGR1cmF0aW9uIG9mIGVhY2ggc29uZyBpcywgaW4gd2Vla3MKU09OR19EVVJBVElPTlMgPC0gYygxMiw1LDE1LDE2LDMsMTApCgpgYGAKCgojIEdlbmVyYXRlIERlY2xpbmUgCgpGdW5jdGlvbiB0byBnZW5lcmF0ZSBhIGxpbmVhciBkZWNsaW5lIGZyb20gMSB0byB6ZXJvIG92ZXIgYSBjZXJ0YWluIG51bWJlciBvZiB3ZWVrcywgc3RheWluZyBhdCB6ZXJvIHVudGlsIGEgbWF4IG51bWJlciBvZiB3ZWVrcy4gTGF0ZXIgd2UgY2FuIGRlYWwgd2l0aCBhIGN1cnZpbmcgZGVjbGluZS4gVGhlIGdyYXBoIHNob3dzIHRoZSBnZW5lcmljIGRlY2xpbmUKCmBgYHtyfQpnZW5lcmF0ZV9kZWNsaW5lIDwtIGZ1bmN0aW9uKGxhc3Rfd2VlayxtYXhfd2Vla3MpIHsKICAjZmlyc3QgbWFrZSBhIHNlcXVlbmNlIHRoYXQgZGVjbGluZXMgZnJvbSBhIGZpcnN0IHZhbHVlIHRvIHplcm8KICAjd2UgbWFrZSBpdCBvbmUgdG9vIGxvbmcgYW5kIHRoZW4gY2xpcCB0aGUgbGFzdCB2YWx1ZSwgd2hpY2ggaXMgemVybwogICN0aGlzIGdpdmVzIHVzIGFsbCBub24temVybyB2YWx1ZXMKICByZXN1bHQgPC0gc2VxKDEsMCxsZW5ndGgub3V0PWxhc3Rfd2VlaysxKVtjKDE6bGFzdF93ZWVrKV0KICAjdGhlbiBpZiBpdHMgdG9vIHNob3J0LCB3ZSBhZGQgemVyb3MgdG8gdGhlIGVuZAogIGlmIChsYXN0X3dlZWsgPCBtYXhfd2Vla3MpIHJlc3VsdCA8LSBjKHJlc3VsdCxyZXAoMCxsZW5ndGgub3V0PW1heF93ZWVrcy1sYXN0X3dlZWspKQogIHJlc3VsdAp9CgojdGhpcyBmdW5jdGlvbiB3aWxsIHBpY2sgYSByYW5kb20gZHVyYXRpb24gZm9yIG91ciBzb25nLCBpbiB3ZWVrcwpSYW5kRHVyYXRpb24gPC0gZnVuY3Rpb24oKSB7CiAgcm91bmQocnVuaWYoMSxNSU5fRFVSQVRJT04sTUFYX0RVUkFUSU9OKSkKfQoKbGluZWFyX2RlY2xpbmUgPC0gZ2VuZXJhdGVfZGVjbGluZSg1LDEwKQpwbG90KGxpbmVhcl9kZWNsaW5lLHR5cGU9ImwiKQpgYGAKCiMgVHJlbmQgQm9vc3QKCkEgdHJlbmQgYm9vc3QgaXMgdGhlIGJvb3N0IGEgc29uZyBnZXRzIGlmIGl0IGlzICJvbiB0cmVuZCIgaW4gIGEgZ2l2ZW4gd2Vlay4gRXZlbnR1YWxseSB0aGVyZSBjb3VsZCBiZSBkaWZmZXJlbnQgdHJlbmRzOyBmb3Igbm93LCBqdXN0IG9uZS4gRm9yIG5vdywgSSdtIGdvaW5nIHRvIHNheSB0aGUgdHJlbmQga2lja3MgaW4gd2VlayA2IGFuZCBsYXN0cyA0IHdlZWtzIC0tIGFuZCBpdCB0cmlwbGVzIHRoZSBsaXN0ZW5zIGZvciBzb25ncyB0aGF0IGFyZSBvbiB0cmVuZC4gSXQgaXMganVzdCBhIHZlY3RvciBvZiAxNiBudW1iZXJzIHdpdGggMS4wIGZvciB0aGUgbm9ybWFsIHdlZWtzIGFuZCAzLjAgZm9yIHRoZSB0cmVuZCB3ZWVrcy4KCmBgYHtyfQp0cmVuZF9ib29zdCA8LSBjKHJlcCgxLGxlbmd0aC5vdXQ9NSksCiAgICAgICAgICAgICAgICAgcmVwKDMsbGVuZ3RoLm91dD00KSwKICAgICAgICAgICAgICAgICByZXAoMSxsZW5ndGgub3V0PTcpKQpiYXJwbG90KHRyZW5kX2Jvb3N0KQpgYGAKCgoKCiMgR2VuZXJhdGUgYSBkYXRhIGZyYW1lIHdpdGggc29uZyBsaXN0ZW4gZGF0YQojIAoKYGBge3J9CiMgb3VyIGRhdGEgZnJhbWUgaGFzIGNvbHVtbnMgZm9yIHRoZSBzb25nIG5hbWUsIHdoYXQgd2VlayBpdCBpcywgbGlzdGVucyBhbmQgY3VtdWxhdGl2ZSBsaXN0ZW5zCnNvbmdfbGlzdGVuc19kZiA9IGRhdGEuZnJhbWUoc29uZ19uYW1lPWNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlZWs9aW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbnMgPSBpbnRlZ2VyKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1bXVsYXRpdmVfbGlzdGVucz1pbnRlZ2VyKCkpCgoKCiMgcG9wdWxhdGUgdGhlIHNvbmcgbGlzdGVucyBkYXRhIGZyYW1lLiBUaGUgZm9yIGxvb3AgbWFrZXMgbiBzb25ncy4KZm9yKHNvbmcgaW4gMTpOX1NPTkdTKSB7CiAgCiAjIGFsbCBzb25ncyBzdGFydCBvdXQgYWJvdXQgdGhlIHNhbWUgaW4gdGVybXMgb2YgbGlzdGVucwogc3RhcnRfYXQgPC0gTElTVEVOU19TVEFSVF9BVFtzb25nXQogCiAjIHJhbmRvbW5lc3MgaXMgYSB2ZWN0b3Igd2l0aCBhIHJhbmRvbSBtdWx0aXBsaWVyIGZvciBlYWNoIHdlZWsKIHJhbmRvbW5lc3MgPC0gcm5vcm0oTUFYX0RVUkFUSU9OLG1lYW49MSxzZD0uMjUpCiAKICMgdGhpcyBpcyBhIHZlY3RvciBvZiBkZWNsaW5pbmcgdmFsdWVzCiBkZWNsaW5pbmcgPC0gZ2VuZXJhdGVfZGVjbGluZShTT05HX0RVUkFUSU9OU1tzb25nXSxNQVhfRFVSQVRJT04pCiAKICMgb3VyIHZlY3RvciBvZiBsaXN0ZW5zIG11bHRpcGxpZXMgYSBmbGF0IGluaXRpYWwgdmFsdWUgYnkgcmFuZG9tbmVzcyBieSBkZWNsaW5pbmcKIGxpc3RlbnMgPC0gc3RhcnRfYXQgKiByYW5kb21uZXNzICogZGVjbGluaW5nCiAKICMgYWRkIHRoZSBlZmZlY3Qgb2YgdGhlIHRyZW5kLCBpZiB0aGUgc29uZyBpcyBlZmZlY3RlZCBieSB0aGUgdHJlbmQsIGFnYWluIG11bHRpcGx5aW5nCiBpZiAoU09OR19PTl9UUkVORFtzb25nXSkgbGlzdGVucyA8LSB0cmVuZF9ib29zdCAqIGxpc3RlbnMKIAogI21ha2UgdGhlc2UgaW50byBpbnRlZ2VycwogbGlzdGVucyA8LSBmbG9vciAobGlzdGVucykKICAgICAgICAgICAgICAgICAgICAgIAogIyB3ZSB1c2UgdGhpcyBidWlsdC1pbiBSIGZ1bmN0aW9uLCBjdW1zdW0sIHRvIGFjY3VtdWxhdGUgdGhlIGxpc3RlbnMKIGN1bV9saXN0ZW5zIDwtIGN1bXN1bShsaXN0ZW5zKQoKICNvaywgbm93IHdlIGNhbiBtYWtlIGEgZGF0YWZyYW1lIGZvciB0aGlzIHNvbmcsIGFuZCBhZGQgaXQgdG8gb3VyIG92ZXJhbGwgZGF0YWZyYW1lCiBuZXdfZGYgPSBkYXRhLmZyYW1lKHNvbmdfbmFtZSA9IFNPTkdfTkFNRVNbc29uZ10sCiAgICAgICAgICAgICAgICAgICAgICB3ZWVrID0gMTpNQVhfRFVSQVRJT04sCiAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5zID0gbGlzdGVucywKICAgICAgICAgICAgICAgICAgICAgIGN1bXVsYXRpdmVfbGlzdGVucyA9IGN1bV9saXN0ZW5zKQogCiBzb25nX2xpc3RlbnNfZGYgPC0gcmJpbmQoc29uZ19saXN0ZW5zX2RmLG5ld19kZikKIAp9CgojdGhpcyBpcyB0aGUgcmVzdWx0aW5nIG4tc29uZyBkYXRhIGZyYW1lCnNvbmdfbGlzdGVuc19kZgpgYGAKCgojcGxvdCB3ZWVrbHkgbGlzdGVucwoKRmlyc3QsIEknbGwgcGxvdCBpdCB0aGUgd2F5IE1hdHRoZXcgZGlkLCBidXQgSSBoYXZlIHRvIHNheSBJIGxpa2UgdGhlIGNvbHVtbiBwbG90IHdpdGggdGhlIHNtb290aGVkIGZ1bmN0aW9uIGJldHRlciEgQ2FuIHlvdSBzZWUgd2hpY2ggc29uZ3MgYXJlIGdldHRpbmcgYSBib29zdCBpbiB3ZWVrIDY/CgpgYGB7cn0KZ2dwbG90KGRhdGE9c29uZ19saXN0ZW5zX2RmLGFlcyh4PXdlZWsseT1saXN0ZW5zLGNvbG9yPXNvbmdfbmFtZSkpICsKICAgICAgICBnZW9tX2xpbmUoKSArIAogICAgICAgIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoMCwxMDApKSArCiAgICAgICAgZmFjZXRfd3JhcCggfiBzb25nX25hbWUpCmBgYAoKCmBgYHtyfQoKZ2dwbG90KGRhdGE9c29uZ19saXN0ZW5zX2RmLGFlcyh4PXdlZWsseT1saXN0ZW5zLGZpbGw9c29uZ19uYW1lKSkgKwogICAgICAgIGdlb21fY29sKCkgKyAKICAgICAgICBnZW9tX3Ntb290aChmaWxsPU5BLGNvbG9yPSJkaW1ncmF5IixtZXRob2Q9ImxvZXNzIikgKwogICAgICAgIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoMCwxMDApKSArCiAgICAgICAgZmFjZXRfd3JhcCggfiBzb25nX25hbWUpCmBgYAoKCiNwbG90IGN1bXVsdWF0aXZlIGxpc3RlbnMKCkNhbiB5b3Ugc2VlIHdoaWNoIHNvbmdzIGFyZSBnZXR0aW5nIGEgYm9vc3QgaW4gd2VlayA2PyBMb29rIGZvciB0aGUgc3RlZXBlciBzbG9wZXMgZnJvbSB3ZWVrIDYgdG8gMTAuCgpgYGB7cn0KZ2dwbG90KGRhdGE9c29uZ19saXN0ZW5zX2RmLGFlcyh4PXdlZWsseT1jdW11bGF0aXZlX2xpc3RlbnMsY29sb3I9c29uZ19uYW1lKSkgKwogICAgICAgIGdlb21fbGluZShzaXplPTEpICsKICAgICAgICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsODAwKSkKYGBgCiMgU2hvd2luZyB3aGVyZSB0aGUgYm9vc3QgaXMKCkRvZXMgZ3JhcGhpbmcgd2hlcmUgdGhlIHRyZW5kIGJvb3N0IGlzIGF0IHRoZSBib3R0b20gaGVscD8gTm90ZSBvbmx5IHR3byBzb25ncyByZXNwb25kIHRvIHRoZSBib29zdCwgdGhvdWdoIGl0IHNob3dzIGluIGV2ZXJ5IHBsb3QuIFdobyBpcyByZXNwb25kaW5nPwoKCmBgYHtyfQoKdHJlbmRfZGYgPC0gZGF0YS5mcmFtZSh3ZWVrPTE6MTYsdHJlbmQ9dHJlbmRfYm9vc3QtMSkKCmdncGxvdCgpICsKICAgICAgICBnZW9tX2NvbChkYXRhPXNvbmdfbGlzdGVuc19kZixhZXMoeD13ZWVrLHk9bGlzdGVucyxmaWxsPXNvbmdfbmFtZSkpICsgCiAgICAgICAgZ2VvbV9zbW9vdGgoZGF0YT1zb25nX2xpc3RlbnNfZGYsYWVzKHg9d2Vlayx5PWxpc3RlbnMsZmlsbD1zb25nX25hbWUpLAogICAgICAgICAgICAgICAgICAgIGZpbGw9TkEsY29sb3I9ImRpbWdyYXkiLG1ldGhvZD0ibG9lc3MiKSArCiAgICAgICAgIGdlb21fY29sKGRhdGE9dHJlbmRfZGYsYWVzKHg9d2Vlayx5PXRyZW5kKSkgKwogICAgICAgIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoMCw4MCkpICsKICAgICAgICBmYWNldF93cmFwKCB+IHNvbmdfbmFtZSkKYGBgCgpgYGB7cn0KCgpnZ3Bsb3QoKSArCiAgICAgICAgZ2VvbV9saW5lKGRhdGE9c29uZ19saXN0ZW5zX2RmLAogICAgICAgICAgICAgICAgICBhZXMoeD13ZWVrLHk9Y3VtdWxhdGl2ZV9saXN0ZW5zLGNvbG9yPXNvbmdfbmFtZSksc2l6ZSA9IDEpICsKICAgICAgICBnZW9tX2NvbChkYXRhPXRyZW5kX2RmLGFlcyh4PXdlZWsseT10cmVuZCkpICsKICAgICAgICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsODAwKSkKYGBgCgojQW5kIHRoZSBhbnN3ZXIgaXMuLi4uCgpUaGUgc29uZ3MgdGhhdCByZXNwb25kIHRvIHRoZSBib29zdCBhcmUgUm9ja2luIGFuZCBIb3BwZW4uIEhpcHBlbiBsb29rcyBsaWtlIGl0IGRvZXMgKHN0ZWVwIHNsb3BlIGluIHRoZSByZWxldmFudCB3ZWVrcywgYnV0IGl0cyBqdXN0IGEgc29uZyB0aGF0J3MgZG9pbmcgd2VsbCBvdmVyIGFsbCkuIEphenogaXMgdGhlIHdvcmxkJ3MgYmVzdCBtdXNpYyAodGhhdCBubyBvbmUgbGlzdGVucyB0bykuIFJvbGxpbiBidXJuZWQgb3V0IHF1aWNrbHk7IERlRnVuayBzdGFydGVkIG91dCB0aGUgc2FtZSBhcyBqYXp6LCBidXQgaXQgbGFzdGVkIGEgbG90IGxvbmdlciBpbiB0aGUgbWFya2V0LgoKCg==