Related
I am a beginner and not very good with object handling, but I am more of old school looping. That being said I studied object handling because looping became very troublesome with objects, but now I am stuck. I also checked some similar subjects on this forum, but that didn't help me very much.
I am trying to loop through an array of objects that can have a different number of attributes and attribute names can be different, accept for attribute time, and calculate the group average of a given number of object(attributes):
data[
{time: 1674054188078,
val_1: 7,
val_2: 8,
val_3: 9,
val_n: 10
},
{time: 1674053988079,
val_1: 9,
val_2: 2,
val_3: 3,
val_n: 10
},
{time: 1674054188080,
val_1: 4,
val_2: 3,
val_3: 2,
val_n: 6
},
{time: 1674054188081,
val_1: 9,
val_2: 8,
val_3: 7,
val_n: 1
},
{time: 1674054188082,
val_1: 7,
val_2: 8,
val_3: 9
val_n: 10
},
{time: 1674054188083,
val_1: 7,
val_2: 8,
val_3: 9
val_n: 10
}
//etc.....
]
This is what I got so far. But no solution to average calculation.
// Calculate average of points within the chosen interval
// data is original array
// s_table is new array with calculated averages
groupby = 3; // Is the number of objects(attributes) to get mean value of
const arrObjects = Object.keys(data[0]) ?? [];
arrObjects.splice(arrObjects.indexOf("time"), 1);
let s_table = [];
arrObjects.forEach((element, index) => {
s_table[index] = [];
data.forEach(obj => {
//some sort of loop with average calculation with "groupby"
s_table[index].push({ time: obj.time, ???: (obj[element]), etc.... })
})
})
node.warn(s_table);
What I expect is follow results:
groupby = 2;
s_table[
{time: 1674054188078,
val_1: 8,
val_2: 5,
val_3: 6,
val_n: 10
},
{time: 1674054188080,
val_1: 6.5,
val_2: 5.5,
val_3: 4.5,
val_n: 3.5
},
{time: 1674054188082,
val_1: 7,
val_2: 8,
val_3: 9
val_n: 10
},
//etc.....
]
groupby = 3;
s_table[
{time: 1674054188078,
val_1: 6.66,
val_2: 4.33,
val_3: 4.66,
val_n: 8.66
},
{time: 1674054188081,
val_1: 7.66,
val_2: 8,
val_3: 8.33,
val_n: 7
},
//etc.....
]
This is an approach using reduce over the the data array and returning an array containing objects where each property is the average value of the that same property across the whole group. The group size is given by groupBy.
The function modeled here reduceAverage({entries, groupBy}) uses the most functional approach on iterations and embeds in distinct callback functions its atomic operations to better deliver their semantics.
It also takes into account the chance that the last "group" might be smaller than the given groupBy and in that case it will calculate the average based on the size of that latest group.
In case the group size requested is smaller then the passed array size, it will throw an error.
const data = [
{time: 1674054188078,val_1: 7,val_2: 8,val_3: 9,val_n: 10},
{time: 1674053988079,val_1: 9,val_2: 2,val_3: 3,val_n: 10},
{time: 1674054188080,val_1: 4,val_2: 3,val_3: 2,val_n: 6},
{time: 1674054188081,val_1: 9,val_2: 8,val_3: 7,val_n: 1},
{time: 1674054188082,val_1: 7,val_2: 8,val_3: 9,val_n: 10},
{time: 1674054188083,val_1: 7,val_2: 8,val_3: 9,val_n: 10}
];
const o = reduceAverage({entries: data, groupBy:2});
console.log(o);
function reduceAverage({entries, groupBy}){
if(entries.length < groupBy)
throw new Error('groupBy should be less than entries.length');
//returns the properties of obj except time
const getProps = (obj)=>{
return Object.entries(obj).filter( ([key]) => key !== "time" );
}
//resets each property value in group as the average based on groupBy
const setAverage = (group, groupBy)=>{
getProps(group).forEach( ([key, value]) => group[key] = value / groupBy );
};
//sums all the property values from orig object to dest object
const sumPropertyValues = ({orig, dest})=>{
getProps(orig).forEach( ([key, value]) =>{
if(!(key in dest)) dest[key] = 0;
dest[key] += value;
});
};
//returns latest group pushing a new one in groups if needed
const feedLatestGroup = ({groups, groupBy, i, entry})=>{
//latest group
let group = groups[groups.length-1];
//if current entry index belongs to next group
if (i % groupBy == 0){
//sets the average of the previous group object, before..
if (groups.length > 0) setAverage(group, groupBy);
//..creating and pushing the next group with the current time
group = {time: entry.time};
groups.push(group);
}
return group;
}
//creates the reduction of the passed data array
const reduction = entries.reduce(
//for each entry
(groups, entry, i) => {
//feeds and return the current group
const group =
feedLatestGroup({groups: groups, groupBy: groupBy, i: i, entry: entry});
//sums the property values of current entry into current group
sumPropertyValues({orig: entry, dest: group});
//returns the accumulator
return groups;
},
//groups initalized as empty array
[]
);
//calculates the average of the last object using the correct count
const reminder = entries.length % groupBy;
const count = (reminder == 0) ? groupBy : reminder;
setAverage(reduction[reduction.length-1], count);
return reduction;
}
Considering we have the following:
typealias Fruit = String
typealias Count = Int
struct Smoothie {
let uuid: String
let fruits: [Fruit: Count]
}
let smoothies: [Smoothie] = ...
Given a conditions array set by the user:
let conditions: [Fruit] = ...
The lowest the index in the conditions array, the most important we consider it is for sorting.
I would like to sort the smoothies array so that the smoothies that contain the highest number of fruits (Count) from the conditions array, appear first.
If a fruit doesn't appear in the fruits dictionary, we consider its count as 0.
For example:
let fruitsA = ["Apple": 3, "Mango": 4, "Pear": 8, "Cherry": 1, "Banana": 2]
let smoothieA = Smoothie(uuid: "smoothie_a", fruits: fruitsA)
let fruitsB = ["Apple": 10, "Mango": 9, "Grapes": 8, "Cherry": 9]
let smoothieB = Smoothie(uuid: "smoothie_b", fruits: fruitsB)
let fruitsC = ["Apple": 23, "Kiwi": 4, "Pear": 1, "Cherry": 17, "Banana": 8]
let smoothieC = Smoothie(uuid: "smoothie_c", fruits: fruitsC)
let fruitsD = ["Apple": 1, "Orange": 6, "Pear": 8]
let smoothieD = Smoothie(uuid: "smoothie_d", fruits: fruitsD)
let conditions: [Fruit] = ["Apple", "Banana", "Cherry"]
Should return the following:
let sortedSmoothies = [smoothieC, smoothieB, smoothieA, smoothieD]
Because we're first sorting by Apple, then by Banana, then by Cherry count.
If I knew it would always be about Apple, Banana & Cherry, I could do the following and it would just work:
let sortedSmoothies = smoothies.sorted {
if $0.fruits["Apple"] != $1.fruits["Apple"] {
return $0.fruits["Apple"] > $1.fruits["Apple"]
} else if $0.fruits["Banana"] != $1.fruits["Banana"] {
return $0.fruits["Banana"] > $1.fruits["Banana"]
} else {
return $0.fruits["Cherry"] > $1.fruits["Cherry"]
}
}
But in my case I do not know what are the fruits (and how many of them) that the user is going to select for filtering in the conditions array.
Does anyone has an idea how to sort this out please?
Thank you!
let sorted = [smoothieA, smoothieB, smoothieC, smoothieD].sorted { (a, b) in
for condition in conditions {
let aAmount = a.fruits[condition] ?? 0
let bAmount = b.fruits[condition] ?? 0
if aAmount == bAmount {
continue
}
return aAmount > bAmount
}
return true
}
Keep in mind that with your sample data, since "Apple" doesn't have any smoothies with equal values, any test will return that same order. But, you could test with some more interesting numbers that have smoothies that have equal numbers of fruits:
let fruitsA = ["Apple": 10, "Mango": 4, "Pear": 8, "Cherry": 20, "Banana": 2]
let smoothieA = Smoothie(uuid: "smoothie_a", fruits: fruitsA)
let fruitsB = ["Apple": 10, "Mango": 9, "Grapes": 8, "Cherry": 9, "Banana": 2]
let smoothieB = Smoothie(uuid: "smoothie_b", fruits: fruitsB)
let fruitsC = ["Apple": 23, "Kiwi": 4, "Pear": 1, "Cherry": 17, "Banana": 8]
let smoothieC = Smoothie(uuid: "smoothie_c", fruits: fruitsC)
let fruitsD = ["Apple": 1, "Orange": 6, "Pear": 8]
let smoothieD = Smoothie(uuid: "smoothie_d", fruits: fruitsD)
You could create an array of the fruit arrays - String[][] which you might call FruitsArr. You would also create an array or list of fruits that can hold as many fruits as the user puts in.
This is all in loose terms, because the swift syntax is unfamiliar. It might just confuse you to see too much Java syntax, sorry. The logic is universal, though.
for (int i = 0; i < fruits.length; i++){
/*fruits is our imaginary array of user-input fruits. This is sorting the arrays one by one by each fruit in fruits*/
String[] mostFruit; //fill it with zeroes or something.
for (int j = 0; j < FruitsArr.length; j++){
String[] thisFruit = FruitsArr[j]l
if (thisFruit[i] > mostFruit[i]){mostFruit = thisFruit;}
}
}
That checks the question: is the value at index i in thisFruit greater than the value in index i in mostFruit? If so, mostFruit becomes thisFruit. The first thisFruit that has at least 1 of the fruit becomes mostFruit, because 1 > 0. Then the comparison becomes more simple.
Your loop will continue through fruit 1, fruit 2, fruit 3, and the rest that the user puts in. It will check each fruit against every array:
Fruit 1:
Does thisFruit have more than 0 of fruit1?
(next)
Does thisFruit have more of this fruit than the current mostFruit?
(next)
Does thisFruit have more of this fruit than the current mostFruit?
Thus it continues through the rest of the fruit arrays and the rest of the fruits.
I have six items in my array: [1, 2, 3, 4, 5, 6]. I would like to group array items per 3 items like that: [[1, 2, 3], [4, 5, 6]]. Is it possible with underscore?
You can use array#reduce to group your array element.
const arr = [1, 2, 3, 4, 5, 6],
group = 3,
result = arr.reduce((r,v,i) => {
let index = Math.floor(i/group);
(r[index] = r[index] || [])[i%group] = v;
return r;
},[]);
console.log(result);
In Python I can create a repeating list like this:
>>> [1,2,3]*3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
Is there a concise way to do this in Swift?
The best I can do is:
1> var r = [Int]()
r: [Int] = 0 values
2> for i in 1...3 {
3. r += [1,2,3]
4. }
5> print(r)
[1, 2, 3, 1, 2, 3, 1, 2, 3]
You can create a 2D array and then use flatMap to turn it into a 1D array:
let array = [[Int]](repeating: [1,2,3], count: 3).flatMap{$0}
If you want to have a general way of doing this, here's an extension that adds an init method and a repeating method that takes an array which makes this a bit cleaner:
extension Array {
init(repeating: [Element], count: Int) {
self.init([[Element]](repeating: repeating, count: count).flatMap{$0})
}
func repeated(count: Int) -> [Element] {
return [Element](repeating: self, count: count)
}
}
let array = [1,2,3].repeated(count: 3) // => [1, 2, 3, 1, 2, 3, 1, 2, 3]
Note that with the new initializer you can get an ambiguous method call if you use it without providing the expected type:
let array = Array(repeating: [1,2,3], count: 3) // Error: Ambiguous use of ‛init(repeating:count:)‛
Use instead:
let array = [Int](repeating: [1,2,3], count: 3) // => [1, 2, 3, 1, 2, 3, 1, 2, 3]
or
let array:[Int] = Array(repeating: [1,2,3], count: 3) // => [1, 2, 3, 1, 2, 3, 1, 2, 3]
This ambiguity can be avoided if you change the method signature to init(repeatingContentsOf: [Element], count: Int) or similar.
With Swift 5, you can create an Array extension method in order to repeat the elements of the given array into a new array. The Playground sample code below shows a possible implementation for this method:
extension Array {
func repeated(count: Int) -> Array<Element> {
assert(count > 0, "count must be greater than 0")
var result = self
for _ in 0 ..< count - 1 {
result += self
}
return result
}
}
let array = [20, 11, 87]
let newArray = array.repeated(count: 3)
print(newArray) // prints: [20, 11, 87, 20, 11, 87, 20, 11, 87]
If needed, you can also create an infix operator to perform this operation:
infix operator **
extension Array {
func repeated(count: Int) -> Array<Element> {
assert(count > 0, "count must be greater than 0")
var result = self
for _ in 0 ..< count - 1 {
result += self
}
return result
}
static func **(lhs: Array<Element>, rhs: Int) -> Array<Element> {
return lhs.repeated(count: rhs)
}
}
let array = [20, 11, 87]
let newArray = array ** 3
print(newArray) // prints: [20, 11, 87, 20, 11, 87, 20, 11, 87]
You can use modulo operations for index calculations of your base collection and functional programming for this:
let base = [1, 2, 3]
let n = 3 //number of repetitions
let r = (0..<(n*base.count)).map{base[$0%base.count]}
You can create a custom overload for the * operator, which accepts an array on the left and an integer on the right side.
func * <T>(left: [T], right: Int) -> [T] {
return (0..<(right*left.count)).map{left[$0%left.count]}
}
You can then use your function just like in python:
[1, 2, 3] * 3
// will evaluate to [1, 2, 3, 1, 2, 3, 1, 2, 3]
Solution 1:
func multiplerArray(array: [Int], time: Int) -> [Int] {
var result = [Int]()
for _ in 0..<time {
result += array
}
return result
}
Call this
print(multiplerArray([1,2,3], time: 3)) // [1, 2, 3, 1, 2, 3, 1, 2, 3]
Solution 2:
let arrays = Array(count:3, repeatedValue: [1,2,3])
// [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
var result = [Int]()
for array in arrays {
result += array
}
print(result) //[1, 2, 3, 1, 2, 3, 1, 2, 3]
How to remove elements from array that match elements in another array?
Assume we have an array and we loop through it and find out which elements to remove:
var sourceItems = [ ... ]
var removedItems = [SKShapeNode]()
for item : SKShapeNode in sourceItems {
if item.position.y > self.size.height {
removedItems.append(item)
item.removeFromParent()
}
}
sourceItems -= removedItems // well that won't work.
You can use the filter function.
let a = [1, 2, 3]
let b = [2, 3, 4]
let result = a.filter { element in
return !b.contains(element)
}
result will be [1]
Or more succinctly...
let result = a.filter { !b.contains($0) }
Check out the Swift Standard Library Reference
Or you can use the Set type.
let c = Set<Int>([1, 2, 3])
let d = Set<Int>([2, 3, 4])
c.subtract(d)
Be mindful if using the Set option, that your results only be unique values and will not maintain the initial ordering, if that matters to you, whereas the Array filter option will maintain the initial array's order, at least what elements remain.
Swift 3
let c = Set<Int>([65, 1, 2, 3, 1, 3, 4, 3, 2, 55, 43])
let d = Set<Int>([2, 3, 4])
c.subtracting(d)
c = {65, 2, 55, 4, 43, 3, 1}
d = {2, 3, 4}
result = {65, 55, 43, 1}