fun main() {
val data = ArrayList<List<String>>()
data.add(listOf("32701", "First"))
data.add(listOf("32702", "Second"))
data.add(listOf("32702", "Second"))
data.add(listOf("32701", "First True"))
println(data.distinct())
}
Result :
[[32701, First], [32702, Second], [32701, First True]]
Question How about removing data [32701, First] and get new data with the same value ?
Expected :
[32702, Second], [32701, First True]]
The problem is that distinct() uses the equals methods and comparing the entirety of the list.
You could use distinctyBy { it.first() } if you can ensure lists wont be empty
Edit
In order to get latest value you can:
a) Reverse the list and then call distinctBy
yourList
.reversed() // Now latest values are first in the list
.distinctBy { it.first() } // first element of list holds the id
b) Associate the values into a map of Map<String, List<String>> by calling associateBy { it.first()} and getting the last value of the map by calling
val correctResults = map.values.map { valueList -> valueList.last() }
As a whole would look like:
yourList
.associateBy { it.first() }
.values
.map { valueList -> valueList.last() }
Be aware that any of these approaches IS NOT dealing with empty lists.
In order to deal with empty lists you could filter them out by just doing
val listsThatAreNotEmpty = yourList.filter { it.isNotEmpty() }
Use a combination of reversed and disinctBy:
fun main() {
val Data = ArrayList<List<String>>()
Data.add(listOf("32701", "First"))
Data.add(listOf("32702", "Second"))
Data.add(listOf("32702", "Second"))
Data.add(listOf("32701", "First True"))
println(Data.reversed().distinctBy{it[0]} )
// prints [[32701, First True], [32702, Second]]
}
You can reverse the result again to get the original relative order.
As mentioned by others, the use of listOf is sub-optimal, here is a cleaner version:
data class Item(val id: String, val text: String)
fun distinct(data : List<Item>) = data.reversed().distinctBy{it.id}
fun main() {
val data = listOf(
Item("32701", "First"),
Item("32702", "Second"),
Item("32702", "Second"),
Item("32701", "First True")
)
println(distinct(data) )
// [Item(id=32701, text=First True), Item(id=32702, text=Second)]
}
Related
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
basically the title.
If I had to find what I know as a single field,
a.any {
it.name == "user"
}
Now I've a listOf(Groups)
Which contains a unique ID
I want to check
if user.groups.anyItemInThisList.UNIQUEID == otheruser.groups.anyItemInThisList.UNIQUEID
My data looks like this
{
"groups":[
{
"id":4
"group":"Test Group",
"role":"creator",
"member_count":1,
"userType":"local"
}
]
}
To rephrase your question (making sure I understand correctly), you have two Lists of the same kind of item, and you want to determine if there is any value of the id property of an item that appears in both lists.
To do this with simple code, but O(n^2) time, you could use this. It iterates all items from a and for each item it iterates b to see if there are any matches.
val result = a.any { x1 -> b.any { x2 -> x1.id == x2.id} }
To do it in O(n) you can do it with a Set. This creates a set of the names from the first list, and then it only has to iterate the second list once to see if any of the names are in the first set.
val aIds = a.mapTo(HashSet(a.size)) { it.id }
val result = b.any { it.id in aIds }
I have the following code:
struct MyData {
var company = String();
var score:Int;
}
let data = [
MyData(company: "smiths", score: 4 ),
MyData(company: "lukes", score: 4),
MyData(company: "lukes", score: 9)
]
extension MyData: CustomStringConvertible {
var description: String {
return "(\(company), \(score))"
}
}
data.sorted { ($0.company, $1.score) < ($1.company, $0.score) }
print(data)
My output is:
[(smiths, 4), (lukes, 4), (lukes, 9)]
However, I want it to be the other way around:
[(lukes, 9), (lukes, 4), (smiths, 4)]
Can someone show me what it is I'm doing wrong?
You want to sort by company ascending then by score descending.
First of all – as mentioned in the comments – you have to assign the result to a new variable, print(data) prints the unsorted original array.
The sort algorithm is supposed to be
if the companies are equal sort the scores descending otherwise sort the companies ascending
let sortedData = data.sorted { ($0.company == $1.company) ? $0.score > $1.score : $0.company < $1.company}
print(sortedData)
Using the sorting rule described in comments, this is how your custom sorting function should look like:
let sorted = data.sorted(by: { this, next in
if this.company < next.company {
return true //first sort based on ascending company name
} else if this.company == next.company {
return this.score > next.score //if company name is the same, sort by descending score
} else {
return false //if company name is later in the alphabet, put it later in the sorted output
}
})
print(sorted)
Output:
[(lukes, 9), (lukes, 4), (smiths, 4)]
Moreover, the code you used didn't work for two reasons: sorted returns a new array containing the sorted results, it's not a mutating function. It couldn't even work with a mutating function, since data is immutable.
So I discovered that my solution was almost there. Because the sorted function returns a new array, and doesn't actually sort the array it is being called on, my print statement was always printing the original collection. The solution to my problem was:
var myArray = data.sorted { ($0.company, $1.score) < ($1.company, $0.score) }
print(myArray)
Thanks very much for the help. :-)
In testing, it would be convenient to have a simple singleton data model that acts like an array. That is, if I have a singleton class called MySingletonClass that supports a sharedInstance variable you should be able to change an element in the array with subscripts:
MySingletonClass.sharedInstance[ 0 ] = "New item 0"
Also, when the app opens, it would be great if a simple assignment allowed the system to provide initial values to the array:
MySingletonClass.sharedInstance = ["Item 0", "Item 1", "Item 2"]
I can do the first by providing the MySingletonClass with a subscript/get/set statement. However, I've not found any suggestions about how to perform the latter. Here's my class as it currently exists:
class MySingletonClass {
class var sharedInstance: MySingletonClass {
get {
struct Singleton {
static let instance = MySingletonClass()
}
return Singleton.instance
}
}
var toDoItems = [ String ]()
subscript( row: Int ) -> NSString? {
get {
if isValidIndex( row ) {
return toDoItems[ row ]
} else {
return nil
}
}
set {
assert(isValidIndex( row ), "invalid row number \(row) in a ToDo array of \(toDoItems.count) items")
toDoItems[ row ] = newValue!
}
}
/**
Returns true if the passed index is valid in the toDoItems array
The array is indexed from 0 to toDoItems.count - 1 (inclusive). The "row" value is judged
to be valid only if it lies within this range.
:param: row (Int), specifies an index number ( to count-1) in the toDoItems array
:returns: a boolean to indicate if the passed index number is valid
*/
func isValidIndex( row: Int ) -> Bool {
if ( row >= 0 ) && ( row < toDoItems.count ) {
return true
} else {
return false
}
}
}
There is a kludgey way to assign initial values to this class using an array:
MySingletonClass.sharedInstance.toDoItems = ["zero", "one", "two"]
println( "first element: '\(MySingletonClass.sharedInstance[ 0 ]!)'" )
However, it forces the user to be aware of the "toDoItems" variable, which I would prefer to remain hidden. Any thoughts?
To get the behavior that you're looking for you can create a private variable that's only accessible within the scope of your MySingletonClass file, but not actually part of the class itself.
private var instance: MySingletonClass!
class MySingletonClass {
class var sharedInstance: MySingletonClass {
get {
return instance
}
set(newItems) {
println("NewItems")
}
}
private var toDoItems = [String]()
subscript( row: Int ) -> String? {
get {
if isValidIndex( row ) {
return toDoItems[ row ]
} else {
return nil
}
}
set {
//assert(isValidIndex( row ), "invalid row number \(row) in a ToDo array of \(toDoItems.count) items")
toDoItems[ row ] = newValue!
}
}
private init(items: [String]) {
self.toDoItems = items
}
class func initialize(items: [String]) {
instance = MySingletonClass(items: items)
}
func isValidIndex( row: Int ) -> Bool {
if ( row >= 0 ) && ( row < toDoItems.count ) {
return true
} else {
return false
}
}
}
Now you can use the class func initialize to create your shared instance without exposing toDoItems outside of the class.
class Controller {
init() {
MySingletonClass.initialize(["Get sack of crickets", "Pick up Lizards from daycare"])
MySingletonClass.sharedInstance[ 0 ] = "Turn on Heat Lamps"
}
}
The bad news is that in Swift 1.1 you cannot use the standard assignment operator to assign an array to a singleton data model. In Apples book "The Swift Programming Language" there is a note saying:
It is not possible to overload the default assignment operator (=).
Of course, you could make up your own assignment operator, but that would just leave your users guessing at your departure from normal array practice. You may see suggestions that a singleton array could be created using a "struct". I have not been able to make that suggestion work as a true singleton, see this link.
The good news is that it is possible to set up a singleton data model that otherwise acts just like an array. That is, you can use the standard array functions like ".append", ".remove: atIndex:", etc. You only have to set up an empty Swift file and paste in the code below. The code is in two parts. The first is a protocol called Item. The second part is a class that establishes the singleton and encapsulates an array called "items". You can add, modify, or remove objects in "items" provided that the objects conform with the Item protocol. An example is shown below.
Model.data.append( myItemConformingInstance )
One word of warning, Xcode has some difficulties presenting singletons and this file makes Xcode really twitchy. You're welcome to modify it, but you will need patience!
import Foundation
/**
This module provides a simple Data Model for you to use in a Model-View-Controller setup. The class Model provides a singleton called data. You can treat the "data" singleton as if it were an array (see below for an exception related to assignments). Model.data stores objects that conform to the Item protocol. You provide your own class that conforms to this protocol, and also provides instance variablesimport Foundation
*/
protocol Item {
/**
Returns a single-line description of the data stored in an instance conforming to the Item protocol.
Required to support plist-like description of the values stored in the items array
:returns: (String) a description of the item's data
*/
var description: String { get }
/* Make your Item-conforming class also conform to Equatable. It should have a function that determines if two instances of your class are considered equal. The function must be in the same file, but NOT INSIDE your Item-conforming class. The function looks like:
func ==(left: <NewItemClass>, right: <NewItemClass>) -> Bool
*/
}
// MARK: - start of Model singleton
/**
The Model.data singleton should look like a Swift Array, so it supports these functions:
ACCESSING ARRAY ELEMENTS
subscript(_: Int)
gets or sets existing elements using square bracket subscripting. If the index is not valid for the current items array then an assertion is triggered. It is not possible to insert additional items into the array using subscripts larger than the current highest index for the items array (see .append).
subscript(_: Range<Int>)
gets or sets a subrange of existing elements in the items array. If any of the indices in the specified range are not valid then an assertion is triggered. A subrange can be replaced (or eliminated) by a new array of objects conforming to the Item protocol, and the new array does not have to have the same number of entries as is specified by the Range.
ADDING OR REMOVING ELEMENTS
Model.data.append( newItem: Item )
Adds a new Item-conforming object as the last element in the items array
Model.data.insertAtIndex( <Item>, atIndex: <Int> )
Inserts an Item-conforming object into the items array at the given index. If the index is not valid then an assertion is triggered.
Model.data.removeAtIndex( <Int> )
Removes the element in the items collection at the given index and returns the removed element. If the index is invalid then an asertion is triggered
Model.data.removeLast()
Removes the last element in the items collection and returns the removed element. If the items array is already empty then an assertion is triggered.
Model.data.removeAll( keepCapacity: Bool)
Removes all elements from the items array and by default clears the underlying storage buffer
Model.data.reserveCapacity( minimumCapacity: Int )
QUERYING THE ARRAY OF ITEMS
Model.data.count
Returns the number of elements in the items array.
Model.data.isEmpty
Returns true if the items array is empty, otherwise returns false.
Model.data.capacity
Returns an integer value representing the number of elements that the items array can store without reallocation.
Model.data.isValidIndex( index: Int ) (not standard)
returns true if the given index is between 0 and items.count - 1, otherwise it returns false.
Model.data.description( optional limit: <Int>? ) (not standard)
returns "empty" if the items array is empty, otherwise returns an enumerated list of Item descriptions. Ordinarily all items are listed, unless you provide a limit to tell the system how many items to list.
ISSUES
Although it would be nice to be able to assign Model.data usign the standard approach, e.g.
*Model.data = [ item1, item2, item3 ]
*THIS WON'T WORK, and the Swift 1.1 documentation says that the assign operator (=) cannot be overridden. Instead, use the underlying items array. E.G:
*Model.data.items = [ Item1, Item2, Item3 ]
This approach uses the normal singleton method for Swift 1.1. Reportedly, it gets much simpler in Swift 1.2
*/
class Model {
class var data: Model {
get {
struct Singleton {
static let instance = Model()
}
return Singleton.instance
}
}
var items = [ Item ]()
// MARK: - ACCESSING ARRAY ELEMENTS
subscript( index: Int ) -> Item? {
get {
if isValidIndex( index ) {
return items[ index ]
} else {
return nil
}
}
set {
assert(isValidIndex( index ),
"Fatal error: could not replace an item in Model.data using index number: \(index) in an items array of: \(items.count) items")
items[ index ] = newValue!
}
}
subscript( subRange: Range<Int> ) -> Slice<Item> {
get {
assert(isValidIndex( subRange.startIndex ),
"Fatal error: could not retrieve a range of items in Model.data using low index number: \(subRange.startIndex) from an items array of: \(items.count) items")
// in testing, it looks as though subRange is always set up to do a test of range
// [ .startIndex..<.endIndex ], which means that .endIndex can be equal to the count
// of elements in a array.
assert(subRange.endIndex <= items.count,
"Fatal error: could not retrieve a range of items in Model.data using high index number: \(subRange.endIndex) from an items array of: \(items.count) items")
return self.items[ subRange ]
}
set {
assert(isValidIndex( subRange.startIndex ),
"Fatal error: could not replace a range of items in Model.data using low index number: \(subRange.startIndex) in an items array of: \(items.count) items")
assert( subRange.endIndex <= items.count,
"Fatal error: could not replace a range of items in Model.data using high index number: \(subRange.endIndex) in an items array of: \(items.count) items")
items[ subRange ] = newValue
}
}
// MARK: - ADDING OR REMOVING ELEMENTS
/**
Adds a new object conforming to Item at the end of items array
:param: newItem (Item) an object conforming to the Item protocol
*/
func append( newItem: Item ) {
self.items.append( newItem )
}
/**
Inserts a new item into the items array based on a passed index number
The insertion places an object conforming with the Item protocol into the items array at the position specified by atIndex. Existing items that are at that position or higher are moved up by 1. Insertions fail if the passed index number is not valid.
:param: newItem (Item) an object conforming with Item
:param: atIndex (Int) the position in the list where the new task name is to be inserted.
*/
func insert( newItem: Item, atIndex: Int ) {
assert(isValidIndex( atIndex ),
"Fatal error: could not insert a new item into Model.data with invalid index number: \(atIndex) in an items array of: \(items.count) items")
self.items.insert( newItem, atIndex: atIndex )
}
/**
removes an item at the items array at the specified index position
If the specified index is not valid then a runtime error is generated. After a successful deletion, the items that started with index numbers higher than the passed index number will end with their index numbers decremented by 1.
:param: atIndex (Int) the index number of the data item to be removed
:returns: (Item) This is the item that was removed from the items array.
*/
func removeAtIndex(atIndex: Int) -> Item? {
assert(isValidIndex( atIndex ),
"Fatal error: could not remove an item from Model.data using index number: \(atIndex) in an items array of: \(items.count) items")
return self.items.removeAtIndex( atIndex )
}
/**
removes the last Item object in the items array in Model.data
If the items array is empty then a runtime error is generated
:returns: (Item) an object conforming to the Item protocol that was removed from the items array.
*/
func removeLast() -> Item {
assert( self.items.count > 0, "Fatal error: could not remove the 'last' item from Model.data since the items collection was already empty." )
return self.items.removeLast()
}
/**
Removes all items in the items array
:param: keepCapacity: Bool if true then overrides default clearing of the underlying buffer
*/
func removeAll( keepCapacity: Bool = false ) {
self.items.removeAll(keepCapacity: keepCapacity)
}
/*
Ensures that the underlying storage for the items array can hold the given total number of elements
*/
func reserveCapacity(minimumCapacity: Int) {
self.items.reserveCapacity( minimumCapacity )
}
// MARK: - QUERYING THE ARRAY OF ITEMS
/**
Returns an integer value indicating how many items the items array in Model.data can store
You will have to reallocate the Model.data.items array if storage capacity is exceeded, using
reserveCapacity.
:returns: (Int) the number of items that the items array can store.
*/
var capacity: Int {
get {
return self.items.capacity
}
}
/**
Returns the number of items in the items array
*/
var count: Int {
get {
return items.count
}
}
/**
Returns true if the items array of Model.data is empty
:returns: (Bool) true if the items array s empty, otherwise false.
*/
var isEmpty: Bool {
get {
return self.items.isEmpty
}
}
/**
Returns true if the passed index is valid in the data array
The items array is indexed from 0 to items.count - 1 (inclusive). The index is judged to be valid only if it lies within this range.
:param: index (Int), specifies an index number in for an object in the items array.
:returns: a boolean to indicate if the passed index number is valid
*/
func isValidIndex( index: Int ) -> Bool {
if ( index >= 0 ) && ( index < items.count ) {
return true
} else {
return false
}
}
/**
Returns a string in the form of an ennumerated list that shows a description for each item in the items array
:param: limit: Int? tells the system to limit the list to "limit" items.
:returns: (String) the ennumerated list of items
*/
func description( limit: Int? = nil, title: String? = nil ) -> String {
var msg = ""
if let label = title {
msg = "*** \(label)\n"
}
if items.count == 0 {
msg += "the items array is empty\n"
} else {
var index = 0
for nextItem in self.items {
msg = msg + "[\(index)].\t\(nextItem.description)\n"
index++
}
}
return msg
}
}
To test this, imagine you want the singleton array to represent items on a to-do list. Your class might look like:
import Foundation
class ToDoItem: Item, Equatable {
var name: String
var createdAt: NSDate
var done: Bool
init( name: String, createdAt: NSDate, done: Bool = false ) {
self.name = name
self.createdAt = createdAt
self.done = done
}
var description: String {
get {
return "Task Name:\( name ), Created On:\( createdAt ), Done: \(done)"
}
}
}
// External function to make equivalence between Items testable
func ==( left: ToDoItem, right: ToDoItem ) -> Bool {
return
left.done == right.done
&&
left.createdAt == right.createdAt
&&
left.name == right.name
}
The singleton array Model.data is in your project just by including its class. You can begin adding to the singleton array Model.data by instantiating your Item class and appending:
let task1 = ToDoItem(name: "task 1", createdAt: NSDate(), done: false )
let task2 = ToDoItem(name: "task 2", createdAt: NSDate(), done: false )
let task3 = ToDoItem(name: "task 3", createdAt: NSDate(), done: false )
Model.data.append( task1 )
Model.data.append( task2 )
Model.data.append( task3 )
In addition to the standard functions provided by Swift Array objects, there is a method, description( limit:Int?, title:String: ). The method returns a string listing each item in your singleton array. The limit parameter is optional, and tells the system that you only want to see a limited number of items from your array. The title parameter puts a text at the start of the description. For example:
Model.data.description( title: "First Items" )
This produces the following string:
> *** First Items:
>[0]. Task Name:task 1, Created On:2015-03-05 22:25:14, Done: false
>[1]. Task Name:task 2, Created On:2015-03-05 22:25:14, Done: false
>[2]. Task Name:task 3, Created On:2015-03-05 22:25:14, Done: false
I'm trying extract some values from a String. The string contains several lines with values. The values on each line are number, firstname, last name. Then I want to filter by a given pattern and remove the duplicate numbers.
This is my test:
test("Numbers should be unique") {
val s = Cool.prepareListAccordingToPattern(ALLOWED_PATTERN, "1234,örjan,nilsson\n4321,eva-lisa,nyman\n1234,eva,nilsson")
assert(s.length == 2, "Well that didn't work.. ")
info("Chopping seems to work. Filtered duplicate numbers. Expected 1234:4321, got: "+s(0)(0)+":"+s(1)(0))
}
The methods:
def prepareListAccordingToPattern(allowedPattern: String, s: String) : Array[Array[String]] = {
val lines = chop("\n", s)
val choppedUp = lines.map(line =>
chop(",", line)).filter(array =>
array.length == 3 && array(0).matches(allowedPattern)
)
choppedUp
}
def chop(splitSymbol: String, toChop: String) : Array[String] = {
toChop.split(splitSymbol)
}
My test fails as expected since I receive back a multidimensional array with duplicates:
[0]["1234","örjan","nilsson"]
[1]["4321","eva-lisa","nyman"]
[2]["1234","eva","nilsson"]
What I would like to do is to filter out the duplicated numbers, in this case "1234"
so that I get back:
[0]["1234","örjan","nilsson"]
[1]["4321","eva-lisa","nyman"]
How should I do this in a scala way? Maybe I could attack this problem differently?
val arr = Array(
Array("1234","rjan","nilsson"),
Array("4321","eva-lisa","nyman"),
Array("1234","eva","nilsson")
)
arr.groupBy( _(0)).map{ case (_, vs) => vs.head}.toArray
// Array(Array(1234, rjan, nilsson), Array(4321, eva-lisa, nyman))
If you have a collection of elements (in this case Array of Array[String]) and want to get single element with every value of some property (in this case property is the first string from Array[String]) you should group elements of collection based on this property (arr.groupBy( _(0))) and then somehow select one element from every group. In this case we picked up the first element (Array[String]) from every group.
If you want to select any (not necessary first) element for every group you could convert every element (Array[String]) to the key-value pair ((String, Array[String])) where key is the value of target property, and then convert this collection of pairs to Map:
val myMap = arr.map{ a => a(0) -> a }.toMap
// Map(1234 -> Array(1234, eva, nilsson), 4321 -> Array(4321, eva-lisa, nyman))
myMap.values.toArray
// Array(Array(1234, eva, nilsson), Array(4321, eva-lisa, nyman))
In this case you'll get the last element from every group.
A bit implicit, but should work:
val arr = Array(
Array("1234","rjan","nilsson"),
Array("4321","eva-lisa","nyman"),
Array("1234","eva","nilsson")
)
arr.view.reverse.map(x => x.head -> x).toMap.values
// Iterable[Array[String]] = MapLike(Array(1234, rjan, nilsson), Array(4321, eva-lisa, nyman))
Reverse here to override "eva","nilsson" with "rjan","nilsson", not vice versa