Problem in generation of world in predicate - alloy

I'm having a problem in the generation of the world for last predicate, even though there are more than 3 signatures, only 3 are shown.
What am I missing?
Do I need to specify something in pred show(){} ?
Do I need to run the predicate enforcing a specific number of objects?
open util/boolean
// Signatures
abstract sig Sensor {
available: one Bool
}
sig HeartRateSensor extends Sensor{}
sig BloodPressureSensor extends Sensor{}
sig SleepMonitoringSensor extends Sensor{}
sig Smartwatch {
user: one User,
sensors: set Sensor,
compatible: one Bool
} {
// Must have at least 3 different sensors:
// HeartRateSensor, BloodPressureSensor, SleepMonitoringSensor
all disj s, s', s'': Sensor {
((s in sensors && s' in sensors && s'' in sensors) &&
(s != s' && s' != s'' && s != s'') &&
( s.available = True && s'.available = True && s''.available = True)) <=> compatible = True
}
}
abstract sig Customer {
username: lone String,
password: lone String,
canRegister: one Bool,
isRegistered: one Bool
} {
isRegistered = True => canRegister = True
canRegister = False => isRegistered = False
#username = 0 || #password = 0 => canRegister = False
}
sig User extends Customer {
fiscalCodeOrSSN: lone String,
smartwatch: one Smartwatch,
notifications: set Notification,
acceptsDataRequestFrom: set Company
} {
(#fiscalCodeOrSSN = 0 || smartwatch.compatible = False) => canRegister = False
}
sig Company extends Customer {
paymentMethod: lone String,
queries: set Query
} {
#paymentMethod = 0 => canRegister = False
isRegistered = False => #queries = 0
}
abstract sig Query {
company: one Company
}
sig AnonQuery extends Query {
people: set User,
isValid: one Bool
} {
isValid = True <=> #people >= 5
}
sig IndividualQuery extends Query {
person: one User,
userAccepts: lone Bool, // lone cause if #userAccepts = 0 it must mean that a notification had been received by the user
} {
all u: User {
u = person => userAccepts = True else #userAccepts = 0 && #person.notifications > 0
}
}
sig Notification {
user: one User,
company: one Company
}
// Facts: Consistency
fact UsernameConsistency {
// There are no 2 Customer(s) with the same username
all disj c, c': Customer | c.username != c'.username
}
fact FiscalCodeOrSSNConsistency {
// There are no 2 User(s) with the same fiscalCodeOrSSN
all disj u, u': User | u.fiscalCodeOrSSN != u'.fiscalCodeOrSSN
}
fact SmartWatchConsistency {
// Let's suppose, wlog, that the cardinality of the relation
// between smartwatch and user is 1 to 1
all s: Smartwatch, u: User | s.user = u <=> u.smartwatch = s
}
fact QueryConsistency {
// If a query has been made by a company
// it must be in the set of all the queries
// made by the company
all q: Query, c: Company | q.company = c <=> q in c.queries
}
fact NotificationConsistency {
// If a notification has been sent to a user
// the user must have it in the set of
// all notifications
all n: Notification, u: User | n.user = u <=> n in u.notifications
}
// Assertions
// Goal G2: The system should allow users to register by providing his
// Fiscal Code or his Social Security Number, an username and a password.
assert UserCanRegister {
all u: User {
(
#u.username = 1 &&
#u.password = 1 &&
#u.fiscalCodeOrSSN = 1 &&
u.isRegistered = False &&
u.(smartwatch.compatible) = True
) => u.canRegister = True
}
}
check UserCanRegister for 5
// Goal G3: The system should allow companies to register
assert CompaniesCanRegister {
all c: Company {
(
#c.username = 1 &&
#c.password = 1 &&
#c.paymentMethod = 1
) => c.canRegister = True
}
}
check CompaniesCanRegister for 5
// Goal G4: The system should allow registered companies to request data
// from an anonymized group of individuals, only if individuals in the
// group are more than 1000.
assert CompaniesCanMakeAnonimizedQueries {
all c: Company, q: AnonQuery{
(
c.isRegistered = True &&
#queries > 0 &&
#q.people >= 5
) => q.isValid = True && q in c.queries
}
}
check CompaniesCanMakeAnonimizedQueries for 5
// Goal G5: The system should allow registered companies to request data
// from an individual person, only if individuals accept the request.
assert CompaniesCanMakeIndividualQueries {
// If a company requests data from a single person
// either
// the person accepts <=> company is in the person acceptance list
// or
// the person still hasn't accepted => there's a notification concerning the company and the user in the person notification list
all q: IndividualQuery, c: Company, n: Notification {
(q.company = c) &&
(
(q.userAccepts = True <=> c in q.(person.acceptsDataRequestFrom)) ||
(#q.userAccepts = 0 => (
n.user = q.person &&
n.company = c &&
n in q.(person.notifications)
))
)
}
}
check CompaniesCanMakeAnonimizedQueries for 5
pred show(){}
run show for 10

Nevermind, I just figured out.
Just kidding, the problem was a strange behaviour in declaring an attribute of type String. Substituting the String declaration with a "custom" signature (for instance sig Charset {}) allowed for the world to be displayed properly.

See answer to : How to model a consitent database in alloy?.
For the builtin signature String to work , you have to initialize a custom "pool" of String-typed atoms.
In your case, I'd do it as follows for each signature having a string-typed field:
sig Company extends Customer {
paymentMethod: lone String,
queries: set Query
} {
payementMethod in "PayPal"+ "Bank Transfer"+"Credit Card"
...
}

Related

What's the right way to actually get this search functionality to work?

I have this app which displays a list of "coins" to the users . This list was parsed from an JSON API and I used Jetpack Compose for the UI. I implemented
Here is the code of the Jetpack composable list of "coins"
#Composable
fun CoinListScreen(
navController: NavController,
viewModel: CoinListViewModel = hiltViewModel(),
) {
val state = viewModel.state.value
Surface {
Box(modifier = Modifier.fillMaxSize()) {
Column {
androidx.compose.foundation.Image(painter = painterResource(id = R.drawable.ic_baseline_currency_bitcoin_24),
contentDescription = "BTC",
modifier = Modifier
.fillMaxWidth()
.align(CenterHorizontally)
.size(50.dp, 50.dp)
)
SearchBar(
hint = "Search..",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
){
viewModel.searchCoinsList(it) **//here I'm calling my search function from the view model, inside my search bar**
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.coins) { coin ->
Spacer(modifier = Modifier.height(5.dp))
CoinListItem(
coin = coin,
onItemClick = {
navController.navigate(Screen.CoinDetailScreen.route + "/${coin.id}")
}
)
Divider()
}
}
}
if (state.error.isNotBlank()) {
Text(
text = state.error,
color = MaterialTheme.colors.error,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.align(Alignment.Center)
)
}
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}
}
}
**//and this Is my composable search bar**
#Composable
fun SearchBar(
modifier: Modifier = Modifier,
hint: String = "",
onSearch: (String) -> Unit = {}
) {
var text by remember {
mutableStateOf("")
}
var isHint by remember {
mutableStateOf(hint != "")
}
Box(modifier = modifier){
BasicTextField(
value = text,
onValueChange = {
text = it
onSearch(it)
},
maxLines = 1,
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.shadow(5.dp, CircleShape)
.background(Color.White, CircleShape)
.padding(horizontal = 20.dp, vertical = 12.dp)
.onFocusChanged {
isHint = it.isFocused != true
}
)
if(isHint){
Text(
text = hint,
color = Color.LightGray,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp)
)
}
}
}
and this is my view model, this is where I'm implementing the search function, this is where I'm lost, variables that I'm searching for are name, rank, and symbol from the Coin domain list
#HiltViewModel //injecting the use case
class CoinListViewModel #Inject constructor (
private val getCoinsUseCase: GetCoinsUseCase,
) : ViewModel() {
//vmstate Live Template, only the view model touches it
private val _state =
mutableStateOf(CoinListState())
val state: State<CoinListState> = _state
**//for search purposes , this is where I'm lost**
private var coinsList = mutableStateOf<List<Coin>>(listOf())
private var cachedCoinsList = listOf<Coin>()
private var isSearchStarting = true
private var isSearching = mutableStateOf(false)
init {
getCoins()
}
**//for search purposes , this is where I'm lost**
fun searchCoinsList(query: String){
val listToSearch = if(isSearchStarting){
coinsList.value
} else {
cachedCoinsList
}
viewModelScope.launch(Dispatchers.Default) {
if(query.isEmpty()){
coinsList.value = cachedCoinsList
isSearching.value = false
isSearchStarting = true
return#launch
}
val results = listToSearch.filter {
//val iterate: Int = coins.size
it.name.contains(query.trim(), ignoreCase = true) ||
(it.rank.toString() == query.trim()) ||
it.symbol.contains(query.trim(), ignoreCase = true)
}
if(isSearchStarting){
cachedCoinsList = coinsList.value
isSearchStarting = false
}
coinsList.value = results
isSearching.value = true
}
}
//function that calls our GetCoinsUseCase and puts the data inside the state object
//to display that in the UI
private fun getCoins() {
//overwrote the invoke function earlier for the use case which allows us to call the use case as a function
getCoinsUseCase().onEach { result ->
when (result) {
is Resource.SUCCESS -> {
_state.value =
CoinListState(coins = result.data ?: arrayListOf())
}
is Resource.ERROR -> {
_state.value =
CoinListState(
error = result.message ?: "An unexpected error occurred"
)
}
is Resource.LOADING -> {
_state.value = CoinListState(isLoading = true)
}
}
}.launchIn(viewModelScope)
}
}
CoinsListState data class used in view model
data class CoinListState(
val isLoading: Boolean = false,
val coins: ArrayList<Coin> = arrayListOf(),
val error: String = ""
)
this is my "GetCoinsUseCase" to get the coins
class GetCoinsUseCase #Inject constructor(
private val repository: CoinRepository
) {
// overwriting the operator fun invoke allows us to call the use case
//GetCoinsUseCase as if it was a function, and we return a flow because
// we want to emit states LOADING -> for progress bar, SUCCESS -> attach list of coins,
// and ERROR
operator fun invoke(): kotlinx.coroutines.flow.Flow<Resource<ArrayList<Coin>>> = flow {
try {
emit(Resource.LOADING<ArrayList<Coin>>())
//we mapped it to toCoin because we returning a list of coin, not coinDTO
val coins = repository.getCoins().map { it.toCoin() }
emit(Resource.SUCCESS<ArrayList<Coin>>(coins as ArrayList<Coin>))
}catch (e: HttpException){
emit(Resource.ERROR<ArrayList<Coin>>(e.localizedMessage ?: "An unexpected error occurred"))
}catch (e: IOException){
emit(Resource.ERROR<ArrayList<Coin>>("Couldn't reach server. Check connection"))
}
}
}
just the coin repository that is implemented in another place
interface CoinRepository {
//repository definitions
suspend fun getCoins() : ArrayList<CoinDTO>
suspend fun getCoinById(coinId: String) : CoinDetailDTO
}
This is my domain - Domain - only contains the data needed
data class Coin(
var id: String,
var isActive: Boolean,
var name: String,
var rank: Int,
var symbol: String
)
and this is how I'm mapping it
data class CoinDTO(
val id: String,
#SerializedName("is_active")
val isActive: Boolean,
#SerializedName("is_new")
val isNew: Boolean,
val name: String,
val rank: Int,
val symbol: String,
val type: String
)
fun CoinDTO.toCoin(): Coin {
return Coin(
id = id,
isActive = isActive,
name = name,
rank = rank,
symbol = symbol,
// logo = CoinDetailLogo(logo = String()).logo
)
}
Coin list item if needed for reference, this is what is displayed to the user in the list
#Composable
fun CoinListItem (
coin: Coin,
onItemClick: (Coin) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onItemClick(coin) }
.padding(20.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "${coin.rank}. ${coin.name} (${coin.symbol})",
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis
)
Text(
text = if(coin.isActive) "active" else "inactive",
color = if(coin.isActive) Color.Green else Color.Red,
fontStyle = FontStyle.Italic,
textAlign = TextAlign.End,
style = MaterialTheme.typography.body2,
modifier = Modifier.align(CenterVertically)
)
}
}
as well as the "Resource" generic for states
//UIStates
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class SUCCESS<T>(data: T) : Resource<T>(data)
class ERROR<T>(message: String, data: T? = null) : Resource<T>(data, message)
class LOADING<T>(data: T? = null) : Resource<T>(data)
}
again, given this info, how can I get the function searchCoinList in the view model to correctly view the searched data (name, rank, or symbol) when it is called in the CoinListScreen inside the Search Bar. Thank you so much
It seems like you want to implement a basic instant search functionality. It's pretty easy to achieve using Kotlin's StateFlow and its operators. Consider the following implementation with description:
// CoinListViewModel
private val queryFlow = MutableStateFlow("")
private val coinsList = mutableStateOf<List<Coin>>(listOf())
init {
queryFlow
.debounce(300) // filters out values that are followed by the newer values within the given timeout. The latest value is always emitted.
.filterNot { query -> userInput.isEmpty() } // filter the unwanted string like an empty string in this case to avoid the unnecessary network call.
.distinctUntilChanged() // to avoid duplicate network calls
.flowOn(Dispatchers.IO) // Changes the context where this flow is executed to Dispatchers.IO
.flatMapLatest { query -> // to avoid the network call results which are not needed more for displaying to the user
getCoinsUseCase(query).catch { emitAll(flowOf(emptyList())}
}
.onEach { coins: List<Coin> -> // go through each list of Coins
coinsList.value = coins
}
.launchIn(viewModelScope)
}
fun searchCoinsList(query: String) {
queryFlow.value = query
}

Unrolling tree-recursion into an iterative program

I wrote a library that can generate arbitrary strings given a spec-object (https://github.com/rgrannell1/revexp) and I want to convert the function that reads the spec from a recursive algorithm to an iterative algorithm. I'm running into stackoverflow errors due to the depth of the specs I'm traversing.
I believe I need to move from using the call-stack to an explicit stack, but I've never done this before. I've read through previous posts on StackOverflow but I didn't fully understand how to apply the solutions to this problem.
Here is an example spec object.
const data = {
every: [
{
digit: { zero: false }
},
{
repeat: {
value: { digit: {} }
}
}
]
}
and a minimal example of how the algorithm currently traverses the spec and generates a string matching the spec.
const fromSpec = (data) => {
if (data.every) {
return fromSpec.every(data)
} else if (data.digit) {
return fromSpec.digit()
} else if (data.repeat) {
return fromSpec.repeat(data.repeat)
}
}
fromSpec.digit = () => {
return Math.floor(Math.random() * 10)
}
fromSpec.every = part => {
let message = ''
for (const elem of part.every) {
message += fromSpec(elem)
}
return message
}
fromSpec.repeat = part => {
let message = ''
// -- just using fixed repeat for the example
for (let ith = 0; ith < 10; ++ith) {
message += fromSpec(part.value)
}
return message
}
const result = fromSpec(data)
result // 1034856872
I'd appreciate any advice on how to traverse this data-structure and generate an output string in an iterative rather than recursive fashion.
The following example modifies the code to use a stack data structure. Data on the stack is processed incrementally, with new data possibly added on each iteration.
const fromSpec = (data) => {
const stack = [data];
let message = '';
while (stack.length > 0) {
const item = stack.pop();
// Assumption based on the code in the question:
// 'every', 'digit', and 'repeat' keys are mutually exclusive.
if (item.every) {
// Add items in reverse order, so that items are popped off the stack
// in the original order.
for (let i = item.every.length - 1; i >= 0; --i) {
stack.push(item.every[i]);
}
} else if (item.digit) {
message += String(Math.floor(Math.random() * 10));
} else if (item.repeat) {
for (let i = 0; i < 10; ++i) {
stack.push(item.repeat.value);
}
}
}
return message;
}
An alternative approach would be required for a more complicated scenario (e.g., when a node in the tree requires processing both 1) when it's initially encountered in the traversal and 2) after all its children have been traversed).
The following links may be relevant.
https://web.archive.org/web/20120227170843/http://cs.saddleback.edu/rwatkins/CS2B/Lab%20Exercises/Stacks%20and%20Recursion%20Lab.pdf
https://web.archive.org/web/20161206082402/https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/recursionConversion/page/recursionConversion.html
It's natural to write RevExp.toString as a recursive program because it is expected to process a recursively structured input. However because recursive programs can lead to deep stacks, it's not uncommon to flatten a recursive process to an iterative one.
Good programmers know that maintaining complexity goes a long way in sustaining our sanity. I want to keep my recursive program and I want the computer to handle flattening the process for me. Can I have my cake and eat it too?
Here's another way to look a the problem -
// example1.js
import { concat, upper, digit, str, toString } from './RevExp.js'
const licensePlate =
concat(upper(), upper(), upper(), str("-"), digit(), digit(), digit())
console.log(toString(licensePlate))
console.log(toString(licensePlate))
console.log(toString(licensePlate))
// RFX-559
// VKT-794
// KSF-823
Let's begin writing the RevExp module. We'll start by creating constructors for each of our expression types -
// RevExp.js
const str = (value = "") =>
({ type: str, value })
const lower = () =>
({ type: lower })
const upper = () =>
({ type: upper })
const digit = ({ zero = true } = {}) =>
({ type: digit, zero })
const concat = (...exprs) =>
({ type: concat, exprs })
Now let's work on RevExp.toString -
// RevExp.js (continued)
import { inRange } from './Rand.js'
const toString = (e) =>
{ switch (e.type)
{ case str:
return String(e.value)
case lower:
return String.fromCharCode(inRange(97, 122))
case upper:
return String.fromCharCode(inRange(65, 90))
case digit:
return e.zero
? String(inRange(0, 9))
: String(inRange(1, 9))
case concat:
return e.exprs.reduce((r, v) => r + toString(v), "")
default: throw Error(`unsupported expression type: ${e.type}`)
}
}
export { lower, upper, digit, alpha, repeat, concat, str, toString }
It should be possible to make complex expressions by combining several simple expressions. And we imagine some new types like alpha, and repeat -
// example2.js
import { alpha, digit, repeat, concat, str, toString } from './RevExp.js'
const segment =
concat(alpha(), digit(), alpha(), digit(), alpha())
const serial =
concat
( repeat
( concat(segment, str("-"))
, { count: 4 }
)
, segment
)
console.log(toString(serial))
console.log(toString(serial))
console.log(toString(serial))
// F3Q7U-b6k8Q-R8e3A-a2q3M-j0a9k
// g6G3w-h2O3O-b8O3k-L4p1y-m5I0y
// m6E0M-A4C2y-K3g0M-d7X7j-w8v5G
And add corresponding support in the RevExp module -
// RevExp.js (enhanced)
import { inRange, sample } from './Rand.js'
const str = // ...
const lower = // ...
const upper = // ...
const digit = // ...
const concat = // ...
const alpha = () =>
oneOf(upper(), lower())
const oneOf = (...exprs) =>
({ type: oneOf, exprs })
const repeat = (expr = {}, { count = 10 } = {}) =>
({ type: repeat, expr, count })
const toString = (e) =>
{ switch (e.type)
{ case str: // ...
case lower: // ...
case upper: // ...
case digit: // ...
case concat: // ...
case oneOf:
return toString(sample(e.exprs))
case repeat:
return toString(concat(...Array(e.count).fill(e.expr)))
default: // ...
}
}
export { /* ..., */ alpha, oneOf, repeat }
Now let's transform the recursive program to an iterative one. And without having to think about the stack or mutate state as the program runs, too!
// RevExp.js (stack-safe)
// ...
import * as Str from './Str.js'
import { loop, recur, call } from './TailRec.js'
// ...
const toString = (e = {}) =>
loop(toStringTailRec, e)
const toStringTailRec = e =>
{ switch (e.type)
{ case str: // ...
case lower: // ...
case upper: // ...
case digit: // ...
case concat:
return e.exprs.length
? call
( Str.concat
, recur(e.exprs[0])
, recur(concat(...e.exprs.slice(1)))
)
: Str.empty
case oneOf:
return recur(sample(e.exprs))
case repeat:
return recur(concat(...Array(e.count).fill(e.expr)))
default: throw Error(`unsupported expression type: ${e.type}`)
}
}
export { /*...*/, toString } // <-- don't export toStringTailRec helper
And here are the remaining modules, Str, Rand, and TailRec -
// Str.js
const empty =
""
const concat = (a = "", b = "") =>
a + b
export { empty, concat }
// Rand.js
const rand = (n = 2) =>
Math.floor(Math.random() * n)
const inRange = (min = 0, max = 1) =>
rand(max - min + 1) + min
const sample = (t = []) =>
t[rand(t.length)]
export { rand, inRange, sample }
Writing modules is an important factor in creating reusable code. This TailRec module was written in another post and can be reused, without modification1, to meet our program's needs.
Now we can rely on recursively structured programs without having to introduce complexity or requiring a change in how we think every time we encounter a recursive problem. Write the module once, reuse as needed -
// TailRec.js
const identity = x =>
x
const call = (f, ...values) =>
({ type: call, f, values })
const recur = (...values) =>
({ type: recur, values })
const loop = (f, ...init) =>
{ const aux1 = (e, k) =>
e.type === recur
? call(aux, e.values, r => call(aux1, f(...r), k))
: e.type === call
? call(aux, e.values, r => call(aux1, e.f(...r), k))
: call(k, e)
const aux = (exprs, k) =>
call
( exprs.reduce
( (mr, e) =>
k => call(mr, r => call(aux1, e, x => call(k, [ ...r, x ])))
, k => call(k, [])
)
, k
)
return run(aux1(f(...init), identity))
}
const run = r =>
{ while (r && r.type === call)
r = r.f(...r.values)
return r
}
export { loop, call, recur }
In the end, the approach here is virtually the same as yours. However instead of representing expressions writing JS objects by hand, we use functions, which can be parameterized and composed, and can handle the tedious and precarious assembly for us. Programmer sanity maintained -
// example2.js
// ...
console.log(serial)
{ type: concat
, exprs:
[ { type: repeat
, expr:
{ type: concat
, exprs:
[ { type: concat
, exprs:
[ { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
]
}
, { type: str, value: "-" }
]
}
, count: 4
}
, { type: concat
, exprs:
[ { type: concat
, exprs:
[ { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
]
}
]
}
]
}
I hope this was seen as an exciting way to see the same problem from a different perspective. If you end up using the TailRec module, see the original post for additional explanation. I'm happy to answer any follow-up questions.
1. Minor formatting changes and variable renaming for consistency with this answer

How to compare a previous list and updated a field in multi thread

I have a local cache where I store the runner's lap info, I need to show if the runner's current lap was better or worse than the current lap, while displaying the current lap information.
data class RunInfo(
val runnerId: String,
val lapTime: Double,
var betterThanLastLap: BETTERTHANLASTLAP
)
enum class BETTERTHANLASTLAP {
NA, YES, NO
}
object RunDB {
private var listOfRunners: MutableList<RunInfo> =
java.util.Collections.synchronizedList(mutableListOf())
private var previousList: MutableList<RunInfo> = mutableListOf()
fun save(runList: MutableList<RunInfo>) {
previousList = listOfRunners.toMutableList()
listOfRunners.clear()
listOfRunners.addAll(runList)
listOfRunners.forEach { runner ->
previousList.forEach { previousLap ->
if (runner.runnerId == previousLap.runnerId) {
runner.betterThanLastLap =
when {
previousLap.lapTime == 0.0 -> BETTERTHANLASTLAP.NA
runner.lapTime >= previousLap.lapTime -> BETTERTHANLASTLAP.YES
else -> BETTERTHANLASTLAP.NO
}
}
}
}
}
}
This seems to do the job, but often I get concurrent modification exception. Is there a better way of solving this problem?
I don't recommend combining mutable lists with read-write var properties. Making it mutable in two different ways creates ambiguity and is error prone. Since you're just clearing and replacing the list contents, I would make it a read-only list and a read-write property.
You need to synchronize the whole function so it can only be executed once at a time.
object RunDB {
private var listOfRunners: List<RunInfo> = listOf()
private var previousList: List<RunInfo> = listOf()
fun save(runList: List<RunInfo>) {
sychronized(this) {
previousList = listOfRunners.toList()
listOfRunners = runList.toList()
listOfRunners.forEach { runner ->
previousList.forEach { previousLap ->
if (runner.runnerId == previousLap.runnerId) {
runner.betterThanLastLap =
when {
previousLap.lapTime == 0.0 -> BETTERTHANLASTLAP.NA
runner.lapTime >= previousLap.lapTime -> BETTERTHANLASTLAP.YES
else -> BETTERTHANLASTLAP.NO
}
}
}
}
}
}
}
It also feels error prone to have a mutable data class in these lists that you're copying and shuffling around. I recommend making it immutable:
data class RunInfo(
val runnerId: String,
val lapTime: Double,
val betterThanLastLap: BETTERTHANLASTLAP
)
object RunDB {
private var listOfRunners: List<RunInfo> = listOf()
private var previousList: List<RunInfo> = listOf()
fun save(runList: List<RunInfo>) {
sychronized(this) {
previousList = listOfRunners.toList()
listOfRunners = runList.map { runner ->
val previousLap = previousList.find { runner.runnerId == previousLap.runnerId }
runner.copy(betterThanLastLap = when {
previousLap == null || previousLap.lapTime == 0.0 -> BETTERTHANLASTLAP.NA
runner.lapTime >= previousLap.lapTime -> BETTERTHANLASTLAP.YES
else -> BETTERTHANLASTLAP.NO
})
}
}
}
}

How to Merge two or more Objects in a ObjectsList

I have would like to know if there is a way to merge two ( or more ) objects in one list.
Exemple:
I have this class:
class User {
String name
Integer age
Integer score
}
and I got this method on another class
methodTest() {
User a = new User().with{
it.name = "JACK"
it.age = 20
}
User b = new User().with{
it.name = "JACK"
it.score = 50
}
User c = new User().with{
it.name = "TONY"
it.age = 25
}
User d = new User().with{
it.name = "TONY"
it.score = 30
}
List userList = new ArrayList()
userList.add(a)
userList.add(b)
userList.add(c)
userList.add(d)
}
Tere is a way to get a userList merged by name? Something like :
userList = userList.mergeBy(it.name)
and then get a list of Users with:
[{name:"Jack", age: 20 , score: 50},{name:"TONY", age: 25, score: 30}]
You can use .groupBy to group your list by User.name and then transform it to a List<User> by applying .inject function. Below you can find an example (fixed version the code you have shown us):
import groovy.json.JsonOutput
class User {
String name
Integer age
Integer score
}
User a = new User(name: "JACK", age: 20)
User b = new User(name: "JACK", score: 50)
User c = new User(name: "TONY", age: 25)
User d = new User(name: "TONY", score: 30)
List userList = new ArrayList()
userList.add(a)
userList.add(b)
userList.add(c)
userList.add(d)
List<User> users = userList.groupBy { it.name } // (1)
.values() // (2)
.inject([]) { result, users -> // (3)
result << users.inject(new User()) { User merged, User user -> // (4)
merged.name = user.name ?: merged.name
merged.age = user.age ?: merged.age
merged.score = user.score ?: merged.score
return merged
}
}
println JsonOutput.toJson(users)
Let's see what happens here step-by-step:
(1) userList.groupBy { it.name } produces following map:
[JACK:[User(JACK, 20, null), User(JACK, null, 50)], TONY:[User(TONY, 25, null), User(TONY, null, 30)]]
(2) calling .values() on this map returns a list of list of users:
[[User(JACK, 20, null), User(JACK, null, 50)], [User(TONY, 25, null), User(TONY, null, 30)]]
(3) then .inject([]) { result, users -> /* ... */ } collects every list of users, applies transformation and adds result to result list (we start with empty [] here)
(4) here we call another .inject() function on users list (this users list contains a list of users with same name, e.g. [JACK:[User(JACK, 20, null), User(JACK, null, 50)]). We start with a new "empty" user (.inject(new User())). We access it by merged variable inside the closure - this variable holds the last result of each iteration inside .inject() function. So it starts with this empty user, gets the first one, sets the name and age (score is not set, because it is null), then it gets second user, sets name (same one) and score (age is not set, because in this user has null age). Final User is added to result list using left shift operator <<.
Eventually when you print to console your final users list you will see desired output:
[{"age":20,"score":50,"name":"JACK"},{"age":25,"score":30,"name":"TONY"}]
Final note
Of course you can make this code even simple, e.g. you can add a method to User class that merges two user instances, something like:
import groovy.json.JsonOutput
class User {
String name
Integer age
Integer score
User merge(User user) {
return new User(
name: user.name ?: name,
age: user.age ?: age,
score: user.score ?: score
)
}
}
List<User> userList = [
new User(name: "JACK", age: 20),
new User(name: "JACK", score: 50),
new User(name: "TONY", age: 25),
new User(name: "TONY", score: 30)
]
List<User> users = userList.groupBy { it.name }
.values()
.inject([]) { result, users ->
result << users.inject(new User()) { User merged, User user -> merged.merge(user) }
}
println JsouOutput.toJson(users)
Using some simple groovy magic:
class User{
String name
Integer age
Integer score
String toString(){ "$name:$age:$score" }
}
User a = new User(
name:"JACK",
age : 20
)
User b = new User(
name : "JACK",
score :50
)
User c = new User(
name : "TONY",
age : 25
)
User d = new User(
name : "TONY",
score : 30
)
List userList = [ a, b, c, d ]
def mergedList = userList.inject( [:].withDefault{ new User() } ){ res, User u ->
res[ u.name ].name = u.name
if( u.age ) res[ u.name ].age = u.age
if( u.score ) res[ u.name ].score = u.score
res
}.values()
assert '[JACK:20:50, TONY:25:30]' == mergedList.toString()

swift How to use enum as parameter in constructing struct?

I was doing an experiment of Swift programming book and stuck with construct a struct inner the struct itself. But the error reported the parameter is unwrapped. How could I take it value as parameter?
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
func FullDeck() -> Card[] {
var deck: Card[]
for i in 1...13
{
for j in 0...3
{
let rank_para = Rank.fromRaw(i)
let suit_para = Suit.fromRaw(j)
**deck.append(Card(rank: rank_para, suit : suit_para ))
//value of optional type unwrapped;did you mean to use ? or !**
}
}
return deck
}
}
enum Rank: Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
func simpleDescription() -> String {
switch self {
case .Ace:
return "ace"
case .Jack:
return "jack"
case .Queen:
return "queen"
case .King:
return "king"
default:
return String(self.toRaw())
}
}
func compare(sec:Rank) -> Bool {
var first = 0
var second = 0
if self.toRaw() == 1 {
first = 1
} else {
first = self.toRaw()
}
if sec.toRaw() == 1 {
second = 1
} else {
second = self.toRaw()
}
return first > second
}
}
enum Suit: Int{
case Spades = 0
case Hearts, Diamonds, Clubs
func simpleDescription() -> String {
switch self {
case .Spades:
return "spades"
case .Hearts:
return "hearts"
case .Diamonds:
return "diamonds"
case .Clubs:
return "clubs"
}
}
}
the fromRaw method returns an optional value: Rank? and Suit?. That means that the value could be nil. You need to check for that:
if let aRank = rank_para {
if let aSuit = suit_para {
deck.append(Card(rank: aRank, suit: aSuit))
}
}
By using "if let", you "unwrap" the optional value into a value (aRank and aSuit) that is no longer optional (cannot be nil).
Another way to do that:
if rank_para and suit_para {
deck.append(Card(rank: rank_para!, suit: suit_para!))
}
Here, you are checking if rank_para and suit_para are nil. If they both are not, you call append and "unwrap" the optional values using !. ! means if the value is nil throw a runtime error, otherwise, treat this variable as if it cannot be nil.

Resources