package mlrval import ( "bytes" "fmt" "slices" "strconv" "github.com/johnkerl/miller/v6/pkg/lib" ) // IsEmpty determines if a map is empty. func (mlrmap *Mlrmap) IsEmpty() bool { return mlrmap.Head == nil } func (mlrmap *Mlrmap) Has(key string) bool { return mlrmap.findEntry(key) != nil } func (mlrmap *Mlrmap) Get(key string) *Mlrval { pe := mlrmap.findEntry(key) if pe == nil { return nil } return pe.Value } // PutReference copies the key but not the value. This is not safe for DSL use, // where we could create undesired references between different objects. Only // intended to be used at callsites which allocate a mlrval on the spot, solely // for the purpose of putting into the map. func (mlrmap *Mlrmap) PutReference(key string, value *Mlrval) { pe := mlrmap.findEntry(key) if pe == nil { mlrmap.putReferenceNewAux(key, value) } else { pe.Value = value } } // putReferenceNewAux is a helper function for code shared between PutReference // and PutReferenceMaybeDedupe. It should not be invoked from anywhere else -- // it doesn't do its own check if the key already exists in the record or not. func (mlrmap *Mlrmap) putReferenceNewAux(key string, value *Mlrval) { mlrmap.linkNewEntry(newMlrmapEntry(key, value)) } // linkNewEntry appends an already-constructed entry to the tail of the list and // updates the index if present. The entry must not already be in the map. It is // shared by the normal put path and by the batch-arena reader fast path. func (mlrmap *Mlrmap) linkNewEntry(pe *MlrmapEntry) { if mlrmap.Head == nil { mlrmap.Head = pe mlrmap.Tail = pe } else { pe.Prev = mlrmap.Tail pe.Next = nil mlrmap.Tail.Next = pe mlrmap.Tail = pe } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[pe.Key] = pe } mlrmap.FieldCount++ } // PutReferenceMaybeDedupe is the default inserter for key-value pairs in input records -- // if the input is 'x=8,x=9` then we make a record with x=8 and x_2=9. This can be suppressed // via a command-line flag which this method's dedupe flag respects. func (mlrmap *Mlrmap) PutReferenceMaybeDedupe(key string, value *Mlrval, dedupe bool) (string, error) { if !dedupe { mlrmap.PutReference(key, value) return key, nil } pe := mlrmap.findEntry(key) if pe == nil { mlrmap.putReferenceNewAux(key, value) return key, nil } for i := 2; ; i++ { newKey := key + "_" + strconv.Itoa(i) pe := mlrmap.findEntry(newKey) if pe == nil { mlrmap.putReferenceNewAux(newKey, value) return newKey, nil } } } // PutCopy copies the key and value (deep-copying in case the value is array/map). // This is safe for DSL use. See also PutReference. func (mlrmap *Mlrmap) PutCopy(key string, value *Mlrval) { pe := mlrmap.findEntry(key) if pe == nil { pe = newMlrmapEntry(key, value.Copy()) if mlrmap.Head == nil { mlrmap.Head = pe mlrmap.Tail = pe } else { pe.Prev = mlrmap.Tail pe.Next = nil mlrmap.Tail.Next = pe mlrmap.Tail = pe } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[key] = pe } mlrmap.FieldCount++ } else { pe.Value = value.Copy() } } // PrependReference is the same as PutReference, but puts a new entry first, not last. func (mlrmap *Mlrmap) PrependReference(key string, value *Mlrval) { pe := mlrmap.findEntry(key) if pe == nil { pe = newMlrmapEntry(key, value) if mlrmap.Tail == nil { mlrmap.Head = pe mlrmap.Tail = pe } else { pe.Prev = nil pe.Next = mlrmap.Head mlrmap.Head.Prev = pe mlrmap.Head = pe } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[key] = pe } mlrmap.FieldCount++ } else { pe.Value = value } } // TODO: COMMENT func (mlrmap *Mlrmap) PutReferenceAfter( pe *MlrmapEntry, key string, value *Mlrval, ) *MlrmapEntry { if pe == nil || pe.Next == nil { // New entry is supposed to go 'after' old, but there is no such old // entry. Or, the old entry exists and is at the tail. In either case, // add the new entry at the end of the record (new tail). // TODO: make a helper method for code-dedupe pf := newMlrmapEntry(key, value) if mlrmap.Head == nil { // First entry into empty map mlrmap.Head = pf mlrmap.Tail = pf } else { // Before: ... pc pd // After: ... pc pd pf pf.Prev = mlrmap.Tail pf.Next = nil mlrmap.Tail.Next = pf mlrmap.Tail = pf } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[key] = pf } mlrmap.FieldCount++ return pf } // Before: ... pe pg ... // After: ... pe pf pg ... // // New entry is neither the new head (pe != nil) nor the new tail // (pe.Next != nil, otherwise we'd be in the if-branch above). pf := newMlrmapEntry(key, value) pg := pe.Next pe.Next = pf pf.Next = pg pf.Prev = pe if pg != nil { pg.Prev = pf } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[key] = pf } mlrmap.FieldCount++ return pf } // findEntry is the basic hash-map accessor for Has, Put, Get, Remove, etc. // Hashmaps in Go are a built-in type; Mlrmap is (a) // insertion-order-preserving, and (b) supports either hashed or non-hashed // access. func (mlrmap *Mlrmap) findEntry(key string) *MlrmapEntry { if mlrmap.keysToEntries != nil { return mlrmap.keysToEntries[key] } // Lazily build the key-to-entry index when a lookup happens on a record // wide enough to benefit. Narrow records (the common case) and records // that are never looked up stay on the linear-search path and never pay // for a map. See mlrmapHashThreshold. if mlrmap.autoHash && mlrmap.FieldCount >= mlrmapHashThreshold { mlrmap.buildIndex() return mlrmap.keysToEntries[key] } for pe := mlrmap.Head; pe != nil; pe = pe.Next { if pe.Key == key { return pe } } return nil } // buildIndex populates keysToEntries from the linked list. Once built, all // mutators keep it in sync (each guards on keysToEntries != nil). On duplicate // keys the last entry wins, matching the linear-search semantics of findEntry // (which returns the first match), since records are deduped on insert. func (mlrmap *Mlrmap) buildIndex() { m := make(map[string]*MlrmapEntry, mlrmap.FieldCount) for pe := mlrmap.Head; pe != nil; pe = pe.Next { if _, ok := m[pe.Key]; !ok { m[pe.Key] = pe } } mlrmap.keysToEntries = m } // findEntryByPositionalIndex is for '$[1]' etc. in the DSL. // // Notes: // - This is a linear search. // - Indices are 1-up not 0-up // - Indices -n..-1 are aliases for 1..n. In particular, it will be faster to // get the -1st field than the nth. // - Returns 0 on invalid index: 0, or < -n, or > n where n is the number of // fields. func (mlrmap *Mlrmap) findEntryByPositionalIndex(position int64) *MlrmapEntry { if position > mlrmap.FieldCount || position < -mlrmap.FieldCount || position == 0 { return nil } if position > 0 { var i int64 = 1 for pe := mlrmap.Head; pe != nil; pe = pe.Next { if i == position { return pe } i++ } lib.InternalCodingErrorIf(true) } else { var i int64 = -1 for pe := mlrmap.Tail; pe != nil; pe = pe.Prev { if i == position { return pe } i-- } lib.InternalCodingErrorIf(true) } lib.InternalCodingErrorIf(true) return nil } func (mlrmap *Mlrmap) PutCopyWithMlrvalIndex(key *Mlrval, value *Mlrval) error { // $[3] acts as $["3"] if key.IsStringOrInt() { mlrmap.PutCopy(key.String(), value) return nil } return fmt.Errorf( "record/map indices must be string, int, or array thereof; got %s", key.GetTypeName(), ) } func (mlrmap *Mlrmap) PrependCopy(key string, value *Mlrval) { mlrmap.PrependReference(key, value.Copy()) } // Merges that into mlrmap. func (mlrmap *Mlrmap) Merge(other *Mlrmap) { for pe := other.Head; pe != nil; pe = pe.Next { mlrmap.PutCopy(pe.Key, pe.Value) } } // Exposed for the 'nest' verb func (mlrmap *Mlrmap) GetEntry(key string) *MlrmapEntry { return mlrmap.findEntry(key) } func (mlrmap *Mlrmap) GetKeys() []string { keys := make([]string, mlrmap.FieldCount) i := 0 for pe := mlrmap.Head; pe != nil; pe = pe.Next { keys[i] = pe.Key i++ } return keys } // Returns an array of keys, not including the ones specified. The ones // specified are to be passed in as a map from string to bool, as Go // doesn't have hash-sets. func (mlrmap *Mlrmap) GetKeysExcept(exceptions map[string]bool) []string { keys := []string{} for pe := mlrmap.Head; pe != nil; pe = pe.Next { if _, present := exceptions[pe.Key]; !present { keys = append(keys, pe.Key) } } return keys } // TODO: put error-return into this API func (mlrmap *Mlrmap) PutNameWithPositionalIndex(position int64, name *Mlrval) { positionalEntry := mlrmap.findEntryByPositionalIndex(position) if positionalEntry == nil { // TODO: handle out-of-bounds accesses return } s := "" if name.IsString() || name.IsInt() { s = name.String() } else { // TODO: return MlrvalFromError() return } // E.g. there are fields named 'a' and 'b', as positions 1 and 2, // and the user does '$[[1]] = $[[2]]'. Then there would be two b's. mapEntry := mlrmap.findEntry(s) if mapEntry != nil && mapEntry != positionalEntry { if mlrmap.keysToEntries != nil { delete(mlrmap.keysToEntries, positionalEntry.Key) } mlrmap.Unlink(mapEntry) } lib.InternalCodingErrorIf(s == "") positionalEntry.Key = s if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[s] = positionalEntry } } func (mlrmap *Mlrmap) GetWithPositionalIndex(position int64) *Mlrval { mapEntry := mlrmap.findEntryByPositionalIndex(position) if mapEntry == nil { return nil } return mapEntry.Value } func (mlrmap *Mlrmap) GetWithMlrvalIndex(index *Mlrval) (*Mlrval, error) { if index.IsArray() { return mlrmap.getWithMlrvalArrayIndex(index) } return mlrmap.getWithMlrvalSingleIndex(index) } // This lets the user do '$y = $x[ ["a", "b", "c"] ]' in lieu of // '$y = $x["a"]["b"]["c"]'. func (mlrmap *Mlrmap) getWithMlrvalArrayIndex(index *Mlrval) (*Mlrval, error) { current := mlrmap var retval *Mlrval = nil lib.InternalCodingErrorIf(!index.IsArray()) array := index.intf.([]*Mlrval) n := len(array) for i, piece := range array { next, err := current.GetWithMlrvalIndex(piece) if err != nil { return nil, err } if next == nil { return nil, nil } if i < n-1 { if !next.IsMap() { return nil, fmt.Errorf("cannot multi-index non-map") } current = next.intf.(*Mlrmap) } else { retval = next.Copy() } } return retval, nil } func (mlrmap *Mlrmap) getWithMlrvalSingleIndex(index *Mlrval) (*Mlrval, error) { if index.IsString() { return mlrmap.Get(index.printrep), nil } else if index.IsInt() { // $[3] acts as $["3"] return mlrmap.Get(index.String()), nil } return nil, fmt.Errorf( "record/map indices must be string, int, or array thereof; got %s", index.GetTypeName(), ) } // For '$[[1]]' etc. in the DSL. // // Notes: // * This is a linear search. // * Indices are 1-up not 0-up // * Indices -n..-1 are aliases for 1..n. In particular, it will be faster to // get the -1st field than the nth. // * Returns 0 on invalid index: 0, or < -n, or > n where n is the number of // fields. func (mlrmap *Mlrmap) GetNameAtPositionalIndex(position int64) (string, bool) { mapEntry := mlrmap.findEntryByPositionalIndex(position) if mapEntry == nil { return "", false } return mapEntry.Key, true } // Copies the key and value (deep-copying in case the value is array/map). // This is safe for DSL use. See also PutReference. // TODO: put error-return into this API func (mlrmap *Mlrmap) PutCopyWithPositionalIndex(position int64, value *Mlrval) { mapEntry := mlrmap.findEntryByPositionalIndex(position) if mapEntry != nil { mapEntry.Value = value.Copy() } else { return } } func (mlrmap *Mlrmap) RemoveWithPositionalIndex(position int64) { mapEntry := mlrmap.findEntryByPositionalIndex(position) if mapEntry != nil { mlrmap.Unlink(mapEntry) } } func (mlrmap *Mlrmap) Equals(other *Mlrmap) bool { if mlrmap.FieldCount != other.FieldCount { return false } if !mlrmap.Contains(other) { return false } if !other.Contains(mlrmap) { return false } return true } // True if this contains other, i.e. if other is contained by mlrmap. // * If any key of other is not a key of this, return false. // * If any key of other has a value unequal to this' value at the same key, return false. // * Else return true func (mlrmap *Mlrmap) Contains(other *Mlrmap) bool { for pe := other.Head; pe != nil; pe = pe.Next { if !mlrmap.Has(pe.Key) { return false } thisval := mlrmap.Get(pe.Key) thatval := pe.Value if !Equals(thisval, thatval) { return false } } return true } func (mlrmap *Mlrmap) Clear() { mlrmap.FieldCount = 0 // Assuming everything unreferenced is getting GC'ed by the Go runtime mlrmap.Head = nil mlrmap.Tail = nil if mlrmap.keysToEntries != nil { mlrmap.keysToEntries = make(map[string]*MlrmapEntry) } } func (mlrmap *Mlrmap) Copy() *Mlrmap { var other *Mlrmap if mlrmap.autoHash { // Preserve lazy-hashing semantics: don't force an eager index on the // copy just because the source happens to have built one. other = newMlrmapLazyHashed() } else { other = NewMlrmapMaybeHashed(mlrmap.isHashed()) } for pe := mlrmap.Head; pe != nil; pe = pe.Next { other.PutCopy(pe.Key, pe.Value) } return other } // Returns true if it was found and removed func (mlrmap *Mlrmap) Remove(key string) bool { pe := mlrmap.findEntry(key) if pe == nil { return false } mlrmap.Unlink(pe) return true } func (mlrmap *Mlrmap) MoveToHead(key string) { pe := mlrmap.findEntry(key) if pe != nil { mlrmap.Unlink(pe) mlrmap.linkAtHead(pe) } } func (mlrmap *Mlrmap) MoveToTail(key string) { pe := mlrmap.findEntry(key) if pe != nil { mlrmap.Unlink(pe) mlrmap.linkAtTail(pe) } } // E.g. '$name[1]["foo"] = "bar"' or '$*["foo"][1] = "bar"' // In the former case the indices are ["name", 1, "foo"] and in the latter case // the indices are ["foo", 1]. See also indexed-lvalues.md. // // This is a Mlrmap (from string to Mlrval) so we handle the first level of // indexing here, then pass the remaining indices to the Mlrval at the desired // slot. func (mlrmap *Mlrmap) PutIndexed(indices []*Mlrval, rvalue *Mlrval) error { return putIndexedOnMap(mlrmap, indices, rvalue) } func (mlrmap *Mlrmap) RemoveIndexed(indices []*Mlrval) error { return removeIndexedOnMap(mlrmap, indices) } func (mlrmap *Mlrmap) GetKeysJoined() string { var buffer bytes.Buffer i := 0 for pe := mlrmap.Head; pe != nil; pe = pe.Next { if i > 0 { buffer.WriteString(",") } i++ buffer.WriteString(pe.Key) } return buffer.String() } // For mlr reshape func (mlrmap *Mlrmap) GetValuesJoined() string { var buffer bytes.Buffer i := 0 for pe := mlrmap.Head; pe != nil; pe = pe.Next { if i > 0 { buffer.WriteString(",") } i++ buffer.WriteString(pe.Value.String()) } return buffer.String() } // For group-by in several transformers. If the record is 'a=x,b=y,c=3,d=4,e=5' and // selectedFieldNames is 'a,b,c' then values are 'x,y,3'. This is returned as a // comma-joined string. The boolean ok is false if not all selected field // names were present in the record. // // It's OK for the selected-field-namees list to be empty. This happens for // transformers which support a -g option but are invoked without it (e.g. 'mlr tail // -n 1' vs 'mlr tail -n 1 -g a,b,c'). In this case the return value is simply // the empty string. func (mlrmap *Mlrmap) GetSelectedValuesJoined(selectedFieldNames []string) (string, bool) { if len(selectedFieldNames) == 0 { // The fall-through is functionally correct, but this is quicker with // skipping setting up an empty bytes-buffer and stringifying it. The // non-grouped case is quite normal and is worth optimizing for. return "", true } var buffer bytes.Buffer for i, selectedFieldName := range selectedFieldNames { entry := mlrmap.findEntry(selectedFieldName) if entry == nil { return "", false } if i > 0 { buffer.WriteString(",") } // This may be an array or map, or just a string/int/etc. Regardless we // stringify it. buffer.WriteString(entry.Value.String()) } return buffer.String(), true } // As with GetSelectedValuesJoined but also returning the array of mlrvals. // For sort. // TODO: put 'Copy' into the method name func (mlrmap *Mlrmap) GetSelectedValuesAndJoined(selectedFieldNames []string) ( string, []*Mlrval, bool, ) { mlrvals := make([]*Mlrval, 0, len(selectedFieldNames)) if len(selectedFieldNames) == 0 { // The fall-through is functionally correct, but this is quicker with // skipping setting up an empty bytes-buffer and stringifying it. The // non-grouped case is quite normal and is worth optimizing for. return "", mlrvals, true } var buffer bytes.Buffer for i, selectedFieldName := range selectedFieldNames { entry := mlrmap.findEntry(selectedFieldName) if entry == nil { return "", mlrvals, false } if i > 0 { buffer.WriteString(",") } // This may be an array or map, or just a string/int/etc. Regardless we // stringify it. buffer.WriteString(entry.Value.String()) mlrvals = append(mlrvals, entry.Value.Copy()) } return buffer.String(), mlrvals, true } // As above but only returns the array. Also, these are references, NOT copies. // For step and join. func (mlrmap *Mlrmap) ReferenceSelectedValues(selectedFieldNames []string) ([]*Mlrval, bool) { allFound := true mlrvals := make([]*Mlrval, 0, len(selectedFieldNames)) for _, selectedFieldName := range selectedFieldNames { entry := mlrmap.findEntry(selectedFieldName) if entry != nil { mlrvals = append(mlrvals, entry.Value) } else { mlrvals = append(mlrvals, nil) allFound = false } } return mlrvals, allFound } // TODO: rename to CopySelectedValues // As previous but with copying. For stats1. func (mlrmap *Mlrmap) GetSelectedValues(selectedFieldNames []string) ([]*Mlrval, bool) { allFound := true mlrvals := make([]*Mlrval, 0, len(selectedFieldNames)) for _, selectedFieldName := range selectedFieldNames { entry := mlrmap.findEntry(selectedFieldName) if entry != nil { mlrvals = append(mlrvals, entry.Value.Copy()) } else { mlrvals = append(mlrvals, nil) allFound = false } } return mlrvals, allFound } // Similar to the above but only checks availability. For join. func (mlrmap *Mlrmap) HasSelectedKeys(selectedFieldNames []string) bool { for _, selectedFieldName := range selectedFieldNames { entry := mlrmap.findEntry(selectedFieldName) if entry == nil { return false } } return true } // For mlr nest implode across records. func (mlrmap *Mlrmap) GetKeysJoinedExcept(px *MlrmapEntry) string { var buffer bytes.Buffer i := 0 for pe := mlrmap.Head; pe != nil; pe = pe.Next { if pe == px { continue } if i > 0 { buffer.WriteString(",") } i++ buffer.WriteString(pe.Key) } return buffer.String() } // For mlr nest implode across records. func (mlrmap *Mlrmap) GetValuesJoinedExcept(px *MlrmapEntry) string { var buffer bytes.Buffer i := 0 for pe := mlrmap.Head; pe != nil; pe = pe.Next { if pe == px { continue } if i > 0 { buffer.WriteString(",") } i++ // This may be an array or map, or just a string/int/etc. Regardless we // stringify it. buffer.WriteString(pe.Value.String()) } return buffer.String() } func (mlrmap *Mlrmap) Rename(oldKey string, newKey string) bool { entry := mlrmap.findEntry(oldKey) if entry == nil { // Rename field from 'a' to 'b' where there is no 'a': no-op return false } existing := mlrmap.findEntry(newKey) if existing == nil { // Rename field from 'a' to 'b' where there is no 'b': simple update entry.Key = newKey if mlrmap.keysToEntries != nil { delete(mlrmap.keysToEntries, oldKey) mlrmap.keysToEntries[newKey] = entry } } else { // Rename field from 'a' to 'b' where there are both 'a' and 'b': // remove old 'a' and put its value into the slot of 'b'. existing.Value = entry.Value if mlrmap.keysToEntries != nil { delete(mlrmap.keysToEntries, oldKey) } mlrmap.Unlink(entry) } return true } func (mlrmap *Mlrmap) Label(newNames []string) { other := NewMlrmapAsRecord() i := 0 numNewNames := len(newNames) for i < numNewNames { pe := mlrmap.pop() if pe == nil { break } // Old record will be GC'ed: just move pointers other.PutReference(newNames[i], pe.Value) i++ } for { pe := mlrmap.pop() if pe == nil { break } // Example: // * Input record has keys a,b,i,x,y // * Requested labeling is d,x,f // * The first three records a,b,i should be renamed to d,x,f // * The old x needs to disappear (for key-uniqueness) // * The y field is carried through if other.Has(pe.Key) { continue } other.PutReference(pe.Key, pe.Value) } *mlrmap = *other } func (mlrmap *Mlrmap) SortByKey() { keys := mlrmap.GetKeys() slices.Sort(keys) other := NewMlrmapAsRecord() for _, key := range keys { // Old record will be GC'ed: just move pointers other.PutReference(key, mlrmap.Get(key)) } *mlrmap = *other } func (mlrmap *Mlrmap) SortByKeyRecursively() { keys := mlrmap.GetKeys() slices.Sort(keys) other := NewMlrmapAsRecord() for _, key := range keys { // Old record will be GC'ed: just move pointers val := mlrmap.Get(key) if val.IsMap() { val.intf.(*Mlrmap).SortByKeyRecursively() } other.PutReference(key, val) } *mlrmap = *other } // Only checks to see if the first entry is a map. For emit/emitp. func (mlrmap *Mlrmap) IsNested() bool { if mlrmap.Head == nil { return false } else if mlrmap.Head.Value.GetMap() == nil { //TODO: check IsArrayOrMap() return false } return true } // PRIVATE METHODS func (mlrmap *Mlrmap) Unlink(pe *MlrmapEntry) { if pe == mlrmap.Head { if pe == mlrmap.Tail { mlrmap.Head = nil mlrmap.Tail = nil } else { mlrmap.Head = pe.Next pe.Next.Prev = nil } } else { pe.Prev.Next = pe.Next if pe == mlrmap.Tail { mlrmap.Tail = pe.Prev } else { pe.Next.Prev = pe.Prev } } if mlrmap.keysToEntries != nil { delete(mlrmap.keysToEntries, pe.Key) } mlrmap.FieldCount-- } // Does not check for duplicate keys func (mlrmap *Mlrmap) linkAtHead(pe *MlrmapEntry) { if mlrmap.Head == nil { pe.Prev = nil pe.Next = nil mlrmap.Head = pe mlrmap.Tail = pe } else { pe.Prev = nil pe.Next = mlrmap.Head mlrmap.Head.Prev = pe mlrmap.Head = pe } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[pe.Key] = pe } mlrmap.FieldCount++ } // Does not check for duplicate keys func (mlrmap *Mlrmap) linkAtTail(pe *MlrmapEntry) { if mlrmap.Head == nil { pe.Prev = nil pe.Next = nil mlrmap.Head = pe mlrmap.Tail = pe } else { pe.Prev = mlrmap.Tail pe.Next = nil mlrmap.Tail.Next = pe mlrmap.Tail = pe } if mlrmap.keysToEntries != nil { mlrmap.keysToEntries[pe.Key] = pe } mlrmap.FieldCount++ } func (mlrmap *Mlrmap) pop() *MlrmapEntry { if mlrmap.Head == nil { return nil } pe := mlrmap.Head mlrmap.Unlink(pe) return pe } // ToPairsArray is used for sorting maps by key/value/etc, e.g. the sortmf DSL function. func (mlrmap *Mlrmap) ToPairsArray() []MlrmapPair { pairsArray := make([]MlrmapPair, mlrmap.FieldCount) i := 0 for pe := mlrmap.Head; pe != nil; pe = pe.Next { pairsArray[i].Key = pe.Key pairsArray[i].Value = pe.Value.Copy() i++ } return pairsArray } // MlrmapFromPairsArray is used for sorting maps by key/value/etc, e.g. the sortmf DSL function. func MlrmapFromPairsArray(pairsArray []MlrmapPair) *Mlrmap { mlrmap := NewMlrmap() for i := range pairsArray { mlrmap.PutCopy(pairsArray[i].Key, pairsArray[i].Value) } return mlrmap } // GetFirstPair returns the first key-value pair as its own map. If the map is // empty (i.e. there is no first pair) it returns nil. func (mlrmap *Mlrmap) GetFirstPair() *Mlrmap { if mlrmap.Head == nil { return nil } pair := NewMlrmap() pair.PutCopy(mlrmap.Head.Key, mlrmap.Head.Value) return pair }