Swift split array into chunks based on total value - arrays

I am looking for a way to split an array into chunks with a max value, but can't seem to find a solution.
Lets say we have the following code:
struct FooBar {
let value: Int
}
let array: [FooBar] = [
FooBar(value: 1),
FooBar(value: 2),
FooBar(value: 1),
FooBar(value: 1),
FooBar(value: 1),
FooBar(value: 2),
FooBar(value: 2),
FooBar(value: 1)
]
And we want to split this into chunks where the maxSize of FooBar.value doesn't exceed 3. The end result should be something like:
let ExpectedEndResult: [[FooBar]] = [
[
FooBar(value: 1),
FooBar(value: 2)
],
[
FooBar(value: 1),
FooBar(value: 1),
FooBar(value: 1)
],
[
FooBar(value: 2),
],
[
FooBar(value: 2),
FooBar(value: 1)
]
]
I've written this so far, but there is an issue when a 3rd item could be added, also... I believe there must be simpler way but I just can't think of one right now:
extension Array where Element == FooBar {
func chunked(maxValue: Int) -> [[FooBar]] {
var chunks: [[FooBar]] = []
var chunk: [FooBar] = []
self.enumerated().forEach { key, value in
chunk.append(value)
if self.count-1 > key {
let next = self[key+1]
if next.value + value.value > maxValue {
chunks.append(chunk)
chunk = []
}
} else {
chunks.append(chunk)
}
}
return chunks
}
}
Any suggestions?

I would use reduce(into:) for this
let maxValue = 3 //limit
var currentValue = 0 // current total value for the last sub array
var index = 0 // index of last (current) sub array
let groups = array.reduce(into: [[]]) {
if $1.value > maxValue || $1.value + currentValue > maxValue {
$0.append([$1])
currentValue = $1.value
index += 1
} else {
$0[index].append($1)
currentValue += $1.value
}
}
To make it more universal, here is a generic function as an extension to Array that also uses a KeyPath for the value to chunk over
extension Array {
func chunk<ElementValue: Numeric & Comparable>(withLimit limit: ElementValue,
using keyPath: KeyPath<Element, ElementValue>) -> [[Element]] {
var currentValue = ElementValue.zero
var index = 0
return self.reduce(into: [[]]) {
let value = $1[keyPath: keyPath]
if value > limit || value + currentValue > limit {
$0.append([$1])
currentValue = value
index += 1
} else {
$0[index].append($1)
currentValue += value
}
}
}
}
Usage for the sample
let result = array.chunk(withLimit: 3, using: \.value)

Something like:
extension Array where Element == FooBar {
func chunked(maxValue: Int) -> [[FooBar]] {
var chunks: [[FooBar]] = []
var chunk: [FooBar] = []
let filtered = self.filter({ item in
item.value <= maxValue
})
filtered.enumerated().forEach { index, foo in
let currentTotal = chunk.reduce(0, { sum, nextFoo in sum + nextFoo.value })
let newValue = currentTotal + foo.value
if newValue < maxValue {
chunk.append(foo)
} else if newValue == maxValue {
chunk.append(foo)
chunks.append(chunk)
chunk = []
} else {
chunks.append(chunk)
chunk = [foo]
}
}
return chunks
}
}
It could be interesting to write something that goes looking in the array for the perfect groups. The problem with the sequential approach is that one can end up with groups are very low in value when there are perfectly good foos that could fit in the chunk, but they just aren't the next item.
Edit: Added a filter for values above maxValue ... just in case.

Related

Typescript - replace value in an array object

Tried two ways to get partial summary within each array object, but failed.
var arr = [
{ "value": 10, "newBalance": 0 },
{ "value": -10, "newBalance": 0 },
{ "value": 15, "newBalance": 0 },
];
let total = 0;
for (let i = 0, l = arr.length; i < l; ++i) {
total = total + arr[i].value;
arr.map( item => { item.newBalance = total; return item; });
// update all newBalance values with the last total value
arr.map(item => item.newBalance != 0 ? { ...item, newBalance: total } : item);
// doesn't update newBalance
}
console.log(arr);
What am I doing wrong here ?
It can be done cleanly in a single loop with reduce. Note this returns a new array, it won't mutate the original. But you can always reassign arr to the new one if you want.
With reduce you can access the current accumulation which is very useful here as you can get the previous rolling value to build on for each item.
const arrayWithSummary = arr.reduce((summary, currentLineItem, index) => {
return [...summary, { ...currentLineItem, newBalance: currentLineItem.value + (summary?.[index - 1]?.newBalance ?? 0)}]
}, [])
The result is in arrayWithSummary.

Finding indices of max value in swift array

I have 2 arrays. One for players and one for scores. e.g.
var players = ["Bill", "Bob", "Sam", "Dave"]
var scores = [10,15,12,15]
I can find the index of the (first) max score (and the winner's name) by using:
let highScore = scores.max()
let winningPlayerIndex = scores.index(of: highScore!)
let winningPlayer = players[winningPlayerIndex!]
This works fine if there is only one player with the highest score but how would I return multiple indices (i.e. 1 and 3 in this example) for all values that are equal to the max value? I need the indices to then map back to the players array to pull out the names of all the players with the highest score. Or is there a better way to do all of this?
The accepted answer doesn't generalize to comparing computed values on the elements. The simplest and most efficient way to get the min/max value and index is to enumerate the list and work with the tuples (offset, element) instead:
struct Player {
let name: String
let stats: [Double]
}
let found = players.enumerated().max(by: { (a, b) in
battingAvg(a.element.stats) < battingAvg(b.element.stats)
})
print(found.element.name, found.offset) // "Joe", 42
In general you shouldn't rely on comparing floating point values by equality and even where you can, if the computation is expensive you don't want to repeat it to find the item in the list.
What you need is to use custom class or structure and make array of it then find max score and after that filter your array with max score.
struct Player {
let name: String
let score: Int
}
Now create array of this Player structure
var players = [Player(name: "Bill", score: 10), Player(name: "Bob", score: 15), Player(name: "Sam", score: 12), Player(name: "Dave", score: 15)]
let maxScore = players.max(by: { $0.0.score < $0.1.score })?.score ?? 0
To get the array of player with max core use filter on array like this.
let allPlayerWithMaxScore = players.filter { $0.score == maxScore }
To get the array of index for player having high score use filter on array like this.
let indexForPlayerWithMaxScore = players.indices.filter { players[$0].score == maxScore }
print(indexForPlayerWithMaxScore) //[1, 3]
To answer just the question in the title -- find the index of the max value in a (single) array:
extension Array where Element: Comparable {
var indexOfMax: Index? {
guard var maxValue = self.first else { return nil }
var maxIndex = 0
for (index, value) in self.enumerated() {
if value > maxValue {
maxValue = value
maxIndex = index
}
}
return maxIndex
}
}
The extension returns nil if the array is empty. Else, it starts by assuming the first value is the max, iterates over all values, updates the index and value to any larger values found, and finally returns the result.
If you have 2 arrays and need to find max score from first one in order to pull the name from second one, then I would recommend you to convert both arrays into one using zip high order func and retrieve the max value from there.
So having your data it will look like this:
let players = ["Bill", "Bob", "Sam", "Dave"]
let scores = [10,15,12,15]
let data = zip(players, scores)
// max score
let maxResult = data.max(by: ({ $0.1 < $1.1 }))?.1 ?? 0
// outputs 15
// leaders
let leaders = data.filter { $0.1 >= maxResult }.map { "\($0.0) - \($0.1)" }
// outputs ["Bob - 15", "Dave - 15"]
You can zip the collection indices with its elements and get the minimum value using collection min method and pass a predicate to compare the elements. Get the result and extract the index of the tuple:
let numbers = [2, 4, 4, 2, 3, 1]
let minIndex = zip(numbers.indices, numbers).min(by: { $0.1 < $1.1 })?.0 // 5
let maxIndex = zip(numbers.indices, numbers).max(by: { $0.1 < $1.1 })?.0 // 1
As an extension where the elements are comparable:
extension Collection where Element: Comparable {
func firstIndexOfMaxElement() -> Index? {
zip(indices, self).max(by: { $0.1 < $1.1 })?.0
}
func firstIndexOfMinElement() -> Index? {
zip(indices, self).min(by: { $0.1 < $1.1 })?.0
}
}
Usage:
numbers.firstIndexOfMinElement() // 5
If you need to find the maximum or minimum properties:
extension Collection {
func firstIndexOfMaxElement<T: Comparable>(_ predicate: (Element) -> T) -> Index? {
zip(indices, self).max(by: { predicate($0.1) < predicate($1.1) })?.0
}
func firstIndexOfMinElement<T: Comparable>(_ predicate: (Element) -> T) -> Index? {
zip(indices, self).min(by: { predicate($0.1) < predicate($1.1) })?.0
}
}
Usage:
struct Product {
let price: Int
}
let products: [Product] = [.init(price: 2),
.init(price: 4),
.init(price: 4),
.init(price: 2),
.init(price: 3),
.init(price: 1),]
let minPrice = products.firstIndexOfMinElement(\.price) // 5
To return the maximum and minimum elements and their indices:
extension Collection where Element: Comparable {
func maxElementAndIndices() -> (indices: [Index], element: Element)? {
guard let maxValue = self.max() else { return nil }
return (indices.filter { self[$0] == maxValue }, maxValue)
}
func minElementAndIndices() -> (indices: [Index], element: Element)? {
guard let minValue = self.min() else { return nil }
return (indices.filter { self[$0] == minValue }, minValue)
}
}
And the corresponding methods to custom structures/classes:
extension Collection {
func maxElementsAndIndices<T: Comparable>(_ predicate: (Element) -> T) -> [(index: Index, element: Element)] {
guard let maxValue = self.max(by:{ predicate($0) < predicate($1)}) else { return [] }
return zip(indices, self).filter { predicate(self[$0.0]) == predicate(maxValue) }
}
func minElementsAndIndices<T: Comparable>(_ predicate: (Element) -> T) -> [(index: Index, element: Element)] {
guard let minValue = self.min(by:{ predicate($0) < predicate($1)}) else { return [] }
return zip(indices, self).filter { predicate(self[$0.0]) == predicate(minValue) }
}
}
Usage:
let maxNumbers = numbers.maxElementAndIndices() // ([1, 2], element 4)
let minNumbers = numbers.minElementAndIndices() // ([5], element 1)
let maxPriceIndices = products.maxElementsAndIndices(\.price) // [(index: 1, element: Product(price: 4)), (index: 2, element: Product(price: 4))]
let minPriceIndices = products.minElementsAndIndices(\.price) // [(index: 5, element: __lldb_expr_22.Product(price: 1))]
There are a couple of ways to solve your problem, you can solve this by saving the indices of scores.max() and iterate through the players list, and also using then zip function:
var max_score = scores.max()
var players_and_score = zip(players, scores)
for player in players_and_score{
if player.1 == max_score{
print(player.0)
}
}

Index out of range - error in function

I have a function with 2 parameters - arrays of Int called Numbers and Numbers1. I want to multiply each element in Numbers on index "i" with each element in Numbers2 on the same index. Then I want to get a total sum of the results from the multiplying. When I call the function, it displays error - Index out of range. Code bellow:
var sum = Int()
var Array = [Int]()
var totalsum = Int()
func prumerdanehopredmetu(Numbers:[Int], Numbers2:[Int]) -> Int {
for i in Numbers {
sum = Numbers[i] * Numbers2[i]
Array.insert(sum, at: 0)
}
totalsum = Array.reduce(0,+)
return totalsum
}
prumerdanehopredmetu(Numbers: [1,2,3], Numbers2: [1,2,3]) //error
update:
for i in Numbers.indices
This worked.
You're iterating the numbers ([1, 2, 3]), not their indices (0, 1, 2). Try this instead:
var sum = Int()
var sums = [Int]()
var totalsum = Int()
func prumerdanehopredmetu(_ numbers: [Int], _ numbers2: [Int]) -> Int {
for i in numbers.indices {
sum = numbers[i] * numbers[i]
sums.append(sum)
}
totalsum = Array.reduce(0,+)
return totalsum
}
prumerdanehopredmetu([1,2,3], [1,2,3])
There is a much easier/simpler way though. You can just use zip, and map
let products = zip([1, 2, 3], [1, 2, 3]).map(*) // results in [1, 4, 9]
let sumOfProducts = products.reduce(0, +) // 14
In your loop, "i" is equal to the values in "Numbers", not the indices of "Numbers". Here's an example of how such a "for" loop works in Swift:
let myArray = ["John", "Timothy", "James", "Tanmay"]
for i in myArray {
print(i) // "John", "Timothy" ...
}
Whereas, your code expects "i" to be 0, 1, 2 ...
In Swift, you can also have a for loop iterate through the indices and values of an array, by enumerating the array:
let myArray = ["John", "Timothy", "James", "Tanmay"]
for (index, value) in myArray.enumerated() {
print(index) // 0, 1 ...
print(value) // "John", "Timothy" ...
}
Of course, in Swift, you can also loop through just the indices, by finding the indices of the array:
let myArray = ["John", "Timothy", "James", "Tanmay"]
for i in myArray.indices {
print(i) // 0, 1 ...
}
So, in order for your code to work, you must loop through the indices of "Numbers", and not the values:
func prumerdanehopredmetu(Numbers:[Int], Numbers2:[Int]) -> Int {
for i in Numbers.indices {
sum = Numbers[i] * Numbers2[i]
Array.insert(sum, at: 0)
}
totalsum = Array.reduce(0,+)
return totalsum
}
func multiply(array1: [Int], array2: [Int]) -> Int {
if array1.count != array2.count {
//error handler
return 0
} else {
var i = 0
var sum = 0
while i < array1.count {
sum = sum + array1[i]*array2[i]
i = i + 1
}
return sum
}
}
you should consider your array have different number of values.

Getting the most frequent value of an array

I have an Array of numbers and I want to know which number is most frequent in this array. The array sometimes has 5-6 integers, sometimes it has 10-12, sometimes even more - also the integers in the array can be different. So I need a function which can work with different lengths and values of an array.
One example:
myArray = [0, 0, 0, 1, 1]
Another example:
myArray = [4, 4, 4, 3, 3, 3, 4, 6, 6, 5, 5, 2]
Now I am searching for a function which gives out 0 (in the first example) as Integer, as it is 3 times in this array and the other integer in the array (1) is only 2 times in the array. Or for the second example it would be 4.
It seems pretty simple, but I cannot find a solution for this. Found some examples in the web, where the solution is to work with dictionaries or where the solution is simple - but I cannot use it with Swift 3 it seems...
However, I did not find a solution which works for me. Someone has an idea how to get the most frequent integer in an array of integers?
You can also use the NSCountedSet, here's the code
let nums = [4, 4, 4, 3, 3, 3, 4, 6, 6, 5, 5, 2]
let countedSet = NSCountedSet(array: nums)
let mostFrequent = countedSet.max { countedSet.count(for: $0) < countedSet.count(for: $1) }
Thanks to #Ben Morrow for the smart suggestions in the comments below.
let myArray = [4, 4, 4, 3, 3, 3, 4, 6, 6, 5, 5, 2]
// Create dictionary to map value to count
var counts = [Int: Int]()
// Count the values with using forEach
myArray.forEach { counts[$0] = (counts[$0] ?? 0) + 1 }
// Find the most frequent value and its count with max(by:)
if let (value, count) = counts.max(by: {$0.1 < $1.1}) {
print("\(value) occurs \(count) times")
}
Output:
4 occurs 4 times
Here it is as a function:
func mostFrequent(array: [Int]) -> (value: Int, count: Int)? {
var counts = [Int: Int]()
array.forEach { counts[$0] = (counts[$0] ?? 0) + 1 }
if let (value, count) = counts.max(by: {$0.1 < $1.1}) {
return (value, count)
}
// array was empty
return nil
}
if let result = mostFrequent(array: [1, 3, 2, 1, 1, 4, 5]) {
print("\(result.value) occurs \(result.count) times")
}
1 occurs 3 times
Update for Swift 4:
Swift 4 introduces reduce(into:_:) and default values for array look ups which enable you to generate the frequencies in one efficient line. And we might as well make it generic and have it work for any type that is Hashable:
func mostFrequent<T: Hashable>(array: [T]) -> (value: T, count: Int)? {
let counts = array.reduce(into: [:]) { $0[$1, default: 0] += 1 }
if let (value, count) = counts.max(by: { $0.1 < $1.1 }) {
return (value, count)
}
// array was empty
return nil
}
if let result = mostFrequent(array: ["a", "b", "a", "c", "a", "b"]) {
print("\(result.value) occurs \(result.count) times")
}
a occurs 3 times
The most frequent value is called the "mode". Here's a concise version:
let mode = myArray.reduce([Int: Int]()) {
var counts = $0
counts[$1] = ($0[$1] ?? 0) + 1
return counts
}.max { $0.1 < $1.1 }?.0
Whether that's considered "unreadable" or "elegant" depends on your feelings towards higher order functions. Nonetheless, here it is as a generic method in an extension on Array (so it'll work with any Hashable element type):
extension Array where Element: Hashable {
var mode: Element? {
return self.reduce([Element: Int]()) {
var counts = $0
counts[$1] = ($0[$1] ?? 0) + 1
return counts
}.max { $0.1 < $1.1 }?.0
}
}
Simply remove the .0 if you'd rather have a tuple that includes the count of the mode.
My take on it with Swift 5:
extension Collection {
/**
Returns the most frequent element in the collection.
*/
func mostFrequent() -> Self.Element?
where Self.Element: Hashable {
let counts = self.reduce(into: [:]) {
return $0[$1, default: 0] += 1
}
return counts.max(by: { $0.1 < $1.1 })?.key
}
}
I have tried the following code. It helps especially when the max count is applicable for 2 or more values.
var dictionary = arr.reduce(into: [:]) { counts, number in counts[number, default: 0] += 1}
var max = dictionary.values.max()!
dictionary = dictionary.filter{$0.1 == max}
mode = dictionary.keys.min()!
func mostR(num : [Int]) -> (number : Int , totalRepeated : Int)
{
var numberTofind : Int = 0
var total : Int = 0
var dic : [Int : Int] = [:]
for index in num
{
if let count = dic[index]
{
dic[index] = count + 1
}
else
{
dic[index] = 1
}
}
var high = dic.values.max()
for (index , count) in dic
{
if dic[index] == high
{
numberTofind = index
top.append(count)
total = count
}
}
return (numberTofind , total)
}
var array = [1,22,33,55,4,3,2,0,0,0,0]
var result = mostR(num : [1,22,3,2,43,2,11,0,0,0])
print("the number is (result.number) and its repeated by :(result.totalRepeated)" )
Here is an encapsulated/reusable method.
extension Array where Element: Hashable {
/// The mode will be nil when the array is empty.
var mode: Element? {
var counts: [Element: Int] = [:]
forEach { counts[$0] = (counts[$0] ?? 0) + 1 }
if let (value, count) = counts.max(by: {$0.1 < $1.1}) {
print("\(value) occurs \(count) times")
return value
} else {
return nil
}
}
}
usage:
print([3, 4, 5, 6, 6].mode) // 6
Keep track of each occurrence, counting the value of each key in a dictionary. This case is exclusive for integers. Will update this method using generics.
func mostCommon(of arr: [Int]) -> Int {
var dict = [Int:Int]()
arr.forEach {
if let count = dict[$0] {
dict[$0] = count + 1
} else {
dict[$0] = 1
}
}
let max = dict.values.max()
for (_ , value) in dict {
if value == max {
return value
}
}
return -1
}

How would I go about categorizing items within an Array in swift?

In swift, I want to categorize items in an existing array and place them accordingly in one new string.
Here is an example of what I want to do:
originalArray = ["hotdog","fries","hotdog","coke","coke","fries","hotdog"]
resultingString = "hotdog x 3, fries x 2, coke x 2"
How would I go about doing this?
Try this:
let originalArray = ["hotdog","fries","hotdog","coke","coke","fries","hotdog"]
var dict = [String: Int]()
let resultString = originalArray.reduce(dict) { _, element in
if dict[element] == nil {
dict[element] = 1
} else {
dict[element]! += 1
}
return dict
}
.map { "\($0) x \($1)" }
.joinWithSeparator(", ")
If you want to keep the original order of the array (ie: hotdog, fries, coke), the code is slightly more complicated:
let originalArray = ["hotdog","fries","hotdog","coke","coke","fries","hotdog"]
var dict = [String: (index: Int, count: Int)]()
let resultString = originalArray.enumerate()
.reduce(dict) { _ , e in
if let value = dict[e.element] {
dict[e.element] = (index: value.index, count: value.count + 1)
} else {
dict[e.element] = (index: e.index, count: 1)
}
return dict
}
.sort { return $0.1.index < $1.1.index }
.map { "\($0) x \($1.count)" }
.joinWithSeparator(", ")
print(resultString)
I think this will help you:
let originalArray = ["hotdog","fries","hotdog","coke","coke","fries","hotdog"]
var resultingString = ""
var counts:[String:Int] = [:]
for item in originalArray {
counts[item] = (counts[item] ?? 0) + 1
}
resultingString = counts.map { (key, value) -> String in
return "\(key) x \(value)"
}.joinWithSeparator(",")
print(resultingString)
Here is the output: coke x 2, hotdog x 3, fries x 2

Resources