In Scala, how do I return a list of all object instances with a property set to a certain value - scala

I am using Scala, and would like to be able to select all instances of an object with a property set to a given value. Say I have an Order class and a Order Item class, and I want to select or pull all of the order items with an order id of say 1, and return all of these in a list, how do I do this?
I would like to be able to return a list of order line items that have an order id of say 1
Here is my Line Item class definition:
Code:
case class LineItem(val itemId : Int, val orderId:Int, val productId:Int, val quantity:Int)
I then have some order items defined in an object:
Code:
object LineItem {
val li1 = new LineItem(1, 1, 1, 10)
val li2 = new LineItem(2, 1, 4, 1)
val li3 = new LineItem(3, 1, 2, 1)
val li4 = new LineItem(4, 1, 3, 1)
val li5 = new LineItem(5, 2, 1, 1)
val li6 = new LineItem(6, 1, 7, 1)
var lineItems = Set(li1, li2, li3, li4, li5, li6)
def findItemsInOrder(ordId:Int) = lineItems.find(_.orderId == ordId)
}
As you can see 5 of the line items belong to the order with an id of 1.
So first a list of orders are printed out for the user to see, then I want the user to be able to select an order to see all the line items within that order, so I would like these line items to be printed out in the shell.
So the order id is inputted by the user:
Code:
val orderNo = readLine("Which order would you like to view?").toInt
And then this line:
Code:
println("OrderID: " + LineItem.findItemsInOrder(orderNo).get.orderId + ", ProductID: " + LineItem.findItemsInOrder(orderNo).get.productId + ", Quantity: " + LineItem.findItemsInOrder(orderNo).get.quantity)
Only prints out the first line item with an order id of 1.
I have then tried to loop through all line items, like this:
Code:
var currentLineItems = new ListBuffer[LineItem]()
for (item <- LineItem.lineItems){
val item1: LineItem = LineItem(LineItem.findItemsInOrder(orderNo).get.lineId, LineItem.findItemsInOrder(orderNo).get.orderId, LineItem.findItemsInOrder(orderNo).get.productId, LineItem.findItemsInOrder(orderNo).get.quantity)
currentLineItems += item1
}
for (item <- currentLineItems ){
println("OrderID: " + LineItem.findItemsInOrder(orderNo).get.orderId + ", ProductID: " + LineItem.findItemsInOrder(orderNo).get.productId + ", Quantity: " + LineItem.findItemsInOrder(orderNo).get.quantity)
}
But this code just prints out the same line item 6 times.
I would be very grateful for any help received to help solve my problem
Thanks
Jackie

Define findItemsInOrder to filter out all elements in the list that match the order id:
def findItemsInOrder(ordId: Int): List[LineItem] = lineItems.filter(_.orderId == ordId)
find will locate the first element matching the id, if found will return Some[T] where T is the element type, else None:
Finds the first element of the sequence satisfying a predicate, if any.
If you have multiple elements which match against the id, you need filter:
Selects all elements of this traversable collection which satisfy a predicate.

Related

how to sort Map<String,double> based on its value in flutter

I have a map with String,double and I want to sort this map based on its value and want to take only first 4 key value pair..like following
Map<String,dynamic> mymap={
'A':2000,
'B':8000,
'C':300,
'D':3890,
'E':8030,
'F':300,
'G':900,
};
and I want to convert into following
Map<String,dynamic> resultmap={
'E':8030,
'B':8000,
'D':3890,
'A':2000,
'Others':1500,
};
To sort a map in descending order by value, you can use something like below (look here for more info).
var numbers = {'one': 1, 'two': 2, 'three': 3, 'four': 4};
print(numbers); // {one: 1, two: 2, three: 3, four: 4}
final sortedValuesDesc = SplayTreeMap<String, dynamic>.from(
numbers, (keys1, keys2) => numbers[keys2]!.compareTo(numbers[keys1]!));
print(sortedValuesDesc); // {four: 4, three: 3, two: 2, one: 1}
To get the sum of the rest of the values, there are some different options to choose from. I found this approach here on Stack Overflow:
final sum = numbers
.values
.skip(4)
.reduce((value, element) => value + element);
What remains is to make sure the summed elements are removed and the above sum is added to the map along with your desired key.
Let me know if this worked for you. :-)
You can do this in several steps.
First declare your map.
Map<String,dynamic> mymap={
'A':2000,
'B':8000,
'C':300,
'D':3890,
'E':8030,
'F':300,
'G':900,
};
Then, sorting the declared map in descending order by using this code.
var sortedList = mymap.entries.toList()..sort((b,a)=>a.value.compareTo(b.value));
Then, create a new map and add the sorted list as entries inside the new map.
Map newMap={};
newMap.addEntries(mapList);
Now, find other number's sum using this code.
int otherNumbersSum = newMap.values.skip(4).reduce((value, element) => value + element);
Finally, create a new map and add entries in that map by checking that either they are three digits or four digits and at last adding the sum which we got in the last step.
Map finalMap ={};
for(var a in newMap.entries){
if(a.value>999){
finalMap[a.key] = a.value;
}
}
finalMap["Others"] = otherNumbersSum;
You will get result like this.
finalMap ={
'E':8030,
'B':8000,
'D':3890,
'A':2000,
'Others':1500,
};
Hope it will help :)

Mongo Template - create or update

I have a collection that represents items,
The collection has multiple indexes (not one unique id).
model looks something like:
data class Item {
_id
customer_id
item_type: (game / movie / equipment)
}
I wanna create a query that uses these indexes to find or create, for example:
val item: Item
mongoTemplate.findOrCreate(customer_id: x, item_type: y, item)
I can of course guarantee that there wont be 2 items with same customer_id and type.
You can create a function like this:
fun MongoTemplate.findOrCreate(customer_id: String, item_type: String): Item {
val query = Query.query(
Criteria().andOperator(
Criteria.where("customer_id").`is`(customer_id),
Criteria.where("item_type").`is`(item_type)
)
)
return findOne(query, Item::class.java, "collectionName")
?: insert(Item(customer_id, item_type), "collectionName")
}
MongoTemplate.findOne will return the object or null if it is not found, if null we use the MongoTemplate.insert to insert the new object. Insert will also return the object.
You would call this function like this:
val item: Item = mongoTemplate.findOrCreate(customer_id: x, item_type: y)

Scala how to use reduceBykey when I have two keys

Data format of one row:
id: 123456
Topiclist: ABCDE:1_8;5_10#BCDEF:1_3;7_11
One id can have many rows:
id: 123456
Topiclist:ABCDE:1_1;7_2;#BCDEF:1_2;7_11#
Target: (123456, (ABCDE,9,2),(BCDEF,5,2))
Records in topic list are split by #, so ABCDE:1_8;5_10 is one record.
A record is in the format <topicid>:<topictype>_<topicvalue>
E.g for ABCDE:1_8 has
topicid = ABCDE
topictype = 1
topicvalue = 8
Target: sum the total value of TopicType1 , and count frequency of TopicType1
so should be (id, (topicid, value,frequency)), eg: (123456, (ABCDE,9,2),(BCDEF,5,2))
Assume that your data are "123456!ABCDE:1_8;5_10#BCDEF:1_3;7_11" and "123456!ABCDE:1_1;7_2#BCDEF:1_2;7_11", so we use "!" to get your userID "123456"
rdd.map{f=>
val userID = f.split("!")(0)
val items = f.split("!")(1).split("#")
var result = List[Array[String]]()
for (item <- items){
val topicID = item.split(":")(0)
for (topicTypeValue <- item.split(":")(1).split(";") ){
println(topicTypeValue);
if (topicTypeValue.split("_")(0)=="1"){result = result:+Array(topicID,topicTypeValue.split("_")(1),"1") }
}
}
(userID,result)
}
.flatMapValues(x=>x).filter(f=>f._2.length==3)
.map{f=>( (f._1,f._2(0)),(f._2(1).toInt,f._2(2).toInt) )}
.reduceByKey{case(x,y)=> (x._1+y._1,x._2+y._2) }
.map(f=>(f._1._1,(f._1._2,f._2._1,f._2._2))) // (userID, (TopicID,valueSum,frequences) )
The output is ("12345",("ABCDE",9,2)), ("12345",("BCDEF",5,2)) a little different from your output, you can group this result if you really need ("12345",("ABCDE",9,2), ("BCDEF",5,2) )

Getting Cassandra query results asynchronously using Scala + Monix

I'm building a REST API using AKKA Http, Monix and Datastax Java Driver for Apache Cassandra and I'm having some troubles while trying to fetch some Items from cassandra, wait for the query to be fulfilled and returning the results.
I'm able to print all the results easily, but unable to wait for the
query to be done and return all the items. My rest point simply
returns an empty array of items since it does not wait for the query
to be done.
I have an executeQuery method that takes:
queryString: String representing a cassandra query
page: Int useful for pagination
parameters: Any* representing parameters, if necessary for the query
And returns an Observable[Row].
Then, in order to perform such query, retrieve its result, parse them and send them back, I use Monix Observable and Subscription.
Let's suppose I want to retrieve some items by a common field known as pid:
import monix.execution.Ack
import monix.execution.Scheduler.Implicits.global
import com.datastax.driver.core.Row
import monix.reactive.Observable
import cassandra.src.CassandraHelper
import item.src.entity.{Item, Items}
. . .
val keyspace = "my_keyspace"
val table = "items"
. . .
def getItems() : Items = {
var itemList: Items = List()
val observable: Observable[Row] = CassandraHelper.executeQuery(
"SELECT * FROM " + keyspace + "." + table,
1
)
observable.subscribe { row =>
itemList ::= ItemMapper.rowToItem()(row)
Ack.Continue
}
Items(itemList)
}
Where rowToItem simply parses a row into an Item and Items: List[Item].
I was taking a look at Task but I'm not quite sure its what I'm looking for.
EDIT
With #Alexandru Nedelcu solution I'm able to print all the items in itemList as soon as they get inserted into it, but still getting an empty response for that call: { "items" : [] }.
Here's the edited code:
def getItems() : Items = {
var itemList: List[Item] = List()
val observable: Observable[Row] = CassandraHelper.executeQuery(
"SELECT * FROM " + keyspace + "." + table,
1
)
observable.subscribe { row =>
println(itemList)
itemList ::= ItemMapper.rowToItem()(row)
Ack.Continue
}
Items(itemList)
}
How can I wait for the results to be all parsed and inserted into items and then send them back?
From what I understand you have an Observable[Row] and you want to build an Items out of it, which aggregates every Row element from the source stream, is that correct?
If so, the foldLeftL is what you want, which will aggregate every element into a state and return the final result once the source stream completes:
// We need to suspend the Task, because your Items is probably a
// mutable object and it's best to suspend side effects ;-)
val items: Task[Items] = Task.suspend {
val initial: Items = _
val observable: Observable[Row] = ???
// This returns a Task[Items] when the source completes
observable.foldLeftL(initial) { (items, elem) =>
items ::= ItemMapper.rowToItem()(row)
// I don't understand if your `Items` is mutable or not
// but returning the same reference is fine
items
}
}
A Task is a lazy Future. And you can convert it into a Future with runAsync. More details here: https://monix.io/docs/2x/eval/task.html

Counter inside of scala for comprehension

I have this piece of code.
for {
country <- getCountryList
city <- getCityListForCountry(country)
person <- getPersonListForCity(person)
} {...}
When we run this code, we need to have a counter inside the body of the loop which increments every time the loop executes. This counter needs to show the number of people processed per country. So it has to reset itself to 0 every time we start executing the loop for a new country.
I tried
for {
country <- getCountryList
counterPerCountry = 0
city <- getCityListForCountry(country)
person <- getPersonListForCity(city)
} {counterPerCountry = counterPerCountry + 1; ...}
but this says that I am trying to reassign a value to val.
so I tried
var counterPerCountry = 0
for {
country <- getCountryList
counterPerCountry = 0
city <- getCityListForCountry(country)
person <- getPersonListForCity(city)
} {counterPerCountry = counterPerCountry + 1; ...}
also tried
for {
country <- getCountryList
var counterPerCountry = 0
city <- getCityListForCountry(country)
person <- getPersonListForCity(city)
} {counterPerCountry = counterPerCountry + 1; ...}
If you're just trying to figure out how to assign a value to a var within a for-comprehension for science, here's a solution:
var counter = 0
for {
a <- getList1
_ = {counter = 0}
b <- getList2(a)
c <- getList3(b)
} {
counter = counter + 1
...
}
If you're actually trying to count the number of people in a country, and you say it's the number of people in a city times the number of cities in a country - then it comes down to simple arithmetics:
for {
country <- getCountryList
cities = getCityListForCountry(country)
city <- cities
persons = getPersonListForCity(person)
personsPerCountry = cities.length * persons.length
person <- persons
} {...}
I agree with #pamu that a for-comprehension does not seem the like a natural choice here. But if you turn the for comprehension into the underlying operations, I think you can get a solution that, while not as readable as a for comprehension, works with Scala's functional style and avoids mutable variables. I'm thinking of something along this line:
getCountryList flatMap (country =>
(getCityListForCountry(country) flatMap (city =>
getPersonListForCity(city))
).zipWithIndex
)
That should yield a list of (person, index) tuples where the index starts at zero for each country.
The inner part could be turned back into a for comprehension, but I'm not sure whether that would improve readability.
I don't think for-comprehension allows this naturally. You have to do it bit hacky way. Here is one way to do it.
var counter = 0
for {
country <- getCountryList.map { elem => counter = 0; elem }
city <- getCityForCountry(country)
person <- getPersonForCity(person)
} {
counter + 1
//do something else here
}
or use function for being modular
var counter = 0
def reset(): Unit = counter = 0
for {
country <- getCountryList
_ = reset()
city <- getCityForCountry(country)
person <- getPersonForCity(person)
} {
counter + 1
//do something else here
}
People per country
val peoplePerCountry =
for {
country <- getCountryList
cities = getCityForCountry(country)
city <- cities
persons = getPersonForCity(person)
} yield (country -> (cities.length * persons.length))
The code returns list of country, persons per that country
The above for-comprehension is the answer, you do not have to go for counter approach. This functional and clean. No mutable state.
One more approach, if your only need is the actual sum would be something compact and functional such as:
getCountryList.map( country => //-- for each country
(country, //-- return country, and ...
getCityListForCountry(country).map ( city => //-- the sum across cities
getPersonListForCity(city).length //-- of the number of people in that city
).sum
)
)
which is a list of tuples of countries with the number of people in each country. I like to think of map as the "default" loop where I would have used a for in the past. I've found the index value is very seldom needed. The index value is available with the zipWithIndex method as mentioned in another answer.