In Swift, what is the best way to assign two arrays to key/value of a dictionary? - dictionary

Suppose I have two arrays:
let myKeys = ["one", "two", "three"]
let myValues = ["1", "2", "3"]
and empty dictionary:
var myDictionary = Dictionary<String, String>()
What is the best way to assign myKeys and myValues as keys and values, respectively, of myDictionary?

With my knowledge, this is the way to assign values/keys for your dictionary:
let count = myKeys.count
let count2 = myValues.count
if (count == count2) {
for index in 0..count {
myDictionary.updateValue(myValues[index], forKey:myKeys[index])
}
}
Edit: Swift 2
let count = myKeys.count
let count2 = myValues.count
if (count == count2) {
for index in 0 ..< count {
myDictionary.updateValue(myValues[index], forKey:myKeys[index])
}
}

Related

Updating map value for a specific key in Kotlin

I am new at Kotlin and trying to catch up the language.
I have a function that returns DayofWeek as a key and an Int as a value.
The issue that I am facing is that I need to take a list of object that has another list of object inside that has an Int value which I need to save and increment for every time I see the same value.
Here is my function -
class OrdersAnalyzer {
data class Order(val orderId: Int, val creationDate: LocalDateTime, val orderLines: List<OrderLine>)
data class OrderLine(val productId: Int, val name: String, val quantity: Int, val unitPrice: BigDecimal)
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> {
val map: MutableMap<DayOfWeek, Int>? = mutableMapOf(
Pair(DayOfWeek.SUNDAY, 0),
Pair(DayOfWeek.MONDAY, 0),
Pair(DayOfWeek.TUESDAY, 0),
Pair(DayOfWeek.WEDNESDAY, 0),
Pair(DayOfWeek.THURSDAY, 0),
Pair(DayOfWeek.FRIDAY, 0),
Pair(DayOfWeek.SATURDAY, 0)
)
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
var quantity = 0
map?.put(dayOfWeek, quantity)
}
return map!!
}
}
So the issues I am facing right now are 2 -
1) How can I increment the value of each pair when it is the corrent DayOfWeek? I don't want to replace, I want to add it to the last value.
2) When returning the Map, I do not want to return the DayOfWeeks that have the value of 0. How can I do that?
Here is a modified version of the elegant answer provided by Arjan, with some test code.
Updated the answer after the comment from Alon Shlider -- now counting all order item quantities grouped by day of the week:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { sumItemQuantities(it.value) }
fun sumItemQuantities(orders: List<Order>) =
orders.flatMap { it.orderLines.map { line -> line.quantity } }.sum()
fun main() {
val orders = listOf(
Order(
1,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(6, "laptop", 28, 1200.toBigDecimal())
)
),
Order(
2,
LocalDateTime.now().minusDays(1),
listOf(
OrderLine(496, "VR headset", 6, 400.toBigDecimal())
)
)
)
println(totalDailySales(orders))
}
Output:
{FRIDAY=28, SATURDAY=6}
With this approach, Kotlin functions do the grouping and counting for you. The groupBy function creates a map from DayOfWeek to a list of orders (grouping all orders with the same day of week in a list). The mapValues function transforms that map by replacing the lists with the result of the sumItemQuantities function (for each list).
In the for loop in your code, you can retrieve the current quantity for a specific day (or use zero if it isn't set yet), increase it by the right amount and then store it. To return only the map entries with non zero values, you could filter (return totalsPerDay.filter { it.value > 0 }) or start with an empty map. This is your function with some changes:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> {
val totalsPerDay = mutableMapOf<DayOfWeek, Int>()
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
val currentQuantity = totalsPerDay[dayOfWeek] ?: 0
// This is not the best way to increment by the sum of the order
// item quantities...
val orderItemQuantities = sumItemQuantities(listOf(order))
totalsPerDay[dayOfWeek] = currentQuantity + orderItemQuantities
}
return totalsPerDay
}
Output after calling it:
println(OrdersAnalyzer().totalDailySales(orders))
{FRIDAY=28, SATURDAY=6}
Updated answer (also thanks to Freek de Bruijn), with some test code.
I think it would be something like this:
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { it.value.flatMap { it.orderLines } }
.mapValues { it.value.map { it.quantity } }
.mapValues { it.value.sum() }
groupBy creates a Map where the values are of type List<Order>, so you need to call a few steps to convert these values to Int. First we use flatMap to convert List<Order> to List<OrderLine> (map would convert to List<List<OrderLine>>). Then we use map to get the quantities out of List<OrderLine>, and finally sum() to add up all those quantities.
val orders = listOf(
Order(
2,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(5, "monitor", 10, 200.toBigDecimal()),
OrderLine(4, "keyboard", 5, 50.toBigDecimal())
)
)
)
println(totalDailySales(orders))
This results in the output:
{FRIDAY=15}

Kotlin - group elements by a key under some conditions with new value type

I'm trying to find a way to use Kotlin collection operation to do some logic that I'm going to explain:
Let's say type Classroom contains a list of Student as a field in it, eg. classroom.getSudents() returns a list of certain studends.
Now I have a list of mixed Student that I need to group by one of its fields say major, and the value of the resultant map to be Classroom.
So I need to convert List<Student> to Map<Student.major, Classroom>
Also at some cases of major, for example for all major == chemistry, I'll need to group by another criteria, say firstname, so the keys of major chemistry would be major_firstname
Here's an example, I have a list of Student(major, firstname):
[
Student("chemistry", "rafael"),
Student("physics", "adam"),
Student("chemistry", "michael"),
Student("math", "jack"),
Student("chemistry", "rafael"),
Student("biology", "kevin")
]
I need the result to be:
{
"math" -> Classroom(Student("math", "jack")),
"physics" -> Classroom(Student("physics", "adam")),
"chemistry_michael" -> Classroom(Student("chemistry", "michael")),
"chemistry_rafael" -> Classroom(Student("chemistry", "rafael"), Student("chemistry", "rafael")),
"biology" -> Classroom(Student("biology", "kevin"))
}
I've tried groupBy, flatMapTo and associateBy but as far as I understand all of these doesn't group by a certain condition.
I will try to answer the 1st part as Roland posted an answer for the 2nd part (although I did not try it).
Assuming your classes are:
class Student(val major: String, val firstName: String)
class Classroom(val studentList: MutableList<Student>) {
fun getStudents(): MutableList<Student> {
return studentList
}
}
and with an initialization like:
val list = mutableListOf<Student>(
Student("chemistry", "rafael"),
Student("physics", "adam"),
Student("chemistry", "michael"),
Student("math", "jack"),
Student("chemistry", "rafael"),
Student("biology", "kevin"))
val classroom = Classroom(list)
val allStudents = classroom.getStudents()
you can have a result list:
val finalList: MutableList<Pair<String, Classroom>> = mutableListOf()
allStudents.map { it.major }.distinctBy { it }.forEach { major ->
finalList.add(major to Classroom(allStudents.filter { it.major == major }.toMutableList()))
}
so by the below code:
finalList.forEach {
println(it.first + "->")
it.second.getStudents().forEach { println(" " + it.major + ", " + it.firstName) }
}
this will be printed:
chemistry->
chemistry, rafael
chemistry, michael
chemistry, rafael
physics->
physics, adam
math->
math, jack
biology->
biology, kevin
It's actually the mixture of those methods which you require. There are also other ways to achieve it, but here is one possible example using groupBy and flatMap:
val result = students.groupBy { it.major }
.flatMap { (key, values) -> when (key) {
"chemistry" -> values.map { it.firstname }
.distinct()
.map { firstname -> "chemistry_$firstname" to ClassRoom(values.filter { it.firstname == firstname }) }
else -> listOf(key to ClassRoom(values))
}
}.toMap()
Assuming the following data classes:
data class Student(val major: String, val firstname: String)
data class ClassRoom(val students : List<Student>)
If you also want a map with all students grouped by major, the following suffices:
val studentsPerMajor = students.groupBy { it.major }
.map { (major, values) -> major to ClassRoom(values) }
If you then rather want to continue working with that map instead of recalculating everything from the source, it's also possible, e.g. the following will then return your desired map based on the studentsPerMajor:
val result = studentsPerMajor.flatMap { (key, classroom) -> when (key) {
"chemistry" -> classroom.students.map { it.firstname }
.distinct()
.map { firstname -> "chemistry_$firstname" to ClassRoom(classroom.students.filter { it.firstname == firstname }) }
else -> listOf(key to classroom)
}
}.toMap()

Swift Dictionary Filter

So it looks like the filter function on a Swift (2.x) dictionary returns a tuple array. My question is there an elegant solution to turning it back into a dictionary? Thanks in advance.
let dictionary: [String: String] = [
"key1": "value1",
"key2": "value2",
"key3": "value3"
]
let newTupleArray: [(String, String)] = dictionary.filter { (tuple: (key: String, value: String)) -> Bool in
return tuple.key != "key2"
}
let newDictionary: [String: String] = Dictionary(dictionaryLiteral: newTupleArray) // Error: cannot convert value of type '[(String, String)]' to expected argument type '[(_, _)]'
If you are looking for a more functional approach:
let result = dictionary.filter {
$0.0 != "key2"
}
.reduce([String: String]()) { (var aggregate, elem) in
aggregate[elem.0] = elem.1
return aggregate
}
reduce here is used to construct a new dictionary from the filtered tuples.
Edit: since var parameters has been deprecated in Swift 2.2, you need to create a local mutable copy of aggregate:
let result = dictionary.filter {
$0.0 != "key2"
}
.reduce([String: String]()) { aggregate, elem in
var newAggregate = aggregate
newAggregate[elem.0] = elem.1
return newAggregate
}
You can extend Dictionary so that it takes a sequence of tuples as initial values:
extension Dictionary {
public init<S: SequenceType where S.Generator.Element == (Key, Value)>(_ seq: S) {
self.init()
for (k, v) in seq { self[k] = v }
}
}
and then do
let newDictionary = Dictionary(newTupleArray)

How can I create new map with new values but same keys from an existing map?

I have an existing map in Groovy.
I want to create a new map that has the same keys but different values in it.
Eg.:
def scores = ["vanilla":10, "chocolate":9, "papaya": 0]
//transformed into
def preference = ["vanilla":"love", "chocolate":"love", "papaya": "hate"]
Any way of doing it through some sort of closure like:
def preference = scores.collect {//something}
You can use collectEntries
scores.collectEntries { k, v ->
[ k, 'new value' ]
}
An alternative to using a map for the ranges would be to use a switch
def grade = { score ->
switch( score ) {
case 10..9: return 'love'
case 8..6: return 'like'
case 5..2: return 'meh'
case 1..0: return 'hate'
default : return 'ERR'
}
}
scores.collectEntries { k, v -> [ k, grade( v ) ] }
Nice, functional style solution(including your ranges, and easy to modify):
def scores = [vanilla:10, chocolate:9, papaya: 0]
// Store somewhere
def map = [(10..9):"love", (8..6):"like", (5..2):"meh", (1..0):"hate"]
def preference = scores.collectEntries { key, score -> [key, map.find { score in it.key }.value] }
// Output: [vanilla:love, chocolate:love, papaya:hate]
def scores = ["vanilla":10, "chocolate":9, "papaya": 0]
def preference = scores.collectEntries {key, value -> ["$key":(value > 5 ? "like" : "hate")]}
Then the result would be
[vanilla:like, chocolate:like, papaya:hate]
EDIT: If you want a map, then you should use collectEntries like tim_yates said.

Comparing two lists using LINQ

If I have 2 lists of strings:
List<string> firstList = new List<string>("010", "111", "123");
List<string> secondList = new List<string>("010", "111", "999");
How can I compare each individual character in each item from the lists? Ex: Should compare "0" with "0", "1" with "1", "0" with "0" and so on. It appears that I can use SelectMany but I am stuck on how to do it
EDIT:
These lists should return true when compared with each other (as asterisk means any character and I am validating to ensure that each item is exactly 3 chars in length)
List<string> firstList = new List<string>("010", "111", "123");
List<string> secondList = new List<string>("010", "1*1", "***");
Updated with wildcards
class WildcardStringComparer : IEqualityComparer<string>
{
public bool Equals(string s1, string s2)
{
if (s1.Length != s2.Length) return false;
for (int i = 0; i < s1.Length; i++)
{
if (s1[i] != s2[i] && s1[i] != '*' && s2[i] != '*')
return false;
}
return true;
}
public int GetHashCode(string obj)
{
throw new NotImplementedException();
}
}
Results:
List<string> firstList = new List<string>{"010", "111", "999"};
List<string> secondList = new List<string>{"010", "111", "999"};
bool res = firstList.SequenceEqual(secondList, new WildcardStringComparer()); // True
and
List<string> firstList = new List<string>{"010", "111", "999"};
List<string> secondList = new List<string>{"010", "111", "*99"};
bool res = firstList.SequenceEqual(secondList, new WildcardStringComparer()); // True
and
List<string> firstList = new List<string>{"010", "111", "999"};
List<string> secondList = new List<string>{"010", "111", "199"};
bool res = firstList.SequenceEqual(secondList, new WildcardStringComparer()); // False
If you just want to compare for a matching character sequence between your lists:
bool sameCharacters = Enumerable.SequenceEqual(firstList.SelectMany(x => x),
secondList.SelectMany(x => x));
This would result in true, i.e. for the following two lists - their character sequences match ("010111123" for both), their individual string entries do not:
List<string> firstList = new List<string> {"010", "111", "123" };
List<string> secondList = new List<string> {"010", "11", "1123" };
Edit in response to comments:
For a wildcard match you could use Zip() and compare each character, return true if they match based on wildcard conditions, then just check that each element in the zipped sequence is true.
var isWildCardMatch = firstList.SelectMany(x => x).Zip(secondList.SelectMany( x => x),
(a,b) =>
{
if(a==b || a =='' || b == '')
return true;
return false;
}).All( x=> x);
Above approach crossed string entry boundaries, which would cause false matches - here a better approach:
bool isWildCardMatch = firstList.Zip(secondList, (x, y) =>
{
var matchWord = y.Select((c, i) => c == '*' ? x[i] : c);
return matchWord.SequenceEqual(x);
}).All(x => x);
Assuming you want to compare the first character of the first string in the first list to the first character of the first string of the second list, the second character of the first string in the first list to the second character of the first string in the second list, etc. I can think of two implementations.
The one I would start with:
var firstCharList = new List<char>();
var secondCharList = new List<char>();
firstList.foreach(s =>
{
foreach(char c in s)
{
firstCharList.Add(c);
}
});
secondList.foreach(s =>
{
foreach(char c in s)
{
secondCharList.Add(c);
}
});
for(int i = 0; i < firstCharList.Length; i++)
{
if(firstCharList[i] == secondCharList[i]) yield return i;
}
That would generate a list (or array, or whatever) of ints that correspond to indexes of which both strings had the same character.
The second would be something like:
firstList.foreach(s =>
{
var index = firstList.IndexOf(s);
var sPrime = secondList[index];
for(int i = 0; i < s.Length; i++)
{
if(s[i] == sPrime[i]) yield return s[i];
}
}
That one just returns any characters that are equal at the same indexes.

Resources