Count string elements in an array in a multidimensional array - arrays

I have an array that I populate from firestore that uses a struct. Is there a way to count the number of times there is a matching string for the productName var.
This is my struct...
struct PlayerStock: Codable, Identifiable {
#DocumentID var id: String?
var productName: String
var qty: Int
var saleUID: String
var totalPrice: Int
var uid: String
var unitPrice: Int
}
This is what's in my VC, I populate this from firestore and then want to count matching strings in productName
var playerStock: [PlayerStock] = []
Is there a way to do this without using a for loop?
Strings I'd like to count in productName include "smartphone" or "laptop" I want to store the matching total count as an int like this:
var smartphoneTotal =
var laptopTotal =
etc etc..
I've tried using filters and compact map but can't find anything that works, I think its because the array is multidimensional or because its using a dictionary?
Pretty noob here so any help appreciated!

First group the array by productName
let groupedProducts = Dictionary.init(grouping: playerStock, by: \.productName)
you'll get
["smartphone":[PlayerStock(..), PlayerStock(..), PlayerStock(..)],
"laptop":[PlayerStock(..), PlayerStock(..)]
then map the values to their amount of items
.mapValues(\.count)
The result is
["smartphone":3, "laptop":2]

If you want to use filter, something like this should work with your struct:
var laptopTotal = playerStock.filter { $0.productName == "laptop" }.count

This may help
let wordsToFind = ["smartphone", "laptop"]
var foundCounts: [String: Int] = [:]
for p in playerStock {
for word in wordsToFind {
if p.name.contains(word) {
foundCounts[word] = foundCounts[word, default: 0] + 1
}
}
}
foundCounts
If you really want a functional "no for-loops" version, and if you mean you want to find things that contain your search terms, then:
let wordsToFind = ["smartphone", "laptop"]
let founds = wordsToFind.map { word -> (String, Int) in
playerStock.reduce(("", 0)) { partialResult, player in
(word, partialResult.1 + (player.name.contains(word) ? 1 : 0))
}
}

You could use the higher order functions filter() or reduce(). #ShawnFrank already gave an answer using filter(). (voted.)
For a small number of items, there isn't a big difference between filter() and reduce(). For large datasets, though, filter creates a second array containing all the items that match the filter criteria. Arrays are value types, so they hold copies of the entries they contain. This would increase the memory footprint needed to do the counting. (You'd have the original array and a copy containing all the matching elements in memory).
The higher order function reduce() works differently. it takes a starting value (a total starting at 0 in our case) for the result, and a closure. The closure takes the current result, and an element from the array you are parsing. At runtime, the reduce() function calls your closure over and over, passing in each element from the array you are reducing. In the first call to the closure, it sends the closure the initial value for result (a zero total, in our case.) In each subsequent call to the closure, it passes the result of the previous call. (The running total, for our implementation.) The reduce() function returns the result returned by the last call to your closure.
You can use reduce to count the number of items that match a given test without having to build a temporary array. Below is a sample implementation using reduce(). Note that I tweaked your PlayerStock type to add default values for all the properties other than productName since I don't care about those.
// Define the PlayerStock type, but use default values for everything but `productName`
struct PlayerStock: Codable, Identifiable {
var id: String? = nil
var productName: String
var qty: Int = Int.random(in: 1...10)
var saleUID: String = ""
var totalPrice: Int = Int.random(in: 10...200)
var uid: String = ""
var unitPrice: Int = Int.random(in: 10...200)
}
// Create an array of test data
let players = [
PlayerStock(productName: "smartphone"),
PlayerStock(productName: "CD Player"),
PlayerStock(productName: "laptop"),
PlayerStock(productName: "CD Player"),
PlayerStock(productName: "smartphone"),
PlayerStock(productName: "laptop"),
PlayerStock(productName: "smartphone"),
PlayerStock(productName: "boom box"),
PlayerStock(productName: "laptop"),
PlayerStock(productName: "smartphone"),
]
/// This is a function that counts and returns the number of PlayerStock items who's productName property matches a the string nameToFind.
/// If you pass in printResult = true, it logs its result for debugging.
/// - Parameter nameToFind: The `productName` to search for
/// - Parameter inArray: The array of `PlayerStock` items to search
/// - Parameter printResult: a debugging flag. If true, the function prints the count if items to the console. Defaults to `false`
/// - Returns: The number of `PlayerStock` items that have a `productName` == `nameToFind`
#discardableResult func countPlayers(nameToFind: String, inArray array: [PlayerStock], printResult: Bool = false) -> Int {
let count = array.reduce(0, { count, item in
item.productName == nameToFind ? count+1 : count
})
if printResult {
print("Found \(count) players with productName == \(nameToFind)")
}
return count
}
let smartphoneCount = countPlayers(nameToFind: "smartphone", inArray: players, printResult: true)
let laptopCount = countPlayers(nameToFind: "laptop", inArray: players, printResult: true)
let cdPlayerCount = countPlayers(nameToFind: "CD Player", inArray: players, printResult: true)
This sample code produces the following output:
Found 4 players with productName == smartphone
Found 3 players with productName == laptop
Found 2 players with productName == CD Player

Related

How prevent Object.keys() sort?

The problem with the ECMA standard for sort of Object.keys() is known:
Object.keys() handle all keys with integer (example: 168), including integer as strings (example: "168"), as a integer. The result is, both are the same (168 === "168"), and overwrite itself.
var object = {};
object["168"] = 'x';
object[168] = 'y';
Object.keys(object); // Array [ "168" ]
object[Object.keys(object)]; // "y"
Interestingly, all keys (including pure integer keys) are returned as a string.
The ecma262 wrote about this: All keys will be handle as a integer, expect the key is a String but is not an array index.
https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
That should tell us: 168 === "168". A toString() do not solve the problem.
var object = {};
object[[3].toString()] = 'z';
object[[1].toString()] = 'x';
object[[2].toString()] = 'y';
Object.keys(object);
// Array(3) [ "1", "2", "3" ]
Paradoxically, in this case, only integer apply as "enumerable" (it's ignoring array.sort(), that sort also strings with letters.).
My question about this is simple: How can i prevent the sort function in Object.keys()? I have testet the Object.defineProperties(object, 1, {value: "a", enumerable: true/false}), but that mean not realy enumerable in the case of integer or string or integer-like string. It means only should it be counted with or not. It means "counted" like omit (if it false), not "enumerabled" like ascending or descending.
A answere like that is not a good answer: Please use only letters [a-zA-Z] or leastwise a letter at the first position of keyword.
What I want: That the keys are not sorted, but output in the order in which they were entered, whether integer, string or symbol.
Disclaimer: Please solutions only in JavaScript.
Javascript Objects are unordered by their nature. If you need an ordered object-like variable I would suggest using a map.
To achieve what you're looking for with a map instead of object you'd do something like the below:
var map1 = new Map();
map1.set("123", "c");
map1.set(123, "b");
var iterator1 = map1.keys();
var myarray = [];
for (var i = 0; i < map1.size; i++) {
myarray.push(iterator1.next().value);
}
console.log(myarray);
// Array ["123", 123]
Unfortunately it's not compatible with IE and I'm not sure how else you could achieve what you need without it. A quick Google did return something about jQuery maps, though.
If you don't want to use jQuery and still need to support IE some points are below:
Is there anything stopping you using an array rather than JS object to store the data you need? This will retain the order per your requirements unlike objects. You could have an object entry in each iteration which represents the key then use a traditional foreach to obtain them as an array. I.e.
The array:
var test_array = [
{key: 123, value: 'a value here'},
{key: "123", value: 'another value here'}
];
// console.log(test_array);
Getting the keys:
var test_array_keys = [];
test_array.forEach(function(obj) { test_array_keys.push(obj['key']); } );
// console.log(test_array_keys);
Then if you needed to check whether the key exists before adding a new entry (to prevent duplicates) you could do:
function key_exists(key, array)
{
return array.indexOf(key) !== -1;
}
if(key_exists('12345', test_array_keys))
{
// won't get here, this is just for example
console.log('Key 12345 exists in array');
}
else if(key_exists('123', test_array_keys))
{
console.log('Key 123 exists in array');
}
Would that work? If not then the only other suggestion would be keeping a separate array alongside the object which tracks the keys and is updated when an entry is added or removed to/from the object.
Object Keys sorted and store in array
First Creating student Object. then sort by key in object,last keys to store in array
const student={tamil:100, english:55, sci:85,soc:57}
const sortobj =Object.fromEntries(Object.entries(student).sort())
console.log(Object.keys(sortobj))
use map instead of an object.
let map = new Map()
map.set("a", 5)
map.set("d", 6)
map.set("b", 12)
to sort the keys (for example, to update a chart data)
let newMap = new Map([...map.entries()].sort())
let keys = Array.from(newMap.keys()) // ['a','b','d']
let values = Array.from(newMap.values()) // [5,12,6]

Sorting a dictionary by key and converting it into an array

I have a dictionary of prices and quantities. I am getting updates on the price and values multiple times in a second so I don't want to store them in an array because dictionary are much faster.
let mainPriceValDict = [Double:Double]()
The data is coming in as an array of JSON so I am using codable to parse the JSON and put it into a dictionary. When I use the data, it needs to be sorted in ascending and/or descending order because I am looping through each price in order to get to a certain total quantity. The format that the array is in that I am looping through is as follows:
let loopingArray = [PriceQuantityEntry]()
struct PriceQuantityEntry {
let price : Double
let size : Double
}
I want to sort the prices which are the keys in the dictionary above and convert them into an array of PriceQuantityEntry. What is the best way to do this? In ascending and deciding order. I have tried first getting all the keys sorted and then grabbing associated values and putting them into the array in order but this seems like more processing than this task actually requires.
I think the best way to do this would be to put a custom initializer in the struct to convert the dictionary value to a value of type PriceQuantityEntry but I am not exactly sure how that would work with the sorting.
This is what I am currently doing to get it to work. I just feel like there is a more efficient way for it to be done. If you feel like I should keep the structure as an array instead of converting it to a dict, let me know.
loopingArray = self.mainPriceValDict.sorted { $0.0 < $1.0 }.map { PriceQuantityEntry(price: $0.0, size: $0.1) }
If you are getting a lot of updates to individual entries, both a dictionary and an array may cause memory copies of the whole memory structure every time an entry is changed.
I would suggest using objects (classes) instead of structures. This will allow you to use both an array and a dictionary to reference the object instances. The dictionary will provide direct access for updates and the array will allow sequential processing in forward or backward order.
[EDIT] Example:
class PriceQuantityEntry
{
static var all:[PriceQuantityEntry] = []
static var prices:[Double:PriceQuantityEntry] = [:]
var price : Double
var size : Double
init(price:Double, size:Double)
{
self.price = price
self.size = size
PriceQuantityEntry.all.append(self)
// PriceQuantityEntry.all.resort() // on demand or when new prices added
PriceQuantityEntry.prices[price] = self
}
class func update(price:Double, with size:Double)
{
if let instance = PriceQuantityEntry.prices[price]
{ instance.size = size }
else
{
let _ = PriceQuantityEntry(price:price, size:size)
PriceQuantityEntry.resort()
}
}
class func resort()
{
PriceQuantityEntry.all.sort{$0.price < $1.price}
}
}
// if adding multiple initial entries before updates ...
let _ = PriceQuantityEntry(price:1, size:3)
let _ = PriceQuantityEntry(price:1.25, size:2)
let _ = PriceQuantityEntry(price:0.95, size:1)
PriceQuantityEntry.resort()
// for updates ...
PriceQuantityEntry.update(price:1, with: 2)
// going throug list ...
var count:Double = 0
var total:Double = 0
var quantity:Double = 5
for entry in PriceQuantityEntry.all
{
total += min(entry.size,quantity-count) * entry.price
count = min(quantity,count + entry.size)
if count == quantity {break}
}

Generate random Strings without a shuffle and repetition of previous ones?

My code already generates a random String of an array when I press a button, but sometimes a String gets repeated. What do I have to do so that the String "Mango" only gets called again when all the other Strings where already called without using a shuffle, I want to call one String at a time?
Example: "Mango", "Kiwi", "Banana", "Pineapple", "Melon", "Mango", "Kiwi",.....
Here is my code:
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
let fruits = Int(arc4random_uniform(UInt32(array.count)))
print(array[fruits])
In order to avoid repetitions you need to keep track of which fruits have previously been seen. There are several ways to do that an all of the proposed solutions do it in one way or another.
For your specific use case, you will need this tracking to be retained outside of the code executed by the button (in your view controller for example).
Here is a generalized structure that could help with this:
(you can define it inside the view controller if this is a one-time thing or outside of it if you intend to reuse the mechanism elsewhere)
struct RandomItems
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
}
To use it, you would declare a variable (in your VC) that will keep track of your fruits:
var fruits = RandomItems(["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"])
And in the button's code use that variable (fruits) to print a non-repeating fruit name at each execution
func buttonPressed() // <- use your function here
{
print( fruits.next() )
}
You need to implement some logic. It's quite easy if you think harder. Run this in your Playground, or if you fully understand this block of code, you can do this in your project already.
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var selectedIndices = [Int]()
for _ in 1...20 {
let randomFruitIndex = Int(arc4random_uniform(UInt32(array.count)))
// Print only if not yet printed once
if !selectedIndices.contains(randomFruitIndex) {
print(array[randomFruitIndex])
selectedIndices.append(randomFruitIndex)
}
// Reset
if selectedIndices.count == array.count {
print("----- CLEARING SELECTED INDICES----")
selectedIndices.removeAll()
}
}
So as you can see, we are adding each generated random number (in your case, it's the fruits variable.) into an array of Int. Then if the number of selectedIndices is equal to the count of the array of fruits, clear all the stored selectedIndices.
OUTPUT:
Pineapple
Melon
Mango
Kiwi
Banana
Apple
----- CLEARING SELECTED INDICES----
Mango
Melon
This is an adaption from the accepted answer of the linked topic in my comment:
var source = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var usedElements = [String]()
func choosePseudoRandomElement() -> String {
if source.count == 0 {
source = usedElements
usedElements = []
}
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
let randomItem = source[randomIndex]
usedElements.append(randomItem)
source.remove(at: randomIndex)
return randomItem
}
for _ in 1...18 {
print("Item: \(choosePseudoRandomElement())")
}
One potential issue with this solution is that it may happen, that the last element of one complete iteration also occurs as the first element of the second iteration. You can handle that case by comparing the randomly chosen item with the item which was chosen before (use a while loop until the items doesn't match anymore).
Also, this does remove elements from the source array. If you do not want that, create a copy of the source array.

swift - using .map on struct array

i have a struct array that i want "break up" into smaller arrays that can be called as needed or at least figure out how i can map the items needed off one text value.
the struct:
struct CollectionStruct {
var name : String
var description : String
var title : String
var image : PFFile
var id: String
}
and the array made from the struct
var collectionArray = [CollectionStruct]()
var i = 0
for item in collectionArray {
print(collectionArray[i].name)
i += 1
}
printing partArray[i].name gives the following result:
pk00_pt01
pk00_pt02
pk00_pt03
pk01_pt01
pk01_pt02
pk01_pt03
pk01_pt04
pk01_pt05
pk01_pt06
pk01_pt07
pk01_pt08
this is just some test values but there could be thousands of entries here so i wanted to filter the entire array just by the first 4 characters of [i].name i can achieve this by looping through as above but is this achievable using something like .map?
I wanted to filter the entire array just by the first 4 characters of
[i].name
You can achieve this by filtering the array based on the substring value of the name, as follows:
let filteredArray = collectionArray.filter {
$0.name.substring(to: $0.name.index($0.name.startIndex, offsetBy: 4)).lowercased() == "pk00"
// or instead of "pk00", add the first 4 characters you want to compare
}
filteredArray will be filled based on what is the compared string.
Hope this helped.
If you want to group all data automatically by their name prefix. You could use a reducer to generate a dictionary of grouped items. Something like this:
let groupedData = array.reduce([String: [String]]()) { (dictionary, myStruct) in
let grouper = myStruct.name.substring(to: myStruct.name.index(myStruct.name.startIndex, offsetBy: 4))
var newDictionart = dictionary
if let collectionStructs = newDictionart[grouper] {
newDictionart[grouper] = collectionStructs + [myStruct.name]
} else {
newDictionart[grouper] = [myStruct.name]
}
return newDictionart
}
This will produce a dictionary like this:
[
"pk00": ["pk00_pt01", "pk00_pt02", "pk00_pt03"],
"pk01": ["pk01_pt01", "pk01_pt02", "pk01_pt03", "pk01_pt04", "pk01_pt05", "pk01_pt06", "pk01_pt07"],
"pk02": ["pk02_pt08"]
]
Not sure if i am understanding you correctly but it sounds like you are looking for this...
To create a new array named partArray from an already existing array named collectionArray (that is of type CollectionStruct) you would do...
var partArray = collectionArray.map{$0.name}

Swift: associative array with multiple keys:values

I am not expert in Swift and I have been using it for few months to build Mac Apps. I would like to represent in memory a data structure like that of PHP associative arrays but in Swift. Let's imagine that I have a table of data to load in memory with the following fields/records:
ID Surname Name
1 XXX YYY
2 ZZZ WWW
3 JJJ KKK
What I would like to obtain is an associative array like the one I would be able to obtain in PHP:
$arr[1]["Surname"] = "XXX"
$arr[1]["Name"] = "YYY"
$arr[2]["Surname"] = "ZZZ"
$arr[2]["Name"] = "WWW"
I just cannot find the right data structure in Swift to obtain the same result. I tried with the following piece of code:
class resObject: NSObject {
private var cvs = [Int: [String: String]]()
override init() {
self.cvs[0] = ["Name" : "XXX"]
self.cvs[0] = ["Surname" : "YYY"]
self.cvs[1] = ["Name" : "ZZZ"]
self.cvs[1] = ["Surname" : "WWW"]
for (key, arr) in cvs {
let sur = arr["Surname"]
let nam = arr["Name"]
println("Row \(key) - Surname: \(sur), Name: \(nam)")
}
super.init()
}
}
It looks to me pretty close, but it does not work. What I get in the output is the following (I don't care about the "Optional(s)":
Row 0 - Surname: Optional("XXX"), Name: nil
Row 1 - Surname: Optional("ZZZ"), Name: nil
I tried to make some tests in debug and I noticed that the data that are saved in memory are just that of the last key:value pair used (i.e. if I assign Surname first and Name second I get Surname as nil and Name with the correct value).
Please consider that, as in the example, I don't know the data structure when I declare the variable, so I declare it empty and fill it programmatically later.
I don't know if it is just me not declaring the data structure correctly, or if it is Swift that does not allow to do that. Any help will be greatly appreciated.
Thanks a lot.
Regards,
Alessio
One way is a Dictionary of structs. Consider:
struct Person {
var firstName: String
var lastName: String
}
var peopleByID = [ Int: Person ]()
peopleByID[1] = Person(firstName: "First", lastName: "Last")
peopleByID[27] = Person(firstName: "Another", lastName: "LastName")
var myID = 1 // Try changing this to 2 later
if let p = peopleByID[myID] {
println("Found: \(p.firstName) with ID: \(myID)")
}
else {
println("No one found with ID: \(myID)")
}
You can then update the structure:
peopleByID[1].firstName = "XXX"
peopleByID[27].lastName = "ZZZ"
You can iterate freely:
for p in peopleByID.keys {
println("Key: \(p) value: \(peopleByID[p]!.firstName)")
}
Note that a mere array of [Person] isn't so hot, because the IDs:
-- may not be Ints, but are often Strings
-- even if they remain Ints, an array takes up storage in proportion to the highest numbered index, whereas a Dictionary only takes up storage in proportion to the number of stored objects. Imagine storing just two ID's: 523123, and 2467411.
EDIT
It seems like you don't know the attributes ahead of time that will go into each Person object. That's odd, but you should then do:
struct Person {
var attributes = [String : String]() // A dictionary of String keys and String values
}
var peopleByID = [ Int : Person ]()
// and then:
var p1 = Person()
var p2 = Person()
p1.attributes["Surname"] = "Somename"
p1.attributes["Name"] = "Firstname"
p2.attributes["Address"] = "123 Main St."
peopleByID[1] = p1
peopleByID[2] = p2
if let person1 = peopleByID[1] {
println(person1.attributes["Surname"]!)
for attrKey in person1.attributes.keys {
println("Key: \(attrKey) value: \(person1.attributes[attrKey]!)")
}
}

Resources