Compare same value in 2 different lists - Kotlin - kotlin

I'm very new to kotlin and I'm doing this exercise where you need 2 lists, one for the invoice and another for the invoice values, for example:
Invoice..........................│ Invoice Values
invoiceNumber = 1........│ itemName = Pepsi
customerName = Alfred│ quantity = 2
TotalValue = $13,00......│ price = 2.50
......................................│ invoiceNumber = 1
......................................│
......................................│ itemName = Cereal
......................................│ quantity = 2
......................................│ price = 4
......................................│ invoiceNumber = 1
and I got it to show only the invoiceNumber that I want, but I can't manage to get the results to do "total += quantity*price" here is what I tried so far:
fun main() {
data class VarInvoice(val numInvoice: Int, val dateInvoice: String, val ssn: String, val total: Double)
data class VarItem(val nameItem: String, val quantity: Int, val unitPrice: Double, val numInvoice2: Int)
val invoice= mutableListOf(
VarInvoice(1, "25/05/1990", "84739572857", 0.00),
VarInvoice(2, "02/09/2009", "38295840284", 0.00),
VarInvoice(3, "13/07/2020", "74959572857", 0.00)
)
val invoiceItem = mutableListOf(
VarItem("Pepsi", 2, 2.50, 1),
VarItem("Cereal", 2, 4.00, 1),
VarItem("Coke", 4, 3.5, 2),
VarItem("Chicken", 1, 13.50, 3)
)
val itemInvoice = invoiceItem.groupBy(VarItem::numInvoice2)
val invoiceEqual = itemInvoice.getValue(1)
println(invoiceEqual)
//but this returns me: [VarItem(nameItem=Pepsi, quantity=2, unitPrice=2.5, numInvoice2=1), VarItem(nameItem=Cereal, quantity=2, unitPrice=4.0, numInvoice2=1)]
//and i have no idea how to do like: if invoiceNumber2 == invoiceNumber { (total += price * quantity)}

Map to total
Assuming you are trying to get the total of a invoice, here is something you could do:
val totals = invoiceItem.groupBy { it.numInvoice2 }.mapValues { item ->
item.value.sumByDouble { it.unitPrice * it.quantity }
}
The totals is a map of invoice number to the total price of all associated items. (assuming you are trying to join invoice to invoiceItem on invoiceNumber2 = invoiceNumber
Extension Property
Or you can slap an extension property onto the VarInvoice, instead of declaring it on the data class as a property (just beware that you can't do this in a function (e.g. your main function))
val VarInvoice.total get() = invoiceItem
.filter { it.numInvoice2 == numInvoice }
.sumByDouble { it.unitPrice * it.quantity }
It would be nice you can provide more information on how you are going to build your application. There could be better solution to this.
P.S. 1
after your code, I got: {1=13.0, 2=14.0, 3=13.5} and I'm trying to figure a way of it beeing sorted and to include the ssn of the person, like this: {1=13.0 "by person with ssn: "84739572857, 3=13.5 "by person with ssn: "74959572857, 2=14.0 "by person with ssn: "38295840284 Sorry, english is not my main language and I can't thank you enough!
Technically you can try something like this:
val totaledInvoice = invoiceItem.groupBy { it.numInvoice2 }.mapValues { item ->
invoice.first { it.numInvoice == item.value.first().numInvoice2 }.copy(
total = item.value.sumByDouble { it.unitPrice * it.quantity }
)
}
You can return whatever you want in the mapValues lambda, but the code is getting ridiculous and inefficient. I would suggest trying out the extension property solution.

Related

How to deconstruct map to list?

I have a List that contain country, I used groupBy to group them by country and now i would like to get fallowing result (for recycled view):
Country1
Airport1
Airport2
Airport3
Country2
Airport4
Airport5
Airport6
could someone give me a tip on how to do this?
My data class:
data class Airport(
val city: City,
val code: String,
val country: Country,
val name: String,
)
Assuming:
after you call groupBy you have a Map<Country, List<Airport>>
you want the countries to be in alphabetical order
you want to transform the map to a List<String>
This should do it:
airportsByCountry
.toSortedMap()
.flatMap { (country, airports) ->
listOf(country.toString()) + airports.map { it.toString() }
}

Join dataset with case class spark scala

I am converting a dataframe into a dataset using case class which has a sequence of another case class
case class IdMonitor(id: String, ipLocation: Seq[IpLocation])
case class IpLocation(
ip: String,
ipVersion: Byte,
ipType: String,
city: String,
state: String,
country: String)
Now I have another dataset of strings that has just IPs. My requirement is to get all records from IpLocation if ipType == "home" or IP dataset has the given IP from ipLocation. I am trying to use bloom filter on the IP dataset to search through that dataset but it is inefficient and not working that well in general. I want to join the IP dataset with IpLocation but I'm having trouble since this is in a Seq. I'm very new to spark and scala so I'm probably missing something. Right now my code looks like this
def buildBloomFilter(Ips: Dataset[String]): BloomFilter[String] = {
val count = Ips.count
val bloomFilter = Ips.rdd
.mapPartitions { iter =>
val b = BloomFilter.optimallySized[String](count, FP_PROBABILITY)
iter.foreach(i => b += i)
Iterator(b)
}
.treeReduce(_|_)
bloomFilter
}
val ipBf = buildBloomFilter(Ips)
val ipBfBroadcast = spark.sparkContext.broadcast(ipBf)
idMonitor.map { x =>
x.ipLocation.filter(
x => x.ipType == "home" && ipBfBroadcast.value.contains(x.ip)
)
}
I just want to figure out how to join IpLocation and Ips
Sample:
Starting from your case class,
case class IpLocation(
ip: String,
ipVersion: Byte,
ipType: String,
city: String,
state: String,
country: String
)
case class IdMonitor(id: String, ipLocation: Seq[IpLocation])
I have defined the sample data as follows:
val ip_locations1 = Seq(IpLocation("123.123.123.123", 12.toByte, "home", "test", "test", "test"), IpLocation("123.123.123.124", 12.toByte, "otherwise", "test", "test", "test"))
val ip_locations2 = Seq(IpLocation("123.123.123.125", 13.toByte, "company", "test", "test", "test"), IpLocation("123.123.123.124", 13.toByte, "otherwise", "test", "test", "test"))
val id_monitor = Seq(IdMonitor("1", ip_locations1), IdMonitor("2", ip_locations2))
val df = id_monitor.toDF()
df.show(false)
+---+------------------------------------------------------------------------------------------------------+
|id |ipLocation |
+---+------------------------------------------------------------------------------------------------------+
|1 |[{123.123.123.123, 12, home, test, test, test}, {123.123.123.124, 12, otherwise, test, test, test}] |
|2 |[{123.123.123.125, 13, company, test, test, test}, {123.123.123.124, 13, otherwise, test, test, test}]|
+---+------------------------------------------------------------------------------------------------------+
and the IPs:
val ips = Seq("123.123.123.125")
val df_ips = ips.toDF("ips")
df_ips.show()
+---------------+
| ips|
+---------------+
|123.123.123.125|
+---------------+
Join:
From the above example data, explode the array of the IdMonitor and join with the IPs.
df.withColumn("ipLocation", explode('ipLocation)).alias("a")
.join(df_ips.alias("b"), col("a.ipLocation.ipType") === lit("home") || col("a.ipLocation.ip") === col("b.ips"), "inner")
.select("ipLocation.*")
.as[IpLocation].collect()
Finally, the collected result is given as follows:
res32: Array[IpLocation] = Array(IpLocation(123.123.123.123,12,home,test,test,test), IpLocation(123.123.123.125,13,company,test,test,test))
You can explode your array sequence in your IpMonitor objects using explode function, then use a left outer join to match ips present in your Ips dataset, then filter out on ipType == "home" or ip is present in Ips dataset and finally rebuild your IpLocation sequence by grouping by id and collect_list.
Complete code is as follows:
import org.apache.spark.sql.functions.{col, collect_list, explode}
val result = idMonitor.select(col("id"), explode(col("ipLocation")))
.join(Ips, col("col.ip") === col("value"), "left_outer")
.filter(col("col.ipType") === "home" || col("value").isNotNull())
.groupBy("id")
.agg(collect_list("col").as("value"))
.drop("id")
.as[Seq[IpLocation]]

How correctly built an object graph based on multi level join in Slick?

I have a model structure as following:
Group -> Many Parties -> Many Participants
In on of the API calls I need to get single groups with parties and it's participants attached.
This whole structure is built on 4 tables:
group
party
party_participant
participant
Naturally, with SQL it's a pretty straight forward join that combines all of them. And this is exactly what I am trying to do with slick.
Mu method is dao class looks something like this:
def findOneByKeyAndAccountIdWithPartiesAndParticipants(key: UUID, accountId: Int): Future[Option[JourneyGroup]] = {
val joins = JourneyGroups.groups join
Parties.parties on (_.id === _.journeyGroupId) joinLeft
PartiesParticipants.relations on (_._2.id === _.partyId) joinLeft
Participants.participants on (_._2.map(_.participantId) === _.id)
val query = joins.filter(_._1._1._1.accountId === accountId).filter(_._1._1._1.key === key)
val q = for {
(((journeyGroup, party), partyParticipant), participant) <- query
} yield (journeyGroup, party, participant)
val result = db.run(q.result)
result ????
}
The problem here, is that the result is type of Future[Seq[(JourneyGroup, Party, Participant)]]
However, what I really need is Future[Option[JourneyGroup]]
Note: case classes of JourneyGroup and Party have sequences for there children defined:
case class Party(id: Option[Int] = None,
partyType: Parties.Type.Value,
journeyGroupId: Int,
accountId: Int,
participants: Seq[Participant] = Seq.empty[Participant])
and
case class JourneyGroup(id: Option[Int] = None,
key: UUID,
name: String,
data: Option[JsValue],
accountId: Int,
parties: Seq[Party] = Seq.empty[Party])
So they both can hold the descendants.
What is the correct way to convert to the result I need? Or am I completely in a wrong direction?
Also, is this statement is correct:
Participants.participants on (_._2.map(_.participantId) === _.id) ?
I ended up doing something like this:
journeyGroupDao.findOneByKeyAndAccountIdWithPartiesAndParticipants(key, account.id.get) map { data =>
val groupedByJourneyGroup = data.groupBy(_._1)
groupedByJourneyGroup.map { case (group, rows) =>
val parties = rows.map(_._2).distinct map { party =>
val participants = rows.filter(r => r._2.id == party.id).flatMap(_._3)
party.copy(participants = participants)
}
group.copy(parties = parties)
}.headOption
}
where DAO method's signature is:
def findOneByKeyAndAccountIdWithPartiesAndParticipants(key: UUID, accountId: Int): Future[Seq[(JourneyGroup, Party, Option[Participant])]]

RavenDB Get By List of IDs?

In RavenDB
I need to get the latest insert for a document based on it's ID and filter by IDs from a list
ie:
List<Entity> GetLastByIds(List<int> ids);
The Entity is similar to:
class Entity
{
int id; //Unique identifier for the category the price applies to.
int Price; //Changes with time
DateTime Timestamp; //DateTime.UtcNow
}
so if I insert the following:
session.Store(new Entity{Id = 1, Price = 12.5});
session.Store(new Entity{Id = 1, Price = 7.2});
session.Store(new Entity{Id = 1, Price = 10.3});
session.Store(new Entity{Id = 2, Price = 50});
session.Store(new Entity{Id = 3, Price = 34});
...
How can I get the latest price for IDs 1 and 3 ??
I have the Map/Reduce working fine, giving me the latest for each ID, it's the filtering that I am struggling with.
I'd like to do the filtering in Raven, because if there are over 1024 price-points in total for all IDs, doing filtering on the client side is useless.
Much appreciate any help I can get.
Thank you very much in advance :)
If the Id is supposed to represent the category, then you should call it CategoryId. By calling the property Id, you are picking up on Raven's convention that it should be treated as the primary key for that document. You can't save multiple versions of the same document. It will just overwrite the last version.
Assuming you've built your index correctly, you would just query it like so:
using Raven.Client.Linq;
...
var categoryIds = new[] {1, 3}; // whatever
var results = session.Query<Entity, YourIndex>()
.Where(x=> x.CategoryId.In(categoryIds));
(The .In extension method is in the Raven.Client.Linq namespace.)

Joining tables in LINQ/SQL

I have below a collection of rows and each row consists of productid, unitid, countryid.
I need to find the details for each row in the corresponding tables (products, units, country)
for product - select product name, updatedby
for unitid - select unit name , updatedby
for countryid - select countryname, uploadby
and returning the rows which has the same format
Id = product id or unitid or countryid
name = proudct name or unit name or countryname
modified = product updatedby or unit updated by or country uploadby
So, in summary -
1. For a Collection of rows
a. use the id to get the extra details from the respective table
b. return the same type of collection for the results
2. do step 1 for
2.a For RegularToys (Run this logic on TableBigA)
2.b For CustomToys(Run this logic on TableB)
3. Return all the rows
by adding 2.a and 2.b
How to write an sql/linq query for this? thanks
If I'm understanding correctly, you want to use a given ID to find either a product, a unit or a country but you're not sure which. If that's the case, then you can build out deferred queries like this to find the given entity:
var prod = from p in db.Products
where p.ProductId = id
select new { Id = p.ProductId, Name = p.ProductName, Modified = p.UpdatedBy };
var unit = from u in db.Units
where p.UnitId = id
select new { Id = u.UnitId, Name = u.UnitName, Modified = p.UpdatedBy };
var ctry = from c in db.Countries
where c.CountryId = id
select new { Id = c.CountryId, Name = c.CountryName, Modified = c.UploadBy };
And then execute the queries until you find an entity that matches (with ?? being the null-coalesce operator that returns the right value if the left result is null).
var res = prod.SingleOrDefault() ??
unit.SingleOrDefault() ??
ctry.SingleOrDefault() ??
new { Id = id, Name = null, Modifed = null };
Without any further details I can't be more specific about the condition below, but I think you are asking for something along these lines. I'm assuming your Id's are int's (but this can be easily changed if not) and you already have an Entity Data Model for the tables you describe.
Create a class for your common data:
class RowDetail
{
public int Id { get; set; }
public string Name { get; set; }
public string Modified { get; set; }
}
Pull the information out of each of the sub tables into a new record:
IEnumerable<RowDetail> products =
from p in db.Products
where <<CONDITION>>
select
new RowDetail()
{
Id = p.ProductId,
Name = p.ProductName,
Modified = p.UpdatedBy
};
IEnumerable<RowDetail> units =
from u in db.Units
where <<CONDITION>>
select
new RowDetail()
{
Id = u.UnitId,
Name = u.UnitName,
Modified = u.UpdatedBy
};
IEnumerable<RowDetail> countries =
from c in db.Countries
where <<CONDITION>>
select
new RowDetail()
{
Id = c.CountryId,
Name = c.CountryName,
Modified = c.UploadBy
};
Finally pull all the records together in a single collection:
IEnumerable<RowDetail> results = products.Union(units).Union(countries);
I'm not sure if this is exactly what you are looking for, so feel free to give feedback and/or more details if further assistance is required.